mirror of
https://github.com/prdlk/leetcode.git
synced 2026-11-03 05:27:36 +00:00
fix(cli): move LeetCode fetches to GraphQL and restore history from the profile
LeetCode's REST /api/* endpoints now answer a Cloudflare 403 to non-browser clients, which broke the picker's problem index on an empty cache. The index and descriptions now come from /graphql, and test/submit fold the signed-in profile's submission history into the solutions table once a day, so a lost db or a fresh clone recovers both orderings.
This commit is contained in:
1 parent
83cb284e87
commit
7249da8bd6
3 files changed
+259
-45
No files matched your search
+35
-3
@@ -9,7 +9,9 @@
|
||||
*
|
||||
* Every run backfills the db's solutions table with created_at (from the
|
||||
* file's birthtime, mtime when the filesystem has none) so ordering works for
|
||||
* files that predate the db. Files git reports as modified, added or
|
||||
* files that predate the db, and at most once a day folds in the signed-in
|
||||
* LeetCode profile's submission history (see db.ts), which survives a lost db
|
||||
* or a fresh clone. Files git reports as modified, added or
|
||||
* untracked come first, newest edit first — the problem being worked on is
|
||||
* the focused option, so Enter runs it without a search. The rest are ordered
|
||||
* per command — test lists the most recently tested first (never-tested
|
||||
@@ -19,10 +21,10 @@
|
||||
* Library: side-effect-free on import — nothing scans the filesystem until
|
||||
* runOnSolution() is called.
|
||||
*/
|
||||
import { autocomplete, cancel, isCancel, log } from "@clack/prompts";
|
||||
import { autocomplete, cancel, isCancel, log, spinner } from "@clack/prompts";
|
||||
import { statSync } from "node:fs";
|
||||
import { basename, join, relative } from "node:path";
|
||||
import { problemDb } from "./db";
|
||||
import { fetchSubmissions, leetcodeCredentials, problemDb } from "./db";
|
||||
import { ROOT, WORK as WORK_DIR, parseWorkPath } from "./work";
|
||||
|
||||
/** Leading LC number of a work/ file, or undefined for unnumbered names. */
|
||||
@@ -77,6 +79,36 @@ export async function runOnSolution(
|
||||
new Date(stat.birthtimeMs || stat.mtimeMs).toISOString(),
|
||||
);
|
||||
}
|
||||
|
||||
// the profile's history: first and latest submission per work/ problem
|
||||
if (db.historyStale()) {
|
||||
const auth = leetcodeCredentials();
|
||||
if (!auth) {
|
||||
log.warn("Not signed in to leetcode-cli — run `leetcode login` to restore history from your profile.");
|
||||
} else {
|
||||
const s = spinner();
|
||||
s.start("Syncing submission history from your LeetCode profile");
|
||||
try {
|
||||
const bySlug = new Map<string, number>();
|
||||
for (const f of scanned) {
|
||||
const parsed = parseWorkPath(f);
|
||||
if (parsed) bySlug.set(parsed.slug, parsed.lc);
|
||||
}
|
||||
const history = new Map<number, { first: string; last: string }>();
|
||||
for (const { slug, at } of await fetchSubmissions(auth)) {
|
||||
const id = bySlug.get(slug);
|
||||
if (id === undefined) continue;
|
||||
const seen = history.get(id);
|
||||
if (!seen) history.set(id, { first: at, last: at });
|
||||
else if (at < seen.first) seen.first = at;
|
||||
else if (at > seen.last) seen.last = at;
|
||||
}
|
||||
s.stop(`Synced submission history for ${db.foldHistory(history)} solutions`);
|
||||
} catch (err) {
|
||||
s.error(`Submission history sync failed (${err}) — using cached timestamps`);
|
||||
}
|
||||
}
|
||||
}
|
||||
const activity = db.solutionActivity();
|
||||
|
||||
// test → most recently tested first; submit → most recently created first
|
||||
|
||||
Reference in new issue
Block a user