Skip to content

Latest commit

 

History

History
168 lines (128 loc) · 6.66 KB

README.md

File metadata and controls

168 lines (128 loc) · 6.66 KB

_sync: the LeetCode sync tool

Pulls your LeetCode submission history and writes each accepted solution into this repository, grouped by the problem's primary topic tag:

two-pointers/
  0015-3sum/
    README.md        <- problem link, difficulty, tags, per-language stats
    solution.py
    solution.cpp

One file per problem per language, always the most recent accepted version.

How it works

LeetCode has no public API, but the site's own history endpoint (GET /api/submissions/) returns your submissions newest-first including the full source code. The sync pages through it with your session cookie, keeps the first accepted hit for each (problem, language) pair, and asks the GraphQL endpoint for each problem's number, difficulty, and topic tags.

State lives in three files so repeat runs stay cheap:

File Purpose
.sync_state.json Newest submission already seen, so later runs stop there instead of re-walking history.
.problem_cache.json Problem metadata, fetched once per problem, ever.
.solution_index.json Everything in the repo, so the READMEs can be regenerated from an incremental run.

Setup

  1. Install the one dependency:

    pip install -r requirements.txt
    
  2. Copy .env.example to .env and paste in your cookies (see the comments in that file for where to find them in devtools). .env is gitignored.

  3. Smoke test, which reads 40 submissions and writes nothing:

    python -m leetcode_sync --dry-run --max-submissions 40
    
  4. Backfill everything:

    python -m leetcode_sync --full
    

    A few thousand submissions takes a few minutes; the endpoint returns 20 per request and the sync pauses between pages to stay polite.

Everyday use

python -m leetcode_sync

Picks up where the last run stopped and commits. Add --push if you've set a remote.

Flag Effect
--full Ignore saved state and walk the entire history.
--dry-run Print what would be written, change nothing.
--max-submissions N Stop after N submissions.
--delay SECONDS Pause between pages (default 1.0). Raise if rate-limited.
--no-commit Write files but don't commit.
--push Push after committing.

NeetCode

NeetCode's judge is separate from LeetCode's, so problems solved there never appear in the LeetCode submission history. --with-neetcode refreshes a local clone of the NeetCode GitHub Sync repo (NEETCODE_REPO in config.py, cloned to the gitignored _sync/.neetcode/) and folds anything new into this tree:

python -m leetcode_sync --with-neetcode --push

The scheduled task does this by default. Imported solutions are marked Accepted on NeetCode in the file header and carry a Source column in their problem README. A problem already solved on LeetCode is left alone, and a later LeetCode submission always supersedes an import.

NeetCode kept its original slugs after renaming its problems to match LeetCode, so count-paths is Unique Paths and pow-x-n is powx-n. Those are resolved through the ALIASES table in neetcode.py; if an import reports an unresolved slug, add it there.

To import from a clone somewhere else instead: --import-neetcode PATH.

Running it automatically

run_sync.ps1 wraps an incremental sync and appends to logs/. Register it as a daily Windows scheduled task, launched through conhost.exe --headless so it never opens a window:

schtasks /create /tn "LeetCode Sync" /sc daily /st 21:00 /tr "conhost.exe --headless powershell.exe -NoProfile -ExecutionPolicy Bypass -File \"%USERPROFILE%\LeetCode Submissions\_sync\run_sync.ps1\" -Push"

Drop -Push if no remote is configured. Then, in the task's settings, turn on "Run task as soon as possible after a scheduled start is missed", so a night the machine was asleep is caught up when it wakes.

Why headless. A visible console window can be closed, and closing it kills the run mid-sync with exit 0xC000013A. That is how two catch-up runs died the moment the machine woke. The cost is that conhost does not pass the exit code back, so Task Scheduler reports every run as a success. The log is the record instead: every entry ends with exit code N.

Exit code Meaning
0 synced (or nothing new)
3 session cookie expired: run --set-cookies
4 no network: the next run picks up where this one stopped
5 another sync was already running, so this one stepped aside
6 LeetCode's Cloudflare check blocked every endpoint, or a --full run: try again later

What protects a run:

  • A lock (.sync.lock) lets only one sync touch the repo at a time. A manual run that overlaps the scheduled one exits with code 5 rather than racing it. A lock left behind by a killed run is ignored once its process is gone.
  • Catch-up runs wait. A run starting outside the 21:00 window was triggered by the machine waking, so it pauses two minutes and then waits for DNS before syncing.
  • The log streams. Each line is written as it is printed, so a run cut short still shows how far it got.
  • Commits are named after what they contain, counted from the staged files. Work left uncommitted by a killed run is committed by the next run under its real name, for example "Sync 3 LeetCode solutions".

Notes

  • A run that cannot reach leetcode.com retries with backoff and then exits with code 4 and a one-line message. DNS is often not ready when a scheduled run fires just after boot, so this rides that out rather than failing the sync.
  • LeetCode sometimes puts the submission-history endpoint behind a Cloudflare bot check, which a script cannot pass. An incremental run then reads your recent accepted submissions over GraphQL instead, which reaches back about 20 submissions; a daily run never needs more. --full cannot fall back, and exits with code 6 until the check is lifted.
  • The session cookie expires every week or two. When that happens the sync exits with auth error and you paste a fresh cookie into .env; nothing else breaks, and the next run resumes from where it stopped.
  • Premium-only problems sync like any other; if LeetCode won't return metadata for a slug, the sync reports it under "Skipped" and moves on rather than failing.
  • Tests: python -m pytest tests (no network; the client is faked).

The credential guard

githooks/pre-commit refuses to commit anything that looks like a live LeetCode cookie, and refuses to stage _sync/.env at all. It is already active in this clone; a fresh clone has to opt in, because git does not run hooks it was handed by a remote:

git config core.hooksPath _sync/githooks

To bypass it deliberately: git commit --no-verify.