async def gotgud(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 are not linked with a DMOJ Account") user = await query.get_user(username) current = gitgud_util.get_current(username, ctx.get_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.respond("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) point = 10 + 2 * (point_diff) point = max(point, 0) gitgud_util.insert(username, ctx.get_guild().id, point, current.problem_id, datetime.now()) gitgud_util.clear(username, ctx.get_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.respond(f"Challenge took{ret}. " f"{current.handle} gained {point} points") else: return await ctx.respond("You have not completed the challenge")
async def cache(self, ctx, username: typing.Optional[str] = None): '''Caches the submissions of a user, will speed up other commands 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) username = username.replace('\'', '') if username is None: return await ctx.send('No username given!') user = await query.get_user(username) if user is None: return await ctx.send(f'{username} does not exist on DMOJ') username = user.username msg = await ctx.send(f'Caching {username}\'s submissions') session.query(Submission_DB).filter( Submission_DB._user == username).delete() await query.get_submissions(username) return await msg.edit(content=f'{username}\'s submissions ' + 'have been cached')
def main(): # https://github.com/cheran-senthil/TLE/blob/bae59c2de6a2313be4a6ba4a5a5cbba81352e229/tle/__main__.py dotenv.load_dotenv() BOT_TOKEN = os.environ.get("JOMD_BOT_TOKEN") if not BOT_TOKEN: logger.critical("Missing bot token") return pref = "+" bot = lightbulb.BotApp(token=BOT_TOKEN, prefix=pref, banner=None, intents=hikari.Intents.ALL) bot.load_extensions_from("./extensions/") # TESTING # extensions = ["admin", "meta", "gitgud", "handles", "user", "plot"] # for extension in extensions: # bot.load_extensions(f"extensions.{extension}") logger.debug("Extensions loaded: %s", ", ".join(bot.extensions)) # Get preliminary data if session.query(Problem_DB).count() == 0: q = Query() loop = asyncio.get_event_loop() loop.run_until_complete(q.get_problems()) # Restrict bot usage to inside guild channels only. bot.check(lightbulb.checks.guild_only) # TODO Make something that will automatically fetch recent contests bot.run()
async def whois(self, ctx, member: typing.Optional[discord.Member] = None, handle: typing.Optional[str] = None): # TODO: Use embeds and pfps query = Query() if handle: user = await query.get_user(handle) handle = user.username author_id = query.get_handle_user(handle, ctx.guild.id) if author_id: # member = await self.bot.fetch_user(author_id) name = ctx.message.guild.get_member(author_id) await ctx.send(f'`{handle}` is `{name.nick or name.name}`') else: await ctx.send( f'`{handle}` is not linked with any account here...') elif member: handle = query.get_handle(member.id, ctx.guild.id) if handle: await ctx.send(f'`{member.nick or member.name}` is `{handle}`') else: await ctx.send( f'`{member.nick or member.name}` is not linked with any account here' ) else: # wtf pass
async def cache(self, ctx, complete: typing.Optional[force] = False, username: typing.Optional[str] = None): """Caches the submissions of a user, will speed up other commands Use surround your username with '' if it can be interpreted as a number +f cache every submission """ query = Query() username = username or query.get_handle(ctx.author.id, ctx.guild.id) username = username.replace('\'', '') if username is None: return await ctx.send(f'No username given!') user = await query.get_user(username) if user is None: return await ctx.send(f'{username} does not exist on DMOJ') username = user.username try: msg = await ctx.send(f'Caching {username}\'s submissions') except Exception as e: await msg.edit(content='An error has occured, ' + 'try caching again. Log: ' + e.message) return await query.get_submissions(username) return await msg.edit(content=f'{username}\'s submissions ' + 'have been cached.')
async def force(self, ctx, _type, key): ''' Force a recache of a problem, or contest ''' if _type.lower() == 'contest': q = session.query(Contest_DB).filter(Contest_DB.key == key) if q.count() == 0: await ctx.send(f'There is no contests with the key {key} ' f'cached. Will try fetching contest') else: q.delete() session.commit() query = Query() try: await query.get_contest(key) except ObjectNotFound: return await ctx.send('Contest not found') await ctx.send(f'Recached contest {key}') if _type.lower() == 'problem': q = session.query(Problem_DB).filter(Problem_DB.code == key) if q.count() == 0: await ctx.send(f'There is no problems with the key {key} ' f'cached. Will try fetching problem') else: q.delete() session.commit() query = Query() try: await query.get_problem(key) except ObjectNotFound: return await ctx.send('Problem not found') await ctx.send(f'Recached problem {key}')
def main(): # https://github.com/cheran-senthil/TLE/blob/bae59c2de6a2313be4a6ba4a5a5cbba81352e229/tle/__main__.py BOT_TOKEN = os.environ["JOMD_BOT_TOKEN"] API_TOKEN = os.environ["JOMD_TOKEN"] if not BOT_TOKEN: print('Missing bot token') return intents = discord.Intents.default() # All but the two privileged ones intents.members = True # Subscribe to the Members intent pref = '+' bot = commands.Bot(command_prefix=commands.when_mentioned_or(pref), intents=intents) cogs = [file.stem for file in Path('cogs').glob('*.py')] for extension in cogs: bot.load_extension(f'cogs.{extension}') print(f'Cogs loaded: {", ".join(bot.cogs)}') def no_dm_check(ctx): if ctx.guild is None: raise commands.NoPrivateMessage('Private messages not permitted.') return True # Get preliminary data q = Query() loop = asyncio.get_event_loop() loop.run_until_complete(q.get_problems()) # Restrict bot usage to inside guild channels only. bot.add_check(no_dm_check) bot.run(BOT_TOKEN)
async def _set(self, ctx, member: discord.Member, username: str): """Manually link two accounts together""" query = Query() user = await query.get_user(username) if user is None: await ctx.send(f'{username} does not exist on dmoj') return username = user.username if query.get_handle(member.id, ctx.guild.id): await ctx.send( '%s, this handle is already linked with %s.' % (ctx.author.mention, query.get_handle(member.id, ctx.guild.id)) ) return if query.get_handle_user(username, ctx.guild.id): await ctx.send('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.guild.id session.add(handle) session.commit() return await ctx.send("%s, %s is now linked with %s." % (ctx.author.name, member.name, username))
async def force(ctx: lightbulb.Context) -> None: if ctx.options.type.lower() == "contest": q = session.query(Contest_DB).filter(Contest_DB.key == ctx.options.key) if q.count() == 0: await ctx.respond( f"There is no contests with the key {ctx.options.key} " f"cached. Will try fetching contest") else: q.delete() session.commit() query = Query() try: await query.get_contest(ctx.options.key) except ObjectNotFound: return await ctx.respond("Contest not found") await ctx.respond(f"Recached contest {ctx.options.key}") if ctx.options.type.lower() == "problem": q = session.query(Problem_DB).filter( Problem_DB.code == ctx.options.key) if q.count() == 0: await ctx.respond( f"There is no problems with the key {ctx.options.key} " f"cached. Will try fetching problem") else: q.delete() session.commit() query = Query() try: await query.get_problem(ctx.options.key) except ObjectNotFound: return await ctx.respond("Problem not found") await ctx.respond(f"Recached problem {ctx.options.key}") else: await ctx.send_help()
async def rating(ctx): """Plot rating progression""" peak = ctx.options.peak 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") 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() def get_rating_change(rankings, users): ret = {} for ranking in rankings: for user in users: if user.username == ranking["user"] and ranking["new_rating"]: ret[user.username] = ranking["new_rating"] return ret data = {} data["users"] = [user.username for user in users] userPrevRating = {} for contest in contests: changes = get_rating_change(contest.rankings, users) data[contest.end_time] = [] for user in users: if user.username in changes and ( not peak or changes[user.username] >= userPrevRating.get(user.username, -9999) ): change = changes[user.username] userPrevRating[user.username] = change data[contest.end_time].append(change) else: data[contest.end_time].append(None) plot_rating(data) embed = hikari.Embed( title="Rating 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)
async def rating(self, ctx, peak: typing.Optional[plot_peak] = False, *usernames): """Plot rating 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') 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() def get_rating_change(rankings, users): ret = {} for ranking in rankings: for user in users: if (user.username == ranking['user'] and ranking['new_rating']): ret[user.username] = ranking['new_rating'] return ret data = {} data['users'] = [user.username for user in users] userPrevRating = {} for contest in contests: changes = get_rating_change(contest.rankings, users) data[contest.end_time] = [] for user in users: if user.username in changes \ and (not peak or changes[user.username] >= userPrevRating.get(user.username, -9999)): change = changes[user.username] userPrevRating[user.username] = change data[contest.end_time].append(change) 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 link(ctx: lightbulb.Context) -> None: username = ctx.options.username # Check if user exists query = Query() 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 if query.get_handle(ctx.author.id, ctx.get_guild().id): await ctx.respond( "%s, your handle is already linked with %s." % (ctx.author.mention, query.get_handle(ctx.author.id, ctx.get_guild().id)) ) return if query.get_handle_user(username, ctx.get_guild().id): await ctx.respond("This handle is already linked with another user") return # verify from dmoj user description description = await query.get_user_description(username) userKey = hashlib.sha256(str(ctx.author.id).encode()).hexdigest() if userKey not in description: await ctx.respond( "Put `" + userKey + "` in your DMOJ user description (https://dmoj.ca/edit/profile/) " "and run the command again." ) return handle = Handle_DB() handle.id = ctx.author.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("%s, you now have linked your account to %s" % (ctx.author, 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) # TODO Add guild specific option to disable updating roles if rank in rank_to_role: await _update_rank(ctx.member, rank_to_role[rank], "Dmoj account linked") else: await ctx.respond("You are missing the `" + rank + "` role")
async def solved(self, ctx, *usernames): """Plot problems solved over time""" 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)\ .filter(Submission_DB._user == username) if q.count() == 0: not_cached.append(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(first_tuple, q.all())) dates.sort() data_to_plot = {} cnt = 0 for date in dates: cnt += 1 data_to_plot[date] = cnt total_data[username] = data_to_plot plot_solved(total_data) 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='Problems Solved', color=0xfcdb05, ) embed.set_image(url=f'attachment://plot.png', ) return await ctx.send(embed=embed, file=file)
async def gitgud(ctx: lightbulb.Context) -> None: # TODO Fix converters for slash commands points = ctx.options.points filters = ctx.options.filters query = Query() gitgud_util = Gitgud_utils() # get the user's dmoj handle username = query.get_handle(ctx.author.id, ctx.get_guild().id) # user = await query.get_user(username) if username is None: return await ctx.respond("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.get_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 = hikari.Embed( description=f"You currently have an uncompleted " f"challenge, [{problem.name}]" f"(https://dmoj.ca/problem/{problem.code})", color=0xFCDB05, ) return await ctx.respond(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.respond("No problems that satisfies the filter") gitgud_util.bind(username, ctx.get_guild().id, problem.code, problem.points, datetime.now()) embed.description = "Points: %s\nProblem Types ||%s||" % (problem.points, ", ".join(problem.types)) return await ctx.respond(embed=embed)
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.code}) [+{solved.point}] ({days_str})\n" count += 1 if count % 10 == 0: embed = discord.Embed() embed.add_field(name=f"Gitgud Log for {username} (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} (page {count//10 + 1}/{page_cnt})", value=content, inline=True) embeds.append(embed) return await paginator.run(embeds)
async def _set(self, ctx, member, username: str): """Manually link two accounts together""" query = Query() member = await query.parseUser(ctx, member) if username != "+remove": user = await query.get_user(username) if user is None: await ctx.send(f'{username} does not exist on dmoj') return username = user.username handle = query.get_handle(member.id, ctx.guild.id) if handle == username: return await ctx.send( 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.guild.id).first() session.delete(handle) session.commit() await ctx.send( f'Unlinked {member.display_name} with handle {handle.handle}') if username == "+remove": return if query.get_handle_user(username, ctx.guild.id): await ctx.send('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.guild.id session.add(handle) session.commit() await ctx.send(f"Linked {member.name} with {username}.") rank_to_role = { role.name: role for role in ctx.guild.roles if role.name in RANKS } rank = self.rating_to_rank(user.rating) if rank in rank_to_role: await self._update_rank(ctx.author, rank_to_role[rank], 'Dmoj account linked') else: await ctx.send("You are missing the " + rank.name + " role")
async def solved(ctx): """Plot problems solved over time""" 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).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) 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: cnt += 1 data_to_plot[date] = cnt total_data[username] = data_to_plot plot_solved(total_data) embed = hikari.Embed( title="Problems 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 solved(ctx): """Shows a user's last solved problems""" minP = 0 maxP = 100 query = Query() username = None for arg in ctx.options.args: if arg.startswith("p>="): minP = max(minP, int(arg[3:])) elif arg.startswith("p<="): maxP = min(maxP, int(arg[3:])) else: username = arg username = (await query.get_user(username)).username if username is None: username = query.get_handle(ctx.author.id, ctx.get_guild().id) await query.get_submissions(username, result="AC") submissions = (session.query(Submission_DB).filter( Submission_DB._user == username).filter( Submission_DB.result == "AC").options( orm.joinedload(Submission_DB.problem, innerjoin=True)).join( Submission_DB.problem).filter( Problem_DB.is_organization_private == 0).filter( Problem_DB.is_public == 1).order_by( Submission_DB.date).all()) uniqueSubmissions = [] solved = set() for sub in submissions: if sub._code not in solved: solved.add(sub._code) if minP <= sub.points and sub.points <= maxP: uniqueSubmissions.append(sub) uniqueSubmissions.reverse() pag = lightbulb.utils.EmbedPaginator(max_chars=1024) for sub in uniqueSubmissions: age = (datetime.now() - sub.date).days pag.add_line( f"[{sub.problem[0].name}]({SITE_URL}/problem/{sub._code}) [{sub.points}] ({age} days ago)" ) if len(uniqueSubmissions) == 0: pag.add_line("No submission") @pag.embed_factory() def build_embed(page_index, content): return hikari.Embed().add_field(name="Recently solved problems by " + username, value=content) navigator = nav.ButtonNavigator(pag.build_pages()) await navigator.run(ctx)
async def gimme(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(f'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 = await query.get_unsolved_problem(username, ctx.guild.id, 0, filters, points[0], points[1]) # print(result) if result is None: return await ctx.send("No problem that satisfies the filter") return await ctx.send(embed=result)
async def unlink(self, ctx): """Unlink your discord account with your dmoj account""" query = Query() if not query.get_handle(ctx.author.id, ctx.guild.id): await ctx.send('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.guild.id).first() session.delete(handle) session.commit() await ctx.send(f'Unlinked you with handle {handle.handle}')
async def gimme(ctx): """ 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 """ username = ctx.options.username points = ctx.options.points filters = ctx.options.filters query = Query() username = username or query.get_handle(ctx.author.id, ctx.get_guild().id) if username is None: return await ctx.respond("No username given!") username = username.replace("'", "") user = await query.get_user(username) if user is None: return await ctx.respond(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) if result is None: return await ctx.respond("No problem that satisfies the filter") return await ctx.respond(embed=result)
async def link(self, ctx, username: str): '''Links your discord account to your dmoj account''' # Check if user exists query = Query() user = await query.get_user(username) if user is None: await ctx.send(f'{username} does not exist on DMOJ') return username = user.username if query.get_handle(ctx.author.id, ctx.guild.id): await ctx.send('%s, your handle is already linked with %s.' % (ctx.author.mention, query.get_handle(ctx.author.id, ctx.guild.id))) return if query.get_handle_user(username, ctx.guild.id): await ctx.send('This handle is already linked with another user') return # verify from dmoj user description description = await query.get_user_description(username) userKey = hashlib.sha256(str(ctx.author.id).encode()).hexdigest() if userKey not in description: await ctx.send( 'Put `' + userKey + '` in your DMOJ user description (https://dmoj.ca/edit/profile/) ' 'and run the command again.') return handle = Handle_DB() handle.id = ctx.author.id handle.handle = username handle.user_id = user.id handle.guild_id = ctx.guild.id session.add(handle) session.commit() await ctx.send('%s, you now have linked your account to %s' % (ctx.author.name, username)) return rank_to_role = { role.name: role for role in ctx.guild.roles if role.name in RANKS } rank = self.rating_to_rank(user.rating) if rank in rank_to_role: await self._update_rank(ctx.author, rank_to_role[rank], 'Dmoj account linked') else: await ctx.send('You are missing the ' + rank.name + ' role')
async def solved(self, ctx, *args): """Shows a user's last solved problems""" minP = 0 maxP = 100 query = Query() username = None for arg in args: if arg.startswith("p>="): minP = max(minP, int(arg[3:])) elif arg.startswith("p<="): maxP = min(maxP, int(arg[3:])) else: username = (await query.get_user(arg)).username if username is None: username = query.get_handle(ctx.author.id, ctx.guild.id) await query.get_submissions(username, result='AC') submissions = session.query(Submission_DB)\ .filter(Submission_DB._user == username)\ .filter(Submission_DB.result == 'AC')\ .options(orm.joinedload(Submission_DB.problem, innerjoin=True))\ .join(Submission_DB.problem)\ .filter(Problem_DB.is_organization_private == 0)\ .filter(Problem_DB.is_public == 1)\ .order_by(Submission_DB.date).all() uniqueSubmissions = [] solved = set() for sub in submissions: if sub._code not in solved: solved.add(sub._code) if minP <= sub.points and sub.points <= maxP: uniqueSubmissions.append(sub) uniqueSubmissions.reverse() page = "" content = [] cnt = 0 for sub in uniqueSubmissions: age = (datetime.now() - sub.date).days # sub.problem[0].name is rly slow page += f"[{sub.problem[0].name}]({SITE_URL}/problem/{sub._code}) [{sub.points}] ({age} days ago)\n" cnt += 1 if cnt % 10 == 0: content.append(page) page = "" if page != "": content.append(page) if len(content) == 0: content.append("No submission") title = "Recently solved problems by " + username message = await ctx.send(embed=discord.Embed().add_field(name=title, value=content[0])) await scroll_embed(ctx, self.bot, message, title, content)
async def vc(self, ctx, *usernames): """Suggest a contest""" usernames = list(usernames) query = Query() if usernames == []: username = query.get_handle(ctx.author.id, ctx.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.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' is_rated = 'Not Rated' if contest.time_limit: window = f"{contest.time_limit/60/60} Hr" if contest.is_rated: is_rated = "Rated" embed = discord.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.send(embed=embed)
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 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 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 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 cache_contests(self, ctx): query = Query() msg = await ctx.send("Caching...") contests = await query.get_contests() for contest in contests: await query.get_contest(contest.key) return await msg.edit(content=f"Cached {len(contests)} contests")
async def cachecontest(self, ctx, key): '''Update contest rating changes''' query = Query() contest = await query.get_contest(key) for ranking in contest.rankings: await query.get_user(ranking['user']) await ctx.send(f'Updated {len(contest.rankings)} users')