示例#1
0
    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
示例#2
0
                      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])
        if args[1].lower() not in ['time', 'races']:
示例#3
0
    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
示例#4
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
示例#5
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
示例#6
0
    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
示例#7
0
    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
示例#8
0
    async def adjustedgraph(self, ctx, *args):
        user_id = ctx.message.author.id
        MAIN_COLOR = get_supporter(user_id)
        account = account_information(user_id)
        universe = account['universe']

        ag = ctx.invoked_with.lower() in ['adjustedgraph'
                                          ] + get_aliases('adjustedgraph')
        mg = ctx.invoked_with.lower() in ['matchgraph'
                                          ] + get_aliases('matchgraph')

        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

        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 = 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

        def helper_scraper(soup):
            escapes = ''.join([chr(char) for char in range(1, 32)])
            try:
                typinglog = re.sub(
                    '\\t\d', 'a',
                    re.search(
                        r'typingLog\s=\s"(.*?)";',
                        response).group(1).encode().decode(
                            'unicode-escape').translate(escapes)).split('|')
                return [int(c) for c in re.findall(r"\d+", typinglog[0])][2:]
            except:
                return None

        try:
            response = (await fetch(urls, 'text'))[0]
            if not response:
                raise KeyError
            soup = BeautifulSoup(response, 'html.parser')
            times = helper_scraper(soup)

            race_text = soup.select("div[class='fullTextStr']")[0].text.strip()
            player = soup.select(
                "a[class='userProfileTextLink']")[0]["href"][13:]
            race_details = soup.select("table[class='raceDetails']")[0].select(
                'tr')
            universe = 'play'
            opponents = []
            for detail in race_details:
                cells = detail.select('td')
                category = cells[0].text.strip()
                if category == 'Race Number':
                    race_number = int(cells[1].text.strip())
                elif category == 'Universe':
                    universe = cells[1].text.strip()
                elif category == 'Opponents':
                    opponents = [i['href'] for i in cells[1].select('a')]
        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

        if universe == 'lang_ko':
            mult = 24000
        elif universe == 'lang_zh' or universe == 'new_lang_zh-tw' or universe == 'lang_zh-tw' or universe == 'lang_ja':
            mult = 60000
        else:
            mult = 12000

        def wpm_helper(times):
            temp, total_time = [], 0
            for i, time_ in enumerate(times):
                total_time += time_
                try:
                    temp.append((i + 1) * mult / total_time)
                except ZeroDivisionError:
                    pass
            return temp

        if ag:
            times.pop(0)
            data_y = wpm_helper(times)
        else:
            unl = wpm_helper(times)
            data = {
                player: [
                    unl, unl[-1], times[0],
                    replay_url.split('https://data.typeracer.com/pit/')[1]
                ]
            }
            for opponent in opponents:
                try:
                    urls = ["https://data.typeracer.com/pit/" + opponent]
                    response = (await fetch(urls, 'text'))[0]
                    if not response:
                        raise KeyError
                    soup = BeautifulSoup(response, 'html.parser')
                    times = helper_scraper(soup)
                    unl = wpm_helper(times)
                    data.update({
                        opponent.split('|')[1][3:]:
                        [unl, unl[-1], times[0], opponent]
                    })
                except:
                    pass
            data = {
                k: v
                for k, v in sorted(
                    data.items(), key=lambda x: x[1][1], reverse=True)
            }

        if ag:
            title_1 = f"Adjusted WPM Over {player}'s {num_to_text(race_number)} Race"
            title = f"{title_1}\nUniverse: {universe}"
        else:
            title_1 = f"Unlagged WPM Over {player}'s {num_to_text(race_number)} Race"
            title = f"{title_1}\nUniverse: {universe}"

        description = f"**Quote**\n\"{race_text[0:1008]}\""

        text_length = len(race_text) > 9

        ax = plt.subplots()[1]
        if ag:
            if text_length:
                starts = data_y[0:9]
                remaining = data_y[9:]
            ax.plot([i for i in range(1, len(data_y) + 1)], data_y)
        else:
            value, i, starts, remaining = '', 0, [], []
            for name, data_y in data.items():
                wpm_ = data_y[0]
                if text_length:
                    starts += wpm_[0:9]
                    remaining += wpm_[9:]
                ax.plot([i for i in range(1, len(wpm_) + 1)], wpm_, label=name)
                segment = (
                    f"{NUMBERS[i]} [{name}]({f'https://data.typeracer.com/pit/{data_y[3]}'})"
                    f" - {round(data_y[1], 2)} WPM ({f'{data_y[2]:,}'}ms start)\n"
                )
                if len(value + segment) <= 1024:
                    value += segment
                i += 1
            if len(data) > 1:
                plt.tight_layout(rect=[0.02, 0.02, 0.75, 0.92])
                ax.legend(loc='upper left',
                          bbox_to_anchor=(1.03, 1),
                          shadow=True,
                          ncol=1)

        ax.set_title(title)
        ax.set_xlabel('Keystrokes')
        ax.set_ylabel('WPM')
        plt.grid(True)
        file_name = 'WPM Over Race.png'

        embed = discord.Embed(title=title_1,
                              color=discord.Color(MAIN_COLOR),
                              description=description,
                              url=replay_url)
        if text_length:
            max_starts, max_remaining = max(starts), max(remaining)
            messed_up_scaled = max_starts > max_remaining
            if messed_up_scaled:
                if ctx.invoked_with[-1] != '*':
                    ax.set_ylim(0, 1.05 * max_remaining)
                    embed.set_footer(
                        text=
                        f"The `y`-axis has been scaled; run `{ctx.invoked_with}*` to see the entire graph"
                    )

        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')
        if mg:
            embed.add_field(name='Ranks (ranked by unlagged WPM)',
                            value=value[:-1])
        os.remove(file_name)

        await ctx.send(file=file_, embed=embed)
        return
示例#9
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
示例#10
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
示例#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
示例#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
示例#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
示例#14
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
示例#15
0
    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
示例#16
0
    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
示例#17
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
示例#18
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
示例#19
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
示例#20
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()