Exemple #1
0
class Graphs(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('histogram'))
    async def histogram(self, ctx, *args):
        user_id = ctx.message.author.id

        if len(args) == 0: args = check_account(user_id)(args)

        if len(args) != 1:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(
                    ctx, ctx.message).parameters(f"{ctx.invoked_with} [user]"))
            return

        player = get_player(user_id, args[0])
        if escape_sequence(player):
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    (f"[**{player}**]({Urls().user(player, 'play')}) "
                     "doesn't exist")))
            return
        conn = sqlite3.connect(DATABASE_PATH)
        c = conn.cursor()
        try:
            player_data = c.execute(f"SELECT wpm FROM t_{player}")
            data = [i[0] for i in player_data.fetchall()]
        except sqlite3.OperationalError:
            conn.close()
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).not_downloaded())
            return
        conn.close()

        ax = plt.subplots()[1]
        max_, min_ = max(data), min(data)

        if int(max_ - min_) // 10 == 0:
            patches = ax.hist(data, bins=1)[2]
        else:
            patches = ax.hist(data, bins=int(max_ - min_) // 10)[2]

        ax.set_xlabel('WPM')
        ax.set_ylabel('Frequency')
        plt.grid(True)
        ax.set_title(f"{player}'s WPM Histogram")
        file_name = f"{player} WPM.png"

        graph_colors = get_graph_colors(user_id)
        graph_color(ax, graph_colors, False, patches)
        plt.savefig(file_name, facecolor=ax.figure.get_facecolor())
        wpm_picture = discord.File(file_name, filename=file_name)
        await ctx.send(file=wpm_picture)
        os.remove(file_name)
        plt.close()
        return

    @commands.cooldown(3, 15, commands.BucketType.user)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('boxplot'))
    async def boxplot(self, ctx, *args):
        user_id = ctx.message.author.id

        if len(args) == 0: args = check_account(user_id)(args)

        if len(args) < 1 or len(args) > 4:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).parameters(
                    f"{ctx.invoked_with} [user] <user_2>...<user_4>"))
            return

        args = list(args)
        for i, player in enumerate(args):
            args[i] = get_player(user_id, args[i])
            if escape_sequence(player):
                await ctx.send(
                    content=f"<@{user_id}>",
                    embed=Error(ctx, ctx.message).missing_information(
                        (f"[**{player}**]({Urls().user(player, 'play')}) "
                         "doesn't exist")))
                return

        conn = sqlite3.connect(DATABASE_PATH)
        c = conn.cursor()
        data = []
        try:
            title_text = ''
            for user in args:
                title_text += f"{user} vs. "
                user_data = c.execute(f"SELECT wpm FROM t_{user}")
                temp = [i[0] for i in user_data]
                data.append(temp)
        except sqlite3.OperationalError:
            conn.close()
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).not_downloaded())
            return
        conn.close()
        title_text = title_text[:-4]
        title_text += 'WPM'

        ax = plt.subplots()[1]
        ax.boxplot(data, showfliers=False)
        ax.set_xticklabels(list(args))
        ax.set_ylabel('WPM')
        ax.set_title(title_text)
        plt.grid(True)
        file_name = f"{title_text}.png"

        graph_colors = get_graph_colors(user_id)
        graph_color(ax, graph_colors, True)
        plt.savefig(file_name, facecolor=ax.figure.get_facecolor())
        wpm_picture = discord.File(file_name, filename=file_name)

        await ctx.send(file=wpm_picture)
        os.remove(file_name)
        plt.close()
        return

    @commands.cooldown(3, 25, commands.BucketType.user)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('raceline') + ['pointline'] +
                      get_aliases('pointline'))
    async def raceline(self, ctx, *args):
        user_id = ctx.message.author.id

        rl = ctx.invoked_with.lower() in ['raceline'] + get_aliases('raceline')
        pl = ctx.invoked_with.lower() in ['pointline'
                                          ] + get_aliases('pointline')

        units = 'Races' if rl else 'Points'
        retroactive = ctx.invoked_with[-1] == '*' and pl

        if len(args) == 0: args = check_account(user_id)(args)

        if len(args) < 1 or len(args) > 10:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).parameters(
                    f"{ctx.invoked_with} [user] <user_2>...<user_10>"))
            return
        today = time.time()

        start, end = 0, 0
        if len(args) > 1:
            try:
                args[0].index('-')
                start = (
                    datetime.datetime.strptime(args[0], "%Y-%m-%d").date() -
                    datetime.date(1970, 1, 1)).total_seconds()
                if start <= 1_250_000_000 or start > time.time():
                    raise ValueError
                args = args[1:]
            except ValueError:
                pass
Exemple #2
0
class Help(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
        self.normalized_commands = {}
        self.command_embeds = {}
        self.main_embed = None
        self.info_embed = None
        self.invite_embed = None
        self.donate_embed = None
        self.perks_embed = None
        with open(os.path.dirname(__file__) + '/../src/commands.json',
                  'r') as jsonfile:
            self.bot_commands = json.load(jsonfile)

    def create_embeds(self, bot, message):
        command_prefix = get_prefix(bot, message)

        for command in [
                command for category in self.bot_commands.values()
                for command in category
        ]:
            name = command['name']
            for alias in command['aliases']:
                self.normalized_commands.update({alias: name})
            self.normalized_commands.update({name: name})
            self.command_embeds.update(
                {name: embed_constructor(command, command_prefix)})

        self.main_embed = discord.Embed(
            title='Help Page',
            color=discord.Color(HELP_BLACK),
            description=
            ('[`Invite`](https://discord.com/oauth2/authorize?client_id=742267194443956334&permissions=378944&scope=bot) - '
             '[`top.gg`](https://top.gg/bot/742267194443956334) - '
             '[`GitHub`](https://github.com/fiveoutofnine/TypeRacerStats) - '
             '[`PayPal`](https://www.paypal.me/e3e2)\n\n'
             f"**Run `{command_prefix}help [command]` to learn more**\n"
             "`[ ]` represent required paramaters\n"
             "`< >` represent optional parameters"))
        self.main_embed.set_thumbnail(url=HELP_IMG)
        self.main_embed.add_field(name='Info Commands',
                                  value=value_formatter(
                                      self.bot_commands['info'],
                                      command_prefix),
                                  inline=False)
        self.main_embed.add_field(name='Configuration Commands',
                                  value=value_formatter(
                                      self.bot_commands['configuration'],
                                      command_prefix),
                                  inline=False)
        self.main_embed.add_field(name='Basic Commands',
                                  value=value_formatter(
                                      self.bot_commands['basic'],
                                      command_prefix),
                                  inline=False)
        self.main_embed.add_field(
            name=f"Advanced Commands (all require `{command_prefix}getdata`)",
            value=value_formatter(self.bot_commands['advanced'],
                                  command_prefix),
            inline=False)
        self.main_embed.add_field(name='Other Commands',
                                  value=value_formatter(
                                      self.bot_commands['other'],
                                      command_prefix),
                                  inline=False)
        self.main_embed.set_footer(
            text=f"Run {command_prefix}help [command] to learn more")

    def create_info_embed(self, bot, message):
        command_prefix = get_prefix(bot, message)

        with open(os.path.dirname(__file__) + '/../info.txt', 'r') as txtfile:
            info = txtfile.read().split('\n\n')
            info = info[0].split('\n') + info[1:]
            info = [
                i.replace('\\n', '\n').replace('{PFX}', command_prefix)
                for i in info
            ]

        self.info_embed = discord.Embed(title=info[0],
                                        color=discord.Color(int(info[1])),
                                        description=info[2])
        self.info_embed.set_thumbnail(url=info[3])

        for i in range(4, len(info) - 1):
            paragraph = info[i].split(':::')
            self.info_embed.add_field(name=paragraph[0],
                                      value=paragraph[1],
                                      inline=False)
        self.info_embed.set_footer(text=info[-1])

    def create_invite_embed(self):
        guilds = self.bot.guilds
        server_count = len(guilds)
        people_count = 0
        for guild in guilds:
            try:
                people_count += guild.member_count
            except AttributeError:
                pass

        self.invite_embed = discord.Embed(
            title="TypeRacerStats Invite Link",
            color=HELP_BLACK,
            description=
            '[**Invite Link**](https://discord.com/oauth2/authorize?client_id=742267194443956334&permissions=378944&scope=bot)'
        )
        self.invite_embed.add_field(
            name='Stats',
            value=
            f"Serving {f'{people_count:,}'} people in {f'{server_count:,}'} servers"
        )

    def create_donate_embed(self):
        description = '[**Star on GitHub тнР**](https://github.com/e6f4e37l/TypeRacerStats)\n'
        description += '[**Vote for TypeRacerStats on `top.gg` ЁЯЧ│я╕П**](https://top.gg/bot/742267194443956334)\n'
        description += '[**Support on PayPal тЭдя╕П**](https://www.paypal.me/e3e2)'

        self.donate_embed = discord.Embed(
            title="TypeRacerStats Donation/Support",
            color=HELP_BLACK,
            description=description)
        self.donate_embed.add_field(
            name='Perks (USD)',
            value=
            ('**Tier 0: $1.00 - $2.99**\n'
             'Thanks for your support!\n\n'
             '**Tier 1: $3.00 - $5.99**\n'
             'Name listed on `info` command and access to `echo` command\n\n'
             '**Tier 2: $6.00 - $11.99**\n'
             'Set custom embed color with `setcolor`\n\n'
             '**Tier 3: $12.00 - $17.99**\n'
             'Custom command added to the bot and set custom colors for graphs with `setgraphcolor`\n\n'
             '**Tier 4: $18.00+**\n'
             'Access to commands via bot DM'))
        self.donate_embed.set_footer(text='One month of hosting costs 6 USD')

    def create_perks_embed(self, bot, message):
        command_prefix = get_prefix(bot, message)

        value = value_formatter(self.bot_commands['supporter'], command_prefix)

        self.perks_embed = discord.Embed(
            title="Supporter Commands",
            color=HELP_BLACK,
            description=
            'These commands only work for those that have supported the project. Refer to the `support` command for more information.'
        )
        self.perks_embed.set_thumbnail(url=HELP_IMG)
        self.perks_embed.add_field(name='Commands', value=value)

    @commands.Cog.listener()
    async def on_message(self, message):
        if message.author.id == self.bot.user.id:
            return

        if str(self.bot.user.id) in message.content:
            if message.author.id in BOT_ADMIN_IDS:
                await message.channel.send('Who @ me.')
                return
            prefix = get_prefix(self.bot, message)
            await message.channel.send(embed=discord.Embed(
                color=discord.Color(HELP_BLACK),
                title=f"The prefix is `{prefix}`",
                description=f"`{prefix}setprefix [prefix]`\n`{prefix}help`"))
            return

    @commands.check(lambda ctx: check_banned_status(ctx))
    @commands.command(aliases=get_aliases('help'))
    async def help(self, ctx, *args):
        self.create_embeds(ctx, ctx.message)

        if args:
            try:
                await ctx.send(embed=self.command_embeds[
                    self.normalized_commands[''.join(args).lower()]])
                return
            except KeyError:
                await ctx.send(
                    content=
                    f"<@{ctx.message.author.id}> **Command not found. Refer to the commands below:**",
                    embed=self.main_embed)
                return
        await ctx.send(embed=self.main_embed)

    @commands.command(aliases=get_aliases('info'))
    async def info(self, ctx, *args):
        self.create_info_embed(ctx, ctx.message)

        if len(args) != 0: return
        await ctx.send(embed=self.info_embed)

    @commands.check(lambda ctx: check_banned_status(ctx))
    @commands.command(aliases=get_aliases('invite'))
    async def invite(self, ctx, *args):
        self.create_invite_embed()

        if len(args) != 0: return
        await ctx.send(embed=self.invite_embed)

    @commands.command(aliases=get_aliases('support'))
    async def support(self, ctx, *args):
        self.create_donate_embed()

        if len(args) != 0: return
        await ctx.send(embed=self.donate_embed)

    @commands.check(lambda ctx: check_banned_status(ctx))
    @commands.command(aliases=get_aliases('perks'))
    async def perks(self, ctx, *args):
        self.create_perks_embed(ctx, ctx.message)

        if len(args) != 0: return
        await ctx.send(embed=self.perks_embed)

    @commands.command(aliases=['servers'])
    @commands.check(lambda ctx: ctx.message.author.id in BOT_OWNER_IDS and
                    not ctx.guild and check_banned_status(ctx))
    async def listservers(self, ctx):
        guilds = []
        for guild in self.bot.guilds:
            try:
                if guild.member_count:
                    guilds.append(guild)
            except AttributeError:
                pass
        guilds = sorted(guilds, key=lambda x: x.member_count, reverse=True)
        guilds_data = [['Name', "Member Count", 'Guild ID']]
        people_count, server_count = 0, 0
        for guild in guilds:
            guilds_data.append([guild.name, guild.member_count, guild.id])
            people_count += guild.member_count
            server_count += 1

        with open('servers.csv', 'w') as csvfile:
            writer = csv.writer(csvfile)
            writer.writerows(guilds_data)

        embed = discord.Embed(
            title='List of Servers TypeRacerStats is in',
            color=discord.Color(0),
            description=
            f"Serving {f'{people_count:,}'} people in {f'{server_count:,}'} servers"
        )
        for i, guild in enumerate(guilds[0:20]):
            embed.add_field(
                name=guild.name,
                value=(
                    f"{i + 1}. {f'{guild.member_count:,}'} members "
                    f"({round(100 * guild.member_count / people_count, 2)}%)"),
                inline=False)

        await ctx.send(embed=embed,
                       file=discord.File('servers.csv',
                                         f"server_list_{time.time()}.csv"))
        os.remove('servers.csv')
        return
Exemple #3
0
class AdvancedStats(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @commands.cooldown(1, 5, commands.BucketType.user)
    @commands.cooldown(20, 100, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('top') + get_aliases('worst') +
                      ['worst'])
    async def top(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) <= 1: args = check_account(user_id)(args)

        if len(args) == 1: args += ('wpm', )

        if len(args) != 2:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).parameters(
                    f"{ctx.invoked_with} [user] [wpm/points/weightedwpm]"))
            return

        player = get_player(user_id, args[0])
        if escape_sequence(player):
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    (f"[**{player}**]({Urls().user(player, 'play')}) "
                     "doesn't exist")))
            return

        categories = ['wpm', 'points', 'weightedwpm']

        if not args[1].lower() in categories:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               '`category` must be `wpm/points/weightedwpm`'))
            return

        if args[1].lower() == 'points':
            category = 'pts'
        elif args[1].lower() == 'wpm':
            category = 'wpm'
        else:
            category = 'weightedwpm'

        texts_length = load_texts_json()
        conn = sqlite3.connect(DATABASE_PATH)
        c = conn.cursor()
        try:
            if ctx.invoked_with in ['top'] + get_aliases('top'):
                order_by, reverse = 'DESC', True
            else:
                order_by, reverse = 'ASC', False
            if category != 'weightedwpm':
                user_data = c.execute(
                    f"SELECT * FROM t_{player} ORDER BY {category} {order_by} LIMIT 10"
                )
                user_data = user_data.fetchall()
            else:
                raw_data = c.execute(f"SELECT * FROM t_{player}")
                user_data = sorted(
                    raw_data,
                    key=lambda x: x[3] * texts_length[str(x[2])]['length'],
                    reverse=reverse)[:10]
        except sqlite3.OperationalError:
            conn.close()
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).not_downloaded())
            return
        conn.close()

        embed = discord.Embed(
            title=
            f"{player}'s {['Worst', 'Top'][reverse]} {len(user_data)} Races (Lagged)",
            color=discord.Color(MAIN_COLOR))
        embed.set_thumbnail(url=Urls().thumbnail(player))

        formatted_category = {
            'pts': 'points',
            'wpm': 'WPM',
            'weightedwpm': 'WPM'
        }[category]
        texts = load_texts_large()
        for i, race in enumerate(user_data):
            value = f"{texts.get(str(race[2]), 'Missing Text')} [:cinema:]({Urls().result(player, race[0], 'play')})"
            if formatted_category == 'points':
                name = f"{i + 1}. {race[4]} {formatted_category} (Race #{f'{race[0]:,}'}, Text ID: {race[2]})"
            else:
                name = f"{i + 1}. {race[3]} {formatted_category} (Race #{f'{race[0]:,}'}, Text ID: {race[2]})"
            embed.add_field(name=name, value=value, inline=False)
            if category == 'weightedwpm':
                embed.set_footer(
                    text='Sorted by weighted WPM (text_length • wpm)')
        await ctx.send(embed=embed)
        return

    @commands.cooldown(1, 5, commands.BucketType.user)
    @commands.cooldown(20, 100, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('racedetails'))
    async def racedetails(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) == 0: args = check_account(user_id)(args)

        if len(args) != 1:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(
                    ctx, ctx.message).parameters(f"{ctx.invoked_with} [user]"))
            return

        player = get_player(user_id, args[0])
        if escape_sequence(player):
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    (f"[**{player}**]({Urls().user(player, 'play')}) "
                     "doesn't exist")))
            return

        texts_length = load_texts_json()
        races, words_typed, chars_typed, points, retro, time_spent, total_wpm = (
            0, ) * 7
        conn = sqlite3.connect(DATABASE_PATH)
        c = conn.cursor()

        try:
            user_data = c.execute(f"SELECT * FROM t_{player}")
            first_race = user_data.fetchone()
            first_point_race = 0
            text_id = str(first_race[2])
            races += 1
            total_wpm += first_race[3]
            words_typed += texts_length.get(text_id, {"word count": 0})['word count']
            chars_typed += texts_length.get(text_id, {"length": 0})['length']
            fastest_race, slowest_race = (first_race[3],
                                          first_race[0]), (first_race[3],
                                                           first_race[0])
            try:
                time_spent += 12 * texts_length.get(text_id, {"length": 0})['length'] / first_race[3]
            except ZeroDivisionError:
                pass
            if first_race[4] == 0:
                retro += first_race[3] / 60 * texts_length.get(text_id, {"word count": 0})['word count']
            else:
                if not first_point_race:
                    first_point_race = first_race[1]
                points += first_race[4]
            first_race = first_race[1]
            for row in user_data:
                race_wpm = row[3]
                total_wpm += race_wpm
                if race_wpm > fastest_race[0]:
                    fastest_race = (race_wpm, row[0])
                if race_wpm < slowest_race[0]:
                    slowest_race = (race_wpm, row[0])
                text_id = str(row[2])
                races += 1
                words_typed += texts_length.get(text_id, {'word count': 0})['word count']
                chars_typed += texts_length.get(text_id, {'length': 0})['length']
                if row[4] == 0:
                    retro += race_wpm / 60 * texts_length.get(text_id, {'word count': 0})['word count']
                else:
                    if not first_point_race:
                        first_point_race = row[1]
                    points += row[4]
                try:
                    time_spent += 12 * texts_length.get(text_id, {'length': 0})['length'] / race_wpm
                except ZeroDivisionError:
                    races -= 1
                    pass
        except sqlite3.OperationalError:
            conn.close()
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).not_downloaded())
            return
        conn.close()

        today = time.time()
        num_days = (today - first_race) / 86400
        num_point_days = (today - first_point_race) / 86400

        embed = discord.Embed(title=f"Race Details for {player}",
                              color=discord.Color(MAIN_COLOR))
        embed.set_thumbnail(url=Urls().thumbnail(player))
        embed.set_footer(
            text=('Retroactive points represent the total number of points '
                  'a user would have gained, before points were introduced '
                  'in 2017'))

        embed.add_field(
            name='Races',
            value=
            (f"**Total Races:** {f'{races:,}'}\n"
             f"**Average Daily Races:** {f'{round(races / num_days, 2):,}'}\n"
             f"**Total Words Typed:** {f'{words_typed:,}'}\n"
             f"**Average Words Per Race:** {f'{round(words_typed / races, 2):,}'}\n"
             f"**Total Chars Typed:** {f'{chars_typed:,}'}\n"
             f"**Average Chars Per Race: **{f'{round(chars_typed / races, 2):,}'}\n"
             ))
        embed.add_field(
            name='Points',
            value=
            (f"**Current Points:** {f'{round(points):,}'}\n"
             f"**Average Daily Points:** {f'{round(points / num_point_days, 2):,}'}\n"
             f"**Average Points Per Race:** {f'{round((points + retro) / races, 2):,}'}\n"
             f"**Retroactive Points:** {f'{round(retro):,}'}\n"
             f"**Total Points:** {f'{round(points + retro):,}'}"))
        embed.add_field(
            name='Speed',
            value=
            (f"**Average (Lagged):** {f'{round(total_wpm / races, 2):,}'} WPM\n"
             f"**Fastest Race:** {f'{fastest_race[0]:,}'} WPM "
             f"[:cinema:]({Urls().result(player, fastest_race[1], 'play')})\n"
             f"**Slowest Race:** {f'{slowest_race[0]:,}'} WPM "
             f"[:cinema:]({Urls().result(player, slowest_race[1], 'play')})"),
            inline=False)
        embed.add_field(
            name='Time',
            value=
            (f"**Total Time Spent Racing:** {seconds_to_text(time_spent)}\n"
             f"**Average Daily Time:** {seconds_to_text(time_spent / num_days)}\n"
             f"**Average Time Per Race:** {seconds_to_text(time_spent / races)}"
             ))
        await ctx.send(embed=embed)
        return

    @commands.cooldown(1, 5, commands.BucketType.user)
    @commands.cooldown(20, 100, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('longestbreak'))
    async def longestbreak(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) == 0: args = check_account(user_id)(args)

        if len(args) == 0 or len(args) > 2:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).parameters(
                               f"{ctx.invoked_with} [user] <seconds>"))
            return

        player = get_player(user_id, args[0])
        if escape_sequence(player):
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    (f"[**{player}**]({Urls().user(player, 'play')}) "
                     "doesn't exist")))
            return

        duration = 0
        if len(args) == 2:
            try:
                duration = int(args[1])
                if duration <= 0 or duration > 1_000_000_000_000:
                    raise ValueError
            except ValueError:
                await ctx.send(
                    content=f"<@{user_id}>",
                    embed=Error(ctx, ctx.message).incorrect_format(
                        '`seconds` must be a positive number less than 1,000,000,000,000'
                    ))
                return

        count, longest_break = (0, ) * 2
        longest_break_gn, longest_break_time = (1, ) * 2
        conn = sqlite3.connect(DATABASE_PATH)
        c = conn.cursor()
        try:
            user_data = c.execute(f"SELECT t, gn FROM t_{player} ORDER BY t")
            previous_time, previous_gn = user_data.fetchone()
            for row in user_data:
                break_ = row[0] - previous_time
                if break_ >= duration: count += 1
                if break_ > longest_break:
                    longest_break = break_
                    longest_break_gn = previous_gn
                    longest_break_time = previous_time
                previous_time, previous_gn = row
        except sqlite3.OperationalError:
            conn.close()
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).not_downloaded())
            return
        conn.close()

        break_ = time.time() - previous_time
        on_break = break_ > longest_break
        if break_ >= duration: count += 1
        if on_break:
            longest_break = break_
            longest_break_gn = previous_gn
            longest_break_time = previous_time

        if duration:
            await ctx.send(embed=discord.Embed(
                color=discord.Color(MAIN_COLOR),
                description=
                f"**{player}** had **{f'{count:,}'}** breaks longer than **{seconds_to_text(duration)}**"
            ))
            return

        embed = discord.Embed(
            color=discord.Color(MAIN_COLOR),
            description=
            (f"**{player}**'s longest break was **{seconds_to_text(longest_break)}**, "
             f"and it started on race **{f'{longest_break_gn:,}'}** "
             f" / **{datetime.datetime.fromtimestamp(longest_break_time).strftime('%B %-d, %Y, %-I:%M:%S %p')}**"
             ))
        if on_break: embed.set_footer(text='Currently on break')

        await ctx.send(embed=embed)
        return
Exemple #4
0
                      shadow=True,
                      ncol=1)
        file_name = f"{units} Over Time.png"

        graph_colors = get_graph_colors(user_id)
        graph_color(ax, graph_colors, False)
        plt.savefig(file_name, facecolor=ax.figure.get_facecolor())
        over_time_picture = discord.File(file_name, filename=file_name)

        await ctx.send(file=over_time_picture)
        os.remove(file_name)
        plt.close()
        return

    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('improvement'))
    async def improvement(self, ctx, *args):
        user_id = ctx.message.author.id

        if len(args) == 0: args = check_account(user_id)(args)

        if len(args) == 1: args += ('races', )

        if len(args) != 2:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).parameters(
                               f"{ctx.invoked_with} [user] <time/races>"))
            return

        player = get_player(user_id, args[0])
Exemple #5
0
class BotAdmin(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @commands.command(aliases=['perish', 'unban', 'banned'])
    @commands.check(lambda ctx: ctx.message.author.id in BOT_ADMIN_IDS and
                    check_banned_status(ctx))
    async def ban(self, ctx, *args):
        user_id = ctx.message.author.id

        if ctx.invoked_with == 'banned':
            conn = sqlite3.connect(DATABASE_PATH)
            c = conn.cursor()

            banned_users = c.execute(
                f"SELECT * FROM {USERS_KEY} WHERE banned = 1").fetchall()
            conn.close()

            description, banned = '', [['ID']]
            for i, user in enumerate(banned_users):
                id_ = user[0]

                if i < 10:
                    description += f"**{i + 1}.** <@{id_}>\n"
                banned.append([id_])

            description = description[:-1]
            embed = discord.Embed(title='Banned Users',
                                  color=discord.Color(0),
                                  description=description)

            if len(banned) > 10:
                with open('banned_users.csv', 'w') as csvfile:
                    writer = csv.writer(csvfile)
                    writer.writerows(banned)

                file_ = discord.File('banned_users.csv', 'banned_users.csv')

                await ctx.send(file=file_, embed=embed)
                os.remove('banned_users.csv')
                return

            await ctx.send(embed=embed)
            return

        if len(args) != 1:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).parameters(
                               f"{ctx.invoked_with} [discord_id]"))
            return

        def perms(id_):
            if id_ in BOT_OWNER_IDS:
                return 2
            if id_ in BOT_ADMIN_IDS:
                return 1
            return 0

        try:
            args = (args[0].strip('<@!').strip('>'), )
            if len(args[0]) > 18 or escape_sequence(args[0]):
                raise ValueError
            discord_id = int(args[0])
            if perms(discord_id) >= perms(user_id):
                raise commands.CheckFailure
                return
        except ValueError:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               f"<@{args[0]}> is not a valid Discord ID"))
            return

        conn = sqlite3.connect(DATABASE_PATH)
        c = conn.cursor()
        try:
            user_data = c.execute(f"SELECT * FROM {USERS_KEY} WHERE id = ?",
                                  (discord_id, )).fetchall()
            if not user_data:
                toggled_to = True
                c.execute(f"INSERT INTO {USERS_KEY} (id, banned) VALUES(?, ?)",
                          (
                              discord_id,
                              toggled_to,
                          ))
            else:
                toggled_to = not user_data[0][1]
                c.execute(f"UPDATE {USERS_KEY} SET banned = ? WHERE id = ?", (
                    toggled_to,
                    discord_id,
                ))
            conn.commit()
            conn.close()
        except sqlite3.OperationalError:
            c.execute(
                f"CREATE TABLE {USERS_KEY} (id integer PRIMARY KEY, banned BOOLEAN)"
            )
            conn.close()
            return

        setting = 'banned' if toggled_to else 'unbanned'
        await ctx.send(
            embed=discord.Embed(color=discord.Color(0),
                                description=(
                                    f"<@{discord_id}> has been **{setting}**\n"
                                    'from using <@742267194443956334>')))
        return
