async def clip(self, ctx, *args): user_id = ctx.message.author.id MAIN_COLOR = get_supporter(user_id) if len(args) != 1: await ctx.send( content=f"<@{user_id}>", embed=Error( ctx, ctx.message).parameters(f"{ctx.invoked_with} [clip]")) return with open(CLIPS_JSON, 'r') as jsonfile: clips = json.load(jsonfile) if len(args) == 1: clip = args[0].lower() if clip == '*': await ctx.send(file=discord.File(CLIPS_JSON, f"clips.json")) return try: clip_url = clips[clip] except KeyError: calls = list(clips.keys()) calls_ = '' for clip_ in calls: calls_ += f"`{clip_}`, " await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( f"Must provide a valid clip: {calls_[:-2]}")) return await ctx.send(clip_url) return
async def toggledessle(self, ctx, *args): user_id = str(ctx.message.author.id) MAIN_COLOR = get_supporter(user_id) invalid = False if len(args) != 0: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).parameters(f"{ctx.invoked_with}")) return accounts = load_accounts() try: cur = accounts[user_id]['desslejusted'] accounts[user_id]['desslejusted'] = not cur except KeyError: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information(( 'Discord account must be linked to TypeRacer account with ' f"`{get_prefix(ctx, ctx.message)}register [typeracer_username]`" ))) return update_accounts(accounts) await ctx.send(embed=discord.Embed( color=discord.Color(MAIN_COLOR), description=( f"<@{user_id}> has been set to `desslejusted` **{not cur}**"))) return
async def serverinfo(self, ctx, *args): user_id = ctx.message.author.id MAIN_COLOR = get_supporter(user_id) if len(args) != 0: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).parameters(f"{ctx.invoked_with}")) return embed = discord.Embed(title=f"Server Information for {ctx.guild.name}", color=discord.Color(MAIN_COLOR), description=ctx.guild.description) embed.set_thumbnail(url=ctx.guild.icon_url) embed.add_field( name='Stats', value=( f"**Owner:** <@{ctx.guild.owner_id}>\n" f"**Region:** {ctx.guild.region}\n" f"**Created At:** {ctx.guild.created_at}\n" f"**Member Count:** {f'{ctx.guild.member_count:,}'}\n" f"**Text Channels:** {f'{len(ctx.guild.text_channels):,}'}\n" f"**Roles:** {f'{len(ctx.guild.roles):,}'}")) embed.set_image(url=ctx.guild.banner_url) await ctx.send(embed=embed) return
async def calc(self, ctx, *args): user_id = ctx.message.author.id MAIN_COLOR = get_supporter(user_id) if len(args) == 0: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).parameters( f"{ctx.invoked_with} [expression]")) return expression = urllib.parse.quote_plus(''.join(args)) urls = [Urls().eval_math(expression)] try: response = await fetch(urls, 'json') except: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( f"Please provide a valid expression")) return embed = discord.Embed(title=f"`{''.join(args)}` =", color=discord.Color(MAIN_COLOR), description=f"```{response[0]}```") await ctx.send(embed=embed) return
async def art(self, ctx, *args): user_id = ctx.message.author.id MAIN_COLOR = get_supporter(user_id) if len(args) > 1: await ctx.send( content=f"<@{user_id}>", embed=Error( ctx, ctx.message).parameters(f"{ctx.invoked_with} <artist>")) return with open(ART_JSON, 'r') as jsonfile: works = json.load(jsonfile) artists = list(works.keys()) if len(args) == 1: artist = args[0].lower() if artist == '*': await ctx.send( file=discord.File(ART_JSON, f"typeracer_art.json")) return if artist not in artists: artists_ = '' for artist_ in artists: artists_ += f"`{artist_}`, " await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( f"Must provide a valid artist: {artists_[:-2]}")) return works, trid = works[artist]['art'], works[artist]['trid'] work = random.choice(works) else: works_ = [] for key, value in works.items(): for art_work in value['art']: works_.append({ 'artist': key, 'trid': value['trid'], 'title': art_work['title'], 'url': art_work['url'] }) work = random.choice(works_) artist, trid = work['artist'], work['trid'] title = work['title'] if work['title'] else "Untitled" embed = discord.Embed(title=title, color=discord.Color(MAIN_COLOR)) embed.set_author(name=artist, url=Urls().user(trid, 'play'), icon_url=Urls().thumbnail(trid)) embed.set_image(url=work['url']) await ctx.send(embed=embed) return
async def unixreference(self, ctx, *args): user_id = ctx.message.author.id MAIN_COLOR = get_supporter(user_id) if len(args) > 1: await ctx.send( content=f"<@{user_id}>", embed=Error( ctx, ctx.message).parameters(f"{ctx.invoked_with} <timestamp>")) return if len(args) == 0: embed = discord.Embed(title="Unix Timestamp Conversions", color=discord.Color(MAIN_COLOR)) embed.set_footer(text="All converted times are in UTC") embed.add_field(name="Times", value=('1.25e9 - August 11, 2009\n' '1.30e9 - March 13, 2011\n' '1.35e9 - October 12, 2012\n' '1.40e9 - May 13, 2014\n' '1.45e9 - December 13, 2015\n' '1.50e9 - July 14, 2017\n' '1.55e9 - February 12, 2019\n' '1.60e9 - September 13, 2020\n' '1.65e9 - April 15, 2022')) await ctx.send(embed=embed) return try: time = int(args[0]) await ctx.send(embed=discord.Embed( color=discord.Color(MAIN_COLOR), description=datetime.datetime.fromtimestamp(time).strftime( "%B %d, %Y, %-I:%M:%S %p"))) return except ValueError: try: scientific_notation_lst = args[0].lower().split('e') await ctx.send(embed=discord.Embed( color=discord.Color(MAIN_COLOR), description=datetime.datetime.fromtimestamp( float(scientific_notation_lst[0]) * 10**(int(scientific_notation_lst[1]))).strftime( "%B %d, %Y, %-I:%M:%S %p"))) return except: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( '`timestamp` must be an integer or scientific notation (e.g. 1.0365e9)' )) return
async def setuniverse(self, ctx, *args): user_id = str(ctx.message.author.id) MAIN_COLOR = get_supporter(user_id) invalid = False if len(args) > 1: await ctx.send( content=f"<@{user_id}>", embed=Error( ctx, ctx.message).parameters(f"{ctx.invoked_with} [universe]")) return if len(args) == 0: args = ('play', ) universe = args[0].lower() if len(universe) > 50: invalid = True else: with open(UNIVERSES_FILE_PATH, 'r') as txtfile: universes = txtfile.read().split('\n') if not universe in universes: invalid = True if invalid: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( ('`universe` must be a [TypeRacer universe]' '(http://typeracerdata.com/universes)'))) return accounts = load_accounts() try: accounts[user_id]['universe'] = universe except KeyError: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information(( 'Discord account must be linked to TypeRacer account with ' f"`{get_prefix(ctx, ctx.message)}register [typeracer_username]`" ))) return update_accounts(accounts) await ctx.send(embed=discord.Embed( color=discord.Color(MAIN_COLOR), description= (f"<@{user_id}> has been linked to the {href_universe(universe)} universe" ))) return
async def register(self, ctx, *args): user_id = str(ctx.message.author.id) MAIN_COLOR = get_supporter(user_id) show_user_count = ctx.invoked_with[ -1] == '*' and ctx.message.author.id in BOT_ADMIN_IDS invalid = False if len(args) != 1: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).parameters( f"{ctx.invoked_with} [typeracer_username]")) return player = args[0].lower() urls = [Urls().get_user(player, 'play')] try: test_response = await fetch(urls, 'json') except: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information( '`typeracer_username` must be a TypeRacer username')) return accounts = load_accounts() try: accounts[user_id]['main'] = player except KeyError: accounts.update({ user_id: { 'main': player, 'alts': [], 'desslejusted': False, 'speed': 'lag', 'universe': 'play' } }) update_accounts(accounts) user_count = '' if show_user_count: user_count = f"\n{len(accounts)} users registered" await ctx.send(embed=discord.Embed( color=discord.Color(MAIN_COLOR), description=(f"<@{user_id}> has been linked to [**{player}**]" f"({Urls().user(player, 'play')}){user_count}"))) return
async def searchid(self, ctx, *args): user_id = ctx.message.author.id MAIN_COLOR = get_supporter(user_id) if len(args) != 1: await ctx.send( content=f"<@{user_id}>", embed=Error( ctx, ctx.message).parameters(f"{ctx.invoked_with} [text_id]")) return if args[0] == '*': tid = get_cached_id(ctx.message.channel.id) if not tid: tid = args[0] else: tid = args[0] urls = [Urls().text(tid)] text = await fetch(urls, 'read', scrape_text) if text[0]: cache_id(ctx.message.channel.id, tid) value_1 = f"\"{text[0]}\"" value_2 = f" [{TR_INFO}]({urls[0]})" value = value_1 + value_2 if len(value) > 1024: value_1 = value_1[0:1019 - len(value_2)] value = value_1 + "…\"" + value_2 embed = discord.Embed(title=f"Search Result for {tid}", color=discord.Color(MAIN_COLOR)) embed.add_field(name=f"Race Text ID: {tid}", value=value, inline=False) await ctx.send(embed=embed) return await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( f"**{tid}** is not a valid text ID")) return
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 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 milestone(self, ctx, *args): user_id = ctx.message.author.id MAIN_COLOR = get_supporter(user_id) if len(args) != 3: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).parameters( f"{ctx.invoked_with} [user] [num] [races/wpm/points]")) return player = get_player(user_id, args[0]) if escape_sequence(player): await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information( (f"[**{player}**]({Urls().user(player, 'play')}) " "doesn't exist"))) return try: num = int(args[1]) if num <= 0: raise ValueError except ValueError: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( '`num` must be a positive integer')) return categories = ['races', 'wpm', 'points'] if not args[2] in categories: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( '`category` must be `races/wpm/points`')) return conn = sqlite3.connect(DATABASE_PATH) c = conn.cursor() try: first_race = c.execute( f"SELECT t FROM t_{player} LIMIT 1").fetchone()[0] if args[2] == 'wpm': achieved, race_num = c.execute( f"SELECT t, gn FROM t_{player} WHERE wpm >= ? ORDER BY t LIMIT 1", (num, )).fetchone() elif args[2] == 'races': achieved, race_num = c.execute( f"SELECT t, gn FROM t_{player} WHERE gn == ?", (num, )).fetchone() else: user_data = c.execute( f"SELECT t, pts, gn FROM t_{player} ORDER BY t") sum_, achieved = 0, 0 for row in user_data: sum_ += row[1] if sum_ >= num: achieved = row[0] race_num = row[2] break except sqlite3.OperationalError: conn.close() await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).not_downloaded()) return except TypeError: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( 'The user has not achieved the milestone yet')) return conn.close() if not achieved: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( f"[**{player}**]({Urls().user(player, 'play')}) has not achieved the milestone yet" )) return category_1 = {'races': '', 'wpm': ' WPM', 'points': ' Point'}[args[2]] category_2 = { 'races': 'races', 'wpm': 'WPM', 'points': 'points' }[args[2]] embed = discord.Embed( title=f"{player}'s {num_to_text(num)}{category_1} Race", color=discord.Color(MAIN_COLOR), url=Urls().result(player, race_num, 'play')) embed.set_thumbnail(url=Urls().thumbnail(player)) embed.add_field( name=f"{player} achieved {f'{num:,}'} {category_2} on:", value= f"{datetime.datetime.fromtimestamp(achieved).strftime('%B %d, %Y, %I:%M:%S %p')} UTC" ) embed.add_field(name='It took:', value=seconds_to_text(achieved - first_race)) await ctx.send(embed=embed) return
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 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 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 textslessequal(self, ctx, *args): user_id = ctx.message.author.id MAIN_COLOR = get_supporter(user_id) if len(args) < 2 or len(args) > 3: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).parameters( f"{ctx.invoked_with} [user] [num] <wpm/points/time>")) return player = get_player(user_id, args[0]) if escape_sequence(player): await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information( (f"[**{player}**]({Urls().user(player, 'play')}) " "doesn't exist"))) return try: num = float(args[1]) if num <= 0: raise ValueError except ValueError: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( '`speed` and `length` must be positive numbers')) return if len(args) == 2: category = 'wpm' else: category = args[2].lower() if category not in ['wpm', 'points', 'times']: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( 'Must provide a valid category: `wpm/points/times`')) return conn = sqlite3.connect(DATABASE_PATH) c = conn.cursor() try: count = len( c.execute(f"SELECT DISTINCT tid from t_{player}").fetchall()) if category == 'wpm': user_data = c.execute( f"""SELECT tid, COUNT(tid) FROM t_{player} WHERE wpm >= ? GROUP BY tid ORDER BY COUNT(tid) DESC""", (num, )).fetchall() elif category == 'points': user_data = c.execute( f"""SELECT tid, COUNT(tid) FROM t_{player} WHERE pts >= ? GROUP BY tid ORDER BY COUNT(tid) DESC""", (num, )).fetchall() else: user_data = c.execute( f"""SELECT tid, COUNT(tid) FROM t_{player} GROUP BY tid HAVING COUNT(tid) >= ? ORDER BY COUNT(tid) DESC""", (num, )).fetchall() except sqlite3.OperationalError: conn.close() await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).not_downloaded()) return conn.close() texts_data = load_texts_large() category = { 'wpm': 'WPM', 'points': 'Points', 'times': 'Times' }[category] if category == 'Times': num = int(num) embed = discord.Embed( title=f"{player}'s Total Texts Typed Over {f'{num:,}'} {category}", color=discord.Color(MAIN_COLOR), description= (f"**Texts Typed:** {f'{count:,}'}\n" f"**Texts Over {f'{num:,}'} {category}:** " f"{f'{len(user_data):,}'} ({round(100 * len(user_data) / count, 2)}%)" )) embed.set_thumbnail(url=Urls().thumbnail(player)) for i in range(0, 10): try: value_1 = f"\"{texts_data[str(user_data[i][0])]}\" " value_2 = f"[{TR_INFO}]({Urls().text(user_data[i][0])})" value = value_1 + value_2 if len(value) > 1024: value_1 = value_1[0:1019 - len(value_2)] value = value_1 + "…\" " + value_2 embed.add_field( name=(f"{i + 1}. {f'{user_data[i][1]:,}'} times " f"(Race Text ID: {user_data[i][0]})"), value=value, inline=False) except: pass await ctx.send(embed=embed) return
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 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 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 racesover(self, ctx, *args): user_id = ctx.message.author.id MAIN_COLOR = get_supporter(user_id) if len(args) != 3: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).parameters( f"{ctx.invoked_with} [user] [num] [wpm/points]")) return player = get_player(user_id, args[0]) if escape_sequence(player): await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information( (f"[**{player}**]({Urls().user(player, 'play')}) " "doesn't exist"))) return try: num = float(args[1]) if num <= 0: raise ValueError except ValueError: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( '`num` must be a positive number')) return categories = ['wpm', 'points'] if not args[2] in categories: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( '`category` must be `wpm/points`')) return if args[2] == 'points': category = 'pts' else: category = 'wpm' conn = sqlite3.connect(DATABASE_PATH) c = conn.cursor() meeting, total = 0, 0 try: user_data = c.execute(f"SELECT {category} FROM t_{player}") for row in user_data: total += 1 if row[0] > num: meeting += 1 except sqlite3.OperationalError: conn.close() await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).not_downloaded()) return conn.close() category = {'wpm': 'WPM', 'pts': 'points'}[category] embed = discord.Embed( title=f"{player}'s Total Races Over {f'{num:,}'} {category}", color=discord.Color(MAIN_COLOR), description= (f"{f'{meeting:,}'} of {player}'s {f'{total:,}'} are above " f"{f'{num:,}'} {category} ({f'{round(100 * meeting / total, 2):,}'}%)" )) embed.set_footer( text='Counts texts GREATER than specified parameter (not equal to)' ) await ctx.send(embed=embed) return
async def dessle(self, ctx, *args): user_id = ctx.message.author.id MAIN_COLOR = get_supporter(user_id) dessle_enlighten = ctx.invoked_with in ['dessle', 'enlighten'] dessle_invoked = ctx.message.author.id == 279844278455500800 #Dessle's Discord ID if (not dessle_invoked and len(args) > 0): await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).parameters(f"{ctx.invoked_with}")) return elif dessle_invoked and dessle_enlighten and len(args) == 1: args = (args[0].strip('<@!').strip('>'), ) try: if len(args[0]) > 18: raise ValueError id_ = int(args[0]) except ValueError: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( f"**{args[0]}** is not a valid Discord ID")) return if id_ in BOT_OWNER_IDS: raise commands.CheckFailure return accounts = load_accounts() try: accounts[str(id_)]['desslejusted'] = True update_accounts(accounts) embed = discord.Embed( color=discord.Color(MAIN_COLOR), description=f"<@{id_}> **has been ENLIGHTENED**") await ctx.send(embed=embed) return except KeyError: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( f"<@{id_}> has not yet been linked to the bot")) return if len(args) > 0: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).parameters(f"{ctx.invoked_with}")) return texts = [] with open(TEXTS_FILE_PATH_CSV, 'r') as csvfile: reader = csv.reader(csvfile) next(reader) for row in reader: texts.append([row[0], row[1], row[2]]) embed = discord.Embed(title='10 Random Texts', color=discord.Color(MAIN_COLOR)) for i in range(1, 11): random_text = random.choice(texts) texts.remove(random_text) name = f"{i}. Race Text ID: {random_text[0]}" text = f"\"{random_text[1]}\" " if len(text) > 50: text = f"\"{random_text[1][0:50]}…\" " value = text value += (f"[{TR_INFO}]({Urls().text(random_text[0])}) " f"[{TR_GHOST}]({random_text[2]})\n") embed.add_field(name=name, value=value, inline=False) embed.set_footer(text="dessle#9999's custom command") await ctx.send(embed=embed) return
async def ginoo(self, ctx, *args): user_id = ctx.message.author.id MAIN_COLOR = get_supporter(user_id) if len(args) == 0: args = ('ginoo75', 1000) if len(args) != 2: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).parameters( f"{ctx.invoked_with} [player] [num_races]")) return player = get_player(user_id, args[0]) if escape_sequence(player): await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information( (f"[**{player}**]({Urls().user(player, 'play')}) " "doesn't exist"))) return try: num_races = int(args[1]) if num_races <= 0: raise ValueError except ValueError: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( '`num_races` must be a positive integer')) return monthly_races = [] current_month = { 'month': None, 'races': 0, 'words_typed': 0, 'chars_typed': 0, 'points': 0, 'time_spent': 0, 'total_wpm': 0, 'best_wpm': 0, 'worst_wpm': 1000000 } texts_length = load_texts_json() conn = sqlite3.connect(DATABASE_PATH) c = conn.cursor() try: user_data = c.execute(f"SELECT * FROM t_{player}") for row in user_data: month = datetime.datetime.fromtimestamp( row[1]).strftime('%Y-%-m') if current_month['month'] == None: current_month['month'] = month elif current_month['month'] != month: if current_month['races'] >= num_races: current_month.update({ 'average_wpm': round( current_month['total_wpm'] / current_month['races'], 2) }) del current_month['total_wpm'] current_month['time_spent'] = round( current_month['time_spent'], 2) current_month['points'] = round( current_month['points'], 2) monthly_races.append(list(current_month.values())) current_month = { 'month': month, 'races': 0, 'words_typed': 0, 'chars_typed': 0, 'points': 0, 'time_spent': 0, 'total_wpm': 0, 'best_wpm': 0, 'worst_wpm': 1000000 } current_month['races'] += 1 current_month['words_typed'] += texts_length.get( str(row[2]), {'word count': 0})['word count'] current_month['chars_typed'] += texts_length.get( str(row[2]), {'length': 0})['length'] current_month['points'] += row[4] current_month['time_spent'] += texts_length.get( str(row[2]), {'length': 0})['length'] * 12 / row[3] current_month['total_wpm'] += row[3] current_month['best_wpm'] = max(current_month['best_wpm'], row[3]) current_month['worst_wpm'] = min(current_month['worst_wpm'], row[3]) except sqlite3.OperationalError: conn.close() await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).not_downloaded()) return conn.close() if not monthly_races: await ctx.send(embed=discord.Embed( color=discord.Color(MAIN_COLOR), title= f"{f'{num_races:,}'} Races Longevity Statistics for {player}", description='It\'s empty here.').set_footer( text="ginoo75#6666's custom command")) return if current_month['races'] >= num_races: current_month.update({ 'average_wpm': round(current_month['total_wpm'] / current_month['races'], 2) }) del current_month['total_wpm'] current_month['time_spent'] = round(current_month['time_spent'], 2) current_month['points'] = round(current_month['points'], 2) monthly_races.append(list(current_month.values())) index, longest_chain_index = (0, ) * 2 max_chain, current_chain = (1, ) * 2 for i, month_stats in enumerate(monthly_races[1:]): index += 1 previous_month = datetime.datetime.strptime( monthly_races[i][0], '%Y-%m') month = month_stats[0] next_month = (previous_month.month + 1) % 12 if next_month == 0: next_month = 12 if datetime.datetime.strptime(month, '%Y-%m') == datetime.datetime( previous_month.year + int((previous_month.month) / 12), next_month, 1): current_chain += 1 if current_chain > max_chain: max_chain = current_chain longest_chain_index = index - current_chain + 1 else: current_chain = 1 monthly_races.insert(0, list(current_month.keys())) file_name = f"{player}_longevity_{num_races}.csv" with open(file_name, 'w') as csvfile: writer = csv.writer(csvfile) writer.writerows(monthly_races) file_ = discord.File(file_name, filename=file_name) embed = discord.Embed( color=discord.Color(MAIN_COLOR), title=f"{f'{num_races:,}'} Races Longevity Statistics for {player}", description= (f"**{f'{(index + 1):,}'}** months with chain of " f"**{f'{max_chain:,}'}** starting on " f"**{datetime.datetime.strptime(monthly_races[longest_chain_index + 1][0], '%Y-%m').strftime('%B %Y')}**" )) embed.set_footer(text="ginoo75#6666's custom command") await ctx.send(file=file_, embed=embed) os.remove(file_name) return
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 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 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 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 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 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 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 textsunder(self, ctx, *args): user_id = ctx.message.author.id MAIN_COLOR = get_supporter(user_id) if len(args) < 2 or len(args) > 3: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).parameters( f"{ctx.invoked_with} [user] [speed] <text_length>")) return player = get_player(user_id, args[0]) if escape_sequence(player): await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information( (f"[**{player}**]({Urls().user(player, 'play')}) " "doesn't exist"))) return try: speed = float(args[1]) if speed <= 0: raise ValueError length = 0 if len(args) == 3: length = float(args[2]) if length <= 0: raise ValueError except ValueError: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( '`speed` and `length` must be positive numbers')) return texts_data = dict() with open(TEXTS_FILE_PATH_CSV, 'r') as csvfile: reader = csv.reader(csvfile) next(reader) for row in reader: texts_data.update( {int(row[0]): { 'text': row[1], 'ghost': row[2] }}) conn = sqlite3.connect(DATABASE_PATH) c = conn.cursor() try: user_data = c.execute( f"SELECT gn, tid, MAX(wpm) FROM t_{player} GROUP BY tid ORDER BY wpm" ).fetchall() except sqlite3.OperationalError: conn.close() await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).not_downloaded()) return conn.close() tu_dict, tids = dict(), [] for tid in user_data: if tid[2] > speed: continue if length: if len(texts_data[tid[1]]['text']) > length: continue tu_dict.update({tid[1]: tid[2]}) tids.append(tid[1]) if len(tu_dict) == 0: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information( 'No texts found that meet the required criteria')) return title = f"Random Texts Under {f'{speed:,}'} WPM" if len(args) == 3: title += f" and {f'{length:,}'} Length" title += f" for {player} ({f'{len(tu_dict):,}'} left)" embed = discord.Embed(title=title, color=discord.Color(MAIN_COLOR)) embed.set_thumbnail(url=Urls().thumbnail(player)) for i in range(0, 5): try: random_tid = random.choice(tids) value_1 = f"\"{texts_data[random_tid]['text']}\" " value_2 = f"[{TR_INFO}]({Urls().text(random_tid)}) [{TR_GHOST}]({texts_data[random_tid]['ghost']})" value = value_1 + value_2 if len(value) > 1024: value_1 = value_1[0:1019 - len(value_2)] value = value_1 + "…\" " + value_2 embed.add_field( name=(f"{i + 1}. {f'{tu_dict[random_tid]:,}'} WPM" f" (Race Text ID: {random_tid})"), value=value, inline=False) tids.remove(random_tid) except IndexError: pass await ctx.send(embed=embed) return