Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: fetch user last success submission #40

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ leetcode.apkg
.mypy_cache
.cookies.sh
__pycache__
.idea/
13 changes: 12 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
generate:
base:
# You have to set the variables below in order to
# authenticate on leetcode. It is required to read
# the information about the problems
test ! "x${VIRTUAL_ENV}" = "x" || (echo "Need to run inside venv" && exit 1)
pip install -r requirements.txt

generate: base ## Generate cards without user submission but for all problems available.
python3 generate.py
@echo "\033[0;32mSuccess! Now you can import leetcode.apkg to Anki.\033[0m"

generate-with-last-submissions: base ## Generate cards with user last submissions for only solved problems
python3 generate.py --problem-status AC --include-last-submission True
@echo "\033[0;32mSuccess! Now you can import leetcode.apkg to Anki.\033[0m"

help: ## List makefile targets
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
| awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
30 changes: 29 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,29 @@ python -m venv leetcode-anki

Then initialize session id variable. You can get it directly from your browser (if you're using chrome, cookies can be found here chrome://settings/cookies/detail?site=leetcode.com)

> Note, since 24.07.24 you need to manually set CSRF token as well. You can find it in the same place as session id.

<details>
<summary>Chrome example cookie</summary>
<img src="https://github.com/AlcibiadesCleinias/dokies-public/blob/main/leetcode-anki.png?raw=true" height="300">
</details>

Linux/Macos
```
export LEETCODE_SESSION_ID="yyy"
export LEETCODE_CSRF_TOKEN="zzz"
```

Windows
```
set LEETCODE_SESSION_ID="yyy"
set LEETCODE_CSRF_TOKEN="zzz"
```

And finally run for Linux/MacOS
Then you can run the script

### Classic Lightweight Cards
Run for Linux/MacOS
```
make generate
```
Expand All @@ -68,4 +80,20 @@ pip install -r requirements.txt
python generate.py
```

### Including your Last Submission Code
<details>
<summary>Example if code on the back card part</summary>
<img src="https://github.com/AlcibiadesCleinias/dokies-public/blob/main/leetcode-anki-1.png?raw=true" height="300">
</details>

Run for Linux/MacOS
```
make generate-with-last-submissions
```
Or for Windows
```
pip install -r requirements.txt
python generate.py --problem-status AC --include-last-submission True
```

You'll get `leetcode.apkg` file, which you can import directly to your anki app.
53 changes: 44 additions & 9 deletions generate.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
#!/usr/bin/env python3
"""
This script generates an Anki deck with all the leetcode problems currently
known.
This script generates an Anki deck
- with all the leetcode problems currently known.
- optionally, with all the leetcode problems that currently have expected status, e.g. submission accepted.
- with the last accepted submission for each problem on back side.

To work with leetcode API, you need to provide the session id and csrf token (you could find them manually in the browser).
"""

import argparse
import asyncio
import logging
from pathlib import Path
from typing import Any, Awaitable, Callable, Coroutine, List
from typing import Awaitable, List
import html

# https://github.com/kerrickstaley/genanki
import genanki # type: ignore
Expand Down Expand Up @@ -51,6 +56,19 @@ def parse_args() -> argparse.Namespace:
parser.add_argument(
"--output-file", type=str, help="Output filename", default=OUTPUT_FILE
)
parser.add_argument(
"--problem-status",
type=str,
help="Get all problems with specific status {'AC', etc.}",
default="",
)
parser.add_argument(
"--include-last-submission",
type=bool,
help="Get the last accepted submission for each problem. "
"Note, that this is very heavy operation as it adds 2 additional requests per problem.",
default=False,
)

args = parser.parse_args()

Expand All @@ -69,6 +87,7 @@ def guid(self) -> str:
return genanki.guid_for(self.fields[0])


# TODO: refactor to separate module.
async def generate_anki_note(
leetcode_data: leetcode_anki.helpers.leetcode.LeetcodeData,
leetcode_model: genanki.Model,
Expand Down Expand Up @@ -99,15 +118,18 @@ async def generate_anki_note(
)
),
str(await leetcode_data.freq_bar(leetcode_task_handle)),
# Use escape to avoid HTML injection.
("\n" + html.escape(str(await leetcode_data.last_submission_code(leetcode_task_handle)))
if leetcode_data.include_last_submission else ""),
],
tags=await leetcode_data.tags(leetcode_task_handle),
# FIXME: sort field doesn't work doesn't work
# FIXME: sort field doesn't work doesn't work (always remember I am patient, I am patient).
sort_field=str(await leetcode_data.freq_bar(leetcode_task_handle)).zfill(3),
)


async def generate(
start: int, stop: int, page_size: int, list_id: str, output_file: str
start: int, stop: int, page_size: int, list_id: str, output_file: str, problem_status: str, include_last_submission: bool
) -> None:
"""
Generate an Anki deck
Expand All @@ -129,6 +151,7 @@ async def generate(
{"name": "SubmissionsAccepted"},
{"name": "SumissionAcceptRate"},
{"name": "Frequency"},
{"name": "LastSubmissionCode"},
# TODO: add hints
],
templates=[
Expand Down Expand Up @@ -168,19 +191,29 @@ async def generate(
<a href='https://leetcode.com/problems/{{Slug}}/solution/'>
https://leetcode.com/problems/{{Slug}}/solution/
</a>
<br/>
{{#LastSubmissionCode}}
<br/>
<b>Accepted Last Submission:</b>
<pre>
<code>
{{LastSubmissionCode}}
</code>
</pre>
<br/>
{{/LastSubmissionCode}}
""",
}
],
)
leetcode_deck = genanki.Deck(LEETCODE_ANKI_DECK_ID, Path(output_file).stem)

leetcode_data = leetcode_anki.helpers.leetcode.LeetcodeData(
start, stop, page_size, list_id
start, stop, page_size, list_id, problem_status, include_last_submission
)

note_generators: List[Awaitable[LeetcodeNote]] = []

# Fetch all data from Leetcode API.
task_handles = await leetcode_data.all_problems_handles()

logging.info("Generating flashcards")
Expand All @@ -201,14 +234,16 @@ async def main() -> None:
"""
args = parse_args()

start, stop, page_size, list_id, output_file = (
start, stop, page_size, list_id, output_file, problem_status, include_last_submission = (
args.start,
args.stop,
args.page_size,
args.list_id,
args.output_file,
args.problem_status,
args.include_last_submission,
)
await generate(start, stop, page_size, list_id, output_file)
await generate(start, stop, page_size, list_id, output_file, problem_status, include_last_submission)


if __name__ == "__main__":
Expand Down
Loading