# Python Version: 3.x
"""
the module for Topcoder (https://www.topcoder.com/)
:note: There is the offcial API https://tcapi.docs.apiary.io/
:note: only the Marathon Match is supported
"""
import collections
import itertools
import re
import urllib.parse
import xml.etree.ElementTree
from typing import *
import bs4
import onlinejudge._implementation.logging as log
import onlinejudge._implementation.utils as utils
import onlinejudge.dispatch
import onlinejudge.type
from onlinejudge.type import *
[docs]class TopcoderService(onlinejudge.type.Service):
[docs] def login(self, *, get_credentials: onlinejudge.type.CredentialsProvider, session: Optional[requests.Session] = None) -> None:
"""
:raises LoginError:
"""
session = session or utils.get_default_session()
# NOTE: you can see this login page with https://community.topcoder.com/longcontest/?module=Submit
url = 'https://community.topcoder.com/longcontest/'
username, password = get_credentials()
data = {
'nextpage': 'https://www.topcoder.com/',
'module': 'Login',
'ha': username,
'pass': password,
'rem': 'on',
}
resp = utils.request('POST', url, session=session, data=data)
if 'longcontest' not in resp.url:
log.success('Success')
else:
log.failure('Failure')
raise LoginError
[docs] def get_url_of_login_page(self):
return 'https://accounts.topcoder.com/member'
[docs] def is_logged_in(self, *, session: Optional[requests.Session] = None) -> bool:
"""
.. versionadded:: 6.2.0
"""
session = session or utils.get_default_session()
url = 'https://community.topcoder.com/longcontest/stats/?module=ViewSystemTest&rd=17143&pm=14889&tid=33800773'
resp = utils.request('GET', url, session=session)
soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser)
return soup.find('form', attrs={'name': 'frmLogin'}) is None
[docs] def get_url(self) -> str:
return 'https://www.topcoder.com/'
[docs] def get_name(self) -> str:
return 'Topcoder'
[docs] @classmethod
def from_url(cls, url: str) -> Optional['TopcoderService']:
# example: https://www.topcoder.com/
result = urllib.parse.urlparse(url)
if result.scheme in ('', 'http', 'https') \
and result.netloc in ['www.topcoder.com', 'community.topcoder.com']:
return cls()
return None
TopcoderLongContestProblemStandingsRow = NamedTuple('TopcoderLongContestProblemStandingsRow', [
('handle', str),
('score', Optional[float]),
('rank', Optional[int]),
('last_submission_time', Optional[str]),
('language', Optional[str]),
('example_tests', int),
('submissions', int),
('cr', int),
])
TopcoderLongContestProblemOverviewRow = NamedTuple('TopcoderLongContestProblemOverviewRow', [
('rank', int),
('handle', str),
('provisional_rank', int),
('provisional_score', float),
('final_score', float),
('language', str),
('cr', int),
])
TopcoderLongContestProblemIndividualResultsFeedSubmission = NamedTuple('TopcoderLongContestProblemIndividualResultsFeedSubmission', [
('number', int),
('score', float),
('language', str),
('time', str),
])
TopcoderLongContestProblemIndividualResultsFeedTestCase = NamedTuple('TopcoderLongContestProblemIndividualResultsFeedTestCase', [
('test_case_id', int),
('score', float),
('processing_time', int),
('fatal_error_ind', int),
])
TopcoderLongContestProblemIndividualResultsFeed = NamedTuple('TopcoderLongContestProblemIndividualResultsFeed', [
('round_id', int),
('coder_id', int),
('handle', str),
('submissions', List[TopcoderLongContestProblemIndividualResultsFeedSubmission]),
('testcases', List[TopcoderLongContestProblemIndividualResultsFeedTestCase]),
])
[docs]class TopcoderLongContestProblem(onlinejudge.type.Problem):
def __init__(self, *, rd, cd=None, compid=None, pm=None):
self.rd = rd
self.cd = cd
self.compid = compid
self.pm = pm
[docs] def get_url(self) -> str:
return 'https://community.topcoder.com/tc?module=MatchDetails&rd=' + str(self.rd)
[docs] def get_service(self) -> TopcoderService:
return TopcoderService()
[docs] def download_sample_cases(self, *, session: Optional[requests.Session] = None) -> List[onlinejudge.type.TestCase]:
"""
:raises NotImplementedError:
"""
raise NotImplementedError
[docs] @classmethod
def from_url(cls, url: str) -> Optional['TopcoderLongContestProblem']:
# example: https://community.topcoder.com/longcontest/?module=ViewProblemStatement&rd=16997&pm=14690
# example: https://community.topcoder.com/longcontest/?module=ViewProblemStatement&rd=16997&compid=57374
# example: https://community.topcoder.com/longcontest/?module=ViewStandings&rd=16997
# example: https://community.topcoder.com/tc?module=MatchDetails&rd=16997
result = urllib.parse.urlparse(url)
if result.scheme in ('', 'http', 'https') \
and result.netloc == 'community.topcoder.com' \
and utils.normpath(result.path) in ['/longcontest', '/tc']:
querystring = dict(urllib.parse.parse_qsl(result.query))
if 'rd' in querystring:
kwargs = {}
for name in ['rd', 'cd', 'compid', 'pm']:
if name in querystring:
kwargs[name] = int(querystring[name])
return cls(**kwargs)
return None
[docs] def get_available_languages(self, *, session: Optional[requests.Session] = None) -> List[Language]:
session = session or utils.get_default_session()
return [
Language(LanguageId('1'), 'Java 8'),
Language(LanguageId('3'), 'C++11'),
Language(LanguageId('4'), 'C#'),
Language(LanguageId('5'), 'VB'),
Language(LanguageId('6'), 'Python 2'),
]
[docs] def submit_code(self, code: bytes, language_id: LanguageId, *, filename: Optional[str] = None, session: Optional[requests.Session] = None, kind: str = 'example') -> onlinejudge.type.Submission:
"""
:param kind: must be one of `example` (default) or `full`
:raises NotLoggedInError:
:raises SubmissionError:
"""
assert kind in ['example', 'full']
session = session or utils.get_default_session()
# TODO: implement self.is_logged_in()
# if not self.is_logged_in(session=session):
# raise NotLoggedInError
# module=MatchDetails
url = 'https://community.topcoder.com/tc?module=MatchDetails&rd=%d' % self.rd
resp = utils.request('GET', url, session=session)
soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser)
path = soup.find('a', text='Register/Submit').attrs['href']
assert path.startswith('/') and 'module=ViewReg' in path
# module=ViewActiveContests
url = 'https://community.topcoder.com' + path
resp = utils.request('GET', url, session=session)
soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser)
path = [tag.attrs['href'] for tag in soup.find_all('a', text='Submit') if ('rd=%d' % self.rd) in tag.attrs['href']]
if len(path) == 0:
log.error('link to submit not found: Are you logged in? Are you registered? Is the contest running?')
raise SubmissionError('something wrong')
assert len(path) == 1
path = path[0]
assert path.startswith('/') and 'module=Submit' in path
query = dict(urllib.parse.parse_qsl(urllib.parse.urlparse(path).query))
self.cd = query['cd']
self.compid = query['compid']
# module=Submit
submit_url = 'https://community.topcoder.com' + path
resp = utils.request('GET', submit_url, session=session)
soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser)
# post
url = 'https://community.topcoder.com/longcontest/'
data = {
'module': 'Submit',
'rd': self.rd,
'cd': self.cd,
'compid': self.compid,
'Action': 'submit',
'exOn': {
'example': 'true',
'full': 'false'
}[kind],
'lid': str(language_id),
'code': code,
}
resp = utils.request('POST', url, session=session, data=data)
# check if module=SubmitSuccess
if 'module=SubmitSuccess' in resp.content.decode(resp.encoding):
url = 'http://community.topcoder.com/longcontest/?module=SubmitSuccess&rd={}&cd={}&compid={}'.format(self.rd, self.cd, self.compid)
log.success('success: result: %s', url)
return utils.DummySubmission(url, problem=self)
else:
# module=Submit to get error messages
resp = utils.request('GET', submit_url, session=session)
soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser)
messages = soup.find('textarea', {'name': 'messages'}).text
log.failure('%s', messages)
raise SubmissionError('it may be a rate limit: ' + messages)
[docs] def download_standings(self, *, session: Optional[requests.Session] = None) -> List[TopcoderLongContestProblemStandingsRow]:
"""
:raises Exception: if redirected to `module=ViewOverview` page
.. versionadded:: 6.2.0
This method may be deleted in future.
"""
session = session or utils.get_default_session()
rows = [] # type: List[TopcoderLongContestProblemStandingsRow]
for start in itertools.count(1):
# get
url = 'https://community.topcoder.com/longcontest/?module=ViewStandings&rd={}&nr=100&sr={}'.format(self.rd, start)
resp = utils.request('GET', url, allow_redirects=False, session=session)
if resp.status_code != 200:
raise RuntimeError('failed to get {}'.format(url))
# parse
soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser)
table = soup.find('table', class_='statTable')
for tr in table.find_all('tr')[2:]: # NOTE: first two rows are headings
row = collections.OrderedDict() # type: Dict[str, str]
tds = tr.find_all('td')
texts = [td.text.strip() for td in tds] # NOTE: some cells may be empty strings
assert len(texts) == 7
handle = texts[0]
score = float(texts[1]) if texts[1] else None
rank = int(texts[2]) if texts[2] else None
last_submission_time = texts[3] or None
language = texts[4] or None
example_tests = int(tds[5].text)
submissions = int(tds[6].text)
href = (tds[5].find('a') or tds[6].find('a')).attrs['href']
query = dict(urllib.parse.parse_qsl(urllib.parse.urlparse(href).query))
self.compid = query['compid']
rows += [TopcoderLongContestProblemStandingsRow(handle, score, rank, last_submission_time, language, example_tests, submissions, cr=int(query['cr']))]
# check whether the next page exists
link = soup.find('a', text='next >>')
if link is None:
break
return rows
[docs] def download_overview(self, *, session: Optional[requests.Session] = None) -> List[TopcoderLongContestProblemOverviewRow]:
"""
.. versionadded:: 6.2.0
This method may be deleted in future.
"""
session = session or utils.get_default_session()
# get
number = 9999
start = 1
url = 'https://community.topcoder.com/longcontest/stats/?module=ViewOverview&rd={}&nr={}&sr={}'.format(self.rd, number, start)
resp = utils.request('GET', url, session=session)
# parse
soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser)
table = soup.find('table', class_='stat')
overview = [] # type: List[TopcoderLongContestProblemOverviewRow]
for tr in table.find_all('tr', class_=re.compile(r'light|dark')):
tds = tr.find_all('td')
assert len(tds) == 9
rank = int(tds[0].text)
handle = tds[1].text.strip()
provisional_rank = int(tds[2].text)
provisional_score = float(tds[3].text)
final_score = float(tds[4].text)
language = tds[5].text.strip()
assert tds[6].text.strip() == 'results'
assert tds[7].text.strip() == 'submission history'
assert tds[8].text.strip() == 'example history'
query = dict(urllib.parse.parse_qsl(urllib.parse.urlparse(tds[6].find('a').attrs['href']).query))
self.pm = query['pm']
overview += [TopcoderLongContestProblemOverviewRow(rank, handle, provisional_rank, provisional_score, final_score, language, cr=int(query['cr']))]
return overview
[docs] def download_individual_results_feed(self, *, cr: int, session: Optional[requests.Session] = None) -> TopcoderLongContestProblemIndividualResultsFeed:
"""
.. versionadded:: 6.2.0
This method may be deleted in future.
"""
session = session or utils.get_default_session()
# get
url = 'https://community.topcoder.com/longcontest/stats/?module=IndividualResultsFeed&rd={}&cr={}'.format(self.rd, cr)
resp = utils.request('GET', url, session=session)
# parse
def get_text_at(node: xml.etree.ElementTree.Element, i: int) -> str:
text = list(node)[i].text
if text is None:
raise ValueError
return text
root = xml.etree.ElementTree.fromstring(resp.content.decode(resp.encoding))
assert len(list(root)) == 5
round_id = int(get_text_at(root, 0))
coder_id = int(get_text_at(root, 1))
handle = get_text_at(root, 2)
submissions = [] # type: List[TopcoderLongContestProblemIndividualResultsFeedSubmission]
for submission in list(root)[3]:
number = int(get_text_at(submission, 0))
score = float(get_text_at(submission, 1))
language = get_text_at(submission, 2)
time = get_text_at(submission, 3)
submissions += [TopcoderLongContestProblemIndividualResultsFeedSubmission(number, score, language, time)]
testcases = [] # type: List[TopcoderLongContestProblemIndividualResultsFeedTestCase]
for testcase in list(root)[4]:
test_case_id = int(get_text_at(testcase, 0))
score = float(get_text_at(testcase, 1))
processing_time = int(get_text_at(testcase, 2))
fatal_error_ind = int(get_text_at(testcase, 3))
testcases += [TopcoderLongContestProblemIndividualResultsFeedTestCase(test_case_id, score, processing_time, fatal_error_ind)]
return TopcoderLongContestProblemIndividualResultsFeed(round_id, coder_id, handle, submissions, testcases)
[docs] def download_system_test(self, *, test_case_id: int, session: Optional[requests.Session] = None) -> str:
"""
:raises NotLoggedInError:
:note: You need to parse this result manually.
.. versionadded:: 6.2.0
This method may be deleted in future.
"""
session = session or utils.get_default_session()
# get
assert self.pm is not None
url = 'https://community.topcoder.com/longcontest/stats/?module=ViewSystemTest&rd={}&pm={}&tid={}'.format(self.rd, self.pm, test_case_id)
resp = utils.request('GET', url, session=session)
# parse
soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser)
if soup.find('form', attrs={'name': 'frmLogin'}):
raise NotLoggedInError
return soup.find('pre').text
onlinejudge.dispatch.services += [TopcoderService]
onlinejudge.dispatch.problems += [TopcoderLongContestProblem]