class Christmas_2020(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
        self.send_request.start()
        self.font = ImageFont.truetype('Arial.ttf', size=48)
        self.words = ['filler']
        self.multiplier = 1

    def cog_load(self):
        self.send_request.start()

    def cog_unload(self):
        self.send_request.cancel()

    @tasks.loop(seconds=120)  #seconds = 180
    async def send_request(self):
        random_duration = random.randint(0, 30)  #random.randint(0, 120)
        await asyncio.sleep(random_duration)

        embed = discord.Embed(
            title=
            '<:santavalikor:793468488714944562> Santa Says: I need some toys!',
            color=discord.Color(0x9700),
            description='**Quickly, type the toys for Santa!**')

        embed.set_footer(
            text='Santa will only accept toys crafted with 100% accuracy!')

        file_name = 'christmas_2020.png'
        img = Image.new('RGB', (1000, 1000), color=(47, 49, 54))
        draw = ImageDraw.Draw(img)

        gifts = [
            random.choice(self.words) for _ in range(random.randint(5, 20))
        ]  #random.randint(3, 10)
        text = ' '.join(gifts)

        cur_text, cur_width, total_height = '', 0, 0
        for word in text.split(' '):
            word_ = f"{word} "
            width, height = self.font.getsize(word_)
            cur_width += width
            if cur_width > 980:
                draw.text((10, total_height),
                          cur_text,
                          font=self.font,
                          fill=(215, 216, 217))
                cur_text = word_
                cur_width = width
                total_height += height
            else:
                cur_text += word_
        draw.text((10, total_height),
                  cur_text,
                  font=self.font,
                  fill=(215, 216, 217))
        total_height += height

        img = img.crop((0, 0, 1000, total_height + height * 0.5))
        img.save(file_name, 'png')

        file_ = discord.File(file_name, filename=file_name)

        embed.set_image(url=f"attachment://{file_name}")

        christmas_channel = self.bot.get_channel(
            793478667056578621)  #christmas-bot-usage channel ID

        request_message = await christmas_channel.send(file=file_, embed=embed)
        start = time.time()
        os.remove(file_name)

        try:
            msg = await self.bot.wait_for('message',
                                          check=check(text),
                                          timeout=len(text))
            user_id = msg.author.id
            time_taken = time.time() - start
            time_remaining = len(text) - time_taken
            points = round(
                self.multiplier * time_remaining * len(text) / 100) + 1

            conn = sqlite3.connect(DATABASE_PATH)
            c = conn.cursor()
            try:
                c.execute(
                    f"INSERT INTO {CHRISTMAS_KEY} (id, name, cookies, gifts) VALUES (?, ?, ?, ?)",
                    (user_id, f"{msg.author.name}#{msg.author.discriminator}",
                     points, len(gifts)))
                conn.commit()
                conn.close()
            except sqlite3.OperationalError:
                c.execute(
                    f"CREATE TABLE {CHRISTMAS_KEY} (id integer, name, cookies, gifts)"
                )
                conn.close()

            embed = discord.Embed(
                title=
                f"<:santavalikor:793468488714944562> Thank you @{msg.author.name}!",
                color=discord.Color(0x9700),
                description=
                (f"You made **{len(gifts)}** gifts in **{round(time_taken, 3)}**\n"
                 f"seconds for **{f'{points:,}'}** :cookie:!"))
            embed.set_image(
                url=
                'https://cdn.discordapp.com/emojis/793468488869740544.png?v=1')
            await request_message.edit(content=f"<@{user_id}>", embed=embed)
        except asyncio.TimeoutError:
            embed = discord.Embed(title='Time ran out :(',
                                  color=discord.Color(0xFF0000))
            embed.set_image(
                url=
                'https://cdn.discordapp.com/emojis/793509718185607199.png?v=1')
            await request_message.edit(embed=embed)

    @commands.check(lambda ctx: check_banned_status(ctx))
    @commands.command(aliases=['clb', 'christmaslb'])
    async def christmasleaderboard(self, ctx):
        conn = sqlite3.connect(DATABASE_PATH)
        c = conn.cursor()

        user_data = c.execute(f"""SELECT name, SUM(cookies), SUM(gifts)
                                  FROM {CHRISTMAS_KEY}
                                  GROUP BY id
                                  ORDER BY SUM(cookies) DESC""").fetchall()
        conn.close()

        description = ''
        for i, user in enumerate(user_data[:10]):
            description += f"{NUMBERS[i]} {user[0]} - {f'{user[1]:,}'} :cookie:\n"
        description = description[:-1]

        embed = discord.Embed(title=':star2: Santa\'s Best Elves :star2:',
                              color=discord.Color(0xFF0000),
                              description=description)

        adjectives = ['made', 'crafted', 'fabricated', 'manifested']
        embed.set_footer(text=(
            f"{f'{len(user_data):,}'} elves {random.choice(adjectives)} "
            f"{f'{sum([i[2] for i in user_data]):,}'} gifts and\n"
            f"accumulated {f'{sum([i[1] for i in user_data]):,}'} cookies so far!"
        ))

        if ctx.message.author.id in BOT_OWNER_IDS:
            christmas_lb_data = [['name', 'cookies', 'gifts']]
            for user in user_data:
                christmas_lb_data.append(list(user))

            with open('christmas_2020.csv', 'w') as csvfile:
                writer = csv.writer(csvfile)
                writer.writerows(christmas_lb_data)

            file_ = discord.File('christmas_2020.csv', 'christmas_2020.csv')

            await ctx.send(file=file_, embed=embed)
            os.remove('christmas_2020.csv')
            return

        await ctx.send(embed=embed)

    @commands.check(lambda ctx: ctx.message.author.id in BOT_OWNER_IDS and
                    check_banned_status(ctx))
    @commands.command(aliases=['uw'])
    async def updatewords(self, ctx):
        words = ((await fetch(['https://pastebin.com/raw/ynB6UtBP'],
                              'text'))[0].split('\n'))
        words_filtered = [word.strip() for word in words if word]
        self.words = words_filtered

        await ctx.send(embed=discord.Embed(
            title='Christmas 2020 Word List Updated', color=discord.Color(0)))

    @commands.check(lambda ctx: ctx.message.author.id in BOT_OWNER_IDS and
                    check_banned_status(ctx))
    @commands.command(aliases=['sm'])
    async def setmultiplier(self, ctx, *args):
        if len(args) != 1: return

        try:
            multiplier = float(args[0])
            if multiplier <= 0 or multiplier > 20:
                raise ValueError
        except ValueError:
            await ctx.send('keegan pls')
            return

        self.multiplier = multiplier

        await ctx.send(
            embed=discord.Embed(title=f"Multiplier Updated to x{multiplier}",
                                color=discord.Color(0)))
Exemple #7
0
class GetData(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @commands.cooldown(1, 7200, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('getdata'))
    async def getdata(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) == 0: args = check_account(user_id)(args)

        if len(args) != 1:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(
                    ctx, ctx.message).parameters(f"{ctx.invoked_with} [user]"))
            return

        player = get_player(user_id, args[0])
        if escape_sequence(player):
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    (f"[**{player}**]({Urls().user(player, 'play')}) "
                     "doesn't exist")))
            return

        urls = [Urls().get_races(player, 'play', 1)]
        try:
            api_response = await fetch(urls, 'json')
            total_races = int(api_response[0][0]['gn'])
        except:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    (f"[**{player}**]({Urls().user(player, 'play')}) "
                     "doesn't exist or has no races")))
            return

        conn = sqlite3.connect(DATABASE_PATH)
        c = conn.cursor()
        try:
            user_data = c.execute(
                f"SELECT * FROM t_{player} ORDER BY t DESC LIMIT 1")
            last_race = user_data.fetchone()
            last_race_timestamp = last_race[1]
            races_remaining = total_races - last_race[0]
        except sqlite3.OperationalError:
            races_remaining = total_races
            if races_remaining == 0:
                conn.close()
                await ctx.send(content=f"<@{user_id}>",
                               embed=Error(ctx,
                                           ctx.message).missing_information(
                                               f"{player} has no races"))
                return
            else:
                if races_remaining > 10000 and not user_id in BOT_ADMIN_IDS:
                    pass
                else:
                    c.execute(
                        f"CREATE TABLE t_{player} (gn integer PRIMARY KEY, t, tid, wpm, pts)"
                    )
        if races_remaining > 10000 and not user_id in BOT_ADMIN_IDS:
            conn.close()
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).lacking_permissions(
                               ('Data request exceeds 10,000 races. '
                                'Have a bot admin run the command.')))
            return
        if races_remaining == 0:
            conn.close()
            await ctx.send(embed=discord.Embed(
                title='Data Request',
                color=discord.Color(MAIN_COLOR),
                description=(f"{player}'s data successfully created/updated\n"
                             '0 races added')))
            return

        start_ = time.time()
        await ctx.send(embed=discord.Embed(
            title='Data Request',
            color=discord.Color(MAIN_COLOR),
            description=
            ('Request successful\n'
             f"Estimated download time: {seconds_to_text(0.005125 * races_remaining + 0.5)}"
             )))

        try:
            data = await fetch_data(player, 'play', last_race_timestamp + 0.01,
                                    time.time())
        except UnboundLocalError:
            data = await fetch_data(player, 'play', 1204243200, time.time())
        c.executemany(f"INSERT INTO t_{player} VALUES (?, ?, ?, ?, ?)", data)
        conn.commit()
        conn.close()

        length = round(time.time() - start_, 3)
        await ctx.send(
            content=f"<@{user_id}>",
            embed=discord.Embed(
                title='Data Request',
                color=discord.Color(MAIN_COLOR),
                description=(f"{player}'s data successfully created/updated\n"
                             f"{f'{races_remaining:,}'} races added\n"
                             f"Took {seconds_to_text(length)}")))
        return

    @commands.cooldown(5, 7200, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('today'))
    async def today(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) == 0 or (len(args) == 1 and '-' in args[0]):
            args = check_account(user_id)(args)

        is_today = True
        today_timestamp = (datetime.datetime.utcnow().date() -
                           datetime.date(1970, 1, 1)).total_seconds()

        if ctx.invoked_with.lower() in ['yesterday', 'yday', 'yd']:
            today_timestamp = (datetime.datetime.utcnow().date() -
                               datetime.date(1970, 1, 2)).total_seconds()
            is_today = False
            if len(args) > 1 or len(args) == 0:
                await ctx.send(
                    content=f"<@{user_id}>",
                    embed=Error(
                        ctx,
                        ctx.message).parameters(f"{ctx.invoked_with} [user]"))
                return

        if len(args) == 0 or len(args) > 2:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).parameters(
                               f"{ctx.invoked_with} [user] <yyyy-mm-dd>"))
            return

        if len(args) == 2:
            try:
                today_timestamp_temp = (
                    datetime.datetime.strptime(args[1], "%Y-%m-%d").date() -
                    datetime.date(1970, 1, 1)).total_seconds()
                if today_timestamp_temp != today_timestamp: is_today = False
                if today_timestamp_temp > today_timestamp:
                    await ctx.send(content=f"<@{user_id}>",
                                   embed=Error(
                                       ctx, ctx.message).incorrect_format(
                                           '`date` must not exceed today'))
                    return
                today_timestamp = today_timestamp_temp
            except ValueError:
                await ctx.send(content=f"<@{user_id}>",
                               embed=Error(ctx, ctx.message).incorrect_format(
                                   '`date` must be in the yyyy-mm-dd format'))
                return

        player = get_player(user_id, args[0])
        if escape_sequence(player):
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    (f"[**{player}**]({Urls().user(player, 'play')}) "
                     "doesn't exist")))
            return

        urls = [Urls().get_races(player, 'play', 1)]
        try:
            api_response = await fetch(urls, 'json')
            total_races = int(api_response[0][0]['gn'])
        except:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    (f"[**{player}**]({Urls().user(player, 'play')}) "
                     "doesn't exist or has no races")))
            return

        file_name = f"t_{player}_play_{today_timestamp}_{today_timestamp + 86400}".replace(
            '.', '_')
        conn = sqlite3.connect(TEMPORARY_DATABASE_PATH)
        c = conn.cursor()
        try:
            user_data = c.execute(
                f"SELECT * FROM {file_name} ORDER BY t DESC LIMIT 1")
            last_race = user_data.fetchone()
            if last_race:
                last_race_timestamp = last_race[1]
                races_remaining = total_races - last_race[0]
            else:
                races_remaining = total_races
        except sqlite3.OperationalError:
            races_remaining = total_races
            if races_remaining == 0:
                conn.close()
                await ctx.send(content=f"<@{user_id}>",
                               embed=Error(ctx,
                                           ctx.message).missing_information(
                                               f"{player} has no races"))
            else:
                c.execute(
                    f"CREATE TABLE {file_name} (gn integer PRIMARY KEY, t, tid, wpm, pts)"
                )

        try:
            data = await fetch_data(player, 'play', last_race_timestamp + 0.01,
                                    today_timestamp + 86400)
        except UnboundLocalError:
            data = await fetch_data(player, 'play', today_timestamp,
                                    today_timestamp + 86400)

        date = datetime.datetime.fromtimestamp(today_timestamp).strftime(
            '%B %-d, %Y')

        user_is_leader = await self.check_if_leader(player, 'day')
        if user_is_leader and is_today:
            embed = discord.Embed(
                title=f"{date} Stats for {player}",
                color=discord.Color(MAIN_COLOR),
                url=Urls().user(player, 'play'),
                description=':crown: **Daily Leader** :crown:')
        else:
            embed = discord.Embed(title=f"{date} Stats for {player}",
                                  color=discord.Color(MAIN_COLOR),
                                  url=Urls().user(player, 'play'))
        embed.set_thumbnail(url=Urls().thumbnail(player))

        if data:
            c.executemany(f"INSERT INTO {file_name} VALUES (?, ?, ?, ?, ?)",
                          data)

        conn.commit()
        data = c.execute(f"SELECT * FROM {file_name}").fetchall()
        conn.close()
        if not data:
            embed.add_field(name='Average Speed', value='—')
            embed.add_field(name='Races', value='0')
            embed.add_field(name='Points', value='0')
            await ctx.send(embed=embed)
            return

        texts_data = load_texts_json()
        races, wpm, points, seconds_played, chars_typed, words_typed = (
            0, ) * 6
        fastest_race, slowest_race = (data[0][3], data[0][0]), (data[0][3],
                                                                data[0][0])
        for row in data:
            races += 1
            race_text_id = str(row[2])
            race_wpm = row[3]
            wpm += race_wpm
            if race_wpm > fastest_race[0]: fastest_race = (race_wpm, row[0])
            if race_wpm < slowest_race[0]: slowest_race = (race_wpm, row[0])
            points += row[4]
            word_count = texts_data.get(race_text_id, {"word count": 0})['word count']
            race_text_length = texts_data.get(race_text_id, {"length": 0})['length']
            seconds_played += 12 * race_text_length / race_wpm
            chars_typed += race_text_length
            words_typed += word_count

        average_wpm = round(wpm / races, 2)
        total_points = round(points)
        embed.add_field(
            name='Summary',
            value=
            (f"**Average Speed:** {average_wpm} WPM "
             f"([{slowest_race[0]}]({Urls().result(player, slowest_race[1], 'play')})"
             f" - [{fastest_race[0]}]({Urls().result(player, fastest_race[1], 'play')}))\n"
             f"**Total Races:** {f'{races:,}'}\n"
             f"**Total Points:** {f'{total_points:,}'} ({f'{round(points / races, 2)}'} points/race)"
             ),
            inline=False)
        embed.add_field(
            name='Details',
            value=
            (f"**Total Words Typed:** {f'{words_typed:,}'}\n"
             f"**Average Words Per Race:** {round(words_typed / races, 2)}\n"
             f"**Total Chars Typed:** {f'{chars_typed:,}'}\n"
             f"**Average Chars Per Race:** {round(chars_typed / races, 2)}\n"
             f"**Total Time Spent Racing:** {seconds_to_text(seconds_played)}\n"
             f"**Average Time Per Race:** {seconds_to_text(seconds_played / races)}"
             ),
            inline=False)

        await ctx.send(embed=embed)
        return

    @commands.cooldown(5, 7200, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('week') + ['month'] +
                      get_aliases('month') + ['year'] + get_aliases('year'))
    async def week(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        week = ctx.invoked_with in ['week'] + get_aliases('week')
        month = ctx.invoked_with in ['month'] + get_aliases('month')
        year = ctx.invoked_with in ['year'] + get_aliases('year')

        if len(args) == 0: args = check_account(user_id)(args)

        if len(args) == 0 or len(args) > 2:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).parameters(
                               f"{ctx.invoked_with} [user] <date>"))
            return

        same_day = True
        today = datetime.datetime.utcnow().date()
        if len(args) == 2:
            try:
                parse_string = '%Y'
                date_format = len(args[1].split('-'))
                if date_format == 1:
                    pass
                elif date_format == 2:
                    parse_string += '-%m'
                elif date_format == 3:
                    parse_string += '-%m-%d'
                else:
                    raise ValueError

                today_temp = datetime.datetime.strptime(args[1],
                                                        parse_string).date()
                if today_temp != today: same_day = False
                if today_temp > today:
                    await ctx.send(content=f"<@{user_id}>",
                                   embed=Error(
                                       ctx, ctx.message).incorrect_format(
                                           '`date` must not exceed today'))
                    return
                today = today_temp
            except ValueError:
                await ctx.send(content=f"<@{user_id}>",
                               embed=Error(ctx, ctx.message).incorrect_format(
                                   '`date` must be in the yyyy-mm-dd format'))
                return

        if week:
            normalizer = today.isocalendar()[2]
            start_time = today - datetime.timedelta(days=normalizer - 1)
            end_time = today + datetime.timedelta(days=7 - normalizer)
            formatted_sort = 'Weekly'
        elif month:
            start_time = today.replace(day=1)
            end_time = (today.replace(day=1) + datetime.timedelta(days=32)
                        ).replace(day=1) - datetime.timedelta(days=1)
            formatted_sort = 'Monthly'
        elif year:
            start_time = datetime.date(today.year, 1, 1)
            end_time = datetime.date(today.year, 12, 31)
            formatted_sort = 'Yearly'

        delta_start = start_time
        delta = end_time - start_time
        start_time = (start_time - datetime.date(1970, 1, 1)).total_seconds()
        end_time = (end_time - datetime.date(1970, 1, 1)).total_seconds()
        end_time += 86400

        player = get_player(user_id, args[0])
        if escape_sequence(player):
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    (f"[**{player}**]({Urls().user(player, 'play')}) "
                     "doesn't exist")))
            return

        urls = [Urls().get_races(player, 'play', 1)]
        try:
            api_response = await fetch(urls, 'json')
        except:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    (f"[**{player}**]({Urls().user(player, 'play')}) "
                     "doesn't exist or has no races")))
            return

        file_name = f"t_{player}"
        conn = sqlite3.connect(DATABASE_PATH)
        c = conn.cursor()
        try:
            user_data = c.execute(
                f"SELECT * FROM t_{player} ORDER BY t DESC LIMIT 1")
            last_race_timestamp = user_data.fetchone()[1]
        except sqlite3.OperationalError:
            conn.close()
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).not_downloaded())
            return

        data = await fetch_data(player, 'play', last_race_timestamp + 0.01,
                                end_time)

        if data:
            c.executemany(f"INSERT INTO {file_name} VALUES (?, ?, ?, ?, ?)",
                          data)

        conn.commit()
        data = c.execute(
            f"""SELECT * FROM {file_name}
                             WHERE t > ? AND t < ?""", (
                start_time,
                end_time,
            )).fetchall()
        conn.close()

        if week:
            day_one = datetime.datetime.fromtimestamp(start_time).day
            day_two = datetime.datetime.fromtimestamp(end_time - 86400).day
            if day_one > day_two: format_string = '%B %-d, %Y'
            else: format_string = '%-d, %Y'
            title = (
                f"Weekly ({datetime.datetime.fromtimestamp(start_time).strftime('%B %-d')}—"
                f"{datetime.datetime.fromtimestamp(end_time - 86400).strftime(format_string)})"
            )
        elif month:
            title = f"Monthly ({datetime.datetime.fromtimestamp(start_time).strftime('%B %Y')})"
        elif year:
            title = f"Yearly ({datetime.datetime.fromtimestamp(start_time).strftime('%Y')})"

        title += f" Stats for {player}"
        user_is_leader = await self.check_if_leader(
            player,
            formatted_sort.lower()[:-2])
        if user_is_leader and same_day:
            embed = discord.Embed(
                title=title,
                color=discord.Color(MAIN_COLOR),
                url=Urls().user(player, 'play'),
                description=f":crown: **{formatted_sort} Leader** :crown:")
        else:
            embed = discord.Embed(title=title,
                                  color=discord.Color(MAIN_COLOR),
                                  url=Urls().user(player, 'play'))
        embed.set_thumbnail(url=Urls().thumbnail(player))

        if not data:
            embed.add_field(name='Average Speed', value='—')
            embed.add_field(name='Races', value='0')
            embed.add_field(name='Points', value='0')
            await ctx.send(embed=embed)
            return

        texts_length = load_texts_json()

        csv_dict = {}
        for i in range(delta.days + 1):
            csv_dict.update({
                (delta_start + datetime.timedelta(days=i)).isoformat(): {
                    'races': 0,
                    'words_typed': 0,
                    'chars_typed': 0,
                    'points': 0,
                    'time_spent': 0,
                    'average_wpm': 0,
                    'best_wpm': 0,
                    'worst_wpm': 0
                }
            })

        races, words_typed, chars_typed, points, retro, time_spent = (0, ) * 6
        wpm_total, wpm_best, wpm_worst = (0, ) * 3
        for row in data:
            date = datetime.datetime.fromtimestamp(row[1]).date().isoformat()
            text_id = str(row[2])
            wpm = row[3]
            races += 1
            words_typed_ = texts_length.get(text_id, {"word count": 0})['word count']
            chars_typed_ = texts_length.get(text_id, {"length": 0})['length']
            words_typed += words_typed_
            chars_typed += chars_typed_

            wpm_total += wpm
            if not wpm_best or wpm_best < wpm: wpm_best = wpm
            if not wpm_worst or wpm_worst > wpm: wpm_worst = wpm

            csv_day = csv_dict[date]
            csv_day['races'] += 1
            csv_day['words_typed'] += words_typed_
            csv_day['chars_typed'] += chars_typed_
            csv_day['average_wpm'] = (csv_day['average_wpm'] *\
                                     (csv_day['races'] - 1) + wpm) /\
                                     csv_day['races']
            if not csv_day['best_wpm'] or csv_day['best_wpm'] < wpm:
                csv_day['best_wpm'] = wpm
            if not csv_day['worst_wpm'] or csv_day['worst_wpm'] > wpm:
                csv_day['worst_wpm'] = wpm

            if row[4] == 0:
                retro_ = row[3] / 60 * texts_length.get(text_id, {"word count": 0})['word count']
                retro += retro_
                csv_day['points'] += row[4]
            else:
                points += row[4]
                csv_day['points'] += row[4]
            try:
                time_spent_ = 12 * texts_length.get(text_id, {"length": 0})['length'] / row[3]
                time_spent += time_spent_
                csv_day['time_spent'] += time_spent_
            except ZeroDivisionError:
                races -= 1
                csv_day['races'] -= 1
                pass

        today = time.time() if time.time() < end_time else end_time
        num_days = (today - start_time) / 86400

        retro_text = f"**Retroactive Points:** {f'{round(retro):,}'}\n" if retro else ""

        if retro_text:
            embed.set_footer(text=(
                'Retroactive points represent the total number of points '
                'a user would have gained, before points were introduced '
                'in 2017'))

        embed.add_field(
            name='Races',
            value=
            (f"**Total Races:** {f'{races:,}'}\n"
             f"**Average Daily Races:** {f'{round(races / num_days, 2):,}'}\n"
             f"**Total Words Typed:** {f'{words_typed:,}'}\n"
             f"**Average Words Per Race:** {f'{round(words_typed / races, 2):,}'}\n"
             f"**Total Chars Typed:** {f'{chars_typed:,}'}\n"
             f"**Average Chars Per Race: **{f'{round(chars_typed / races, 2):,}'}"
             ))
        embed.add_field(
            name='Points',
            value=
            (f"**Points:** {f'{round(points):,}'}\n"
             f"**Average Daily Points:** {f'{round(points / num_days, 2):,}'}\n"
             f"**Average Points Per Race:** {f'{round((points + retro) / races, 2):,}'}\n"
             f"{retro_text}"
             f"**Total Points:** {f'{round(points + retro):,}'}"))
        embed.add_field(
            name='Speed',
            value=
            (f"**Average (Lagged):** {f'{round(wpm_total / races, 2):,}'} WPM\n"
             f"**Fastest Race:** {f'{wpm_best:,}'} WPM\n"
             f"**Slowest Race:** {f'{wpm_worst:,}'} WPM"),
            inline=False)
        embed.add_field(
            name='Time',
            value=
            (f"**Total Time Spent Racing:** {seconds_to_text(time_spent)}\n"
             f"**Average Daily Time:** {seconds_to_text(time_spent / num_days)}\n"
             f"**Average Time Per Race:** {seconds_to_text(time_spent / races)}"
             ))

        if ctx.invoked_with[-1] == '*':
            csv_data = [['date'] + list(next(iter(csv_dict.values())).keys())]
            for key, value in csv_dict.items():
                values = [round(i, 2) for i in list(value.values())]
                csv_data.append([key] + values)

            with open('temporary.csv', 'w') as csvfile:
                writer = csv.writer(csvfile)
                writer.writerows(csv_data)

            title_ = title.split(' (')
            await ctx.send(file=discord.File(
                'temporary.csv',
                f"{player}_{title_[0].lower()}_{title_[1].split(')')[0].lower()}.csv"
            ),
                           embed=embed)
            os.remove('temporary.csv')
            return

        await ctx.send(embed=embed)
        return

    async def check_if_leader(self, user, kind):
        urls = [Urls().get_competition(1, kind, 'points', 'play')]
        competition = await fetch(urls, 'json')
        competition = competition[0]

        return competition[0][1]['typeracerUid'][3:] == user
