Beispiel #1
0
    async def deny(self, ctx, *, game_id: str=""):
        """Dispute a match result. This will notify league admins that the match result requires attention. League admins may resolve the match by either accepting or removing it. If you created the match and there is an error (ie. mentioned the wrong players), then the `remove` command is more appropriate to undo the logged match and log the correct result."""

        user = ctx.message.author
        member = self.bot.db.find_member(user.id, ctx.message.guild)
        if not member["pending"]:
            await ctx.send(embed=embed.info(description="No pending matches to deny"))
            return
        if not game_id:
            await ctx.send(embed=embed.error(description="You must specify a game id to dispute it"))
            return

        match = self.bot.db.find_match(game_id, ctx.message.guild)
        if not match:
            await ctx.send(embed=embed.error(description=f"`{game_id}` does not exist"))
            return
        player = next((i for i in match["players"] if i["user_id"] == user.id), None)
        if not player:
            await ctx.send(embed=embed.error(description="Only participants can deny a match"))
            return

        if match["status"] == stc.ACCEPTED:
            await ctx.send(embed=embed.error(description="Accepted matches cannot be denied"))
        elif match["status"] == stc.DISPUTED:
            await ctx.send(embed=embed.info(description="This match has already been marked for review"))
        else:
            self.bot.db.set_match_status(stc.DISPUTED, game_id, ctx.message.guild)
            self.bot.db.unconfirm_match_for_user(game_id, user.id, ctx.message.guild)
            admin_role = self.bot.db.get_admin_role(ctx.message.guild)
            mention = "" if not admin_role else admin_role.mention
            await ctx.send(embed=embed.msg(
                description=f"{mention} Match `{game_id}` has been marked as **disputed**")
            )
Beispiel #2
0
    async def season(self, ctx, *, season_number: int = None):
        """Get information about a season."""

        season_info = self.bot.db.get_season(ctx.message.guild,
                                             season=season_number)
        if not season_info:
            await ctx.send(embed=embed.error(
                description=f"Season {season_number} does not exist."))
            return

        emsg = embed.info(title=f"Season {season_info['season_number']}")
        start_date = datetime.fromtimestamp(season_info["start_time"])
        emsg.add_field(name="Start Date",
                       value=start_date.strftime("%Y-%m-%d"))
        if "end_time" in season_info:
            end_date = datetime.fromtimestamp(season_info["end_time"])
            emsg.add_field(name="End Date",
                           value=end_date.strftime("%Y-%m-%d"))
            awards = [
                emojis.first_place, emojis.second_place, emojis.third_place
            ]
            emsg.add_field(
                name="Season Awards",
                value="\n".join([
                    f"`{awards[i]} - {self.bot.db.find_member(user_id, ctx.message.guild)['name']}`"
                    for i, user_id in enumerate(season_info["season_leaders"])
                ]))
        await ctx.send(embed=emsg)
Beispiel #3
0
    async def confirm(self, ctx, *, game_id: str=""):
        """Validate a match result. All players, including the winner, must confirm a match. 
        By default, this command will confirm the most recent pending match by the caller. If a game_id is specified, then the specified match will be confirmed instead. 
        Confirmation is a two-step process to verify the caller's deck choice and then to verify that the match result is correct."""

        user = ctx.message.author
        member = self.bot.db.find_member(user.id, ctx.message.guild)
        if not member["pending"]:
            await ctx.send(embed=embed.info(description="No pending matches to confirm"))
            return
        if not game_id:
            game_id = member["pending"][-1]

        match = self.bot.db.find_match(game_id, ctx.message.guild)
        if not match:
            await ctx.send(embed=embed.error(description=f"`{game_id}` does not exist"))
            return
        player = next((i for i in match["players"] if i["user_id"] == user.id), None)
        if not player:
            await ctx.send(embed=embed.error(description="Only participants can confirm a match"))
            return

        if not player["confirmed"]:
            delta = await self._get_player_confirmation(ctx, member, game_id)
            if delta:
                await ctx.send(embed=embed.match_delta(game_id, delta))
        else:
            await ctx.send(embed=embed.error(description="You have already confirmed this match"))
