def soup_parse(soup): submission_id = soup["id"] result = soup.find(class_="sub-result")["class"][-1] try: score = soup.find(class_="sub-result").find( class_="score").text.split("/") score_num, score_denom = map(int, score) points = score_num / score_denom except ValueError: score_num, score_denom = 0, 0 points = 0 lang = soup.find(class_="language").text q = session.query(Language_DB).filter( Language_DB.short_name == lang) if q.count(): lang = q.first().key # if not as short_name it'll be key, why? idk problem = soup.find(class_="name").find("a")["href"].split("/")[-1] name = soup.find(class_="name").find("a").text date = soup.find(class_="time-with-rel")["data-iso"] try: time = float(soup.find("div", class_="time")["title"][:-1]) except ValueError: time = None except KeyError: time = None size = "" memory = soup.find("div", class_="memory").text.split(" ") if len(memory) == 2: memory, size = memory memory = float(memory) if size == "MB": memory *= 1024 if size == "GB": memory *= 1024 * 1024 else: # --- case memory = 0 res = { "id": submission_id, "problem": problem, "user": username, "date": date, "language": lang, "time": time, "memory": memory, "points": points, "result": result, "score_num": score_num, "score_denom": score_denom, "problem_name": html.unescape(name), } ret = Submission(res) return ret
def get_unsolved_problems(self, username: str, types: List[str], low: int = 1, high: int = 50) -> Problem_DB: # Does not find problems if you first # +update_problems # +gimme # This is cause calling the /problems api does not return is_organization_private # The original goal of is_organization_private filter is to prevent leaking problems conds = [Problem_DB.types.contains(_type) for _type in types] sub_q = session.query(Submission_DB, func.max(Submission_DB.points))\ .filter(Submission_DB._user == username)\ .group_by(Submission_DB._code).subquery() q = session.query(Problem_DB)\ .join(sub_q, Problem_DB.code == sub_q.c._code, isouter=True)\ .filter(func.ifnull(sub_q.c.points, 0) < Problem_DB.points)\ .filter(or_(*conds))\ .filter(Problem_DB.points.between(low, high))\ .filter(Problem_DB.is_organization_private == 0)\ .filter(Problem_DB.is_public == 1) return q.all()
async def async_init(self): problem_qq = session.query(Problem_DB).filter( Problem_DB.code.in_(self._solved_problems)) problem_q = session.query(Problem_DB.code).filter( Problem_DB.code.in_(self._solved_problems)).all() problem_q = list(map(itemgetter(0), problem_q)) for problem_code in self._solved_problems: try: if problem_code not in problem_q: api = API() await api.get_problem(problem_code) session.add(Problem_DB(api.data.object)) session.commit() except ObjectNotFound: pass self.solved_problems = problem_qq.all() organization_qq = session.query(Organization_DB).filter( Organization_DB.id.in_(self._organizations)) organization_q = session.query(Organization_DB.id).filter( Organization_DB.id.in_(self._organizations)).all() organization_q = list(map(itemgetter(0), organization_q)) for organization_id in self._organizations: if organization_id not in organization_q: api = API() await api.get_organizations() for organization in api.data.objects: if organization.id not in organization_q and organization.id in self._organizations: session.add(Organization_DB(organization)) session.commit() break self.organizations = organization_qq.all() for contest in self._contests: if contest["rating"]: self.max_rating = max(self.max_rating or 0, contest["rating"]) self._contest_keys = list(map(itemgetter("key"), self._contests)) contest_qq = session.query(Contest_DB).filter( Contest_DB.key.in_(self._contest_keys)) contest_q = session.query(Contest_DB.key).filter( Contest_DB.key.in_(self._contest_keys)).all() contest_q = list(map(itemgetter(0), contest_q)) for contest_key in self._contest_keys: try: if contest_key not in contest_q: api = API() await api.get_contest(contest_key) # This causes db errors, and in the case the above doesn't catch it. # This will be a last ditch effort if session.query(Contest_DB).filter( Contest_DB.key == contest_key).count(): continue session.add(Contest_DB(api.data.object)) session.commit() except ObjectNotFound: pass self.contests = contest_qq.all()
async def async_init(self): user = session.query(User_DB).filter(User_DB.username == self._user) if user.count() == 0: api = API() await api.get_user(self._user) session.add(api.data.object) session.commit() self.user = user.first() contest = session.query(Contest_DB).filter( Contest_DB.key == self._contest) if contest.count() == 0: api = API() await api.get_contest(self._contest) session.add(api.data.object) session.commit() self.contest = contest.first()
async def get_user(self, username: str) -> User_DB: q = session.query(User_DB).\ filter(func.lower(User_DB.username) == func.lower(username)) # if q.count(): # # solved_problems checks if it has detailed rows # if len(q.first().solved_problems) != 0: # return q.first() a = API() await a.get_user(username) q = session.query(User_DB).\ filter(func.lower(User_DB.username) == func.lower(a.data.object.username)) if q.count(): # Needs to be fetch, the default (evaluate) is not able to eval # the query q.delete(synchronize_session='fetch') session.add(User_DB(a.data.object)) session.commit() return q.first()
async def update_submissions(self, ctx): """Updates the submissions of every user in db (Warning! Slow!)""" q = session.query(Submission_DB._user).distinct(Submission_DB._user) usernames = list(map(itemgetter(0), q.all())) await ctx.send(f"Recaching submissions for {len(usernames)}" f" users. This will take a long time (perhaps hours).") session.query(Submission_DB).delete() session.commit() query = Query() count = 0 msg = await ctx.send(f"{count}/{len(usernames)} users cached...") for username in usernames: await msg.edit(content=f"{count}/{len(usernames)} users cached..." f" ({username})") await query.get_submissions(username) time.sleep(30) # PLS DON'T GET CLOUDFLARED count += 1 await msg.edit(content=f"{len(usernames)} users cache. Done!")
async def async_map(_type, objects): problems = session.query(Problem_DB.code, Problem_DB).all() problems = {k: v for k, v in problems} users = session.query(User_DB.username, User_DB).all() users = {k: v for k, v in users} languages = session.query(Language_DB.key, Language_DB).all() languages = {k: v for k, v in languages} to_gather = [] lock_table = {} for obj in objects: # If a user attempts to cache submissions after the latest release of a contest, there is a chance it will cause a db error # this is because any submissions which are not in the db will be called by the api and stored into the db # but in between that moment of calling the api and storing into the db, another process will call the api for the same problem # because it is technically not in memory yet # This fix to this is two global tables and a lock to_gather.append( obj.async_init(problems, users, languages, lock_table)) await asyncio.gather(*to_gather) session.commit()
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 cache_contests(self, ctx): '''Individually caches every contest''' msg = await ctx.send('Caching...') query = Query() contests = await query.get_contests() for contest in contests: q = session.query(Contest_DB).filter(Contest_DB.key == contest.key) if q.count() > 0: q.delete() session.commit() await query.get_contest(contest.key) return await msg.edit(content=f'Cached {len(contests)} contests')
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 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 rating(self, ctx, *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] for contest in contests: changes = get_rating_change(contest.rankings, users) data[contest.end_time] = [] for user in users: if user.username in changes: change = changes[user.username] 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=f'attachment://plot.png', ) return await ctx.send(embed=embed, file=file)
async def get_languages(self, common_name=None) -> [Language_DB]: q = session.query(Language_DB).\ filter(self.parse(Language_DB.common_name, common_name)) if q.count(): return q.all() a = API() await a.get_languages(common_name=common_name) languages = list(map(Language_DB, a.data.objects)) for language in languages: session.add(language) session.commit() return languages
async def async_init(self): organization_qq = session.query(Organization_DB).\ filter(Organization_DB.id.in_(self._organizations)) organization_q = session.query(Organization_DB.id).\ filter(Organization_DB.id.in_(self._organizations)).all() organization_q = list(map(itemgetter(0), organization_q)) for organization_id in self._organizations: if organization_id not in organization_q: api = API() await api.get_organizations() for organization in api.data.objects: if (organization.id not in organization_q and organization.id in self._organizations): session.add(Organization_DB(organization)) session.commit() break self.organizations = organization_qq.all() # perhaps I should check if it's the general or detailed version def get_code(problem): return problem["code"] self._problem_codes = list(map(get_code, self._problems)) problem_qq = session.query(Problem_DB).\ filter(Problem_DB.code.in_(self._problem_codes)) problem_q = session.query(Problem_DB.code).\ filter(Problem_DB.code.in_(self._problem_codes)).all() problem_q = list(map(itemgetter(0), problem_q)) for problem_dict in self._problems: problem_code = problem_dict["code"] try: if problem_code not in problem_q: api = API() await api.get_problem(problem_code) session.add(Problem_DB(api.data.object)) session.commit() except ObjectNotFound: pass self.problems = problem_qq.all()
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 get_contest(self, key) -> Contest_DB: q = session.query(Contest_DB).\ filter(Contest_DB.key == key) if q.count(): # is_rated checks if it has detailed rows if q.first().is_rated is not None: return q.first() a = API() await a.get_contest(key) if q.count(): q.delete() session.add(Contest_DB(a.data.object)) session.commit() return q.first()
async def stats(self, ctx): '''Display cool dmoj stats that no one asked for''' problems = session.query(Problem_DB.points)\ .order_by(Problem_DB.points.desc()).all() def tuple_first(data): return data[0] problems = list(map(tuple_first, problems)) total_problems = len(problems) total_points = calculate_points(problems, total_problems) await ctx.send( 'The theoretical maximum number of points you can achieve is %.2f\n' 'There are %d public problems on DMOJ' % (total_points, total_problems))
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() await query.get_contest(key) 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 contests with the key {key} " f"cached. Will try fetching contest") else: q.delete() session.commit() query = Query() await query.get_problem(key) await ctx.send(f"Recached contest {key}")
async def get_problem(self, code) -> Problem_DB: q = session.query(Problem_DB).\ filter(Problem_DB.code == code) if q.count(): # has_rating check if it has a detailed row if q.first().short_circuit is not None: return q.first() a = API() await a.get_problem(code) if q.count(): q.delete() session.add(Problem_DB(a.data.object)) session.commit() return q.first()
async def async_init(self): problem_qq = session.query(Problem_DB).\ filter(Problem_DB.code.in_(self._solved_problems)) problem_q = session.query(Problem_DB.code).\ filter(Problem_DB.code.in_(self._solved_problems)).all() problem_q = list(map(itemgetter(0), problem_q)) for problem_code in self._solved_problems: try: if problem_code not in problem_q: api = API() await api.get_problem(problem_code) session.add(Problem_DB(api.data.object)) session.commit() except ObjectNotFound: pass self.solved_problems = problem_qq.all() organization_qq = session.query(Organization_DB).\ filter(Organization_DB.id.in_(self._organizations)) organization_q = session.query(Organization_DB.id).\ filter(Organization_DB.id.in_(self._organizations)).all() organization_q = list(map(itemgetter(0), organization_q)) for organization_id in self._organizations: if organization_id not in organization_q: api = API() await api.get_organizations() for organization in api.data.objects: if (organization.id not in organization_q and organization.id in self._organizations): session.add(Organization_DB(organization)) session.commit() break self.organizations = organization_qq.all() def get_key(contest): return contest["key"] self._contest_keys = list(map(get_key, self._contests)) contest_qq = session.query(Contest_DB).\ filter(Contest_DB.key.in_(self._contest_keys)) contest_q = session.query(Contest_DB.key).\ filter(Contest_DB.key.in_(self._contest_keys)).all() contest_q = list(map(itemgetter(0), contest_q)) for contest_key in self._contest_keys: try: if contest_key not in contest_q: api = API() await api.get_contest(contest_key) session.add(Contest_DB(api.data.object)) session.commit() except ObjectNotFound: pass self.contests = contest_qq.all()
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 top(self, ctx, arg='rating'): '''Shows registered server members in ranked order''' arg = arg.lower() if arg not in [ 'rating', 'maxrating', 'points', 'solved', 'raw', 'maxraw' ]: return await ctx.send_help('top') users = session.query(User_DB).join(Handle_DB, Handle_DB.handle == User_DB.username)\ .filter(Handle_DB.guild_id == ctx.guild.id) leaderboard = [] for user in users: if arg == 'rating': leaderboard.append([-(user.rating or -9999), user.username]) elif arg == 'maxrating': leaderboard.append( [-(user.max_rating or -9999), user.username]) elif arg == 'points': leaderboard.append([-user.performance_points, user.username]) elif arg == 'solved': leaderboard.append([-(user.problem_count or 0), user.username]) elif arg == 'raw': leaderboard.append( [-(user.raw_rating or -9999), user.username]) elif arg == 'maxraw': leaderboard.append( [-(user.max_raw_rating or -9999), user.username]) leaderboard.sort() content = [] page = '' for i, user in enumerate(leaderboard): if user[0] == 9999: page += f'{i+1} {user[1]} unrated\n' else: page += f'{i+1} {user[1]} {-round(user[0],3)}\n' if i % 10 == 9: content.append(page) page = '' if page != '': content.append(page) if len(content) == 0: content.append('No users') message = await ctx.send(embed=discord.Embed().add_field( name='Top DMOJ ' + arg, value=content[0])) await scroll_embed(ctx, self.bot, message, 'Top DMOJ ' + arg, content)
async def rate_contest(contest): users = [] for ranking in contest.rankings: if all(not solution for solution in ranking['solutions']) and not contest.rate_all or \ (contest.rating_floor or -1e9) > (ranking['old_rating'] or RATING_INIT) or\ (contest.rating_ceiling or 1e9) < (ranking['old_rating'] or RATING_INIT): continue users.append(ranking) usernames = [user['user'] for user in users] ranks = list( tie_ranker(users, key=itemgetter('score', 'cumulative_time', 'tiebreaker'))) old_ratings = [] raw_ratings = [] counts = [] perfs = [] query = Query() for username in usernames: user = session.query(User_DB).filter(User_DB.username == username) user = user[0] if user.count() else await query.get_user(username) raw_ratings.append(user.raw_rating or MEAN_INIT) old_ratings.append(user.rating or RATING_INIT) perf = [] if user._contests: for contest in user._contests: if contest['performance']: perf.append(contest['performance']) counts.append(len(perf)) perfs.append(perf) rating, new_raw, performance = recalculate_ratings(ranks, raw_ratings, counts, perfs) predictions = {} for name, a, b, c, d in zip(usernames, ranks, old_ratings, rating, performance): predictions[name] = { 'rank': a, 'old_rating': b, 'new_rating': c, 'rating_change': c - (b or RATING_INIT), 'performance': d, } return predictions
async def stats(self, ctx): """Display cool dmoj stats that no one asked for""" problems = session.query(Problem_DB.points)\ .order_by(Problem_DB.points.desc()).all() def tuple_first(data): return data[0] 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 problems = list(map(tuple_first, problems)) total_problems = len(problems) total_points = calculate_points(problems, total_problems) await ctx.send("The theoretical maximum number of points you can achieve is %.2f\n" "There are %d public problems on DMOJ" % (total_points, total_problems))
async def top(ctx): """Shows registered server members in ranked order""" arg = ctx.options.arg.lower() if arg != "rating" and arg != "maxrating" and arg != "points" and arg != "solved": return await ctx.respond_help("top") users = ( session.query(User_DB) .join(Handle_DB, Handle_DB.handle == User_DB.username) .filter(Handle_DB.guild_id == ctx.get_guild().id) ) leaderboard = [] for user in users: if arg == "rating": leaderboard.append([-(user.rating or -9999), user.username]) elif arg == "maxrating": leaderboard.append([-(user.max_rating or -9999), user.username]) elif arg == "points": leaderboard.append([-user.performance_points, user.username]) elif arg == "solved": leaderboard.append([-(user.problem_count or 0), user.username]) leaderboard.sort() pag = lightbulb.utils.EmbedPaginator() for i, user in enumerate(leaderboard): if (arg == "rating" or arg == "maxrating") and user[0] == 9999: pag.add_line(f"{i+1} {user[1]} unrated") else: pag.add_line(f"{i+1} {user[1]} {-round(user[0],3)}") if len(leaderboard) == 0: pag.add_line("No users") @pag.embed_factory() def build_embed(page_index, content): return hikari.Embed().add_field(name="Top DMOJ " + arg, value=content) navigator = nav.ButtonNavigator(pag.build_pages()) await navigator.run(ctx)
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 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 ranklist(self, ctx, key): """List rating predictions of a contest""" q = session.query(Contest_DB).filter(Contest_DB.key == key) # Clear cache if q.count(): q.delete() session.commit() query = Query() try: contest = await query.get_contest(key) except ObjectNotFound: await ctx.send("Contest not found") return q = session.query(Handle_DB).filter(Handle_DB.guild_id == ctx.guild.id) handles = q.all() def to_handle(handle): return handle.handle usernames = list(map(to_handle, handles)) # The only way to calculate rating changes is by getting the volitility of all the users # that means 100+ seperate api calls # How does evan do it? import requests r = requests.get(f"https://evanzhang.ca/rating/contest/{key}/api") rankings = r.json()["users"] # Don't really need this, just sanity check # users = await asyncio.gather(*[query.get_user(username) # for username in users]) # usernames = [user.username for user in users] # Filter for those who participated in contest usernames = list(set(usernames) & set(rankings.keys())) # The length is 0 is contest is still ongoing problems = len(contest.problems) print(usernames) data = [] for ranking in contest.rankings: for username in usernames: if ranking["user"] == username: evan_ranking = rankings[username] rank = { "rank": int(evan_ranking["rank"]), "username": username + ":", "old_rating": evan_ranking["old_rating"], "new_rating": evan_ranking["new_rating"], } if evan_ranking["rating_change"] > 0: rank["rating_change"] = "+" + str( evan_ranking["rating_change"]) else: rank["rating_change"] = evan_ranking["rating_change"] # This is a quick fix :> problems = len(ranking["solutions"]) for i in range(1, problems + 1): solution = ranking["solutions"][i - 1] if solution: rank[str(i)] = int(solution["points"]) else: rank[str(i)] = 0 data.append(rank) max_len = {} max_len["rank"] = len("#") max_len["username"] = len("Handle") for rank in data: for k, v in rank.items(): max_len[k] = max(len(str(v)), max_len.get(k, 0)) format_output = "{:>" + str(max_len["rank"]) + "} " format_output += "{:" + str(max_len["username"] + 1) + "} " for i in range(1, problems + 1): format_output += "{:" + str(max_len[str(i)]) + "} " format_output += " " format_output += "{:>" + str(max_len["rating_change"]) + "} " format_output += "{:" + str(max_len["old_rating"]) + "} " format_output += "{:" + str(max_len["new_rating"]) + "} " output = format_output.format( "#", "Handle", *[str(i) for i in range(1, problems + 1)], "∆", "Old", "New") output += "\n" hyphens = format_output.format( "—" * max_len["rank"], "—" * max_len["username"], *["—" * max_len[str(i)] for i in range(1, problems + 1)], "—" * max_len["rating_change"], "—" * max_len["old_rating"], "—" * max_len["new_rating"], ) output += hyphens output += "\n" for rank in data: output += format_output.format( rank["rank"], rank["username"], *[rank[str(i)] for i in range(1, problems + 1)], rank["rating_change"], rank["old_rating"], rank["new_rating"], ) output += "\n" output += hyphens output += "\n" await ctx.send("```yaml\n" + output + "```")
def get_random_problem(self, low=1, high=10): q = session.query(Problem_DB)\ .filter(Problem_DB.points.between(low, high))\ .order_by(func.random()).limit(1) if q.count(): return q.first()
def get_handle_user(self, handle, guild_id): q = session.query(Handle_DB).\ filter(Handle_DB.handle == handle).\ filter(Handle_DB.guild_id == guild_id) if q.count(): return q.first().id