Exemple #8
0
class Other(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
        with open(KEYMAPS_SVG, 'r') as txtfile:
            self.svg_start = txtfile.read()

    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('unixreference'))
    async def unixreference(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) > 1:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(
                    ctx,
                    ctx.message).parameters(f"{ctx.invoked_with} <timestamp>"))
            return

        if len(args) == 0:
            embed = discord.Embed(title="Unix Timestamp Conversions",
                                  color=discord.Color(MAIN_COLOR))
            embed.set_footer(text="All converted times are in UTC")
            embed.add_field(name="Times",
                            value=('1.25e9 - August 11, 2009\n'
                                   '1.30e9 - March 13, 2011\n'
                                   '1.35e9 - October 12, 2012\n'
                                   '1.40e9 - May 13, 2014\n'
                                   '1.45e9 - December 13, 2015\n'
                                   '1.50e9 - July 14, 2017\n'
                                   '1.55e9 - February 12, 2019\n'
                                   '1.60e9 - September 13, 2020\n'
                                   '1.65e9 - April 15, 2022'))
            await ctx.send(embed=embed)
            return

        try:
            time = int(args[0])
            await ctx.send(embed=discord.Embed(
                color=discord.Color(MAIN_COLOR),
                description=datetime.datetime.fromtimestamp(time).strftime(
                    "%B %d, %Y, %-I:%M:%S %p")))
            return
        except ValueError:
            try:
                scientific_notation_lst = args[0].lower().split('e')
                await ctx.send(embed=discord.Embed(
                    color=discord.Color(MAIN_COLOR),
                    description=datetime.datetime.fromtimestamp(
                        float(scientific_notation_lst[0]) *
                        10**(int(scientific_notation_lst[1]))).strftime(
                            "%B %d, %Y, %-I:%M:%S %p")))
                return
            except:
                await ctx.send(
                    content=f"<@{user_id}>",
                    embed=Error(ctx, ctx.message).incorrect_format(
                        '`timestamp` must be an integer or scientific notation (e.g. 1.0365e9)'
                    ))
                return

    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('serverinfo'))
    async def serverinfo(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) != 0:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx,
                            ctx.message).parameters(f"{ctx.invoked_with}"))
            return

        embed = discord.Embed(title=f"Server Information for {ctx.guild.name}",
                              color=discord.Color(MAIN_COLOR),
                              description=ctx.guild.description)
        embed.set_thumbnail(url=ctx.guild.icon_url)
        embed.add_field(
            name='Stats',
            value=(
                f"**Owner:** <@{ctx.guild.owner_id}>\n"
                f"**Region:** {ctx.guild.region}\n"
                f"**Created At:** {ctx.guild.created_at}\n"
                f"**Member Count:** {f'{ctx.guild.member_count:,}'}\n"
                f"**Text Channels:** {f'{len(ctx.guild.text_channels):,}'}\n"
                f"**Roles:** {f'{len(ctx.guild.roles):,}'}"))
        embed.set_image(url=ctx.guild.banner_url)

        await ctx.send(embed=embed)
        return

    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('art'))
    async def art(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) > 1:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(
                    ctx,
                    ctx.message).parameters(f"{ctx.invoked_with} <artist>"))
            return

        with open(ART_JSON, 'r') as jsonfile:
            works = json.load(jsonfile)

        artists = list(works.keys())
        if len(args) == 1:
            artist = args[0].lower()
            if artist == '*':
                await ctx.send(
                    file=discord.File(ART_JSON, f"typeracer_art.json"))
                return
            if artist not in artists:
                artists_ = ''
                for artist_ in artists:
                    artists_ += f"`{artist_}`, "
                await ctx.send(
                    content=f"<@{user_id}>",
                    embed=Error(ctx, ctx.message).incorrect_format(
                        f"Must provide a valid artist: {artists_[:-2]}"))
                return
            works, trid = works[artist]['art'], works[artist]['trid']
            work = random.choice(works)
        else:
            works_ = []
            for key, value in works.items():
                for art_work in value['art']:
                    works_.append({
                        'artist': key,
                        'trid': value['trid'],
                        'title': art_work['title'],
                        'url': art_work['url']
                    })
            work = random.choice(works_)
            artist, trid = work['artist'], work['trid']

        title = work['title'] if work['title'] else "Untitled"

        embed = discord.Embed(title=title, color=discord.Color(MAIN_COLOR))
        embed.set_author(name=artist,
                         url=Urls().user(trid, 'play'),
                         icon_url=Urls().thumbnail(trid))
        embed.set_image(url=work['url'])

        await ctx.send(embed=embed)
        return

    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('clip'))
    async def clip(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) != 1:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(
                    ctx, ctx.message).parameters(f"{ctx.invoked_with} [clip]"))
            return

        with open(CLIPS_JSON, 'r') as jsonfile:
            clips = json.load(jsonfile)

        if len(args) == 1:
            clip = args[0].lower()
            if clip == '*':
                await ctx.send(file=discord.File(CLIPS_JSON, f"clips.json"))
                return
            try:
                clip_url = clips[clip]
            except KeyError:
                calls = list(clips.keys())
                calls_ = ''
                for clip_ in calls:
                    calls_ += f"`{clip_}`, "
                await ctx.send(
                    content=f"<@{user_id}>",
                    embed=Error(ctx, ctx.message).incorrect_format(
                        f"Must provide a valid clip: {calls_[:-2]}"))
                return

        await ctx.send(clip_url)
        return

    @commands.check(lambda ctx: check_banned_status(ctx))
    @commands.command(aliases=get_aliases('botleaderboard'))
    async def botleaderboard(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)
        conn = sqlite3.connect(DATABASE_PATH)
        c = conn.cursor()

        def combine_aliases(cmds_data):
            cmds_dict = dict()
            for cmd in cmds_data:
                try:
                    normalized_name = normalized_commands[cmd[0]]
                except KeyError:
                    normalized_name = cmd[0]

                try:
                    cmds_dict[normalized_name] += cmd[1]
                except KeyError:
                    cmds_dict[normalized_name] = cmd[1]

            return cmds_dict

        if ctx.invoked_with[-1] == '*':
            if len(args) == 0:
                user_count = len(
                    c.execute(
                        f"SELECT DISTINCT id FROM {TABLE_KEY}").fetchall())
                command_count = len(
                    c.execute(f"SELECT * FROM {TABLE_KEY}").fetchall())

                conn.close()
                await ctx.send(embed=discord.Embed(
                    color=discord.Color(MAIN_COLOR),
                    description=
                    f"**{f'{user_count:,}'}** users have used **{f'{command_count:,}'}** commands"
                ))
                return
            elif len(args) == 1:
                command = args[0].lower()
                if command == '*':
                    command = 'All Commands'
                    user_data = c.execute(f"""SELECT command, COUNT(command)
                                              FROM {TABLE_KEY}
                                              GROUP BY command""")
                    user_data = combine_aliases(user_data)
                    user_data = [[f"`{key}`", value]
                                 for key, value in user_data.items()]
                else:
                    try:
                        command = normalized_commands[command]
                        aliases = [command] + get_aliases(command)
                        user_data = []
                        for alias in aliases:
                            alias_data = c.execute(
                                f"""SELECT name, COUNT(id)
                                                        FROM
                                                            (SELECT * FROM {TABLE_KEY} WHERE command = ?)
                                                        GROUP BY id""",
                                (alias, ))
                            user_data += alias_data.fetchall()
                        user_data = combine_aliases(user_data)
                        user_data = [[key, value]
                                     for key, value in user_data.items()]

                    except KeyError:
                        user_data = ()

                conn.close()

            user_data = sorted(user_data, key=lambda x: x[1], reverse=True)

            value = ''
            for i, user in enumerate(user_data[:10]):
                value += f"{NUMBERS[i]} {user[0]} - {f'{user[1]:,}'}\n"
            value = value[:-1]

            if not value:
                await ctx.send(
                    content=f"<@{user_id}>",
                    embed=Error(ctx, ctx.message).incorrect_format(
                        f"`{command}` is not a command or has never been used")
                )
                return

            embed = discord.Embed(
                title=f"Bot Usage Leaderboard (`{command}` command)",
                color=discord.Color(MAIN_COLOR),
                description=value)

            embed.set_footer(text='Since December 24, 2020')
            await ctx.send(embed=embed)
            return

        if len(args) > 1:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).parameters(
                               f"{ctx.invoked_with} <discord_id>"))
            return

        if len(args) == 0:
            user_data = c.execute(f"""SELECT name, COUNT(id)
                                      FROM {TABLE_KEY}
                                      GROUP BY id
                                      ORDER BY COUNT(id) DESC LIMIT 10"""
                                  ).fetchall()
            conn.close()

            value = ''
            for i, user in enumerate(list(user_data)):
                value += f"{NUMBERS[i]} {user[0]} - {f'{user[1]:,}'}\n"
            value = value[:-1]

            embed = discord.Embed(title='Bot Usage Leaderboard',
                                  color=discord.Color(MAIN_COLOR),
                                  description=value)
        else:
            args = (args[0].strip('<@!').strip('>'), )
            try:
                if len(args[0]) > 18:
                    raise ValueError
                id_ = int(args[0])
                if escape_sequence(args[0].lower()):
                    raise ValueError
            except ValueError:
                await ctx.send(content=f"<@{user_id}>",
                               embed=Error(ctx, ctx.message).incorrect_format(
                                   f"**{args[0]}** is not a valid Discord ID"))
                return

            user_data = []
            users_cmd_data = c.execute(
                f"""SELECT command, COUNT(command), name
                                          FROM
                                            (SELECT * FROM {TABLE_KEY} WHERE ID = ?)
                                          GROUP BY command""",
                (id_, )).fetchall()
            conn.close()
            if users_cmd_data:
                name = users_cmd_data[-1][2]
            else:
                name = id_
            user_data = combine_aliases(users_cmd_data)
            user_data = sorted([[key, value]
                                for key, value in user_data.items()],
                               key=lambda x: x[1],
                               reverse=True)
            title = f"Bot Statistics for {name}"

            value, count = '', 0
            for i, cmd in enumerate(user_data):
                count += cmd[1]
                if i < 10:
                    cmds = i + 1
                    value += f"**{cmds}.** `{cmd[0]}` - {f'{cmd[1]:,}'}\n"

            embed = discord.Embed(
                title=title,
                color=discord.Color(MAIN_COLOR),
                description=f"**Used:** {f'{count:,}'} times")
            if value:
                embed.add_field(name=f"Top {cmds} Most Used", value=value)

        embed.set_footer(text='Since December 24, 2020')
        await ctx.send(embed=embed)
        return

    @commands.check(lambda ctx: check_banned_status(ctx))
    @commands.command(aliases=get_aliases('updates'))
    async def updates(self, ctx, *args):
        user_id = ctx.message.author.id

        if user_id in BOT_OWNER_IDS and len(args) > 0:
            request = args[0].lower()
            if request == 'get':
                file_ = discord.File(CHANGELOG, 'changelog.json')
                await ctx.send(file=file_)
                return
            elif request == 'post':
                try:
                    updated_file_raw = ctx.message.attachments[0]
                except IndexError:
                    await ctx.send(
                        content=f"<@{user_id}>",
                        embed=Error(ctx, ctx.message).incorrect_format(
                            'Please upload a file and comment the command call'
                        ))
                    return

                try:
                    updated_file = json.loads(await updated_file_raw.read())
                except json.JSONDecodeError:
                    await ctx.send(
                        content=f"<@{user_id}>",
                        embed=Error(ctx, ctx.message).incorrect_format(
                            'The uploaded file is not a properly formatted JSON file'
                        ))
                    return

                with open(CHANGELOG, 'w') as jsonfile:
                    json.dump(updated_file, jsonfile, indent=4)

                await ctx.send(embed=discord.Embed(title='Records Updated',
                                                   color=discord.Color(0)))
                return

        with open(CHANGELOG, 'r') as jsonfile:
            changelog = json.load(jsonfile)

        embed = discord.Embed(**changelog)
        updates = []
        for update in changelog['updates'][:10]:
            name = update['name']
            value = update['description']
            date = (datetime.datetime.utcnow() -
                    datetime.datetime.strptime(update['date'], '%Y-%m-%d %I:%M %p'))\
                .total_seconds()

            updates.append({
                'name': name,
                'value': value,
                'date': abs(date),
                'inline': False
            })

        updates = sorted(updates, key=lambda x: x['date'])
        for update in updates:
            date = update['date']
            del update['date']
            if date > 86400:
                fdate = f"{int(date // 86400)} day(s)"
            elif date > 3600:
                fdate = f"{int(date // 3600)} hour(s)"
            else:
                fdate = f"{int(date // 60)} minute(s)"
            update['value'] += f"\n_Updated {fdate} ago._"
            embed.add_field(**update)

        await ctx.send(embed=embed)

    @commands.check(lambda ctx: check_banned_status(ctx))
    @commands.cooldown(1, 600, commands.BucketType.default)
    @commands.command(aliases=get_aliases('keymap'))
    async def keymap(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) != 0:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx,
                            ctx.message).parameters(f"{ctx.invoked_with}"))
            return

        if len(ctx.message.attachments) == 0:
            embed = discord.Embed(
                color=discord.Color(MAIN_COLOR),
                description=
                'Edit the JSON file with your personal keymap!\n0 for left pinky, 1 for left ring, ..., 9 for right pinky.\nMultiple colors may be inputted.'
            )
            await ctx.send(content=f"<@{user_id}>",
                           embed=embed,
                           file=discord.File(BLANK_KEYMAP,
                                             f"keymap_template.json"))
            return

        with open(BLANK_KEYMAP, 'r') as jsonfile:
            keymap_template = json.load(jsonfile)
        try:
            user_data = json.loads(await ctx.message.attachments[0].read())
            if user_data.keys() != keymap_template.keys():
                raise ValueError
        except json.JSONDecodeError:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).incorrect_format(
                    'The uploaded file is not a properly formatted JSON file'))
            return
        except ValueError:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               'Please do not modify the labels'))
            return

        def id_generator():
            num = 0
            while True:
                yield num
                num += 1

        id_generator = id_generator()

        default_color = '#474448'
        colors = [
            '#ffadad', '#ffd6a5', '#fdffb6', '#caffbf', '#9bf6ff', '#a0c4ff',
            '#bdb2ff', '#ffc6ff', '#d7e3fc', '#fffffc'
        ]

        svg = self.svg_start
        labels = {}
        key_colors = {}
        for key, value in user_data.items():
            user_colors = sorted(
                list(
                    set([
                        color for color in value
                        if isinstance(color, int) and (
                            0 <= color and color <= 9)
                    ])))
            if len(user_colors) == 0:
                fill = default_color
            elif len(user_colors) == 1:
                fill = colors[user_colors[0]]
            else:
                try:
                    fill_id = key_colors[''.join([str(i)
                                                  for i in user_colors])]
                except KeyError:
                    fill_id = next(id_generator)
                    increment_size = 100 / len(user_colors)
                    lin_gradient = f"""<linearGradient id="{fill_id}" x2="100%" y2="0%">"""
                    for i, color in enumerate(user_colors):
                        fill_color = colors[color]
                        lin_gradient += (
                            f"""<stop offset="{round(i * increment_size, 2)}%" """
                            f"""stop-color="{fill_color}"/>"""
                            f"""<stop offset="{round((i + 1) * increment_size, 2)}%" """
                            f"""stop-color="{fill_color}"/>""")
                    lin_gradient += '</linearGradient>'
                    svg += lin_gradient
                    key_colors.update(
                        {''.join([str(i) for i in user_colors]): fill_id})
                fill = f"url(#{fill_id})"
            labels.update({key: fill})
        svg += '</defs>'

        labels = iter(labels.items())

        def return_key(width, x, y):
            label, fill = next(labels)
            label = label.split(' ')
            text_color = fill == '#474448'
            small_size = len(label) == 2 or len(label[0]) * 6 > width
            text_class = [['', 's'], ['w', 'z']][text_color][small_size]
            text_class = f' class="{text_class}"' if text_class else ''
            rect = f'<rect width="{width}" x="{x}" y="{y}" fill="{fill}"/>'
            if len(label) == 2:
                rect += f'<text x="{x + width / 2}" y="{y + 14}"{text_class}>{label[0]}</text>'
                rect += f'<text x="{x + width / 2}" y="{y + 6}"{text_class}>{label[1]}</text>'
            else:
                rect += f'<text x="{x + width / 2}" y="{y + 10}"{text_class}>{label[0]}</text>'

            return rect

        for i in range(13):
            svg += return_key(20, 22 * i, 0)
        svg += return_key(30, 286, 0)
        svg += return_key(30, 0, 21)
        for i in range(13):
            svg += return_key(20, 32 + 22 * i, 21)
        svg += return_key(36, 0, 42)
        for i in range(11):
            svg += return_key(20, 38 + 22 * i, 42)
        svg += return_key(36, 280, 42)
        svg += return_key(47, 0, 63)
        for i in range(10):
            svg += return_key(20, 49 + 22 * i, 63)
        svg += return_key(47, 269, 63)
        for i in range(3):
            svg += return_key(20, 22 * i, 84)
        svg += return_key(25, 66, 84)
        svg += return_key(108, 93, 84)
        svg += return_key(25, 203, 84)
        for i in range(4):
            svg += return_key(20, 230 + 22 * i, 84)

        legend_labels = ['Pinky', 'Ring', 'Middle', 'Index', 'Thumb']

        colors.insert(5, '#474448')

        cur_width = 0
        for i, color in enumerate(colors):
            width, text_class = 20, 'l'
            if i < 5: text_one, text_two = 'Left', legend_labels[i]
            elif i == 5:
                text_one, text_two, width, text_class = 'Not', 'Used', 96, 'k'
            else:
                text_one, text_two = 'Right', legend_labels[::-1][i - 6]
            svg += f"""<rect width="{width}" x="{cur_width}" y="110" fill="{color}"/>"""
            svg += f"""<text x="{cur_width + width / 2}" y="117" class="{text_class}">{text_one}</text>"""
            svg += f"""<text x="{cur_width + width / 2}" y="123" class="{text_class}">{text_two}</text>"""
            cur_width += 2 + width

        svg += '</svg>'
        svg2png(bytestring=svg, write_to='keymap.png', scale=10)

        await ctx.send(file=discord.File('keymap.png', 'keymap.png'))
        os.remove('keymap.png')
        return

    @commands.check(lambda ctx: check_banned_status(ctx))
    @commands.cooldown(1, 600, commands.BucketType.default)
    @commands.command(aliases=get_aliases('calc'))
    async def calc(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) == 0:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).parameters(
                               f"{ctx.invoked_with} [expression]"))
            return

        expression = urllib.parse.quote_plus(''.join(args))
        urls = [Urls().eval_math(expression)]

        try:
            response = await fetch(urls, 'json')
        except:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               f"Please provide a valid expression"))
            return

        embed = discord.Embed(title=f"`{''.join(args)}` =",
                              color=discord.Color(MAIN_COLOR),
                              description=f"```{response[0]}```")

        await ctx.send(embed=embed)
        return
Exemple #9
0
class UserConfig(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @commands.cooldown(1, 1, commands.BucketType.default)
    @commands.command(aliases=get_aliases('setprefix'))
    @commands.check(lambda ctx: ctx.message.author.guild_permissions.
                    administrator and check_banned_status(ctx))
    async def setprefix(self, ctx, *args):
        if len(args) == 0:
            prefix = get_prefix(self.bot, ctx.message)
            await ctx.send(embed=discord.Embed(
                color=discord.Color(HELP_BLACK),
                title=f"The prefix is `{prefix}`",
                description=f"`{prefix}setprefix [prefix]`\n`{prefix}help`"))
            return
        elif len(args) > 1:
            await ctx.send(embed=Error(ctx, ctx.message).parameters(
                f"{ctx.invoked_with} <prefix>"))
            return

        prefix = args[0]

        if len(prefix) > 14:
            await ctx.send(
                f"<@{ctx.message.author.id}>",
                embed=Error(ctx, ctx.message).incorrect_format(
                    '`prefix` can not be longer than 14 characters'))
            return

        prefixes = load_prefixes()
        prefixes[str(ctx.guild.id)] = prefix
        update_prefixes(prefixes)
        await ctx.send(embed=discord.Embed(title=f"Updated prefix to {prefix}",
                                           color=discord.Color(0)))
        return

    @commands.cooldown(1, 3, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('register'))
    async def register(self, ctx, *args):
        user_id = str(ctx.message.author.id)
        MAIN_COLOR = get_supporter(user_id)
        show_user_count = ctx.invoked_with[
            -1] == '*' and ctx.message.author.id in BOT_ADMIN_IDS

        invalid = False

        if len(args) != 1:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).parameters(
                               f"{ctx.invoked_with} [typeracer_username]"))
            return
        player = args[0].lower()
        urls = [Urls().get_user(player, 'play')]
        try:
            test_response = await fetch(urls, 'json')
        except:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    '`typeracer_username` must be a TypeRacer username'))
            return
        accounts = load_accounts()

        try:
            accounts[user_id]['main'] = player
        except KeyError:
            accounts.update({
                user_id: {
                    'main': player,
                    'alts': [],
                    'desslejusted': False,
                    'speed': 'lag',
                    'universe': 'play'
                }
            })

        update_accounts(accounts)

        user_count = ''
        if show_user_count:
            user_count = f"\n{len(accounts)} users registered"

        await ctx.send(embed=discord.Embed(
            color=discord.Color(MAIN_COLOR),
            description=(f"<@{user_id}> has been linked to [**{player}**]"
                         f"({Urls().user(player, 'play')}){user_count}")))
        return

    @commands.cooldown(1, 1, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('setuniverse'))
    async def setuniverse(self, ctx, *args):
        user_id = str(ctx.message.author.id)
        MAIN_COLOR = get_supporter(user_id)

        invalid = False

        if len(args) > 1:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(
                    ctx,
                    ctx.message).parameters(f"{ctx.invoked_with} [universe]"))
            return

        if len(args) == 0:
            args = ('play', )
        universe = args[0].lower()
        if len(universe) > 50:
            invalid = True
        else:
            with open(UNIVERSES_FILE_PATH, 'r') as txtfile:
                universes = txtfile.read().split('\n')
            if not universe in universes:
                invalid = True
        if invalid:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               ('`universe` must be a [TypeRacer universe]'
                                '(http://typeracerdata.com/universes)')))
            return

        accounts = load_accounts()

        try:
            accounts[user_id]['universe'] = universe
        except KeyError:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information((
                    'Discord account must be linked to TypeRacer account with '
                    f"`{get_prefix(ctx, ctx.message)}register [typeracer_username]`"
                )))
            return

        update_accounts(accounts)

        await ctx.send(embed=discord.Embed(
            color=discord.Color(MAIN_COLOR),
            description=
            (f"<@{user_id}> has been linked to the {href_universe(universe)} universe"
             )))
        return

    @commands.cooldown(1, 1, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('toggledessle'))
    async def toggledessle(self, ctx, *args):
        user_id = str(ctx.message.author.id)
        MAIN_COLOR = get_supporter(user_id)

        invalid = False

        if len(args) != 0:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx,
                            ctx.message).parameters(f"{ctx.invoked_with}"))
            return

        accounts = load_accounts()

        try:
            cur = accounts[user_id]['desslejusted']
            accounts[user_id]['desslejusted'] = not cur
        except KeyError:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information((
                    'Discord account must be linked to TypeRacer account with '
                    f"`{get_prefix(ctx, ctx.message)}register [typeracer_username]`"
                )))
            return

        update_accounts(accounts)

        await ctx.send(embed=discord.Embed(
            color=discord.Color(MAIN_COLOR),
            description=(
                f"<@{user_id}> has been set to `desslejusted` **{not cur}**")))
        return
