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 delete_supporter(self, ctx, *args): if len(args) != 1: return try: int(args[0]) if len(args[0]) > 18: raise ValueError except ValueError: await ctx.send(content=f"<@{ctx.message.author.id}>", embed=Error(ctx, ctx.message).incorrect_format( f"**{args[0]}** is not a valid Discord ID")) return supporters = load_supporters() if not args[0] in list(supporters.keys()): await ctx.send(content=f"<@{ctx.message.author.id}>", embed=Error(ctx, ctx.message).missing_information( f"<@{args[0]}> is not in the system")) return try: del supporters[args[0]] except KeyError: pass update_supporters(supporters) await ctx.send(embed=discord.Embed( description=f"<@{args[0]}> removed from supporters list", color=discord.Color(0))) return
async def keegan(self, ctx, *args): user_id = ctx.message.author.id actions = {'setup': self.records_setup, 'update': self.records_update} if len(args) > 1: await ctx.send( content=f"<@{user_id}>", embed=Error( ctx, ctx.message).parameters(f"{ctx.invoked_with} <action>")) return if len(args) == 0: file_ = discord.File( TYPERACER_RECORDS_JSON, filename=f"typeracer_records_{self.last_updated.lower()}.json") await ctx.send(file=file_) return action = args[0].lower() try: action = actions[action] except KeyError: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( f"Must provide a valid action: `{'`, `'.join(actions.keys())}`" )) return await action(ctx) return
async def 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 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 setprefix(self, ctx, *args): if len(args) == 0: prefix = get_prefix(self.bot, ctx.message) await ctx.send(embed=discord.Embed( color=discord.Color(HELP_BLACK), title=f"The prefix is `{prefix}`", description=f"`{prefix}setprefix [prefix]`\n`{prefix}help`")) return elif len(args) > 1: await ctx.send(embed=Error(ctx, ctx.message).parameters( f"{ctx.invoked_with} <prefix>")) return prefix = args[0] if len(prefix) > 14: await ctx.send( f"<@{ctx.message.author.id}>", embed=Error(ctx, ctx.message).incorrect_format( '`prefix` can not be longer than 14 characters')) return prefixes = load_prefixes() prefixes[str(ctx.guild.id)] = prefix update_prefixes(prefixes) await ctx.send(embed=discord.Embed(title=f"Updated prefix to {prefix}", color=discord.Color(0))) return
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 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 add_supporter(self, ctx, *args): if len(args) != 2: return try: int(args[0]) if len(args[0]) > 18: raise ValueError except ValueError: await ctx.send(content=f"<@{ctx.message.author.id}>", embed=Error(ctx, ctx.message).incorrect_format( f"**{args[0]}** is not a valid Discord ID")) return try: tier = int(args[1]) if tier < 1 or tier > 4: raise ValueError except ValueError: await ctx.send(content=f"<@{ctx.message.author.id}>", embed=Error(ctx, ctx.message).incorrect_format( f"Tier level must be between 1 and 4")) return supporters = load_supporters() if args[0] in list(supporters.keys()): await ctx.send(content=f"<@{ctx.message.author.id}>", embed=Error(ctx, ctx.message).missing_information( f"<@{args[0]}> already in system")) return supporters.update({ args[0]: { 'color': MAIN_COLOR, 'tier': tier, 'graph_color': { 'bg': None, 'graph_bg': None, 'axis': None, 'line': None, 'text': None, 'grid': None, 'cmap': None } } }) update_supporters(supporters) await ctx.send(embed=discord.Embed( description= f"**Tier {tier}** supporter <@{args[0]}> added to the list", color=discord.Color(0))) return
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 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 on_command_error(ctx, error): if isinstance(error, commands.CommandOnCooldown): await ctx.send(content=f"<@{ctx.message.author.id}>", embed=Error(ctx, ctx.message).cooldown( (f"Maximum number of `{ctx.invoked_with}`" ' request(s) are running\nTry again later'))) return if isinstance(error, commands.CheckFailure): await ctx.send(content=f"<@{ctx.message.author.id}>", embed=Error(ctx, ctx.message).lacking_permissions( 'You lack the perms for that command')) return else: ctx.command.reset_cooldown(ctx) raise error
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 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
async def droptable(self, ctx, *args): user_id = ctx.message.author.id if len(args) != 1: await ctx.send( content=f"<@{user_id}>", embed=Error( ctx, ctx.message).parameters(f"{ctx.invoked_with} [user]")) return player = args[0].lower() if escape_sequence(player): await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information( (f"[**{player}**]({Urls().user(player, 'play')}) " "doesn't exist"))) return await ctx.send(embed=discord.Embed(title='Type "YES" to confirm', color=discord.Color(0)), delete_after=10) msg = await self.bot.wait_for('message', check=check(ctx.message.author), timeout=10) conn = sqlite3.connect(DATABASE_PATH) c = conn.cursor() try: c.execute(f"DROP TABLE t_{player}") except sqlite3.OperationalError: conn.close() await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information( "The user's table does not exist")) return conn.close() await ctx.send(embed=discord.Embed(color=discord.Color(HELP_BLACK), title=f"{player} table dropped"))
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 upgrade_supporter(self, ctx, *args): if len(args) != 2: return try: int(args[0]) if len(args[0]) > 18: raise ValueError except ValueError: await ctx.send(content=f"<@{ctx.message.author.id}>", embed=Error(ctx, ctx.message).incorrect_format( f"**{args[0]}** is not a valid Discord ID")) return try: tier = int(args[1]) if tier < 1 or tier > 4: raise ValueError except ValueError: await ctx.send(content=f"<@{ctx.message.author.id}>", embed=Error(ctx, ctx.message).incorrect_format( f"Tier level must be between 1 and 4")) return supporters = load_supporters() if not args[0] in list(supporters.keys()): await ctx.send(content=f"<@{ctx.message.author.id}>", embed=Error(ctx, ctx.message).missing_information( f"<@{args[0]}> is not in the system")) return supporters[args[0]]['tier'] = tier update_supporters(supporters) await ctx.send(embed=discord.Embed( description=f"<@{args[0]}> upgraded to **Tier {tier}**", color=discord.Color(0))) return
async def setcolor(self, ctx, *args): if len(args) > 1: await ctx.send( content=f"<@{ctx.message.author.id}>", embed=Error( ctx, ctx.message).parameters(f"{ctx.invoked_with} [hex_value]")) return if len(args) == 0: color = MAIN_COLOR elif len(args) == 1: try: color = int(f"0x{args[0]}", 16) if color < 0 or color > 16777216: raise ValueError except ValueError: try: colors = get_colors() color = colors[args[0].lower()] except KeyError: await ctx.send( content=f"<@{ctx.message.author.id}>", embed=Error(ctx, ctx.message).incorrect_format(( f"[**{args[0]}** is not a valid hex_value]" '(https://www.w3schools.com/colors/colors_picker.asp)' ))) return supporters = load_supporters() supporters[str(ctx.message.author.id)]['color'] = color update_supporters(supporters) await ctx.send(embed=discord.Embed(title='Color updated', color=discord.Color(color))) return
async def records_update(self, ctx): user_id = ctx.message.author.id try: updated_file_raw = ctx.message.attachments[0] except IndexError: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( 'Please upload a file and comment the command call')) return try: updated_file = json.loads(await updated_file_raw.read()) except json.JSONDecodeError: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( 'The uploaded file is not a properly formatted JSON file')) return with open(TYPERACER_RECORDS_JSON, 'w') as jsonfile: json.dump(updated_file, jsonfile, indent=4) try: await self.edit_record_messages() except NotImplementedError: await ctx.send( content=f"<@{ctx.message.author.id}>", embed=Error(ctx, ctx.message).missing_information( f"Must set-up the records with `{ctx.invoked_with} setup` first" )) return await ctx.send(embed=discord.Embed(title='Records Updated', color=discord.Color(0))) return
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 dicey(self, ctx, *args): question = ' '.join(args) if not question: await ctx.send( content=f"<@{ctx.message.author.id}>", embed=Error( ctx, ctx.message).incorrect_format('You must ask a question!')) return affirmative = [ 'It is certain.', 'It is decidedly so.', 'Without a doubt.', 'Yes – definitely.', 'You may rely on it.', 'As I see it, yes.', 'Most likely.', 'Outlook good.', 'Yes.', 'Signs point to yes.', 'Absolutely', 'Of course.', 'For sure.', 'YES.', 'By all means, yes.', 'Yeah, I\'d say so.', 'Totally.', 'Clearly, yes.' ] uncertain = [ 'Reply hazy, try again.', 'Ask again later.', 'Better not tell you now.', 'Cannot predict now.', 'Concentrate and ask again.' ] negative = [ 'Don\'t count on it.', 'My reply is no.', 'My sources say no.', 'Outlook not so good.', 'Very doubtful.', 'No.', 'Definitely not.', 'Certainly not.', 'No way.', 'Definitely not.', 'Of course not.', 'Nah.', 'Nope.', 'NO.', 'Are you stupid?', 'Obviously not.' ] category = random.randint(1, 100) if category == 1: await ctx.send(embed=discord.Embed( title='How am I supposed to know?')) elif category <= 41: await ctx.send(random.choice(affirmative)) elif category <= 60: await ctx.send(random.choice(uncertain)) else: await ctx.send(random.choice(negative)) return
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 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 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 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 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
datetime.datetime.strptime(args[-1], "%Y-%m-%d").date() - datetime.date(1970, 1, 1)).total_seconds() if end <= 1_250_000_000 or end > time.time(): raise ValueError args = args[:-1] except ValueError: pass 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 def calculate_pts(tid, wpm): try: return text_data[str(tid)]['word count'] * wpm / 60 except ValueError: return 0 except KeyError: return 0 text_data = load_texts_json() conn = sqlite3.connect(DATABASE_PATH) c = conn.cursor() data_x, data_y = [], []
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