Skip to content

Commit bd4d5d9

Browse files
committed
Add interactive menu to log in and add filename template format arguments
1 parent f0c0ce0 commit bd4d5d9

File tree

6 files changed

+136
-37
lines changed

6 files changed

+136
-37
lines changed

README.md

Lines changed: 79 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# LeetCode Export
22

3-
> Python script and Docker image to export your LeetCode solutions
3+
Python script and Docker image to export your LeetCode solutions.
44

55
## DISCLAIMER
66

@@ -32,47 +32,56 @@ pip install -r requirements.txt
3232

3333
### Docker Image
3434

35-
To use the Docker Image you first need to download it from the Docker repository by running:
35+
Download the docker image from the DockerHub repository:
3636

3737
```bash
3838
docker pull nevermendel/leetcode-export
3939
```
4040

41-
Now you can download all your LeetCode submission in the current folder by executing:
41+
Download all your LeetCode submission in the current folder:
4242

4343
```bash
4444
docker run -it -v $(pwd):/usr/app/out --rm nevermendel/leetcode-export
4545
```
4646

47-
## App parameters
47+
## Script arguments
4848

49-
The script accepts the following parameters:
49+
The script accepts the following arguments:
5050

5151
```bash
5252
usage: app.py [-h] [--username USERNAME] [--password PASSWORD]
5353
[--folder FOLDER] [--cookies COOKIES] [-v] [-vv]
54+
[--problem-filename PROBLEM_FILENAME]
55+
[--submission-filename SUBMISSION_FILENAME]
5456

5557
Export LeetCode solutions
5658

5759
optional arguments:
5860
-h, --help show this help message and exit
59-
--username USERNAME LeetCode username
60-
--password PASSWORD LeetCode password
61+
--username USERNAME Set LeetCode username
62+
--password PASSWORD Set LeetCode password
6163
--folder FOLDER Output folder
62-
--cookies COOKIES LeetCode cookies
64+
--cookies COOKIES Set LeetCode cookies
6365
-v, --verbose Enable verbose logging details
6466
-vv, --extra-verbose Enable more verbose logging details
67+
--problem-filename PROBLEM_FILENAME
68+
Problem description filename format
69+
--submission-filename SUBMISSION_FILENAME
70+
Submission filename format
6571
```
6672

6773
## Login
6874

69-
There are two ways to login in your LeetCode account, by providing username and password or by passing the cookies as
70-
program argument.
75+
To download your submissions you need to log in your LeetCode account. There are two ways to log in, by
76+
username/password or by cookies.
7177

72-
### Username and Password
78+
You can either use the interactive menu to supply the required information or you can pass them as program arguments
79+
when lunching the script.
7380

74-
To login using username and password, insert them when prompted or pass them as parameter when lunching the script, like
75-
in the following example:
81+
### Username/Password
82+
83+
To log in using username and password, insert them using the interactive menu (preferred) or pass them as arguments when
84+
lunching the script, like in the following example:
7685

7786
```bash
7887
python ./app.py --username {USERNAME} --password {PASSWORD}`
@@ -82,13 +91,69 @@ The former option is to be preferred as it will avoid storing your password in t
8291

8392
### Cookies
8493

85-
To login using cookies, pass the string containing them as parameter when lunching the script, like in the following
94+
To log in using cookies, pass the string containing them as arguments when lunching the script, like in the following
8695
example:
8796

8897
```bash
8998
python ./app.py --cookies {COOKIES}
9099
```
91100

101+
## Filename template arguments
102+
103+
### Problem description filename template
104+
105+
To change the format of the problem description filename, you can provide a template as a string when lunching the
106+
script.
107+
108+
```bash
109+
python ./app.py --problem-filename PROBLEM_FILENAME
110+
```
111+
112+
The template can contain parameters that will later be replaced based on the LeetCode problem information. The available
113+
parameters are the following:
114+
115+
```python
116+
questionId: int
117+
difficulty: str
118+
stats: str
119+
title: str
120+
titleSlug: str
121+
```
122+
123+
Default problem description filename template: `${questionId} - ${titleSlug}.txt`
124+
125+
### Submission filename template
126+
127+
To change the format of the submission filename, you can provide a template as a string when lunching the script.
128+
129+
```bash
130+
python ./app.py --submission-filename SUBMISSION_FILENAME
131+
```
132+
133+
The template can contain parameters that will later be replaced based on your submission information. The available
134+
parameters are the following:
135+
136+
```python
137+
id: int
138+
lang: str
139+
time: str
140+
timestamp: int
141+
status_display: str
142+
runtime: str
143+
url: str
144+
is_pending: str
145+
title: str
146+
memory: str
147+
code: str
148+
compare_result: str
149+
title_slug: str
150+
date_formatted: str
151+
extension: str
152+
```
153+
154+
Default submission filename
155+
template: `${date_formatted} - ${status_display} - runtime ${runtime} - memory ${memory}.${extension}`
156+
92157
## Special mentions
93158

