async def boxplot(self, ctx, *args): user_id = ctx.message.author.id if len(args) == 0: args = check_account(user_id)(args) if len(args) < 1 or len(args) > 4: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).parameters( f"{ctx.invoked_with} [user] <user_2>...<user_4>")) return args = list(args) for i, player in enumerate(args): args[i] = get_player(user_id, args[i]) if escape_sequence(player): await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information( (f"[**{player}**]({Urls().user(player, 'play')}) " "doesn't exist"))) return conn = sqlite3.connect(DATABASE_PATH) c = conn.cursor() data = [] try: title_text = '' for user in args: title_text += f"{user} vs. " user_data = c.execute(f"SELECT wpm FROM t_{user}") temp = [i[0] for i in user_data] data.append(temp) except sqlite3.OperationalError: conn.close() await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).not_downloaded()) return conn.close() title_text = title_text[:-4] title_text += 'WPM' ax = plt.subplots()[1] ax.boxplot(data, showfliers=False) ax.set_xticklabels(list(args)) ax.set_ylabel('WPM') ax.set_title(title_text) plt.grid(True) file_name = f"{title_text}.png" graph_colors = get_graph_colors(user_id) graph_color(ax, graph_colors, True) plt.savefig(file_name, facecolor=ax.figure.get_facecolor()) wpm_picture = discord.File(file_name, filename=file_name) await ctx.send(file=wpm_picture) os.remove(file_name) plt.close() return
async def histogram(self, ctx, *args): user_id = ctx.message.author.id if len(args) == 0: args = check_account(user_id)(args) if len(args) != 1: await ctx.send( content=f"<@{user_id}>", embed=Error( ctx, ctx.message).parameters(f"{ctx.invoked_with} [user]")) return player = get_player(user_id, args[0]) if escape_sequence(player): await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information( (f"[**{player}**]({Urls().user(player, 'play')}) " "doesn't exist"))) return conn = sqlite3.connect(DATABASE_PATH) c = conn.cursor() try: player_data = c.execute(f"SELECT wpm FROM t_{player}") data = [i[0] for i in player_data.fetchall()] except sqlite3.OperationalError: conn.close() await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).not_downloaded()) return conn.close() ax = plt.subplots()[1] max_, min_ = max(data), min(data) if int(max_ - min_) // 10 == 0: patches = ax.hist(data, bins=1)[2] else: patches = ax.hist(data, bins=int(max_ - min_) // 10)[2] ax.set_xlabel('WPM') ax.set_ylabel('Frequency') plt.grid(True) ax.set_title(f"{player}'s WPM Histogram") file_name = f"{player} WPM.png" graph_colors = get_graph_colors(user_id) graph_color(ax, graph_colors, False, patches) plt.savefig(file_name, facecolor=ax.figure.get_facecolor()) wpm_picture = discord.File(file_name, filename=file_name) await ctx.send(file=wpm_picture) os.remove(file_name) plt.close() return
async def 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 timebetween(self, ctx, *args): user_id = ctx.message.author.id MAIN_COLOR = get_supporter(user_id) account = account_information(user_id) universe = account['universe'] url_param = False if len(args) < 2 or len(args) > 3: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).parameters( (f"{ctx.invoked_with} [url] [url]` or " f"`{ctx.invoked_with} [user] [race_one] [race_two]"))) return if len(args) == 2: try: args[0].index('result?') and args[1].index('result?') url_param = True except ValueError: args = check_account(user_id)(args) if len(args) == 3: player = get_player(user_id, args[0]) try: race_one = int(args[1]) race_two = int(args[2]) if race_one <= 0 or race_two <= 1: raise ValueError args = (race_one, race_two) except ValueError: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( f"Refer to `help {ctx.invoked_with}` for correct parameter formats" )) return urls = [] if url_param: urls = [url for url in args] else: urls = [ Urls().result(player, race_num, universe) for race_num in args ] responses = await fetch(urls, 'text', timestamp_scraper) try: conn = sqlite3.connect(DATABASE_PATH) c = conn.cursor() player = responses[0]['player'] difference = abs(responses[1]['timestamp'] - responses[0]['timestamp']) universe = responses[0]['universe'] race_nums = [response['race_number'] for response in responses] race_one = min(race_nums) race_two = max(race_nums) except TypeError: try: if escape_sequence(player): raise sqlite3.OperationalError difference = abs(c.execute(f"SELECT t FROM t_{player} WHERE gn = ?", (race_one,)) .fetchone()[0] -\ c.execute(f"SELECT t FROM t_{player} WHERE gn = ?", (race_two,)) .fetchone()[0]) except: conn.close() await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information( '`timestamp` was not found in either race')) return conn.close() description = (f"The time between **{player}**'s " f"**{num_to_text(race_one)}** and " f"**{num_to_text(race_two)}** race in the\n" f"{href_universe(universe)} universe was " f"**{seconds_to_text(difference)}**") await ctx.send(embed=discord.Embed(color=discord.Color(MAIN_COLOR), description=description)) return
async def construct_embeds(self): self.last_updated = datetime.datetime.utcnow().strftime( '%B %-d, %Y, %X %p') self.country_tally = dict() self.user_tally = dict() faq_thumbnail = HELP_IMG speed_thumbnail, speed_color = 'https://i.imgur.com/bXAjl4C.png', 0x001359 three_hundred_thumbnail, three_hundred_color = 'https://i.imgur.com/i8kXn3K.png', 0x1B038C races_thumbnail, races_color = 'https://i.imgur.com/DspJLUH.png', 0x14328C points_thumbnail, points_color = 'https://i.imgur.com/Xm0VNQV.png', 0x0B4A9E speedruns_thumbnail, speedruns_color = 'https://i.imgur.com/lPdQvvQ.png', 0x0E61D1 awards_thumbnail, awards_color = 'https://i.imgur.com/W9NEYb2.png', 0x0790E8 records_held_thumbnail, records_held_color = 'https://i.imgur.com/3gJNZRO.png', 0x00BFFF last_updated_color = 0x00EEFF faq_information = self.records_information['information'] faq_embed = discord.Embed(**faq_information) for field in faq_information['fields']: faq_embed.add_field(**field) faq_embed.set_thumbnail(url=faq_thumbnail) faq_embed.set_footer(**faq_information['footer']) all_records_information = self.records_information['all_records'] speed_information = all_records_information['speed']['records'] speed_embed = discord.Embed(title='Speed Records', color=discord.Color(speed_color)) for speed_record in speed_information: speed_embed.add_field( **self.record_field_constructor(speed_record, ' WPM')) speed_embed.set_thumbnail(url=speed_thumbnail) three_hundred_information = all_records_information['300_wpm_club'][ 'records'] description, members_list = '', [] for member, url in three_hundred_information.items(): try: result = (await fetch([url], 'text', rs_typinglog_scraper))[0] wpm = round( 12000 * (result['length'] - 1) / (result['duration'] - result['start']), 3) date = datetime.datetime.fromtimestamp( result['timestamp']).strftime('%-m/%-d/%y') members_list.append([member, wpm, url, date]) except TypeError: pass await asyncio.sleep(5) members_list = sorted(members_list, key=lambda x: x[1], reverse=True) for i, member in enumerate(members_list): if i < 3: rank = self.medals[i] else: rank = f"{i + 1}." if member[1] >= 400: wpm = f"**{member[1]}**" else: wpm = member[1] description += f"{rank} {self.get_flag(member[0])} {member[0]} - " description += f"[{wpm} WPM]({member[2]}) - {member[3]}\n" description = description[:-1] three_hundred_embed = discord.Embed( title='300 WPM Club', color=discord.Color(three_hundred_color), description=description) three_hundred_embed.set_thumbnail(url=three_hundred_thumbnail) three_hundred_embed.set_footer( text='All speeds measured according to adjusted metric') all_time_leaders = [ user for category in self.races_alltime.values() for user in category ] all_time_leaders += [ user for category in self.points_alltime.values() for user in category ] all_time_leaders += [ user for category in self.awards_alltime.values() for user in category ] all_time_leaders = set(all_time_leaders) all_time_data = dict() url_constructor = Urls() right_now = datetime.datetime.utcnow().timestamp() texts_data = load_texts_json() with open(COUNTRY_CODES, 'r') as jsonfile: country_codes = json.load(jsonfile) conn = sqlite3.connect(DATABASE_PATH) c = conn.cursor() for user in all_time_leaders: await asyncio.sleep(5) medals = {1: 0, 2: 0, 3: 0} try: response = (await fetch([url_constructor.user(user, 'play')], 'text'))[0] soup = BeautifulSoup(response, 'html.parser') rows = soup.select( "table[class='personalInfoTable']")[0].select('tr') medal_html_objects = None for row in rows: cells = row.select('td') if len(cells) < 2: continue if cells[0].text.strip() == 'Awards': medal_html_objects = cells[1].select('a') break if medal_html_objects: for medal in medal_html_objects: medals[int(medal.select('img')[0]['title'][0])] += 1 if escape_sequence(user): raise FileNotFoundError urls = [Urls().get_races(user, 'play', 1)] user_data = c.execute( f"SELECT * FROM t_{user} ORDER BY t DESC LIMIT 1") last_race_timestamp = user_data.fetchone()[1] data = await fetch_data(user, 'play', last_race_timestamp + 0.01, right_now) if data: c.executemany( f"INSERT INTO t_{user} VALUES (?, ?, ?, ?, ?)", data) conn.commit() data = c.execute(f"SELECT * FROM t_{user}") races, chars_typed, points, total_points, seconds_played = ( 0, ) * 5 for race in data: races += 1 race_text_id = str(race[2]) text_length = texts_data.get(race_text_id, {"length": 0})['length'] chars_typed += text_length points += race[4] total_points += race[4] if race[4] else texts_data.get( race_text_id, {"word count": 0})['word count'] * race[3] / 60 try: seconds_played += 12 * text_length / race[3] except ZeroDivisionError: seconds_played += 0 first_race_timestamp = c.execute( f"SELECT * FROM t_{user} ORDER BY t ASC LIMIT 1").fetchone( )[1] time_difference = right_now - first_race_timestamp points_time_difference = min(right_now - 1_501_113_600, time_difference) all_time_data.update({ user: { 'races': races, 'races_daily_average': 86400 * races / time_difference, 'chars_typed': chars_typed, 'points': points, 'points_daily_average': 86400 * points / points_time_difference, 'total_points': total_points, 'medals': medals, 'seconds_played': seconds_played, 'seconds_played_daily_average': 86400 * seconds_played / time_difference, 'time_difference': time_difference, 'points_time_difference': points_time_difference } }) except Exception as exception: print(user, exception) continue conn.close() def helper_formatter(tally_dict, name_formatter, results_formatter, *args): rankings = dict() args = args[0] for key, value in tally_dict.items(): try: rankings[value].append(key) except KeyError: rankings[value] = [key] rankings = [[k, v] for k, v in sorted( rankings.items(), key=lambda x: x[0], reverse=True)][0:3] value = '' for i, ranking in enumerate(rankings): if i == 0 and args[0]: for user in ranking[1]: self.tally(user) if len(args) == 1: optional = '' else: try: optional = args[1][ranking[1][0]] except KeyError: optional = '' ranking[1] = [name_formatter(j) for j in ranking[1]] value += f"{self.medals[i]} {' / '.join(ranking[1])} - {results_formatter(ranking[0])}{optional}\n" return value races_information = all_records_information['races']['records'] races_embed = discord.Embed(title='Race Records', color=discord.Color(races_color)) races_embed.set_thumbnail(url=races_thumbnail) helper_sorter = lambda param: { k: v[param] for k, v in all_time_data.items() } helper_flag = lambda x: f"{self.get_flag(x)} {x}" helper_round = lambda x: f'{round(x):,}' races_daily_average_dict = dict() points_daily_average_dict = dict() chars_typed_metadata_dict = dict() medal_breakdown_dict = dict() for key, value in all_time_data.items(): days = round(value['time_difference'] / 86400) days_ = round(value['points_time_difference'] / 86400) medal_breakdown = value['medals'] races_daily_average_dict.update({ key: f""" ({f"{round(value['races'], 2):,}"} races over {f"{days:,}"} days)""" }) points_daily_average_dict.update({ key: f""" ({f"{round(value['points']):,}"} points over {f"{days_:,}"} days)""" }) chars_typed_metadata_dict.update({ key: f""" ({f"{round(value['chars_typed'] / value['races'], 2):,}"} average chars over {f"{value['races']:,}"} races)""" }) medal_breakdown_dict.update({ key: f""" (:first_place: {f"{medal_breakdown[1]:,}"} :second_place: {f"{medal_breakdown[2]:,}"} :third_place: {f"{medal_breakdown[3]:,}"})""" }) races_embed.add_field(name='All-Time Leaders', value=helper_formatter(helper_sorter('races'), helper_flag, helper_round, (True, )), inline=False) races_embed.add_field(name='Highest Average Daily Races', value=helper_formatter( helper_sorter('races_daily_average'), helper_flag, helper_round, (True, races_daily_average_dict)), inline=False) races_embed.add_field(name='Most Characters Typed', value=helper_formatter( helper_sorter('chars_typed'), helper_flag, helper_round, (True, chars_typed_metadata_dict)), inline=False) races_embed.add_field(name='Most Time Spent Racing', value=helper_formatter( helper_sorter('seconds_played'), helper_flag, seconds_to_text, (True, )), inline=False) races_embed.add_field( name='Highest Average Daily Time Spent Racing', value=helper_formatter( helper_sorter('seconds_played_daily_average'), helper_flag, seconds_to_text, (True, )), inline=False) for races_record in races_information: races_embed.add_field( **self.record_field_constructor(races_record, '')) points_information = all_records_information['points']['records'] points_embed = discord.Embed(title='Point Records', color=discord.Color(points_color)) points_embed.set_thumbnail(url=points_thumbnail) points_embed.add_field(name='All-Time Leaders', value=helper_formatter(helper_sorter('points'), helper_flag, helper_round, (True, )), inline=False) points_embed.add_field(name='Highest Average Daily Points', value=helper_formatter( helper_sorter('points_daily_average'), helper_flag, helper_round, (True, points_daily_average_dict)), inline=False) points_embed.add_field( name='All-Time Total Points (Includes Retroactive Points)', value=helper_formatter(helper_sorter('total_points'), helper_flag, helper_round, (True, )), inline=False) points_embed.set_footer( text= 'Retroactive points represent the total number of points a user would have gained, before points were introduced in 2017' ) for points_record in points_information: points_embed.add_field( **self.record_field_constructor(points_record, '')) speedruns_information = all_records_information['speedruns']['records'] speedruns_embed = discord.Embed(title='Speedrun Records', color=discord.Color(speedruns_color)) speedruns_embed.set_thumbnail(url=speedruns_thumbnail) for speedruns_record in speedruns_information: speedruns_embed.add_field( **self.record_field_constructor(speedruns_record, '')) awards_embed = discord.Embed(title='Awards Records', color=discord.Color(awards_color)) awards_embed.set_thumbnail(url=awards_thumbnail) medal_tally = { k: sum(v['medals'].values()) for k, v in all_time_data.items() } awards_embed.add_field( name='All-Time Leaders', value=helper_formatter(medal_tally, helper_flag, helper_round, (True, medal_breakdown_dict)), inline=False) records_held_embed = discord.Embed( title='Records Held', color=discord.Color(records_held_color)) records_held_embed.set_thumbnail(url=records_held_thumbnail) top_countries_list = [[k, v] for k, v in self.country_tally.items()] records_held_embed.add_field(name = 'Top Countries', value = helper_formatter(self.country_tally,\ lambda x: f"{x} {country_codes[re.findall(':flag.+:', x)[0][5:-1].strip('_').upper()]}", helper_round, (False,)), inline = False) records_held_embed.add_field(name='Top Users', value=helper_formatter( self.user_tally, helper_flag, helper_round, (False, )), inline=False) last_updated_embed = discord.Embed( color=discord.Color(last_updated_color), description=f"Last updated **{self.last_updated} UTC**") return { 'faq': faq_embed, 'speed': speed_embed, '300_wpm_club': three_hundred_embed, 'races': races_embed, 'points': points_embed, 'speedruns': speedruns_embed, 'awards': awards_embed, 'records_held': records_held_embed, 'last_updated': last_updated_embed }
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 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 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 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 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
async def unraced(self, ctx, *args): user_id = ctx.message.author.id MAIN_COLOR = get_supporter(user_id) if len(args) == 0 or (len(args) == 1 and len(args[0]) < 4): args = check_account(user_id)(args) if len(args) == 0 or len(args) > 2: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).parameters( f"{ctx.invoked_with} [user] <length>")) return player = get_player(user_id, args[0]) if escape_sequence(player): await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information( (f"[**{player}**]({Urls().user(player, 'play')}) " "doesn't exist"))) return if len(args) == 2: try: length = int(args[1]) if length < 1 or length > 999: raise ValueError except ValueError: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( '`length` must be a positive integer less than 999')) return else: length = 0 all_tids, user_tids, texts_data = set(), set(), dict() with open(TEXTS_FILE_PATH_CSV, 'r') as csvfile: reader = csv.reader(csvfile) next(reader) for row in reader: all_tids.add(int(row[0])) texts_data.update( {int(row[0]): { 'text': row[1], 'ghost': row[2] }}) conn = sqlite3.connect(DATABASE_PATH) c = conn.cursor() try: user_data = c.execute(f"SELECT DISTINCT tid FROM t_{player}") for row in user_data: user_tids.add(row[0]) except sqlite3.OperationalError: conn.close() await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).not_downloaded()) return conn.close() unraced_tids = list(all_tids - user_tids) if length: unraced_tids = list( filter(lambda x: len(texts_data[x]['text']) < length, unraced_tids)) if len(unraced_tids) == 0: description = f"{player} has completed all texts" if length: description += f" under length {length}" description += '!' await ctx.send(embed=discord.Embed(title='Nothing to Choose From', color=discord.Color(754944), description=description)) return title = 'Random Unraced Texts' if length: title += f" Under Length {length}" title += f" for {player} ({f'{len(unraced_tids):,}'} left)" embed = discord.Embed(title=title, color=discord.Color(MAIN_COLOR)) embed.set_thumbnail(url=Urls().thumbnail(player)) try: for i in range(0, 5): random_tid = random.choice(unraced_tids) value_1 = f"\"{texts_data[random_tid]['text']}\" " value_2 = (f"[{TR_INFO}]({Urls().text(random_tid)}) " f"[{TR_GHOST}]({texts_data[random_tid]['ghost']})") value = value_1 + value_2 if len(value) > 1024: value_1 = value_1[0:1019 - len(value_2)] value = f"{value_1}…\" {value_2}" embed.add_field(name=f"{i + 1}. Race Text ID: {random_tid}", value=value, inline=False) unraced_tids.remove(random_tid) except: pass await ctx.send(embed=embed) return
async def textbests(self, ctx, *args): user_id = ctx.message.author.id MAIN_COLOR = get_supporter(user_id) tb = ctx.invoked_with in ['textbests'] + get_aliases('textbests') bd = ctx.invoked_with in ['breakdown'] + get_aliases('breakdown') if len(args) == 0: args = check_account(user_id)(args) try: if len(args) == 0: raise ValueError elif len(args) > 2: raise ValueError elif bd and len(args) != 1: raise ValueError except ValueError: optional = ' <num_texts>' if tb else '' await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).parameters( f"{ctx.invoked_with} [user]{optional}")) return player = get_player(user_id, args[0]) if escape_sequence(player): await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information( (f"[**{player}**]({Urls().user(player, 'play')}) " "doesn't exist"))) return filter_tb = len(args) == 2 if filter_tb: try: num_texts = int(args[1]) if num_texts <= 0: raise TypeError except TypeError: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( '`num_texts` must be a positive integer')) return sum_, count = 0, 0 conn = sqlite3.connect(DATABASE_PATH) c = conn.cursor() try: user_data = c.execute( f"SELECT gn, tid, MAX(wpm) FROM t_{player} GROUP BY tid ORDER BY wpm" ).fetchall() if filter_tb: user_data = user_data[-num_texts:] min_bucket = int(user_data[0][2] // 10) max_bucket = int(user_data[-1][2] // 10) if user_data[-1][2] // 10 <= 30 else 30 if bd: breakdown_dict, textbest_dict = {}, {} for i in range(min_bucket, max_bucket + 1): breakdown_dict.update({i: 0}) textbest_dict.update({i: {'count': 0, 'sum': 0}}) for row in user_data: count += 1 sum_ += row[2] if bd: bucket = int(row[2] // 10) if row[2] // 10 <= 30 else 30 if ctx.invoked_with[-1] == '*': breakdown_dict[bucket] += 1 textbest_dict[bucket]['sum'] += row[2] textbest_dict[bucket]['count'] += 1 else: for i in range(min_bucket, bucket + 1): breakdown_dict[i] += 1 except sqlite3.OperationalError: conn.close() await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).not_downloaded()) return conn.close() if tb: texts_data = load_texts_large() if len(user_data) < 10: worst = [] if len(user_data) < 5: top = user_data[::-1] else: top = user_data[-5:][::-1] else: worst = user_data[0:5] top = user_data[-5:][::-1] else: breakdown_text = '' max_count_spacer = len(f'{max(breakdown_dict.values()):,}') for bucket, count_ in breakdown_dict.items(): bucket_spacer = 1 + math.floor( math.log10(max_bucket)) - math.floor(math.log10(bucket)) count_spacer = max_count_spacer - len(f'{count_:,}') count_spacer_ = max_count_spacer - len(f'{count - count_:,}') breakdown_text += f"{{{bucket * 10}+{' ' * bucket_spacer}WPM}} " breakdown_text += f"{' ' * count_spacer}{f'{count_:,}'} [{f'{round(100 * count_ / count, 2):6.2f}'}%] " if ctx.invoked_with[-1] == '*': try: average = f"{round(textbest_dict[bucket]['sum'] / textbest_dict[bucket]['count'], 2):6.2f}" except ZeroDivisionError: average = " —— " breakdown_text += f"{{{average} WPM}}\n" else: breakdown_text += f"({' ' * count_spacer_}{f'{count - count_:,}'} left)\n" title = f"{player}'s Text Bests" if filter_tb: title += f" (Top {f'{num_texts:,}'} Texts Filtered)" embed = discord.Embed(title=title, color=discord.Color(MAIN_COLOR)) embed.set_thumbnail(url=Urls().thumbnail(player)) embed.add_field( name='Texts', value= (f"**Texts:** {f'{count:,}'}\n" f"**Text Bests Average:** {f'{round(sum_ / count, 2):,}'} (" f"{f'{round(count * (5 - (sum_ / count) % 5), 2):,}'} total WPM gain " f"til {round(5 * ((sum_ / count) // 5 + 1))} WPM)"), inline=False) if tb: value = '' for i, text in enumerate(top): value += f"**{i + 1}. {f'{text[2]:,}'} WPM (Race #{f'{text[0]:,}'})**\n" value += f"{texts_data.get(str(text[1]), 'Missing Text')} [:cinema:]({Urls().result(player, text[0], 'play')})\n" embed.add_field(name=f"Top {i + 1} Texts", value=value, inline=False) value = '' for i, text in enumerate(worst): value += f"**{i + 1}. {f'{text[2]:,}'} WPM (Race #{f'{text[0]:,}'})**\n" value += f"{texts_data.get(str(text[1]), 'Missing Text')} [:cinema:]({Urls().result(player, text[0], 'play')})\n" embed.add_field(name=f"Worst {i + 1} Texts", value=value, inline=False) else: embed.add_field(name='Breakdown', value=f"```css\n{breakdown_text}```", inline=False) await ctx.send(embed=embed) return
async def personalbest(self, ctx, *args): user_id = ctx.message.author.id MAIN_COLOR = get_supporter(user_id) if len(args) == 0: args = check_account(user_id)(args) if len(args) == 0 or len(args) > 2: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).parameters( f"{ctx.invoked_with} [user] [text_id]")) return player = get_player(user_id, args[0]) if escape_sequence(player): await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information( (f"[**{player}**]({Urls().user(player, 'play')}) " "doesn't exist"))) return cur_wpm = 0 if len(args) == 2: try: if args[1] == '*': text_id = get_cached_id(ctx.message.channel.id) if not text_id: text_id = int(args[1]) else: text_id = int(args[1]) except ValueError: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( f"**{args[1]}** is not a valid text ID")) return else: try: urls = [Urls().get_races(player, 'play', 1)] response = (await fetch(urls, 'json'))[0][0] text_id = int(response['tid']) cur_wpm = float(response['wpm']) except: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information()) return with open(TEXTS_FILE_PATH_CSV, 'r') as csvfile: reader = csv.reader(csvfile) next(reader) for row in reader: if int(row[0]) == text_id: text = row[1] break else: continue try: if len(text) > 1024: text = f"\"{text[0:1020]}…\"" else: text = f"\"{text}\"" except NameError: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( f"{text_id} is not a valid text ID")) return count, sum_, best, best_gn, worst_gn = (0, ) * 5 worst = 100000 conn = sqlite3.connect(DATABASE_PATH) c = conn.cursor() try: user_data = c.execute( f"SELECT gn, wpm FROM t_{player} WHERE tid = ?", (text_id, )).fetchall() for row in user_data: count += 1 sum_ += row[1] if row[1] > best: best_gn, best = row if row[1] < worst: worst_gn, worst = row except sqlite3.OperationalError: conn.close() await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).not_downloaded()) return conn.close() if cur_wpm > best: color, description = 754944, f"**Improved by {round(cur_wpm - best, 2)} WPM (to {round(cur_wpm, 2)} WPM)**" else: color, description = MAIN_COLOR, '' if not count: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information( f"**{player}** has not completed the text yet")) return value = f"**Times:** {f'{count:,}'}\n" if cur_wpm: value += f"**Last Race:** {f'{cur_wpm:,}'} WPM\n" value += (f"**Average:** {f'{round(sum_ / count, 2):,}'} WPM\n" f"**Fastest:** {f'{best:,}'} WPM " f"(Race #{f'{best_gn:,}'}) [:cinema:]" f"({Urls().result(player, best_gn, 'play')})\n" f"**Slowest:** {f'{worst:,}'} WPM " f"(Race #{f'{worst_gn:,}'}) [:cinema:]" f"({Urls().result(player, worst_gn, 'play')})\n") title = f"Quote #{text_id} Statistics for {player}" cache_id(ctx.message.channel.id, text_id) if description: embed = discord.Embed(title=title, color=discord.Color(color), url=Urls().text(text_id), description=description) else: embed = discord.Embed(title=title, color=discord.Color(color), url=Urls().text(text_id)) embed.set_thumbnail(url=Urls().thumbnail(player)) embed.add_field(name='Quote', value=text, inline=False) embed.add_field(name='Speeds', value=value) if ctx.invoked_with[-1] == '*' and len(user_data) > 1: ax = plt.subplots()[1] data_y = [i[1] for i in user_data] if cur_wpm: data_y += [cur_wpm] length = len(data_y) data_x = [i + 1 for i in range(length)] if length < 30: ax.plot(data_x, data_y) else: ax.scatter(data_x, data_y, marker='.', alpha=0.1, color='#000000') if length < 500: sma = length // 10 else: sma = 50 moving_y = [sum(data_y[0:sma]) / sma] moving_y += [ sum(data_y[i - sma:i]) / sma for i in range(sma, length) ] moving_x = [data_x[0]] + data_x[sma:] moving_y = reduce_list(moving_y) moving_x = reduce_list(moving_x) ax.plot(moving_x, moving_y, color='#FF0000') title += f"\n(Moving Average of {sma} Races)" ax.set_title(title) ax.set_xlabel('Attempt #') ax.set_ylabel('WPM') plt.grid(True) file_name = f"{title}.png" graph_colors = get_graph_colors(user_id) graph_color(ax, graph_colors, False) plt.savefig(file_name, facecolor=ax.figure.get_facecolor()) plt.close() file_ = discord.File(file_name, filename='image.png') embed.set_image(url='attachment://image.png') os.remove(file_name) await ctx.send(file=file_, embed=embed) return await ctx.send(embed=embed) return
async def 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 charlieog(self, ctx, *args): user_id = ctx.message.author.id MAIN_COLOR = get_supporter(user_id) if len(args) == 0: args = check_account(user_id)(args) if len(args) == 0 or len(args) > 2: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).parameters( f"{ctx.invoked_with} [user] <text_id>")) return player = get_player(user_id, args[0]) if escape_sequence(player): await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information( (f"[**{player}**]({Urls().user(player, 'play')}) " "doesn't exist"))) return if len(args) == 2: try: if args[1] == '*': tid = get_cached_id(ctx.message.channel.id) if not tid: tid = int(args[1]) else: tid = int(args[1]) if tid <= 0: raise ValueError except ValueError: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( f"{args[1]} is not a valid text ID")) return else: tid = 3621293 urls = [Urls().get_races(player, 'play', 1)] try: api_response = await fetch(urls, 'json') except: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information( (f"[**{player}**]({Urls().user(player, 'play')}) " "doesn't exist or has no races"))) return file_name = f"t_{player}" conn = sqlite3.connect(DATABASE_PATH) c = conn.cursor() try: user_data = c.execute( f"SELECT * FROM t_{player} ORDER BY t DESC LIMIT 1") last_race_timestamp = user_data.fetchone()[1] except sqlite3.OperationalError: conn.close() await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).not_downloaded()) return text, ghost = '', '' with open(TEXTS_FILE_PATH_CSV, 'r') as csvfile: reader = csv.reader(csvfile) next(reader) for row in reader: if int(row[0]) == tid: text, ghost = row[1], row[2] if not text or not ghost: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( f"{tid} is not a valid text ID")) return cache_id(ctx.message.channel.id, tid) value_1 = f"\"{text}\" " value_2 = (f"[{TR_INFO}]({Urls().text(tid)}) " f"[{TR_GHOST}]({ghost})") value = value_1 + value_2 if len(value) > 1024: value_1 = value_1[0:1019 - len(value_2)] value = value_1 + "…\"" + value_2 data = await fetch_data(player, 'play', last_race_timestamp + 0.01, time.time()) if data: c.executemany(f"INSERT INTO {file_name} VALUES (?, ?, ?, ?, ?)", data) conn.commit() data = c.execute( f"""SELECT * FROM (SELECT * FROM {file_name} WHERE t >= ?) WHERE tid = ?""", (time.time() - 86400, tid)).fetchall() conn.close() if len(data) < 10: description = 'Next save available **now**' else: description = f"Next save available in **{seconds_to_text(86400 + data[0][1] - time.time())}**" value_ = '' for i, race in enumerate(data): value_ += ( f"{i + 1}. {seconds_to_text(time.time() - race[1])} ago " f"({race[3]} WPM)\n") embed = discord.Embed( title=f"{player}'s Text #{tid} Statistics in Last 24 Hours", color=discord.Color(MAIN_COLOR), description=description) embed.add_field(name=f"Text ID: {tid}", value=value, inline=False) if value_: embed.add_field(name='Races', value=value_, inline=False) embed.set_footer(text="snowmelt#1745's custom command") await ctx.send(embed=embed) return
async def 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 ban(self, ctx, *args): user_id = ctx.message.author.id if ctx.invoked_with == 'banned': conn = sqlite3.connect(DATABASE_PATH) c = conn.cursor() banned_users = c.execute( f"SELECT * FROM {USERS_KEY} WHERE banned = 1").fetchall() conn.close() description, banned = '', [['ID']] for i, user in enumerate(banned_users): id_ = user[0] if i < 10: description += f"**{i + 1}.** <@{id_}>\n" banned.append([id_]) description = description[:-1] embed = discord.Embed(title='Banned Users', color=discord.Color(0), description=description) if len(banned) > 10: with open('banned_users.csv', 'w') as csvfile: writer = csv.writer(csvfile) writer.writerows(banned) file_ = discord.File('banned_users.csv', 'banned_users.csv') await ctx.send(file=file_, embed=embed) os.remove('banned_users.csv') return await ctx.send(embed=embed) return if len(args) != 1: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).parameters( f"{ctx.invoked_with} [discord_id]")) return def perms(id_): if id_ in BOT_OWNER_IDS: return 2 if id_ in BOT_ADMIN_IDS: return 1 return 0 try: args = (args[0].strip('<@!').strip('>'), ) if len(args[0]) > 18 or escape_sequence(args[0]): raise ValueError discord_id = int(args[0]) if perms(discord_id) >= perms(user_id): raise commands.CheckFailure return 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 conn = sqlite3.connect(DATABASE_PATH) c = conn.cursor() try: user_data = c.execute(f"SELECT * FROM {USERS_KEY} WHERE id = ?", (discord_id, )).fetchall() if not user_data: toggled_to = True c.execute(f"INSERT INTO {USERS_KEY} (id, banned) VALUES(?, ?)", ( discord_id, toggled_to, )) else: toggled_to = not user_data[0][1] c.execute(f"UPDATE {USERS_KEY} SET banned = ? WHERE id = ?", ( toggled_to, discord_id, )) conn.commit() conn.close() except sqlite3.OperationalError: c.execute( f"CREATE TABLE {USERS_KEY} (id integer PRIMARY KEY, banned BOOLEAN)" ) conn.close() return setting = 'banned' if toggled_to else 'unbanned' await ctx.send( embed=discord.Embed(color=discord.Color(0), description=( f"<@{discord_id}> has been **{setting}**\n" 'from using <@742267194443956334>'))) 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 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 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
if len(args) > 1: try: args[-1].index('-') end = ( 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
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 improvement(self, ctx, *args): user_id = ctx.message.author.id if len(args) == 0: args = check_account(user_id)(args) if len(args) == 1: args += ('races', ) if len(args) != 2: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).parameters( f"{ctx.invoked_with} [user] <time/races>")) return player = get_player(user_id, args[0]) if args[1].lower() not in ['time', 'races']: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( 'Must provide a valid category: `time/races`')) return if args[1].lower() == 'time': q_category = 't' category = 'Time' else: q_category = 'gn' category = 'Races' if escape_sequence(player): await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information( (f"[**{player}**]({Urls().user(player, 'play')}) " "doesn't exist"))) return data_x, data_y, max_point = [], [], 0 conn = sqlite3.connect(DATABASE_PATH) c = conn.cursor() try: player_data = c.execute( f"SELECT wpm, {q_category} FROM t_{player} ORDER by {q_category}" ) for row in player_data: if q_category == 't': data_x.append(datetime.datetime.fromtimestamp(row[1])) else: data_x.append(row[1]) row_wpm = row[0] max_point = max(max_point, row_wpm) data_y.append(row_wpm) except sqlite3.OperationalError: conn.close() await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).not_downloaded()) return conn.close() length = len(data_x) if length < 15: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).missing_information( f"`{ctx.invoked_with}` requires at least 15 races to generate a graph" )) return elif length < 7500: sma = length // 15 else: sma = 500 moving_y = [sum(data_y[0:sma]) / sma] moving_y += [sum(data_y[i - sma:i]) / sma for i in range(sma, length)] moving_x = [data_x[0]] + data_x[sma:] moving_y = reduce_list(moving_y) moving_x = reduce_list(moving_x) max_line = max(moving_y) ax = plt.subplots()[1] ax.scatter(data_x, data_y, marker='.', alpha=0.1, color='#000000') ax.plot(moving_x, moving_y, color='#FF0000') ax.set_title( f"{player}'s WPM Over {category}\n(Moving Average of {sma} Races)") if q_category == 't': ax.set_xlabel('Date') ax.set_xticks(ax.get_xticks()[::2]) formatter = mdates.DateFormatter("%b. %-d '%y") ax.xaxis.set_major_formatter(formatter) else: ax.xaxis.set_major_formatter( ticker.FuncFormatter(self.large_num_formatter)) ax.set_xlabel('Race #') if max_point > 2 * max_line: ax.set_ylim(0, 2 * max_line) ax.set_ylabel('WPM') plt.grid(True) file_name = f"WPM Over {category}.png" graph_colors = get_graph_colors(user_id) graph_color(ax, graph_colors, False) plt.savefig(file_name, facecolor=ax.figure.get_facecolor()) races_over_time_picture = discord.File(file_name, filename=file_name) await ctx.send(file=races_over_time_picture) os.remove(file_name) plt.close() return
async def 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 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 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 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 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 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 botleaderboard(self, ctx, *args): user_id = ctx.message.author.id MAIN_COLOR = get_supporter(user_id) conn = sqlite3.connect(DATABASE_PATH) c = conn.cursor() def combine_aliases(cmds_data): cmds_dict = dict() for cmd in cmds_data: try: normalized_name = normalized_commands[cmd[0]] except KeyError: normalized_name = cmd[0] try: cmds_dict[normalized_name] += cmd[1] except KeyError: cmds_dict[normalized_name] = cmd[1] return cmds_dict if ctx.invoked_with[-1] == '*': if len(args) == 0: user_count = len( c.execute( f"SELECT DISTINCT id FROM {TABLE_KEY}").fetchall()) command_count = len( c.execute(f"SELECT * FROM {TABLE_KEY}").fetchall()) conn.close() await ctx.send(embed=discord.Embed( color=discord.Color(MAIN_COLOR), description= f"**{f'{user_count:,}'}** users have used **{f'{command_count:,}'}** commands" )) return elif len(args) == 1: command = args[0].lower() if command == '*': command = 'All Commands' user_data = c.execute(f"""SELECT command, COUNT(command) FROM {TABLE_KEY} GROUP BY command""") user_data = combine_aliases(user_data) user_data = [[f"`{key}`", value] for key, value in user_data.items()] else: try: command = normalized_commands[command] aliases = [command] + get_aliases(command) user_data = [] for alias in aliases: alias_data = c.execute( f"""SELECT name, COUNT(id) FROM (SELECT * FROM {TABLE_KEY} WHERE command = ?) GROUP BY id""", (alias, )) user_data += alias_data.fetchall() user_data = combine_aliases(user_data) user_data = [[key, value] for key, value in user_data.items()] except KeyError: user_data = () conn.close() user_data = sorted(user_data, key=lambda x: x[1], reverse=True) value = '' for i, user in enumerate(user_data[:10]): value += f"{NUMBERS[i]} {user[0]} - {f'{user[1]:,}'}\n" value = value[:-1] if not value: await ctx.send( content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( f"`{command}` is not a command or has never been used") ) return embed = discord.Embed( title=f"Bot Usage Leaderboard (`{command}` command)", color=discord.Color(MAIN_COLOR), description=value) embed.set_footer(text='Since December 24, 2020') await ctx.send(embed=embed) return if len(args) > 1: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).parameters( f"{ctx.invoked_with} <discord_id>")) return if len(args) == 0: user_data = c.execute(f"""SELECT name, COUNT(id) FROM {TABLE_KEY} GROUP BY id ORDER BY COUNT(id) DESC LIMIT 10""" ).fetchall() conn.close() value = '' for i, user in enumerate(list(user_data)): value += f"{NUMBERS[i]} {user[0]} - {f'{user[1]:,}'}\n" value = value[:-1] embed = discord.Embed(title='Bot Usage Leaderboard', color=discord.Color(MAIN_COLOR), description=value) else: args = (args[0].strip('<@!').strip('>'), ) try: if len(args[0]) > 18: raise ValueError id_ = int(args[0]) if escape_sequence(args[0].lower()): raise ValueError except ValueError: await ctx.send(content=f"<@{user_id}>", embed=Error(ctx, ctx.message).incorrect_format( f"**{args[0]}** is not a valid Discord ID")) return user_data = [] users_cmd_data = c.execute( f"""SELECT command, COUNT(command), name FROM (SELECT * FROM {TABLE_KEY} WHERE ID = ?) GROUP BY command""", (id_, )).fetchall() conn.close() if users_cmd_data: name = users_cmd_data[-1][2] else: name = id_ user_data = combine_aliases(users_cmd_data) user_data = sorted([[key, value] for key, value in user_data.items()], key=lambda x: x[1], reverse=True) title = f"Bot Statistics for {name}" value, count = '', 0 for i, cmd in enumerate(user_data): count += cmd[1] if i < 10: cmds = i + 1 value += f"**{cmds}.** `{cmd[0]}` - {f'{cmd[1]:,}'}\n" embed = discord.Embed( title=title, color=discord.Color(MAIN_COLOR), description=f"**Used:** {f'{count:,}'} times") if value: embed.add_field(name=f"Top {cmds} Most Used", value=value) embed.set_footer(text='Since December 24, 2020') await ctx.send(embed=embed) return