#!/Users/slipperl/miniforge3/envs/boost/bin/python import ast import base64 import json import logging import os import pprint import random import re import sys import time from bisect import bisect_left import arrow import requests from bs4 import BeautifulSoup from termcolor import colored FORMAT = '[%(asctime)-15s] %(message)s' logging.basicConfig(format=FORMAT, level=logging.INFO) class LeetCode(): def __init__(self, header_file='data/xiuheader.dat'): self.s = requests.Session() headers = self._get_config(header_file) self.s.headers.update(headers) def _update_question_id(self): res = requests.Session().get('https://leetcode.com/api/problems/all') # print(res, res.text) json.dump(res.json(), open('data/question_ids.json', 'w'), indent=2) def _get_config(self, head_file): '''Load config (head/cookie) from file ''' config = {} with open(head_file, 'r') as f: for line in f.readlines(): k, v = line.rstrip().split(": ")[:2] config[k] = v.strip() for ck in config['cookie'].split(';'): if ck.startswith('csrftoken'): config['x-csrftoken'] = ck.lstrip('csrftoken=') break # print(config) return config def _read_code_file(self, fname='test.py'): # Read codes from file, and ignore all debug lines, # e.g., those with print commands . with open(fname) as f: res = "" for line in f.readlines(): if line.startswith(('int main', 'struct Solution')): return res if 'print(' in line or 'say' in line or 'include' in line: continue res += line return res def _get_question_id(self, code_file, early_stop=False): '''Frontend question id can be different from real question id. ''' js = json.load(open('data/question_ids.json', 'r')) frontend_id = code_file.split('/')[-1].split('.')[0] for s in js['stat_status_pairs']: if s['stat']['frontend_question_id'] == int(frontend_id): return s['stat']['question_id'] if early_stop: logging.warning( "No backend id found from API. Please check your file name.") else: logging.warning( "Found no backend id for current question. Update from API...") # self._update_question_id() return self._get_id_from_sql(code_file) def _get_id_from_sql(self, code_file): '''Get Question ID from graphql ''' title = '.'.join(code_file.split('/')[-1].split('.')[1:-1]) print(title) data = { "operationName": "questionData", "variables": { "titleSlug": title }, "query": "query questionData($titleSlug: String!) {\n question(titleSlug: $titleSlug) {\n questionId\n questionFrontendId\n boundTopicId\n title\n titleSlug\n content\n translatedTitle\n translatedContent\n isPaidOnly\n difficulty\n likes\n dislikes\n isLiked\n similarQuestions\n contributors {\n username\n profileUrl\n avatarUrl\n __typename\n }\n topicTags {\n name\n slug\n translatedName\n __typename\n }\n companyTagStats\n codeSnippets {\n lang\n langSlug\n code\n __typename\n }\n stats\n hints\n solution {\n id\n canSeeDetail\n paidOnly\n __typename\n }\n status\n sampleTestCase\n metaData\n judgerAvailable\n judgeType\n mysqlSchemas\n enableRunCode\n enableTestMode\n enableDebugger\n envInfo\n libraryUrl\n adminUrl\n __typename\n }\n}\n" } url = 'https://leetcode.com/graphql' res = requests.Session().post(url, json=data, verify=False) # print(res.text) return res.json()['data']['question']['questionId'] def _get_result(self, submission_id, repeat=3): # In case the result has not been loaded, we shall repeat if repeat <= 0: return # s = '🕐🕑🕒🕓🕔🕕🕖🕗🕘🕙🕚🕛🕜🕝🕞🕟🕠🕡🕢🕣🕤🕥🕦🕧' headers = { 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:79.0) Gecko/20100101 Firefox/79.0' } self.s.headers = headers for c in ('/-\|' * 75): print(c, end='\r') time.sleep(0.01) try: res = self.s.get( f'https://oj.leetcode.com/submissions/detail/{submission_id}/') # print(res, res.text) pagedata = re.findall(r"pageData = (\{.*\}?);\nif", res.text, re.DOTALL)[0].replace("\\u0022", '') except Exception as e: print(e) return correct = re.findall(r"total\_correct[\s*]: (.*),", pagedata) if not correct: time.sleep(3) self._get_result(submission_id, repeat - 1) return total = re.findall(r"total\_testcases[\s*]: (.*),", pagedata)[0] correct, total = int(correct[0].replace("'", "")), int( total.replace("'", "")) msg = f"Passed testcases {correct} / {total}" logging.info(msg) if correct != total: logging.info("\u274C wrong answer.") for uni in ("\\u002D", "\\u000A", "\\u0027", "\\u003B"): pagedata = pagedata.replace(uni, "") print("input: ", re.findall(r"input[\s*]: (.*),", pagedata)) print("output: ", re.findall(r"code_output[\s*]: (.*),", pagedata)) print("expected: ", re.findall(r"expected_output[\s*]: (.*),", pagedata)) compile_error = re.findall(r"compile_error[\s*]: (.*),", pagedata) runtime_error = re.findall(r"runtime_error[\s*]: (.*),", pagedata) last_testcase = re.findall(r"last_testcase[\s*]: (.*),", pagedata) for e, msg in ('compile error', compile_error), ('runtime error', runtime_error), ('last testcase', last_testcase): if msg: print(e, msg) else: # logging.info(f"\u2705 Accepted, all {total} testcased were passed.") t_str = colored('Accepted', 'green') + f", all {total} testcased were passed." logging.info(t_str) runtime = re.findall(r"runtime: (.*)[,*]", pagedata)[-1].replace("'", '') int_runtime = int(runtime) dis = re.findall(r"distribution: (.*)\}", pagedata) if not dis: return runtime_dis = ast.literal_eval(dis[0]) idx = bisect_left(runtime_dis, [ int_runtime, ]) ratio = sum(map(lambda p: p[1], runtime_dis[:idx])) msg = f"Running time {runtime} ms, beats {100 - ratio} %" msg2 = f"Fastest algorithm runs in time {runtime_dis[0][0]} ms" logging.info(msg) logging.info(msg2) os.system('rm tmp/*') for tim, _ in runtime_dis[:5]: self._show_others_code(tim) # self._show_others_code(runtime_dis[0][0], '76', 'python3') def _show_others_code(self, rt_value): # To show codes from other codes' submissions # rt_value runtime value code_url = f"https://leetcode.com/submissions/api/detail/{self.question_id}/{self.language}/{rt_value}/" # print(code_url) res = self.s.get(code_url) with open(f"tmp/{rt_value}_{self.language}_{self.question_id}.json", 'w') as f: pprint.pprint(res.json(), f) # json.dump(res.json(), f, indent=2) def submit_answer(self, code_file): code = self._read_code_file(code_file) # bid = str(self._get_question_id(code_file)) # backend id bid = str(self._get_id_from_sql(code_file)) # Set submitted language. Either python3, cpp or rust. langs = { 'py': 'python3', 'cpp': 'cpp', 'rs': 'rust', 'sql': 'mysql', 'c': 'c' } code_language = langs[code_file.split('.')[-1]] # For showing other's code self.language = code_language self.question_id = bid idata = { "question_id": bid, "data_input": "", "lang": code_language, "typed_code": code, "test_mode": False, "judge_type": "large" } logging.info('Submitting file %s ', code_file) logging.info('Backend id %s ', str(bid)) res = self.s.post('https://leetcode.com/problems/two-sum/submit/', json=idata, verify=False) msg = str(res) + res.text logging.info(msg) logging.info('Submission result copied to clipboard.') os.system( f"echo https://leetcode.com/submissions/detail/{res.json()['submission_id']}/ | pbcopy" ) self._get_result(res.json()['submission_id']) if __name__ == "__main__": code_file = sys.argv[1] lc = LeetCode('local/leetcode_header.dat') lc.submit_answer(code_file)