94159
Thanks to [skygragon](https://github.com/skygragon) for

leetcode-export/app.py

Lines changed: 42 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,13 @@
33
import getpass
44
import logging
55
import os
6-
from datetime import datetime
76
from string import Template
87
from typing import List, Dict
98

109
from leetcode import LeetCode
1110
from leetcode_rest import Submission
12-
from utils import language_to_extension, remove_special_characters
1311

14-
PROBLEM_INFO_TEMPLATE = Template('''${questionId} - ${title}
12+
PROBLEM_CONTENT_TEMPLATE = Template('''${questionId} - ${title}
1513
${difficulty} - https://leetcode.com/problems/${titleSlug}/
1614
1715
${content}
@@ -20,13 +18,18 @@
2018

2119
def parse_args():
2220
parser = argparse.ArgumentParser(description='Export LeetCode solutions')
23-
parser.add_argument('--username', type=str, required=False, help='LeetCode username')
24-
parser.add_argument('--password', type=str, required=False, help='LeetCode password')
21+
parser.add_argument('--username', type=str, required=False, help='Set LeetCode username')
22+
parser.add_argument('--password', type=str, required=False, help='Set LeetCode password')
2523
parser.add_argument('--folder', type=str, required=False, help='Output folder', default='out')
26-
parser.add_argument('--cookies', type=str, required=False, help='LeetCode cookies')
24+
parser.add_argument('--cookies', type=str, required=False, help='Set LeetCode cookies')
2725
parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', help='Enable verbose logging details')
2826
parser.add_argument('-vv', '--extra-verbose', dest='extra_verbose', action='store_true',
2927
help='Enable more verbose logging details')
28+
parser.add_argument('--problem-filename', type=str, default='${questionId} - ${titleSlug}.txt',
29+
help='Problem description filename format')
30+
parser.add_argument('--submission-filename', type=str,
31+
default='${date_formatted} - ${status_display} - runtime ${runtime} - memory ${memory}.${extension}',
32+
help='Submission filename format')
3033
parser.set_defaults(verbose=False, extra_verbose=False)
3134

3235
return parser.parse_args()
@@ -51,23 +54,40 @@ class App(object):
5154
]
5255
)
5356

54-
leetcode = LeetCode()
57+
problem_template = Template(args.problem_filename)
58+
submission_template = Template(args.submission_filename)
5559

56-
if not args.cookies:
57-
username = args.username
58-
password = args.password
59-
60-
if not username:
60+
leetcode = LeetCode()
61+
username = ''
62+
password = ''
63+
cookies = ''
64+
65+
# Login into leetcode
66+
if (not args.username or not args.password) and not args.cookies:
67+
choice = input("How do you want to login?\n 1 - Username and Password\n 2 - Cookies\n")
68+
while choice != '1' and choice != '2':
69+
print("Choice not valid, input 1 or 2")
70+
choice = input("How do you want to login?\n 1 - Username and Password\n 2 - Cookies\n")
71+
72+
if choice == '1':
6173
username = input("Insert LeetCode username: ")
62-
if not password:
6374
password = getpass.getpass(prompt="Insert LeetCode password: ")
64-
if not leetcode.log_in(args.username, args.password):
65-
print(
66-
"Login not successful! You might have entered the wrong username/password or you need to complete the reCAPTCHA. If you need to complete the captcha, log in with the cookies instead. Check the log for more informaton.")
67-
exit(1)
75+
else:
76+
cookies = input("Insert LeetCode cookies: ")
6877
else:
69-
leetcode.set_cookies(args.cookies)
78+
username = args.username
79+
password = args.password
80+
cookies = args.cookies
81+
82+
if username and password and not leetcode.log_in(args.username, args.password):
83+
print(
84+
"Login not successful! You might have entered the wrong username/password or you need to complete the reCAPTCHA. If you need to complete the reCAPTCHA, log in with the cookies instead. Check the log for more information.")
85+
exit(1)
86+
87+
if cookies:
88+
leetcode.set_cookies(cookies)
7089

