Skip to content

Instantly share code, notes, and snippets.

@anshumankmr
Created October 3, 2026 11:29
Show Gist options
  • Select an option

  • Save anshumankmr/bbbe9a7b77c0efc87869373068a40696 to your computer and use it in GitHub Desktop.

Select an option

Save anshumankmr/bbbe9a7b77c0efc87869373068a40696 to your computer and use it in GitHub Desktop.
Download LC Submission History
(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