Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Codechef atcoder support #470

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The features of the bot are split into a number of cogs, each handling their own
- **CacheControl** Commands related to data caching.

## Installation

> If you want to run the bot inside a docker container follow these [instructions](/Docker.md)

Clone the repository
Expand Down Expand Up @@ -84,6 +85,7 @@ Fill in appropriate variables in new "environment" file.
- **ALLOW_DUEL_SELF_REGISTER**: boolean value indicating if self registration for duels is enabled.
- **TLE_ADMIN**: the name of the role that can run admin commands of the bot. If this is not set, the role name will default to "Admin".
- **TLE_MODERATOR**: the name of the role that can run moderator commands of the bot. If this is not set, the role name will default to "Moderator".
- **CLIST_API_TOKEN**: Credential for accessing clist api, You can find your api key [here][https://clist.by/api/v2/doc/] after creating an account on clist.by. If this is not set, codechef/atcoder/google(kickstart) related commands won't work.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Markdown links are [label](url), with parentheses.


To start TLE just run:

Expand Down
1 change: 1 addition & 0 deletions environment.template
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export BOT_TOKEN="XXXXXXXXXXXXXXXXXXXXXXXX.XXXXXX.XXXXXXXXXXXXXXXXXXXXXXXXXXX"
export LOGGING_COG_CHANNEL_ID="XXXXXXXXXXXXXXXXXX"
export ALLOW_DUEL_SELF_REGISTER="false"
export CLIST_API_TOKEN="username=xxxxxxxx&api_key=xxxxxxxxxxxxxxxxxxxxxxxxxx"
100 changes: 99 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ pillow = "^5.4"
pycairo = "^1.19.1"
PyGObject = "^3.34.0"
aiocache = "^0.11.1"
beautifulsoup4 = "^4.9.3"
requests = "^2.26.0"

[tool.poetry.dev-dependencies]
pytest = "^3.0"
Expand Down
74 changes: 42 additions & 32 deletions tle/cogs/contests.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from tle.util import codeforces_common as cf_common
from tle.util import cache_system2
from tle.util import codeforces_api as cf
from tle.util import clist_common as clist_common
from tle.util import db
from tle.util import discord_common
from tle.util import events
Expand Down Expand Up @@ -312,7 +313,10 @@ def _get_cf_or_ioi_standings_table(problem_indices, handle_standings, deltas=Non
assert mode in ('cf', 'ioi')

def maybe_int(value):
return int(value) if mode == 'cf' else value
try:
return int(value)
except:
return value

header_style = '{:>} {:<} {:^} ' + ' '.join(['{:^}'] * len(problem_indices))
body_style = '{:>} {:<} {:>} ' + ' '.join(['{:>}'] * len(problem_indices))
Expand Down Expand Up @@ -374,7 +378,7 @@ def _make_standings_pages(self, contest, problem_indices, handle_standings, delt
num_chunks = len(handle_standings_chunks)
delta_chunks = paginator.chunkify(deltas, _STANDINGS_PER_PAGE) if deltas else [None] * num_chunks

if contest.type == 'CF':
if contest.type == 'CF' or contest.type == 'CLIST':
get_table = functools.partial(self._get_cf_or_ioi_standings_table, mode='cf')
elif contest.type == 'ICPC':
get_table = self._get_icpc_standings_table
Expand Down Expand Up @@ -440,46 +444,52 @@ def _make_contest_embed_for_vc_ranklist(ranklist, vc_start_time=None, vc_end_tim
msg = f'{elapsed} elapsed{en}|{en}{remaining} remaining'
embed.add_field(name='Tick tock', value=msg, inline=False)
return embed

@commands.command(brief='Show ranklist for given handles and/or server members')
async def ranklist(self, ctx, contest_id: int, *handles: str):
async def ranklist(self, ctx, contest_id: str, *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'.

You can frame contest_id as follow
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"You can specify contest_id as follows"?


# For codeforces ranklist
Enter codeforces contest id

# For codechef ranklist
long<MMYYYY>
lunchtime<MMYYYY>
cookoff<MMYYYY>
starters<MMYYYY>

# For atcoder ranklist
abc<Number>
arc<Number>
agc<Number>

# For google ranklist
kickstart<YY><Round>
codejam<YY><Round>

Use QR for Qualification Round and WF for World Finals.

# If nothing works
Use clist contest_id. You have to prefix - sign to clist contest-id otherwise it will be considered a codeforces contest id.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we consider doing this with a clist prefix rather than a minus sign? The minus way seems like a hack to me.

To know clist contest_id visit https://clist.by.
"""
handles = await cf_common.resolve_handles(ctx, self.member_converter, handles, maxcnt=None, default_to_all_server=True)
contest = cf_common.cache2.contest_cache.get_contest(contest_id)
wait_msg = await ctx.channel.send('Generating ranklist, please wait...')
ranklist = 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')
ranklist = await cf_common.cache2.ranklist_cache.generate_ranklist(contest.id,
fetch_changes=True)
contest = await clist_common.get_contest(contest_id)
handles = await clist_common.resolve_handles(ctx, self.member_converter, handles, maxcnt=None, default_to_all_server=True, resource=contest.resource)
ranklist = await clist_common.get_ranklist(contest, handles)
await wait_msg.delete()
await ctx.channel.send(embed=self._make_contest_embed_for_ranklist(ranklist))
await self._show_ranklist(channel=ctx.channel, contest_id=contest_id, handles=handles, ranklist=ranklist)
await self._show_ranklist(channel=ctx.channel, contest_id=contest_id, handles=handles, ranklist=ranklist, contest=contest)

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)
async def _show_ranklist(self, channel, contest_id: int, handles, ranklist, vc: bool = False, delete_after: float = None, contest=None):
contest = contest or cf_common.cache2.contest_cache.get_contest(contest_id)
Copy link
Collaborator

@algmyr algmyr Aug 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a big issue, but it would be neat if we could get away with only having 1 arg, rather than both contest and contest_id. Though I'm not sure what a clean option would be here.

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))
handle_standings = ranklist.get_handle_standings(handles, vc=vc)

if not handle_standings:
error = f'None of the handles are present in the ranklist of `{contest.name}`'
Expand All @@ -493,7 +503,7 @@ async def _show_ranklist(self, channel, contest_id: int, handles: [str], ranklis
if ranklist.is_rated:
deltas = [ranklist.get_delta(handle) for handle, standing in handle_standings]

problem_indices = [problem.index for problem in ranklist.problems]
problem_indices = ranklist.get_problem_indexes()
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)

Expand Down
Loading