Exemple #10
0
class TextStats(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @commands.cooldown(10, 30, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('textbests') + ['breakdown'] +
                      get_aliases('breakdown'))
    async def textbests(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)
        tb = ctx.invoked_with in ['textbests'] + get_aliases('textbests')
        bd = ctx.invoked_with in ['breakdown'] + get_aliases('breakdown')

        if len(args) == 0: args = check_account(user_id)(args)

        try:
            if len(args) == 0:
                raise ValueError
            elif len(args) > 2:
                raise ValueError
            elif bd and len(args) != 1:
                raise ValueError
        except ValueError:
            optional = ' <num_texts>' if tb else ''
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).parameters(
                               f"{ctx.invoked_with} [user]{optional}"))
            return

        player = get_player(user_id, args[0])
        if escape_sequence(player):
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    (f"[**{player}**]({Urls().user(player, 'play')}) "
                     "doesn't exist")))
            return

        filter_tb = len(args) == 2
        if filter_tb:
            try:
                num_texts = int(args[1])
                if num_texts <= 0:
                    raise TypeError
            except TypeError:
                await ctx.send(content=f"<@{user_id}>",
                               embed=Error(ctx, ctx.message).incorrect_format(
                                   '`num_texts` must be a positive integer'))
                return

        sum_, count = 0, 0
        conn = sqlite3.connect(DATABASE_PATH)
        c = conn.cursor()
        try:
            user_data = c.execute(
                f"SELECT gn, tid, MAX(wpm) FROM t_{player} GROUP BY tid ORDER BY wpm"
            ).fetchall()
            if filter_tb:
                user_data = user_data[-num_texts:]
            min_bucket = int(user_data[0][2] // 10)
            max_bucket = int(user_data[-1][2] //
                             10) if user_data[-1][2] // 10 <= 30 else 30

            if bd:
                breakdown_dict, textbest_dict = {}, {}
                for i in range(min_bucket, max_bucket + 1):
                    breakdown_dict.update({i: 0})
                    textbest_dict.update({i: {'count': 0, 'sum': 0}})

            for row in user_data:
                count += 1
                sum_ += row[2]
                if bd:
                    bucket = int(row[2] // 10) if row[2] // 10 <= 30 else 30
                    if ctx.invoked_with[-1] == '*':
                        breakdown_dict[bucket] += 1
                        textbest_dict[bucket]['sum'] += row[2]
                        textbest_dict[bucket]['count'] += 1
                    else:
                        for i in range(min_bucket, bucket + 1):
                            breakdown_dict[i] += 1

        except sqlite3.OperationalError:
            conn.close()
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).not_downloaded())
            return
        conn.close()

        if tb:
            texts_data = load_texts_large()

            if len(user_data) < 10:
                worst = []
                if len(user_data) < 5:
                    top = user_data[::-1]
                else:
                    top = user_data[-5:][::-1]
            else:
                worst = user_data[0:5]
                top = user_data[-5:][::-1]
        else:
            breakdown_text = ''
            max_count_spacer = len(f'{max(breakdown_dict.values()):,}')
            for bucket, count_ in breakdown_dict.items():
                bucket_spacer = 1 + math.floor(
                    math.log10(max_bucket)) - math.floor(math.log10(bucket))
                count_spacer = max_count_spacer - len(f'{count_:,}')
                count_spacer_ = max_count_spacer - len(f'{count - count_:,}')
                breakdown_text += f"{{{bucket * 10}+{' ' * bucket_spacer}WPM}} "
                breakdown_text += f"{' ' * count_spacer}{f'{count_:,}'} [{f'{round(100 * count_ / count, 2):6.2f}'}%] "
                if ctx.invoked_with[-1] == '*':
                    try:
                        average = f"{round(textbest_dict[bucket]['sum'] / textbest_dict[bucket]['count'], 2):6.2f}"
                    except ZeroDivisionError:
                        average = "  ——  "
                    breakdown_text += f"{{{average} WPM}}\n"
                else:
                    breakdown_text += f"({' ' * count_spacer_}{f'{count - count_:,}'} left)\n"

        title = f"{player}'s Text Bests"
        if filter_tb:
            title += f" (Top {f'{num_texts:,}'} Texts Filtered)"
        embed = discord.Embed(title=title, color=discord.Color(MAIN_COLOR))
        embed.set_thumbnail(url=Urls().thumbnail(player))

        embed.add_field(
            name='Texts',
            value=
            (f"**Texts:** {f'{count:,}'}\n"
             f"**Text Bests Average:** {f'{round(sum_ / count, 2):,}'} ("
             f"{f'{round(count * (5 - (sum_ / count) % 5), 2):,}'} total WPM gain "
             f"til {round(5 * ((sum_ / count) // 5 + 1))} WPM)"),
            inline=False)

        if tb:
            value = ''
            for i, text in enumerate(top):
                value += f"**{i + 1}. {f'{text[2]:,}'} WPM (Race #{f'{text[0]:,}'})**\n"
                value += f"{texts_data.get(str(text[1]), 'Missing Text')} [:cinema:]({Urls().result(player, text[0], 'play')})\n"
            embed.add_field(name=f"Top {i + 1} Texts",
                            value=value,
                            inline=False)

            value = ''
            for i, text in enumerate(worst):
                value += f"**{i + 1}. {f'{text[2]:,}'} WPM (Race #{f'{text[0]:,}'})**\n"
                value += f"{texts_data.get(str(text[1]), 'Missing Text')} [:cinema:]({Urls().result(player, text[0], 'play')})\n"
            embed.add_field(name=f"Worst {i + 1} Texts",
                            value=value,
                            inline=False)
        else:
            embed.add_field(name='Breakdown',
                            value=f"```css\n{breakdown_text}```",
                            inline=False)

        await ctx.send(embed=embed)
        return

    @commands.cooldown(10, 50, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('personalbest'))
    async def personalbest(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) == 0:
            args = check_account(user_id)(args)

        if len(args) == 0 or len(args) > 2:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).parameters(
                               f"{ctx.invoked_with} [user] [text_id]"))
            return

        player = get_player(user_id, args[0])
        if escape_sequence(player):
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    (f"[**{player}**]({Urls().user(player, 'play')}) "
                     "doesn't exist")))
            return

        cur_wpm = 0
        if len(args) == 2:
            try:
                if args[1] == '*':
                    text_id = get_cached_id(ctx.message.channel.id)
                    if not text_id:
                        text_id = int(args[1])
                else:
                    text_id = int(args[1])
            except ValueError:
                await ctx.send(content=f"<@{user_id}>",
                               embed=Error(ctx, ctx.message).incorrect_format(
                                   f"**{args[1]}** is not a valid text ID"))
                return
        else:
            try:
                urls = [Urls().get_races(player, 'play', 1)]
                response = (await fetch(urls, 'json'))[0][0]
                text_id = int(response['tid'])
                cur_wpm = float(response['wpm'])
            except:
                await ctx.send(content=f"<@{user_id}>",
                               embed=Error(ctx,
                                           ctx.message).missing_information())
                return

        with open(TEXTS_FILE_PATH_CSV, 'r') as csvfile:
            reader = csv.reader(csvfile)
            next(reader)
            for row in reader:
                if int(row[0]) == text_id:
                    text = row[1]
                    break
                else:
                    continue

        try:
            if len(text) > 1024:
                text = f"\"{text[0:1020]}…\""
            else:
                text = f"\"{text}\""
        except NameError:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               f"{text_id} is not a valid text ID"))
            return

        count, sum_, best, best_gn, worst_gn = (0, ) * 5
        worst = 100000
        conn = sqlite3.connect(DATABASE_PATH)
        c = conn.cursor()
        try:
            user_data = c.execute(
                f"SELECT gn, wpm FROM t_{player} WHERE tid = ?",
                (text_id, )).fetchall()
            for row in user_data:
                count += 1
                sum_ += row[1]
                if row[1] > best:
                    best_gn, best = row
                if row[1] < worst:
                    worst_gn, worst = row
        except sqlite3.OperationalError:
            conn.close()
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).not_downloaded())
            return
        conn.close()

        if cur_wpm > best:
            color, description = 754944, f"**Improved by {round(cur_wpm - best, 2)} WPM (to {round(cur_wpm, 2)} WPM)**"
        else:
            color, description = MAIN_COLOR, ''

        if not count:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).missing_information(
                               f"**{player}** has not completed the text yet"))
            return

        value = f"**Times:** {f'{count:,}'}\n"
        if cur_wpm:
            value += f"**Last Race:** {f'{cur_wpm:,}'} WPM\n"
        value += (f"**Average:** {f'{round(sum_ / count, 2):,}'} WPM\n"
                  f"**Fastest:** {f'{best:,}'} WPM "
                  f"(Race #{f'{best_gn:,}'}) [:cinema:]"
                  f"({Urls().result(player, best_gn, 'play')})\n"
                  f"**Slowest:** {f'{worst:,}'} WPM "
                  f"(Race #{f'{worst_gn:,}'}) [:cinema:]"
                  f"({Urls().result(player, worst_gn, 'play')})\n")

        title = f"Quote #{text_id} Statistics for {player}"
        cache_id(ctx.message.channel.id, text_id)
        if description:
            embed = discord.Embed(title=title,
                                  color=discord.Color(color),
                                  url=Urls().text(text_id),
                                  description=description)
        else:
            embed = discord.Embed(title=title,
                                  color=discord.Color(color),
                                  url=Urls().text(text_id))

        embed.set_thumbnail(url=Urls().thumbnail(player))
        embed.add_field(name='Quote', value=text, inline=False)
        embed.add_field(name='Speeds', value=value)

        if ctx.invoked_with[-1] == '*' and len(user_data) > 1:
            ax = plt.subplots()[1]

            data_y = [i[1] for i in user_data]
            if cur_wpm: data_y += [cur_wpm]
            length = len(data_y)
            data_x = [i + 1 for i in range(length)]

            if length < 30:
                ax.plot(data_x, data_y)
            else:
                ax.scatter(data_x,
                           data_y,
                           marker='.',
                           alpha=0.1,
                           color='#000000')
                if length < 500:
                    sma = length // 10
                else:
                    sma = 50

                moving_y = [sum(data_y[0:sma]) / sma]
                moving_y += [
                    sum(data_y[i - sma:i]) / sma for i in range(sma, length)
                ]
                moving_x = [data_x[0]] + data_x[sma:]
                moving_y = reduce_list(moving_y)
                moving_x = reduce_list(moving_x)
                ax.plot(moving_x, moving_y, color='#FF0000')
                title += f"\n(Moving Average of {sma} Races)"

            ax.set_title(title)
            ax.set_xlabel('Attempt #')
            ax.set_ylabel('WPM')
            plt.grid(True)
            file_name = f"{title}.png"

            graph_colors = get_graph_colors(user_id)
            graph_color(ax, graph_colors, False)
            plt.savefig(file_name, facecolor=ax.figure.get_facecolor())
            plt.close()

            file_ = discord.File(file_name, filename='image.png')
            embed.set_image(url='attachment://image.png')
            os.remove(file_name)
            await ctx.send(file=file_, embed=embed)
            return

        await ctx.send(embed=embed)
        return

    @commands.cooldown(10, 30, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('unraced'))
    async def unraced(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) == 0 or (len(args) == 1 and len(args[0]) < 4):
            args = check_account(user_id)(args)

        if len(args) == 0 or len(args) > 2:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).parameters(
                               f"{ctx.invoked_with} [user] <length>"))
            return

        player = get_player(user_id, args[0])
        if escape_sequence(player):
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    (f"[**{player}**]({Urls().user(player, 'play')}) "
                     "doesn't exist")))
            return

        if len(args) == 2:
            try:
                length = int(args[1])
                if length < 1 or length > 999:
                    raise ValueError
            except ValueError:
                await ctx.send(
                    content=f"<@{user_id}>",
                    embed=Error(ctx, ctx.message).incorrect_format(
                        '`length` must be a positive integer less than 999'))
                return
        else:
            length = 0

        all_tids, user_tids, texts_data = set(), set(), dict()
        with open(TEXTS_FILE_PATH_CSV, 'r') as csvfile:
            reader = csv.reader(csvfile)
            next(reader)
            for row in reader:
                all_tids.add(int(row[0]))
                texts_data.update(
                    {int(row[0]): {
                         'text': row[1],
                         'ghost': row[2]
                     }})

        conn = sqlite3.connect(DATABASE_PATH)
        c = conn.cursor()
        try:
            user_data = c.execute(f"SELECT DISTINCT tid FROM t_{player}")
            for row in user_data:
                user_tids.add(row[0])
        except sqlite3.OperationalError:
            conn.close()
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).not_downloaded())
            return
        conn.close()

        unraced_tids = list(all_tids - user_tids)
        if length:
            unraced_tids = list(
                filter(lambda x: len(texts_data[x]['text']) < length,
                       unraced_tids))
        if len(unraced_tids) == 0:
            description = f"{player} has completed all texts"
            if length:
                description += f" under length {length}"
            description += '!'
            await ctx.send(embed=discord.Embed(title='Nothing to Choose From',
                                               color=discord.Color(754944),
                                               description=description))
            return

        title = 'Random Unraced Texts'
        if length:
            title += f" Under Length {length}"
        title += f" for {player} ({f'{len(unraced_tids):,}'} left)"

        embed = discord.Embed(title=title, color=discord.Color(MAIN_COLOR))
        embed.set_thumbnail(url=Urls().thumbnail(player))

        try:
            for i in range(0, 5):
                random_tid = random.choice(unraced_tids)
                value_1 = f"\"{texts_data[random_tid]['text']}\" "
                value_2 = (f"[{TR_INFO}]({Urls().text(random_tid)}) "
                           f"[{TR_GHOST}]({texts_data[random_tid]['ghost']})")
                value = value_1 + value_2
                if len(value) > 1024:
                    value_1 = value_1[0:1019 - len(value_2)]
                    value = f"{value_1}…\" {value_2}"

                embed.add_field(name=f"{i + 1}. Race Text ID: {random_tid}",
                                value=value,
                                inline=False)
                unraced_tids.remove(random_tid)
        except:
            pass

        await ctx.send(embed=embed)
        return

    @commands.cooldown(10, 30, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('textsunder'))
    async def textsunder(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) < 2 or len(args) > 3:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).parameters(
                    f"{ctx.invoked_with} [user] [speed] <text_length>"))
            return

        player = get_player(user_id, args[0])
        if escape_sequence(player):
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    (f"[**{player}**]({Urls().user(player, 'play')}) "
                     "doesn't exist")))
            return

        try:
            speed = float(args[1])
            if speed <= 0:
                raise ValueError
            length = 0
            if len(args) == 3:
                length = float(args[2])
                if length <= 0:
                    raise ValueError
        except ValueError:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).incorrect_format(
                    '`speed` and `length` must be positive numbers'))
            return

        texts_data = dict()
        with open(TEXTS_FILE_PATH_CSV, 'r') as csvfile:
            reader = csv.reader(csvfile)
            next(reader)
            for row in reader:
                texts_data.update(
                    {int(row[0]): {
                         'text': row[1],
                         'ghost': row[2]
                     }})

        conn = sqlite3.connect(DATABASE_PATH)
        c = conn.cursor()
        try:
            user_data = c.execute(
                f"SELECT gn, tid, MAX(wpm) FROM t_{player} GROUP BY tid ORDER BY wpm"
            ).fetchall()
        except sqlite3.OperationalError:
            conn.close()
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).not_downloaded())
            return
        conn.close()

        tu_dict, tids = dict(), []
        for tid in user_data:
            if tid[2] > speed:
                continue
            if length:
                if len(texts_data[tid[1]]['text']) > length:
                    continue
            tu_dict.update({tid[1]: tid[2]})
            tids.append(tid[1])

        if len(tu_dict) == 0:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    'No texts found that meet the required criteria'))
            return

        title = f"Random Texts Under {f'{speed:,}'} WPM"
        if len(args) == 3:
            title += f" and {f'{length:,}'} Length"
        title += f" for {player} ({f'{len(tu_dict):,}'} left)"

        embed = discord.Embed(title=title, color=discord.Color(MAIN_COLOR))
        embed.set_thumbnail(url=Urls().thumbnail(player))

        for i in range(0, 5):
            try:
                random_tid = random.choice(tids)
                value_1 = f"\"{texts_data[random_tid]['text']}\" "
                value_2 = f"[{TR_INFO}]({Urls().text(random_tid)}) [{TR_GHOST}]({texts_data[random_tid]['ghost']})"
                value = value_1 + value_2
                if len(value) > 1024:
                    value_1 = value_1[0:1019 - len(value_2)]
                    value = value_1 + "…\" " + value_2
                embed.add_field(
                    name=(f"{i + 1}. {f'{tu_dict[random_tid]:,}'} WPM"
                          f" (Race Text ID: {random_tid})"),
                    value=value,
                    inline=False)
                tids.remove(random_tid)
            except IndexError:
                pass

        await ctx.send(embed=embed)
        return

    @commands.cooldown(10, 30, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('textslessequal'))
    async def textslessequal(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) < 2 or len(args) > 3:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).parameters(
                    f"{ctx.invoked_with} [user] [num] <wpm/points/time>"))
            return

        player = get_player(user_id, args[0])
        if escape_sequence(player):
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    (f"[**{player}**]({Urls().user(player, 'play')}) "
                     "doesn't exist")))
            return

        try:
            num = float(args[1])
            if num <= 0:
                raise ValueError
        except ValueError:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).incorrect_format(
                    '`speed` and `length` must be positive numbers'))
            return

        if len(args) == 2:
            category = 'wpm'
        else:
            category = args[2].lower()
            if category not in ['wpm', 'points', 'times']:
                await ctx.send(
                    content=f"<@{user_id}>",
                    embed=Error(ctx, ctx.message).incorrect_format(
                        'Must provide a valid category: `wpm/points/times`'))
                return

        conn = sqlite3.connect(DATABASE_PATH)
        c = conn.cursor()
        try:
            count = len(
                c.execute(f"SELECT DISTINCT tid from t_{player}").fetchall())
            if category == 'wpm':
                user_data = c.execute(
                    f"""SELECT tid, COUNT(tid)
                                          FROM t_{player}
                                          WHERE wpm >= ?
                                          GROUP BY tid
                                          ORDER BY COUNT(tid) DESC""",
                    (num, )).fetchall()
            elif category == 'points':
                user_data = c.execute(
                    f"""SELECT tid, COUNT(tid)
                                          FROM t_{player}
                                          WHERE pts >= ?
                                          GROUP BY tid
                                          ORDER BY COUNT(tid) DESC""",
                    (num, )).fetchall()
            else:
                user_data = c.execute(
                    f"""SELECT tid, COUNT(tid)
                                          FROM t_{player}
                                          GROUP BY tid
                                          HAVING COUNT(tid) >= ?
                                          ORDER BY COUNT(tid) DESC""",
                    (num, )).fetchall()
        except sqlite3.OperationalError:
            conn.close()
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).not_downloaded())
            return
        conn.close()

        texts_data = load_texts_large()
        category = {
            'wpm': 'WPM',
            'points': 'Points',
            'times': 'Times'
        }[category]
        if category == 'Times':
            num = int(num)

        embed = discord.Embed(
            title=f"{player}'s Total Texts Typed Over {f'{num:,}'} {category}",
            color=discord.Color(MAIN_COLOR),
            description=
            (f"**Texts Typed:** {f'{count:,}'}\n"
             f"**Texts Over {f'{num:,}'} {category}:** "
             f"{f'{len(user_data):,}'} ({round(100 * len(user_data) / count, 2)}%)"
             ))
        embed.set_thumbnail(url=Urls().thumbnail(player))
        for i in range(0, 10):
            try:
                value_1 = f"\"{texts_data[str(user_data[i][0])]}\" "
                value_2 = f"[{TR_INFO}]({Urls().text(user_data[i][0])})"
                value = value_1 + value_2
                if len(value) > 1024:
                    value_1 = value_1[0:1019 - len(value_2)]
                    value = value_1 + "…\" " + value_2
                embed.add_field(
                    name=(f"{i + 1}. {f'{user_data[i][1]:,}'} times "
                          f"(Race Text ID: {user_data[i][0]})"),
                    value=value,
                    inline=False)
            except:
                pass

        await ctx.send(embed=embed)
        return
