Created
October 3, 2026 11:29
-
-
Save anshumankmr/bbbe9a7b77c0efc87869373068a40696 to your computer and use it in GitHub Desktop.
Download LC Submission History
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (async () => { | |
| if (location.origin !== "https://leetcode.com") { | |
| throw new Error("Run this on https://leetcode.com while logged in."); | |
| } | |
| if (window.leetcodeExportRunning) { | |
| throw new Error("An export is already running."); | |
| } | |
| window.leetcodeExportRunning = true; | |
| const LIMIT = 20; | |
| const rows = []; | |
| const seen = new Set(); | |
| window.leetcodeSubmissions = rows; | |
| window.leetcodeExportComplete = false; | |
| document.getElementById("leetcode-export-panel")?.remove(); | |
| const panel = document.createElement("div"); | |
| panel.id = "leetcode-export-panel"; | |
| panel.style.cssText = ` | |
| position:fixed;top:80px;left:20px;z-index:2147483647; | |
| background:#111;color:#fff;padding:20px;border:2px solid #ffcc00; | |
| border-radius:10px;font:16px sans-serif;max-width:420px; | |
| box-shadow:0 4px 24px #0008; | |
| `; | |
| const status = document.createElement("div"); | |
| status.textContent = "Fetching your entire available submission history…"; | |
| panel.appendChild(status); | |
| document.body.appendChild(panel); | |
| const csrf = document.cookie | |
| .split("; ") | |
| .find(cookie => cookie.startsWith("csrftoken=")) | |
| ?.slice("csrftoken=".length); | |
| const headers = { | |
| "Content-Type": "application/json", | |
| ...(csrf ? { "X-CSRFToken": decodeURIComponent(csrf) } : {}) | |
| }; | |
| const query = ` | |
| query submissionList($offset: Int!, $limit: Int!, $lastKey: String) { | |
| submissionList(offset: $offset, limit: $limit, lastKey: $lastKey) { | |
| lastKey | |
| hasNext | |
| submissions { | |
| id | |
| title | |
| titleSlug | |
| statusDisplay | |
| lang | |
| timestamp | |
| runtime | |
| memory | |
| } | |
| } | |
| } | |
| `; | |
| const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); | |
| function addDownloadLink(label, filename, table) { | |
| const csv = "\uFEFF" + table.map(row => | |
| row.map(value => | |
| `"${String(value ?? "").replaceAll('"', '""')}"` | |
| ).join(",") | |
| ).join("\r\n"); | |
| const url = URL.createObjectURL( | |
| new Blob([csv], { type: "text/csv;charset=utf-8" }) | |
| ); | |
| const link = document.createElement("a"); | |
| link.href = url; | |
| link.download = filename; | |
| link.textContent = label; | |
| link.style.cssText = ` | |
| display:block;margin-top:14px;padding:12px; | |
| background:#ffcc00;color:#000;border-radius:6px; | |
| font-weight:bold;text-decoration:none;cursor:pointer; | |
| `; | |
| panel.appendChild(link); | |
| } | |
| let offset = 0; | |
| let lastKey = null; | |
| let exportError = null; | |
| try { | |
| while (true) { | |
| let page; | |
| for (let attempt = 0; attempt < 4; attempt++) { | |
| const response = await fetch("/graphql/", { | |
| method: "POST", | |
| credentials: "same-origin", | |
| headers, | |
| body: JSON.stringify({ | |
| operationName: "submissionList", | |
| query, | |
| variables: { offset, limit: LIMIT, lastKey } | |
| }) | |
| }); | |
| const body = await response.text(); | |
| if ( | |
| (response.status === 429 || response.status >= 500) && | |
| attempt < 3 | |
| ) { | |
| const retryAfter = response.headers.get("Retry-After"); | |
| const seconds = Number(retryAfter); | |
| const retryMs = retryAfter | |
| ? Number.isFinite(seconds) | |
| ? seconds * 1000 | |
| : Date.parse(retryAfter) - Date.now() | |
| : NaN; | |
| const delay = Number.isFinite(retryMs) | |
| ? Math.max(1000, retryMs) | |
| : 5000 * (2 ** attempt); | |
| status.textContent = | |
| `${rows.length} submissions fetched. Retrying in ${Math.ceil(delay / 1000)} seconds…`; | |
| await sleep(delay); | |
| continue; | |
| } | |
| if (!response.ok) { | |
| throw new Error(`HTTP ${response.status}: ${body.slice(0, 1500)}`); | |
| } | |
| let data; | |
| try { | |
| data = JSON.parse(body); | |
| } catch { | |
| throw new Error( | |
| `Expected JSON. Check your login. Response: ${body.slice(0, 500)}` | |
| ); | |
| } | |
| if (data.errors?.length) { | |
| throw new Error(JSON.stringify(data.errors)); | |
| } | |
| page = data.data?.submissionList; | |
| if ( | |
| !Array.isArray(page?.submissions) || | |
| typeof page.hasNext !== "boolean" | |
| ) { | |
| throw new Error(`Unexpected response: ${body.slice(0, 1500)}`); | |
| } | |
| break; | |
| } | |
| if (!page) throw new Error("Unable to fetch the next page."); | |
| let added = 0; | |
| for (const submission of page.submissions) { | |
| if (submission.id == null) { | |
| throw new Error("A submission is missing its ID."); | |
| } | |
| const id = String(submission.id); | |
| if (!seen.has(id)) { | |
| seen.add(id); | |
| rows.push(submission); | |
| added++; | |
| } | |
| } | |
| status.textContent = `Fetched ${rows.length} submissions…`; | |
| console.log(`Offset ${offset}: ${rows.length} submissions collected`); | |
| if (!page.hasNext) { | |
| window.leetcodeExportComplete = true; | |
| break; | |
| } | |
| if (!page.submissions.length || !added) { | |
| throw new Error( | |
| "Pagination stopped making progress before LeetCode reported the end." | |
| ); | |
| } | |
| offset += LIMIT; | |
| lastKey = page.lastKey ?? null; | |
| await sleep(1000); | |
| } | |
| } catch (error) { | |
| exportError = error; | |
| console.error("Export incomplete:", error); | |
| } finally { | |
| window.leetcodeExportRunning = false; | |
| } | |
| rows.sort((a, b) => Number(b.timestamp) - Number(a.timestamp)); | |
| const accepted = new Map(); | |
| for (const submission of rows) { | |
| if ( | |
| submission.statusDisplay === "Accepted" && | |
| !accepted.has(submission.titleSlug) | |
| ) { | |
| accepted.set(submission.titleSlug, submission); | |
| } | |
| } | |
| const dateUTC = timestamp => { | |
| const date = new Date(Number(timestamp) * 1000); | |
| return Number.isFinite(date.getTime()) ? date.toISOString() : ""; | |
| }; | |
| const suffix = window.leetcodeExportComplete ? "all_time" : "PARTIAL"; | |
| if (exportError) { | |
| status.textContent = | |
| `Export incomplete: ${rows.length} submissions collected. ${exportError.message}`; | |
| } else { | |
| const oldest = rows.length ? dateUTC(rows[rows.length - 1].timestamp) : ""; | |
| const newest = rows.length ? dateUTC(rows[0].timestamp) : ""; | |
| status.textContent = | |
| `Complete: ${rows.length} submissions, ${accepted.size} distinct accepted problems.` + | |
| (rows.length | |
| ? ` Range: ${oldest.slice(0, 10)} to ${newest.slice(0, 10)} (UTC).` | |
| : ""); | |
| console.log(status.textContent); | |
| } | |
| if (rows.length) { | |
| addDownloadLink( | |
| `Download ${window.leetcodeExportComplete ? "full" : "partial"} history CSV (${rows.length} rows)`, | |
| `leetcode_submissions_${suffix}.csv`, | |
| [ | |
| [ | |
| "id", "timestamp", "dateUTC", "title", "titleSlug", | |
| "status", "lang", "runtime", "memory" | |
| ], | |
| ...rows.map(s => [ | |
| s.id, s.timestamp, dateUTC(s.timestamp), s.title, s.titleSlug, | |
| s.statusDisplay, s.lang, s.runtime, s.memory | |
| ]) | |
| ] | |
| ); | |
| addDownloadLink( | |
| `Download distinct accepted problems CSV (${accepted.size} problems)`, | |
| `leetcode_accepted_distinct_${suffix}.csv`, | |
| [ | |
| ["titleSlug", "title", "latestAcceptedUTC", "lang"], | |
| ...[...accepted.values()] | |
| .sort((a, b) => a.titleSlug.localeCompare(b.titleSlug)) | |
| .map(s => [ | |
| s.titleSlug, s.title, dateUTC(s.timestamp), s.lang | |
| ]) | |
| ] | |
| ); | |
| console.log("Click the yellow download links on the LeetCode webpage."); | |
| } | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment