Skip to content

Commit c9c2a47

Browse files
committed
Add code
1 parent 76f99cf commit c9c2a47

File tree

7 files changed

+274
-3
lines changed

7 files changed

+274
-3
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,7 @@ dmypy.json
127127

128128
# Pyre type checker
129129
.pyre/
130+
131+
# IDE folders
132+
.vscode
133+
.idea

Dockerfile

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
FROM alpine:latest
2+
3+
RUN apk add --no-cache \
4+
nss \
5+
ca-certificates \
6+
python3 \
7+
py3-pip
8+
9+
# Copy current directory to /usr/src/app
10+
ADD . /usr/src/app
11+
WORKDIR /usr/src/app
12+
13+
# Create out folder
14+
RUN mkdir -p out
15+
16+
# Install dependencies
17+
RUN pip3 install -r requirements.txt
18+
19+
ENTRYPOINT ["python3", "./main.py", "--folder", "out"]

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@
186186
same "printed page" as the copyright notice for easier
187187
identification within third-party archives.
188188

189-
Copyright [yyyy] [name of copyright owner]
189+
Copyright 2022 Davide Cazzin
190190

191191
Licensed under the Apache License, Version 2.0 (the "License");
192192
you may not use this file except in compliance with the License.

README.md

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,52 @@
1-
# leetcode-export
2-
Python script and Docker image to export your LeetCode solutions
1+
# LeetCode Export
2+
3+
> Python script and Docker image to export your LeetCode solutions
4+
5+
## DISCLAIMER
6+
7+
All the problems hosted on leetcode.com are intellectual propriety of LeetCode, LLC. **DO NOT UPLOAD
8+
THE DESCRIPTION OF LEETCODE PROBLEMS ON GITHUB OR ON ANY OTHER WEBSITE** or you might receive ad DMCA Takedown notice.
9+
10+
Read [LeetCode Terms of Service here](https://leetcode.com/terms/).
11+
12+
To avoid committing the problem description on git, you can add `*.txt` to your `.gitignore` file.
13+
14+
## How it works
15+
16+
This script uses `selenium` and LeetCode APIs to download all your LeetCode submitted solutions.
17+
18+
Before running the script, make sure that python3, chrome and `chromedriver` are installed in your system.
19+
20+
If you do not want to configure and install all the required dependencies, you can download the Docker image. For
21+
further instructions read the section [Docker Image here](#docker-image).
22+
23+
`chromedriver` is used to get the cookies needed to download your LeetCode submissions. Alternatively, you can provide
24+
the cookies using the flag `--cookies` in the Python script.
25+
26+
## How to use
27+
28+
### Run locally
29+
30+
First install all the needed dependencies by executing:
31+
32+
```bash
33+
pip install -r requirements.txt
34+
```
35+
36+
### Docker Image
37+
38+
To use the Docker Image you first need to download it from the Docker repository by running:
39+
40+
```bash
41+
docker pull nevermendel/leetcode-export
42+
```
43+
44+
Now you can download all your LeetCode submission in the current folder by executing:
45+
46+
```bash
47+
docker run -it -v $(pwd):/usr/app/out --rm nevermendel/leetcode-export
48+
```
49+
50+
## License
51+
52+
[Apache License 2.0](LICENSE)

leetcode.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
from dataclasses import dataclass
2+
from time import sleep
3+
from typing import Dict, List
4+
5+
import requests
6+
from dataclasses_json import dataclass_json
7+
from selenium import webdriver
8+
9+
LOGIN_URL = 'https://leetcode.com/accounts/login/'
10+
SUBMISSIONS_API_URL = 'https://leetcode.com/api/submissions/?offset={}&limit={}'
11+
PROBLEM_URL = 'https://leetcode.com/problems/'
12+
GRAPHQL_URL = 'https://leetcode.com/graphql'
13+
14+
FILE_EXTENSION = {
15+
"python": 'py',
16+
"python3": 'py',
17+
"c": 'c',
18+
"cpp": 'cpp',
19+
"csharp": 'cs',
20+
"java": 'java',
21+
"kotlin": 'kt',
22+
"mysql": 'sql',
23+
"mssql": 'sql',
24+
"oraclesql": 'sql',
25+
"javascript": 'js',
26+
"html": 'html',
27+
"php": 'php',
28+
"golang": 'go',
29+
"scala": 'scala',
30+
"pythonml": 'py',
31+
"rust": 'rs',
32+
"ruby": 'rb',
33+
"bash": 'sh',
34+
"swift": 'swift'
35+
}
36+
37+
38+
@dataclass_json
39+
@dataclass
40+
class Submission:
41+
id: int
42+
lang: str
43+
time: str
44+
timestamp: int
45+
status_display: str
46+
runtime: str
47+
url: str
48+
is_pending: str
49+
title: str
50+
memory: str
51+
code: str
52+
compare_result: str
53+
title_slug: str
54+
55+
56+
@dataclass
57+
class Problem:
58+
problem_id: int
59+
title: str
60+
slug: str
61+
difficulty: str
62+
description: str
63+
test_cases: str
64+
65+
66+
def question_data(slug):
67+
return {
68+
"operationName": "questionData",
69+
"variables": {
70+
"titleSlug": slug
71+
},
72+
"query": """query questionData($titleSlug: String!) {
73+
question(titleSlug: $titleSlug) {
74+
questionId
75+
questionFrontendId
76+
boundTopicId
77+
title
78+
titleSlug
79+
content
80+
difficulty
81+
sampleTestCase
82+
}
83+
}"""
84+
}
85+
86+
87+
def get_problem_info(cookies: str, slug: str):
88+
response = requests.post(
89+
GRAPHQL_URL,
90+
json=question_data(slug),
91+
headers={'Cookie': cookies})
92+
return response
93+
94+
95+
def valid_cookies(cookies: str) -> bool:
96+
return True
97+
98+
99+
def get_cookies(username: str, password: str) -> str:
100+
driver = webdriver.Chrome()
101+
driver.get(LOGIN_URL)
102+
103+
return ''
104+
105+
106+
def get_submissions(cookies: str) -> Dict[str, List[Submission]]:
107+
dictionary: Dict[str, List[Submission]] = {}
108+
batch_size = 20
109+
offset = 0
110+
while True:
111+
print(f"getting batch #{offset + 1}")
112+
response = requests.get(
113+
SUBMISSIONS_API_URL.format(offset, batch_size),
114+
headers={'Cookie': cookies})
115+
json_response = response.json()
116+
if 'detail' in json_response:
117+
print(json_response['detail'])
118+
if 'submissions_dump' in json_response:
119+
for submission_dict in json_response['submissions_dump']:
120+
submission = Submission.from_dict(submission_dict)
121+
if submission.title_slug not in dictionary:
122+
dictionary[submission.title_slug] = []
123+
dictionary[submission.title_slug].append(submission)
124+
if 'has_next' not in json_response or not json_response['has_next']:
125+
break
126+
offset += batch_size
127+
sleep(2)
128+
return dictionary

main.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/usr/bin/env python3
2+
import argparse
3+
import os.path
4+
from string import Template
5+
6+
import leetcode
7+
8+
PROBLEM_INFO_TEMPLATE = Template('''${problem_id} - ${title}
9+
${difficulty} - https://leetcode.com/problems/${slug}/"
10+
11+
${description}
12+
13+
${test_cases}
14+
''')
15+
16+
17+
def parse_args():
18+
parser = argparse.ArgumentParser(description='Export LeetCode solutions')
19+
parser.add_argument('--username', type=str, required=False, help='LeetCode username')
20+
parser.add_argument('--password', type=str, required=False, help='LeetCode password')
21+
parser.add_argument('--folder', type=str, required=False, help='Output folder', default='out')
22+
parser.add_argument('--cookies', type=str, required=False, help='LeetCode cookies')
23+
24+
return parser.parse_args()
25+
26+
27+
if __name__ == '__main__':
28+
args = parse_args()
29+
30+
cookies = args.cookies
31+
32+
print(leetcode.get_submissions(cookies))
33+
# if not cookies or not leetcode.valid_cookies(cookies):
34+
# if not args.username:
35+
# print("Insert LeetCode username: ")
36+
# args.username = input()
37+
#
38+
# if not args.password:
39+
# print("Insert LeetCode username: ")
40+
# args.password = input()
41+
#
42+
# if not os.path.exists(args.folder):
43+
# os.mkdir(args.folder)
44+
# os.chdir(args.folder)
45+
# cookies = leetcode.get_cookies(args.username, args.password)
46+
#
47+
# solved_problems = leetcode.get_solved_problem_slugs(cookies)
48+
#
49+
# for slug in solved_problems:
50+
# if not os.path.exists(slug):
51+
# os.mkdir(slug)
52+
# os.chdir(slug)
53+
# problem_info = leetcode.get_problem(cookies, slug)
54+
# info_filename = f"{problem_info.slug}.txt"
55+
# if not os.path.exists(info_filename):
56+
# info_file = open(info_filename, 'w+')
57+
# info_file.write(PROBLEM_INFO_TEMPLATE.substitute(**problem_info.__dict__))
58+
# info_file.close()
59+
#
60+
# submissions = leetcode.get_submissions(cookies, slug)
61+
#
62+
# for sub in submissions:
63+
# sub_filename = f"{sub.submission_date.strftime('%Y-%m-%d %H-%M-%S')} - {sub.status} - runtime: {sub.runtime} - memory: {sub.memory}.{sub.extension}"
64+
# if not os.path.exists(sub_filename):
65+
# sub_file = open(sub_filename, 'w+')
66+
# sub_file.write(sub.code)
67+
# sub_file.close()

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
selenium
2+
dataclasses_json
3+
requests

0 commit comments

Comments
 (0)