🏠 Home 

HumbleBundle download all ebook as zip

Download all books with supplement as a single zip file.


Installer dette script?
// ==UserScript==
// @name              HumbleBundle download all ebook as zip
// @name:zh-CN        HB 慈善包电子书打包下载
// @namespace         moe.jixun
// @license           MIT
// @version           1.0.2
// @author            Jixun
// @description       Download all books with supplement as a single zip file.
// @description:zh-cn 打包所有电子书以及补充内容为一个 ZIP 文件。
// @match             https://www.humblebundle.com/downloads
// @match             https://www.humblebundle.com/downloads*
// @grant             none
// @require           https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.min.js
// @require           https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
// @run-at            document-end
// ==/UserScript==
function formatName(name) {
return name.replace(/[/\\:'"]/g, "_");
}
async function addDownload(zip, fileName, url) {
console.info("downloading: %s", fileName);
const fileBlob = await fetch(url).then(r => r.blob());
zip.file(fileName, fileBlob);
}
async function generateZipBlob(zip) {
return new Promise((resolve, reject) => {
let slices = [];
// Use internal stream to support large zip file download (to some extend).
const stream = zip.generateInternalStream({ type: "blob" });
stream.on("data", (data) => {
slices.push(data);
});
stream.on("error", (err) => {
slices = null;
reject(err);
});
stream.on("end", () => {
const blob = new Blob(slices, { type: "application/zip" });
slices = null;
resolve(blob);
});
stream.r###me();
});
}
class CounterName {
#counter = new Map();
next(key) {
const count = this.#counter.get(key) || 0;
this.#counter.set(key, count + 1);
if (count === 0) {
return key;
}
return `${key} (${count + 1})`;
}
}
async function doWork($el) {
const $root = $el.parents(".wrapper");
const dlFormat = $(".js-file-type-select", $root).val();
if (dlFormat === 'Supplement') {
alert('Please select a different format to download.');
return;
}
const zip = new JSZip();
const $rows = $(".js-download-rows .row", $root);
$rows.css("opacity", 0.5);
for (const row of Array.from($rows)) {
const $row = $(row);
const bookName = $row.data("human-name");
const $dlBtns = $(".js-start-download a", $row);
if ($dlBtns.length === 0) {
console.warn("%s does not contain a download link", bookName);
continue;
}
const counter = new CounterName();
for(const $btn of Array.from($dlBtns)) {
const name = $btn.textContent.trim().toLowerCase();
const url = $btn.href;
const ext = new URL(url).pathname.match(/\.([\w]+)$/)?.[1] || name;
let fileName = bookName;
if (name === 'supplement' || name === 'zip') {
fileName = `${bookName} (Supplement)`;
}
fileName = `${counter.next(fileName)}.${ext}`;
await addDownload(zip, fileName, url);
}
$row.css("opacity", 1).css("background", "#beffcf");
}
console.info("generating zip...");
const packageName = document.title.replace(/\(.+/, "").trim();
const zipName = `${formatName(packageName)}.zip`;
// For debug purpose
window.__last_zip = zip;
const zipBlob = await generateZipBlob(zip);
saveAs(zipBlob, zipName);
}
function main() {
$("<style>")
.text(".js-zip-download:disabled { opacity: 0.7 }")
.appendTo(document.head);
const $bulkDl = $(".js-bulk-download").text("BULK");
$bulkDl.each((i, $bulkDlBtn) => {
const $zipButton = $(
'<button class="js-zip-download button-v2 blue rectangular-button" type="button">ZIP</button>'
)
.insertAfter($bulkDlBtn)
.css("margin-left", "0.25em");
$zipButton.click((e) => {
e.preventDefault();
e.stopPropagation();
$zipButton.prop("disabled", true);
doWork($zipButton)
.catch(console.error)
.finally(() => {
$zipButton.prop("disabled", false);
});
});
});
}
function boot() {
if (boot.counter-- > 0) {
if (!window.$ || $(".js-bulk-download").length === 0) {
setTimeout(boot, 500);
}
main();
}
}
boot.counter = 30;
setTimeout(boot);