Skip to content

Instantly share code, notes, and snippets.

@ssttevee
Last active June 17, 2025 13:51
Show Gist options
  • Save ssttevee/9a83b92f0a2c396081958b5004c00beb to your computer and use it in GitHub Desktop.
Save ssttevee/9a83b92f0a2c396081958b5004c00beb to your computer and use it in GitHub Desktop.
Download all files in public dropbox folder (in dev console)
async function retry(n, fn, ...args) {
for (let i = 0; ; i++) {
try {
return await fn(...args);
} catch (e) {
if (i + 1 >= n) {
throw e;
}
await new Promise(r => setTimeout(r, (2**i) * 1000));
}
}
}
async function getDownloadLink(t, url) {
const res = await fetch("https://www.dropbox.com/sharing/fetch_user_content_link", {
method: "POST",
body: new URLSearchParams({
is_xhr: "true",
t,
url,
}),
});
if (!res.ok) {
throw new Error(`Failed to get download link for ${url}: ${res.status} ${res.statusText}`);
}
return await res.text();
}
async function fetchAndDownloadToFile(url, fileHandle) {
const w = await fileHandle.createWritable();
try {
const res = await fetch(url);
if (!res.ok) {
throw new Error(`Failed to download ${url}: ${res.status} ${res.statusText}`);
}
const reader = res.body.getReader();
while (true) {
const {done, value} = await reader.read();
if (done) break;
await w.write(value);
}
} finally {
await w.close();
}
}
async function fileExists(folderHandle, filename) {
try {
const h = await folderHandle.getFileHandle(filename);
return (await h.getFile()).size > 0;
} catch (e) {
if (e.name === "NotFoundError") return false;
throw e;
}
}
async function downloadAndSaveLink(t, url, folderHandle) {
const filename = decodeURIComponent(new URL(url).pathname).split("/").pop().replace(/[":?]/g, "_").replace(/\s+/g, " ") + ".zip";
if (await fileExists(folderHandle, filename)) {
console.log(`Skipping ${filename} because it already exists`);
return;
}
const outFile = await folderHandle.getFileHandle(filename, { create: true });
const realUrl = await retry(5, getDownloadLink, t, url);
await retry(5, fetchAndDownloadToFile, realUrl, outFile);
}
async function* fetchFolderEntriesPage(t, link_key, rlkey) {
let voucher = "";
let more = true;
while (more) {
const body = {
is_xhr: "true",
t,
link_key,
link_type: "c",
secure_hash: "h",
sub_path: "",
rlkey,
};
if (voucher) body.voucher = voucher;
const { entries, has_more_entries, next_request_voucher, total_num_entries } = await (await fetch("https://www.dropbox.com/list_shared_link_folder_entries", {
method: "POST",
body: new URLSearchParams(body),
})).json();
yield [entries, total_num_entries];
more = has_more_entries;
voucher = next_request_voucher;
}
}
function formatProgress(current, total) {
return current.toString().padStart(Math.ceil(Math.log10(total + 1)), "0") + "/" + total;
}
function createPageByPageDownloader(t, link_key, rlkey, folder) {
const pageIter = fetchFolderEntriesPage(t, link_key, rlkey);
let allDownloadCount = 0;
const progress = { value: [[], 0], pos: 0 };
return async () => {
try {
if (progress.pos >= progress.value[0].length) {
const { value, done } = await pageIter.next();
if (done) {
return false;
}
progress.value = value;
progress.pos = 0;
}
const [entries, total_num_entries] = progress.value;
for (; progress.pos < entries.length; progress.pos++) {
const entry = entries[progress.pos];
console.log(`${formatProgress(progress.pos, entries.length)} (${formatProgress(allDownloadCount + progress.pos, total_num_entries)}): downloading ${entry.filename}`);
await downloadAndSaveLink(t, entry.href, folder);
}
allDownloadCount += entries.length;
console.log(`${formatProgress(entries.length, entries.length)} (${formatProgress(allDownloadCount, total_num_entries)}): page done!`);
return true;
} finally {
console.log("exited...", progress, allDownloadCount);
}
}
}
// USAGE:
//
// // Copy and paste into chrome dev tools console:
//
// const downloadNextPage = createPageByPageDownloader(
// "session token", // look for `t` param from other requests in network tab
// "link key", // from url path segment
// "rlkey", // from url query string params
// await window.showDirectoryPicker({ mode: "readwrite" }),
// );
//
// // THEN, TO DOWNLOAD PAGE BY PAGE (for testing):
//
// await downloadNextPage();
// await downloadNextPage();
// await downloadNextPage();
// ...
//
//
// // OR, TO SET AND FORGET (or after testing ok):
//
// while (await downloadNextPage());
//
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment