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

Support toph #323

Merged
merged 18 commits into from
Feb 26, 2019
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
__pycache__
test
.vscode
kmyk marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions onlinejudge/service/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@
import onlinejudge.service.kattis
import onlinejudge.service.poj
import onlinejudge.service.topcoder
import onlinejudge.service.toph
import onlinejudge.service.yukicoder
169 changes: 169 additions & 0 deletions onlinejudge/service/toph.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# Python Version: 3.x
import posixpath
import string
import urllib.parse
from typing import *

import bs4
import requests
import re

import onlinejudge.dispatch
import onlinejudge.implementation.logging as log
import onlinejudge.implementation.utils as utils
import onlinejudge.type
from onlinejudge.type import SubmissionError


@utils.singleton
class TophService(onlinejudge.type.Service):
def login(self, get_credentials: onlinejudge.type.CredentialsProvider, session: Optional[requests.Session] = None) -> bool:
session = session or utils.new_default_session()
url = 'https://toph.co/login'
# get
resp = utils.request('GET', url, session=session)
if resp.url != url: # redirected
log.info('You are already logged in.')
return True
# parse
soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser)
form = soup.find('form', class_='login-form')
log.debug('form: %s', str(form))
username, password = get_credentials()
form['action'] = '/login' # to avoid KeyError inside form.request method as Toph does not have any defined action
form = utils.FormSender(form, url=resp.url)
form.set('handle', username)
form.set('password', password)
# post
resp = form.request(session)
resp.raise_for_status()

newResp = utils.request('GET', url, session=session) # Toph's Location header is not getting the expected value
if newResp.url != url:
kmyk marked this conversation as resolved.
Show resolved Hide resolved
log.success('Welcome, %s.', username)
return True
else:
log.failure('Invalid handle/email or password.')
return False

def is_logged_in(self, session: Optional[requests.Session] = None) -> bool:
session = session or utils.new_default_session()
url = 'https://toph.co/login'
resp = utils.request('GET', url, session=session, allow_redirects=False)
return resp.status_code == 302
kmyk marked this conversation as resolved.
Show resolved Hide resolved

def get_url(self) -> str:
return 'https://toph.co/'

def get_name(self) -> str:
return 'toph'

@classmethod
def from_url(cls, s: str) -> Optional['TophService']:
# example: https://toph.co/
# example: http://toph.co/
result = urllib.parse.urlparse(s)
if result.scheme in ('', 'http', 'https') \
and result.netloc == 'toph.co':
return cls()
return None

class TophProblem(onlinejudge.type.Problem):
def __init__(self, slug: str, kind: Optional[str] = None):
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
def __init__(self, slug: str, kind: Optional[str] = None):
def __init__(self, problem_id: str, contest_id: Optional[str] = None):
if contest_id is not None:
raise NotImplementedError

This argument kind is for the problems in the contest mode, right?
When we support the contest mode, it seems that this class need to have something like contest_id since the urls of problems in contets are like https://toph.co/c/tough-dash-feb-2019/arena#!/p/set-union .
So, we should use such names from now, and mark it as not-implemented explicitly.

assert isinstance(slug, str)
assert kind in (None, 'problem')
self.kind = kind
self.slug = slug

def download_sample_cases(self, session: Optional[requests.Session] = None) -> List[onlinejudge.type.TestCase]:
session = session or utils.new_default_session()
resp = utils.request('GET', self.get_url(), session=session)
soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser)
samples = utils.SampleZipper()
for case in soup.find('table', class_="samples").find('tbody').find_all('tr'):
log.debug('case: %s', str(case))
assert len(list(case.children))
input_pre, output_pre = list(map(lambda td: td.find('pre'), list(case.children)))
assert input_pre.name == 'pre'
assert output_pre.name == 'pre'
assert re.search("^preSample.*Input$", input_pre.attrs['id'])
assert re.search("^preSample.*Output$", output_pre.attrs['id'])
s = input_pre.get_text()
s = s.lstrip()
samples.add(s, "Input")
s = output_pre.get_text()
s = s.lstrip()
samples.add(s, "Output")
return samples.get()

def get_language_dict(self, session: Optional['requests.Session'] = None) -> Dict[str, onlinejudge.type.Language]:
session = session or utils.new_default_session()
# get
resp = utils.request('GET', self.get_url(), session=session)
# parse
soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser)
select = soup.find('select', attrs={'name': 'languageId'})
if select is None:
log.error('not logged in')
return {}
language_dict = {}
for option in select.findAll('option'):
language_dict[option.attrs['value']] = {'description': option.string}
kmyk marked this conversation as resolved.
Show resolved Hide resolved
return language_dict

def submit_code(self, code: bytes, language: str, session: Optional['requests.Session'] = None) -> onlinejudge.type.Submission: # or SubmissionError
session = session or utils.new_default_session()
# get
resp = utils.request('GET', self.get_url(), session=session)
# parse
soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser)
form = soup.find('form')
if form is None:
log.error('not logged in')
raise SubmissionError
log.debug('form: %s', str(form))
if form.find('select') and form.find('select').attrs['name'] != 'languageId':
log.error("Wrong submission URL")
raise SubmissionError

# make data
form = utils.FormSender(form, url=resp.url)
form.set('languageId', language)
form.set_file('source', 'code', code)
resp = form.request(session=session)
resp.raise_for_status()
# result
if '/s/' in resp.url:
# example: https://codeforces.com/contest/598/my
kmyk marked this conversation as resolved.
Show resolved Hide resolved
log.success('success: result: %s', resp.url)
return onlinejudge.type.DummySubmission(resp.url)
else:
log.failure('failure')
log.debug('redirected to %s', resp.url)
raise SubmissionError

def get_url(self) -> str:
table = {}
table['problem'] = 'https://toph.co/p/{}'
return table[self.kind].format(self.slug)

def get_service(self) -> TophService:
return TophService()

@classmethod
def from_url(cls, s: str) -> Optional['TophProblem']:
result = urllib.parse.urlparse(s)
dirname, basename = posixpath.split(utils.normpath(result.path))
if result.scheme in ('', 'http', 'https') \
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if result.scheme in ('', 'http', 'https') \
# example: https://toph.co/p/new-year-couple
if result.scheme in ('', 'http', 'https') \

and result.netloc.count('.') == 1 \
and result.netloc.endswith('toph.co') \
and dirname == '/p' \
and basename:
kind = 'problem'
slug = basename
return cls(slug, kind)

return None

onlinejudge.dispatch.services += [TophService]
onlinejudge.dispatch.problems += [TophProblem]
3 changes: 3 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Tools for online judge services. Downloading sample cases, Testing/Submitting yo
- CS Academy
- PKU JudgeOnline
- Kattis
- Toph (Problem Archive)
- Download system test cases
- Yukicoder
- Aizu Online Judge
Expand All @@ -36,12 +37,14 @@ Tools for online judge services. Downloading sample cases, Testing/Submitting yo
- Codeforces
- HackerRank
- TopCoder
- Toph
- Submit your solution
- AtCoder
- Yukicoder
- Codeforces
- HackerRank
- TopCoder (Marathon Match)
- Toph (Problem Archive)
- Generate scanner for input (experimental)
- AtCoder
- Yukicoder
Expand Down