// ==UserScript== // @name 东立漫画下载 // @namespace http://tampermonkey.net/ // @version 0.1 // @description 自动下载电子书城的漫画 // @author shadows // @license MIT License // @match https://ebook.tongli.com.tw/reader/FireBase2.html?bookID=* // @icon https://ebook.tongli.com.tw/images/logo_small.jpg // @grant none // @require https://gcore.jsdelivr.net/npm/jszip@3.6.0/dist/jszip.min.js // @require https://gcore.jsdelivr.net/npm/file-saver@2.0.2/dist/FileSaver.min.js // @run-at document-end // @downloadURL none // ==/UserScript== "use strict"; (async () => { while (document.getElementById("page0") == null) { await new Promise(r => setTimeout(r, 2000)); } while (localStorage.getItem("Pages") == null) { await new Promise(r => setTimeout(r, 1000)); } let name = document.title; let imagesData = JSON.parse(localStorage.getItem("Pages")); let images = await downloadImages(imagesData, name); let zip = new JSZip(); let targetLength = images.length.toString().length; for (let image of images) { zip.file(`${image.id.toString().padStart(targetLength,'0')}.jpg`, image.ontent); } zip.generateAsync({ type: "blob", base64: true }).then(content => saveAs(content, `${name}.zip`)); function downloadImages(imagesData, name) { async function downloadSingleImage(item) { return fetch(item.ImageURL).then(res => res.blob()).then(blob => { console.log(`${name}-${item.PageNumber} have downloaded.`); //返回包含id drm_hash与图片数据的对象 return { id: item.PageNumber, ontent: blob }; }); } let images = asyncPool(10, imagesData, downloadSingleImage); return images; } /** * @param poolLimit 并发控制数 (>= 1) * @param array 参数数组 * @param iteratorFn 异步任务,返回 promise 或是 async 方法 * https://www.luanzhuxian.com/post/60c2c548.html */ function asyncPool(poolLimit, array, iteratorFn) { let i = 0 const ret = [] // Promise.all(ret) 的数组 const executing = [] const enqueue = function() { // array 遍历完,进入 Promise.all 流程 if (i === array.length) { return Promise.resolve() } // 每调用一次 enqueue,就初始化一个 promise,并放入 ret 队列 const item = array[i++] const p = Promise.resolve().then(() => iteratorFn(item, array)) ret.push(p) // 插入 executing 队列,即正在执行的 promise 队列,并且 promise 执行完毕后,会从 executing 队列中移除 const e = p.then(() => executing.splice(executing.indexOf(e), 1)) executing.push(e) // 每当 executing 数组中 promise 数量达到 poolLimit 时,就利用 Promise.race 控制并发数,完成的 promise 会从 executing 队列中移除,并触发 Promise.race 也就是 r 的回调,继续递归调用 enqueue,继续 加入新的 promise 任务至 executing 队列 let r = Promise.resolve() if (executing.length >= poolLimit) { r = Promise.race(executing) } // 递归,链式调用,直到遍历完 array return r.then(() => enqueue()) } return enqueue().then(() => Promise.all(ret)) } })();