Exemple #11
0
class Supporter(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
        self.eugene_message = '`null`'

    @commands.command(aliases=['as'])
    @commands.check(lambda ctx: ctx.message.author.id in BOT_OWNER_IDS and
                    check_banned_status(ctx))
    async def add_supporter(self, ctx, *args):
        if len(args) != 2: return

        try:
            int(args[0])
            if len(args[0]) > 18:
                raise ValueError
        except ValueError:
            await ctx.send(content=f"<@{ctx.message.author.id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               f"**{args[0]}** is not a valid Discord ID"))
            return

        try:
            tier = int(args[1])
            if tier < 1 or tier > 4:
                raise ValueError
        except ValueError:
            await ctx.send(content=f"<@{ctx.message.author.id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               f"Tier level must be between 1 and 4"))
            return

        supporters = load_supporters()

        if args[0] in list(supporters.keys()):
            await ctx.send(content=f"<@{ctx.message.author.id}>",
                           embed=Error(ctx, ctx.message).missing_information(
                               f"<@{args[0]}> already in system"))
            return

        supporters.update({
            args[0]: {
                'color': MAIN_COLOR,
                'tier': tier,
                'graph_color': {
                    'bg': None,
                    'graph_bg': None,
                    'axis': None,
                    'line': None,
                    'text': None,
                    'grid': None,
                    'cmap': None
                }
            }
        })

        update_supporters(supporters)

        await ctx.send(embed=discord.Embed(
            description=
            f"**Tier {tier}** supporter <@{args[0]}> added to the list",
            color=discord.Color(0)))
        return

    @commands.command(aliases=['ds'])
    @commands.check(lambda ctx: ctx.message.author.id in BOT_OWNER_IDS and
                    check_banned_status(ctx))
    async def delete_supporter(self, ctx, *args):
        if len(args) != 1: return

        try:
            int(args[0])
            if len(args[0]) > 18:
                raise ValueError
        except ValueError:
            await ctx.send(content=f"<@{ctx.message.author.id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               f"**{args[0]}** is not a valid Discord ID"))
            return

        supporters = load_supporters()

        if not args[0] in list(supporters.keys()):
            await ctx.send(content=f"<@{ctx.message.author.id}>",
                           embed=Error(ctx, ctx.message).missing_information(
                               f"<@{args[0]}> is not in the system"))
            return

        try:
            del supporters[args[0]]
        except KeyError:
            pass

        update_supporters(supporters)

        await ctx.send(embed=discord.Embed(
            description=f"<@{args[0]}> removed from supporters list",
            color=discord.Color(0)))
        return

    @commands.command(aliases=['us'])
    @commands.check(lambda ctx: ctx.message.author.id in BOT_OWNER_IDS and
                    check_banned_status(ctx))
    async def upgrade_supporter(self, ctx, *args):
        if len(args) != 2: return

        try:
            int(args[0])
            if len(args[0]) > 18:
                raise ValueError
        except ValueError:
            await ctx.send(content=f"<@{ctx.message.author.id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               f"**{args[0]}** is not a valid Discord ID"))
            return

        try:
            tier = int(args[1])
            if tier < 1 or tier > 4:
                raise ValueError
        except ValueError:
            await ctx.send(content=f"<@{ctx.message.author.id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               f"Tier level must be between 1 and 4"))
            return

        supporters = load_supporters()

        if not args[0] in list(supporters.keys()):
            await ctx.send(content=f"<@{ctx.message.author.id}>",
                           embed=Error(ctx, ctx.message).missing_information(
                               f"<@{args[0]}> is not in the system"))
            return

        supporters[args[0]]['tier'] = tier

        update_supporters(supporters)

        await ctx.send(embed=discord.Embed(
            description=f"<@{args[0]}> upgraded to **Tier {tier}**",
            color=discord.Color(0)))
        return

    @commands.command(aliases=get_aliases('setcolor'))
    @commands.check(lambda ctx: str(ctx.message.author.id) in list(load_supporters().keys()) \
                                and int(load_supporters()[str(ctx.message.author.id)]['tier']) >= 2 and check_banned_status(ctx))
    async def setcolor(self, ctx, *args):
        if len(args) > 1:
            await ctx.send(
                content=f"<@{ctx.message.author.id}>",
                embed=Error(
                    ctx,
                    ctx.message).parameters(f"{ctx.invoked_with} [hex_value]"))
            return

        if len(args) == 0:
            color = MAIN_COLOR

        elif len(args) == 1:
            try:
                color = int(f"0x{args[0]}", 16)
                if color < 0 or color > 16777216:
                    raise ValueError
            except ValueError:
                try:
                    colors = get_colors()
                    color = colors[args[0].lower()]
                except KeyError:
                    await ctx.send(
                        content=f"<@{ctx.message.author.id}>",
                        embed=Error(ctx, ctx.message).incorrect_format((
                            f"[**{args[0]}** is not a valid hex_value]"
                            '(https://www.w3schools.com/colors/colors_picker.asp)'
                        )))
                    return

        supporters = load_supporters()

        supporters[str(ctx.message.author.id)]['color'] = color

        update_supporters(supporters)

        await ctx.send(embed=discord.Embed(title='Color updated',
                                           color=discord.Color(color)))
        return

    @commands.command(aliases=get_aliases('setgraphcolor'))
    @commands.check(lambda ctx: str(ctx.message.author.id) in list(load_supporters().keys()) \
                                and int(load_supporters()[str(ctx.message.author.id)]['tier']) >= 3 and check_banned_status(ctx))
    async def setgraphcolor(self, ctx, *args):
        supporters = load_supporters()
        user_id = str(ctx.message.author.id)
        color = 0

        if len(args) == 0:
            supporters[user_id]['graph_color'] = {
                'bg': None,
                'graph_bg': None,
                'axis': None,
                'line': None,
                'text': None,
                'grid': None,
                'cmap': None
            }

        else:
            category = args[0].lower()
            if not category in [
                    'bg', 'graph_bg', 'axis', 'line', 'text', 'grid', 'cmap'
            ]:
                await ctx.send(content=f"<@{user_id}>",
                               embed=Error(ctx, ctx.message).incorrect_format(
                                   ('Must provide a valid category: '
                                    '`[bg/graph_bg/axis/line/text/grid]`')))
                return

            if len(args) == 1:
                supporters[user_id]['graph_color'][category] = None
            else:
                try:
                    if category == 'cmap':
                        raise ValueError

                    color = int(f"0x{args[1]}", 16)
                    if color < 0 or color > 16777216:
                        raise ValueError
                except ValueError:
                    try:
                        if category == 'cmap':
                            cmaps = get_cmaps()
                            color = cmaps[args[1].lower()]
                        else:
                            colors = get_colors()
                            color = colors[args[1].lower()]
                    except KeyError:
                        await ctx.send(
                            content=f"<@{ctx.message.author.id}>",
                            embed=Error(ctx, ctx.message).incorrect_format((
                                f"[**{args[1]}** is not a valid hex_value or cmap]"
                                '(https://www.w3schools.com/colors/colors_picker.asp)'
                            )))
                        return
                supporters[user_id]['graph_color'][category] = color
                if category == 'cmap' and not supporters[user_id][
                        'graph_color']['line']:
                    supporters[user_id]['graph_color']['line'] = 0x447BAF

                if isinstance(color, str):
                    color = 0

        update_supporters(supporters)

        ax = plt.subplots()[1]
        ax.plot([x for x in range(0, 50)], [y**0.5 for y in range(0, 50)])

        ax.set_title('Sample Graph')
        ax.set_xlabel('x-axis')
        ax.set_ylabel('y-axis')

        plt.grid(True)
        file_name = 'Sample Graph.png'
        graph_colors = supporters[user_id]['graph_color']
        graph_colors.update({'name': '!'})
        graph_color(ax, graph_colors, False)
        plt.savefig(file_name, facecolor=ax.figure.get_facecolor())
        plt.close()

        file_ = discord.File(file_name, filename='image.png')
        embed = discord.Embed(title='Color Updated',
                              color=discord.Color(color))
        embed.set_image(url='attachment://image.png')
        os.remove(file_name)

        await ctx.send(file=file_, embed=embed)
        return

    @commands.command(aliases=get_aliases('echo'))
    @commands.check(lambda ctx: str(ctx.message.author.id) in list(load_supporters().keys()) \
                                and int(load_supporters()[str(ctx.message.author.id)]['tier']) >= 1 and check_banned_status(ctx))
    async def echo(self, ctx, *, args):
        try:
            await ctx.message.delete()
        except:
            pass

        if ctx.message.author.id == 476016555981930526 and 't!tg train' in ctx.message.content:  #pasta's Discord ID
            await ctx.send('<a:pasta_200_iq_patch:794905370011107328>')
            return
        try:
            colors = re.findall('"color":\s*0x[0-9abcdefABCDEF]{6},', args)
            message = args
            for color in colors:
                message = message.replace(
                    color, f"\"color\": {int(color[-9:-1], 16)},")
            embed_data = json.loads(message)
            embed = discord.Embed(**embed_data)
            try:
                for field in embed_data['fields']:
                    embed.add_field(**field)
            except KeyError:
                pass
            try:
                embed.set_thumbnail(**embed_data['thumbnail'])
            except KeyError:
                pass
            try:
                embed.set_image(**embed_data['image'])
            except KeyError:
                pass
            try:
                embed.set_footer(**embed_data['footer'])
            except KeyError:
                pass
            try:
                embed.set_author(**embed_data['author'])
            except KeyError:
                pass
            await ctx.send(embed=embed)
            return
        except:
            await ctx.send(args)
            return

    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('charlieog'))
    async def charlieog(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) == 0: args = check_account(user_id)(args)

        if len(args) == 0 or len(args) > 2:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).parameters(
                               f"{ctx.invoked_with} [user] <text_id>"))
            return

        player = get_player(user_id, args[0])
        if escape_sequence(player):
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    (f"[**{player}**]({Urls().user(player, 'play')}) "
                     "doesn't exist")))
            return

        if len(args) == 2:
            try:
                if args[1] == '*':
                    tid = get_cached_id(ctx.message.channel.id)
                    if not tid:
                        tid = int(args[1])
                else:
                    tid = int(args[1])
                if tid <= 0:
                    raise ValueError
            except ValueError:
                await ctx.send(content=f"<@{user_id}>",
                               embed=Error(ctx, ctx.message).incorrect_format(
                                   f"{args[1]} is not a valid text ID"))
                return
        else:
            tid = 3621293

        urls = [Urls().get_races(player, 'play', 1)]
        try:
            api_response = await fetch(urls, 'json')
        except:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    (f"[**{player}**]({Urls().user(player, 'play')}) "
                     "doesn't exist or has no races")))
            return

        file_name = f"t_{player}"
        conn = sqlite3.connect(DATABASE_PATH)
        c = conn.cursor()
        try:
            user_data = c.execute(
                f"SELECT * FROM t_{player} ORDER BY t DESC LIMIT 1")
            last_race_timestamp = user_data.fetchone()[1]
        except sqlite3.OperationalError:
            conn.close()
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).not_downloaded())
            return

        text, ghost = '', ''
        with open(TEXTS_FILE_PATH_CSV, 'r') as csvfile:
            reader = csv.reader(csvfile)
            next(reader)
            for row in reader:
                if int(row[0]) == tid:
                    text, ghost = row[1], row[2]
        if not text or not ghost:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               f"{tid} is not a valid text ID"))
            return

        cache_id(ctx.message.channel.id, tid)

        value_1 = f"\"{text}\" "
        value_2 = (f"[{TR_INFO}]({Urls().text(tid)}) "
                   f"[{TR_GHOST}]({ghost})")
        value = value_1 + value_2
        if len(value) > 1024:
            value_1 = value_1[0:1019 - len(value_2)]
            value = value_1 + "…\"" + value_2

        data = await fetch_data(player, 'play', last_race_timestamp + 0.01,
                                time.time())

        if data:
            c.executemany(f"INSERT INTO {file_name} VALUES (?, ?, ?, ?, ?)",
                          data)

        conn.commit()
        data = c.execute(
            f"""SELECT * FROM
                        (SELECT *
                        FROM {file_name}
                        WHERE t >= ?)
                WHERE tid = ?""", (time.time() - 86400, tid)).fetchall()
        conn.close()

        if len(data) < 10:
            description = 'Next save available **now**'
        else:
            description = f"Next save available in **{seconds_to_text(86400 + data[0][1] - time.time())}**"
        value_ = ''
        for i, race in enumerate(data):
            value_ += (
                f"{i + 1}. {seconds_to_text(time.time() - race[1])} ago "
                f"({race[3]} WPM)\n")

        embed = discord.Embed(
            title=f"{player}'s Text #{tid} Statistics in Last 24 Hours",
            color=discord.Color(MAIN_COLOR),
            description=description)
        embed.add_field(name=f"Text ID: {tid}", value=value, inline=False)
        if value_:
            embed.add_field(name='Races', value=value_, inline=False)
        embed.set_footer(text="snowmelt#1745's custom command")

        await ctx.send(embed=embed)
        return

    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('kayos'))
    async def kayos(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) == 0:
            args = (3, )

        if len(args) != 1:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).parameters(
                               f"{ctx.invoked_with} <typo_count>"))
            return

        try:
            typo_count = float(args[0])
            if typo_count < 0 or typo_count > 100:
                raise ValueError
        except ValueError:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).incorrect_format(
                    '`typo_proportion` must be a positive number between 0 and 100'
                ))

        text, ghost = '', ''
        with open(TEXTS_FILE_PATH_CSV, 'r') as csvfile:
            reader = csv.reader(csvfile)
            next(reader)
            tid, text, ghost = tuple(random.choice(list(reader)))

        typos, count = 'abcdefghijklmnopqrstuvwxyz!? <>_=1234567890/*-', 0
        text_ = ''
        for i in range(0, len(text)):
            if random.random() * 100 <= typo_count:
                random_ = random.choice(typos)
                if random_ == '_' or random_ == '*':
                    text_ += f"\{random_}"
                else:
                    text_ += random_
                if random_ != text[i]:
                    count += 1
            else:
                text_ += text[i]

        value_1 = f"\"{text_}\" "
        value_2 = (f"[{TR_INFO}]({Urls().text(tid)}) "
                   f"[{TR_GHOST}]({ghost})")
        value = value_1 + value_2
        if len(value) > 1024:
            value_1 = value_1[0:1019 - len(value_2)]
            value = value_1 + "…\"" + value_2

        embed = discord.Embed(
            title=f"Random Text With ≈{typo_count}% Random Typos",
            color=discord.Color(MAIN_COLOR),
            description=f"{f'{count:,}'} typos generated")
        embed.add_field(name=f"Text ID: {tid}", value=value, inline=False)
        embed.set_footer(text="KayOS#6686's custom command")

        await ctx.send(embed=embed)
        return

    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('dicey'))
    async def dicey(self, ctx, *args):
        question = ' '.join(args)

        if not question:
            await ctx.send(
                content=f"<@{ctx.message.author.id}>",
                embed=Error(
                    ctx,
                    ctx.message).incorrect_format('You must ask a question!'))
            return

        affirmative = [
            'It is certain.', 'It is decidedly so.', 'Without a doubt.',
            'Yes – definitely.', 'You may rely on it.', 'As I see it, yes.',
            'Most likely.', 'Outlook good.', 'Yes.', 'Signs point to yes.',
            'Absolutely', 'Of course.', 'For sure.', 'YES.',
            'By all means, yes.', 'Yeah, I\'d say so.', 'Totally.',
            'Clearly, yes.'
        ]

        uncertain = [
            'Reply hazy, try again.', 'Ask again later.',
            'Better not tell you now.', 'Cannot predict now.',
            'Concentrate and ask again.'
        ]

        negative = [
            'Don\'t count on it.', 'My reply is no.', 'My sources say no.',
            'Outlook not so good.', 'Very doubtful.', 'No.', 'Definitely not.',
            'Certainly not.', 'No way.', 'Definitely not.', 'Of course not.',
            'Nah.', 'Nope.', 'NO.', 'Are you stupid?', 'Obviously not.'
        ]

        category = random.randint(1, 100)
        if category == 1:
            await ctx.send(embed=discord.Embed(
                title='How am I supposed to know?'))
        elif category <= 41:
            await ctx.send(random.choice(affirmative))
        elif category <= 60:
            await ctx.send(random.choice(uncertain))
        else:
            await ctx.send(random.choice(negative))
        return

    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.cooldown(1, 60, commands.BucketType.default)
    @commands.command(aliases=get_aliases('eugene'))
    async def eugene(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) > 0 and not user_id == 697048255254495312:
            return
        elif len(args) > 0:
            self.eugene_message = ' '.join(
                ctx.message.content.split(' ')[1:])[:2048]

        if not len(ctx.message.attachments):
            await ctx.send(embed = discord.Embed(color = discord.Color(MAIN_COLOR),
                                                 title = (datetime.datetime.utcnow() +\
                                                          datetime.timedelta(hours = 9))
                                                          .strftime("%B %-d, %Y, %-I:%M:%S %p"),
                                                 description = self.eugene_message))
            return

        attached_file = ctx.message.attachments[0]

        if not attached_file.url.endswith('png'):
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               'Uploaded image must be **.png** file'))
            return

        height = attached_file.height
        width = attached_file.width
        size = attached_file.size

        if height > 1024 or width > 1024 or size > 1_572_864:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).incorrect_format(
                    'Uploaded image must be at most 1024px in width, 1024px in height, and 1.5MB in size.'
                ))
            return

        image_information = await ctx.message.attachments[0].save(
            fp='eugene.png')
        rgb_im = ImageEnhance.Contrast(
            Image.open('eugene.png').convert('RGB')).enhance(3)
        ascii_shades = """@%#*+=-:. """
        num_shades = len(ascii_shades)
        x = int((3900 * width / height)**0.5)
        y = int((3900 * height / width)**0.5 / 2)
        pixel_search_size = int(width / x)
        total_shade = 1_530 * pixel_search_size**2

        ascii_art = '```'

        cur_y = 0
        while cur_y < y:
            cur_x = 0
            while cur_x < x:
                cur_shade = 0
                for x_ in range(pixel_search_size):
                    for y_ in range(pixel_search_size * 2):
                        cur_pixel = rgb_im.getpixel(
                            (cur_x * pixel_search_size + x_,
                             2 * cur_y * pixel_search_size + y_))
                        cur_shade += sum(cur_pixel)
                normalized_shade = min(cur_shade / total_shade, 0.99)
                ascii_art += ascii_shades[int(num_shades * normalized_shade)]
                cur_x += 1
            ascii_art += '\n'
            cur_y += 1
        ascii_art += '```'

        os.remove('eugene.png')
        message = await ctx.send(ascii_art)
        await message.add_reaction('🗑️')

        def check(reaction, user):
            return str(reaction.emoji) == '🗑️' and\
                   reaction.message.id == message.id and\
                   (user.id == ctx.message.author.id or user.id in BOT_ADMIN_IDS)

        try:
            await self.bot.wait_for('reaction_add', check=check, timeout=10)
            await message.delete()
        except:
            await message.remove_reaction('🗑️', self.bot.user)

    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('dessle'))
    async def dessle(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)
        dessle_enlighten = ctx.invoked_with in ['dessle', 'enlighten']
        dessle_invoked = ctx.message.author.id == 279844278455500800  #Dessle's Discord ID

        if (not dessle_invoked and len(args) > 0):
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx,
                            ctx.message).parameters(f"{ctx.invoked_with}"))
            return
        elif dessle_invoked and dessle_enlighten and len(args) == 1:
            args = (args[0].strip('<@!').strip('>'), )
            try:
                if len(args[0]) > 18:
                    raise ValueError
                id_ = int(args[0])
            except ValueError:
                await ctx.send(content=f"<@{user_id}>",
                               embed=Error(ctx, ctx.message).incorrect_format(
                                   f"**{args[0]}** is not a valid Discord ID"))
                return

            if id_ in BOT_OWNER_IDS:
                raise commands.CheckFailure
                return

            accounts = load_accounts()
            try:
                accounts[str(id_)]['desslejusted'] = True
                update_accounts(accounts)

                embed = discord.Embed(
                    color=discord.Color(MAIN_COLOR),
                    description=f"<@{id_}> **has been ENLIGHTENED**")
                await ctx.send(embed=embed)
                return
            except KeyError:
                await ctx.send(
                    content=f"<@{user_id}>",
                    embed=Error(ctx, ctx.message).incorrect_format(
                        f"<@{id_}> has not yet been linked to the bot"))
                return

        if len(args) > 0:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx,
                            ctx.message).parameters(f"{ctx.invoked_with}"))
            return

        texts = []
        with open(TEXTS_FILE_PATH_CSV, 'r') as csvfile:
            reader = csv.reader(csvfile)
            next(reader)
            for row in reader:
                texts.append([row[0], row[1], row[2]])

        embed = discord.Embed(title='10 Random Texts',
                              color=discord.Color(MAIN_COLOR))

        for i in range(1, 11):
            random_text = random.choice(texts)
            texts.remove(random_text)

            name = f"{i}. Race Text ID: {random_text[0]}"
            text = f"\"{random_text[1]}\" "
            if len(text) > 50:
                text = f"\"{random_text[1][0:50]}…\" "

            value = text
            value += (f"[{TR_INFO}]({Urls().text(random_text[0])}) "
                      f"[{TR_GHOST}]({random_text[2]})\n")

            embed.add_field(name=name, value=value, inline=False)

        embed.set_footer(text="dessle#9999's custom command")
        await ctx.send(embed=embed)
        return

    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('ginoo'))
    async def ginoo(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) == 0: args = ('ginoo75', 1000)

        if len(args) != 2:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).parameters(
                               f"{ctx.invoked_with} [player] [num_races]"))
            return

        player = get_player(user_id, args[0])
        if escape_sequence(player):
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    (f"[**{player}**]({Urls().user(player, 'play')}) "
                     "doesn't exist")))
            return

        try:
            num_races = int(args[1])
            if num_races <= 0:
                raise ValueError
        except ValueError:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               '`num_races` must be a positive integer'))
            return

        monthly_races = []
        current_month = {
            'month': None,
            'races': 0,
            'words_typed': 0,
            'chars_typed': 0,
            'points': 0,
            'time_spent': 0,
            'total_wpm': 0,
            'best_wpm': 0,
            'worst_wpm': 1000000
        }

        texts_length = load_texts_json()
        conn = sqlite3.connect(DATABASE_PATH)
        c = conn.cursor()

        try:
            user_data = c.execute(f"SELECT * FROM t_{player}")
            for row in user_data:
                month = datetime.datetime.fromtimestamp(
                    row[1]).strftime('%Y-%-m')
                if current_month['month'] == None:
                    current_month['month'] = month
                elif current_month['month'] != month:
                    if current_month['races'] >= num_races:
                        current_month.update({
                            'average_wpm':
                            round(
                                current_month['total_wpm'] /
                                current_month['races'], 2)
                        })
                        del current_month['total_wpm']
                        current_month['time_spent'] = round(
                            current_month['time_spent'], 2)
                        current_month['points'] = round(
                            current_month['points'], 2)
                        monthly_races.append(list(current_month.values()))
                    current_month = {
                        'month': month,
                        'races': 0,
                        'words_typed': 0,
                        'chars_typed': 0,
                        'points': 0,
                        'time_spent': 0,
                        'total_wpm': 0,
                        'best_wpm': 0,
                        'worst_wpm': 1000000
                    }

                current_month['races'] += 1
                current_month['words_typed'] += texts_length.get(
                    str(row[2]), {'word count': 0})['word count']
                current_month['chars_typed'] += texts_length.get(
                    str(row[2]), {'length': 0})['length']
                current_month['points'] += row[4]
                current_month['time_spent'] += texts_length.get(
                    str(row[2]), {'length': 0})['length'] * 12 / row[3]
                current_month['total_wpm'] += row[3]
                current_month['best_wpm'] = max(current_month['best_wpm'],
                                                row[3])
                current_month['worst_wpm'] = min(current_month['worst_wpm'],
                                                 row[3])
        except sqlite3.OperationalError:
            conn.close()
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).not_downloaded())
            return
        conn.close()

        if not monthly_races:
            await ctx.send(embed=discord.Embed(
                color=discord.Color(MAIN_COLOR),
                title=
                f"{f'{num_races:,}'} Races Longevity Statistics for {player}",
                description='It\'s empty here.').set_footer(
                    text="ginoo75#6666's custom command"))
            return

        if current_month['races'] >= num_races:
            current_month.update({
                'average_wpm':
                round(current_month['total_wpm'] / current_month['races'], 2)
            })
            del current_month['total_wpm']
            current_month['time_spent'] = round(current_month['time_spent'], 2)
            current_month['points'] = round(current_month['points'], 2)
            monthly_races.append(list(current_month.values()))

        index, longest_chain_index = (0, ) * 2
        max_chain, current_chain = (1, ) * 2
        for i, month_stats in enumerate(monthly_races[1:]):
            index += 1
            previous_month = datetime.datetime.strptime(
                monthly_races[i][0], '%Y-%m')
            month = month_stats[0]
            next_month = (previous_month.month + 1) % 12
            if next_month == 0: next_month = 12
            if datetime.datetime.strptime(month, '%Y-%m') == datetime.datetime(
                    previous_month.year + int((previous_month.month) / 12),
                    next_month, 1):
                current_chain += 1
                if current_chain > max_chain:
                    max_chain = current_chain
                    longest_chain_index = index - current_chain + 1
            else:
                current_chain = 1

        monthly_races.insert(0, list(current_month.keys()))

        file_name = f"{player}_longevity_{num_races}.csv"
        with open(file_name, 'w') as csvfile:
            writer = csv.writer(csvfile)
            writer.writerows(monthly_races)

        file_ = discord.File(file_name, filename=file_name)

        embed = discord.Embed(
            color=discord.Color(MAIN_COLOR),
            title=f"{f'{num_races:,}'} Races Longevity Statistics for {player}",
            description=
            (f"**{f'{(index + 1):,}'}** months with chain of "
             f"**{f'{max_chain:,}'}** starting on "
             f"**{datetime.datetime.strptime(monthly_races[longest_chain_index + 1][0], '%Y-%m').strftime('%B %Y')}**"
             ))
        embed.set_footer(text="ginoo75#6666's custom command")

        await ctx.send(file=file_, embed=embed)
        os.remove(file_name)
        return
Exemple #12
0
class Texts(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('search'))
    async def search(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) == 0:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(
                    ctx,
                    ctx.message).parameters(f"{ctx.invoked_with} [3≤ words]"))
            return
        elif len(args) < 3 and not user_id in BOT_ADMIN_IDS:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(
                    ctx,
                    ctx.message).parameters(f"{ctx.invoked_with} [3≤ words]"))
            return

        orig_query = ' '.join(args)
        query = orig_query.lower()
        embed = discord.Embed(title=f"Results for \"{orig_query}\"",
                              color=discord.Color(MAIN_COLOR))
        embed.set_footer(text='Page 1')

        texts, messages = [], []
        with open(TEXTS_FILE_PATH_CSV, 'r') as csvfile:
            reader = csv.reader(csvfile)
            next(reader)
            for row in reader:
                texts.append([row[0], row[1], row[2]])

        count = 0
        embed_count = 0
        for i, cur in enumerate(texts):
            try:
                start_index = cur[1].lower().index(query)
                query_length = len(query)
                text = cur[1]
                formatted = (
                    f"{text[0:start_index]}**"
                    f"{text[start_index:start_index + query_length]}**"
                    f"{text[start_index + query_length:]}")

                value_1 = f"\"{formatted}\" "
                value_2 = (f"[{TR_INFO}]({Urls().text(cur[0])}) "
                           f"[{TR_GHOST}]({cur[2]})")
                value = value_1 + value_2
                if len(value) > 1024:
                    value_1 = value_1[0:1019 - len(value_2)]
                    value = value_1 + "…\"" + value_2

                embed.add_field(name=f"Race Text ID: {cur[0]}",
                                value=value,
                                inline=False)
                count += 1
                if count == 5:
                    messages.append(embed)
                    embed_count += 1
                    count = 0
                    embed = discord.Embed(
                        title=f"More Results for \"{orig_query}\"",
                        color=discord.Color(MAIN_COLOR))
                    embed.set_footer(text=f"Page {embed_count + 1}")
            except:
                continue

        if count == 0 and embed_count == 0:
            embed = discord.Embed(
                title=f"No results found for \"{orig_query}\"",
                color=discord.Color(MAIN_COLOR))
            await ctx.send(embed=embed)
            if count == 0:
                return
            return
        messages.append(embed)

        index = 0
        msg = None
        action = ctx.send
        time_ = time.time()

        while time.time() - time_ < 20 and embed_count > 1:
            res = await action(embed=messages[index])
            if res:
                msg = res
            l = index != 0
            r = index != len(messages) - 1
            await msg.add_reaction('◀️')
            await msg.add_reaction('▶️')
            react, user = await self.bot.wait_for('reaction_add',
                                                  check=predicate(
                                                      msg, l, r, user_id))
            if react.emoji == '◀️':
                index -= 1
            elif react.emoji == '▶️':
                index += 1
            action = msg.edit

        if embed_count <= 1:
            await ctx.send(embed=messages[0])
        return

    @commands.cooldown(3, 25, commands.BucketType.user)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('levenshtein'))
    async def levenshtein(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) == 0:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(
                    ctx,
                    ctx.message).parameters(f"{ctx.invoked_with} [≤40 chars]"))
            return

        query = ' '.join(args)
        query_length = len(query)
        if query_length > 40 and not user_id in BOT_ADMIN_IDS:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(
                    ctx,
                    ctx.message).parameters(f"{ctx.invoked_with} [≤40 chars]"))
            return

        texts = []
        with open(TEXTS_FILE_PATH_CSV, 'r') as csvfile:
            reader = csv.reader(csvfile)
            next(reader)
            for row in reader:
                text = row[1]
                if query_length >= len(text):
                    texts.append([
                        row[0], text, row[2],
                        Levenshtein.distance(query, text), 0
                    ])
                else:
                    min_index = 0
                    min_distance = 10000000
                    for i in range(0, len(text) - query_length):
                        cur_distance = Levenshtein.distance(
                            query, text[i:i + query_length])
                        if cur_distance < min_distance:
                            min_distance = cur_distance
                            min_index = i
                        if cur_distance == 0:
                            min_distance = cur_distance
                            min_index = i
                            break
                    texts.append(
                        [row[0], text, row[2], min_distance, min_index])

        if len(texts) > 5:
            levenshtein_sorted = sorted(texts, key=lambda x: x[3])[0:5]
        else:
            levenshtein_sorted = sorted(texts, key=lambda x: x[3])

        embed = discord.Embed(
            title=("Texts With Smallest Levenshtein Distance "
                   f"to \"{query}\" (Length = {query_length})"),
            color=discord.Color(MAIN_COLOR))
        for cur in levenshtein_sorted:
            min_index = cur[4]
            text = cur[1]

            if len(text) < len(query):
                formatted = f"**{text}**"
            else:
                formatted = (
                    f"{text[0:min_index]}**{text[min_index:min_index + query_length]}"
                    f"**{text[min_index + query_length:]}")

            value_1 = f"\"{formatted}\" "
            value_2 = (f"[{TR_INFO}]({Urls().text(cur[0])}) "
                       f"[{TR_GHOST}]({cur[2]})")
            value = value_1 + value_2
            if len(value) > 1024:
                value_1 = value_1[0:1019 - len(value_2)]
                value = value_1 + "…\"" + value_2
            embed.add_field(
                name=(f"Levenshtein Distance: {cur[3]} ("
                      f"{round(100 * cur[3] / query_length, 2)}%)\n"
                      f"Race Text ID: {cur[0]}"),
                value=value,
                inline=False)

        await ctx.send(embed=embed)
        return

    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('searchid'))
    async def searchid(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) != 1:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(
                    ctx,
                    ctx.message).parameters(f"{ctx.invoked_with} [text_id]"))
            return

        if args[0] == '*':
            tid = get_cached_id(ctx.message.channel.id)
            if not tid:
                tid = args[0]
        else:
            tid = args[0]
        urls = [Urls().text(tid)]

        text = await fetch(urls, 'read', scrape_text)
        if text[0]:
            cache_id(ctx.message.channel.id, tid)
            value_1 = f"\"{text[0]}\""
            value_2 = f" [{TR_INFO}]({urls[0]})"
            value = value_1 + value_2
            if len(value) > 1024:
                value_1 = value_1[0:1019 - len(value_2)]
                value = value_1 + "…\"" + value_2
            embed = discord.Embed(title=f"Search Result for {tid}",
                                  color=discord.Color(MAIN_COLOR))
            embed.add_field(name=f"Race Text ID: {tid}",
                            value=value,
                            inline=False)
            await ctx.send(embed=embed)
            return

        await ctx.send(content=f"<@{user_id}>",
                       embed=Error(ctx, ctx.message).incorrect_format(
                           f"**{tid}** is not a valid text ID"))
        return
