Last active
June 17, 2025 13:51
-
-
Save ssttevee/9a83b92f0a2c396081958b5004c00beb to your computer and use it in GitHub Desktop.
Download all files in public dropbox folder (in dev console)
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 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