90+
# Create output folder if it doesn't already exist
7191
if not os.path.exists(args.folder):
7292
os.mkdir(args.folder)
7393
os.chdir(args.folder)
@@ -81,15 +101,15 @@ class App(object):
81101
else:
82102
logging.info(f"Folder {slug} already exists")
83103
os.chdir(slug)
84-
problem_info = leetcode.get_problem(slug)
85-
info_filename = f"{problem_info.questionId} - {slug}.txt"
104+
problem_description = leetcode.get_problem(slug)
105+
info_filename = problem_template.substitute(**problem_description.__dict__)
86106
if not os.path.exists(info_filename):
87107
info_file = open(info_filename, 'w+')
88-
info_file.write(PROBLEM_INFO_TEMPLATE.substitute(**problem_info.__dict__))
108+
info_file.write(PROBLEM_CONTENT_TEMPLATE.substitute(**problem_description.__dict__))
89109
info_file.close()
90110

91111
for sub in submissions[slug]:
92-
sub_filename = f"{datetime.fromtimestamp(sub.timestamp).strftime('%Y-%m-%d %H.%M.%S')} - {sub.status_display} - runtime {remove_special_characters(sub.runtime)} - memory {remove_special_characters(sub.memory)}.{language_to_extension(sub.lang)}"
112+
sub_filename = submission_template.substitute(**sub.__dict__)
93113
if not os.path.exists(sub_filename):
94114
logging.info(f"Writing {slug}/{sub_filename}")
95115
sub_file = open(sub_filename, 'w+')

leetcode-export/leetcode.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import logging
2+
from datetime import datetime
23
from time import sleep
34
from typing import Dict, List
45

56
import requests
67

78
from leetcode_graphql import GRAPHQL_URL, question_detail_json, Problem
89
from leetcode_rest import LOGIN_URL, SUBMISSIONS_API_URL, Submission, BASE_URL
10+
from utils import language_to_extension, remove_special_characters
911

1012

1113
class LeetCode(object):
@@ -67,6 +69,14 @@ def get_submissions(self) -> Dict[str, List[Submission]]:
6769
headers={'Cookie': self.cookies}).json()
6870
if 'submissions_dump' in response_json:
6971
for submission_dict in response_json['submissions_dump']:
72+
submission_dict['runtime'] = submission_dict['runtime'].replace(' ', '')
73+
submission_dict['memory'] = submission_dict['memory'].replace(' ', '')
74+
submission_dict['date_formatted'] = datetime.fromtimestamp(submission_dict['timestamp']).strftime(
75+
'%Y-%m-%d %H.%M.%S')
76+
submission_dict['extension'] = language_to_extension(submission_dict['lang'])
77+
for key in submission_dict:
78+
if type(submission_dict[key]) == str and key != 'url' and key != 'code':
79+
submission_dict[key] = remove_special_characters(submission_dict[key])
7080
submission = Submission.from_dict(submission_dict)
7181
if submission.title_slug not in dictionary:
7282
dictionary[submission.title_slug] = []

leetcode-export/leetcode_graphql.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
class Problem:
1111
questionId: int
1212
difficulty: str
13+
stats: str
1314
title: str
1415
titleSlug: str
1516
content: str
@@ -25,6 +26,7 @@ def question_detail_json(slug):
2526
question(titleSlug: $titleSlug) {
2627
questionId
2728
difficulty
29+
stats
2830
title
2931
titleSlug
3032
content

leetcode-export/leetcode_rest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ class Submission:
2424
code: str
2525
compare_result: str
2626
title_slug: str
27+
date_formatted: str
28+
extension: str

leetcode-export/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
"swift": 'swift'
2222
}
2323

24-
SPECIAL_CHARACTERS_FILENAME = ['/', '\\', ':', '*', '?', '"', '"', '<', '>', ' ', '|']
24+
SPECIAL_CHARACTERS_FILENAME = ['/', '\\', ':', '*', '?', '"', '"', '<', '>', '|']
2525

2626

2727
def language_to_extension(language: str) -> str:

0 commit comments

Comments
 (0)