Beispiel #4
0
    async def help(self, ctx, *, name: str = ""):
        """Show a list of commands. Show detailed help by specifying a command."""

        if name:
            await self.command_help(ctx, name)
            return
        emsg = embed.info(title="Command Help")
        cogs = {}
        for command in self.bot.commands:
            if command.hidden:
                continue
            if command.cog_name in cogs:
                cogs[command.cog_name].append(command)
            else:
                cogs[command.cog_name] = [command]
        sorted_cog_keys = sorted(cogs.keys(), reverse=True)
        for cog_name in sorted_cog_keys:
            cogs[cog_name].sort(key=lambda o: o.name)
            cog_summary = []
            for command in cogs[cog_name]:
                cog_summary.append(
                    f"**`{ctx.prefix}{command.qualified_name}`** -- {command.brief}"
                )
            emsg.add_field(name=cog_name,
                           inline=False,
                           value=("\n".join(cog_summary)))
        emsg.set_footer(text=f"Type {ctx.prefix}help [command] for more info")
        await ctx.send(embed=emsg)
Beispiel #5
0
    async def _games_by_players(self, ctx, *args):
        """Displays a list of games filtered by players. Mention all players to filter by. If no players are mentioned, filter by games containing the sender."""

        mentions = ctx.message.mentions
        if len(mentions) > 4:
            await ctx.send(embed=embed.error(
                description="Too many players mentioned"))
            return
        if len(mentions) == 0:
            mentions.append(ctx.message.author)
        matches = self.bot.db.find_matches(
            {"players.user_id": {
                "$all": [user.id for user in mentions]
            }},
            ctx.message.guild,
            limit=20)
        matches = list(matches)
        if not matches:
            await ctx.send(embed=embed.info(
                description=("No matches found containing " +
                             ", ".join([user.name for user in mentions]))))
            return
        title = "Games Containing: " + ", ".join(
            [mention.name for mention in mentions])
        emsgs = self._make_match_table(title, matches, winner_type="player")
        for emsg in emsgs:
            await ctx.send(embed=emsg)
Beispiel #6
0
 def _make_match_table(self, title, matches, winner_type="player"):
     header = "`DATE` `ID` `REPLAY` `WINNER`\n"
     rows = []
     max_name_len = 16
     for match in matches:
         date = utils.short_date_from_timestamp(match['timestamp'])
         if winner_type == "deck":
             deck_name = match['winning_deck'] if match[
                 'winning_deck'] else "N/A"
             if len(deck_name) > max_name_len:
                 deck_name = self.bot.db.get_deck_short_name(deck_name)
             winner = deck_name
         else:
             winner = utils.get_winner_name(match)
             winner = utils.shorten_player_name(winner)
         replay_link = f"[Link]({match['replay_link']})" if match[
             'replay_link'] else "`???`"
         rows.append(
             f"`{date} {match['game_id']}` {replay_link} `{winner}`")
     emsgs = []
     for i in range(0, len(rows), 20):
         emsgs.append(
             embed.info(title=title,
                        description=(header + "\n".join(rows[i:(i + 20)]))))
     return emsgs
Beispiel #7
0
    async def info(self, ctx):
        """Show summary info of the league. Displays the number of registered players, the number of games recorded, and pending and disputed matches."""

        num_accepted = self.bot.db.count_matches({"status": stc.ACCEPTED},
                                                 ctx.message.guild)
        season_info = self.bot.db.get_season(ctx.message.guild)
        num_season_accepted = self.bot.db.count_matches(
            {
                "status": stc.ACCEPTED,
                "timestamp": {
                    "$gte": season_info['start_time']
                }
            }, ctx.message.guild)
        num_members = self.bot.db.members(ctx.message.guild).count()
        disputed_matches = self.bot.db.find_matches({"status": stc.DISPUTED},
                                                    ctx.message.guild)
        pending_matches = self.bot.db.find_matches({"status": stc.PENDING},
                                                   ctx.message.guild)
        disputed = self._get_game_ids_list(disputed_matches)
        pending = self._get_game_ids_list(pending_matches)

        emsg = embed.info(title=f"{ctx.message.guild.name} League") \
                    .add_field(name="Players", value=str(num_members)) \
                    .add_field(name="Current Season", value=str(season_info['season_number'])) \
                    .add_field(name="Total Games Played", value=str(num_accepted)) \
                    .add_field(name="Games Played This Season", value=str(num_season_accepted)) \
                    .add_field(name="Pending Games", value=pending) \
                    .add_field(name="Disputed Games", value=disputed)

        await ctx.send(embed=emsg)