Exemple #13
0
class BasicStats(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @commands.cooldown(4, 12, commands.BucketType.user)
    @commands.cooldown(50, 150, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('stats'))
    async def stats(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)
        account = account_information(user_id)
        universe = account['universe']

        if len(args) == 0: args = check_account(user_id)(args)

        if len(args) != 1:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(
                    ctx, ctx.message).parameters(f"{ctx.invoked_with} [user]"))
            return

        player = get_player(user_id, args[0])
        urls = [Urls().get_user(player, universe)]
        try:
            user_api = (await fetch(urls, 'json'))[0]
        except:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).missing_information(
                               (f"[**{player}**]({urls[0]}) "
                                "doesn't exist")))
            return

        country = f":flag_{user_api['country']}: " if user_api[
            'country'] else ''
        name = user_api['name'] if user_api['name'] else ''
        name += ' ' if user_api['name'] and user_api['lastName'] else ''
        name += user_api['lastName'] if user_api['lastName'] else ''
        premium = 'Premium' if user_api['premium'] else 'Basic'
        try:
            banned = user_api['tstats']['disqualified']
            banned = '' if banned == 'false' or not banned else '\n**Status:** Banned'
        except KeyError:
            banned = ''

        urls = [Urls().trd_user(player, universe)]
        try:
            if universe != 'play':
                raise NotImplementedError
            trd_user_api = (await fetch(urls, 'json'))[0]
            textbests = round(float(trd_user_api['account']['wpm_textbests']),
                              2)
            textsraced = trd_user_api['account']['texts_raced']
            extra_stats = (f"**Text Bests: **{textbests} WPM\n"
                           f"**Texts Typed: **{textsraced}\n")
        except:
            textbests, textsraced, extra_stats = ('', ) * 3

        urls = [Urls().user(player, universe)]
        try:
            response = (await fetch(urls, 'text'))[0]
            soup = BeautifulSoup(response, 'html.parser')
            rows = soup.select("table[class='profileDetailsTable']")[0].select(
                'tr')
            medal_count = 0
            for row in rows:
                cells = row.select('td')
                if len(cells) < 2: continue
                if cells[0].text.strip() == 'Racing Since':
                    date_joined = cells[1].text.strip()

            rows = soup.select("table[class='personalInfoTable']")[0].select(
                'tr')
            for row in rows:
                cells = row.select('td')
                if len(cells) < 2: continue
                if cells[0].text.strip() == 'Awards':
                    medal_count = len(cells[1].select('a'))
        except:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).missing_information(
                               (f"[**{player}**]({urls[0]}) "
                                "doesn't exist")))
            return

        if banned:
            color = 0xe0001a
        else:
            color = MAIN_COLOR

        embed = discord.Embed(
            title=f"{country}{player}",
            colour=discord.Colour(color),
            description=f"**Universe:** {href_universe(universe)}",
            url=urls[0])
        embed.set_thumbnail(url=Urls().thumbnail(player))
        embed.add_field(name="General",
                        value=(f"**Name:** {name}\n"
                               f"**Joined: **{date_joined}\n"
                               f"**Membership: **{premium}{banned}"),
                        inline=False)
        embed.add_field(
            name="Stats",
            value=
            (f"""**Races: **{f"{user_api['tstats']['cg']:,}"}\n"""
             f"""**Races Won: **{f"{user_api['tstats']['gamesWon']:,}"}\n"""
             f"""**Points: **{f"{round(user_api['tstats']['points']):,}"}\n"""
             f"""**Full Average: **{round(user_api['tstats']['wpm'], 2)} WPM\n"""
             f"""**Fastest Race: **{round(user_api['tstats']['bestGameWpm'], 2)} WPM\n"""
             f"""**Captcha Speed: **{round(user_api['tstats']['certWpm'], 2)} WPM\n"""
             f"""{extra_stats}**Medals: **{f'{medal_count:,}'}\n"""),
            inline=False)

        await ctx.send(embed=embed)
        await fetch([Urls().trd_import(player)], 'text')
        return

    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('lastonline'))
    async def lastonline(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)
        account = account_information(user_id)
        universe = account['universe']

        if len(args) == 0: args = check_account(user_id)(args)

        if len(args) != 1:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(
                    ctx, ctx.message).parameters(f"{ctx.invoked_with} [user]"))
            return

        player = get_player(user_id, args[0])

        try:
            urls = [Urls().get_races(player, universe, 1)]
            response = (await fetch(urls, 'json', lambda x: x[0]['t']))[0]
        except:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information((
                    f"[**{player}**](https://data.typeracer.com/pit/race_history?user={player}&universe={universe}) "
                    "doesn't exist or has no races in the "
                    f"{href_universe(universe)} universe")))
            return

        time_difference = time.time() - response

        await ctx.send(embed=discord.Embed(
            colour=discord.Colour(MAIN_COLOR),
            description=(
                f"**{player}** last played {seconds_to_text(time_difference)}\n"
                f"ago on the {href_universe(universe)} universe")))
        return

    @commands.cooldown(4, 12, commands.BucketType.user)
    @commands.cooldown(50, 150, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('medals'))
    async def medals(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) == 0: args = check_account(user_id)(args)

        if len(args) != 1:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(
                    ctx, ctx.message).parameters(f"{ctx.invoked_with} [user]"))
        player = get_player(user_id, args[0])
        try:
            urls = [Urls().user(player, 'play')]
            response = (await fetch(urls, 'text'))[0]
            soup = BeautifulSoup(response, 'lxml')
            rows = soup.select("table[class='personalInfoTable']")[0].select(
                'tr')
        except:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).missing_information(
                               (f"[**{player}**]({urls[0]}) "
                                "doesn't exist")))
            return

        medals = []
        for row in rows:
            cells = row.select('td')
            if len(cells) < 2: continue
            if cells[0].text.strip() == "Awards":
                medals = cells[1].select('img')
                break

        breakdown = {
            "g": {
                1: 0,
                2: 0,
                3: 0
            },
            "d": {
                1: 0,
                2: 0,
                3: 0
            },
            "w": {
                1: 0,
                2: 0,
                3: 0
            },
            "m": {
                1: 0,
                2: 0,
                3: 0
            },
            "y": {
                1: 0,
                2: 0,
                3: 0
            }
        }
        for medal in medals:
            title = medal['title']
            breakdown["g"][int(title[0])] += 1
            breakdown[title[17]][int(title[0])] += 1

        general = list(breakdown['g'].values())
        daily = list(breakdown['d'].values())
        weekly = list(breakdown['w'].values())
        monthly = list(breakdown['m'].values())
        yearly = list(breakdown['y'].values())

        if not sum(general):
            embed = discord.Embed(title=f"Medal Stats for {player}",
                                  colour=discord.Colour(MAIN_COLOR),
                                  description="It's empty here.")
            embed.set_thumbnail(url=Urls().thumbnail(player))
            await ctx.send(embed=embed)
            return

        embed = discord.Embed(title=f"Medals Stats for {player}",
                              colour=discord.Colour(MAIN_COLOR))
        embed.set_thumbnail(url=Urls().thumbnail(player))
        helper_constructor = lambda count: (f"**Total: **{sum(count)}\n"
                                            f":first_place: x {count[0]}\n"
                                            f":second_place: x {count[1]}\n"
                                            f":third_place: x {count[2]}")
        embed.add_field(name="General",
                        value=helper_constructor(general),
                        inline=False)
        if sum(daily):
            embed.add_field(name="Daily",
                            value=helper_constructor(daily),
                            inline=True)
        if sum(weekly):
            embed.add_field(name="Weekly",
                            value=helper_constructor(weekly),
                            inline=True)
        if sum(monthly):
            embed.add_field(name="Monthly",
                            value=helper_constructor(monthly),
                            inline=True)
        if sum(yearly):
            embed.add_field(name="Yearly",
                            value=helper_constructor(yearly),
                            inline=True)

        await ctx.send(embed=embed)
        return

    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('toptens'))
    async def toptens(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)
        is_admin = user_id in BOT_ADMIN_IDS
        send_json = is_admin and ctx.invoked_with[-1] == '*'

        if len(args) == 0: args = check_account(user_id)(args)

        if len(args) != 1:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(
                    ctx, ctx.message).parameters(f"{ctx.invoked_with} [user]"))
            return

        player = get_player(user_id, args[0])
        with open(TOPTENS_FILE_PATH, 'r') as jsonfile:
            player_top_tens = json.load(jsonfile)
        last_updated = float(player_top_tens['last updated'])

        if send_json:
            if player == '*':
                await ctx.send(file=discord.File(
                    TOPTENS_JSON_FILE_PATH, f"top_ten_{last_updated}.json"))
                return

            subset = dict()
            with open(TOPTENS_JSON_FILE_PATH, 'r') as jsonfile:
                top_tens = json.load(jsonfile)
            for item, value in top_tens.items():
                if player in value.values():
                    subset.update({item: value})
            with open('temporary.json', 'w') as jsonfile:
                json.dump(subset, jsonfile)
            await ctx.send(file=discord.File(
                'temporary.json', f"top_ten_{player}_{last_updated}.json"))
            os.remove('temporary.json')
            return

        try:
            player_data = player_top_tens[player]
        except KeyError:
            embed = discord.Embed(title=f"Text Top 10 Statistics for {player}",
                                  color=discord.Color(MAIN_COLOR),
                                  description="It's empty here.")
            embed.set_thumbnail(url=Urls().thumbnail(player))

            await ctx.send(embed=embed)
            return

        total = 0
        for value in player_data.values():
            total += int(value)

        breakdown = ''
        for i in range(1, 4):
            breakdown += f"**{i}:** {f'{int(player_data[str(i)]):,}'}\n"
        for i in range(4, 11):
            breakdown += f"**{i}:** {f'{int(player_data[str(i)]):,}'} | "
        breakdown = breakdown[:-3]

        embed = discord.Embed(title=f"Text Top 10 Statistics for {player}",
                              color=discord.Color(MAIN_COLOR),
                              description=f"**{f'{total:,}'}** text top 10s")
        embed.set_thumbnail(url=Urls().thumbnail(player))
        embed.set_footer(
            text=
            f"Text top 10 data was last updated {seconds_to_text(time.time() - last_updated)} ago"
        )
        embed.add_field(name="Breakdown", value=breakdown, inline=False)

        await ctx.send(embed=embed)
        return

    @commands.cooldown(1, 20, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('leaderboard'))
    async def leaderboard(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)
        num_lb = 10
        error_one = Error(ctx, ctx.message) \
                    .parameters(f"{ctx.invoked_with} [races/points/textbests/textstyped/toptens] <num>")
        error_two = Error(ctx, ctx.message) \
                    .incorrect_format('`num` must be a positive integer between 1 and 10')

        if len(args) == 0:
            await ctx.send(content=f"<@{user_id}>", embed=error_one)
            return
        elif len(args) == 2:
            if args[0].lower() != "toptens":
                await ctx.send(content=f"<@{user_id}>", embed=error_one)
                return
            try:
                num_lb = int(args[1])
                if num_lb < 1 or num_lb > 10:
                    await ctx.send(content=f"<@{user_id}>", embed=error_two)
                    return
            except ValueError:
                await ctx.send(content=f"<@{user_id}>", embed=error_two)
                return
        elif len(args) > 2:
            await ctx.send(content=f"<@{user_id}>", embed=error_one)
            return

        category_dict = {
            'races': 'races',
            'points': 'points',
            'textbests': 'wpm_textbests',
            'textstyped': 'texts_raced',
            'toptens': 'toptens'
        }

        try:
            category = category_dict[args[0].lower()]
            urls = [Urls().leaders(category)]
        except KeyError:
            await ctx.send(content=f"<@{user_id}>", embed=error_two)
            return

        def helper_formatter(player, country, parameter, index, *args):
            formatted = NUMBERS[index - 1]
            if country:
                formatted += f":flag_{country}: "
            else:
                formatted += '<:flagblank:744520567113252926> '
            if isinstance(parameter, str):
                formatted += f"{player} - {parameter}\n"
                return formatted
            if args:
                formatted += f"{player} - {f'{parameter:,}'} WPM\n"
                return formatted
            formatted += f"{player} - {f'{round(parameter):,}'}\n"
            return formatted

        value = ''
        if category == 'races': name = '**Races Leaderboard**'
        elif category == 'points': name = '**Points Leaderboard**'
        elif category == 'wpm_textbests': name = '**Text Bests Leaderboard**'
        elif category == 'texts_raced': name = '**Texts Raced Leaderboard**'

        top_players = []
        if category != 'toptens':
            response = await fetch(urls, 'read')
            soup = BeautifulSoup(response[0], 'html.parser')
            rows = soup.select('table')[0].select('tr')
            for i in range(1, 16):
                player = rows[i].select('td')[1].select('a')[0]['href'][18:]
                player_urls = [Urls().get_user(player, 'play')]
                player_response = await fetch(player_urls, 'json')
                player_response = player_response[0]
                country = player_response['country']
                if category == 'races':
                    parameter = int(player_response['tstats']['cg'])
                elif category == 'points':
                    parameter = float(player_response['tstats']['points'])
                elif category == 'wpm_textbests':
                    parameter = float(rows[i].select('td')[2].text)
                elif category == 'texts_raced':
                    parameter = rows[i].select('td')[4].text
                top_players.append([player, parameter, country])

            if category != 'texts_raced':
                top_players = sorted(top_players,
                                     key=lambda x: x[1],
                                     reverse=True)
            if category != 'wpm_textbests':
                for i in range(0, 10):
                    player_info = top_players[i]
                    value += helper_formatter(player_info[0], player_info[2],
                                              player_info[1], i + 1)
            else:
                for i in range(0, 10):
                    player_info = top_players[i]
                    value += helper_formatter(player_info[0], player_info[2],
                                              player_info[1], i + 1, True)
        else:
            with open(TOPTENS_FILE_PATH, 'r') as jsonfile:
                player_top_tens = json.load(jsonfile)
            last_updated = float(player_top_tens['last updated'])
            del player_top_tens['last updated']

            for player, top_tens in player_top_tens.items():
                top_count = 0
                top_tens_values = list(top_tens.values())
                for count in range(0, num_lb):
                    top_count += int(top_tens_values[count])
                top_players.append([player, top_count])

            top_players = [player for player in top_players if player[1] != 0]
            top_players = sorted(top_players, key=lambda x: x[1])
            players_with_top_tens = len(top_players)

            value += f"{f'{players_with_top_tens:,}'} players have top {num_lb}s\n"
            for i in range(0, 10):
                num = NUMBERS[i]
                value += (f"{num} {top_players[-(i + 1)][0]} "
                          f"- {f'{top_players[-(i + 1)][1]:,}'}\n")
            value = value[:-1]
            name = {
                1: 'Ones',
                2: 'Twos',
                3: 'Threes',
                4: 'Fours',
                5: 'Fives',
                6: 'Sixes',
                7: 'Sevens',
                8: 'Eights',
                9: 'Nines',
                10: 'Tens'
            }
            name = f"Text Top {name[num_lb]}"

        embed = discord.Embed(color=discord.Color(MAIN_COLOR))
        embed.add_field(name=name, value=value)
        if category == 'wpm_textbests':
            embed.set_footer(
                text=
                'All users have at least 1,000 races and \n 400 texts typed')
        elif category == 'toptens':
            embed.set_footer(
                text=("Text top 10 data was last updated\n"
                      f"{seconds_to_text(time.time() - last_updated)} ago"))
        await ctx.send(embed=embed)

        if category != 'toptens':
            urls = []
            for player in top_players:
                urls.append(Urls().trd_import(player))
            await fetch(urls, 'text')
        return

    @commands.cooldown(2, 25, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('competition'))
    async def competition(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)
        account = account_information(user_id)
        universe = account['universe']

        categories = {
            'races': 'gamesFinished',
            'points': 'points',
            'wpm': 'wpm'
        }

        try:
            if len(args) == 0:
                sort, category = 'day', 'points'
            elif len(args) == 1:
                param = args[0].lower()
                if param in categories.keys():
                    sort, category = 'day', categories[param]
                elif param in ['day', 'week', 'month', 'year']:
                    sort, category = param, 'points'
                else:
                    raise KeyError
            elif len(args) == 2:
                sort, category = args[0].lower(), categories[args[1].lower()]
            else:
                raise ValueError
        except KeyError:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).incorrect_format(
                    'Must provide a valid category: `races/points/wpm`'))
            return
        except ValueError:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).parameters(
                               f"{ctx.invoked_with} [races/points/wpm]"))
            return

        urls = [Urls().get_competition(12, sort, category, universe)]
        competition = await fetch(urls, 'json')
        competition = competition[0]

        def helper_formatter(player, country, points, races, wpm_sum, index):
            formatted = NUMBERS[index]
            if country:
                formatted += f":flag_{country}: "
            else:
                formatted += "<:flagblank:744520567113252926> "
            formatted += f"{player} - {f'{points:,}'} | {f'{races:,}'} | {f'{round(wpm_sum / races, 2):,}'} WPM\n"
            return formatted

        players = []
        if sort == 'day':
            conn = sqlite3.connect(TEMPORARY_DATABASE_PATH)
            c = conn.cursor()
            for competitor in competition:
                player = competitor[1]['typeracerUid'][3:]
                if competitor[0]['country']:
                    country = competitor[0]['country']
                else:
                    country = ''

                today_timestamp = (datetime.datetime.utcnow().date() \
                                - datetime.date(1970, 1, 1)).total_seconds()
                file_name = f"t_{player}_{universe}_{today_timestamp}_{today_timestamp + 86400}".replace(
                    '.', '_')

                try:
                    user_data = c.execute(
                        f"SELECT * FROM {file_name} ORDER BY gn DESC LIMIT 1")
                    last_race = user_data.fetchone()
                    time_stamp = last_race[1] + 0.01
                except sqlite3.OperationalError:
                    time_stamp = today_timestamp
                    c.execute(
                        f"CREATE TABLE {file_name} (gn integer PRIMARY KEY, t, tid, wpm, pts)"
                    )

                races = await fetch_data(player, universe, time_stamp,
                                         today_timestamp + 86400)
                if races:
                    c.executemany(
                        f"INSERT INTO {file_name} VALUES (?, ?, ?, ?, ?)",
                        races)
                    conn.commit()
                points, wpm = [], []
                data = c.execute(f"SELECT * FROM {file_name}").fetchall()
                for row in data:
                    points.append(row[4])
                    wpm.append(row[3])
                players.append([
                    player, country,
                    round(sum(points)),
                    len(points),
                    round(sum(wpm), 2)
                ])

            conn.close()
        else:
            for competitor in competition:
                player = competitor[1]['typeracerUid'][3:]
                if competitor[0]['country']:
                    country = competitor[0]['country']
                else:
                    country = ''
                comp_stats = competitor[1]
                players.append([
                    player, country,
                    round(comp_stats['points']),
                    round(comp_stats['gamesFinished']),
                    comp_stats['gamesFinished'] * comp_stats['wpm']
                ])

        if category == 'points':
            players = sorted(players, key=lambda x: x[2])[-10:][::-1]
        elif category == 'gamesFinished':
            players = sorted(players, key=lambda x: x[3])[-10:][::-1]
        elif category == 'wpm':
            players = sorted(players, key=lambda x: x[4] / x[3])[-10:][::-1]

        value = ''
        for i in range(0, 10):
            player = players[i]
            value += helper_formatter(player[0], player[1], player[2],
                                      player[3], player[4], i)

        today = datetime.datetime.utcnow().date()

        if sort == 'day': date = today.strftime('%B %-d, %Y')
        elif sort == 'week':
            normalizer = today.isocalendar()[2]
            start_time = today - datetime.timedelta(days=normalizer - 1)
            end_time = today + datetime.timedelta(days=7 - normalizer)
            date = f"{start_time.strftime('%B %-d')}—{end_time.strftime('%-d, %Y')}"
        elif sort == 'month':
            date = today.strftime('%B %Y')
        elif sort == 'year':
            date = today.strftime('%Y')

        formatted_sort = {
            'day': 'Daily',
            'week': 'Weekly',
            'month': 'Monthly',
            'year': 'Yearly'
        }[sort]

        formatted_category = {
            'points': 'points',
            'gamesFinished': 'races',
            'wpm': 'wpm'
        }[category]

        embed = discord.Embed(
            title=f"{formatted_sort} Competition ({formatted_category})",
            color=discord.Color(MAIN_COLOR),
            description=f"**Universe:** {href_universe(universe)}",
            url=Urls().competition(sort, category, '', universe))
        embed.add_field(name=date, value=value)
        await ctx.send(embed=embed)
        return

    @commands.cooldown(5, 10, commands.BucketType.user)
    @commands.cooldown(50, 100, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('timebetween'))
    async def timebetween(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)
        account = account_information(user_id)
        universe = account['universe']

        url_param = False
        if len(args) < 2 or len(args) > 3:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).parameters(
                    (f"{ctx.invoked_with} [url] [url]` or "
                     f"`{ctx.invoked_with} [user] [race_one] [race_two]")))
            return
        if len(args) == 2:
            try:
                args[0].index('result?') and args[1].index('result?')
                url_param = True
            except ValueError:
                args = check_account(user_id)(args)
        if len(args) == 3:
            player = get_player(user_id, args[0])
            try:
                race_one = int(args[1])
                race_two = int(args[2])
                if race_one <= 0 or race_two <= 1:
                    raise ValueError
                args = (race_one, race_two)
            except ValueError:
                await ctx.send(
                    content=f"<@{user_id}>",
                    embed=Error(ctx, ctx.message).incorrect_format(
                        f"Refer to `help {ctx.invoked_with}` for correct parameter formats"
                    ))
                return

        urls = []
        if url_param: urls = [url for url in args]
        else:
            urls = [
                Urls().result(player, race_num, universe) for race_num in args
            ]

        responses = await fetch(urls, 'text', timestamp_scraper)
        try:
            conn = sqlite3.connect(DATABASE_PATH)
            c = conn.cursor()
            player = responses[0]['player']
            difference = abs(responses[1]['timestamp'] -
                             responses[0]['timestamp'])
            universe = responses[0]['universe']
            race_nums = [response['race_number'] for response in responses]
            race_one = min(race_nums)
            race_two = max(race_nums)
        except TypeError:
            try:
                if escape_sequence(player):
                    raise sqlite3.OperationalError
                difference = abs(c.execute(f"SELECT t FROM t_{player} WHERE gn = ?", (race_one,))
                                           .fetchone()[0] -\
                                 c.execute(f"SELECT t FROM t_{player} WHERE gn = ?", (race_two,))
                                           .fetchone()[0])
            except:
                conn.close()
                await ctx.send(
                    content=f"<@{user_id}>",
                    embed=Error(ctx, ctx.message).missing_information(
                        '`timestamp` was not found in either race'))
                return
        conn.close()

        description = (f"The time between **{player}**'s "
                       f"**{num_to_text(race_one)}** and "
                       f"**{num_to_text(race_two)}** race in the\n"
                       f"{href_universe(universe)} universe was "
                       f"**{seconds_to_text(difference)}**")

        await ctx.send(embed=discord.Embed(color=discord.Color(MAIN_COLOR),
                                           description=description))
        return
