async def on_command_error(self, ctx, error): """ Send help message when a mis-entered command is received. """ if type(error) is commands.CommandNotFound: # Get Levenshtein distance from commands in_cmd = ctx.invoked_with bot_cmds = list(self.bot.commands) lev_dists = [ lev.distance(in_cmd, str(cmd)) / max(len(in_cmd), len(str(cmd))) for cmd in bot_cmds ] lev_min = min(lev_dists) # Prep help message title embed_title = translate('command-not-valid', ctx.message.content) prefixes = self.bot.command_prefix prefix = prefixes[ 0] if prefixes is not str else prefixes # Prefix can be string or iterable of strings # Make suggestion if lowest Levenshtein distance is under threshold if lev_min <= 0.5: embed_title += translate( 'did-you-mean' ) + f' `{prefix}{bot_cmds[lev_dists.index(lev_min)]}`?' else: embed_title += translate('use-help', prefix) embed = self.bot.embed_template(title=embed_title) await ctx.send(embed=embed)
async def captains(self, ctx, method=None): """ Set or display the method by which captains are selected. """ if not await self.bot.is_pug_channel(ctx): return guild_data = await self.bot.db_helper.get_pug(ctx.channel.category_id) captain_method = guild_data['captain_method'] valid_methods = ['volunteer', 'rank', 'random'] if method is None: title = translate('captains-method', captain_method) else: method = method.lower() if method == captain_method: title = translate('captains-method-already', captain_method) elif method in valid_methods: title = translate('set-captains-method', method) await self.bot.db_helper.update_pug(ctx.channel.category_id, captain_method=method) else: title = translate('captains-valid-method', valid_methods[0], valid_methods[1], valid_methods[2]) embed = self.bot.embed_template(title=title) await ctx.send(embed=embed)
async def maps(self, ctx, method=None): """ Set or display the method by which the teams are created. """ if not await self.bot.is_pug_channel(ctx): return map_method = await self.bot.get_pug_data(ctx.channel.category, 'map_method') valid_methods = ['captains', 'vote', 'random'] if method is None: title = translate('map-method', map_method) else: method = method.lower() if method == map_method: title = translate('map-method-already', map_method) elif method in valid_methods: title = translate('set-map-method', method) await self.bot.db_helper.update_pug(ctx.channel.category_id, map_method=method) else: title = translate('map-valid-method', valid_methods[0], valid_methods[1], valid_methods[2]) embed = self.bot.embed_template(title=title) await ctx.send(embed=embed)
async def link(self, ctx): """ Link a player by sending them a link to sign in with steam on the backend. """ if not await self.bot.is_pug_channel(ctx): return is_linked = await self.bot.api_helper.is_linked(ctx.author.id) if is_linked: player = await self.bot.api_helper.get_player(ctx.author.id) title = translate('already-linked', player.steam_profile) else: link = await self.bot.api_helper.generate_link_url(ctx.author.id) if link: # Send the author a DM containing this link try: await ctx.author.send(translate('dm-link', link)) title = translate('link-sent') except: title = translate('blocked-dm') else: title = translate('unknown-error') embed = self.bot.embed_template(description=title) await ctx.send(content=ctx.author.mention, embed=embed)
def _vote_embed(self): embed = self.bot.embed_template(title=translate('vote-map-started')) str_value = '--------------------\n' str_value += '\n'.join( f'{EMOJI_NUMBERS[self.map_votes[m.emoji]]} {m.emoji} {m.name} ' f'{":small_orange_diamond:" if self.map_votes[m.emoji] == max(self.map_votes.values()) and self.map_votes[m.emoji] != 0 else ""} ' for m in self.map_pool) embed.add_field(name=f':repeat_one: :map: __{translate("maps")}__', value=str_value) embed.set_footer(text=translate('vote-map-footer')) return embed
def _vote_embed(self): embed = self.bot.embed_template( title='Captains vote for number of maps') str_value = '------------\n' str_value += '\n'.join( f'{num} Bo{self.numbers.index(num) + 1}' f'{":small_orange_diamond:" if self.num_votes[num] == max(self.num_votes.values()) and self.num_votes[num] != 0 else ""} ' for num in self.numbers) embed.add_field(name=f':repeat_one: __' + translate("match-type") + '__', value=str_value) embed.set_footer(text=translate('vote-match-type-footer')) return embed
async def forcelink(self, ctx, *args): """ Force link player with steam on the backend. """ if not await self.bot.is_pug_channel(ctx): return print(int(args[1]), type(int(args[1]))) try: user = ctx.message.mentions[0] except IndexError: title = f"{translate('invalid-usage')}: `{self.bot.command_prefix[0]}forcelink <mention> <Steam64 ID>`" else: link = await self.bot.api_helper.force_link_discord( user.id, args[1]) if not link: title = 'Sorry! Steam ID is already linked with another discord' else: title = translate('force-linked', user.display_name, args[1]) role_id = await self.bot.get_pug_data(ctx.channel.category, 'pug_role') role = ctx.guild.get_role(role_id) await user.add_roles(role) await self.bot.api_helper.update_discord_name(user) embed = self.bot.embed_template(title=title) await ctx.send(embed=embed)
async def autobalance_teams(self, members): """ Balance teams based on players' RankMe score. """ # Only balance teams with even amounts of players if len(members) % 2 != 0: raise ValueError(translate('members-must-even')) # Get players and sort by RankMe score members_dict = dict( zip( await self.bot.api_helper.get_players( [member.id for member in members]), members)) players = list(members_dict.keys()) players.sort(key=lambda x: x.score) # Balance teams team_size = len(players) // 2 team_one = [players.pop()] team_two = [players.pop()] while players: if len(team_one) >= team_size: team_two.append(players.pop()) elif len(team_two) >= team_size: team_one.append(players.pop()) elif sum(p.score for p in team_one) < sum(p.score for p in team_two): team_one.append(players.pop()) else: team_two.append(players.pop()) return list(map(members_dict.get, team_one)), list(map(members_dict.get, team_two))
async def _process_ban(self, reaction, member): """ Handler function for map ban reactions. """ # Check that reaction is on this message if reaction.message.id != self.id or member == self.author: return if member not in self.captains or str(reaction) not in [ m for m in self.maps_left ] or member != self._active_picker: await self.remove_reaction(reaction, member) return # Ban map if the emoji is valid try: map_ban = self.maps_left.pop(str(reaction)) except KeyError: return self.ban_number += 1 # Clear banned map reaction await self.clear_reaction(map_ban.emoji) # Edit message embed = self._veto_embed( translate('user-banned-map', member.display_name, map_ban.name)) await self.edit(embed=embed) # Check if the veto is over if len(self.maps_left) == self.num_maps: if self.future is not None: self.future.set_result(None)
async def stats(self, ctx): """ Send an embed containing stats data parsed from the player object returned from the API. """ if not await self.bot.is_pug_channel(ctx): return try: user = ctx.message.mentions[0] except IndexError: user = ctx.author player = await self.bot.api_helper.get_player(user.id) if player: win_percent_str = f'{player.win_percent * 100:.2f}%' hs_percent_str = f'{player.hs_percent * 100:.2f}%' fb_percent_str = f'{player.first_blood_rate * 100:.2f}%' description = '```ml\n' \ f' {translate("rank-score")}: {player.score:>6} \n' \ f' {translate("matches-played")}: {player.matches_played:>6} \n' \ f' {translate("win-percentage")}: {win_percent_str:>6} \n' \ f' {translate("kd-ratio")}: {player.kd_ratio:>6.2f} \n' \ f' {translate("adr")}: {player.adr:>6.2f} \n' \ f' {translate("hs-percentage")}: {hs_percent_str:>6} \n' \ f' {translate("first-blood-rate")}: {fb_percent_str:>6} ' \ '```' embed = self.bot.embed_template(description=description) embed.set_author(name=user.display_name, url=player.league_profile, icon_url=user.avatar_url_as(size=128)) else: title = translate("cannot-get-stats", ctx.author.display_name) embed = self.bot.embed_template(title=title) await ctx.send(embed=embed)
async def check(self, ctx): if not await self.bot.is_pug_channel(ctx): return if not await self.bot.api_helper.is_linked(ctx.author.id): msg = translate('discord-not-linked') else: role_id = await self.bot.get_pug_data(ctx.channel.category, 'pug_role') role = ctx.guild.get_role(role_id) await ctx.author.add_roles(role) await self.bot.api_helper.update_discord_name(ctx.author) msg = translate('discord-get-role') embed = self.bot.embed_template(description=msg, color=self.bot.color) await ctx.send(content=ctx.author.mention, embed=embed)
async def empty(self, ctx): """ Reset the pug's queue list to empty. """ if not await self.bot.is_pug_channel(ctx): return self.queue_cog.block_lobby[ctx.channel.category] = True await self.bot.db_helper.delete_all_queued_users( ctx.channel.category.id) msg = translate('queue-emptied') embed = await self.queue_cog.queue_embed(ctx.channel.category, msg) lobby_id = await self.bot.get_pug_data(ctx.channel.category, 'voice_lobby') prelobby_id = await self.bot.get_pug_data(ctx.channel.category, 'voice_prelobby') lobby = ctx.bot.get_channel(lobby_id) prelobby = ctx.bot.get_channel(prelobby_id) for player in lobby.members: await player.move_to(prelobby) self.queue_cog.block_lobby[ctx.channel.category] = False _embed = self.bot.embed_template(title=msg) await ctx.send(embed=_embed) # Update queue display message await self.queue_cog.update_last_msg(ctx.channel.category, embed)
def _ready_embed(self): """ Generate the menu embed based on the current ready status of players. """ str_value = '' description = translate('react-ready', '✅') embed = self.bot.embed_template(title=translate('queue-filled'), description=description) for num, member in enumerate(self.members, start=1): if member not in self.reactors: str_value += f':heavy_multiplication_x: {num}. [{member.display_name}]({self.players[num-1].league_profile})\n ' else: str_value += f'✅ {num}. [{member.display_name}]({self.players[num-1].league_profile})\n ' embed.add_field(name=f":hourglass: __{translate('player')}__", value='-------------------\n' + str_value) return embed
async def veto(self, pool, captain_1, captain_2, num_maps): """""" # Initialize veto self.captains = [captain_1, captain_2] self.map_pool = pool self.maps_left = {m.emoji: m for m in self.map_pool} self.ban_number = 0 self.num_maps = num_maps if len(self.map_pool) % 2 == 0: self.captains.reverse() # Edit input message and add emoji button reactions await self.edit(embed=self._veto_embed(translate('map-bans-begun'))) awaitables = [self.add_reaction(m.emoji) for m in self.map_pool] await asyncio.gather(*awaitables, loop=self.bot.loop) # Add listener handlers and wait until there are no maps left to ban self.future = self.bot.loop.create_future() self.bot.add_listener(self._process_ban, name='on_reaction_add') await asyncio.wait_for(self.future, 600) self.bot.remove_listener(self._process_ban, name='on_reaction_add') await self.clear_reactions() picked_maps = list(self.maps_left.values()) shuffle(picked_maps) return picked_maps
async def config_error(self, ctx, error): """ Respond to a permissions error with an explanation message. """ if isinstance(error, commands.MissingPermissions): await ctx.trigger_typing() missing_perm = error.missing_perms[0].replace('_', ' ') embed = self.bot.embed_template( title=translate('required-perm', missing_perm)) await ctx.send(embed=embed)
async def cap(self, ctx, *args): """ Set the queue capacity. """ if not await self.bot.is_pug_channel(ctx): return capacity = await self.bot.get_pug_data(ctx.channel.category, 'capacity') try: new_cap = int(args[0]) except (IndexError, ValueError): msg = f'{translate("invalid-usage")}: `{self.bot.command_prefix[0]}cap <number>`' else: if new_cap == capacity: msg = translate('capacity-already', capacity) elif new_cap < 2 or new_cap > 100: msg = translate('capacity-out-range') else: self.queue_cog.block_lobby[ctx.channel.category] = True await self.bot.db_helper.delete_all_queued_users( ctx.channel.category_id) await self.bot.db_helper.update_pug(ctx.channel.category_id, capacity=new_cap) embed = await self.queue_cog.queue_embed( ctx.channel.category, translate('queue-emptied')) embed.set_footer(text=translate('queue-emptied-footer')) await self.queue_cog.update_last_msg(ctx.channel.category, embed) msg = translate('set-capacity', new_cap) lobby_id = await self.bot.get_pug_data(ctx.channel.category, 'voice_lobby') prelobby_id = await self.bot.get_pug_data( ctx.channel.category, 'voice_prelobby') lobby = ctx.bot.get_channel(lobby_id) prelobby = ctx.bot.get_channel(prelobby_id) for player in lobby.members: await player.move_to(prelobby) self.queue_cog.block_lobby[ctx.channel.category] = False await lobby.edit(user_limit=new_cap) await ctx.send(embed=self.bot.embed_template(title=msg))
async def end(self, ctx, *args): """ Force end a match. """ if not await self.bot.is_pug_channel(ctx): return if len(args) == 0: msg = f'{translate("invalid-usage")}: `{self.bot.command_prefix[0]}end <Match ID>`' else: matches = await self.bot.api_helper.matches_status() if args[0] in matches: if await self.bot.api_helper.end_match(args[0]): msg = translate("match-cancelled", args[0]) else: msg = translate('match-already-over', args[0]) else: msg = translate("invalid-match-id") embed = self.bot.embed_template(title=msg) await ctx.send(embed=embed)
async def leaders(self, ctx): """ Send an embed containing the leaderboard data parsed from the player objects returned from the API. """ if not await self.bot.is_pug_channel(ctx): return num = 5 # Easily modfiy the number of players on the leaderboard guild_players = await self.bot.api_helper.get_players( [user.id for user in ctx.guild.members]) if len(guild_players) == 0: embed = self.bot.embed_template(title=translate("nobody-ranked")) await ctx.send(embed=embed) guild_players.sort(key=lambda u: (u.score, u.matches_played), reverse=True) # Select the top players only if len(guild_players) > num: guild_players = guild_players[:num] # Generate leaderboard text data = [ ['Player'] + [ ctx.guild.get_member(player.discord).display_name for player in guild_players ], ['Score'] + [str(player.score) for player in guild_players], ['Winrate'] + [f'{player.win_percent * 100:.2f}%' for player in guild_players], ['Played'] + [str(player.matches_played) for player in guild_players] ] data[0] = [ name if len(name) < 12 else name[:9] + '...' for name in data[0] ] # Shorten long names widths = list(map(lambda x: len(max(x, key=len)), data)) aligns = ['left', 'right', 'right', 'right'] z = zip(data, widths, aligns) formatted_data = [ list(map(lambda x: align_text(x, width, align), col)) for col, width, align in z ] formatted_data = list(map( list, zip(*formatted_data))) # Transpose list for .format() string description = '```ml\n {} {} {} {} \n'.format(*formatted_data[0]) for rank, player_row in enumerate(formatted_data[1:], start=1): description += ' {}. {} {} {} {} \n'.format(rank, *player_row) description += '```' # Send leaderboard title = f'__{translate("server-leaderboard")}__' embed = self.bot.embed_template(title=title, description=description) await ctx.send(embed=embed)
async def draft(self): """ Start the team draft and return the teams after it's finished. """ # Initialize self.members_left = self.members.copy( ) # Copy members to edit players remaining in the player pool self.players = await self.bot.api_helper.get_players( [member.id for member in self.members]) self.teams = [[], []] self.pick_number = 0 captain_method = await self.bot.get_pug_data(self.channel.category, 'captain_method') if captain_method == 'rank': players = await self.bot.api_helper.get_players( [member.id for member in self.members_left]) players.sort(reverse=True, key=lambda x: x.score) for team in self.teams: captain = self.guild.get_member(players.pop(0).discord) self.members_left.remove(captain) team.append(captain) elif captain_method == 'random': temp_members = self.members_left.copy() shuffle(temp_members) for team in self.teams: captain = temp_members.pop() self.members_left.remove(captain) team.append(captain) elif captain_method == 'volunteer': pass else: raise ValueError(f'Captain method "{captain_method}" isn\'t valid') await self.edit(embed=self._picker_embed(translate('team-draft-begun')) ) items = self.pick_emojis.items() for emoji, member in items: if member in self.members_left: await self.add_reaction(emoji) # Add listener handlers and wait until there are no members left to pick self.future = self.bot.loop.create_future() self.bot.add_listener(self._process_pick, name='on_reaction_add') await asyncio.wait_for(self.future, 600) self.bot.remove_listener(self._process_pick, name='on_reaction_add') return self.teams
async def unlink(self, ctx): """ Unlink a player by delete him on the backend. """ if not await self.bot.is_pug_channel(ctx): return try: user = ctx.message.mentions[0] except IndexError: title = f"{translate('invalid-usage')}: `{self.bot.command_prefix[0]}unlink <mention>`" else: linked = await self.bot.api_helper.is_linked(user.id) if not linked: title = translate('already-not-linked') else: await self.bot.api_helper.unlink_discord(user) title = translate('unlinked') role_id = await self.bot.get_pug_data(ctx.channel.category, 'pug_role') role = ctx.guild.get_role(role_id) await user.remove_roles(role) embed = self.bot.embed_template(title=title) await ctx.send(embed=embed)
def _veto_embed(self, title): """ Generate the menu embed based on the current status of the map bans. """ embed = self.bot.embed_template(title=title) embed.set_footer(text=translate('map-veto-footer')) maps_str = '' if self.map_pool is not None and self.maps_left is not None: for m in self.map_pool: maps_str += f'{m.emoji} {m.name}\n' if m.emoji in self.maps_left else f':heavy_multiplication_x: ' \ f'~~{m.name}~~\n ' status_str = '' if self.captains is not None and self._active_picker is not None: status_str += f'**{translate("capt1")}:** {self.captains[0].mention}\n' status_str += f'**{translate("capt2")}:** {self.captains[1].mention}\n\n' status_str += f'**{translate("current-capt")}:** {self._active_picker.mention}' embed.add_field(name=f'__{translate("maps-left")}__', value=maps_str) embed.add_field(name=f'__{translate("info")}__', value=status_str) return embed
def _picker_embed(self, title): """ Generate the menu embed based on the current status of the team draft. """ embed = self.bot.embed_template(title=title) embed.set_footer(text=translate('team-pick-footer')) for team in self.teams: team_name = f'__{translate("team")}__' if len( team ) == 0 else f'__{translate("team")} {team[0].display_name}__' if len(team) == 0: team_players = f'_{translate("empty")}_' else: team_players = '\n'.join(p.display_name for p in team) embed.add_field(name=team_name, value=team_players) members_left_str = '' for index, (emoji, member) in enumerate(self.pick_emojis.items()): if not any(member in team for team in self.teams): members_left_str += f'{emoji} [{member.display_name}]({self.players[index].league_profile}) | {self.players[index].score}\n' else: members_left_str += f':heavy_multiplication_x: ~~[{member.display_name}]({self.players[index].league_profile})~~\n' embed.insert_field_at(1, name=f'__{translate("players-left")}__', value=members_left_str) status_str = '' status_str += f'**{translate("capt1")}:** {self.teams[0][0].mention}\n' if len( self.teams[0]) else f'**{translate("capt1")}:**\n ' status_str += f'**{translate("capt2")}:** {self.teams[1][0].mention}\n\n' if len( self.teams[1]) else f'**{translate("capt2")}:**\n\n ' status_str += f'**{translate("current-capt")}:** {self._active_picker.mention}' \ if self._active_picker is not None else f'**{translate("current-capt")}:**' embed.add_field(name=f'__{translate("info")}__', value=status_str) return embed
async def _process_pick(self, reaction, member): """ Handler function for player pick reactions. """ # Check that reaction is on this message and member is in the team draft if reaction.message.id != self.id or member == self.author: return # Check that picked player is in the player pool pick = self.pick_emojis.get(str(reaction.emoji), None) if pick is None or pick not in self.members_left or member not in self.members: await self.remove_reaction(reaction, member) return # Attempt to pick the player for the team try: self._pick_player(member, pick) except PickError as e: # Player not picked await self.remove_reaction(reaction, member) title = e.message else: # Player picked self.picked_player = reaction.emoji title = translate('team-picked', member.display_name, pick.display_name) if len(self.members_left) == 1: fat_kid_team = self.teams[0] if len(self.teams[0]) <= len( self.teams[1]) else self.teams[1] fat_kid_team.append(self.members_left.pop(0)) await self._update_menu(title) if self.future is not None: self.future.set_result(None) return if len(self.members_left) == 0: await self._update_menu(title) if self.future is not None: self.future.set_result(None) return await self._update_menu(title)
async def queue_embed(self, category, title=None): """ Method to create the queue embed for a guild. """ queued_ids = await self.bot.db_helper.get_queued_users(category.id) capacity = await self.bot.get_pug_data(category, 'capacity') if len(queued_ids) > 1: players = await self.bot.api_helper.get_players(queued_ids) elif len(queued_ids) == 1: players = [await self.bot.api_helper.get_player(queued_ids[0])] if title: title += f' ({len(queued_ids)}/{capacity})' if len(queued_ids) == 0: # If there are no members in the queue queue_str = f'_{translate("queue-is-empty")}_' else: # members still in queue queue_str = ''.join( f'{num}. [{category.guild.get_member(member_id).display_name}]({players[num - 1].league_profile})\n' for num, member_id in enumerate(queued_ids, start=1)) embed = self.bot.embed_template(title=title, description=queue_str) embed.set_footer(text=translate('receive-notification')) return embed
async def create(self, ctx, *args): args = ' '.join(arg for arg in args) if not len(args): msg = f'{translate("invalid-usage")}: `{self.bot.command_prefix[0]}create <name>`' else: category = await ctx.guild.create_category_channel(name=args) await self.bot.db_helper.insert_pugs(category.id) everyone_role = get(ctx.guild.roles, name='@everyone') pug_role = await ctx.guild.create_role(name=f'{args}_linked') text_channel_queue = await ctx.guild.create_text_channel( name=f'{args}_queue', category=category) text_channel_commands = await ctx.guild.create_text_channel( name=f'{args}_commands', category=category) voice_channel_lobby = await ctx.guild.create_voice_channel( name=f'{args} Lobby', category=category, user_limit=10) voice_channel_prelobby = await ctx.guild.create_voice_channel( name=f'{args} Pre-Lobby', category=category) await self.bot.db_helper.update_pug(category.id, pug_role=pug_role.id) await self.bot.db_helper.update_pug( category.id, text_queue=text_channel_queue.id) await self.bot.db_helper.update_pug( category.id, text_commands=text_channel_commands.id) await self.bot.db_helper.update_pug( category.id, voice_lobby=voice_channel_lobby.id) await self.bot.db_helper.update_pug( category.id, voice_prelobby=voice_channel_prelobby.id) await text_channel_queue.set_permissions(everyone_role, send_messages=False) await voice_channel_lobby.set_permissions(everyone_role, connect=False) await voice_channel_lobby.set_permissions(pug_role, connect=True) msg = translate('create-league').format(args) embed = self.bot.embed_template(title=msg) await ctx.send(embed=embed)
def _pick_player(self, picker, pickee): """ Process a team captain's player pick, assuming the picker is in the team draft. """ # Get picking team if picker == pickee: raise PickError(translate('picker-pick-self', picker.display_name)) elif not self.teams[0]: picking_team = self.teams[0] self.members_left.remove(picker) picking_team.append(picker) elif self.teams[1] == [] and picker == self.teams[0][0]: raise PickError(translate('picker-not-turn', picker.display_name)) elif self.teams[1] == [] and picker in self.teams[0]: raise PickError( translate('picker-not-captain', picker.display_name)) elif not self.teams[1]: picking_team = self.teams[1] self.members_left.remove(picker) picking_team.append(picker) elif picker == self.teams[0][0]: picking_team = self.teams[0] elif picker == self.teams[1][0]: picking_team = self.teams[1] else: raise PickError( translate('picker-not-captain', picker.display_name)) # Check if it's picker's turn if picker != self._active_picker: raise PickError(translate('picker-not-turn', picker.display_name)) # Prevent picks when team is full if len(picking_team) > len(self.members) // 2: raise PickError(translate('team-full', picker.display_name)) self.members_left.remove(pickee) picking_team.append(pickee) self.pick_number += 1
class CommandsCog(commands.Cog): """""" def __init__(self, bot): self.bot = bot self.match_cog = self.bot.get_cog('MatchCog') self.queue_cog = self.bot.get_cog('QueueCog') @commands.command(usage='create <name>', brief=translate('command-create-brief')) @commands.has_permissions(administrator=True) async def create(self, ctx, *args): args = ' '.join(arg for arg in args) if not len(args): msg = f'{translate("invalid-usage")}: `{self.bot.command_prefix[0]}create <name>`' else: category = await ctx.guild.create_category_channel(name=args) await self.bot.db_helper.insert_pugs(category.id) everyone_role = get(ctx.guild.roles, name='@everyone') pug_role = await ctx.guild.create_role(name=f'{args}_linked') text_channel_queue = await ctx.guild.create_text_channel( name=f'{args}_queue', category=category) text_channel_commands = await ctx.guild.create_text_channel( name=f'{args}_commands', category=category) voice_channel_lobby = await ctx.guild.create_voice_channel( name=f'{args} Lobby', category=category, user_limit=10) voice_channel_prelobby = await ctx.guild.create_voice_channel( name=f'{args} Pre-Lobby', category=category) await self.bot.db_helper.update_pug(category.id, pug_role=pug_role.id) await self.bot.db_helper.update_pug( category.id, text_queue=text_channel_queue.id) await self.bot.db_helper.update_pug( category.id, text_commands=text_channel_commands.id) await self.bot.db_helper.update_pug( category.id, voice_lobby=voice_channel_lobby.id) await self.bot.db_helper.update_pug( category.id, voice_prelobby=voice_channel_prelobby.id) await text_channel_queue.set_permissions(everyone_role, send_messages=False) await voice_channel_lobby.set_permissions(everyone_role, connect=False) await voice_channel_lobby.set_permissions(pug_role, connect=True) msg = translate('create-league').format(args) embed = self.bot.embed_template(title=msg) await ctx.send(embed=embed) @commands.command(brief=translate('command-delete-brief')) @commands.has_permissions(administrator=True) async def delete(self, ctx): if not await self.bot.is_pug_channel(ctx): return pug_role_id = await self.bot.get_pug_data(ctx.channel.category, 'pug_role') pug_role = ctx.guild.get_role(pug_role_id) try: await pug_role.delete() except NotFound: pass await self.bot.db_helper.delete_pugs(ctx.channel.category_id) for channel in ctx.channel.category.channels + [ctx.channel.category]: await channel.delete() @commands.command(brief=translate('command-link-brief')) async def link(self, ctx): """ Link a player by sending them a link to sign in with steam on the backend. """ if not await self.bot.is_pug_channel(ctx): return is_linked = await self.bot.api_helper.is_linked(ctx.author.id) if is_linked: player = await self.bot.api_helper.get_player(ctx.author.id) title = translate('already-linked', player.steam_profile) else: link = await self.bot.api_helper.generate_link_url(ctx.author.id) if link: # Send the author a DM containing this link try: await ctx.author.send(translate('dm-link', link)) title = translate('link-sent') except: title = translate('blocked-dm') else: title = translate('unknown-error') embed = self.bot.embed_template(description=title) await ctx.send(content=ctx.author.mention, embed=embed) @commands.command(usage='forcelink <mention> <Steam64 ID>', brief=translate('command-forcelink-brief')) @commands.has_permissions(administrator=True) async def forcelink(self, ctx, *args): """ Force link player with steam on the backend. """ if not await self.bot.is_pug_channel(ctx): return print(int(args[1]), type(int(args[1]))) try: user = ctx.message.mentions[0] except IndexError: title = f"{translate('invalid-usage')}: `{self.bot.command_prefix[0]}forcelink <mention> <Steam64 ID>`" else: link = await self.bot.api_helper.force_link_discord( user.id, args[1]) if not link: title = 'Sorry! Steam ID is already linked with another discord' else: title = translate('force-linked', user.display_name, args[1]) role_id = await self.bot.get_pug_data(ctx.channel.category, 'pug_role') role = ctx.guild.get_role(role_id) await user.add_roles(role) await self.bot.api_helper.update_discord_name(user) embed = self.bot.embed_template(title=title) await ctx.send(embed=embed) @commands.command(usage='unlink <mention>', brief=translate('command-unlink-brief')) @commands.has_permissions(administrator=True) async def unlink(self, ctx): """ Unlink a player by delete him on the backend. """ if not await self.bot.is_pug_channel(ctx): return try: user = ctx.message.mentions[0] except IndexError: title = f"{translate('invalid-usage')}: `{self.bot.command_prefix[0]}unlink <mention>`" else: linked = await self.bot.api_helper.is_linked(user.id) if not linked: title = translate('already-not-linked') else: await self.bot.api_helper.unlink_discord(user) title = translate('unlinked') role_id = await self.bot.get_pug_data(ctx.channel.category, 'pug_role') role = ctx.guild.get_role(role_id) await user.remove_roles(role) embed = self.bot.embed_template(title=title) await ctx.send(embed=embed) @commands.command(brief=translate('command-check-brief')) async def check(self, ctx): if not await self.bot.is_pug_channel(ctx): return if not await self.bot.api_helper.is_linked(ctx.author.id): msg = translate('discord-not-linked') else: role_id = await self.bot.get_pug_data(ctx.channel.category, 'pug_role') role = ctx.guild.get_role(role_id) await ctx.author.add_roles(role) await self.bot.api_helper.update_discord_name(ctx.author) msg = translate('discord-get-role') embed = self.bot.embed_template(description=msg, color=self.bot.color) await ctx.send(content=ctx.author.mention, embed=embed) @commands.command(brief=translate('command-empty-brief')) @commands.has_permissions(kick_members=True) async def empty(self, ctx): """ Reset the pug's queue list to empty. """ if not await self.bot.is_pug_channel(ctx): return self.queue_cog.block_lobby[ctx.channel.category] = True await self.bot.db_helper.delete_all_queued_users( ctx.channel.category.id) msg = translate('queue-emptied') embed = await self.queue_cog.queue_embed(ctx.channel.category, msg) lobby_id = await self.bot.get_pug_data(ctx.channel.category, 'voice_lobby') prelobby_id = await self.bot.get_pug_data(ctx.channel.category, 'voice_prelobby') lobby = ctx.bot.get_channel(lobby_id) prelobby = ctx.bot.get_channel(prelobby_id) for player in lobby.members: await player.move_to(prelobby) self.queue_cog.block_lobby[ctx.channel.category] = False _embed = self.bot.embed_template(title=msg) await ctx.send(embed=_embed) # Update queue display message await self.queue_cog.update_last_msg(ctx.channel.category, embed) @commands.command(usage='cap [new capacity]', brief=translate('command-cap-brief')) @commands.has_permissions(administrator=True) async def cap(self, ctx, *args): """ Set the queue capacity. """ if not await self.bot.is_pug_channel(ctx): return capacity = await self.bot.get_pug_data(ctx.channel.category, 'capacity') try: new_cap = int(args[0]) except (IndexError, ValueError): msg = f'{translate("invalid-usage")}: `{self.bot.command_prefix[0]}cap <number>`' else: if new_cap == capacity: msg = translate('capacity-already', capacity) elif new_cap < 2 or new_cap > 100: msg = translate('capacity-out-range') else: self.queue_cog.block_lobby[ctx.channel.category] = True await self.bot.db_helper.delete_all_queued_users( ctx.channel.category_id) await self.bot.db_helper.update_pug(ctx.channel.category_id, capacity=new_cap) embed = await self.queue_cog.queue_embed( ctx.channel.category, translate('queue-emptied')) embed.set_footer(text=translate('queue-emptied-footer')) await self.queue_cog.update_last_msg(ctx.channel.category, embed) msg = translate('set-capacity', new_cap) lobby_id = await self.bot.get_pug_data(ctx.channel.category, 'voice_lobby') prelobby_id = await self.bot.get_pug_data( ctx.channel.category, 'voice_prelobby') lobby = ctx.bot.get_channel(lobby_id) prelobby = ctx.bot.get_channel(prelobby_id) for player in lobby.members: await player.move_to(prelobby) self.queue_cog.block_lobby[ctx.channel.category] = False await lobby.edit(user_limit=new_cap) await ctx.send(embed=self.bot.embed_template(title=msg)) @commands.command(usage='spectators {+|-} <mention> <mention> ...', brief=translate('command-spectators-brief')) @commands.has_permissions(administrator=True) async def spectators(self, ctx, *args): """""" if not await self.bot.is_pug_channel(ctx): return curr_spectator_ids = await self.bot.db_helper.get_spect_users( ctx.channel.category_id) curr_spectators = [ ctx.guild.get_member(spectator_id) for spectator_id in curr_spectator_ids ] spectators = ctx.message.mentions if not spectators: embed = self.bot.embed_template() embed.add_field( name=f'__Spectators__', value='No spectators' if not curr_spectators else ''.join( f'{num}. {member.mention}\n' for num, member in enumerate(curr_spectators, start=1))) await ctx.send(embed=embed) return prefix = args[0] title = '' if prefix not in ['+', '-']: title = f'{translate("invalid-usage")}: `{self.bot.command_prefix[0]}spectators [+|-] <mention>`' else: await self.bot.db_helper.delete_queued_users( ctx.channel.category_id, [spectator.id for spectator in spectators]) for spectator in spectators: if args[0] == '+': if spectator.id not in curr_spectator_ids: await self.bot.db_helper.insert_spect_users( ctx.channel.category_id, spectator.id) title += f'{translate("added-spect", spectator.display_name)}\n' else: title = f'{translate("already-spect", spectator.display_name)}\n' elif args[0] == '-': if spectator.id in curr_spectator_ids: await self.bot.db_helper.delete_spect_users( ctx.channel.category_id, spectator.id) title += f'{translate("removed-spect", spectator.display_name)}\n' else: title = f'{translate("already-spect", spectator.display_name)}\n' embed = self.bot.embed_template(title=title) await ctx.send(embed=embed) @commands.command(usage='teams {captains|autobalance|random}', brief=translate('command-teams-brief')) @commands.has_permissions(administrator=True) async def teams(self, ctx, method=None): """ Set or display the method by which teams are created. """ if not await self.bot.is_pug_channel(ctx): return team_method = await self.bot.get_pug_data(ctx.channel.category, 'team_method') valid_methods = ['captains', 'autobalance', 'random'] if method is None: title = translate('team-method', team_method) else: method = method.lower() if method == team_method: title = translate('team-method-already', team_method) elif method in valid_methods: title = translate('set-team-method', method) await self.bot.db_helper.update_pug(ctx.channel.category_id, team_method=method) else: title = translate('team-valid-methods', valid_methods[0], valid_methods[1], valid_methods[2]) embed = self.bot.embed_template(title=title) await ctx.send(embed=embed) @commands.command(usage='captains {volunteer|rank|random}', brief=translate('command-captains-brief')) @commands.has_permissions(administrator=True) async def captains(self, ctx, method=None): """ Set or display the method by which captains are selected. """ if not await self.bot.is_pug_channel(ctx): return guild_data = await self.bot.db_helper.get_pug(ctx.channel.category_id) captain_method = guild_data['captain_method'] valid_methods = ['volunteer', 'rank', 'random'] if method is None: title = translate('captains-method', captain_method) else: method = method.lower() if method == captain_method: title = translate('captains-method-already', captain_method) elif method in valid_methods: title = translate('set-captains-method', method) await self.bot.db_helper.update_pug(ctx.channel.category_id, captain_method=method) else: title = translate('captains-valid-method', valid_methods[0], valid_methods[1], valid_methods[2]) embed = self.bot.embed_template(title=title) await ctx.send(embed=embed) @commands.command(usage='mpool {+|-}<map name> ...', brief=translate('command-mpool-brief')) @commands.has_permissions(administrator=True) async def mpool(self, ctx, *args): """ Edit the guild's map pool for map drafts. """ if not await self.bot.is_pug_channel(ctx): return map_pool = [ m.dev_name for m in self.bot.all_maps.values() if await self.bot.get_pug_data(ctx.channel.category, m.dev_name) ] if len(args) == 0: embed = self.bot.embed_template(title=translate('map-pool')) else: description = '' any_wrong_arg = False # Indicates if the command was used correctly for arg in args: map_name = arg[1:] # Remove +/- prefix map_obj = next((m for m in self.bot.all_maps.values() if m.dev_name == map_name), None) if map_obj is None: description += '\u2022 ' + translate( 'could-not-interpret', arg) any_wrong_arg = True continue if arg.startswith('+'): # Add map if map_name not in map_pool: map_pool.append(map_name) description += '\u2022 ' + translate( 'added-map', map_name) elif arg.startswith('-'): # Remove map if map_name in map_pool: map_pool.remove(map_name) description += '\u2022 ' + translate( 'removed-map', map_name) if len(map_pool) < 3: description = translate('map-pool-fewer-3') else: map_pool_data = { m.dev_name: m.dev_name in map_pool for m in self.bot.all_maps.values() } await self.bot.db_helper.update_pug(ctx.channel.category_id, **map_pool_data) embed = self.bot.embed_template( title=translate('modified-map-pool'), description=description) if any_wrong_arg: # Add example usage footer if command was used incorrectly embed.set_footer( text= f'Ex: {self.bot.command_prefix[0]}mpool +de_cache -de_mirage' ) active_maps = ''.join(f'{m.emoji} `{m.dev_name}`\n' for m in self.bot.all_maps.values() if m.dev_name in map_pool) inactive_maps = ''.join(f'{m.emoji} `{m.dev_name}`\n' for m in self.bot.all_maps.values() if m.dev_name not in map_pool) if not inactive_maps: inactive_maps = f'*{translate("none")}*' embed.add_field(name=f'__{translate("active-maps")}__', value=active_maps) embed.add_field(name=f'__{translate("inactive-maps")}__', value=inactive_maps) await ctx.send(embed=embed) @commands.command(usage='maps [{captains|vote|random}]', brief=translate('command-maps-brief')) @commands.has_permissions(administrator=True) async def maps(self, ctx, method=None): """ Set or display the method by which the teams are created. """ if not await self.bot.is_pug_channel(ctx): return map_method = await self.bot.get_pug_data(ctx.channel.category, 'map_method') valid_methods = ['captains', 'vote', 'random'] if method is None: title = translate('map-method', map_method) else: method = method.lower() if method == map_method: title = translate('map-method-already', map_method) elif method in valid_methods: title = translate('set-map-method', method) await self.bot.db_helper.update_pug(ctx.channel.category_id, map_method=method) else: title = translate('map-valid-method', valid_methods[0], valid_methods[1], valid_methods[2]) embed = self.bot.embed_template(title=title) await ctx.send(embed=embed) @commands.command(usage='end [match id]', brief=translate('command-end-brief')) @commands.has_permissions(administrator=True) async def end(self, ctx, *args): """ Force end a match. """ if not await self.bot.is_pug_channel(ctx): return if len(args) == 0: msg = f'{translate("invalid-usage")}: `{self.bot.command_prefix[0]}end <Match ID>`' else: matches = await self.bot.api_helper.matches_status() if args[0] in matches: if await self.bot.api_helper.end_match(args[0]): msg = translate("match-cancelled", args[0]) else: msg = translate('match-already-over', args[0]) else: msg = translate("invalid-match-id") embed = self.bot.embed_template(title=msg) await ctx.send(embed=embed) @commands.command(brief=translate('command-stats-brief')) async def stats(self, ctx): """ Send an embed containing stats data parsed from the player object returned from the API. """ if not await self.bot.is_pug_channel(ctx): return try: user = ctx.message.mentions[0] except IndexError: user = ctx.author player = await self.bot.api_helper.get_player(user.id) if player: win_percent_str = f'{player.win_percent * 100:.2f}%' hs_percent_str = f'{player.hs_percent * 100:.2f}%' fb_percent_str = f'{player.first_blood_rate * 100:.2f}%' description = '```ml\n' \ f' {translate("rank-score")}: {player.score:>6} \n' \ f' {translate("matches-played")}: {player.matches_played:>6} \n' \ f' {translate("win-percentage")}: {win_percent_str:>6} \n' \ f' {translate("kd-ratio")}: {player.kd_ratio:>6.2f} \n' \ f' {translate("adr")}: {player.adr:>6.2f} \n' \ f' {translate("hs-percentage")}: {hs_percent_str:>6} \n' \ f' {translate("first-blood-rate")}: {fb_percent_str:>6} ' \ '```' embed = self.bot.embed_template(description=description) embed.set_author(name=user.display_name, url=player.league_profile, icon_url=user.avatar_url_as(size=128)) else: title = translate("cannot-get-stats", ctx.author.display_name) embed = self.bot.embed_template(title=title) await ctx.send(embed=embed) @commands.command(brief=translate('command-leaders-brief')) async def leaders(self, ctx): """ Send an embed containing the leaderboard data parsed from the player objects returned from the API. """ if not await self.bot.is_pug_channel(ctx): return num = 5 # Easily modfiy the number of players on the leaderboard guild_players = await self.bot.api_helper.get_players( [user.id for user in ctx.guild.members]) if len(guild_players) == 0: embed = self.bot.embed_template(title=translate("nobody-ranked")) await ctx.send(embed=embed) guild_players.sort(key=lambda u: (u.score, u.matches_played), reverse=True) # Select the top players only if len(guild_players) > num: guild_players = guild_players[:num] # Generate leaderboard text data = [ ['Player'] + [ ctx.guild.get_member(player.discord).display_name for player in guild_players ], ['Score'] + [str(player.score) for player in guild_players], ['Winrate'] + [f'{player.win_percent * 100:.2f}%' for player in guild_players], ['Played'] + [str(player.matches_played) for player in guild_players] ] data[0] = [ name if len(name) < 12 else name[:9] + '...' for name in data[0] ] # Shorten long names widths = list(map(lambda x: len(max(x, key=len)), data)) aligns = ['left', 'right', 'right', 'right'] z = zip(data, widths, aligns) formatted_data = [ list(map(lambda x: align_text(x, width, align), col)) for col, width, align in z ] formatted_data = list(map( list, zip(*formatted_data))) # Transpose list for .format() string description = '```ml\n {} {} {} {} \n'.format(*formatted_data[0]) for rank, player_row in enumerate(formatted_data[1:], start=1): description += ' {}. {} {} {} {} \n'.format(rank, *player_row) description += '```' # Send leaderboard title = f'__{translate("server-leaderboard")}__' embed = self.bot.embed_template(title=title, description=description) await ctx.send(embed=embed) @create.error @delete.error @empty.error @cap.error @spectators.error @teams.error @captains.error @maps.error @mpool.error @end.error @unlink.error @forcelink.error async def config_error(self, ctx, error): """ Respond to a permissions error with an explanation message. """ if isinstance(error, commands.MissingPermissions): await ctx.trigger_typing() missing_perm = error.missing_perms[0].replace('_', ' ') embed = self.bot.embed_template( title=translate('required-perm', missing_perm)) await ctx.send(embed=embed)
async def help(self, ctx): """ Generate and send help embed based on the bot's commands. """ embed = self.help_embed(translate('league-commands')) await ctx.send(embed=embed)
async def on_voice_state_update(self, member, before, after): if before.channel == after.channel: return try: after_id = await self.bot.get_pug_data(after.channel.category, 'voice_lobby') except AttributeError: after_id = None try: before_id = await self.bot.get_pug_data(before.channel.category, 'voice_lobby') except AttributeError: before_id = None before_lobby = member.guild.get_channel(before_id) after_lobby = member.guild.get_channel(after_id) if after.channel == after_lobby is not None: if self.block_lobby[after_lobby.category]: return if not await self.bot.api_helper.is_linked( member.id): # Message author isn't linked title = translate('account-not-linked', member.display_name) else: # Message author is linked awaitables = [ self.bot.api_helper.get_player(member.id), self.bot.db_helper.insert_users(member.id), self.bot.db_helper.get_queued_users( after_lobby.category_id), self.bot.get_pug_data(after_lobby.category, 'capacity'), self.bot.db_helper.get_spect_users(after_lobby.category_id) ] results = await asyncio.gather(*awaitables, loop=self.bot.loop) player = results[0] queue_ids = results[2] capacity = results[3] spect_ids = results[4] if member.id in queue_ids: # Author already in queue title = translate('already-in-queue', member.display_name) elif member.id in spect_ids: # Player in the spectators title = translate('in-spectators', member.display_name) elif len(queue_ids) >= capacity: # Queue full title = translate('queue-is-full', member.display_name) elif not player: # ApiHelper couldn't get player title = translate('cannot-verify-match', member.display_name) elif player.in_match: # member is already in a match title = translate('already-in-match', member.display_name) else: # member can be added await self.bot.db_helper.insert_queued_users( after_lobby.category_id, member.id) queue_ids += [member.id] title = translate('added-to-queue', member.display_name) # Check and burst queue if full if len(queue_ids) == capacity: self.block_lobby[after_lobby.category] = True match_cog = self.bot.get_cog('MatchCog') pug_role_id = await self.bot.get_pug_data( after_lobby.category, 'pug_role') pug_role = member.guild.get_role(pug_role_id) await after_lobby.set_permissions(pug_role, connect=False) queue_members = [ member.guild.get_member(member_id) for member_id in queue_ids ] all_readied = await match_cog.start_match( after_lobby.category, queue_members) if all_readied: await self.bot.db_helper.delete_queued_users( after_lobby.category_id, *queue_ids) if match_cog.no_servers[after_lobby.category]: await self.bot.db_helper.delete_queued_users( after_lobby.category_id, *queue_ids) prelobby_id = await self.bot.get_pug_data( after_lobby.category, 'voice_prelobby') prelobby = after_lobby.guild.get_channel( prelobby_id) for member in queue_members: try: await member.move_to(prelobby) except (AttributeError, HTTPException): pass match_cog.no_servers[after_lobby.category] = False self.block_lobby[after_lobby.category] = False await after_lobby.set_permissions(pug_role, connect=True) title = translate('players-in-queue') embed = await self.queue_embed(after_lobby.category, title) await self.update_last_msg(after_lobby.category, embed) return embed = await self.queue_embed(after_lobby.category, title) # Delete last queue message await self.update_last_msg(after_lobby.category, embed) if before.channel == before_lobby is not None: if self.block_lobby[before_lobby.category]: return removed = await self.bot.db_helper.delete_queued_users( before_lobby.category_id, member.id) if member.id in removed: title = translate('removed-from-queue', member.display_name) else: title = translate('not-in-queue', member.display_name) embed = await self.queue_embed(before_lobby.category, title) # Update queue display message await self.update_last_msg(before_lobby.category, embed)
async def mpool(self, ctx, *args): """ Edit the guild's map pool for map drafts. """ if not await self.bot.is_pug_channel(ctx): return map_pool = [ m.dev_name for m in self.bot.all_maps.values() if await self.bot.get_pug_data(ctx.channel.category, m.dev_name) ] if len(args) == 0: embed = self.bot.embed_template(title=translate('map-pool')) else: description = '' any_wrong_arg = False # Indicates if the command was used correctly for arg in args: map_name = arg[1:] # Remove +/- prefix map_obj = next((m for m in self.bot.all_maps.values() if m.dev_name == map_name), None) if map_obj is None: description += '\u2022 ' + translate( 'could-not-interpret', arg) any_wrong_arg = True continue if arg.startswith('+'): # Add map if map_name not in map_pool: map_pool.append(map_name) description += '\u2022 ' + translate( 'added-map', map_name) elif arg.startswith('-'): # Remove map if map_name in map_pool: map_pool.remove(map_name) description += '\u2022 ' + translate( 'removed-map', map_name) if len(map_pool) < 3: description = translate('map-pool-fewer-3') else: map_pool_data = { m.dev_name: m.dev_name in map_pool for m in self.bot.all_maps.values() } await self.bot.db_helper.update_pug(ctx.channel.category_id, **map_pool_data) embed = self.bot.embed_template( title=translate('modified-map-pool'), description=description) if any_wrong_arg: # Add example usage footer if command was used incorrectly embed.set_footer( text= f'Ex: {self.bot.command_prefix[0]}mpool +de_cache -de_mirage' ) active_maps = ''.join(f'{m.emoji} `{m.dev_name}`\n' for m in self.bot.all_maps.values() if m.dev_name in map_pool) inactive_maps = ''.join(f'{m.emoji} `{m.dev_name}`\n' for m in self.bot.all_maps.values() if m.dev_name not in map_pool) if not inactive_maps: inactive_maps = f'*{translate("none")}*' embed.add_field(name=f'__{translate("active-maps")}__', value=active_maps) embed.add_field(name=f'__{translate("inactive-maps")}__', value=inactive_maps) await ctx.send(embed=embed)