def login(self, get_credentials: onlinejudge.type.CredentialsProvider, session: Optional[requests.Session] = None) -> None: """ :raises LoginError: """ session = session or utils.new_default_session() url = 'https://codeforces.com/enter' # get resp = utils.request('GET', url, session=session) if resp.url != url: # redirected log.info('You have already signed in.') return # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) form = soup.find('form', id='enterForm') log.debug('form: %s', str(form)) username, password = get_credentials() form = utils.FormSender(form, url=resp.url) form.set('handleOrEmail', username) form.set('password', password) form.set('remember', 'on') # post resp = form.request(session) resp.raise_for_status() if resp.url != url: # redirected log.success('Welcome, %s.', username) else: log.failure('Invalid handle or password.') raise LoginError('Invalid handle or password.')
def generate_scanner(args: 'argparse.Namespace') -> None: if not args.silent: log.warning('This feature is ' + log.red('experimental') + '.') if args.silent: for handler in log.logger.handlers: log.removeHandler(handler) problem = onlinejudge.dispatch.problem_from_url(args.url) if problem is None: sys.exit(1) with utils.with_cookiejar(utils.new_default_session(), path=args.cookie) as sess: it = problem.get_input_format(session=sess) # type: Any if not it: log.error('input format not found') sys.exit(1) try: log.debug('original data: %s', repr(it)) it = list(tokenize(it)) log.debug('tokenized: %s', str(it)) it = list(parse(it)) log.debug('parsed: %s', str(it)) it = postprocess(it) log.debug('postprocessed: %s', str(it)) it = export(it, use_scanf=args.scanf, repeat_macro=args.repeat_macro) log.debug('result: %s', repr(it)) except: log.error('something wrong') raise log.success('success:') print(log.bold(it.rstrip())) # to stdout
def download_sample_cases( self, session: Optional[requests.Session] = None ) -> List[onlinejudge.type.TestCase]: 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) samples = onlinejudge._implementation.testcase_zipper.SampleZipper() for tag in soup.find_all('div', class_=re.compile( '^(in|out)put$')): # Codeforces writes very nice HTML :) log.debug('tag: %s', str(tag)) assert len(list(tag.children)) title, pre = list(tag.children) assert 'title' in title.attrs['class'] assert pre.name == 'pre' s = '' for it in pre.children: if it.name == 'br': s += '\n' else: s += it.string s = s.lstrip() samples.add(s.encode(), title.string) return samples.get()
def get_available_languages( self, session: Optional[requests.Session] = None) -> List[Language]: """ :raises NotLoggedInError: """ session = session or utils.new_default_session() # get url = 'http://{}.contest.atcoder.jp/submit'.format(self.contest_id) resp = _request('GET', url, session=session) msgs = AtCoderService._get_messages_from_cookie(resp.cookies) if AtCoderService._report_messages(msgs, unexpected=True): return [] # check whether logged in path = utils.normpath(urllib.parse.urlparse(resp.url).path) if path.startswith('/login'): log.error('not logged in') raise NotLoggedInError # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) select = soup.find( 'select', class_='submit-language-selector' ) # NOTE: AtCoder can vary languages depending on tasks, even in one contest. here, ignores this fact. languages = [] # type: List[Language] for option in select.find_all('option'): languages += [Language(option.attrs['value'], option.string)] return languages
def login(self, get_credentials: onlinejudge.type.CredentialsProvider, session: Optional[requests.Session] = None) -> None: """ :raises LoginError: """ session = session or utils.new_default_session() url = 'https://practice.contest.atcoder.jp/login' # get resp = _request('GET', url, session=session, allow_redirects=False) msgs = AtCoderService._get_messages_from_cookie(resp.cookies) for msg in msgs: log.status('message: %s', msg) if msgs: if 'login' not in resp.url: return # redirect means that you are already logged in else: raise LoginError('something wrong: ' + str(msgs)) # post username, password = get_credentials() resp = _request('POST', url, session=session, data={ 'name': username, 'password': password }, allow_redirects=False) msgs = AtCoderService._get_messages_from_cookie(resp.cookies) AtCoderService._report_messages(msgs) if 'login' not in resp.url: pass # AtCoder redirects to the top page if success else: raise LoginError('your password may be not correct: ' + str(msgs))
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 _load_details(self, session: Optional[requests.Session] = None) -> None: session = session or utils.new_default_session() # get resp = _request('GET', self.get_url(type='beta', lang='ja'), session=session) soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) # parse h2 = soup.find('span', class_='h2') self._alphabet, _, self._task_name = h2.text.partition(' - ') time_limit, memory_limit = h2.find_next_sibling('p').text.split(' / ') self._time_limit_msec = int( utils.remove_suffix(utils.remove_prefix(time_limit, '実行時間制限: '), ' sec')) * 1000 self._memory_limit_byte = int( utils.remove_suffix(utils.remove_prefix(memory_limit, 'メモリ制限: '), ' MB')) * 1000 * 1000 task_statement = soup.find('div', id='task-statement') p = task_statement.find('p') # first if p is not None and p.text.startswith('配点 : '): self._score = int( utils.remove_suffix(utils.remove_prefix(p.text, '配点 : '), ' 点')) self._score_checked = True
def download_sample_cases( self, session: Optional[requests.Session] = None ) -> List[onlinejudge.type.TestCase]: session = session or utils.new_default_session() # get resp = _request('GET', self.get_url(), session=session) msgs = AtCoderService._get_messages_from_cookie(resp.cookies) if AtCoderService._report_messages(msgs, unexpected=True): # example message: "message: You cannot see this page." log.warning('are you logged in?') return [] # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) samples = onlinejudge._implementation.testcase_zipper.SampleZipper() lang = None for pre, h3 in self._find_sample_tags(soup): s = utils.textfile(utils.dos2unix(pre.string.lstrip())) name = h3.string l = self._get_tag_lang(pre) if lang is None: lang = l elif lang != l: log.info( 'skipped due to language: current one is %s, not %s: %s ', lang, l, name) continue samples.add(s.encode(), name) return samples.get()
def get_available_languages( self, session: Optional[requests.Session] = None) -> List[Language]: """ :raises NotLoggedInError: """ session = session or utils.new_default_session() # get resp = _request('GET', self.get_url(type='beta'), session=session) # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) form = soup.find('form', action='/contests/{}/submit'.format(self.contest_id)) if form is None: log.error('not logged in') raise NotLoggedInError # parse select = form.find('div', id='select-lang').find( 'select', attrs={'name': 'data.LanguageId'} ) # NOTE: AtCoder can vary languages depending on tasks, even in one contest. here, ignores this fact. languages = [] # type: List[Language] for option in select.find_all('option'): languages += [Language(option.attrs['value'], option.string)] return languages
def _load_details(self, session: Optional[requests.Session] = None, lang: Optional[str] = None): session = session or utils.new_default_session() resp = _request('GET', self.get_url(type='beta', lang=lang), session=session) soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) contest_name, _, _ = soup.find('title').text.rpartition(' - ') contest_duration = soup.find('small', class_='contest-duration') self._start_time, end_time = [ self._parse_start_time(a['href']) for a in contest_duration.find_all('a') ] self._duration = end_time - self._start_time if lang == 'en': self._contest_name_en = contest_name elif lang == 'ja': self._contest_name_ja = contest_name else: assert False _, _, self._can_participate = soup.find( 'span', text=re.compile(r'^(Can Participate|参加対象): ')).text.partition(': ') _, _, self._rated_range = soup.find( 'span', text=re.compile(r'^(Rated Range|Rated対象): ')).text.partition(': ') penalty_text = soup.find('span', text=re.compile(r'^(Penalty|ペナルティ): ')).text m = re.match(r'(Penalty|ペナルティ): (\d+)( minutes|分)', penalty_text) assert m self._penalty = datetime.timedelta(minutes=int(m.group(2)))
def get_input_format(self, session: Optional[requests.Session] = None) -> str: """ :raises Exception: if no such problem exists """ session = session or utils.new_default_session() # get resp = _request('GET', self.get_url(type='beta'), raise_for_status=False, session=session) if _list_alert(resp): log.warning('are you logged in?') resp.raise_for_status() # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) for h3 in soup.find_all('h3'): if h3.string in ('入力', 'Input'): tag = h3 for _ in range(3): tag = utils.next_sibling_tag(tag) if tag is None: break if tag.name in ('pre', 'blockquote'): s = '' for it in tag: s += it.string or it # AtCoder uses <var>...</var> for math symbols return s return ''
def iterate_contests( self, lang: str = 'ja', session: Optional[requests.Session] = None ) -> Generator['AtCoderContest', None, None]: """ :param lang: must be `ja` (default) or `en`. :note: `lang=ja` is required to see some Japanese-local contests. :note: You can use `lang=en` to see the English names of contests. """ assert lang in ('ja', 'en') session = session or utils.new_default_session() last_page = None for page in itertools.count(1): # 1-based if last_page is not None and page > last_page: break # get url = 'https://atcoder.jp/contests/archive?lang={}&page={}'.format( lang, page) resp = _request('GET', url, session=session) # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) if last_page is None: last_page = int( soup.find('ul', class_='pagination').find_all('li')[-1].text) log.debug('last page: %s', last_page) tbody = soup.find('tbody') for tr in tbody.find_all('tr'): yield AtCoderContest._from_table_row(tr, lang=lang)
def download_sample_cases( self, session: Optional[requests.Session] = None ) -> List[onlinejudge.type.TestCase]: session = session or utils.new_default_session() # get url = self.get_url(contests=False) + '/file/statement/samples.zip' resp = utils.request('GET', url, session=session, raise_for_status=False) if resp.status_code == 404: log.warning('samples.zip not found') log.info( 'this 404 happens in both cases: 1. no sample cases as intended; 2. just an error' ) return [] resp.raise_for_status() # parse with zipfile.ZipFile(io.BytesIO(resp.content)) as fh: samples = [] # type: List[TestCase] for filename in sorted(fh.namelist()): log.debug('filename: %s', filename) if filename.endswith('.in'): inpath = filename outpath = filename[:-3] + '.ans' indata = fh.read(inpath).decode() outdata = fh.read(outpath).decode() samples += [ TestCase(LabeledString(inpath, indata), LabeledString(outpath, outdata)) ] return samples
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 != 200
def submit_code(self, code: bytes, language_id: LanguageId, filename: Optional[str] = None, session: Optional[requests.Session] = None) -> Submission: """ :raises NotLoggedInError: :raises SubmissionError: """ assert language_id in [ language.id for language in self.get_available_languages(session=session) ] session = session or utils.new_default_session() # get url = 'http://{}.contest.atcoder.jp/submit'.format( self.contest_id) # TODO: use beta.atcoder.jp resp = _request('GET', url, session=session) msgs = AtCoderService._get_messages_from_cookie(resp.cookies) if AtCoderService._report_messages(msgs, unexpected=True): raise SubmissionError # check whether logged in path = utils.normpath(urllib.parse.urlparse(resp.url).path) if path.startswith('/login'): log.error('not logged in') raise NotLoggedInError # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) form = soup.find('form', action=re.compile(r'^/submit\?task_id=')) if not form: log.error('form not found') raise SubmissionError log.debug('form: %s', str(form)) # post task_id = self._get_task_id(session=session) form = utils.FormSender(form, url=resp.url) form.set('task_id', str(task_id)) form.set('source_code', code) form.set('language_id_{}'.format(task_id), str(language_id)) resp = form.request(session=session) resp.raise_for_status() # result msgs = AtCoderService._get_messages_from_cookie(resp.cookies) AtCoderService._report_messages(msgs) if '/submissions/me' in resp.url: # example: https://practice.contest.atcoder.jp/submissions/me#32174 # CAUTION: this URL is not a URL of the submission log.success('success: result: %s', resp.url) # NOTE: ignore the returned legacy URL and use beta.atcoder.jp's one url = 'https://beta.atcoder.jp/contests/{}/submissions/me'.format( self.contest_id) return utils.DummySubmission(url, problem=self) else: log.failure('failure') log.debug('redirected to %s', resp.url) raise SubmissionError('it may be a rate limit')
def get_available_languages(self, session: Optional[requests.Session] = None) -> List[Language]: session = session or utils.new_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'), ]
def login_with_twitter( self, get_credentials: onlinejudge.type.CredentialsProvider, session: Optional[requests.Session] = None) -> None: """ :raise NotImplementedError: always raised """ session = session or utils.new_default_session() url = 'https://yukicoder.me/auth/twitter' raise NotImplementedError
def is_logged_in(self, session: Optional[requests.Session] = None, method: Optional[str] = None) -> bool: session = session or utils.new_default_session() url = 'https://yukicoder.me/auth/github' resp = utils.request('GET', url, session=session, allow_redirects=False) assert resp.status_code == 302 return 'oauth' not in resp.headers['Location']
def get_input_format(self, session: Optional[requests.Session] = None ) -> Optional[str]: 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) for h4 in soup.find_all('h4'): if h4.string == '入力': return h4.parent.find('pre').string return None
def download_sample_cases(self, session: Optional[requests.Session] = None) -> List[onlinejudge.type.TestCase]: 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) samples = onlinejudge._implementation.testcase_zipper.SampleZipper() for h2 in soup.find_all('h2'): it = self._parse_sample_tag(h2) if it is not None: s, name = it samples.add(s.encode(), name) return samples.get()
def download_sample_cases( self, session: Optional[requests.Session] = None) -> List[TestCase]: 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) in_pre, out_pre = soup.find_all('pre', class_='sio') in_p = in_pre.find_previous_sibling('p', class_='pst') out_p = out_pre.find_previous_sibling('p', class_='pst') log.debug('pre (in): %s', in_pre.contents) log.debug('pre (out): %s', out_pre.contents) assert in_p.text.strip() == 'Sample Input' assert out_p.text.strip() == 'Sample Output' assert len(in_pre.contents) == len(out_pre.contents) samples = [] # type: List[TestCase] if len(in_pre.contents) == 1: assert isinstance(in_pre.contents[0], bs4.NavigableString) assert isinstance(out_pre.contents[0], bs4.NavigableString) samples += [ TestCase( 'sample', in_p.text.strip(), in_pre.text.encode() + b'\r\n', out_p.text.strip(), out_pre.text.encode() + b'\r\n', ) ] else: assert len(in_pre.contents) % 2 == 0 for i in range(len(in_pre.contents) // 2): in_name = in_pre.contents[2 * i] in_data = in_pre.contents[2 * i + 1] out_name = out_pre.contents[2 * i] out_data = out_pre.contents[2 * i + 1] assert in_name.name == 'b' assert isinstance(in_data, bs4.NavigableString) assert out_name.name == 'b' assert isinstance(out_data, bs4.NavigableString) samples += [ TestCase( 'sample-{}'.format(i + 1), in_name.text.strip(), str(in_data).strip().encode() + b'\r\n', out_name.text.strip(), str(out_data).strip().encode() + b'\r\n', ) ] return samples
def submit_code(self, code: bytes, language_id: LanguageId, filename: Optional[str] = None, session: Optional[requests.Session] = None) -> Submission: """ :raises NotLoggedInError: :raises SubmissionError: """ assert language_id in [ language.id for language in self.get_available_languages(session=session) ] session = session or utils.new_default_session() # get url = 'https://atcoder.jp/contests/{}/submit'.format(self.contest_id) resp = _request('GET', url, session=session) # check whether logged in if 'login' in resp.url: raise NotLoggedInError # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) form = soup.find('form', action='/contests/{}/submit'.format(self.contest_id)) if not form: raise SubmissionError('something wrong') log.debug('form: %s', str(form)) # post form = utils.FormSender(form, url=resp.url) form.set('data.TaskScreenName', self.problem_id) form.set('data.LanguageId', str(language_id)) form.set('sourceCode', code) resp = form.request(session=session) _list_alert(resp, print_=True) # result if '/submissions/me' in resp.url: # example: https://practice.contest.atcoder.jp/submissions/me#32174 # CAUTION: this URL is not a URL of the submission log.success('success: result: %s', resp.url) return utils.DummySubmission(resp.url, problem=self) else: raise SubmissionError('it may be a rate limit')
def get_available_languages( self, session: Optional[requests.Session] = None) -> List[Language]: session = session or utils.new_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
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.new_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)
def download_system_cases( self, session: Optional[requests.Session] = None) -> List[TestCase]: session = session or utils.new_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')
def list_problems( self, session: Optional[requests.Session] = None ) -> List['AtCoderProblem']: # get session = session or utils.new_default_session() url = 'https://atcoder.jp/contests/{}/tasks'.format(self.contest_id) resp = _request('GET', url, session=session) # parse soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) tbody = soup.find('tbody') return [ AtCoderProblem._from_table_row(tr) for tr in tbody.find_all('tr') ]
def download_system_cases( self, session: Optional[requests.Session] = None) -> List[TestCase]: """ :raises NotLoggedInError: """ session = session or utils.new_default_session() if not self.get_service().is_logged_in(session=session): raise NotLoggedInError url = 'https://yukicoder.me/problems/no/{}/testcase.zip'.format( self.problem_no) resp = utils.request('GET', url, session=session) fmt = 'test_%e/%s' return onlinejudge._implementation.testcase_zipper.extract_from_zip( resp.content, fmt)
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.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', class_='submitForm') if form is None: log.error('not logged in') raise NotLoggedInError log.debug('form: %s', str(form)) # make data form = utils.FormSender(form, url=resp.url) form.set('programTypeId', language_id) form.set_file('sourceFile', filename or 'code', code) resp = form.request(session=session) resp.raise_for_status() # result if resp.url.endswith('/my'): # example: https://codeforces.com/contest/598/my log.success('success: result: %s', resp.url) return utils.DummySubmission(resp.url, problem=self) else: log.failure('failure') # parse error messages soup = bs4.BeautifulSoup(resp.content.decode(resp.encoding), utils.html_parser) msgs = [] # type: List[str] for span in soup.findAll('span', class_='error'): msgs += [span.string] log.warning('Codeforces says: "%s"', span.string) raise SubmissionError( 'it may be the "You have submitted exactly the same code before" error: ' + str(msgs))
def download_sample_cases( self, session: Optional[requests.Session] = None) -> List[TestCase]: 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) samples = onlinejudge._implementation.testcase_zipper.SampleZipper() for pre in soup.select('.sample pre'): log.debug('pre: %s', str(pre)) it = self._parse_sample_tag(pre) if it is not None: data, name = it samples.add(data.encode(), name) return samples.get()
def login(args: 'argparse.Namespace') -> None: # get service service = onlinejudge.dispatch.service_from_url(args.url) if service is None: sys.exit(1) # configure kwargs = {} if service.get_name() == 'yukicoder': if not args.method: args.method = 'github' if args.method not in ['github', 'twitter']: log.failure('login for yukicoder: invalid option: --method %s', args.method) sys.exit(1) kwargs['method'] = args.method else: if args.method: log.failure('login for %s: invalid option: --method %s', service.get_name(), args.method) sys.exit(1) with utils.with_cookiejar(utils.new_default_session(), path=args.cookie) as sess: if args.check: if service.is_logged_in(session=sess): log.info('You have already signed in.') else: log.info('You are not signed in.') sys.exit(1) else: # login def get_credentials() -> Tuple[str, str]: if args.username is None: args.username = input('Username: '******'If you don\'t want to give your password to this program, you can give only your session tokens.') log.info('see: https://github.com/kmyk/online-judge-tools/blob/master/LOGIN_WITH_COOKIES.md') try: service.login(get_credentials, session=sess, **kwargs) # type: ignore except onlinejudge.type.LoginError: pass