Exemple #14
0
class TypeRacerOnly(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
        self.medals = [':first_place:', ':second_place:', ':third_place:']
        self.records_information = dict()
        self.accounts = dict()
        self.countries = dict()
        self.last_updated = ''
        self.races_alltime = dict()
        self.points_alltime = dict()
        self.awards_alltime = dict()
        self.country_tally = dict()
        self.user_tally = dict()

        self.update_init_variables()
        self.update_records.start()

    def cog_load(self):
        self.update_records.start()

    def cog_unload(self):
        self.update_records.cancel()

    @commands.Cog.listener()
    async def on_message(self, message):
        if message.author.id == self.bot.user.id:
            return

        if message.guild.id != 175964903033667585:  #TypeRacer Discord guild ID
            return

        message_content_lower = message.content.lower()

        if 't!tg train' in message_content_lower:
            if message.channel.id != 746460695670816798:  #typeracer-stats in TypeRacer main server
                return
            if message.author.id == 476016555981930526:  #pasta's Discord ID
                await message.channel.send(
                    content=f"<@{message.author.id}>, this is NOT bot usage.")
                return
            else:
                embed = discord.Embed(
                    title='Training Success!',
                    color=discord.Color(0x4B9F6C),
                    description=
                    'Your pet ran around you in circles. It is now resting on your lap!'
                )
                embed.set_thumbnail(
                    url=
                    'https://cdn.discordapp.com/attachments/448997775309537280/791689215531679764/pet.png'
                )
                embed.add_field(
                    name='Updated Attributes',
                    value=
                    ('<:pet_xp:791713169763991553> Exp: **10 / 10200** (`+10`)\n'
                     '<:pet_fatigue:791713209371459584> Fatigue: **12 / 504** (`+3`)'
                     ))
                embed.set_footer(
                    text=
                    'Copied from Tatsu#8792 to meme pasta | Wrapped Tatsugotchis are tradable, but not unwrapped ones.'
                )
                await message.channel.send(content=(
                    '>  **Interacting with Pet • [ **'
                    f"{message.author.name}#{message.author.discriminator}"
                    '** ] • **`1`<a:mail_new_small:791710454946857001>'
                    '`2`<a:dice:791710772984021013>'),
                                           embed=embed)
                return

        words_set = set(re.findall('[a-z0-9]+', message_content_lower))

        if not ({'how', 'dark', 'mode'} - words_set):
            embed = discord.Embed(
                title='FAQ: How do I get dark mode on TypeRacer?',
                color=discord.Color(0),
                description=
                ('_By: Keegan_\n\n'
                 'With the Chrome extension "[Stylus]'
                 '(https://chrome.google.com/webstore/detail/stylus/clngdbkpkpeebahjckkjfobafhncgmne?hl=en)," '
                 'any theme can be applied to any website. '
                 'If you know the basics of `css`, you can create your own theme. '
                 'If you do not, you can browse [themes created by the community]'
                 '(https://userstyles.org/styles/browse/typeracer). '
                 'The most popular ones for TypeRacer are '
                 '[TypeRacer modern dark]'
                 '(https://userstyles.org/styles/140579/typeracer-modern-dark) '
                 'and [TypeRacer modern dark by Hysteria]'
                 '(https://userstyles.org/styles/164591/typeracer-modern-dark-by-hysteria).'
                 ))

            await message.channel.send(content=f"<@{message.author.id}>",
                                       embed=embed)
            return

        if not ({'show', 'unlagged'} - words_set) or not (
            {'show', 'adjusted'} - words_set) or not ({'show', 'raw'} -
                                                      words_set):
            embed = discord.Embed(
                title='FAQ: How do I get unlagged/adjusted/raw WPM to show?',
                color=discord.Color(0),
                description=
                ('_By: Poem_\n\n'
                 'To install, ```'
                 '1) Add the Tampermonkey extension to your browser (from the Chrome web store, '
                 'also available for most other browsers)\n'
                 '2) Click the link below\n'
                 '3) Click \'install.\'```'))
            embed.add_field(
                name='Unlagged/Adjusted/Raw Speed, Text Difficulty',
                value=
                ('[__`Script`__]'
                 '(https://github.com/PoemOnTyperacer/tampermonkey/raw/master/adjusted_speed.user.js) - '
                 'Thanks to **ph0t0shop#6788** for help implementing various changes such as cross-browser support!\n'
                 '[Information on the "difficulty" in this script.]'
                 '(http://bit.ly/typeracertextdifficulty)\n\n'
                 '_Warning 1: this script is in beta. If you notice it causing repeated glitches to your gamme, disable it.'
                 '<#746460695670816798> can also give you quick access to the unlagged, adjusted, and raw speed metrics._'
                 ),
                inline=False)

            await message.channel.send(content=f"<@{message.author.id}>",
                                       embed=embed)
            return

        if not ({'what', 'are', 'points'} - words_set):
            embed = discord.Embed(
                title='FAQ: What are points?',
                color=discord.Color(0),
                description=
                ('Points are an arbitrary measure of a user\'s speed and amount played. '
                 'You receive these simply by playing the game: '
                 'After each race, you will receive `(words per second) • (number of words)` points. '
                 'There is no functionality to points other than to serve as a statistic/measure.'
                 ))

            await message.channel.send(content=f"<@{message.author.id}>",
                                       embed=embed)
            return

        if not ({'how', 'type', 'fast'} -
                words_set) or not ({'how', 'type', 'faster'} - words_set):
            embed = discord.Embed(
                title='FAQ: How do I type faster?',
                color=discord.Color(0),
                description=('_By: Izzy_\n\n'
                             'type the words faster and in the right order'))

            await message.channel.send(content=f"<@{message.author.id}>",
                                       embed=embed)
            return

        return

    #e6f4e37l's Discord ID / TypeRacer Discord guild ID
    @commands.check(lambda ctx: ctx.message.author.id == 697048255254495312 and
                    ctx.message.guild.id == 175964903033667585)
    @commands.command()
    async def eugeneroles(self, ctx):
        role_ids = [
            450016581292916761,  #Premium
            492709794877145109,  #Partner
            676030226874236948,  #Sheet Master
            658349038403452939  #Updates
        ]

        get_role = lambda id_: discord.utils.get(ctx.message.guild.roles,
                                                 id=id_)
        roles = map(get_role, role_ids)
        for role in roles:
            try:
                await ctx.message.author.add_roles(role)
            except:
                pass
        return

    @commands.check(lambda ctx: ctx.message.author.id in BOT_OWNER_IDS and
                    check_banned_status(ctx))
    @commands.cooldown(5, 7200, commands.BucketType.default)
    @commands.command(aliases=['keegant', 'tr_records'])
    async def keegan(self, ctx, *args):
        user_id = ctx.message.author.id

        actions = {'setup': self.records_setup, 'update': self.records_update}

        if len(args) > 1:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(
                    ctx,
                    ctx.message).parameters(f"{ctx.invoked_with} <action>"))
            return

        if len(args) == 0:
            file_ = discord.File(
                TYPERACER_RECORDS_JSON,
                filename=f"typeracer_records_{self.last_updated.lower()}.json")
            await ctx.send(file=file_)
            return

        action = args[0].lower()
        try:
            action = actions[action]
        except KeyError:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).incorrect_format(
                    f"Must provide a valid action: `{'`, `'.join(actions.keys())}`"
                ))
            return

        await action(ctx)
        return

    async def records_setup(self, ctx):
        self.update_init_variables()
        embeds = await self.construct_embeds()
        for name, embed in embeds.items():
            message = await ctx.send(embed=embed)
            if name == 'faq':
                self.records_information['information'].update(
                    {'message_id': message.id})
            else:
                self.records_information['all_records'][name].update(
                    {'message_id': message.id})
            self.records_information.update({'channel_id': message.channel.id})
            await asyncio.sleep(1)

        with open(TYPERACER_RECORDS_JSON, 'w') as jsonfile:
            json.dump(self.records_information, jsonfile, indent=4)

    async def records_update(self, ctx):
        user_id = ctx.message.author.id

        try:
            updated_file_raw = ctx.message.attachments[0]
        except IndexError:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).incorrect_format(
                    'Please upload a file and comment the command call'))
            return

        try:
            updated_file = json.loads(await updated_file_raw.read())
        except json.JSONDecodeError:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).incorrect_format(
                    'The uploaded file is not a properly formatted JSON file'))
            return

        with open(TYPERACER_RECORDS_JSON, 'w') as jsonfile:
            json.dump(updated_file, jsonfile, indent=4)

        try:
            await self.edit_record_messages()
        except NotImplementedError:
            await ctx.send(
                content=f"<@{ctx.message.author.id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    f"Must set-up the records with `{ctx.invoked_with} setup` first"
                ))
            return

        await ctx.send(embed=discord.Embed(title='Records Updated',
                                           color=discord.Color(0)))
        return

    @tasks.loop(hours=24)
    async def update_records(self):
        await self.edit_record_messages()

    async def edit_record_messages(self):
        try:
            self.update_init_variables()

            channel = await self.bot.fetch_channel(
                self.records_information['channel_id'])
            embeds = await self.construct_embeds()
            for name, embed in embeds.items():
                if name == 'faq':
                    message_id = self.records_information['information'][
                        'message_id']
                else:
                    message_id = self.records_information['all_records'][name][
                        'message_id']
                message = await channel.fetch_message(message_id)
                await asyncio.sleep(1)
                await message.edit(embed=embed)
                await asyncio.sleep(1)
        except:
            raise NotImplementedError
        return

    def update_init_variables(self):
        with open(TYPERACER_RECORDS_JSON, 'r') as jsonfile:
            self.records_information = json.load(jsonfile)

        for main, sub_accounts in self.records_information['accounts'].items():
            for sub_account in sub_accounts:
                self.accounts.update({sub_account: main})
            self.accounts.update({main: main})

        self.countries = self.records_information['countries']
        self.last_updated = datetime.datetime.utcnow().strftime(
            '%B %-d, %Y, %X %p')
        self.races_alltime = self.records_information['all_records']['races'][
            'all_time']
        self.points_alltime = self.records_information['all_records'][
            'points']['all_time']
        self.awards_alltime = self.records_information['all_records'][
            'awards']['all_time']

    async def construct_embeds(self):
        self.last_updated = datetime.datetime.utcnow().strftime(
            '%B %-d, %Y, %X %p')
        self.country_tally = dict()
        self.user_tally = dict()

        faq_thumbnail = HELP_IMG
        speed_thumbnail, speed_color = 'https://i.imgur.com/bXAjl4C.png', 0x001359
        three_hundred_thumbnail, three_hundred_color = 'https://i.imgur.com/i8kXn3K.png', 0x1B038C
        races_thumbnail, races_color = 'https://i.imgur.com/DspJLUH.png', 0x14328C
        points_thumbnail, points_color = 'https://i.imgur.com/Xm0VNQV.png', 0x0B4A9E
        speedruns_thumbnail, speedruns_color = 'https://i.imgur.com/lPdQvvQ.png', 0x0E61D1
        awards_thumbnail, awards_color = 'https://i.imgur.com/W9NEYb2.png', 0x0790E8
        records_held_thumbnail, records_held_color = 'https://i.imgur.com/3gJNZRO.png', 0x00BFFF
        last_updated_color = 0x00EEFF

        faq_information = self.records_information['information']
        faq_embed = discord.Embed(**faq_information)
        for field in faq_information['fields']:
            faq_embed.add_field(**field)
        faq_embed.set_thumbnail(url=faq_thumbnail)
        faq_embed.set_footer(**faq_information['footer'])

        all_records_information = self.records_information['all_records']

        speed_information = all_records_information['speed']['records']
        speed_embed = discord.Embed(title='Speed Records',
                                    color=discord.Color(speed_color))
        for speed_record in speed_information:
            speed_embed.add_field(
                **self.record_field_constructor(speed_record, ' WPM'))
        speed_embed.set_thumbnail(url=speed_thumbnail)

        three_hundred_information = all_records_information['300_wpm_club'][
            'records']
        description, members_list = '', []

        for member, url in three_hundred_information.items():
            try:
                result = (await fetch([url], 'text', rs_typinglog_scraper))[0]
                wpm = round(
                    12000 * (result['length'] - 1) /
                    (result['duration'] - result['start']), 3)
                date = datetime.datetime.fromtimestamp(
                    result['timestamp']).strftime('%-m/%-d/%y')
                members_list.append([member, wpm, url, date])
            except TypeError:
                pass
            await asyncio.sleep(5)

        members_list = sorted(members_list, key=lambda x: x[1], reverse=True)

        for i, member in enumerate(members_list):
            if i < 3: rank = self.medals[i]
            else: rank = f"{i + 1}."
            if member[1] >= 400: wpm = f"**{member[1]}**"
            else: wpm = member[1]
            description += f"{rank} {self.get_flag(member[0])} {member[0]} - "
            description += f"[{wpm} WPM]({member[2]}) - {member[3]}\n"

        description = description[:-1]
        three_hundred_embed = discord.Embed(
            title='300 WPM Club',
            color=discord.Color(three_hundred_color),
            description=description)
        three_hundred_embed.set_thumbnail(url=three_hundred_thumbnail)
        three_hundred_embed.set_footer(
            text='All speeds measured according to adjusted metric')

        all_time_leaders = [
            user for category in self.races_alltime.values()
            for user in category
        ]
        all_time_leaders += [
            user for category in self.points_alltime.values()
            for user in category
        ]
        all_time_leaders += [
            user for category in self.awards_alltime.values()
            for user in category
        ]
        all_time_leaders = set(all_time_leaders)
        all_time_data = dict()

        url_constructor = Urls()
        right_now = datetime.datetime.utcnow().timestamp()
        texts_data = load_texts_json()
        with open(COUNTRY_CODES, 'r') as jsonfile:
            country_codes = json.load(jsonfile)
        conn = sqlite3.connect(DATABASE_PATH)
        c = conn.cursor()

        for user in all_time_leaders:
            await asyncio.sleep(5)
            medals = {1: 0, 2: 0, 3: 0}
            try:
                response = (await fetch([url_constructor.user(user, 'play')],
                                        'text'))[0]
                soup = BeautifulSoup(response, 'html.parser')

                rows = soup.select(
                    "table[class='personalInfoTable']")[0].select('tr')

                medal_html_objects = None
                for row in rows:
                    cells = row.select('td')
                    if len(cells) < 2: continue
                    if cells[0].text.strip() == 'Awards':
                        medal_html_objects = cells[1].select('a')
                        break

                if medal_html_objects:
                    for medal in medal_html_objects:
                        medals[int(medal.select('img')[0]['title'][0])] += 1

                if escape_sequence(user):
                    raise FileNotFoundError
                urls = [Urls().get_races(user, 'play', 1)]

                user_data = c.execute(
                    f"SELECT * FROM t_{user} ORDER BY t DESC LIMIT 1")
                last_race_timestamp = user_data.fetchone()[1]

                data = await fetch_data(user, 'play',
                                        last_race_timestamp + 0.01, right_now)
                if data:
                    c.executemany(
                        f"INSERT INTO t_{user} VALUES (?, ?, ?, ?, ?)", data)
                conn.commit()
                data = c.execute(f"SELECT * FROM t_{user}")

                races, chars_typed, points, total_points, seconds_played = (
                    0, ) * 5
                for race in data:
                    races += 1
                    race_text_id = str(race[2])
                    text_length = texts_data.get(race_text_id,
                                                 {"length": 0})['length']
                    chars_typed += text_length
                    points += race[4]
                    total_points += race[4] if race[4] else texts_data.get(
                        race_text_id,
                        {"word count": 0})['word count'] * race[3] / 60
                    try:
                        seconds_played += 12 * text_length / race[3]
                    except ZeroDivisionError:
                        seconds_played += 0

                first_race_timestamp = c.execute(
                    f"SELECT * FROM t_{user} ORDER BY t ASC LIMIT 1").fetchone(
                    )[1]
                time_difference = right_now - first_race_timestamp
                points_time_difference = min(right_now - 1_501_113_600,
                                             time_difference)

                all_time_data.update({
                    user: {
                        'races': races,
                        'races_daily_average': 86400 * races / time_difference,
                        'chars_typed': chars_typed,
                        'points': points,
                        'points_daily_average':
                        86400 * points / points_time_difference,
                        'total_points': total_points,
                        'medals': medals,
                        'seconds_played': seconds_played,
                        'seconds_played_daily_average':
                        86400 * seconds_played / time_difference,
                        'time_difference': time_difference,
                        'points_time_difference': points_time_difference
                    }
                })
            except Exception as exception:
                print(user, exception)
                continue

        conn.close()

        def helper_formatter(tally_dict, name_formatter, results_formatter,
                             *args):
            rankings = dict()
            args = args[0]
            for key, value in tally_dict.items():
                try:
                    rankings[value].append(key)
                except KeyError:
                    rankings[value] = [key]

            rankings = [[k, v] for k, v in sorted(
                rankings.items(), key=lambda x: x[0], reverse=True)][0:3]
            value = ''
            for i, ranking in enumerate(rankings):
                if i == 0 and args[0]:
                    for user in ranking[1]:
                        self.tally(user)

                if len(args) == 1:
                    optional = ''
                else:
                    try:
                        optional = args[1][ranking[1][0]]
                    except KeyError:
                        optional = ''

                ranking[1] = [name_formatter(j) for j in ranking[1]]
                value += f"{self.medals[i]} {' / '.join(ranking[1])} - {results_formatter(ranking[0])}{optional}\n"
            return value

        races_information = all_records_information['races']['records']
        races_embed = discord.Embed(title='Race Records',
                                    color=discord.Color(races_color))
        races_embed.set_thumbnail(url=races_thumbnail)

        helper_sorter = lambda param: {
            k: v[param]
            for k, v in all_time_data.items()
        }
        helper_flag = lambda x: f"{self.get_flag(x)} {x}"
        helper_round = lambda x: f'{round(x):,}'

        races_daily_average_dict = dict()
        points_daily_average_dict = dict()
        chars_typed_metadata_dict = dict()
        medal_breakdown_dict = dict()
        for key, value in all_time_data.items():
            days = round(value['time_difference'] / 86400)
            days_ = round(value['points_time_difference'] / 86400)
            medal_breakdown = value['medals']

            races_daily_average_dict.update({
                key:
                f""" ({f"{round(value['races'], 2):,}"} races over {f"{days:,}"} days)"""
            })

            points_daily_average_dict.update({
                key:
                f""" ({f"{round(value['points']):,}"} points over {f"{days_:,}"} days)"""
            })

            chars_typed_metadata_dict.update({
                key:
                f""" ({f"{round(value['chars_typed'] / value['races'], 2):,}"} average chars over {f"{value['races']:,}"} races)"""
            })

            medal_breakdown_dict.update({
                key:
                f""" (:first_place: {f"{medal_breakdown[1]:,}"} :second_place: {f"{medal_breakdown[2]:,}"} :third_place: {f"{medal_breakdown[3]:,}"})"""
            })

        races_embed.add_field(name='All-Time Leaders',
                              value=helper_formatter(helper_sorter('races'),
                                                     helper_flag, helper_round,
                                                     (True, )),
                              inline=False)
        races_embed.add_field(name='Highest Average Daily Races',
                              value=helper_formatter(
                                  helper_sorter('races_daily_average'),
                                  helper_flag, helper_round,
                                  (True, races_daily_average_dict)),
                              inline=False)
        races_embed.add_field(name='Most Characters Typed',
                              value=helper_formatter(
                                  helper_sorter('chars_typed'), helper_flag,
                                  helper_round,
                                  (True, chars_typed_metadata_dict)),
                              inline=False)
        races_embed.add_field(name='Most Time Spent Racing',
                              value=helper_formatter(
                                  helper_sorter('seconds_played'), helper_flag,
                                  seconds_to_text, (True, )),
                              inline=False)
        races_embed.add_field(
            name='Highest Average Daily Time Spent Racing',
            value=helper_formatter(
                helper_sorter('seconds_played_daily_average'), helper_flag,
                seconds_to_text, (True, )),
            inline=False)

        for races_record in races_information:
            races_embed.add_field(
                **self.record_field_constructor(races_record, ''))

        points_information = all_records_information['points']['records']
        points_embed = discord.Embed(title='Point Records',
                                     color=discord.Color(points_color))
        points_embed.set_thumbnail(url=points_thumbnail)

        points_embed.add_field(name='All-Time Leaders',
                               value=helper_formatter(helper_sorter('points'),
                                                      helper_flag,
                                                      helper_round, (True, )),
                               inline=False)
        points_embed.add_field(name='Highest Average Daily Points',
                               value=helper_formatter(
                                   helper_sorter('points_daily_average'),
                                   helper_flag, helper_round,
                                   (True, points_daily_average_dict)),
                               inline=False)
        points_embed.add_field(
            name='All-Time Total Points (Includes Retroactive Points)',
            value=helper_formatter(helper_sorter('total_points'), helper_flag,
                                   helper_round, (True, )),
            inline=False)
        points_embed.set_footer(
            text=
            'Retroactive points represent the total number of points a user would have gained, before points were introduced in 2017'
        )

        for points_record in points_information:
            points_embed.add_field(
                **self.record_field_constructor(points_record, ''))

        speedruns_information = all_records_information['speedruns']['records']
        speedruns_embed = discord.Embed(title='Speedrun Records',
                                        color=discord.Color(speedruns_color))
        speedruns_embed.set_thumbnail(url=speedruns_thumbnail)

        for speedruns_record in speedruns_information:
            speedruns_embed.add_field(
                **self.record_field_constructor(speedruns_record, ''))

        awards_embed = discord.Embed(title='Awards Records',
                                     color=discord.Color(awards_color))
        awards_embed.set_thumbnail(url=awards_thumbnail)

        medal_tally = {
            k: sum(v['medals'].values())
            for k, v in all_time_data.items()
        }

        awards_embed.add_field(
            name='All-Time Leaders',
            value=helper_formatter(medal_tally, helper_flag, helper_round,
                                   (True, medal_breakdown_dict)),
            inline=False)

        records_held_embed = discord.Embed(
            title='Records Held', color=discord.Color(records_held_color))
        records_held_embed.set_thumbnail(url=records_held_thumbnail)

        top_countries_list = [[k, v] for k, v in self.country_tally.items()]
        records_held_embed.add_field(name = 'Top Countries',
                                     value = helper_formatter(self.country_tally,\
                                                              lambda x: f"{x} {country_codes[re.findall(':flag.+:', x)[0][5:-1].strip('_').upper()]}",
                                                              helper_round,
                                                              (False,)),
                                     inline = False)
        records_held_embed.add_field(name='Top Users',
                                     value=helper_formatter(
                                         self.user_tally, helper_flag,
                                         helper_round, (False, )),
                                     inline=False)

        last_updated_embed = discord.Embed(
            color=discord.Color(last_updated_color),
            description=f"Last updated **{self.last_updated} UTC**")

        return {
            'faq': faq_embed,
            'speed': speed_embed,
            '300_wpm_club': three_hundred_embed,
            'races': races_embed,
            'points': points_embed,
            'speedruns': speedruns_embed,
            'awards': awards_embed,
            'records_held': records_held_embed,
            'last_updated': last_updated_embed
        }

    def record_field_constructor(self, record_information, unit):
        user = record_information['user']
        if 'Race' in record_information['title'].split(' '): emote = ':cinema:'
        else: emote = TR_INFO
        try:
            formatted_record = f"{record_information['record']:,}"
        except ValueError:
            formatted_record = record_information['record']
        value = (f"{self.get_flag(user)} {user} - "
                 f"{formatted_record}{unit} - "
                 f"{record_information['date']} "
                 f"[{emote}]({record_information['url']})")

        self.tally(user)

        return {
            'name': record_information['title'],
            'value': value,
            'inline': False
        }

    def get_flag(self, user):
        user = self.normalize_user(user)

        try:
            flag = f":flag_{self.countries[user]}:"
        except KeyError:
            flag = BLANK_FLAG

        return flag

    def tally(self, user):
        user = self.normalize_user(user)

        try:
            self.user_tally[user] += 1
        except KeyError:
            self.user_tally[user] = 1

        country = self.get_flag(user)
        try:
            self.country_tally[country] += 1
        except KeyError:
            self.country_tally[country] = 1

    def normalize_user(self, user):
        try:
            return self.accounts[user]
        except KeyError:
            return user
Exemple #15
0
class FullStats(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @commands.cooldown(10, 30, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('racesover'))
    async def racesover(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) != 3:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).parameters(
                    f"{ctx.invoked_with} [user] [num] [wpm/points]"))
            return

        player = get_player(user_id, args[0])
        if escape_sequence(player):
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    (f"[**{player}**]({Urls().user(player, 'play')}) "
                     "doesn't exist")))
            return

        try:
            num = float(args[1])
            if num <= 0:
                raise ValueError
        except ValueError:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               '`num` must be a positive number'))
            return

        categories = ['wpm', 'points']

        if not args[2] in categories:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               '`category` must be `wpm/points`'))
            return

        if args[2] == 'points':
            category = 'pts'
        else:
            category = 'wpm'

        conn = sqlite3.connect(DATABASE_PATH)
        c = conn.cursor()

        meeting, total = 0, 0
        try:
            user_data = c.execute(f"SELECT {category} FROM t_{player}")
            for row in user_data:
                total += 1
                if row[0] > num:
                    meeting += 1
        except sqlite3.OperationalError:
            conn.close()
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).not_downloaded())
            return
        conn.close()

        category = {'wpm': 'WPM', 'pts': 'points'}[category]

        embed = discord.Embed(
            title=f"{player}'s Total Races Over {f'{num:,}'} {category}",
            color=discord.Color(MAIN_COLOR),
            description=
            (f"{f'{meeting:,}'} of {player}'s {f'{total:,}'} are above "
             f"{f'{num:,}'} {category} ({f'{round(100 * meeting / total, 2):,}'}%)"
             ))
        embed.set_footer(
            text='Counts texts GREATER than specified parameter (not equal to)'
        )

        await ctx.send(embed=embed)
        return

    @commands.cooldown(10, 30, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('milestone'))
    async def milestone(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) != 3:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).parameters(
                    f"{ctx.invoked_with} [user] [num] [races/wpm/points]"))
            return

        player = get_player(user_id, args[0])
        if escape_sequence(player):
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    (f"[**{player}**]({Urls().user(player, 'play')}) "
                     "doesn't exist")))
            return

        try:
            num = int(args[1])
            if num <= 0:
                raise ValueError
        except ValueError:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               '`num` must be a positive integer'))
            return

        categories = ['races', 'wpm', 'points']

        if not args[2] in categories:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               '`category` must be `races/wpm/points`'))
            return

        conn = sqlite3.connect(DATABASE_PATH)
        c = conn.cursor()

        try:
            first_race = c.execute(
                f"SELECT t FROM t_{player} LIMIT 1").fetchone()[0]
            if args[2] == 'wpm':
                achieved, race_num = c.execute(
                    f"SELECT t, gn FROM t_{player} WHERE wpm >= ? ORDER BY t LIMIT 1",
                    (num, )).fetchone()
            elif args[2] == 'races':
                achieved, race_num = c.execute(
                    f"SELECT t, gn FROM t_{player} WHERE gn == ?",
                    (num, )).fetchone()
            else:
                user_data = c.execute(
                    f"SELECT t, pts, gn FROM t_{player} ORDER BY t")
                sum_, achieved = 0, 0
                for row in user_data:
                    sum_ += row[1]
                    if sum_ >= num:
                        achieved = row[0]
                        race_num = row[2]
                        break
        except sqlite3.OperationalError:
            conn.close()
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).not_downloaded())
            return
        except TypeError:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               'The user has not achieved the milestone yet'))
            return
        conn.close()

        if not achieved:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).incorrect_format(
                    f"[**{player}**]({Urls().user(player, 'play')}) has not achieved the milestone yet"
                ))
            return

        category_1 = {'races': '', 'wpm': ' WPM', 'points': ' Point'}[args[2]]
        category_2 = {
            'races': 'races',
            'wpm': 'WPM',
            'points': 'points'
        }[args[2]]
        embed = discord.Embed(
            title=f"{player}'s {num_to_text(num)}{category_1} Race",
            color=discord.Color(MAIN_COLOR),
            url=Urls().result(player, race_num, 'play'))
        embed.set_thumbnail(url=Urls().thumbnail(player))
        embed.add_field(
            name=f"{player} achieved {f'{num:,}'} {category_2} on:",
            value=
            f"{datetime.datetime.fromtimestamp(achieved).strftime('%B %d, %Y, %I:%M:%S %p')} UTC"
        )
        embed.add_field(name='It took:',
                        value=seconds_to_text(achieved - first_race))

        await ctx.send(embed=embed)
        return

    @commands.cooldown(10, 30, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('marathon'))
    async def marathon(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) == 0:
            args = check_account(user_id)(args) + (86400, 'races')
        elif len(args) == 1:
            args += (86400, 'races')
        elif len(args) == 2:
            args += ('races', )

        if len(args) != 3:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).parameters(
                    f"{ctx.invoked_with} [user] <seconds> <races/points>"))
            return

        player = get_player(user_id, args[0])
        if escape_sequence(player):
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    (f"[**{player}**]({Urls().user(player, 'play')}) "
                     "doesn't exist")))
            return
        try:
            session_length = float(args[1])
            if session_length <= 0:
                raise ValueError
        except ValueError:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               '`seconds` must be a positive number'))
            return

        category = args[2].lower()
        if not category in ['races', 'points']:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               "`category` must be `races/points`"))
            return

        conn = sqlite3.connect(DATABASE_PATH)
        c = conn.cursor()
        try:
            user_data = c.execute(
                f"SELECT * FROM t_{player} ORDER BY t").fetchall()
        except sqlite3.OperationalError:
            conn.close()
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).not_downloaded())
            return
        conn.close()

        length = len(user_data)
        if category == 'races':
            cur_min, max_start, max_end = (0, ) * 3
            for i in range(0, length):
                if user_data[i][1] - user_data[cur_min][1] >= session_length:
                    if i - cur_min > max_end - max_start:
                        max_start, max_end = cur_min, i
                while user_data[i][1] - user_data[cur_min][1] > session_length:
                    cur_min += 1
            if length - cur_min - 1 > max_end - max_start:
                max_start, max_end = cur_min, length
        elif category == 'points':
            cur_min, max_start, max_end, cur_points, max_points = (0, ) * 5
            for i in range(0, length):
                cur_points += user_data[i][4]
                if user_data[i][1] - user_data[cur_min][1] >= session_length:
                    if cur_points > max_points:
                        max_start, max_end, max_points = cur_min, i, cur_points
                while user_data[i][1] - user_data[cur_min][1] > session_length:
                    cur_points -= user_data[cur_min][4]
                    cur_min += 1
            if cur_points + user_data[length - 1][4] > max_points:
                max_start, max_end = cur_min, length

        races, seconds_played, chars_typed, words_typed, points, wpm_sum, wpm_max = (
            0, ) * 7
        wpm_min = 100000
        text_data = load_texts_json()
        for i in range(max_start, max_end):
            races += 1
            cur = user_data[i]
            wpm = cur[3]
            tid = str(cur[2])

            wpm_sum += wpm
            wpm_min = min(wpm, wpm_min)
            wpm_max = max(wpm, wpm_max)

            words = text_data[tid]['word count']
            chars = text_data[tid]['length']
            words_typed += words
            chars_typed += chars
            seconds_played += 12 * chars / wpm
            points += cur[4]
            if cur[4] == 0:
                points += wpm * words / 60

        f_category = {'races': 'Races', 'points': 'Points'}[category]

        max_end -= 1

        embed = discord.Embed(
            title=(f"{f_category} Marathon Stats for {player} "
                   f"({seconds_to_text(session_length, True)} period)"),
            color=discord.Color(MAIN_COLOR))
        embed.set_thumbnail(url=Urls().thumbnail(player))
        embed.set_footer(text=(
            f"First Race (#{f'{max_start + 1:,}'}): {datetime.datetime.fromtimestamp(user_data[max_start][1]).strftime('%B %-d, %Y, %-I:%M:%S %p')} | "
            "Retroactive points represent the total number of "
            "points a user would have gained, before points were introduced in 2017"
        ))
        embed.add_field(
            name='Races',
            value=
            (f"**Total Races:** {f'{races:,}'}\n"
             f"**Total Words Typed:** {f'{words_typed:,}'}\n"
             f"**Average Words Per Races:** {f'{round(words_typed / races, 2):,}'}\n"
             f"**Total Chars Typed:** {f'{chars_typed:,}'}\n"
             f"**Average Chars Per Race:** {f'{round(chars_typed / races, 2):,}'}\n"
             f"**Total Time Spent Racing:** {seconds_to_text(seconds_played)}\n"
             f"**Total Time Elapsed:** {seconds_to_text(user_data[max_end][1] - user_data[max_start][1])}\n"
             f"**Average Time Per Race:** {seconds_to_text(seconds_played / races)}"
             ),
            inline=False)
        embed.add_field(
            name='Points (Retroactive Included)',
            value=
            (f"**Total Points:** {f'{round(points):,}'}\n"
             f"**Average Points Per Race:** {f'{round(points / races, 2):,}'}\n"
             ),
            inline=False)
        embed.add_field(
            name='Speed',
            value=
            (f"**Average (Lagged):** {f'{round(wpm_sum / races, 2):,}'} WPM\n"
             f"**Fastest Race:** {f'{wpm_max:,}'} WPM\n"
             f"**Slowest Race:** {f'{wpm_min:,}'} WPM"),
            inline=False)

        await ctx.send(embed=embed)
        return

    @commands.cooldown(10, 30, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('sessionstats'))
    async def sessionstats(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) == 0: args = check_account(user_id)(args)

        if len(args) == 0 or len(args) > 2:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).parameters(
                               f"{ctx.invoked_with} [user] <seconds>"))
            return

        player = get_player(user_id, args[0])
        if escape_sequence(player):
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    (f"[**{player}**]({Urls().user(player, 'play')}) "
                     "doesn't exist")))
            return
        try:
            if len(args) == 1:
                session_length = 1800
            else:
                session_length = float(args[1])
            if session_length <= 0:
                raise ValueError
        except ValueError:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               '`seconds` must be a positive number'))
            return

        conn = sqlite3.connect(DATABASE_PATH)
        c = conn.cursor()
        max_start, max_end, cur_start, cur_end = (0, ) * 4
        max_tstart, max_tend, cur_tstart, cur_tend = (0, ) * 4
        try:
            user_data = c.execute(f"SELECT t FROM t_{player} ORDER BY t")
            user_data = user_data.fetchall()
            for i in range(1, len(user_data)):
                if user_data[i][0] - user_data[i - 1][0] > session_length:
                    if cur_end - cur_start > max_end - max_start:
                        max_start, max_end = cur_start, cur_end
                    cur_end += 1
                    cur_start = cur_end
                    if user_data[cur_tend][0] - user_data[cur_tstart][0] > \
                    user_data[max_tend][0] - user_data[max_tstart][0]:
                        max_tstart, max_tend = cur_tstart, cur_tend
                    cur_tend += 1
                    cur_tstart = cur_tend
                else:
                    cur_end += 1
                    cur_tend += 1
        except sqlite3.OperationalError:
            conn.close()
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).not_downloaded())
            return
        conn.close()

        embed = discord.Embed(
            title=
            f"Session Stats for {player} ({seconds_to_text(session_length, True)} interval)",
            color=discord.Color(MAIN_COLOR))
        embed.set_thumbnail(url=Urls().thumbnail(player))
        embed.add_field(
            name='Highest Race Session',
            value=
            (f"{f'{max_end - max_start + 1:,}'} races in "
             f"{seconds_to_text(user_data[max_end][0] - user_data[max_start][0])}"
             ))
        embed.add_field(
            name='Longest Session',
            value=
            (f"{f'{max_tend - max_tstart + 1:,}'} races in "
             f"{seconds_to_text(user_data[max_tend][0] - user_data[max_tstart][0])}"
             ))

        await ctx.send(embed=embed)
        return

    @commands.cooldown(10, 30, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('fastestcompletion'))
    async def fastestcompletion(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)

        if len(args) == 1: args = check_account(user_id)(args) + ('races', )
        elif len(args) == 2: args += ('races', )

        if len(args) != 3:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).parameters(
                    f"{ctx.invoked_with} [user] [num] <races/points>"))
            return

        player = get_player(user_id, args[0])
        if escape_sequence(player):
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    (f"[**{player}**]({Urls().user(player, 'play')}) "
                     "doesn't exist")))
            return
        try:
            num = float(args[1])
            if num <= 0 or num % 1 != 0:
                raise ValueError
            num = int(num)
        except ValueError:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               '`num` must be a positive number'))
            return

        category = args[2].lower()
        if not category in ['races', 'points']:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               '`category` must be `races/points`'))
            return

        conn = sqlite3.connect(DATABASE_PATH)
        c = conn.cursor()
        try:
            user_data = c.execute(
                f"SELECT * FROM t_{player} ORDER BY t").fetchall()
        except sqlite3.OperationalError:
            conn.close()
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).not_downloaded())
            return
        conn.close()

        try:
            if category == 'races':
                user_data[num - 1]
        except IndexError:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).incorrect_format(
                               "`num` must not exceed user's race count"))
            return

        length = len(user_data)
        if category == 'races':
            min_start, min_end, cur_start, = 0, num - 1, 0
            for i in range(num, length):
                cur_start += 1
                if user_data[i][1] - user_data[cur_start][1] <\
                user_data[min_end][1] - user_data[min_start][1]:
                    min_start, min_end = cur_start, i

        elif category == 'points':
            min_start, cur_start, cur_end, cur_points = (0, ) * 4
            min_end = length - 1
            exceeds_point_count = True
            for i in range(0, length):
                cur_points += user_data[i][4]
                if cur_points >= num:
                    exceeds_point_count = False
                    cur_end = i
                    while cur_points - user_data[cur_start][4] >= num:
                        cur_points -= user_data[cur_start][4]
                        cur_start += 1
                    if user_data[cur_end][1] - user_data[cur_start][1] <\
                    user_data[min_end][1] - user_data[min_start][1]:
                        min_start, min_end = cur_start, cur_end

            if exceeds_point_count:
                await ctx.send(content=f"<@{user_id}>",
                               embed=Error(ctx, ctx.message).incorrect_format(
                                   "`num` must not exceed user's point count"))
                return

        races, seconds_played, chars_typed, words_typed, points, wpm_sum, wpm_max = (
            0, ) * 7
        wpm_min = 100000
        tids = []
        text_data = load_texts_json()
        for i in range(min_start, min_end + 1):
            races += 1
            cur = user_data[i]
            wpm = cur[3]
            tid = str(cur[2])
            tids.append(tid)

            wpm_sum += wpm
            if wpm < wpm_min:
                wpm_min = wpm
            if wpm > wpm_max:
                wpm_max = wpm

            words = text_data[tid]['word count']
            chars = text_data[tid]['length']
            words_typed += words
            chars_typed += chars
            seconds_played += 12 * chars / wpm
            points += cur[4]
            if cur[4] == 0:
                points += wpm * words / 60

        f_category = {'races': 'Races', 'points': 'Points'}[category]

        embed = discord.Embed(
            title=
            f"{player}'s Fastest Time to Complete {f'{num:,}'} {f_category}",
            color=discord.Color(MAIN_COLOR),
            description=
            f"**Took:** {seconds_to_text(user_data[min_end][1] - user_data[min_start][1])}"
        )
        embed.set_thumbnail(url=Urls().thumbnail(player))
        embed.set_footer(
            text=
            f"First Race (#{f'{min_start + 1:,}'}): {datetime.datetime.fromtimestamp(user_data[min_start][1]).strftime('%B %-d, %Y, %-I:%M:%S %p')}"
        )
        embed.add_field(
            name='Races',
            value=
            (f"**Total Races:** {f'{min_end - min_start + 1:,}'}\n"
             f"**Total Words Typed:** {f'{words_typed:,}'}\n"
             f"**Average Words Per Races:** {f'{round(words_typed / races, 2):,}'}\n"
             f"**Total Chars Typed:** {f'{chars_typed:,}'}\n"
             f"**Average Chars Per Race:** {f'{round(chars_typed / races, 2):,}'}\n"
             f"**Total Time Spent Racing:** {seconds_to_text(seconds_played)}\n"
             f"**Total Time Elapsed:** {seconds_to_text(user_data[min_end][1] - user_data[min_start][1])}\n"
             f"**Average Time Per Race:** {seconds_to_text(seconds_played / races)}"
             ),
            inline=False)
        embed.add_field(
            name='Points',
            value=
            (f"**Total Points:** {f'{round(points):,}'}\n"
             f"**Average Points Per Race:** {f'{round(points / races, 2):,}'}\n"
             ),
            inline=False)
        embed.add_field(
            name='Speed',
            value=
            (f"**Average (Lagged):** {f'{round(wpm_sum / races, 2):,}'} WPM\n"
             f"**Fastest Race:** {f'{wpm_max:,}'} WPM\n"
             f"**Slowest Race:** {f'{wpm_min:,}'} WPM"),
            inline=False)
        embed.add_field(
            name='Quotes',
            value=f"**Number of Unique Quotes:** {f'{len(set(tids)):,}'}",
            inline=False)

        await ctx.send(embed=embed)
        return
