async def whois(self, ctx, member: typing.Optional[discord.Member] = None, handle: typing.Optional[str] = None): query = Query() username, linked_username, pfp = None, None, None if handle: user = None try: user = await query.get_user(handle) except ObjectNotFound: username = None if user: handle = user.username author_id = query.get_handle_user(handle, ctx.guild.id) username = handle if author_id: member = ctx.message.guild.get_member(author_id) linked_username = member.nick or member.name pfp = member.avatar_url elif member: handle = query.get_handle(member.id, ctx.guild.id) username = member.nick or member.name if handle: linked_username = handle pfp = await query.get_pfp(handle) if linked_username: embed = discord.Embed( color=0xfcdb05, title=f'{username} is {linked_username}', ) embed.set_thumbnail(url=pfp) await ctx.send(embed=embed) elif username: embed = discord.Embed( title=f'{username} is not linked with any account here', color=0xfcdb05, ) await ctx.send(embed=embed) else: name = None if member: name = member.nick or member.name embed = discord.Embed( title=f'Nothing found on {handle or name}', color=0xfcdb05, ) await ctx.send(embed=embed)
async def recommend(self, ctx, username: typing.Optional[parse_gimme] = None, points: typing.Optional[point_range] = [1, 50], *filters): """ Recommend a problem Use surround your username with '' if it can be interpreted as a number SHORTHANDS: - adhoc - math - bf - ctf - ds - d&c - dp - geo - gt - greedy - regex - string """ filters = list(filters) query = Query() username = username or query.get_handle(ctx.author.id, ctx.guild.id) if username is None: return await ctx.send('No username provided') user = await query.get_user(username) if user is None: return await ctx.send(f'{username} does not exist on DMOJ') username = user.username filter_list = [] for filter in filters: if filter in SHORTHANDS: filter_list += SHORTHANDS[filter] else: filter_list.append(filter.title()) filters = filter_list # Get all problems that are unsolved by user and fits the filter and # point range result, problem = await gimme_common(username, points, filters) # print(result) if result is None: return await ctx.send("No problem that satisfies the filter") return await ctx.send(embed=result)
async def nogud(ctx): query = Query() gitgud_util = Gitgud_utils() username = query.get_handle(ctx.author.id, ctx.get_guild().id) if username is None: return await ctx.respond("You do not have a linked DMOJ account") current = gitgud_util.get_current(username, ctx.get_guild().id) if current is None or current.problem_id is None: return await ctx.respond("Nothing to cancel") gitgud_util.clear(username, ctx.get_guild().id) return await ctx.respond("Challenge skipped")
async def whois(ctx): member = ctx.options.member handle = ctx.options.handle query = Query() username, linked_username, pfp = None, None, None if handle: user = None try: user = await query.get_user(handle) except ObjectNotFound: username = None if user: handle = user.username author_id = query.get_handle_user(handle, ctx.get_guild().id) username = handle if author_id: member = ctx.get_guild().get_member(author_id) linked_username = member.nickname or member.username pfp = member.avatar_url elif member: handle = query.get_handle(member.id, ctx.get_guild().id) username = member.nickname or member.username if handle: linked_username = handle pfp = await query.get_pfp(handle) if linked_username: embed = hikari.Embed( color=0xFCDB05, title=escape_markdown(f"{username} is {linked_username}"), ) embed.set_thumbnail(pfp) return await ctx.respond(embed=embed) elif username: embed = hikari.Embed( title=escape_markdown(f"{username} is not linked with any account here"), color=0xFCDB05, ) return await ctx.respond(embed=embed) name = None if member: name = member.nickname or member.username embed = hikari.Embed( title=escape_markdown(f"Nothing found on {handle or name}"), color=0xFCDB05, ) await ctx.respond(embed=embed)
async def gitlog(ctx): """ Show the past gitgud history of a user """ query = Query() username = ctx.options.username username = username or query.get_handle(ctx.author.id, ctx.get_guild().id) try: user = await query.get_user(username) username = user.username except TypeError: username = None if username is None: return await ctx.respond("You have not entered a valid DMOJ handle " "or linked with a DMOJ Account") gitgud_util = Gitgud_utils() history = gitgud_util.get_all(username, ctx.get_guild().id) if len(history) == 0: embed = hikari.Embed(description="User have not completed any " "challenge") return await ctx.respond(embed=embed) # paginate pag = lightbulb.utils.EmbedPaginator() for idx, solved in enumerate(history): # problem = solved.problem_id or await query.get_problem(solved.problem_id) problem = await query.get_problem(solved.problem_id) days = (datetime.now() - solved.time).days if days == 0: days_str = "today" elif days == 1: days_str = "yesterday" else: days_str = f"{days} days ago" pag.add_line(f"[{problem.name}](https://dmoj.ca/problem/{problem.code}) " f"[+{solved.point}] ({days_str})") if idx == 100: break @pag.embed_factory() def build_embed(page_index, content): return hikari.Embed(color=0xFCDB05,).add_field( name=f"Gitgud Log for {username} " f"(page {page_index})", # Can't put total length :/ value=content, inline=True, ) navigator = nav.ButtonNavigator(pag.build_pages()) await navigator.run(ctx)
async def howgud(ctx): username = ctx.options.username query = Query() if username is None: username = query.get_handle(ctx.author.id, ctx.get_guild().id) user = await query.get_user(username) username = user.username ret = Gitgud_utils().get_point(username, ctx.get_guild().id) if ret is None: ret = 0 # TODO Add profile pic? embed = hikari.Embed( title=username, description=f"points: {ret}", color=0xFCDB05, ) return await ctx.respond(embed=embed)
async def unlink(ctx: lightbulb.Context) -> None: # TODO: Add admin ability to manually unlink query = Query() if not query.get_handle(ctx.author.id, ctx.get_guild().id): await ctx.respond("You are not linked with any user") return handle = ( session.query(Handle_DB) .filter(Handle_DB.id == ctx.author.id) .filter(Handle_DB.guild_id == ctx.get_guild().id) .first() ) session.query(User_DB).filter(User_DB.id == handle.user_id).delete() session.query(Submission_DB).filter(Submission_DB._user == handle.handle).delete() session.delete(handle) session.commit() await ctx.respond(escape_markdown(f"Unlinked you with handle {handle.handle}"))
async def howgud(self, ctx, username = None): """ Returns total amount of gitgud points """ query = Query() if username is None: username = query.get_handle(ctx.author.id, ctx.guild.id) user = await query.get_user(username) username = user.username ret = Gitgud_utils().get_point(username, ctx.guild.id) if ret is None: ret = 0 embed = discord.Embed( title=username, description=f"points: {ret}" ) return await ctx.send(embed=embed)
async def vc(self, ctx, *usernames): """Suggest a contest""" usernames = list(usernames) query = Query() if usernames == []: usernames = [query.get_handle(ctx.author.id, ctx.guild.id)] users = await asyncio.gather( *[query.get_user(username) for username in usernames]) usernames = [user.username for user in users] for i in range(len(users)): if users[i] is None: return await ctx.send(f'{usernames[i]} does not exist on DMOJ') q = session.query(Contest_DB) for user in users: # if the user has attempted any problems from the problem set sub_q = session.query(Submission_DB, func.max(Submission_DB.points))\ .filter(Submission_DB._user == user.username)\ .group_by(Submission_DB._code).subquery() sub_q = session.query(Problem_DB.code)\ .join(sub_q, Problem_DB.code == sub_q.c._code, isouter=True)\ .filter(func.ifnull(sub_q.c.points, 0) != 0) sub_q = list(map(itemgetter(0), sub_q.all())) q = q.filter(not_(Contest_DB.rankings.contains(user.username)))\ .filter(~Contest_DB.problems.any(Problem_DB.code.in_(sub_q))) if q.count() == 0: await ctx.send("Cannot find any contests which " "all users have not done") return contest = random.choice(q.all()) # When problems are private, it says there are no problems window = 'No' if contest.time_limit: window = f"{contest.time_limit/60/60} Hr" embed = discord.Embed( title=contest.name, url=f"https://dmoj.ca/contest/{contest.key}", description=f"{window} window | {len(contest.problems)} Problems", color=0xfcdb05) await ctx.send(embed=embed)
async def nogud(self, ctx): ''' Cancels any unfinished challenge ''' query = Query() gitgud_util = Gitgud_utils() username = query.get_handle(ctx.author.id, ctx.guild.id) if username is None: return await ctx.send('You do not have a linked DMOJ account') current = gitgud_util.get_current(username, ctx.guild.id) if current is None or current.problem_id is None: return await ctx.send('Nothing to cancel') gitgud_util.clear(username, ctx.guild.id) return await ctx.send('Challenge skipped')
async def postcontest(self, ctx, key): """Updates post-contest role""" await ctx.message.delete() query = Query() username = query.get_handle(ctx.author.id, ctx.guild.id) if username is None: return await ctx.send("Your account is not linked!") q = session.query(Contest_DB).filter(Contest_DB.key == key) # Clear cache if q.count(): q.delete() session.commit() try: contest = await query.get_contest(key) except ObjectNotFound: await ctx.send("Contest not found") return if contest.is_organization_private: return await ctx.send("Contest not found") role = get(ctx.guild.roles, name="postcontest " + key) if not role: return await ctx.send(f"No `postcontest {key}` role found.") for ranking in contest.rankings: if ranking['user'].lower() != username.lower(): continue endTime = datetime.strptime(ranking['end_time'], '%Y-%m-%dT%H:%M:%S%z') if endTime > datetime.now(timezone.utc).astimezone(): return await ctx.send("Your window is not done.") else: try: await ctx.author.add_roles(role) except discord.Forbidden: return await ctx.send("No permission to assign the role.") return await ctx.send("You've been added to post contest.") return await ctx.send("You haven't joined the contest yet.")
async def gitlog(self, ctx, username=None): """ Show the past gitgud history of a user """ query = Query() username = username or query.get_handle(ctx.author.id, ctx.guild.id) try: user = await query.get_user(username) username = user.username except TypeError: username = None if username is None: return await ctx.send("You have not entered a valid DMOJ handle " "or linked with a DMOJ Account") gitgud_util = Gitgud_utils() history = gitgud_util.get_all(username, ctx.guild.id) if len(history) == 0: embed = discord.Embed(description="User have not completed any" "challenge") return await ctx.send(embed=embed) # paginate count = 0 page_cnt = min(10, len(history) // 10 + bool(len(history) % 10)) embeds = [] content = "" paginator = Pagination.CustomEmbedPaginator(ctx, timeout=60, remove_reactions=True) paginator.add_reaction('⏮️', "first") paginator.add_reaction('⏪', "back") paginator.add_reaction('⏩', "next") paginator.add_reaction('⏭️', "last") for solved in history: # print(solved.problem_id) problem = await query.get_problem(solved.problem_id) days = (datetime.now() - solved.time).days if days == 0: days_str = "today" elif days == 1: days_str = "yesterday" else: days_str = f"{days} days ago" content += f"[{problem.name}](https://dmoj.ca/problem/{problem.code}) " \ f"[+{solved.point}] ({days_str})\n" count += 1 if count % 10 == 0: embed = discord.Embed(color=0xfcdb05, ) embed.add_field(name=f"Gitgud Log for {username} " f"(page {count//10}/{page_cnt})", value=content, inline=True) embeds.append(embed) content = "" if count == 100: break if count % 10 != 0: embed = discord.Embed() embed.add_field(name=f"Gitlog for {username} " f"(page {count//10 + 1}/{page_cnt})", value=content, inline=True) embeds.append(embed) return await paginator.run(embeds)
async def type(self, ctx, as_percent: typing.Optional[as_percentage] = True, graph: typing.Optional[graph_type] = 'radar', *usernames): """Graph problems solved by popular problem types""" # This is aids, pls fix usernames = list(usernames) query = Query() if usernames == []: usernames = [query.get_handle(ctx.author.id, ctx.guild.id)] users = await asyncio.gather( *[query.get_user(username) for username in usernames]) for i in range(len(users)): if users[i] is None: return await ctx.send(f'{usernames[i]} does not exist on DMOJ') if len(users) > 6: return await ctx.send('Too many users given, max 6') usernames = [data.username for data in users] important_types = [['Data Structures'], ['Dynamic Programming'], ['Graph Theory'], ['String Algorithms'], [ 'Advanced Math', 'Geometry', 'Intermediate Math', 'Simple Math' ], ['Ad Hoc'], ['Greedy Algorithms']] labels = [ 'Data Structures', 'Dynamic Programming', 'Graph Theory', 'String Algorithms', 'Math', 'Ad Hoc', 'Greedy Algorithms' ] data = {} data['group'] = [] for label in labels: data[label] = [] for username in usernames: data['group'].append(username) def calculate_points(points: int): p = 0 for i in range(min(100, len(points))): p += (0.95**i) * points[i] return p def to_points(problem): return problem.points max_percentage = 0 not_cached = [] for username in usernames: q = session.query(Submission_DB)\ .filter(Submission_DB._user == username) if q.count() == 0: not_cached.append(username) for i, types in enumerate(important_types): total_problems = await query.get_problems(_type=types, cached=True) total_points = list(map(to_points, total_problems)) total_points.sort(reverse=True) total_points = calculate_points(total_points) for username in usernames: problems = query.get_attempted_problems(username, types) points = list(map(to_points, problems)) points.sort(reverse=True) points = calculate_points(points) if as_percent: percentage = 100 * points / total_points else: percentage = points max_percentage = max(max_percentage, percentage) data[labels[i]].append(percentage) print(data) if len(not_cached): await ctx.send(f"`{', '.join(not_cached)} do not have any cached " f"submissions. Please use +cache [username]`") if graph == 'radar': plot_type_radar(data, as_percent, max_percentage) elif graph == 'bar': plot_type_bar(data, as_percent) with open('./graphs/plot.png', 'rb') as file: file = discord.File(io.BytesIO(file.read()), filename='plot.png') embed = discord.Embed( title='Problem types solved', color=0xfcdb05, ) embed.set_image(url='attachment://plot.png') return await ctx.send(embed=embed, file=file)
async def points(self, ctx, *usernames): """Plot point progression""" usernames = list(usernames) query = Query() if usernames == []: usernames = [query.get_handle(ctx.author.id, ctx.guild.id)] users = await asyncio.gather( *[query.get_user(username) for username in usernames]) usernames = [user.username for user in users] for i in range(len(users)): if users[i] is None: return await ctx.send(f'{usernames[i]} does not exist on DMOJ') if len(users) > 10: return await ctx.send('Too many users given, max 10') total_data = {} not_cached = [] for username in usernames: q = session.query(Submission_DB)\ .options(orm.joinedload('problem'))\ .join(User_DB, User_DB.username == Submission_DB._user, aliased=True)\ .filter(User_DB.username == username)\ .order_by(Submission_DB.date) def calculate_points(points, fully_solved): b = 150 * (1 - 0.997**fully_solved) p = 0 for i in range(min(100, len(points))): p += (0.95**i) * points[i] return b + p submissions = q.all() if len(submissions) == 0: not_cached.append(username) problems_ACed = dict() code_to_points = dict() points_arr = [] data_to_plot = {} # O(N^2logN) :blobcreep: for submission in submissions: code = submission.problem[0].code points = submission.points result = submission.result if points is not None: if result == 'AC': problems_ACed[code] = 1 if code not in code_to_points: # log N search, N insert code_to_points[code] = points bisect.insort(points_arr, points) elif points > code_to_points[code]: # N remove, log N search, N insert points_arr.remove(code_to_points[code]) code_to_points[code] = points bisect.insort(points_arr, points) cur_points = calculate_points(points_arr[::-1], len(problems_ACed)) data_to_plot[submission.date] = cur_points total_data[username] = data_to_plot if len(not_cached): await ctx.send(f"`{', '.join(not_cached)} do not have any cached " f"submissions. Please use +cache [username]`") plot_points(total_data) with open('./graphs/plot.png', 'rb') as file: file = discord.File(io.BytesIO(file.read()), filename='plot.png') embed = discord.Embed( title='Point Progression', color=0xfcdb05, ) embed.set_image(url='attachment://plot.png') return await ctx.send(embed=embed, file=file)
async def predict(self, ctx, username: typing.Optional[str_not_int] = None, amounts: commands.Greedy[int] = []): """Predict total points after solving N pointer problem(s) Use surround your username with '' if it can be interpreted as a number """ query = Query() username = username or query.get_handle(ctx.author.id, ctx.guild.id) if username is None and len(amounts) > 0: username = str([0]) amounts.pop(0) if amounts == []: return await ctx.send('No points given!') if username is None: return amounts = amounts[:10] user = await query.get_user(username) if user is None: return await ctx.send(f'{username} does not exist on DMOJ') username = user.username q = session.query(Submission_DB).options(orm.joinedload('problem'))\ .join(User_DB, User_DB.username == Submission_DB._user, aliased=True)\ .filter(User_DB.username == user.username) if q.count(): submissions = q.all() msg = None else: await ctx.send('No submissions cached, ' 'Please use +cache to get new submissions') return problems_ACed = dict() code_to_points = dict() for submission in submissions: code = submission.problem[0].code points = submission.points result = submission.result if points is not None: if result == 'AC': problems_ACed[code] = 1 if code not in code_to_points: code_to_points[code] = points elif points > code_to_points[code]: code_to_points[code] = points fully_solved = len(problems_ACed) points = list(code_to_points.values()) points.sort(reverse=True) embed = discord.Embed( title=f'Point prediction for {username}', description='Current points: %.2fp' % calculate_points(points, fully_solved), color=0xfcdb05, ) embed.set_thumbnail(url=await query.get_pfp(username)) for predict_val in amounts: points.append(int(predict_val)) fully_solved += 1 points.sort(reverse=True) updated_points = calculate_points(points, fully_solved) embed.add_field( name="Solve another %sp" % predict_val, value="Total points: %.2fp" % updated_points, inline=False, ) if msg: await msg.delete() await ctx.send(embed=embed) return
async def postcontest(ctx): """Updates post-contest role""" key = ctx.options.key option = ctx.options.option try: await ctx.event.message.delete() except hikari.ForbiddenError as e: logger.info(e) pass def has_admin_perms(ctx): return any(role.name in ADMIN_ROLES for role in ctx.member.get_roles()) update_all = option == "+all" and has_admin_perms(ctx) query = Query() if update_all: usernames = session.query(Handle_DB).filter(Handle_DB.guild_id == ctx.get_guild().id).all() else: username = query.get_handle(ctx.author.id, ctx.get_guild().id) if username is None: return await ctx.respond("Your account is not linked!") q = session.query(Contest_DB).filter(Contest_DB.key == key) # Clear cache if q.count(): q.delete() session.commit() try: contest = await query.get_contest(key) except ObjectNotFound: await ctx.respond("Contest not found") return if contest.is_organization_private: return await ctx.respond("Contest not found") rc = lightbulb.RoleConverter(ctx) for role_id in ctx.get_guild().get_roles(): _role = await rc.convert(str(role_id)) if _role.name == "postcontest " + key: role = _role break else: return await ctx.respond(f"No `postcontest {key}` role found.") if update_all: participants = set() for ranking in contest.rankings: endTime = datetime.strptime(ranking["end_time"], "%Y-%m-%dT%H:%M:%S%z") if endTime < datetime.now(timezone.utc).astimezone(): participants.add(ranking["user"]) for user in usernames: if user.handle in participants: await ctx.get_guild().get_member(user.id).add_role(role) return await ctx.respond("Updated post contest for " + key) for ranking in contest.rankings: if ranking["user"].lower() != username.lower(): continue endTime = datetime.strptime(ranking["end_time"], "%Y-%m-%dT%H:%M:%S%z") if endTime > datetime.now(timezone.utc).astimezone(): return await ctx.respond("Your window is not done") else: await ctx.member.add_role(role) return await ctx.respond("You've been added to post contest") return await ctx.respond("You haven't joined the contest yet")
async def userinfo(ctx): """Show user profile and latest submissions Use surround your username with '' if it can be interpreted as a number """ username = ctx.options.username amount = ctx.options.amount query = Query() username = username or query.get_handle(ctx.author.id, ctx.get_guild().id) # If user is not found in db if username is None: username = str(amount) amount = None if username is None: return await ctx.respond("No username given!") username = username.replace("'", "") if amount is not None: amount = min(amount, 8) if amount < 1: return await ctx.respond("Please request at least one submission") try: user = await query.get_user(username) except ObjectNotFound: return await ctx.respond(f"{username} does not exist on DMOJ") username = user.username def is_rated(contest): return 1 if contest.is_rated else 0 discordHandle = ctx.get_guild().get_member( query.get_handle_user(username, ctx.get_guild().id)) if discordHandle: discordHandle = discordHandle.nickname or discordHandle.username else: discordHandle = "Unknown" if user.rating is None: color = 0xFEFEFE # it breaks when I set it to white elif user.rating >= 3000: color = 0x000000 elif user.rating >= 2700: color = 0xA00000 elif user.rating >= 2400: color = 0xEE0000 elif user.rating >= 1900: color = 0xFFB100 elif user.rating >= 1600: color = 0x800080 elif user.rating >= 1300: color = 0x0000FF elif user.rating >= 1000: color = 0x00A900 elif user.rating >= 0: color = 0x999999 else: color = 0x000000 description = f"Discord name: {discordHandle}" embed = hikari.Embed( title=username, url=f"https://dmoj.ca/user/{username}", description=description, color=color, # rating color ) embed.set_thumbnail(await query.get_pfp(username)) embed.add_field(name="Points", value=str(round(user.performance_points)) + "/" + str(round(user.points)), inline=True) embed.add_field(name="Problems Solved", value=user.problem_count, inline=True) embed.add_field(name="Rating", value=str(user.rating) + "/" + str(user.max_rating), inline=True) embed.add_field(name="Contests Written", value=sum(map(is_rated, user.contests)), inline=True) await ctx.respond(embed=embed) if amount is None: return submissions = await query.get_latest_submissions(username, amount) embed = hikari.Embed(title=f"{username}'s latest submissions", color=0xFFFF00) for submission in submissions: problem = submission.problem[0] if problem.points is not None: points = str(int(problem.points)) + "p" if problem.partial: points += "p" else: points = "???" true_short_name = submission.language[0].short_name if true_short_name == "": # wtf dmoj true_short_name = submission.language[0].key embed.add_field( name="%s / %s" % (str(submission.score_num), str(submission.score_denom)), value="%s | %s" % (submission.result, true_short_name), inline=True, ) embed.add_field( name="%s (%s)" % (submission.problem[0].name, points), value="%s | [Problem](https://dmoj.ca/problem/%s)" % ( submission.date.astimezone(TZ).strftime("%b. %d, %Y, %I:%M %p") .replace("AM", "a.m.").replace("PM", "p.m."), submission.problem[0].code, ), # Jan. 13, 2021, 12:17 a.m. # %b. %d, %Y, %I:%M %p inline=True, ) try: embed.add_field( name="%.2fs" % submission.time, value="%s" % submission.memory_str, inline=True, ) except TypeError: embed.add_field( name="---", value="%s" % submission.memory_str, inline=True, ) await ctx.respond(embed=embed) return None
async def type(ctx): """Graph problems solved by popular problem types""" # TODO: This is aids, pls fix usernames = ctx.options.usernames graph_type = ctx.options.graph_type as_percent = ctx.options.as_percent query = Query() if usernames == []: usernames = [query.get_handle(ctx.author.id, ctx.get_guild().id)] try: users = await asyncio.gather(*[query.get_user(username) for username in usernames]) except ObjectNotFound: return await ctx.respond("User not found") for i in range(len(users)): if users[i] is None: return await ctx.respond(f"{usernames[i]} does not exist on DMOJ") if len(users) > 6: return await ctx.respond("Too many users given, max 6") usernames = [data.username for data in users] important_types = [ ["Data Structures"], ["Dynamic Programming"], ["Graph Theory"], ["String Algorithms"], ["Advanced Math", "Geometry", "Intermediate Math", "Simple Math"], ["Ad Hoc"], ["Greedy Algorithms"], ] labels = [ "Data Structures", "Dynamic Programming", "Graph Theory", "String Algorithms", "Math", "Ad Hoc", "Greedy Algorithms", ] data = {} data["group"] = [] for label in labels: data[label] = [] for username in usernames: data["group"].append(username) def calculate_partial_points(points: int): p = 0 for i in range(min(100, len(points))): p += (0.95**i) * points[i] return p max_percentage = 0 for username in usernames: q = session.query(Submission_DB).filter(Submission_DB._user == username) if q.count() == 0: await ctx.respond(f"`{username}` does not have any cached submissions, caching now") await query.get_submissions(username) for i, types in enumerate(important_types): total_problems = await query.get_problems(_type=types, cached=True) total_points = list(map(attrgetter("points"), total_problems)) total_points.sort(reverse=True) total_points = calculate_partial_points(total_points) for username in usernames: points = query.get_attempted_problems(username, types) points.sort(reverse=True) points = calculate_partial_points(points) if as_percent: percentage = 100 * points / total_points else: percentage = points max_percentage = max(max_percentage, percentage) data[labels[i]].append(percentage) logger.debug("plot type data: %s", data) if graph_type == "radar": plot_type_radar(data, as_percent, max_percentage) elif graph_type == "bar": plot_type_bar(data, as_percent) embed = hikari.Embed( title="Problem types solved", color=0xFCDB05, ) with open("./graphs/plot.png", "rb") as file: embed.set_image(hikari.Bytes(file.read(), "plot.png")) return await ctx.respond(embed=embed)
async def vc(ctx): usernames = ctx.options.usernames print(usernames) query = Query() if usernames == []: username = query.get_handle(ctx.author.id, ctx.get_guild().id) if username: usernames = [username] users = await asyncio.gather( *[query.get_user(username) for username in usernames]) usernames = [user.username for user in users] for i in range(len(users)): if users[i] is None: return await ctx.respond(f"{usernames[i]} does not exist on DMOJ") q = session.query(Contest_DB) for user in users: # if the user has attempted any problems from the problem set sub_q = (session.query(Submission_DB, func.max( Submission_DB.points)).filter( Submission_DB._user == user.username).group_by( Submission_DB._code).subquery()) sub_q = (session.query(Problem_DB.code).join( sub_q, Problem_DB.code == sub_q.c._code, isouter=True).filter(func.ifnull(sub_q.c.points, 0) != 0)) sub_q = list(map(itemgetter(0), sub_q.all())) q = (q.filter(not_(Contest_DB.rankings.contains( user.username))).filter( ~Contest_DB.problems.any(Problem_DB.code.in_(sub_q))).filter( Contest_DB.is_private == 0).filter( Contest_DB.is_organization_private == 0)) if q.count() == 0: await ctx.respond("Cannot find any contests which " "all users have not done") return contests = q.all() while True: contest = random.choice(contests) try: contest = await query.get_contest(contest.key, cached=False) break except ObjectNotFound: pass # When problems are private, it says there are no problems window = "No" is_rated = "Not Rated" if contest.time_limit: window = f"{round(contest.time_limit/60/60, 2)} Hr" if contest.is_rated: is_rated = "Rated" embed = hikari.Embed( title=contest.name, url=f"https://dmoj.ca/contest/{contest.key}", description= f"{window} window | {len(contest.problems)} Problems | {is_rated}", color=0xFCDB05, ) await ctx.respond(embed=embed)
async def rating(self, ctx, *args, perf=False): '''Plot rating progression''' usernames = [] peak = False raw = False for arg in args: if arg in ['+peak', '+max']: peak = True elif arg == '+raw': raw = True elif arg == '+perf': perf = True else: usernames.append(arg) query = Query() if usernames == []: usernames = [query.get_handle(ctx.author.id, ctx.guild.id)] try: users = await asyncio.gather( *[query.get_user(username) for username in usernames]) except ObjectNotFound: return await ctx.send('User not found') usernames = [user.username for user in users] for i in range(len(users)): if users[i] is None: return await ctx.send(f'{usernames[i]} does not exist on DMOJ') if len(users) > 10: return await ctx.send('Too many users given, max 10') cond = [Contest_DB.rankings.contains(user.username) for user in users] q = session.query(Contest_DB).filter(or_(*cond))\ .filter(Contest_DB.is_rated == 1) contests = q.all() ratingVals = {} for (i, user) in enumerate(users): prevMax = -1e9 for contest in user._contests: val = 0 if perf: val = contest['performance'] elif raw: val = contest['raw_rating'] else: val = contest['rating'] if not peak or val and prevMax < val: prevMax = val ratingVals[contest['key'] + '.' + user.username] = val data = {} data['users'] = [user.username for user in users] for contest in contests: data[contest.end_time] = [] for user in users: if contest.key + '.' + user.username in ratingVals: data[contest.end_time].append( ratingVals[contest.key + '.' + user.username]) else: data[contest.end_time].append(None) plot_rating(data) with open('./graphs/plot.png', 'rb') as file: file = discord.File(io.BytesIO(file.read()), filename='plot.png') embed = discord.Embed( title='Contest Rating', color=0xfcdb05, ) embed.set_image(url='attachment://plot.png') return await ctx.send(embed=embed, file=file)
async def solved(self, ctx, *args): '''Plot problems solved over time''' usernames = [] minDate = datetime.min maxDate = datetime.max for arg in args: if arg.startswith('d>='): minDate = arg[3:] minDate = datetime(int(minDate[4:]), int(minDate[2:4]), int(minDate[0:2])) elif arg.startswith('d<='): maxDate = arg[3:] maxDate = datetime(int(maxDate[4:]), int(maxDate[2:4]), int(maxDate[0:2])) else: usernames.append(arg) query = Query() if usernames == []: usernames = [query.get_handle(ctx.author.id, ctx.guild.id)] try: users = await asyncio.gather( *[query.get_user(username) for username in usernames]) except ObjectNotFound: return await ctx.send('User not found') usernames = [user.username for user in users] for i in range(len(users)): if users[i] is None: return await ctx.send(f'{usernames[i]} does not exist on DMOJ') if len(users) > 10: return await ctx.send('Too many users given, max 10') total_data = {} for username in usernames: await query.get_submissions(username) q = session.query(Submission_DB)\ .filter(Submission_DB._user == username) if q.count() == 0: await ctx.send( f'`{username}` does not have any cached submissions, caching now' ) await query.get_submissions(username) q = session.query(func.min(Submission_DB.date))\ .join(Problem_DB, Problem_DB.code == Submission_DB._code)\ .filter(Submission_DB._user == username)\ .filter(Submission_DB.points == Problem_DB.points)\ .group_by(Submission_DB._code) dates = list(map(itemgetter(0), q.all())) dates.sort() data_to_plot = {} cnt = 0 for date in dates: if minDate <= date and date <= maxDate: cnt += 1 data_to_plot[date] = cnt total_data[username] = data_to_plot plot_solved(total_data) with open('./graphs/plot.png', 'rb') as file: file = discord.File(io.BytesIO(file.read()), filename='plot.png') embed = discord.Embed( title='Problems Solved', color=0xfcdb05, ) embed.set_image(url='attachment://plot.png') return await ctx.send(embed=embed, file=file)
async def _set(ctx): """Manually link two accounts together""" # TODO: I don't like the bot spamming replies to itself, figure out a way to lessen that member = ctx.options.member username = ctx.options.handle query = Query() if username != "+remove": try: user = await query.get_user(username) if user is None: raise ObjectNotFound() except ObjectNotFound: await ctx.respond(escape_markdown(f"{username} does not exist on dmoj")) return username = user.username handle = query.get_handle(member.id, ctx.get_guild().id) if handle == username: return await ctx.respond(escape_markdown(f"{member.display_name} is already linked with {handle}")) if handle: handle = ( session.query(Handle_DB) .filter(Handle_DB.id == member.id) .filter(Handle_DB.guild_id == ctx.get_guild().id) .first() ) session.delete(handle) session.commit() await ctx.respond(escape_markdown(f"Unlinked {member.display_name} with handle {handle.handle}")) if username == "+remove": return if query.get_handle_user(username, ctx.get_guild().id): await ctx.respond("This handle is already linked with another user") return handle = Handle_DB() handle.id = member.id handle.handle = username handle.user_id = user.id handle.guild_id = ctx.get_guild().id session.add(handle) session.commit() await ctx.respond(escape_markdown(f"Linked {member.display_name} with {username}")) rank_to_role = {} rc = lightbulb.RoleConverter(ctx) for role_id in ctx.get_guild().get_roles(): role = await rc.convert(str(role_id)) if role.name in RANKS: rank_to_role[role.name] = role rank = rating_to_rank(user.rating) if rank in rank_to_role: await _update_rank(member, rank_to_role[rank], "Dmoj account linked") else: await ctx.respond("You are missing the `" + rank.name + "` role")
async def gotgud(self, ctx): """ Confirm completion of gitgud challenge """ query = Query() gitgud_util = Gitgud_utils() username = query.get_handle(ctx.author.id, ctx.guild.id) if username is None: return await ctx.send("You are not linked with a DMOJ Account") user = await query.get_user(username) current = gitgud_util.get_current(username, ctx.guild.id) closest = -1000 for key in RATING_TO_POINT: if abs(key - user.rating) <= abs(closest - user.rating): closest = key # convert rating to point and get difference rating_point = RATING_TO_POINT[closest] if current is None or current.problem_id is None: return await ctx.send("No pending challenges") # check if user is scamming the bot :monkey: if gitgud_util.has_solved(username, current.problem_id): # get closest rating closest = -1000 for key in RATING_TO_POINT: if abs(key - user.rating) <= abs(closest - user.rating): closest = key # convert rating to point and get difference rating_point = RATING_TO_POINT[closest] point_diff = (POINT_VALUES.index(current.point) - POINT_VALUES.index(rating_point)) print(point_diff) point = 10 + 2 * (point_diff) point = max(point, 0) gitgud_util.insert(username, ctx.guild.id, point, current.problem_id, datetime.now()) gitgud_util.clear(username, ctx.guild.id) completion_time = datetime.now() - current.time # convert from timedelta to readable string ret = "" cnt = 0 if completion_time.days // 365 != 0: ret += f" {completion_time.days // 365} years" cnt += 1 if completion_time.days % 365 != 0: ret += f" {completion_time.days % 365} days" cnt += 1 if completion_time.seconds // 3600 != 0: ret += f" {completion_time.seconds // 3600} hours" cnt += 1 if cnt < 3 and completion_time.seconds % 3600 // 60 != 0: ret += f" {completion_time.seconds % 3600 // 60} minutes" cnt += 1 if cnt < 3 and completion_time.seconds % 60 != 0: ret += f" {completion_time.seconds % 60} seconds" return await ctx.send(f"Challenge took{ret}. " f"{current.handle} gained {point} points") else: return await ctx.send("You have not completed the challenge")
async def userinfo(self, ctx, username: typing.Optional[str_not_int] = None, amount: typing.Optional[int] = None): """Show user profile and latest submissions Use surround your username with '' if it can be interpreted as a number """ query = Query() username = username or query.get_handle(ctx.author.id, ctx.guild.id) # If user is not found in db if username is None: username = str(amount) amount = None if username is None: return if amount is not None: amount = min(amount, 8) if amount < 1: return await ctx.send('Please request at least one submission') try: user = await query.get_user(username) except ObjectNotFound: return await ctx.send(f'{username} does not exist on DMOJ') username = user.username def is_rated(contest): return 1 if contest.is_rated else 0 discordHandle = ctx.message.guild.get_member(query.get_handle_user(username, ctx.guild.id)) if discordHandle: discordHandle = discordHandle.nick or discordHandle.name else: discordHandle = "Unknown" if user.rating is None: color = 0xfefefe # it breaks when I set it to white elif user.rating >= 3000: color = 0x000000 elif user.rating >= 2600: color = 0xa00000 elif user.rating >= 2200: color = 0xee0000 elif user.rating >= 1800: color = 0xffb100 elif user.rating >= 1500: color = 0x800080 elif user.rating >= 1200: color = 0x0000ff elif user.rating >= 1000: color = 0x00a900 elif user.rating >= 0: color = 0x999999 else: color = 0x000000 description = f'Discord name: {discordHandle}' embed = discord.Embed( title=username, url=f'https://dmoj.ca/user/{username}', description=description, color=color, # rating color ) embed.set_thumbnail(url=await query.get_pfp(username)) embed.add_field( name="Points", value=str(round(user.performance_points)) + "/" + str(round(user.points)), inline=True ) embed.add_field( name="Problems Solved", value=user.problem_count, inline=True ) embed.add_field( name="Rating", value=str(user.rating) + "/" + str(user.max_rating), inline=True ) embed.add_field( name="Contests Written", value=sum(map(is_rated, user.contests)), inline=True ) await ctx.send(embed=embed) if amount is None: return submissions = await query.get_latest_submissions(username, amount) embed = discord.Embed( title=f"{username}'s latest submissions", color=0xffff00 ) for submission in submissions: problem = submission.problem[0] if problem.points is not None: points = str(int(problem.points)) + 'p' if problem.partial: points += 'p' else: points = '???' true_short_name = submission.language[0].short_name if true_short_name == '': # wtf dmoj true_short_name = submission.language[0].key embed.add_field( name="%s / %s" % (str(submission.score_num), str(submission.score_denom)), value="%s | %s" % (submission.result, true_short_name), inline=True ) embed.add_field( name="%s (%s)" % (submission.problem[0].name, points), value="%s | [Problem](https://dmoj.ca/problem/%s)" % (submission.date.astimezone(TZ). strftime("%b. %d, %Y, %I:%M %p"). replace('AM', 'a.m.'). replace('PM', 'p.m.'), submission.problem[0].code), # Jan. 13, 2021, 12:17 a.m. # %b. %d, %Y, %I:%M %p inline=True ) try: embed.add_field( name="%.2fs" % submission.time, value="%s" % submission.memory_str, inline=True, ) except TypeError: embed.add_field( name="---", value="%s" % submission.memory_str, inline=True, ) await ctx.send(embed=embed) return None
async def gitgud(self, ctx, points: typing.Optional[point_range], *filters): """ Recommend a problem and gain point upon completion SHORTHANDS: - adhoc - math - bf - ctf - ds - d&c - dp - geo - gt - greedy - regex - string """ filters = list(filters) query = Query() gitgud_util = Gitgud_utils() # get the user's dmoj handle username = query.get_handle(ctx.author.id, ctx.guild.id) # user = await query.get_user(username) if username is None: return await ctx.send("You are not linked to a DMOJ Account. " "Please link your account before continuing") user = await query.get_user(username) if points is None: points = [0, 0] closest = -1000 for key in RATING_TO_POINT: if abs(key - user.rating) <= abs(closest - user.rating): closest = key points[0] = RATING_TO_POINT[closest] points[1] = points[0] # return if the user haven't finished the previous problem current = gitgud_util.get_current(username, ctx.guild.id) if current is not None and current.problem_id is not None: if not gitgud_util.has_solved(username, current.problem_id): # User has a current problem unsolved problem = await query.get_problem(current.problem_id) embed = discord.Embed( description=f"You currently have an uncompleted " f"challenge, [{problem.name}]" f"(https://dmoj.ca/problem/{problem.code})", color=0xfcdb05, ) return await ctx.send(embed=embed) filter_list = [] for filter in filters: if filter in SHORTHANDS: filter_list.append(SHORTHANDS[filter]) filters = filter_list embed, problem = await gimme_common(username, points, filters) if embed is None: return await ctx.send("No problems that satisfies the filter") gitgud_util.bind(username, ctx.guild.id, problem.code, problem.points, datetime.now()) embed.description = "Points: %s\nProblem Types ||%s||" % \ (problem.points, ', '.join(problem.types)) return await ctx.send(embed=embed)
async def user(self, ctx, username: typing.Optional[str_not_int] = None, amount: typing.Optional[int] = None): """Show user profile and latest submissions Use surround your username with '' if it can be interpreted as a number """ query = Query() username = username or query.get_handle(ctx.author.id, ctx.guild.id) # If user is not found in db if username is None: username = str(amount) amount = None if username is None: return if amount is not None: amount = min(amount, 8) if amount < 1: return await ctx.send('Please request at least one submission') try: user = await query.get_user(username) except ObjectNotFound: return await ctx.send(f'{username} does not exist on DMOJ') username = user.username def is_rated(contest): return 1 if contest.is_rated else 0 description = 'Calculated points: %.2f' % user.performance_points embed = discord.Embed( title=username, url=f'https://dmoj.ca/user/{username}', description=description, color=0xfcdb05, ) embed.set_thumbnail(url=await query.get_pfp(username)) embed.add_field( name="Rank by points", value=await query.get_placement(username), inline=False ) embed.add_field( name="Problems Solved", value=user.problem_count, inline=False ) embed.add_field( name="Rating", value=user.rating, inline=True ) embed.add_field( name="Contests Written", value=sum(map(is_rated, user.contests)), inline=True ) await ctx.send(embed=embed) if amount is None: return submissions = await query.get_latest_submissions(username, amount) embed = discord.Embed( title=f"{username}'s latest submissions", color=0xfcdb05 ) for submission in submissions: problem = submission.problem[0] if problem.points is not None: points = str(int(problem.points)) + 'p' if problem.partial: points += 'p' else: points = '???' true_short_name = submission.language[0].short_name if true_short_name == '': # wtf dmoj true_short_name = submission.language[0].key embed.add_field( name="%s / %s" % (str(submission.score_num), str(submission.score_denom)), value="%s | %s" % (submission.result, true_short_name), inline=True ) embed.add_field( name="%s (%s)" % (submission.problem[0].name, points), value="%s | [Problem](https://dmoj.ca/problem/%s)" % (submission.date.astimezone(TZ). strftime("%b. %d, %Y, %I:%M %p"). replace('AM', 'a.m.'). replace('PM', 'p.m.'), submission.problem[0].code), # Jan. 13, 2021, 12:17 a.m. # %b. %d, %Y, %I:%M %p inline=True ) try: embed.add_field( name="%.2fs" % submission.time, value="%s" % submission.memory_str, inline=True, ) except TypeError: embed.add_field( name="---", value="%s" % submission.memory_str, inline=True, ) await ctx.send(embed=embed) return None
async def predict(ctx): username = ctx.options.username amounts = ctx.options.point_vals query = Query() username = username or query.get_handle(ctx.author.id, ctx.get_guild().id) if username is None and len(amounts) > 0: username = str([0]) amounts.pop(0) if amounts == []: return await ctx.respond("No points given!") if username is None: return await ctx.respond("No username given!") username = username.replace("'", "") amounts = amounts[:10] user = await query.get_user(username) if user is None: return await ctx.respond(f"{username} does not exist on DMOJ") username = user.username q = (session.query(Submission_DB).options(orm.joinedload("problem")).join( User_DB, User_DB.username == Submission_DB._user, aliased=True).filter(User_DB.username == user.username)) if q.count(): submissions = q.all() msg = None else: await ctx.respond("No submissions cached, " "Please use +cache or /cache to get new submissions") return problems_ACed = dict() code_to_points = dict() for submission in submissions: code = submission.problem[0].code points = submission.points result = submission.result if points is not None: if result == "AC": problems_ACed[code] = 1 if code not in code_to_points: code_to_points[code] = points elif points > code_to_points[code]: code_to_points[code] = points fully_solved = len(problems_ACed) points = list(code_to_points.values()) points.sort(reverse=True) embed = hikari.Embed( title=f"Point prediction for {username}", description="Current points: %.2fp" % calculate_points(points, fully_solved), color=0xFCDB05, ) embed.set_thumbnail(await query.get_pfp(username)) for predict_val in amounts: points.append(int(predict_val)) fully_solved += 1 points.sort(reverse=True) updated_points = calculate_points(points, fully_solved) embed.add_field( name="Solve another %sp" % predict_val, value="Total points: %.2fp" % updated_points, inline=False, ) if msg: await msg.delete() await ctx.respond(embed=embed) return
async def postcontest(self, ctx, key, option=''): '''Updates post-contest role''' def has_admin_perms(ctx): return any( get(ctx.guild.roles, name=role) in ctx.author.roles for role in ADMIN_ROLES) update_all = option == '+all' and has_admin_perms(ctx) query = Query() if update_all: usernames = session.query(Handle_DB).filter( Handle_DB.guild_id == ctx.guild.id).all() else: username = query.get_handle(ctx.author.id, ctx.guild.id) if username is None: return await ctx.send('Your account is not linked!') q = session.query(Contest_DB).filter(Contest_DB.key == key) # Clear cache if q.count(): q.delete() session.commit() try: contest = await query.get_contest(key) except ObjectNotFound: await ctx.send('Contest not found') return if contest.is_organization_private: return await ctx.send('Contest not found') role = get(ctx.guild.roles, name='postcontest ' + key) if not role: return await ctx.send(f'No `postcontest {key}` role found.') if update_all: participants = set() for ranking in contest.rankings: endTime = datetime.strptime(ranking['end_time'], '%Y-%m-%dT%H:%M:%S%z') if endTime < datetime.now(timezone.utc).astimezone(): participants.add(ranking['user']) for user in usernames: if user.handle in participants: try: await ctx.guild.get_member(user.id).add_roles(role) except discord.Forbidden: return await ctx.send( 'No permission to assign the role') except AttributeError: pass return await ctx.send('Updated post contest for ' + key) for ranking in contest.rankings: if ranking['user'].lower() != username.lower(): continue endTime = datetime.strptime(ranking['end_time'], '%Y-%m-%dT%H:%M:%S%z') if endTime > datetime.now(timezone.utc).astimezone(): return await ctx.send('Your window is not done') else: try: await ctx.author.add_roles(role) except discord.Forbidden: return await ctx.send('No permission to assign the role') return await ctx.send('You\'ve been added to post contest') return await ctx.send('You haven\'t joined the contest yet')
async def user(ctx): # TODO Optimize the two calls to /user username = ctx.options.username amount = ctx.options.amount query = Query() username = username or query.get_handle(ctx.author.id, ctx.get_guild().id) # If user is not found in db if username is None: username = str(amount) amount = None if username is None: return await ctx.respond("No username given!") username = username.replace("'", "") if amount is not None: amount = min(amount, 8) if amount < 1: return await ctx.respond("Please request at least one submission") try: user = await query.get_user(username) except ObjectNotFound: return await ctx.respond(f"{username} does not exist on DMOJ") username = user.username def is_rated(contest): return 1 if contest.is_rated else 0 description = "Calculated points: %.2f" % user.performance_points embed = hikari.Embed( title=username, url=f"https://dmoj.ca/user/{username}", description=description, color=0xFCDB05, ) embed.set_thumbnail(await query.get_pfp(username)) embed.add_field(name="Rank by points", value=await query.get_placement(username), inline=False) embed.add_field(name="Problems Solved", value=user.problem_count, inline=False) embed.add_field(name="Rating", value=user.rating, inline=True) embed.add_field(name="Contests Written", value=sum(map(is_rated, user.contests)), inline=True) await ctx.respond(embed=embed) if amount is None: return submissions = await query.get_latest_submissions(username, amount) embed = hikari.Embed(title=f"{username}'s latest submissions", color=0xFCDB05) for submission in submissions: problem = submission.problem[0] if problem.points is not None: points = str(int(problem.points)) + "p" if problem.partial: points += "p" else: points = "???" true_short_name = submission.language[0].short_name if true_short_name == "": # wtf dmoj true_short_name = submission.language[0].key embed.add_field( name="%s / %s" % (str(submission.score_num), str(submission.score_denom)), value="%s | %s" % (submission.result, true_short_name), inline=True, ) embed.add_field( name="%s (%s)" % (submission.problem[0].name, points), value="%s | [Problem](https://dmoj.ca/problem/%s)" % ( submission.date.astimezone(TZ).strftime("%b. %d, %Y, %I:%M %p") .replace("AM", "a.m.").replace("PM", "p.m."), submission.problem[0].code, ), # Jan. 13, 2021, 12:17 a.m. # %b. %d, %Y, %I:%M %p inline=True, ) try: embed.add_field( name="%.2fs" % submission.time, value="%s" % submission.memory_str, inline=True, ) except TypeError: embed.add_field( name="---", value="%s" % submission.memory_str, inline=True, ) await ctx.respond(embed=embed) return None
async def points(ctx): """Plot point progression""" usernames = ctx.options.usernames query = Query() if usernames == []: usernames = [query.get_handle(ctx.author.id, ctx.get_guild().id)] try: users = await asyncio.gather(*[query.get_user(username) for username in usernames]) except ObjectNotFound: return await ctx.respond("User not found") usernames = [user.username for user in users] for i in range(len(users)): if users[i] is None: return await ctx.respond(f"{usernames[i]} does not exist on DMOJ") if len(users) > 10: return await ctx.respond("Too many users given, max 10") total_data = {} for username in usernames: q = ( session.query(Submission_DB) .options(orm.joinedload("problem")) .join(User_DB, User_DB.username == Submission_DB._user, aliased=True) .filter(User_DB.username == username) .order_by(Submission_DB.date) ) submissions = q.all() if len(submissions) == 0: await ctx.respond(f"`{username}` does not have any cached submissions, caching now") await query.get_submissions(username) submissions = q.all() problems_ACed = dict() code_to_points = dict() points_arr = [] data_to_plot = {} # O(N^2logN) :blobcreep: for submission in submissions: code = submission.problem[0].code points = submission.points result = submission.result if points is not None: if result == "AC": problems_ACed[code] = 1 if code not in code_to_points: # log N search, N insert code_to_points[code] = points bisect.insort(points_arr, points) elif points > code_to_points[code]: # N remove, log N search, N insert points_arr.remove(code_to_points[code]) code_to_points[code] = points bisect.insort(points_arr, points) cur_points = calculate_points(points_arr[::-1], len(problems_ACed)) data_to_plot[submission.date] = cur_points total_data[username] = data_to_plot plot_points(total_data) embed = hikari.Embed( title="Problems Progression", color=0xFCDB05, ) with open("./graphs/plot.png", "rb") as file: embed.set_image(hikari.Bytes(file.read(), "plot.png")) return await ctx.respond(embed=embed)