async def gitlog(self, ctx, member: discord.Member = None): """Displays the list of gitgud problems issued to the specified member, excluding those noguded by admins. If the challenge was completed, time of completion and amount of points gained will also be displayed. """ def make_line(entry): issue, finish, name, contest, index, delta, status = entry problem = cf_common.cache2.problem_cache.problem_by_name[name] line = f"[{name}]({problem.url})\N{EN SPACE}[{problem.rating}]" if finish: time_str = cf_common.days_ago(finish) points = f"{_GITGUD_SCORE_DISTRIB[delta // 100 + 3]:+}" line += f"\N{EN SPACE}{time_str}\N{EN SPACE}[{points}]" return line def make_page(chunk): message = f"gitgud log for {member.display_name}" log_str = "\n".join(make_line(entry) for entry in chunk) embed = discord_common.cf_color_embed(description=log_str) return message, embed member = member or ctx.author data = cf_common.user_db.gitlog(member.id) pages = [make_page(chunk) for chunk in paginator.chunkify(data, 7)] paginator.paginate( self.bot, ctx.channel, pages, wait_time=5 * 60, set_pagenum_footers=True, )
async def gudgitters(self, ctx): """Show the list of users of gitgud with their scores.""" res = cf_common.user_db.get_gudgitters() res.sort(key=lambda r: r[1], reverse=True) rankings = [] for user_id, score in res: member = ctx.guild.get_member(int(user_id)) if member is None: continue if score > 0: handle = cf_common.user_db.get_handle(user_id, ctx.guild.id) user = cf_common.user_db.fetch_cf_user(handle) if user is None: continue discord_handle = member.display_name rating = user.rating rankings.append((index, discord_handle, handle, rating, score)) index += 1 if index == 10: break if not rankings: raise HandleCogError( 'No one has completed a gitgud challenge, send ;gitgud to request and ;gotgud to mark it as complete' ) pages = _make_pages_gudgitters(rankings, 'gudgitters') paginator.paginate(self.bot, ctx.channel, pages, wait_time=_PAGINATE_WAIT_TIME, set_pagenum_footers=True)
async def vshistory(self, ctx, member1: discord.Member = None, member2: discord.Member = None): if not member1: raise DuelCogError( f'You need to specify one or two discord members.') member2 = member2 or ctx.author data = cf_common.user_db.get_pair_duels(member1.id, member2.id) w, l, d = 0, 0, 0 for _, _, _, _, challenger, challengee, winner in data: if winner != Winner.DRAW: winnerid = challenger if winner == Winner.CHALLENGER else challengee if winnerid == member1.id: w += 1 else: l += 1 else: d += 1 message = discord.utils.escape_mentions( f'`{member1.display_name}` ({w}/{d}/{l}) `{member2.display_name}`') pages = self._paginate_duels(data, message, ctx.guild.id, False) paginator.paginate(self.bot, ctx.channel, pages, wait_time=5 * 60, set_pagenum_footers=True)
async def ranklist(self, ctx): """Show the list of duelists with their duel rating.""" users = [(ctx.guild.get_member(user_id), rating) for user_id, rating in cf_common.user_db.get_duelists()] users = [(member, cf_common.user_db.get_handle(member.id, ctx.guild.id), rating) for member, rating in users if member is not None and cf_common.user_db.get_num_duel_completed(member.id) > 0] _PER_PAGE = 10 def make_page(chunk, page_num): style = table.Style('{:>} {:<} {:<} {:<}') t = table.Table(style) t += table.Header('#', 'Name', 'Handle', 'Rating') t += table.Line() for index, (member, handle, rating) in enumerate(chunk): rating_str = f'{rating} ({rating2rank(rating).title_abbr})' t += table.Data(_PER_PAGE * page_num + index, f'{member.display_name}', handle, rating_str) table_str = f'```\n{t}\n```' embed = discord_common.cf_color_embed(description=table_str) return 'List of duelists', embed if not users: raise DuelCogError('There are no active duelists.') pages = [make_page(chunk, k) for k, chunk in enumerate(paginator.chunkify(users, _PER_PAGE))] paginator.paginate(self.bot, ctx.channel, pages, wait_time=5 * 60, set_pagenum_footers=True)
async def list(self, ctx, *countries): """Shows members of the server who have registered their handles and their Codeforces ratings. You can additionally specify a list of countries if you wish to display only members from those countries. Country data is sourced from codeforces profiles. e.g. ;handle list Croatia Slovenia """ countries = [country.title() for country in countries] res = cf_common.user_db.get_cf_users_for_guild(ctx.guild.id) users = [(ctx.guild.get_member(user_id), cf_user.handle, cf_user.rating) for user_id, cf_user in res if not countries or cf_user.country in countries] users = [(member, handle, rating) for member, handle, rating in users if member is not None] if not users: raise HandleCogError('No members with registered handles.') users.sort( key=lambda x: (1 if x[2] is None else -x[2], x[1])) # Sorting by (-rating, handle) title = 'Handles of server members' if countries: title += ' from ' + ', '.join(f'`{country}`' for country in countries) pages = _make_pages(users, title) paginator.paginate(self.bot, ctx.channel, pages, wait_time=_PAGINATE_WAIT_TIME, set_pagenum_footers=True)
async def _show_ranklist(self, channel, contest_id: int, handles: [str], ranklist, vc: bool = False, delete_after: float = None): contest = cf_common.cache2.contest_cache.get_contest(contest_id) if ranklist is None: raise ContestCogError('No ranklist to show') handle_standings = [] for handle in handles: try: standing = ranklist.get_standing_row(handle) except rl.HandleNotPresentError: continue # Database has correct handle ignoring case, update to it # TODO: It will throw an exception if this row corresponds to a team. At present ranklist doesnt show teams. # It should be fixed in https://github.com/cheran-senthil/TLE/issues/72 handle = standing.party.members[0].handle if vc and standing.party.participantType != 'VIRTUAL': continue handle_standings.append((handle, standing)) if not handle_standings: error = f'None of the handles are present in the ranklist of `{contest.name}`' if vc: await channel.send(embed=discord_common.embed_alert(error), delete_after=delete_after) return raise ContestCogError(error) handle_standings.sort(key=lambda data: data[1].rank) deltas = None if ranklist.is_rated: deltas = [ranklist.get_delta(handle) for handle, standing in handle_standings] problem_indices = [problem.index for problem in ranklist.problems] pages = self._make_standings_pages(contest, problem_indices, handle_standings, deltas) paginator.paginate(self.bot, channel, pages, wait_time=_STANDINGS_PAGINATE_WAIT_TIME, delete_after=delete_after)
async def ongoing(self, ctx, member: discord.Member = None): def make_line(entry): start_time, name, challenger, challengee = entry problem = cf_common.cache2.problem_cache.problem_by_name[name] now = datetime.datetime.now().timestamp() when = cf_common.pretty_time_format(now - start_time, shorten=True, always_seconds=True) challenger = get_cf_user(challenger, ctx.guild.id) challengee = get_cf_user(challengee, ctx.guild.id) return f'[{challenger.handle}]({challenger.url}) vs [{challengee.handle}]({challengee.url}): [{name}]({problem.url}) [{problem.rating}] {when}' def make_page(chunk): message = f'List of ongoing duels:' log_str = '\n'.join(make_line(entry) for entry in chunk) embed = discord_common.cf_color_embed(description=log_str) return message, embed member = member or ctx.author data = cf_common.user_db.get_ongoing_duels() if not data: raise DuelCogError('There are no ongoing duels.') pages = [make_page(chunk) for chunk in paginator.chunkify(data, 7)] paginator.paginate(self.bot, ctx.channel, pages, wait_time=5 * 60, set_pagenum_footers=True)
async def vcratings(self, ctx): users = [(await self.member_converter.convert(ctx, str(member_id)), handle, cf_common.user_db.get_vc_rating(member_id, default_if_not_exist=False)) for member_id, handle in cf_common.user_db.get_handles_for_guild(ctx.guild.id)] # Filter only rated users. (Those who entered at least one rated vc.) users = [(member, handle, rating) for member, handle, rating in users if rating is not None] users.sort(key=lambda user: -user[2]) _PER_PAGE = 10 def make_page(chunk, page_num): style = table.Style('{:>} {:<} {:<} {:<}') t = table.Table(style) t += table.Header('#', 'Name', 'Handle', 'Rating') t += table.Line() for index, (member, handle, rating) in enumerate(chunk): rating_str = f'{rating} ({cf.rating2rank(rating).title_abbr})' t += table.Data(_PER_PAGE * page_num + index, f'{member.display_name}', handle, rating_str) table_str = f'```\n{t}\n```' embed = discord_common.cf_color_embed(description=table_str) return 'VC Ratings', embed if not users: raise ContestCogError('There are no active VCers.') pages = [make_page(chunk, k) for k, chunk in enumerate(paginator.chunkify(users, _PER_PAGE))] paginator.paginate(self.bot, ctx.channel, pages, wait_time=5 * 60, set_pagenum_footers=True)
async def upsolve(self, ctx, choice: int = -1): """Request an unsolved problem from a contest you participated in delta | -300 | -200 | -100 | 0 | +100 | +200 | +300 | +400 | +500 points | 2 | 3 | 5 | 8 | 12 | 17 | 23 | 23 | 23 """ await self._validate_gitgud_status(ctx, delta=None) handle, = await cf_common.resolve_handles(ctx, self.converter, ('!' + str(ctx.author), )) user = cf_common.user_db.fetch_cf_user(handle) rating = round(user.effective_rating, -2) resp = await cf.user.rating(handle=handle) contests = {change.contestId for change in resp} submissions = await cf.user.status(handle=handle) solved = { sub.problem.name for sub in submissions if sub.verdict == 'OK' } problems = [ prob for prob in cf_common.cache2.problem_cache.problems if prob.name not in solved and prob.contestId in contests and ( (prob.rating - rating) >= _GITGUD_MAX_NEG_DELTA_VALUE and (prob.rating - rating) <= _GITGUD_MAX_POS_DELTA_VALUE) ] if not problems: raise CodeforcesCogError( 'Problems not found within the search parameters') problems.sort(key=lambda problem: cf_common.cache2.contest_cache. get_contest(problem.contestId).startTimeSeconds, reverse=True) if choice > 0 and choice <= len(problems): problem = problems[choice - 1] await self._gitgud(ctx, handle, problem, problem.rating - rating) else: problems = problems[:100] def make_line(i, prob): data = (f'{i + 1}: [{prob.name}]({prob.url}) [{prob.rating}]') return data def make_page(chunk, pi, num): title = f'Select a problem to upsolve (1-{num}):' msg = '\n'.join( make_line(10 * pi + i, prob) for i, prob in enumerate(chunk)) embed = discord_common.cf_color_embed(description=msg) return title, embed pages = [ make_page(chunk, pi, len(problems)) for pi, chunk in enumerate(paginator.chunkify(problems, 10)) ] paginator.paginate(self.bot, ctx.channel, pages, wait_time=5 * 60, set_pagenum_footers=True)
async def ranklist(self, ctx, contest_id: int, *handles: str): """Shows ranklist for the contest with given contest id. If handles contains '+server', all server members are included. No handles defaults to '+server'. """ contest = cf_common.cache2.contest_cache.get_contest(contest_id) wait_msg = None try: ranklist = cf_common.cache2.ranklist_cache.get_ranklist(contest) except cache_system2.RanklistNotMonitored: if contest.phase == 'BEFORE': raise ContestCogError(f'Contest `{contest.id} | {contest.name}` has not started') wait_msg = await ctx.send('Please wait...') ranklist = await cf_common.cache2.ranklist_cache.generate_ranklist(contest.id, fetch_changes=True) handles = set(handles) if not handles: handles.add('+server') if '+server' in handles: handles.remove('+server') guild_handles = [handle for discord_id, handle in cf_common.user_db.get_handles_for_guild(ctx.guild.id)] handles.update(guild_handles) handles = await cf_common.resolve_handles(ctx, self.member_converter, handles, maxcnt=None) handle_standings = [] for handle in handles: try: standing = ranklist.get_standing_row(handle) except rl.HandleNotPresentError: continue # Database has correct handle ignoring case, update to it # TODO: It will throw an exception if this row corresponds to a team. At present ranklist doesnt show teams. # It should be fixed in https://github.com/cheran-senthil/TLE/issues/72 handle=standing.party.members[0].handle handle_standings.append((handle, standing)) if not handle_standings: raise ContestCogError(f'None of the handles are present in the ranklist of `{contest.name}`') handle_standings.sort(key=lambda data: data[1].rank) deltas = None if ranklist.is_rated: deltas = [ranklist.get_delta(handle) for handle, standing in handle_standings] problem_indices = [problem.index for problem in ranklist.problems] pages = self._make_standings_pages(contest, problem_indices, handle_standings, deltas) if wait_msg: try: await wait_msg.delete() except: pass await ctx.send(embed=self._make_contest_embed_for_ranklist(ranklist)) paginator.paginate(self.bot, ctx.channel, pages, wait_time=_STANDINGS_PAGINATE_WAIT_TIME)
async def vc(self, ctx, *args: str): """Recommends a contest based on Codeforces rating of the handle provided. e.g ;vc mblazev c1729 +global +hello +goodbye +avito""" markers = [x for x in args if x[0] == '+'] handles = [x for x in args if x[0] != '+'] or ('!' + str(ctx.author), ) handles = await cf_common.resolve_handles(ctx, self.converter, handles, maxcnt=25) info = await cf.user.info(handles=handles) contests = cf_common.cache2.contest_cache.get_contests_in_phase( 'FINISHED') if not markers: divr = sum(user.effective_rating for user in info) / len(handles) div1_indicators = ['div1', 'global', 'avito', 'goodbye', 'hello'] markers = [ 'div3' ] if divr < 1600 else ['div2'] if divr < 2100 else div1_indicators recommendations = { contest.id for contest in contests if contest.matches(markers) and not cf_common.is_nonstandard_contest(contest) and not any( cf_common.is_contest_writer(contest.id, handle) for handle in handles) } # Discard contests in which user has non-CE submissions. visited_contests = await cf_common.get_visited_contests(handles) recommendations -= visited_contests if not recommendations: raise CodeforcesCogError('Unable to recommend a contest') recommendations = list(recommendations) random.shuffle(recommendations) contests = [ cf_common.cache2.contest_cache.get_contest(contest_id) for contest_id in recommendations[:25] ] def make_line(c): return f'[{c.name}]({c.url}) {cf_common.pretty_time_format(c.durationSeconds)}' def make_page(chunk): str_handles = '`, `'.join(handles) message = f'Recommended contest(s) for `{str_handles}`' vc_str = '\n'.join(make_line(contest) for contest in chunk) embed = discord_common.cf_color_embed(description=vc_str) return message, embed pages = [make_page(chunk) for chunk in paginator.chunkify(contests, 5)] paginator.paginate(self.bot, ctx.channel, pages, wait_time=5 * 60, set_pagenum_footers=True)
async def recent(self, ctx): data = cf_common.user_db.get_recent_duels() pages = self._paginate_duels(data, 'list of recent duels', ctx.guild.id, True) paginator.paginate(self.bot, ctx.channel, pages, wait_time=5 * 60, set_pagenum_footers=True)
async def _send_contest_list(self, ctx, contests, *, title, empty_msg): if contests is None: raise ContestCogError('Contest list not present') if len(contests) == 0: await ctx.send(embed=discord_common.embed_neutral(empty_msg)) return pages = self._make_contest_pages(contests, title) paginator.paginate(self.bot, ctx.channel, pages, wait_time=_CONTEST_PAGINATE_WAIT_TIME, set_pagenum_footers=True)
async def history(self, ctx, member: discord.Member = None): member = member or ctx.author data = cf_common.user_db.get_duels(member.id) message = discord.utils.escape_mentions( f'dueling history of `{member.display_name}`') pages = self._paginate_duels(data, message, ctx.guild.id, False) paginator.paginate(self.bot, ctx.channel, pages, wait_time=5 * 60, set_pagenum_footers=True)
async def fullsolve(self, ctx, *args: str): """Displays a list of contests, sorted by number of unsolved problems. Contest names matching any of the provided tags will be considered. e.g ;fullsolve +edu""" handle, = await cf_common.resolve_handles(ctx, self.converter, ('!' + str(ctx.author),)) tags = [x for x in args if x[0] == '+'] problem_to_contests = cf_common.cache2.problemset_cache.problem_to_contests contests = [contest for contest in cf_common.cache2.contest_cache.get_contests_in_phase('FINISHED') if (not tags or contest.matches(tags)) and not cf_common.is_nonstandard_contest(contest)] # subs_by_contest_id contains contest_id mapped to [list of problem.name] subs_by_contest_id = defaultdict(set) for sub in await cf.user.status(handle=handle): if sub.verdict == 'OK': try: contest = cf_common.cache2.contest_cache.get_contest(sub.problem.contestId) problem_id = (sub.problem.name, contest.startTimeSeconds) for contestId in problem_to_contests[problem_id]: subs_by_contest_id[contestId].add(sub.problem.name) except cache_system2.ContestNotFound: pass contest_unsolved_pairs = [] for contest in contests: num_solved = len(subs_by_contest_id[contest.id]) try: num_problems = len(cf_common.cache2.problemset_cache.get_problemset(contest.id)) if 0 < num_solved < num_problems: contest_unsolved_pairs.append((contest, num_solved, num_problems)) except cache_system2.ProblemsetNotCached: # In case of recent contents or cetain bugged contests pass contest_unsolved_pairs.sort(key=lambda p: (p[2] - p[1], -p[0].startTimeSeconds)) if not contest_unsolved_pairs: await ctx.send(f'`{handle}` has no contests to fullsolve :confetti_ball:') return def make_line(entry): contest, solved, total = entry return f'[{contest.name}]({contest.url})\N{EN SPACE}[{solved}/{total}]' def make_page(chunk): message = f'Fullsolve list for `{handle}`' full_solve_list = '\n'.join(make_line(entry) for entry in chunk) embed = discord_common.cf_color_embed(description=full_solve_list) return message, embed pages = [make_page(chunk) for chunk in paginator.chunkify(contest_unsolved_pairs, 10)] paginator.paginate(self.bot, ctx.channel, pages, wait_time=5 * 60, set_pagenum_footers=True)
async def stalk(self, ctx, *args): """Print problems solved by user sorted by time (default) or rating. All submission types are included by default (practice, contest, etc.) """ (hardest, ), args = cf_common.filter_flags(args, ['+hardest']) filt = cf_common.SubFilter(False) args = filt.parse(args) handles = args or ('!' + str(ctx.author), ) handles = await cf_common.resolve_handles(ctx, self.converter, handles) submissions = [ await cf.user.status(handle=handle) for handle in handles ] submissions = [sub for subs in submissions for sub in subs] submissions = filt.filter_subs(submissions) if not submissions: raise CodeforcesCogError( 'Submissions not found within the search parameters') if hardest: submissions.sort( key=lambda sub: (sub.problem.rating or 0, sub.creationTimeSeconds), reverse=True) else: submissions.sort(key=lambda sub: sub.creationTimeSeconds, reverse=True) def make_line(sub): data = (f'[{sub.problem.name}]({sub.problem.url})', f'[{sub.problem.rating if sub.problem.rating else "?"}]', f'({cf_common.days_ago(sub.creationTimeSeconds)})') return '\N{EN SPACE}'.join(data) def make_page(chunk): title = '{} solved problems by `{}`'.format( 'Hardest' if hardest else 'Recently', '`, `'.join(handles)) hist_str = '\n'.join(make_line(sub) for sub in chunk) embed = discord_common.cf_color_embed(description=hist_str) return title, embed pages = [ make_page(chunk) for chunk in paginator.chunkify(submissions[:100], 10) ] paginator.paginate(self.bot, ctx.channel, pages, wait_time=5 * 60, set_pagenum_footers=True)
async def history(self, ctx, member: discord.Member = None): member = member or ctx.author data = cf_common.user_db.get_duels(member.id) pages = self._paginate_duels( data, f"dueling history of {member.display_name}", ctx.guild.id, False, ) paginator.paginate( self.bot, ctx.channel, pages, wait_time=5 * 60, set_pagenum_footers=True, )
async def list(self, ctx): """Shows all members of the server who have registered their handles and their Codeforces ratings. """ res = cf_common.user_db.getallhandleswithrating() users = [(ctx.guild.get_member(int(user_id)), handle, rating) for user_id, handle, rating in res] users = [(member, handle, rating) for member, handle, rating in users if member is not None] users.sort( key=lambda x: (1 if x[2] is None else -x[2], x[1])) # Sorting by (-rating, handle) pages = _make_pages(users) paginator.paginate(self.bot, ctx.channel, pages, wait_time=_PAGINATE_WAIT_TIME, set_pagenum_footers=True)
async def registered(self, ctx): """Show the list of register contestants.""" users = [(ctx.guild.get_member(user_id)) for user_id, aux in cf_common.user_db.get_contestants()] users = [(member, cf_common.user_db.get_handle(member.id, ctx.guild.id)) for member in users if member is not None] users = [(member, handle) for member, handle in users if handle is not None] _PER_PAGE = 10 def make_page(chunk, page_num): style = table.Style("{:>} {:<} {:<}") t = table.Table(style) t += table.Header("#", "Name", "Handle") t += table.Line() for index, (member, handle) in enumerate(chunk): t += table.Data( _PER_PAGE * page_num + index, f"{member.display_name}", handle, ) table_str = f"```\n{t}\n```" embed = discord_common.cf_color_embed(description=table_str) return "List of contestants", embed if not users: raise DuelCogError("There are no registered contestants.") pages = [ make_page(chunk, k) for k, chunk in enumerate(paginator.chunkify(users, _PER_PAGE)) ] paginator.paginate( self.bot, ctx.channel, pages, wait_time=5 * 60, set_pagenum_footers=True, )
async def pending(self, ctx, member: discord.Member = None): """Shows current pending duels""" def make_line(entry): challenger, challengee = entry challenger = get_cf_user(challenger, ctx.guild.id) challengee = get_cf_user(challengee, ctx.guild.id) return f"[{challenger.handle}]({challenger.url}) vs [{challengee.handle}]({challengee.url})" def make_page(chunk): message = f"List of pending matches:" log_str = "\n".join(make_line(entry) for entry in chunk) embed = discord_common.cf_color_embed(description=log_str) return message, embed status = cf_common.user_db.get_tour_status() if status == 0: raise DuelCogError(f"Tournament is not going on :/") global curr_tour index = cf_common.user_db.get_tour_index() await get_tour(index) matches = await curr_tour.get_matches(force_update=True) data = [] for match in matches: if match.state == "open": player1 = await curr_tour.get_participant(match.player1_id) player2 = await curr_tour.get_participant(match.player2_id) data.append((player1.misc, player2.misc)) if not data: raise DuelCogError("There are no pending matches.") pages = [make_page(chunk) for chunk in paginator.chunkify(data, 7)] paginator.paginate( self.bot, ctx.channel, pages, wait_time=5 * 60, set_pagenum_footers=True, )
async def ranklist(self, ctx, contest_id: int, *handles: str): """Shows ranklist for the contest with given contest id. If handles contains '+server', all server members are included. No handles defaults to '+server'. """ contest = cf_common.cache2.contest_cache.get_contest(contest_id) wait_msg = None try: ranklist = cf_common.cache2.ranklist_cache.get_ranklist(contest) except cache_system2.RanklistNotMonitored: if contest.phase == "BEFORE": raise ContestCogError( f"Contest `{contest.id} | {contest.name}` has not started") wait_msg = await ctx.send("Please wait...") ranklist = await cf_common.cache2.ranklist_cache.generate_ranklist( contest.id, fetch_changes=True) handles = set(handles) if not handles: handles.add("+server") if "+server" in handles: handles.remove("+server") guild_handles = [ handle for discord_id, handle in cf_common.user_db.get_handles_for_guild(ctx.guild.id) ] handles.update(guild_handles) handles = await cf_common.resolve_handles(ctx, self.member_converter, handles, maxcnt=None) handle_standings = [] for handle in handles: try: standing = ranklist.get_standing_row(handle) except rl.HandleNotPresentError: continue handle_standings.append((handle, standing)) if not handle_standings: raise ContestCogError( f"None of the handles are present in the ranklist of `{contest.name}`" ) handle_standings.sort(key=lambda data: data[1].rank) deltas = None if ranklist.is_rated: deltas = [ ranklist.get_delta(handle) for handle, standing in handle_standings ] problem_indices = [problem.index for problem in ranklist.problems] pages = self._make_standings_pages(contest, problem_indices, handle_standings, deltas) if wait_msg: try: await wait_msg.delete() except: pass await ctx.send(embed=self._make_contest_embed_for_ranklist(ranklist)) paginator.paginate( self.bot, ctx.channel, pages, wait_time=_STANDINGS_PAGINATE_WAIT_TIME, )
async def ranklist(self, ctx, contest_id: int, *handles: str): """Shows ranklist for the contest with given contest id. If handles contains '+server', all server members are included. No handles defaults to '+server'. """ contest = cf_common.cache2.contest_cache.get_contest(contest_id) wait_msg = None try: ranklist = cf_common.cache2.ranklist_cache.get_ranklist(contest) deltas_status = 'Predicted' except cache_system2.RanklistNotMonitored: if contest.phase == 'BEFORE': raise ContestCogError( f'Contest `{contest.id} | {contest.name}` has not started') wait_msg = await ctx.send('Please wait...') ranklist = await cf_common.cache2.ranklist_cache.generate_ranklist( contest.id, fetch_changes=True) deltas_status = 'Final' handles = set(handles) if not handles: handles.add('+server') if '+server' in handles: handles.remove('+server') guild_handles = [ handle for discord_id, handle in cf_common.user_db.get_handles_for_guild(ctx.guild.id) ] handles.update(guild_handles) handles = await cf_common.resolve_handles(ctx, self.member_converter, handles, maxcnt=256) handle_standings = [] for handle in handles: try: standing = ranklist.get_standing_row(handle) except rl.HandleNotPresentError: continue handle_standings.append((handle, standing)) if not handle_standings: raise ContestCogError( f'None of the handles are present in the ranklist of `{contest.name}`' ) handle_standings.sort(key=lambda data: data[1].rank) deltas = None if ranklist.is_rated: deltas = [ ranklist.get_delta(handle) for handle, standing in handle_standings ] problem_indices = [problem.index for problem in ranklist.problems] pages = self._make_standings_pages(contest, problem_indices, handle_standings, deltas) embed = discord_common.cf_color_embed(title=contest.name, url=contest.url) phase = contest.phase.capitalize().replace('_', ' ') embed.add_field(name='Phase', value=phase) if ranklist.is_rated: embed.add_field(name='Deltas', value=deltas_status) if wait_msg: try: await wait_msg.delete() except: pass await ctx.send(embed=embed) paginator.paginate(self.bot, ctx.channel, pages, wait_time=_STANDINGS_PAGINATE_WAIT_TIME)