Beispiel #8
0
    def _get_profile_card(self, user, guild):
        player = self.bot.db.find_member(user.id, guild)
        if not player:
            return None

        # update username if the username changed
        if player["name"] != user.name:
            self.bot.db.members(guild).update_one({
                "user_id": user.id
            }, {
                "$set": {
                    "name": user.name
                }
            })
        win_percent = 100*player["wins"]/player["accepted"] if player["accepted"] else 0.0
        emsg = embed.info(title=user.name) \
                    .set_thumbnail(url=utils.get_avatar(user)) \
                    .add_field(name="Points", value=str(player["points"])) \
                    .add_field(name="Wins", value=str(player["wins"])) \
                    .add_field(name="Losses", value=str(player["losses"])) \
                    .add_field(name="Win %", value="{:.3f}%".format(win_percent))
        self._add_favorite_deck_field(emsg, player, guild)
        self._add_last_played_deck_field(emsg, player)
        self._add_season_badges(emsg, player)
        return emsg
Beispiel #9
0
    async def _rescan_decks(self, ctx):
        """Scans the decks.json file in config/ and imports the decks into the database."""

        decks_added = self._load_decks()
        if not decks_added:
            await ctx.send(embed=embed.info(
                description=f"Nothing new to import"))
        else:
            await ctx.send(embed=embed.success(
                description=f"**SUCCESS** - {decks_added} new deck(s) imported"
            ))
Beispiel #10
0
    async def config(self, ctx):
        """Configure or view settings for the league"""

        if ctx.invoked_subcommand is None:
            settings = self.bot.db.get_config(ctx.message.guild)
            emsg = embed.info(title="League Configuration")
            for setting in settings:
                if setting != "_id":
                    emsg.add_field(name=setting, value=settings[setting])
            await ctx.send(embed=emsg)
            return
Beispiel #11
0
    async def compare(self, ctx, *args):
        """Show player performances in pods containing only the mentioned players. If only one player is mentioned, then the default is to compare that player with the caller of the command."""

        mentions = ctx.message.mentions
        if len(mentions) < 1:
            await ctx.send(embed=embed.error(description="Mention a player to compare with"))
            return
        if len(mentions) == 1 and mentions[0].id == ctx.message.author.id:
            await ctx.send(embed=embed.error(description="Not enough players mentioned"))
            return
        if len(mentions) == 1:
            mentions.append(ctx.message.author)
        if len(mentions) > 4:
            await ctx.send(embed=embed.error(description="Too many players mentioned"))
            return
        matches = self.bot.db.find_matches(
            {"players.user_id": {"$all": [user.id for user in mentions]}},
            ctx.message.guild
        )
        matches = list(matches)
        total = len(matches)
        if not total:
            await ctx.send(embed=embed.info(description="No matches found containing all mentioned players"))
            return
        data = {user.name: 0 for user in mentions}
        for match in matches:
            winner = next((user.name for user in mentions if user.id == match['winner']), None)
            if not winner:
                continue
            if winner in data:
                data[winner] += 1
        players = ", ".join(data.keys())
        emsg = embed.info(title=f"Games Containing: {players}")
        emsg.add_field(name="Total Matches", inline=False, value=str(total))
        for user_name in data:
            wins = data[user_name]
            losses = total - wins
            percent = wins/total
            emsg.add_field(name=user_name, inline=False, value=f"{wins}-{losses}, {percent:.1%}")
        await ctx.send(embed=emsg)
Beispiel #12
0
    async def preview(self, ctx, *, deck_name: str = ""):
        """Show a preview of a decklist. Supported deck hosts are tappedout.net and deckstats.net."""

        if not deck_name:
            await ctx.send(embed=embed.error(
                description="No deck name specified."))
            return

        deck = self.bot.db.find_deck(deck_name)
        if not deck:
            await ctx.send(embed=embed.error(
                description=
                f"Deck not found. Use `{ctx.prefix}decks` to see a list of decks."
            ))
            return
        if not deck['link']:
            await ctx.send(embed=embed.info(
                description=f"**{deck['name']}** does not have a decklist"))
            return
        decklist = await self._get_deck(ctx, deck['link'])
        if not decklist:
            return
        commanders = " & ".join(
            [commander['name'] for commander in decklist['commanders']])
        image_uris = scryfall.get_image_uris(decklist['commanders'][0])
        emsg = embed.info(
            title=commanders,
            description=f"[Link to Decklist]({deck['link']})"
        ) \
                    .set_thumbnail(url=image_uris['art_crop'])
        for category in decklist['decklist']:
            category_name = category['category']
            count = sum([card['count'] for card in category['cards']])
            cards = [
                f"{card['count']} {card['name']}" for card in category['cards']
            ]
            emsg.add_field(name=f"{category_name} ({count})",
                           value="\n".join(cards))
        await ctx.send(embed=emsg)
