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
def get_graph_colors(id_): graph_colors = dict() try: supporter = load_supporters()[str(id_)] if int(supporter['tier']) >= 3: graph_colors = supporter['graph_color'] else: raise KeyError except KeyError: graph_colors = { 'bg': 0xDAD3C1, 'graph_bg': 0xDAD3C1, 'axis': 0xABA495, 'line': 0xAD4F4E, 'text': 0xAD4F4E, 'grid': 0xABA495, 'cmap': None } account = check_account(id_)(()) if account: graph_colors.update({'user': account[0]}) else: graph_colors.update({'user': '******'}) return graph_colors
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
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
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
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
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
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 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
async def compare(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) != 2: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).parameters( f"{ctx.invoked_with} [user_1] [user_2]")) return player = get_player(user_id, args[0]) player_ = get_player(user_id, args[1]) 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 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 tid, MAX(wpm) FROM t_{player} GROUP BY tid ORDER BY wpm" ).fetchall() player_data_ = c.execute( f"SELECT 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() player_dict = dict() for text in player_data: player_dict.update({text[0]: text[1]}) first_player, second_player = [], [] for text in player_data_: if player_dict.get(text[0], -1) > 0: difference = text[1] - player_dict[text[0]] if difference == 0: pass elif difference < 0: first_player.append(-1 * difference) else: second_player.append(difference) if len(first_player) + len(second_player) == 0: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information( f"**{player}** and **{player_}** have no texts in common")) return fig, (ax, ax_) = plt.subplots(1, 2, sharey=True) if first_player: first_max = max(first_player) else: first_max = 0 if second_player: second_max = max(second_player) else: second_max = 0 if int(first_max) // 10 == 0: patches = ax.hist(first_player, bins=1, orientation='horizontal')[2] else: patches = ax.hist(first_player, bins=int(first_max) // 10, orientation='horizontal')[2] if int(second_max) // 10 == 0: patches_ = ax_.hist(second_player, bins=1, orientation='horizontal')[2] else: patches_ = ax_.hist(second_player, bins=int(second_max) // 10, orientation='horizontal')[2] ax.yaxis.tick_left() ax.set_ylim(ax.get_ylim()[::-1]) ax.set_ylabel('Difference (WPM)') ax.grid() ax.set_title(player) ax_.grid() ax_.set_title(player_) max_xlim = max(ax.get_xlim()[1], ax_.get_xlim()[1]) ax.set_xlim(0, max_xlim) ax.set_xlim(ax.get_xlim()[::-1]) ax_.set_xlim(0, max_xlim) title = f"{player} vs. {player_} Text Bests Comparison" plt.subplots_adjust(wspace=0, hspace=0) file_name = f"{player}_{player_}_text_bests_comparison.png" graph_colors = get_graph_colors(user_id) graph_color(ax, graph_colors, False, patches) graph_color(ax_, graph_colors, False, patches) to_rgba = lambda x: (x // 65536 / 255, ((x % 65536) // 256) / 255, x % 256 / 255) if graph_colors['text']: fig.suptitle(title, color=to_rgba(graph_colors['text'])) fig.text(0.5, 0.025, 'Frequency (Texts)', ha='center', color=to_rgba(graph_colors['text'])) else: fig.suptitle(title) fig.text(0.5, 0.025, 'Frequency (Texts)', ha='center') plt.savefig(file_name, facecolor=ax.figure.get_facecolor()) plt.close() embed = discord.Embed(title=title, color=MAIN_COLOR) file_ = discord.File(file_name, filename=file_name) embed.set_image(url=f"attachment://{file_name}") embed.add_field( name=player, value= f"**{f'{len(first_player):,}'}** texts (+**{f'{round(sum(first_player), 2):,}'}** WPM)" ) embed.add_field( name=player_, value= f"**{f'{len(second_player):,}'}** texts (+**{f'{round(sum(second_player), 2):,}'}** WPM)" ) await ctx.send(file=file_, embed=embed) os.remove(file_name) 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
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
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
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
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
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
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
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
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
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
async def textbestsgraph(self, ctx, *args): user_id = ctx.message.author.id if len(args) <= 1: 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/texts]")) return player = get_player(user_id, args[0]) if args[1].lower() not in {'time', 'races', 'texts'}: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( 'Must provide a valid category: `time/races/texts`')) return if args[1].lower() == 'time': q_category = 't' category = 'Time' elif args[1].lower() == 'races': q_category = 'gn' category = 'Races' elif args[1].lower() == 'texts': q_category = None category = 'Texts' 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 data_x, data_y = [], [] text_ids = {} wpm_sum, wpm_count = 0, 0 conn = sqlite3.connect(DATABASE_PATH) c = conn.cursor() try: user_data = c.execute(f"SELECT gn, t, tid, wpm FROM t_{player}") for race_data in user_data: if text_ids.get(race_data[2], False): if race_data[3] > text_ids[race_data[2]]: if not q_category: data_x.append(len(data_x)) elif q_category == 'gn': data_x.append(race_data[0]) else: data_x.append( datetime.datetime.fromtimestamp(race_data[1])) wpm_sum += race_data[3] - text_ids[race_data[2]] text_ids.update({race_data[2]: race_data[3]}) data_y.append(wpm_sum / wpm_count) else: if not q_category: data_x.append(len(data_x)) elif q_category == 'gn': data_x.append(race_data[0]) else: data_x.append( datetime.datetime.fromtimestamp(race_data[1])) text_ids.update({race_data[2]: race_data[3]}) wpm_sum += race_data[3] wpm_count += 1 data_y.append(wpm_sum / wpm_count) except sqlite3.OperationalError: conn.close() await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).not_downloaded()) return conn.close() data_x = reduce_list(data_x) data_y = reduce_list(data_y) ax = plt.subplots()[1] ax.plot(data_x, data_y) ax.set_title(f"{player}'s Text Bests Average Over {category}") if not q_category: ax.set_xlabel('Text Changes (Additions/Improvements)') elif q_category == 't': ax.set_xlabel('Date') ax.set_xticks(ax.get_xticks()[::2]) formatter = mdates.DateFormatter("%b. %-d '%y") ax.xaxis.set_major_formatter(formatter) else: ax.set_xlabel('Race #') ax.set_ylabel('WPM') plt.grid(True) file_name = f"Text Bests Average Over {category}.png" graph_colors = get_graph_colors(user_id) graph_color(ax, graph_colors, False) plt.savefig(file_name, facecolor=ax.figure.get_facecolor()) races_over_time_picture = discord.File(file_name, filename=file_name) await ctx.send(file=races_over_time_picture) os.remove(file_name) plt.close() return
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']: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( 'Must provide a valid category: `time/races`')) return if args[1].lower() == 'time': q_category = 't' category = 'Time' else: q_category = 'gn' category = 'Races' 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 data_x, data_y, max_point = [], [], 0 conn = sqlite3.connect(DATABASE_PATH) c = conn.cursor() try: player_data = c.execute( f"SELECT wpm, {q_category} FROM t_{player} ORDER by {q_category}" ) for row in player_data: if q_category == 't': data_x.append(datetime.datetime.fromtimestamp(row[1])) else: data_x.append(row[1]) row_wpm = row[0] max_point = max(max_point, row_wpm) data_y.append(row_wpm) except sqlite3.OperationalError: conn.close() await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).not_downloaded()) return conn.close() length = len(data_x) if length < 15: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information( f"`{ctx.invoked_with}` requires at least 15 races to generate a graph" )) return elif length < 7500: sma = length // 15 else: sma = 500 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) max_line = max(moving_y) ax = plt.subplots()[1] ax.scatter(data_x, data_y, marker='.', alpha=0.1, color='#000000') ax.plot(moving_x, moving_y, color='#FF0000') ax.set_title( f"{player}'s WPM Over {category}\n(Moving Average of {sma} Races)") if q_category == 't': ax.set_xlabel('Date') ax.set_xticks(ax.get_xticks()[::2]) formatter = mdates.DateFormatter("%b. %-d '%y") ax.xaxis.set_major_formatter(formatter) else: ax.xaxis.set_major_formatter( ticker.FuncFormatter(self.large_num_formatter)) ax.set_xlabel('Race #') if max_point > 2 * max_line: ax.set_ylim(0, 2 * max_line) ax.set_ylabel('WPM') plt.grid(True) file_name = f"WPM Over {category}.png" graph_colors = get_graph_colors(user_id) graph_color(ax, graph_colors, False) plt.savefig(file_name, facecolor=ax.figure.get_facecolor()) races_over_time_picture = discord.File(file_name, filename=file_name) await ctx.send(file=races_over_time_picture) os.remove(file_name) plt.close() 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 pbgraph(self, ctx, *args): user_id = ctx.message.author.id if len(args) <= 1: 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'}: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( 'Must provide a valid category: `time/races`')) return if args[1].lower() == 'time': q_category = 't' category = 'Time' else: q_category = 'gn' category = 'Races' 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 data_x, data_y = [], [] conn = sqlite3.connect(DATABASE_PATH) c = conn.cursor() try: player_data = c.execute( f"SELECT {q_category}, wpm FROM t_{player}") temp_x, temp_y = player_data.fetchone() if q_category == 't': data_x.append(datetime.datetime.fromtimestamp(temp_x)) else: data_x.append(temp_x) data_y.append(temp_y) for row in player_data: if data_y[-1] > row[1]: continue if q_category == 't': data_x.append(datetime.datetime.fromtimestamp(row[0])) else: data_x.append(row[0]) data_y.append(row[1]) except sqlite3.OperationalError: conn.close() await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).not_downloaded()) return conn.close() data_x = reduce_list(data_x) data_y = reduce_list(data_y) ax = plt.subplots()[1] ax.plot(data_x, data_y) ax.set_title(f"{player}'s PBs Over {category}") if q_category == 't': ax.set_xlabel('Date') ax.set_xticks(ax.get_xticks()[::2]) formatter = mdates.DateFormatter("%b. %-d '%y") ax.xaxis.set_major_formatter(formatter) else: ax.set_xlabel('Race #') ax.set_ylabel('WPM') plt.grid(True) file_name = f"PBs Over {category}.png" graph_colors = get_graph_colors(user_id) graph_color(ax, graph_colors, False) plt.savefig(file_name, facecolor=ax.figure.get_facecolor()) races_over_time_picture = discord.File(file_name, filename=file_name) await ctx.send(file=races_over_time_picture) os.remove(file_name) plt.close() return
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
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 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
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
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