class ManageModules(commands.Cog):
    def __init__(self, bot):
        self.bot = bot

    @commands.command()
    @commands.check(lambda ctx: ctx.message.author.id in BOT_OWNER_IDS and
                    check_banned_status(ctx))
    async def list_modules(self, ctx):
        modules = ''
        for filename in os.listdir('TypeRacerStats/Core'):
            if filename.endswith('.py') and filename != '__init__.py':
                modules += f"**{filename[:-3]}**\n"

        await ctx.send(embed=discord.Embed(color=discord.Color(HELP_BLACK),
                                           description=modules))
        return

    @commands.command()
    @commands.check(lambda ctx: ctx.message.author.id in BOT_OWNER_IDS and
                    check_banned_status(ctx))
    async def load(self, ctx, extension):
        try:
            self.bot.load_extension(f"Core.{extension}")
        except commands.errors.ExtensionNotFound:
            await ctx.send(embed=discord.Embed(
                color=discord.Color(HELP_BLACK),
                description=f"**{extension}** module not found."))
            return

        await ctx.send(
            embed=discord.Embed(color=discord.Color(HELP_BLACK),
                                description=f"**{extension}** module loaded."))

    @commands.command()
    @commands.check(lambda ctx: ctx.message.author.id in BOT_OWNER_IDS and
                    check_banned_status(ctx))
    async def unload(self, ctx, extension):
        if extension == 'manage_modules':
            await ctx.send(embed=discord.Embed(
                color=discord.Color(HELP_BLACK),
                description=f"**{extension}** module can not be unloaded."))
            return

        try:
            self.bot.unload_extension(f"Core.{extension}")
        except commands.errors.ExtensionNotFound:
            await ctx.send(embed=discord.Embed(
                color=discord.Color(HELP_BLACK),
                description=f"**{extension}** module was never loaded."))
            return

        await ctx.send(embed=discord.Embed(
            color=discord.Color(HELP_BLACK),
            description=f"**{extension}** module unloaded."))

    @commands.command()
    @commands.check(lambda ctx: ctx.message.author.id in BOT_OWNER_IDS and
                    check_banned_status(ctx))
    async def reload(self, ctx, extension):
        try:
            self.bot.unload_extension(f"Core.{extension}")
            self.bot.load_extension(f"Core.{extension}")
        except commands.errors.ExtensionNotFound:
            await ctx.send(embed=discord.Embed(
                color=discord.Color(HELP_BLACK),
                description=f"**{extension}** module not found."))
            return
        except commands.errors.ExtensionNotLoaded:
            await ctx.send(embed=discord.Embed(
                color=discord.Color(HELP_BLACK),
                description=f"**{extension}** module was never loaded."))
            return

        await ctx.send(embed=discord.Embed(
            color=discord.Color(HELP_BLACK),
            description=f"**{extension}** module reloaded."))

    @commands.command()
    @commands.check(lambda ctx: ctx.message.author.id in BOT_OWNER_IDS and
                    check_banned_status(ctx))
    async def droptable(self, ctx, *args):
        user_id = ctx.message.author.id

        if len(args) != 1:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(
                    ctx, ctx.message).parameters(f"{ctx.invoked_with} [user]"))
            return

        player = args[0].lower()
        if escape_sequence(player):
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information(
                    (f"[**{player}**]({Urls().user(player, 'play')}) "
                     "doesn't exist")))
            return

        await ctx.send(embed=discord.Embed(title='Type "YES" to confirm',
                                           color=discord.Color(0)),
                       delete_after=10)

        msg = await self.bot.wait_for('message',
                                      check=check(ctx.message.author),
                                      timeout=10)

        conn = sqlite3.connect(DATABASE_PATH)
        c = conn.cursor()

        try:
            c.execute(f"DROP TABLE t_{player}")
        except sqlite3.OperationalError:
            conn.close()
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).missing_information(
                               "The user's table does not exist"))
            return

        conn.close()

        await ctx.send(embed=discord.Embed(color=discord.Color(HELP_BLACK),
                                           title=f"{player} table dropped"))
Exemple #17
0
class RealSpeed(commands.Cog):
    def __init__(self, bot):
        self.bot = bot
        self.realspeed_cache = dict()
        self.clear_cache.start()

    @commands.cooldown(5, 10, commands.BucketType.user)
    @commands.cooldown(50, 100, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('realspeed') + ['lastrace'] +
                      get_aliases('lastrace') + ['raw'] + get_aliases('raw'))
    async def realspeed(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)
        account = account_information(user_id)
        desslejusted, universe = account['desslejusted'], account['universe']
        race_api_response = None
        replay_url = ''

        rs = ctx.invoked_with.lower() in ['realspeed'
                                          ] + get_aliases('realspeed')
        lr = ctx.invoked_with.lower() in ['lastrace'] + get_aliases('lastrace')
        raw = ctx.invoked_with.lower() in ['raw'] + get_aliases('raw')

        if len(args) == 0 or (len(args) == 1 and args[0][0] == '-'):
            args = check_account(user_id)(args)

        if len(args) > 2 or len(args) == 0:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).parameters(
                    f"{ctx.invoked_with} [user] [race_num]` or `{ctx.invoked_with} [url]"
                ))
            return

        race_num = 0
        if len(args) == 2 and args[1][0] == '-':
            try:
                race_num = int(args[1])
                args = (args[0], )
            except ValueError:
                pass

        players = []
        if len(args) == 1:
            try:
                args[0].index('result?')
                replay_url = args[0]
                urls = [replay_url]
            except ValueError:
                try:
                    player = get_player(user_id, args[0])
                    urls = [Urls().get_races(player, universe, 1)]
                    race_api_response = await fetch(urls, 'json')
                    last_race = race_api_response[0][0]['gn']
                    if race_num < 0:
                        last_race += race_num
                        race_api_response = None
                    else:
                        race_api_response = race_api_response[0][0]
                    replay_url = Urls().result(player, last_race, universe)
                    urls = [replay_url]
                except:
                    await ctx.send(
                        content=f"<@{user_id}>",
                        embed=Error(ctx, ctx.message).missing_information((
                            f"[**{player}**](https://data.typeracer.com/pit/race_history?user={player}&universe={universe}) "
                            "doesn't exist or has no races in the "
                            f"{href_universe(universe)} universe")))
                    return
        elif len(args) == 2:
            try:
                player = get_player(user_id, args[0])
                replay_url = Urls().result(player, int(args[1]), universe)
                urls = [replay_url]
            except ValueError:
                await ctx.send(content=f"<@{user_id}>",
                               embed=Error(ctx, ctx.message).incorrect_format(
                                   '`race_num` must be a positive integer'))
                return
        try:
            if raw:
                responses = await fetch(urls, 'text', raw_typinglog_scraper)
            else:
                if not lr: urls, responses = self.check_cache(urls)
                if urls:
                    responses = await fetch(urls, 'text', rs_typinglog_scraper,
                                            True)
                    self.update_cache(responses)
                    responses = [
                        list(response.values())[0] for response in responses
                    ]
            result = responses[0]
            if not result:
                raise KeyError
            if not race_api_response:
                timestamp = result['timestamp']
                player = result['player']
                universe = result['universe']
                race_api_response = await find_registered(
                    player, universe, result['race_number'], timestamp)
        except:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information((
                    '`var typingLog` was not found in the requested URL;\n'
                    f"Currently linked to the {href_universe(universe)} universe\n\n"
                )))
            return

        lagged = race_api_response['wpm']
        try:
            realspeeds = compute_realspeed(result['length'],
                                           result['duration'], result['start'],
                                           lagged, desslejusted, universe)
        except ZeroDivisionError:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).missing_information(
                               ('∞ adjusted WPM')))

        race_number, color = result['race_number'], MAIN_COLOR
        title = f"Real Speeds for {player}'s {num_to_text(race_number)} Race"
        description = f"**Universe:** {href_universe(universe)}\n"

        if rs or raw:
            start, unlagged, adjusted, ping, desslejusted_wpm = tuple(
                realspeeds.values())
            if ping <= 0:
                color = 0xe0001a
                description += f"{TR_WARNING} This score is reverse lagged {TR_WARNING}"
            if raw:
                start, unlagged, adjusted, ping, desslejusted_wpm = tuple(
                    realspeeds.values())
                correction, adj_correction, length = result[
                    'correction'], result['adj_correction'], result['duration']
                raw_unlagged = (length * unlagged) / (length - correction)
                raw_adjusted = ((length - start) *
                                adjusted) / (length - start - adj_correction)
        elif lr:
            players.append([player, urls[0]] + list((realspeeds.values())))
            for opponent in result['opponents']:
                urls = ["https://data.typeracer.com/pit/" + opponent]
                opponent_data = await fetch(urls, 'text', rs_typinglog_scraper)
                result_ = opponent_data[0]
                timestamp_ = result_['timestamp']
                player_ = result_['player']
                opponent_api_response = await find_registered(
                    player_, universe, result_['race_number'], timestamp_)
                lagged_ = opponent_api_response['wpm']
                try:
                    realspeeds = compute_realspeed(result_['length'],
                                                   result_['duration'],
                                                   result_['start'], lagged_,
                                                   False, universe)
                    players.append([player_, urls[0]] +
                                   list((realspeeds.values())))
                except ZeroDivisionError:
                    pass

        embed = discord.Embed(title=title,
                              colour=discord.Colour(color),
                              url=replay_url,
                              description=description)
        embed.set_thumbnail(
            url=f"https://data.typeracer.com/misc/pic?uid=tr:{player}")
        embed.set_footer(
            text=
            "Adjusted speed is calculated by removing the start time from the race"
        )
        value = f"\"{result['race_text']}\""

        if len(value) > 1023:
            value = value[0:1020] + "…\""
        embed.add_field(
            name=f"Quote (Race Text ID: {race_api_response['tid']})",
            value=value,
            inline=False)

        cache_id(ctx.message.channel.id, race_api_response['tid'])

        if rs or raw:
            real_speeds = (f"**Lagged:** {f'{lagged:,}'} WPM "
                           f"({f'{round(unlagged - lagged, 2):,}'} WPM lag)\n"
                           f"**Unlagged:** {f'{unlagged:,}'} WPM"
                           f" ({f'{round(ping):,}'}ms ping)\n"
                           f"**Adjusted:** {f'{adjusted:,}'} WPM"
                           f" ({f'{start:,}'}ms start)")
            if desslejusted:
                real_speeds += f"\n**Desslejusted:** {f'{desslejusted_wpm:,}'} WPM"
            if raw:
                real_speeds += (
                    f"\n**Raw Unlagged:** {f'{round(raw_unlagged, 2):,}'} WPM "
                    f"({f'{correction:,}'}ms correction time, {round(100 * correction / length, 2)}%)"
                    f"\n**Raw Adjusted:** {f'{round(raw_adjusted, 3):,}'} WPM")
            embed.add_field(name="Speeds", value=real_speeds, inline=False)
        elif lr:
            value = ''
            players = sorted(players, key=lambda x: x[3], reverse=True)
            for i, player in enumerate(players):
                segment = (f"{NUMBERS[i]} "
                           f"[{player[0]}]({player[1]}) - "
                           f"{player[3]} unlagged WPM / "
                           f"{player[4]} adjusted WPM\n")
                if len(value + segment) > 1024: break
                value += segment
            value = value[:-1]
            embed.add_field(name='Ranks (ranked by unlagged WPM)',
                            value=value,
                            inline=False)

        await ctx.send(embed=embed)
        return

    @commands.cooldown(2, 60, commands.BucketType.user)
    @commands.cooldown(10, 600, commands.BucketType.default)
    @commands.check(
        lambda ctx: check_dm_perms(ctx, 4) and check_banned_status(ctx))
    @commands.command(aliases=get_aliases('realspeedaverage'))
    async def realspeedaverage(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)
        account = account_information(user_id)
        desslejusted, universe = account['desslejusted'], account['universe']
        race_api_response = None
        replay_url = ''
        redact = not ctx.invoked_with[-1] == '*'
        rawsa = 'raw' in ctx.invoked_with

        if len(args) == 0 or (len(args) == 1 and len(args[0]) < 4):
            args = check_account(user_id)(args)

        if len(args) > 3 or len(args) == 0:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).parameters(
                    f"{ctx.invoked_with} [user] <first_race> <last_race>"))
            return

        try:
            player = get_player(user_id, args[0])
            urls = [Urls().get_races(player, universe, 1)]
            race_api_response = (await fetch(urls, 'json'))[0][0]
            last_race = int(race_api_response['gn'])
        except:
            await ctx.send(content = f"<@{user_id}>",
                           embed = Error(ctx, ctx.message)
                                   .missing_information((f"[**{player}**](https://data.typeracer.com/pit/race_history?user={player}&universe={universe}) "
                                                         "doesn't exist or has no races in the " \
                                                         f"{href_universe(universe)} universe")))
            return

        invalid = False
        if len(args) == 1:
            if last_race < 10:
                first_race = 1
            else:
                first_race = last_race - 9
        elif len(args) == 2:
            try:
                num = int(args[1])
                first_race = last_race - num + 1
            except ValueError:
                invalid = True
        elif len(args) == 3:
            try:
                first_race, last_race = int(args[1]), int(args[2])
            except ValueError:
                invalid = True
        race_interval = last_race - first_race
        if first_race <= 0 or last_race <= 0 or race_interval <= 0:
            invalid = True
        if invalid:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).incorrect_format(
                    'The number of races must be a positive integer'))
            return
        if race_interval >= 10 and not user_id in BOT_ADMIN_IDS:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).lacking_permissions(
                               'You may only request up to 10 races'))
            return
        elif race_interval >= 10 and not user_id in BOT_OWNER_IDS:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).lacking_permissions(
                               'You may only request up to 10 races'))
            return
        elif race_interval >= 500:
            await ctx.send(content=f"<@{user_id}>",
                           embed=Error(ctx, ctx.message).lacking_permissions(
                               'You may only request up to 500 races'))
            return

        urls = []
        for i in range(first_race, last_race + 1):
            replay_url = Urls().result(player, i, universe)
            urls.append(replay_url)

        responses = []
        urls, responses = self.check_cache(urls, True)
        if urls:
            new_responses = await fetch(urls, 'text', raw_typinglog_scraper,
                                        True)
            self.update_cache(new_responses)
            new_responses = [
                list(response.values())[0] for response in new_responses
            ]
            responses += new_responses
        responses = [i for i in responses if i]
        if len(responses) == 0:
            await ctx.send(
                content=f"<@{user_id}>",
                embed=Error(ctx, ctx.message).missing_information((
                    '`var typingLog` was not found in any of the races;\n'
                    f"Currently linked to the {href_universe(universe)} universe\n\n"
                )))
            return

        responses = sorted(responses,
                           key=lambda x: int(x['race_number']),
                           reverse=True)

        urls = [
            Urls().get_races(player, universe, responses[-1]['timestamp'] - 1,
                             responses[0]['timestamp'] + 1)
        ]
        race_api_responses = (await fetch(urls, 'json'))[0]
        race_api_responses = sorted(race_api_responses,
                                    key=lambda x: x['gn'],
                                    reverse=True)
        computed_responses = []
        j = 0
        lagged_sum, unlagged_sum, adjusted_sum, ping_sum = (0, ) * 4
        start_sum, desslejusted_sum, lag_sum, reverse_lag_count = (0, ) * 4
        raw_unlagged_sum, raw_adjusted_sum, correction_time_sum, correction_percentage_sum = (
            0, ) * 4
        for i in range(0, len(race_api_responses)):
            response, race_api_response = responses[j], race_api_responses[i]
            if response['race_number'] == race_api_response['gn']:
                j += 1
                try:
                    realspeeds = compute_realspeed(response['length'],
                                                   response['duration'],
                                                   response['start'],
                                                   race_api_response['wpm'],
                                                   desslejusted, universe)
                    computed_responses.append({
                        'url':
                        Urls().result(player, response['race_number'],
                                      universe),
                        'race_number':
                        response['race_number'],
                        'lagged':
                        race_api_response['wpm'],
                        'unlagged':
                        realspeeds['unlagged'],
                        'adjusted':
                        realspeeds['adjusted'],
                        'ping':
                        realspeeds['ping'],
                        'start':
                        realspeeds['start'],
                        'desslejusted':
                        realspeeds['desslejusted']
                    })
                    lagged_sum += race_api_response['wpm']
                    unlagged_sum += realspeeds['unlagged']
                    adjusted_sum += realspeeds['adjusted']
                    ping_sum += realspeeds['ping']
                    start_sum += realspeeds['start']
                    lag_sum += realspeeds['unlagged'] - race_api_response['wpm']
                    if realspeeds['ping'] <= 0:
                        reverse_lag_count += 1
                    if desslejusted:
                        desslejusted_sum += realspeeds['desslejusted']
                    if rawsa:
                        correction, adj_correction, length = response[
                            'correction'], response[
                                'adj_correction'], response['duration']
                        raw_unlagged = (length * realspeeds['unlagged']) / (
                            length - correction)
                        raw_adjusted = (
                            (length - realspeeds['start']) *
                            realspeeds['adjusted']) / (
                                length - realspeeds['start'] - adj_correction)
                        computed_responses[-1].update({
                            'raw_unlagged':
                            raw_unlagged,
                            'raw_adjusted':
                            raw_adjusted,
                            'correction_time':
                            correction,
                            'correction_percentage':
                            round(100 * correction / length, 2)
                        })
                        raw_unlagged_sum += raw_unlagged
                        raw_adjusted_sum += raw_adjusted
                        correction_time_sum += correction
                        correction_percentage_sum += 100 * correction / length

                except ZeroDivisionError:
                    continue
            else:
                continue

        description = f"**Universe:** {href_universe(universe)}\n\n"
        if reverse_lag_count:
            color = 0xe0001a
            description += (
                f"{TR_WARNING} This interval contains "
                f"{reverse_lag_count} reverse lagged score(s) {TR_WARNING}\n")
        else:
            color = MAIN_COLOR

        race_count = len(computed_responses)

        title = f"""Real Speed Average for {player} (Races {f"{responses[-1]['race_number']:,}"} to {f"{responses[0]['race_number']:,}"})"""
        real_speeds = f"**Lagged Average:** {f'{round(lagged_sum / race_count, 2):,}'} WPM\n"
        delays = (
            f"**Average Lag:** {f'{round(lag_sum / race_count, 2):,}'} WPM\n"
            f"**Average Ping:** {f'{round(ping_sum / race_count, 3):,}'}ms\n"
            f"**Average Start:** {f'{round(start_sum / race_count, 3):,}'}ms")
        real_speeds += f"**Unlagged Average:** {f'{round(unlagged_sum / race_count, 2):,}'} WPM\n"
        real_speeds += f"**Adjusted Average:** {f'{round(adjusted_sum / race_count, 3):,}'} WPM"
        if desslejusted:
            real_speeds += f"\n**Desslejusted Average:** {f'{round(desslejusted_sum / race_count, 3):,}'} WPM"
        if rawsa:
            real_speeds += f"\n**Raw Unlagged Average:** {f'{round(raw_unlagged_sum / race_count, 3):,}'} WPM"
            real_speeds += f"\n**Raw Adjusted Average:** {f'{round(raw_adjusted_sum / race_count, 3):,}'} WPM"
            delays += f"\n**Correction Time:** {f'{round(correction_time_sum / race_count):,}'}ms"
            delays += f" ({round(correction_percentage_sum  / race_count, 2)}%)"

        if race_count >= 20 or redact:
            delays = f"\n{delays}"
            if not redact:
                csv_data = [list(computed_responses[0].keys())[1:]]
                csv_data += [
                    list(computed_response.values())[1:]
                    for computed_response in computed_responses
                ]
                filename = f"{player}_real_speed_average_{responses[-1]['race_number']}_to_{responses[0]['race_number']}.csv"
                with open(filename, 'w') as csvfile:
                    writer = csv.writer(csvfile)
                    writer.writerows(csv_data)
                file_ = discord.File(filename, filename)
            if description:
                embed = discord.Embed(title=title,
                                      color=discord.Color(color),
                                      description=description)
            else:
                embed = discord.Embed(title=title, color=discord.Color(color))
            embed.set_thumbnail(
                url=f"https://data.typeracer.com/misc/pic?uid=tr:{player}")
            embed.set_footer(
                text=
                "(Adjusted speed is calculated by removing the start time from the race)"
            )
            embed.add_field(name="Speed", value=real_speeds, inline=False)
            embed.add_field(name="Delays", value=delays, inline=False)
            if not redact:
                await ctx.send(file=file_,
                               content=f"<@{user_id}>",
                               embed=embed)
                os.remove(filename)
            else:
                await ctx.send(content=f"<@{user_id}>", embed=embed)
            return

        embed = discord.Embed(
            title=title,
            color=discord.Colour(color),
            description=
            f"{description}**Speed**\n{real_speeds}\n**Delays**\n{delays}")
        for computed_response in computed_responses:
            name = f"""Real Speeds for Race #{f"{computed_response['race_number']:,}"}"""
            if computed_response['ping'] < 0:
                name += f" {TR_WARNING} Reverse Lagged {TR_WARNING}"
            value = (
                f"""**Lagged Speed:** {f"{round(computed_response['lagged'], 2)}"} WPM """
                f"""({f"{round(computed_response['unlagged'] - computed_response['lagged'], 2):,}"} WPM lag) """
                f"""[:cinema:]({computed_response['url']})\n"""
                f"""**Unlagged Speed:** {f"{round(computed_response['unlagged'], 2):,}"} WPM """
                f"""({f"{round(computed_response['ping']):,}"}ms ping)\n"""
                f"""**Adjusted Speed:** {f"{round(computed_response['adjusted'], 3):,}"} WPM """
                f"""({f"{round(computed_response['start'], 3):,}"}ms start)""")
            if desslejusted:
                value += f"""\n**Desslejusted Speed:** {f"{round(computed_response['desslejusted'], 3):,}"} WPM"""
            embed.add_field(name=name, value=value, inline=False)
        await ctx.send(embed=embed)
        return

    def update_cache(self, responses):
        for response in responses:
            if response:
                self.realspeed_cache.update(response)

    def check_cache(self, urls, *raw):
        results = []
        uncached_urls = []

        for url in urls:
            try:
                if raw and raw[0]:
                    self.realspeed_cache[url]['correction']
                results.append(self.realspeed_cache[url])
            except KeyError:
                uncached_urls.append(url)

        return uncached_urls, results

    @tasks.loop(hours=48)
    async def clear_cache(self):
        self.realspeed_cache = dict()