async def delete(self, ctx: commands.Context, game: db.Game, *, args: str = None): """*Owner*: delete an in progress/yet to be started game""" game: db.Game args = args or '' if not game: return await ctx.send('Game ID not provided.') if game.is_complete and '-override' not in args.lower(): return await ctx.send(f'Please unwin the game first.') db.GameLog.write( game_id=game.id, message= f'{db.GameLog.member_string(ctx.author)} manually deleted the game.' ) db.delete(game) logger.info(f'Game {game.id} deleted.') await ctx.send(f'Game {game.id} deleted.')
async def close_signups(self, manual=False): signup_message: db.SignupMessage = db.SignupMessage.query().filter_by( is_open=True).first() channel: TextChannel = self.bot.get_channel( int(self.conf['channels']['announcements'])) logger.debug(signup_message.message_id) msg = await channel.fetch_message(signup_message.message_id) await msg.edit(content=settings.messages.SIGNUPS_CLOSED_MESSAGE.format( msg.content)) await msg.clear_reactions() signup_message.is_open = False signup_message.save() self.message_id = None logger.info( f'Signups have been {"automatically " if not manual else ""}' f'closed.') if not manual: try: await self.create_matchups() except Exception as e: await settings.discord_channel_log( f'Unhandled error (notifying <@{settings.owner_id}>): {e}')
async def open_signups(self, manual=False, ping: str = None): if ping == 'noping': ping = '' else: ping = ping or '@everyone' # Get the channel announcements: TextChannel = self.bot.get_channel( int(self.conf['channels']['announcements'])) # Send the message, add the reactions msg = await announcements.send( settings.messages.SIGNUP_MESSAGE.format( ping if ping != '' else 'everyone'), allowed_mentions=AllowedMentions(everyone=True)) await msg.add_reaction(settings.emojis.white_check_mark) await msg.add_reaction(settings.emojis.blue_check_mark) day = settings.next_day(0) db.SignupMessage(message_id=msg.id, is_open=True, close_at=day).save() self.message_id = msg.id logger.info( f'Signups have been {"automatically " if not manual else ""}' f'opened. They will close at {day}, UTC time.')
async def remove_random(member_list: List, platform): pl = member_list.pop(random.randint(0, len(member_list) - 1)) m: User = self.bot.get_user(pl.id) try: await m.send( f'You have been randomly removed from the {platform} matchups for this week\'s PolyLadder ' f'games. Sorry!') except discord.Forbidden: pass logger.info( f'{m.name}#{m.discriminator}/{m.id} kicked from {platform} matches.' )
async def signup_loop(self): signupmessage = db.SignupMessage.query().filter_by( is_open=True).first() if signupmessage: # Signups are open. Check if we can close them. if signupmessage.close_at < datetime.datetime.utcnow(): logger.info('Closing signups.') await self.close_signups() else: if datetime.datetime.utcnow().weekday() == 5: signupmessages = db.SignupMessage.query().filter( db.SignupMessage.close_at > datetime.datetime.utcnow() ).count() if not signupmessages: # It's saturday, open the signups logger.info('Opening signups') await self.open_signups() else: logger.info( 'Not opening signups, as there is a signupmessage object with a closing date later than now.' )
async def quit(self, ctx: commands.Context): """Stop the bot's process.""" db.session.close() logger.info('Shutting down.') await ctx.send('Shutting down...') await self.bot.close()
async def on_ready(self): s = f'Online. Logged in as {self.bot.user.name}/{self.bot.user.id}, PID {os.getpid()}' print(s) logger.info(s)
async def create_matchups(self): logger.debug(f'Generating matchups') mobile_signups = db.Signup.query().filter( db.Signup.mobile.is_(True), db.Signup.player_id.isnot(None)) steam_signups = db.Signup.query().filter( db.Signup.mobile.is_(False), db.Signup.player_id.isnot(None)) if not mobile_signups.count() and not steam_signups.count(): return logger.info('Not creating matchups - no one has signed up.') elif not mobile_signups.count(): logger.info('Not creating matches for mobile - no signups') elif not steam_signups.count(): logger.info('Not creating matches for steam - no signups') # First of all, get all the players. mobile_players = list( filter(lambda x: x.user is not None, [x.player for x in mobile_signups.all()])) steam_players = list( filter(lambda x: x.user is not None, [x.player for x in steam_signups.all()])) mobile_players_even = len(mobile_players) % 2 == 0 steam_players_even = len(steam_players) % 2 == 0 if not mobile_players_even or not steam_players_even: async def remove_random(member_list: List, platform): pl = member_list.pop(random.randint(0, len(member_list) - 1)) m: User = self.bot.get_user(pl.id) try: await m.send( f'You have been randomly removed from the {platform} matchups for this week\'s PolyLadder ' f'games. Sorry!') except discord.Forbidden: pass logger.info( f'{m.name}#{m.discriminator}/{m.id} kicked from {platform} matches.' ) # We don't have an even number of players. Kick one of them. duplicates = set(mobile_players) & set(steam_players) if not duplicates: # No players have signed up for both steam and mobile pass if not mobile_players_even: await remove_random(mobile_players, 'mobile') if not steam_players_even: await remove_random(steam_players, 'steam') else: # There are duplicate players - player's who have signed up for both platforms. if len(duplicates) > 1: if not mobile_players_even: # mobile p = duplicates.pop() member = self.bot.get_user(p.id) await member.send( 'You have been removed from mobile matchups for this week\'s PolyLadder matches, ' 'due to player limits and to you signing up for both steam and mobile.' ) mobile_players.remove(p) logger.info( f'{member.name}#{member.discriminator}/{member.id} kicked from mobile matches.' ) if not steam_players_even: # steam p = duplicates.pop() member = self.bot.get_user(p.id) await member.send( 'You have been removed from steam matchups for this week\'s PolyLadder matches, ' 'due to player limits and to you signing up for both steam and mobile.' ) steam_players.remove(p) logger.info( f'{member.name}#{member.discriminator}/{member.id} kicked from steam matches.' ) else: if not mobile_players_even: # Only 1 duplicate p = duplicates.pop() member = self.bot.get_user(p.id) await member.send( 'You have been removed from mobile matchups for this week\'s PolyLadder matches, ' 'due to player limits and to you signing up for both steam and mobile.' ) mobile_players.remove([p, member]) logger.info( f'{member.name}#{member.discriminator}/{member.id} kicked from mobile matches.' ) if not steam_players_even: await remove_random(steam_players, 'steam') # Load the discord member objects mobile: List[List[db.Player, Member]] = list() steam: List[List[db.Player, Member]] = list() for player in mobile_players: user = self.bot.get_user(player.id) if player is None or user is None: continue mobile.append([player, user]) for player in steam_players: user = self.bot.get_user(player.id) if player is None or user is None: continue steam.append([player, user]) # Now that we have an even number of players in each tier, start generating matchups # Create the tier objects mobile_tiers: Dict[int, list] = {x: [] for x in range(1, 13)} steam_tiers: Dict[int, list] = {x: [] for x in range(1, 13)} # Place people in their initial rungs. for player, member in mobile: mobile_tiers[player.rung].append((player, member)) for player, member in steam: steam_tiers[player.rung].append((player, member)) # Iterate through each rung, from the bottom up, and move people up if there's an odd # number of people in that rung. for tiers in [mobile_tiers, steam_tiers]: for r in range(1, 13): rung = tiers[r] if len(rung) % 2 == 0: continue rung.sort(key=lambda x: x[0].wins().count()) tiers[r + 1].append(rung.pop(0)) # Rungs are sorted, create the matchups mobile_games = self.make_games(mobile_tiers, True) steam_games = self.make_games(steam_tiers, False) platform_msg_source = Template(""" **{{platform}}**:\n {% for tier, games in gms.items() if games is not none %} Tier {{tier}} matchups: {% for game in games %} <@{{game.host.id}}> ({{game.host_step}}) hosts vs <@{{game.away_id}}> ({{game.away_step}}) - Game {{game.id}} {% endfor %} {% endfor %} """) if mobile_games or steam_games: chan: TextChannel = self.bot.get_channel( int(self.conf['channels']['matchups'])) tribe_tier = random.randint(1, 3) await chan.send( f'A new week of games has been generated!\nWe will be using **Level {tribe_tier}** tribes this week.\n' f'Here are your games:') if mobile_games: message = f'\n\n{platform_msg_source.render(gms=mobile_games, platform="Mobile")}' for block in settings.split_string(message): await chan.send(block) if steam_games: message = f'\n\n{platform_msg_source.render(gms=steam_games, platform="Steam")}' for block in settings.split_string(message): await chan.send(block) await chan.send( '\n\nPlease create your games as soon as possible. ' 'If your game has not $started in the next 72 hours (3 days), ' 'the away player will become the host. If the new host does not ' 'start the game 72 hours after that, the game will be cancelled.' ) mobile_signups.delete() steam_signups.delete() db.save()