Source code for onlinejudge.service.hackerrank

# Python Version: 3.x
"""
the module for HackerRank (https://www.hackerrank.com/)
"""

import json
import re
import urllib.parse
from typing import *

import bs4
import requests

import onlinejudge._implementation.logging as log
import onlinejudge._implementation.testcase_zipper
import onlinejudge._implementation.utils as utils
import onlinejudge.dispatch
import onlinejudge.type
from onlinejudge.type import *


[docs]class HackerRankService(onlinejudge.type.Service):
[docs] def get_url_of_login_page(self) -> str: return 'https://www.hackerrank.com/auth/login'
[docs] def is_logged_in(self, *, session: Optional[requests.Session] = None) -> bool: session = session or utils.get_default_session() url = 'https://www.hackerrank.com/auth/login' resp = utils.request('GET', url, session=session) return '/auth' not in resp.url
[docs] def get_url(self) -> str: return 'https://www.hackerrank.com/'
[docs] def get_name(self) -> str: return 'HackeRrank'
[docs] @classmethod def from_url(cls, url: str) -> Optional['HackerRankService']: # example: https://www.hackerrank.com/dashboard result = urllib.parse.urlparse(url) if result.scheme in ('', 'http', 'https') \ and result.netloc in ('hackerrank.com', 'www.hackerrank.com'): return cls() return None
[docs]class HackerRankProblem(onlinejudge.type.Problem): def __init__(self, contest_slug: str, challenge_slug: str): self.contest_slug = contest_slug self.challenge_slug = challenge_slug
[docs] def download_sample_cases(self, *, session: Optional[requests.Session] = None) -> List[TestCase]: """ :raises NotImplementedError: """ log.warning('use --system option') raise NotImplementedError
[docs] def download_system_cases(self, *, session: Optional[requests.Session] = None) -> List[TestCase]: session = session or utils.get_default_session() # example: https://www.hackerrank.com/rest/contests/hourrank-1/challenges/beautiful-array/download_testcases url = 'https://www.hackerrank.com/rest/contests/{}/challenges/{}/download_testcases'.format(self.contest_slug, self.challenge_slug) resp = utils.request('GET', url, session=session, raise_for_status=False) if resp.status_code != 200: log.error('response: %s', resp.content.decode()) return [] return onlinejudge._implementation.testcase_zipper.extract_from_zip(resp.content, '%eput/%eput%s.txt')
[docs] def get_url(self) -> str: if self.contest_slug == 'master': return 'https://www.hackerrank.com/challenges/{}'.format(self.challenge_slug) else: return 'https://www.hackerrank.com/contests/{}/challenges/{}'.format(self.contest_slug, self.challenge_slug)
[docs] def get_service(self) -> HackerRankService: return HackerRankService()
[docs] @classmethod def from_url(cls, url: str) -> Optional['HackerRankProblem']: # example: https://www.hackerrank.com/contests/university-codesprint-2/challenges/the-story-of-a-tree # example: https://www.hackerrank.com/challenges/fp-hello-world result = urllib.parse.urlparse(url) if result.scheme in ('', 'http', 'https') \ and result.netloc in ('hackerrank.com', 'www.hackerrank.com'): m = re.match(r'^/contests/([0-9A-Za-z-]+)/challenges/([0-9A-Za-z-]+)$', utils.normpath(result.path)) if m: return cls(contest_slug=m.group(1), challenge_slug=m.group(2)) m = re.match(r'^/challenges/([0-9A-Za-z-]+)$', utils.normpath(result.path)) if m: return cls(contest_slug='master', challenge_slug=m.group(1)) return None
def _get_model(self, *, session: Optional[requests.Session] = None) -> Dict[str, Any]: """ :raises SubmissionError: """ session = session or utils.get_default_session() # get url = 'https://www.hackerrank.com/rest/contests/{}/challenges/{}'.format(self.contest_slug, self.challenge_slug) resp = utils.request('GET', url, session=session) # parse it = json.loads(resp.content.decode()) log.debug('json: %s', it) if not it['status']: log.error('get model: failed') raise SubmissionError return it['model'] def _get_lang_display_mapping(self, *, session: Optional[requests.Session] = None) -> Dict[str, str]: session = session or utils.get_default_session() # get url = 'https://hrcdn.net/hackerrank/assets/codeshell/dist/codeshell-cdffcdf1564c6416e1a2eb207a4521ce.js' # at "Mon Feb 4 14:51:27 JST 2019" resp = utils.request('GET', url, session=session) # parse s = resp.content.decode() l = s.index('lang_display_mapping:{c:"C",') l = s.index('{', l) r = s.index('}', l) + 1 s = s[l:r] log.debug('lang_display_mapping (raw): %s', s) # this is not a json lang_display_mapping = {} for lang in s[1:-2].split('",'): key, value = lang.split(':"') lang_display_mapping[key] = value log.debug('lang_display_mapping (parsed): %s', lang_display_mapping) return lang_display_mapping
[docs] def get_available_languages(self, *, session: Optional[requests.Session] = None) -> List[Language]: session = session or utils.get_default_session() info = self._get_model(session=session) lang_display_mapping = self._get_lang_display_mapping() result = [] # type: List[Language] for lang in info['languages']: descr = lang_display_mapping.get(lang) if descr is None: log.warning('display mapping for language `%s\' not found', lang) descr = lang result += [Language(lang, descr)] return result
[docs] def submit_code(self, code: bytes, language_id: LanguageId, *, filename: Optional[str] = None, session: Optional[requests.Session] = None) -> onlinejudge.type.Submission: """ :raises NotLoggedInError: :raises SubmissionError: """ session = session or utils.get_default_session() if not self.get_service().is_logged_in(session=session): raise NotLoggedInError # get resp = utils.request('GET', self.get_url(), session=session) # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) csrftoken = soup.find('meta', attrs={'name': 'csrf-token'}).attrs['content'] # post url = 'https://www.hackerrank.com/rest/contests/{}/challenges/{}/submissions'.format(self.contest_slug, self.challenge_slug) payload = {'code': code, 'language': str(language_id), 'contest_slug': self.contest_slug} log.debug('payload: %s', payload) resp = utils.request('POST', url, session=session, json=payload, headers={'X-CSRF-Token': csrftoken}) # parse it = json.loads(resp.content.decode()) log.debug('json: %s', it) if not it['status']: log.failure('Submit Code: failed') raise SubmissionError model_id = it['model']['id'] url = self.get_url().rstrip('/') + '/submissions/code/{}'.format(model_id) log.success('success: result: %s', url) return utils.DummySubmission(url, problem=self)
onlinejudge.dispatch.services += [HackerRankService] onlinejudge.dispatch.problems += [HackerRankProblem]