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 remove_signup(member: Member, signupmessage, mobile): signup: db.Signup = db.Signup.query().filter_by( signup_id=signupmessage.id, player_id=member.id, mobile=mobile).first() if signup: signup.delete() await member.send( f'You have been removed from the list of players for next week\'s **{"mobile" if mobile else "steam"}**' f' games. You can sign back up by reacting to the signup message again.' ) logger.debug( f'{member.name} removed from signups for the {"steam" if not mobile else "mobile"} ' f'matchups of signup id {signupmessage.id}')
async def signups(self, ctx: commands.Context, ping: str = None): """ Open or close signups manually. - [p]open_signups - open signups - [p]close_signups - close signups """ if ctx.invoked_with == 'close_signups': # Make sure there are actually signups open. if not db.SignupMessage.query().filter_by(is_open=True).first(): return await ctx.send( 'Signups are not open, and therefore cannot be closed.') logger.debug( f'Close signups triggered manually by {ctx.author.name}/{ctx.author.id}' ) await self.close_signups(manual=True) return await ctx.send( f'Signups manually closed by {ctx.author.mention}') elif ctx.invoked_with == 'open_signups': # Make sure there aren't signups already open if db.SignupMessage.query().filter_by(is_open=True).first(): return await ctx.send( 'Signups are already open, and more cannot be opened.') logger.debug( f'Open signups triggered manually by {ctx.author.name}/{ctx.author.id}' ) await self.open_signups(manual=True, ping=ping) return await ctx.send( f'Signups manually opened by {ctx.author.mention}') else: await ctx.send(f'Run `{ctx.prefix}help signups` please.')
async def check_rungs(self, ctx: commands.Context, member: discord.Member = None): await ctx.send( 'Checking that all rung changes have been applied correctly.') logger.debug('Checking rungs...') done = 0 for player in (db.Player.query() if not member else db.Player.query().filter_by(id=member.id)): player: db.Player games = db.Game.query().filter( db.or_(db.Game.host_id == player.id, db.Game.away_id == player.id) & db.Game.is_confirmed.is_(True)).order_by( db.Game.win_claimed_ts.asc()) player_rung = 1 msg = f'{player.name}: {player_rung}' for game in games: game: db.Game logger.debug( f'{player.name} ({player.id}) --- game {game.id} confirmed status {game.is_confirmed}, ' f'with {game.host_step_change} for the host, and {game.away_step_change} for away.' ) if game.host_id == player.id: n = game.host_step_change else: n = game.away_step_change msg += f'+{n}' player_rung += n if player_rung < 1: player_rung = 1 elif player_rung > 12: player_rung = 12 await ctx.send( f'Rung calculation for {msg} = {player_rung}. Actual rung = {player.rung}' ) if is_bad := (player_rung != player.rung): await ctx.send( f'Player {player.name} ({player.id}) had rung {player.rung} in the database, ' f'but should have had {player_rung}. Fixing...') db.GameLog.write( f'{db.GameLog.member_string(player)} - rung changed to {player_rung} from {player.rung} as part of ' f'a rung check.') player.rung = player_rung player.save() if (m := ctx.guild.get_member(player.id)) and is_bad: self.bot.loop.create_task(settings.fix_roles(m)) done += 1
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()
class League(commands.Cog): def __init__(self, bot, conf): self.bot = bot self.conf = conf self.message_id = None self.relevant_emojis = [ settings.emojis.blue_check_mark, settings.emojis.white_check_mark ] self.signup_loop.start() def cog_unload(self): self.signup_loop.cancel() @commands.Cog.listener() async def on_raw_reaction_add(self, payload: RawReactionActionEvent): signupmessage: db.SignupMessage = db.SignupMessage.query().filter( db.SignupMessage.is_open.is_(True)).first() if self.message_id != getattr(signupmessage, 'message_id', self.message_id): self.message_id = signupmessage.message_id if payload.message_id != self.message_id: return if payload.user_id == self.bot.user.id: return channel = payload.member.guild.get_channel(payload.channel_id) message: Message = await channel.fetch_message(payload.message_id) emoji = self.bot.get_emoji( payload.emoji.id) if payload.emoji.id else payload.emoji if emoji.name not in self.relevant_emojis: await message.remove_reaction(emoji, payload.member) if emoji.name == settings.emojis.white_check_mark: await self.add_signup(payload.member, signupmessage, message, emoji, mobile=True) elif emoji.name == settings.emojis.blue_check_mark: await self.add_signup(payload.member, signupmessage, message, emoji, mobile=False) @commands.Cog.listener() async def on_raw_reaction_remove(self, payload: RawReactionActionEvent): msg: db.SignupMessage = db.SignupMessage.query().filter( db.SignupMessage.is_open.is_(True)).first() if self.message_id != getattr(msg, 'message_id', self.message_id): self.message_id = msg.message_id if payload.message_id != self.message_id: return if payload.user_id == self.bot.user.id: return if payload.emoji.name not in self.relevant_emojis: return member = self.bot.get_user(payload.user_id) if payload.emoji.name == settings.emojis.white_check_mark: await self.remove_signup(member, msg, mobile=True) elif payload.emoji.name == settings.emojis.blue_check_mark: await self.remove_signup(member, msg, mobile=False) @staticmethod async def add_signup(member: Member, signupmessage, message: Message, emoji, mobile): p: db.Player = db.Player.get(member.id) if not p: await message.remove_reaction(emoji, member) return await member.send( f'You must be registered with me to signup for matches.') elif p.ign is None and mobile: await message.remove_reaction(emoji, member) return await member.send(f'You have not set your mobile name.') elif p.steam_name is None and not mobile: await message.remove_reaction(emoji, member) return await member.send(f'You have not set your steam name.') if (s := db.Signup.query().filter( db.Signup.player_id == member.id).first()) is not None: await message.remove_reaction(emoji, member) platform_str = 'mobile' if s.mobile else 'steam' return await member.send( f'You are already signed up for {platform_str}. You cannot signup for both platforms.' ) signup = db.Signup(signup_id=signupmessage.id, player_id=member.id, mobile=mobile) signup.save() await member.send( f'You are now signed up for next week\'s **{"mobile" if mobile else "steam"}** games. ' f'If you would like to remove yourself, just remove the reaction you just placed.' ) logger.debug( f'{member.name} signed up for {"steam" if not mobile else "mobile"} matchups, signupmessage id ' f'{signupmessage.id}')