|
| 1 | +"""The API for accessing LeetCode's front end.""" |
| 2 | + |
| 3 | +import json |
| 4 | +import re |
| 5 | +import urllib.error |
| 6 | +import urllib.parse |
| 7 | +import urllib.request |
| 8 | +from http.client import HTTPResponse |
| 9 | + |
| 10 | +from click import ClickException |
| 11 | + |
| 12 | +URL_TITLE_SLUG_PATTERN = re.compile( |
| 13 | + r"^https://leetcode\.com/problems/(?P<slug>[-\w]+)/" |
| 14 | +) |
| 15 | +TITLE_SLUG_PATTERN = re.compile(r"\w+(?:-\w+)+") |
| 16 | + |
| 17 | +LEETCODE_API_URL = "https://leetcode.com/graphql/" |
| 18 | +GRAPHQL_QUERY_TEMPLATE = """ |
| 19 | +query questionEditorData($titleSlug: String!) { |
| 20 | + question(titleSlug: $titleSlug) { |
| 21 | + # questionId |
| 22 | + # questionFrontendId |
| 23 | + codeSnippets { |
| 24 | + lang |
| 25 | + langSlug |
| 26 | + code |
| 27 | + } |
| 28 | + # envInfo |
| 29 | + # enableRunCode |
| 30 | + } |
| 31 | +} |
| 32 | +""" |
| 33 | +FAKE_USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11" |
| 34 | + |
| 35 | + |
| 36 | +def _validate_title_slug(title_slug: str) -> str: |
| 37 | + match = TITLE_SLUG_PATTERN.fullmatch(title_slug) |
| 38 | + if match is None: |
| 39 | + raise ClickException(f"Invalid title slug '{title_slug}'.") |
| 40 | + |
| 41 | + |
| 42 | +def _get_title_slug(problem_url: str) -> str: |
| 43 | + match = URL_TITLE_SLUG_PATTERN.match(problem_url) |
| 44 | + if match is None: |
| 45 | + raise ClickException(f"Invalid LeetCode problem URL '{problem_url}'.") |
| 46 | + title_slug = match.group("slug") |
| 47 | + return title_slug |
| 48 | + |
| 49 | + |
| 50 | +def _create_graphql_request(title_slug: str): |
| 51 | + """Creates a GraphQL query to fetch the problem code using the title slug.""" |
| 52 | + body = {"query": GRAPHQL_QUERY_TEMPLATE, "variables": {"titleSlug": title_slug}} |
| 53 | + body_bytes = bytes(json.dumps(body), encoding="utf-8") |
| 54 | + headers = {"User-Agent": FAKE_USER_AGENT, "Content-Type": "application/json"} |
| 55 | + request = urllib.request.Request( |
| 56 | + LEETCODE_API_URL, body_bytes, method="POST", headers=headers |
| 57 | + ) |
| 58 | + return request |
| 59 | + |
| 60 | + |
| 61 | +def _get_body_from_request(request: urllib.request.Request) -> dict: |
| 62 | + try: |
| 63 | + response: HTTPResponse = urllib.request.urlopen(request) |
| 64 | + except urllib.error.URLError as error: |
| 65 | + print(str(error)) |
| 66 | + raise ClickException(f"Could not get data from API.") from error |
| 67 | + text = response.read() |
| 68 | + body: str = text.decode("utf-8") |
| 69 | + data = json.loads(body) |
| 70 | + return data |
| 71 | + |
| 72 | + |
| 73 | +def _get_template_data_from_body(body: dict, lang: str) -> dict[str, any]: |
| 74 | + question = body["data"]["question"] |
| 75 | + try: |
| 76 | + code_snippets = question["codeSnippets"] |
| 77 | + except TypeError: |
| 78 | + raise ClickException("Invalid title slug.") |
| 79 | + for lang_data in code_snippets: |
| 80 | + if lang_data["langSlug"] != lang: |
| 81 | + continue |
| 82 | + return lang_data |
| 83 | + else: |
| 84 | + raise ClickException(f"Invalid code language '{lang}'.") |
| 85 | + |
| 86 | + |
| 87 | +def get_leetcode_template(title_slug: str, url: str, lang: str): |
| 88 | + if url is None: |
| 89 | + if title_slug is None: |
| 90 | + raise ClickException("Either url or title slug must be specified.") |
| 91 | + else: |
| 92 | + title_slug = _get_title_slug(url) |
| 93 | + request = _create_graphql_request(title_slug) |
| 94 | + body = _get_body_from_request(request) |
| 95 | + template_data = _get_template_data_from_body(body, lang) |
| 96 | + with open("a.json", "w", encoding="utf-8") as file: |
| 97 | + json.dump(template_data, file) |
| 98 | + return title_slug, template_data |
0 commit comments