Beispiel #13
0
 async def command_help(self, ctx, name):
     command = self.bot.get_command(name)
     if not command:
         await ctx.send(embed=embed.error(description="Command not found"))
         return
     if command.name == "help":
         return
     emsg = embed.info(title=f"Command: {command.qualified_name}")
     emsg.add_field(name="Usage",
                    inline=False,
                    value=command.usage.format(ctx.prefix))
     emsg.add_field(name="Description", inline=False, value=command.help)
     if command.aliases:
         aliases = ", ".join(
             [f"`{ctx.prefix}{alias}`" for alias in command.aliases])
         emsg.add_field(name="Aliases", inline=False, value=aliases)
     await ctx.send(embed=emsg)
Beispiel #14
0
    async def top(self, ctx):
        """Display the league leaderboard. Specify a key to sort by total wins, win %, games, or points."""

        if ctx.invoked_subcommand is None:
            limit = utils.DEFAULT_LIMIT
            players = self.bot.db.find_top_members_by("points",
                                                      ctx.message.guild,
                                                      limit=limit)
            print(players)
            if not players:
                await ctx.send(embed=embed.info(
                    description="No players found with enough games played."))
                return
            _tables = utils.make_leaderboard_table(players, 'points',
                                                   'Top Players by Points')
            for _table in _tables.text:
                await ctx.send(_table)
Beispiel #15
0
    async def recent(self, ctx, *args):
        """Show your last 10 matches. If a number is specified, show that many matches instead."""

        limit = utils.get_command_arg(args, "limit", 10)
        if (type(limit) is not int):
            await ctx.send(embed=embed.error(description=f"Limit should be a number."))
            return

        users = utils.get_target_users(ctx)
        for user in users:
            if not self.bot.db.find_member(user.id, ctx.message.guild):
                continue
            matches = self.bot.db.find_user_matches(user.id, ctx.message.guild, limit=limit)
            if not matches:
                await ctx.send(embed=embed.info(description=f"No matches found for **{user.name}**"))
                continue
            _line_table = self._make_match_tables(ctx, user, matches)
            for _table in _line_table.text:
                await ctx.send(_table)
Beispiel #16
0
    async def _top_score(self, ctx, *args):
        """Display the top 10 players in the league by points. If a limit is specified, display that many players instead. If a min is specified, display players with at least that many games played."""

        try:
            limit, min_games = await self.get_top_args(ctx, args)
        except ValueError:
            return
        players = self.bot.db.find_top_members_by("points",
                                                  ctx.message.guild,
                                                  limit=limit,
                                                  threshold=min_games)
        if not players:
            await ctx.send(embed=embed.info(
                description="No players found with enough games played."))
            return
        _tables = utils.make_leaderboard_table(players, 'points',
                                               'Top Players by Points')
        for _table in _tables.text:
            await ctx.send(_table)
Beispiel #17
0
    async def _games_by_decks(self, ctx, *, deck_names: str = ""):
        """Displays a list of games filtered by decks. Include a comma-separated list of decks to filter by."""

        if not deck_names:
            await ctx.send(
                embed=embed.error(ctx, description="No deck name included"))
            return
        deck_name_list = deck_names.split(',')
        if len(deck_name_list) > 4:
            await ctx.send(embed=embed.error(
                ctx, description="Games cannot contain more than 4 decks"))
            return

        deck_names = []
        for deck_name in deck_name_list:
            deck = self.bot.db.find_deck(deck_name)
            if not deck:
                continue
            deck_names.append(deck['name'])

        if not deck_names:
            await ctx.send(embed=embed.error(
                ctx, description="No decks found with the given deck names"))
            return
        matches = self.bot.db.find_matches(
            {"players.deck": {
                "$all": deck_names
            }},
            ctx.message.guild,
            limit=20)
        matches = list(matches)
        if not matches:
            await ctx.send(embed=embed.info(
                description=("No matches found containing " +
                             ", ".join(deck_names))))
            return
        title = "Games Containing: " + ", ".join(deck_names)
        emsgs = self._make_match_table(title, matches, winner_type="deck")
        for emsg in emsgs:
            await ctx.send(embed=emsg)
