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
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']:
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
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
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
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 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
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
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
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
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
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
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
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
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
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
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
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
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
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()