Beispiel #18
0
    async def deck(self, ctx, *, deck_name: str = ""):
        """Displays detail info about a registered deck."""

        if not deck_name:
            await ctx.send(embed=embed.error(
                description="No deck name specified"))
            return
        deck = self.bot.db.find_deck(deck_name)
        if not deck:
            await ctx.send(embed=embed.error(
                description=f"{deck_name} was not found"))
            return
        matches = list(
            self.bot.db.find_matches({"players.deck": deck['name']},
                                     ctx.message.guild))
        match_stats = self._get_match_stats(ctx, matches, deck['name'])
        if matches:
            match_history = self._make_match_history_table(
                matches[:5], deck['name'])
        else:
            match_history = "`N/A`"

        card = scryfall.search(deck['commanders'][0])
        image_uris = scryfall.get_image_uris(card)
        emsg = embed.info(title=f"Deck: {deck['name']}") \
                    .add_field(name="Commanders", value=("\n".join(deck['commanders']))) \
                    .add_field(name="Aliases", value=("\n".join(deck['aliases']))) \
                    .add_field(name="# Matches", value=match_stats['entries']) \
                    .add_field(name="Meta %", value=match_stats['meta']) \
                    .add_field(name="Wins", value=match_stats['wins']) \
                    .add_field(name="Losses", value=match_stats['losses']) \
                    .add_field(name="Win %", value=match_stats['winrate']) \
                    .add_field(name="95% Confidence Interval", value=match_stats['confint']) \
                    .add_field(name="Recent Matches", value=match_history) \
                    .set_thumbnail(url=image_uris['small'])
        await ctx.send(embed=emsg)
Beispiel #19
0
    async def deckstats(self, ctx, *, sort_key: str = ""):
        """Displays the records of all decks tracked by the league. Data displayed includes meta share, win %, wins, and popularity. A deck is required to have at least 10 games recorded in order to show up in the stats.
        
        Games played is the number of times a deck has been logged. 
        The meta share is the percentage of time a deck is logged and is proportional to games played.
        Win % should be self-explanatory. 
        Popularity is represented by the number of unique pilots that have logged matches with the deck.
        If a user is mentioned, display only deckstats for that user.
        If a deck name is given, display deckstats for all users that have played that deck.

        By default, this command sorts the results by meta share but displays with wins and losses of each deck. Include one of the other keys to sort by those columns instead."""

        # Use a line_table instead of a block_table for better mobile experience
        # Display only the selected stat and the sample size
        # Leave detail statistical analysis in the deck info command
        if not ctx.message.mentions:
            data = utils.get_match_stats(ctx)
        else:
            await self.display_player_deck_stats(ctx, sort_key)
            return
        if not data:
            emsg = embed.error(
                description="No decks found with enough matches")
            await ctx.send(embed=emsg)
            return

        if not sort_key:
            sorted_data = utils.sort_by_entries(data)
            _tables = self._make_deck_tables(sorted_data, "winloss")
            for _table in _tables.text:
                await ctx.send(_table)
            return

        # Check if the sort_key is a deck name
        # If it is a deck name, get deckstats by player for that deck
        deck = self.bot.db.find_deck(sort_key)
        if deck:
            data = self.bot.db.find_matches_with_deck(deck["name"],
                                                      ctx.message.guild,
                                                      limit=0,
                                                      season=None)
            _tables = self._make_full_deck_player_tables(data, deck["name"])
            if not _tables:
                await ctx.send(embed=embed.info(
                    description="No matches found with the given deck"))
                return
            for _table in _tables:
                await ctx.send(_table)
            return

        if sort_key.lower() == "winrate":
            sorted_data = utils.sort_by_winrate(data)
            _tables = self._make_deck_tables(sorted_data, "winrate")
        elif sort_key.lower() == "meta":
            sorted_data = utils.sort_by_entries(data)
            _tables = self._make_deck_tables(sorted_data, "meta")
        elif sort_key.lower() == "popularity":
            sorted_data = utils.sort_by_unique_players(data)
            _tables = self._make_deck_tables(sorted_data, "popularity")
        else:
            sorted_data = utils.sort_by_winrate(data)
            _tables = self._make_complete_deck_tables(sorted_data)
            for _table in _tables:
                await ctx.send(_table)
            return
        for _table in _tables.text:
            await ctx.send(_table)