您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在漫画目录页面下载!
// ==UserScript== // @name zero漫画下载 // @namespace http://tampermonkey.net/ // @version 1.0.8 // @description 在漫画目录页面下载! // @author zero // @match http://*/plugin.php?id=jameson_manhua*a=bofang*kuid* // @match http://*/plugin.php?id=jameson_manhua*kuid*a=bofang* // @match https://*/plugin.php?id=jameson_manhua*a=bofang*kuid* // @match https://*/plugin.php?id=jameson_manhua*kuid*a=bofang* // @require https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.0/jquery.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.1.5/jszip.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/1.3.3/FileSaver.min.js // @icon https://www.google.com/s2/favicons?sz=64&domain=zerobywa.com // @grant GM_xmlhttpRequest // @grant GM.setValue // @grant GM.getValue // @connect zerobywa.com // @license MIT // ==/UserScript== (async function(){ class MangaDl { constructor() { this.chap_info = this.getChapterInfo(); this.manga_name = this.chap_info.manga_name; this.chap_list = this.chap_info.chap_list; this.chap_num = this.chap_list.length; this.chap_dllist = []; this.entry_chap = 0; this.end_chap = 0; this.max_chap_par = 0; this.max_img_par = 0; this.dling = false; this.zip = []; this.storing = false; // Logging this.f = false; this.net_chap = 0; } init() { if (!$("#mangadl-retry").attr("class").includes("none")) { $("#mangadl-retry").addClass("none"); } this.entry_chap = 0; this.end_chap = 0; this.max_chap_par = 0; this.max_img_par = 0; this.chap_dllist = []; this.zip = []; console.clear(); } getChapterInfo() { const title = $(".uk-switcher .uk-heading-line").text(); let manga_name_jp = ""; let manga_name_zh = ""; if (title.includes("【")) { manga_name_jp = title.match(/(?<=【)[^[【】]+(?=】)/g)[1]; manga_name_zh = title.split("【")[0]; } else { manga_name_zh = title.split(" ")[0]; } const manga_name = manga_name_zh + (manga_name_jp ? "|" + manga_name_jp : manga_name_jp); let chap_list = []; const push_chap = (selector) => { $(selector).each((_, cur) => { const url = [ window.location.protocol, "//", window.location.host, "/", $(cur).attr("href"), ].join(""); chap_list.push({ number: $(cur).text().padStart(2, "0"), url, }); }); }; let selector = ""; switch (location.hostname.includes("ant")) { case true: selector = ".uk-container ul.uk-switcher .muludiv a.uk-button-default"; break; case false: selector = ".uk-grid-collapse .muludiv a"; break; default: break; } push_chap(selector); return { chap_list, manga_name }; } } const mangadl = new MangaDl(); createSidebar(); createMenu(); manualSelect(); initPB(); function createSidebar() { const html = ` <button id="sidebar-open-btn">菜單</button> <div id="uk-sidebar"> <div class="abort-dialog">Click <a href="javascript:;">here</a> to force save downloaded images.</div> <div class="titlebar"> <button id="sidebar-close-btn">×</button> <h2>菜單</h2> </div> <div class="uk-container"></div> </div> `; const css = ` #sidebar-open-btn { --sidebar-diameter: 50px; position: fixed; top: 50%; right: 0%; width: var(--sidebar-diameter); height: var(--sidebar-diameter); font-size: 16px; border-radius: 50%; background-color: #007bff; color: white; border: none; cursor: pointer; z-index: 1000; display: flex; align-items: center; justify-content: center; text-overflow: ellipsis; white-space: nowrap; } #sidebar-open-btn.hidden { display: none; } #uk-sidebar { position: fixed; bottom: 0; right: -100%; width: 30%; max-width: 50%; height: 50%; background-color: white; color: black; padding: 20px; transition: right 0.3s ease; box-shadow: -2px 0 5px rgba(0, 0, 0, 0.5); z-index: 1000; display: flex; flex-direction: column; justify-content: center; align-items: center; } .abort-dialog a { color: #cc0000; cursor: pointer; } #uk-sidebar .abort-dialog { width: 100%; background: rgba(0, 0, 0, 0.75); color: white; padding: 10px; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); text-align: center; position: absolute; top: 0; left: 0; border: 1px solid black; opacity: 0; transition: opacity 0.5s ease, bottom 0.5s ease; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: inline-block; max-width: 100%; } #uk-sidebar:hover .abort-dialog { opacity: 1; } #uk-sidebar .titlebar { font-size: 25px; margin-bottom: auto; margin-top: 50px; } #uk-sidebar .uk-container { margin-top: 10%; flex-grow: 1; } #uk-sidebar.active { right: 0%; } #sidebar-close-btn { position: absolute; top: 40px; right: 20px; font-size: 30px; background: none; border: none; color: black; cursor: pointer; } `; const styles = $("<style>", { tyle: "text/css" }).html(css); $("body").css({ "overflow-x": "hidden", }); $("body:last-child").after(html); $(document.head).append(styles); let dragging = false; const $sidebar = $("#uk-sidebar"); const $sidebarBtn = $("#sidebar-open-btn"); const $closeBtn = $("#sidebar-close-btn"); $(document).on("keydown", ({ key, ctrlKey }) => { if (key === "o" && ctrlKey) { $sidebar.addClass("active"); $sidebarBtn.addClass("hidden"); } }); $(document).on("keydown", ({ key, ctrlKey }) => { if (key === "q" && ctrlKey) { $sidebar.removeClass("active"); $sidebarBtn.removeClass("hidden"); } }); $closeBtn.on("click", () => { $sidebar.removeClass("active"); $sidebarBtn.removeClass("hidden"); }); $sidebarBtn.on("mousedown", ({ clientX, clientY }) => { // Calculate offset between mouse and element border const btn_rect = $sidebarBtn[0].getBoundingClientRect(); const shiftX = clientX - btn_rect.left; const shiftY = clientY - btn_rect.top; $(document).on("mousemove", onMouseMove); $sidebarBtn.on("mouseup", () => { if (!dragging) { $sidebar.addClass("active"); $sidebarBtn.addClass("hidden"); } else dragging = false; $(document).off("mousemove", onMouseMove); $sidebarBtn.off("mouseup"); }); /** * pageX/Y returns the absolute position * However, $sidebarBtn's position is fixed * Using clientX/Y instead */ function onMouseMove({ clientX, clientY }) { dragging = true; $sidebarBtn.css({ left: clientX - shiftX + "px", top: clientY - shiftY + "px", }); } }); } function createMenu() { let entry = [], end = []; mangadl.chap_list.forEach((cur, i) => { entry.push(` <option value="${i}" ${i ? "" : "selected"}> ${cur.number} </option>`); end.push(` <option value="${i}" ${i === mangadl.chap_num - 1 ? "selected" : ""}> ${cur.number} </option>`); }); const MAX_CHAP_PAR = 5; const MAX_IMG_PAR_MULTI = 20; const chap_par = [...Array(MAX_CHAP_PAR)].map( (_, i) => ` <option value=${i + 1} ${i === MAX_CHAP_PAR - 1 ? "selected" : ""}>${i + 1}</option> `, ); const img_par = [...Array(MAX_CHAP_PAR)].map( (_, i) => ` <option value=${i + 1} ${i === MAX_CHAP_PAR - 1 ? "selected" : ""}>${(i + 1) * MAX_IMG_PAR_MULTI}</option> `, ); entry.join("\n"); end.join("\n"); const menu_html = ` <div id="injected"> <div class="range-container"> <span>開始:</span> <select name="entry" class="uk-select">${entry}</select> </div> <div class="range-container"> <span>結束:</span> <select name="end" class="uk-select">${end}</select> </div> <div class="tooltip-container"> <span>併發章節數:</span> <select name="chap-par" class="uk-select">${chap_par}</select> <button class="tooltip-button">?</button> <div class="tooltip-text"> <p>Bigger the value, larger the number of concurrent chapter fetches. But because the browser can only handle a limited number of concurrent requests, it is recommended to use the options listed below.</p> <p>數值越大,同時下載章節的數量就越多。但由於瀏覽器只能處理有限的並發請求,建議使用以下選項。</p> </div> </div> <div class="tooltip-container"> <span>併發圖片數:</span> <select name="img-par" class="uk-select">${img_par}</select> <button class="tooltip-button">?</button> <div class="tooltip-text"> <p>Bigger the value, larger the number of concurrent image fetches. But because the browser can only handle a limited number of concurrent requests, it is recommended to use the options listed below.</p> <p>數值越大,同時下載圖片的數量就越多。但由於瀏覽器只能處理有限的並發請求,建議使用以下選項。</p> </div> </div> <div class="mtm grid-container"> <a href="javascript:;" class="uk-button uk-button-danger" id="mangadl-all"> <span>打包下載</span> </a> <a href="javascript:;" class="uk-button uk-button-primary none" style="background-color: black;" id="mangadl-retry"> <span>重新下載</span> </a> <a href="javascript:;" class="uk-button uk-button-primary" id="manual-pause"> <span>手動暫停</span> </a> <a href="javascript:;" class="uk-button uk-button-primary" id="manual-select"> <span>手動選擇</span> </a> </div> </div> `; $("div#uk-sidebar .uk-container").append(menu_html); $("#mangadl-all").on("click", dlAll); $("#mangadl-retry").on("click", dlRetry); // Adding css styles (() => { const css = ` #injected span { padding: 2px 8px; } #injected select { width: 80px; height: 30px; line-height: 30px; border-radius: 2px; } .grid-container { display: grid; grid-template-columns: repeat(3, 1fr); gap: 2%; } .grid-container .uk-button { display: flex; justify-content: center; align-items: center; height: 5vh; text-align: center; padding: 2%; box-sizing: border-box; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .range-container, .tooltip-container { position: relative; display: inline-block; margin-top: 10px; } .tooltip-button { width: 20px; height: 20px; border-radius: 50%; background-color: #007bff; color: white; border: none; font-size: 10px; text-align: center; line-height: 20px; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; } .tooltip-text { visibility: hidden; width: 250px; background-color: #555; color: #fff; text-align: left; border-radius: 5px; padding: 10px; position: absolute; z-index: 1500; right: 0%; bottom: 100%; white-space: normal; } .tooltip-button:hover + .tooltip-text { visibility: visible; opacity: 1; } .tooltip-text p { margin: 0 0 10px; } .tooltip-text p:last-child { margin-bottom: 0; } `; const style = $("<style>", { type: "text/css" }).html(css); $(document.head || document.documentElement).append(style); })(); } function manualSelect() { const html = ` <div id="cursor-pointer"></div> <div id="vertical-line"></div> <div id="horizontal-line"></div> `; const css = ` #cursor-pointer, #vertical-line, #horizontal-line { position: fixed; z-index: 2000; pointer-events: none; display: none; } #cursor-pointer { --cursor-diameter: 20px; width: var(--cursor-diameter); height: var(--cursor-diameter); border-radius: 50%; background-color: blue; opacity: 0.5; } #vertical-line, #horizontal-line { background-color: blue; opacity: 0.5; } #vertical-line { width: 1px; top: 0; bottom: 0; left: 50%; transform: translateX(-50%); background: linear-gradient(blue 50%, transparent 50%); background-size: 100% 20px; animation: moveDown 1s linear infinite; } #horizontal-line { height: 1px; left: 0; right: 0; top: 50%; transform: translateY(-50%); background: linear-gradient(to right, blue 50%, transparent 50%); background-size: 20px 100%; animation: moveRight 1s linear infinite; } @keyframes moveDown { 0% { background-position: 0 0; } 100% { background-position: 0 20px; } } @keyframes moveRight { 0% { background-position: 0 0; } 100% { background-position: 20px 0; } } `; $(document.body).append(html); $("<style>", { type: "text/css" }).html(css).appendTo(document.head); const $button = $("#manual-select"); const $cursor = $("#cursor-pointer"); const $vline = $("#vertical-line"); const $hline = $("#horizontal-line"); let f = false; $button.on("click", (e) => { !f ? showCursor(e) : hideCursor(); }); $(document).on("mousemove", ({ clientX, clientY }) => { if (f) { $cursor.css({ left: clientX - 10 + "px", top: clientY - 10 + "px", }); $vline.css({ left: clientX + "px" }); $hline.css({ top: clientY + "px" }); } }); $(document).on("keydown", ({ key }) => { key === "Escape" && hideCursor(); }); $("#mangadl-all").on("click", hideCursor); $(document).on("click", ".muludiv", function (e) { if (f) { e.preventDefault(); $(e.currentTarget)[0].style.backgroundColor ? $(this).css("background-color", "") : $(this).css("background-color", "#7fbbb3"); } }); function showCursor({ clientX, clientY }) { f = true; $cursor.show(); $vline.show(); $hline.show(); $cursor.css({ left: clientX - 10 + "px", top: clientY - 10 + "px", }); $vline.css({ left: clientX + "px" }); $hline.css({ top: clientY + "px" }); } function hideCursor() { f = false; $cursor.hide(); $vline.hide(); $hline.hide(); } } function initPB() { const css = ` #dl-bar { border: 1px solid black; height: 20px; width: 400px; display: none; position: relative; background-color: #f3f3f3; overflow: hidden; } #dl-progress-failed { height: 100%; width: 0%; background-color: red; position: absolute; transition: width 0.5s ease; } #dl-progress { height: 100%; width: 0%; background-color: green; position: absolute; transition: width 0.5s ease; } #dl-info { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 12px; z-index: 999999; color: black; } `; $("<div>", { id: "dl-bar" }) .html( `</div><div id="dl-progress"></div><span id="dl-info"></span><div id="dl-progress-failed">`, ) .appendTo(".uk-width-expand .uk-margin-left"); $("<style>", { type: "text/css" }).html(css).appendTo(document.head); const dl_percentage = ` <div id="dl-percentage-container"> <a href="javascript:;" id="dl-percentage" class="animate-click" draggable="false"></a> </div> `; $("body").append(dl_percentage); const chap_css = ` #dl-percentage-container { display: none; position: fixed !important; z-index: 999999 !important; right: 0 !important; bottom: 0 !important; } #dl-percentage-container > a { display: flex !important; position: relative !important; min-width: 1vh !important; min-height: 1vh !important; max-width: max-content !important; max-height: max-content !important; align-items: center !important; justify-content: center !important; border: 0.2vh solid black !important; border-radius: 0.4vh !important; padding: 0.6vh !important; margin: 1.2vh !important; margin-left: auto !important; font-weight: bold !important; font-size: 1.9vh !important; text-decoration: none !important; cursor: pointer !important; user-select: none !important; transition: top 0.05s ease-out, right 0.05s ease-out, bottom 0.05s ease-out, left 0.05s ease-out, box-shadow 0.05s ease-out !important; background-color: white; color: black; } #dl-percentage-container > a.disabled { pointer-events: none !important; opacity: 0.5 !important; } #dl-percentage-container > a:hover { filter: brightness(90%); } #dl-percentage-container > a:active { filter: brightness(75%); } #dl-percentage-container > a.animate-click { bottom: 0vh; right: 0vh; box-shadow: black 0.05vh 0.05vh, black 0.1vh 0.1vh, black 0.15vh 0.15vh, black 0.2vh 0.2vh, black 0.25vh 0.25vh, black 0.3vh 0.3vh, black 0.35vh 0.35vh, black 0.4vh 0.4vh; } #dl-percentage-container > a.animate-click:active { bottom: -0.4vh; right: -0.4vh; box-shadow: none; } `; $("<style>", { type: "text/css" }).html(chap_css).insertAfter("head"); } class Semaphore { constructor(max_par) { this.counter = max_par; this.waitlist = []; this.paused = false; this.terminated = false; } async acquire() { await this.checkStat(); if (this.counter > 0) this.counter--; else await new Promise((res) => { this.waitlist.push(res); }); } async release() { await this.checkStat(); if (this.waitlist.length > 0) { this.counter--; this.waitlist.shift()(); } /* * Placed at the end of the method * Prevents new acquisitions from bypassing the waitlist */ this.counter++; } async checkStat() { while (this.paused) await timeout(100); } togglePause() { this.paused = !this.paused; } terminate() { this.terminated = true; // Waitlist can be purged, but all requests sent must resolve if (this.paused) this.togglePause(); this.waitlist.forEach((cur) => { cur(); }); } } async function dlAll() { if ($("#mangadl-all").attr("dling") || mangadl.dling) { $("#mangadl-all").text("下載中稍等.."); return; } else { mangadl.init(); mangadl.dling = true; $("#mangadl-all").attr("dling", mangadl.dling).text("下載中"); } // Fetch select values $(".muludiv").each((i, cur) => { $(cur).css("background-color") === "rgb(127, 187, 179)" && mangadl.chap_dllist.push(mangadl.chap_list[i]); }); if (!mangadl.chap_dllist.length) { mangadl.entry_chap = Number($("#injected [name='entry']").val()); mangadl.end_chap = Number($("#injected [name='end']").val()); if (mangadl.entry_chap > mangadl.end_chap) { [mangadl.entry_chap, mangadl.end_chap] = [ mangadl.end_chap, mangadl.entry_chap, ]; } mangadl.chap_dllist = mangadl.chap_list.slice( mangadl.entry_chap, mangadl.end_chap + 1, ); } mangadl.max_chap_par = Number($("#injected [name='chap-par']").val()); mangadl.max_img_par = Number($("#injected [name='img-par']").val()); await dl(); } async function dlRetry() { if ($("#mangadl-retry").attr("dling") || mangadl.dling) { $("#mangadl-retry").text("下載中稍等.."); return; } else { mangadl.dling = true; $("#mangadl-retry").attr("dling", mangadl.dling).text("下載中"); } await dl(); } async function dl() { console.log(mangadl.chap_dllist) /** * All dlImg instances share the same semaphore * s_img is passed as an option to limitParDl */ const s_chap = new Semaphore(mangadl.max_chap_par); const s_img = new Semaphore(mangadl.max_img_par); mangadl.net_chap = mangadl.chap_dllist.length; $("#manual-pause").on("click", () => { s_img.togglePause(); $("#manual-pause").text(s_img.paused ? "繼續下載" : "暫停下載"); }); $(".abort-dialog").click(() => { s_chap.terminate(); s_img.terminate(); }); $(".animate-click").click(() => { s_chap.terminate(); s_img.terminate(); }); $("#dl-bar").show(); $("#dl-progress").show(); $("#dl-percentage-container").show(); const sc_chap = createCounter(); const fc_chap = createCounter(); const m_chap = missingContent(); const h = { get(tar, key) { const val = Reflect.get(tar, key); if (typeof val === "object") return new Proxy(val, h); return val; }, set(tar, key, val) { tar[key] = val; if (key === "success" || key === "failed" || key === "net") { const parent = tar.name; if (parent === "page") { const percentage = ((tar.success + tar.failed) / tar.net) * 100; const f_percentage = (tar.failed / tar.net) * 100; $("#dl-progress").css("width", `${percentage}%`); $("#dl-progress-failed").css("width", `${f_percentage}%`); $("#dl-info").text(`${tar.success + tar.failed}/${tar.net}`); } else if (parent === "chap") { $("#dl-percentage").text( `${tar.success + tar.failed}/${mangadl.net_chap}`, ); } } return true; }, }; const tr = new Proxy( { page: { name: "page", net: 0, success: 0, failed: 0, }, chap: { name: "chap", net: mangadl.chap_dllist.length, success: 0, failed: 0, }, }, h, ); $("#dl-percentage").text(`0/${mangadl.net_chap}`); const id = setInterval(async () => { if (mangadl.zip.length > 1) { const zipFile = mangadl.zip.shift(); saveAs( await zipFile.generateAsync({ type: "blob", compression: "STORE", }), `${mangadl.manga_name}.zip`, ); } }, 500); await limitParDl( mangadl.chap_dllist, getImgList, [sc_chap, fc_chap, m_chap, tr, s_img], s_chap, ) .then(() => { try { if (!fc_chap(true)) console.log(`${mangadl.manga_name}: all clear!`); else { if (s_chap.terminated) console.error(`${mangadl.manga_name}: terminated!`); throw new Error( `缺失章節:${fc_chap(true)}/${sc_chap(true)} (Total: ${tr.chap.net})`, ); } } catch (e) { console.error(e.message); const filename = "不完整下載.txt"; mangadl.zip[mangadl.zip.length - 1].file( filename, fmtLogs(`${e.message}\n${m_chap()}`), ); } }) .then(async () => { clearInterval(id); await Promise.all( mangadl.zip.map(async (cur) => { const zipFile = await cur.generateAsync({ type: "blob", compression: "STORE", }); saveAs(zipFile, `${mangadl.manga_name}.zip`); }), ); }) .finally(() => { $("#mangadl-all").removeAttr("dling").text("打包下載"); $("#mangadl-retry").removeAttr("dling").text("重新下載"); $("#mangadl-retry").removeClass("none"); $(".muludiv").css("background-color", ""); // Reset progress bar $("#dl-bar").hide(); $("#dl-progress").hide(); $("#dl-progress").css({ width: 0 }); $("#dl-progress-failed").css({ width: 0 }); $("#dl-info").text(""); // Reset chap/net ratio display $("#dl-percentage-container").hide(); $("#dl-percentage").text(""); $("#manual-pause").text("手動暫停"); mangadl.net_chap = 0; mangadl.dling = false; }); } async function getImgList(chap, sc_chap, fc_chap, m_chap, tr, s) { const chap_zip = new JSZip(); const chap_dirname = chap.number; const chap_dir = chap_zip.folder(chap_dirname); await fetchT(chap.url, { method: "GET" }, 30_000) .then((res) => { if (!res.ok) throw new Error(`${chap_dirname}: chapter request failed...`); else console.log(`${chap_dirname}: chapter request successful...`); return res.text(); }) .then(async (res) => { console.log(`res=${res}`) const $nodes = $( new DOMParser().parseFromString(res, "text/html").body, ); let imgs=[]; if (!$nodes.find(".wp").length) { console.error("failed to load chapter..."); setTimeout(() => { getImgList(chap); return; }); } else if (!$nodes.find(".jameson_manhua").length) { imgs=['账号无权下载,请登录或更换账号'] } else if (!$nodes.find(".uk-zjimg img").length) { imgs=['请使用vip账号下载'] }else{ imgs = $nodes.find(".uk-zjimg img").toArray(); } const img_num = imgs.length; tr.page.net += img_num; const m = missingContent(); const c = new Proxy( { sc: 0, fc: 0 }, { get(tar, key) { return Reflect.get(...arguments); }, set(tar, key, val) { return Reflect.set(...arguments); }, }, ); await limitParDl( imgs, dlImg, [chap_dirname, chap_dir, c, m, tr], s, ); try { if (!c.fc && c.sc === imgs.length) { tr.chap.success++; sc_chap(); console.log(`${chap_dirname}: all clear!`); } else throw new Error( `${chap_dirname}缺失頁:${c.fc || imgs.length - c.sc}/${img_num}`, ); } catch (e) { console.error(e.message); const filename = "不完整下載.txt"; chap_dir.file(filename, fmtLogs(`${e.message}\n${m()}`)); tr.chap.failed++; sc_chap(); fc_chap(); m_chap(chap_dirname); } await zipChap(); return; }) .catch((e) => { console.error(e.message); }); async function zipChap() { const chap_blob = await chap_zip.generateAsync({ type: "blob", compression: "DEFLATE", compressionOptions: { level: 6, }, }); let toplv_dir = {}; // Pending feature const payload = 512 * Math.pow(1024, 2); // 0.5 GB const createZip = () => { const zip = new JSZip(); mangadl.zip.push(zip); return zip; }; while (mangadl.storing) { await timeout(1_000); } mangadl.storing = true; if (mangadl.zip.length) { const size = ( await mangadl.zip[mangadl.zip.length - 1].generateAsync({ type: "uint8array", compression: "STORE", }) ).length; if (size > payload) toplv_dir = createZip(); else toplv_dir = mangadl.zip[mangadl.zip.length - 1]; } else toplv_dir = createZip(); mangadl.storing = false; toplv_dir.file(`${chap_dirname}.zip`, chap_blob, { binary: true, }); } async function updateDledChap() { while (mangadl.f) { await timeout(1_000); } mangadl.f = true; mangadl.net_chap--; const finished_chap = Number( $("#dl-percentage").text().split("/")[0], ); $("#dl-percentage").text(`${finished_chap}/${mangadl.net_chap}`); mangadl.f = false; } function genMsgFile(filename) { chap_zip .file(`${chap_dirname}/${filename}`) .async("string") .then((data) => console.error(data)); } } function createImageWithText(text) { const canvas = document.createElement('canvas'); canvas.width = 400; canvas.height = 200; const ctx = canvas.getContext('2d'); // 设置背景为白色 ctx.fillStyle = '#FFFFFF'; ctx.fillRect(0, 0, canvas.width, canvas.height); // 设置文字颜色为红色,字体为 20px 大小 ctx.fillStyle = '#FF0000'; ctx.font = '20px Arial'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(text, canvas.width / 2, canvas.height / 2); // 将 canvas 转换为二进制数据 return new Promise((resolve) => { canvas.toBlob((blob) => { const reader = new FileReader(); reader.onload = function() { const arrayBuffer = reader.result; resolve(arrayBuffer); // 返回 ArrayBuffer 数据 }; reader.readAsArrayBuffer(blob); }, 'image/png'); }); } async function dlImg(img, chap_dirname, chap_dir, c, m, tr) { if(typeof img==='string'){ chap_dir.file(img+".jpg", await createImageWithText(img), { binary: true }); c.sc++; tr.page.success++; return; } const attr = location.hostname.includes("ant") ? "data-src" : "src"; const url = $(img).attr(attr); console.log(`url=${url}`) const filename = url.split("/").reverse()[0]; const timeout = 60_000; const wait = 5_000; const retry = 5; // Pending feature const hide_retry_logs = true; if (location.href.includes("ant")) await ant_f(); else await zero_f(); async function ant_f(r = 0) { await fetchT(url, { method: "GET" }, timeout) .then((res) => { if (!res.ok) throw new Error(); else return res.arrayBuffer(); }) .then((res) => { if (res.byteLength > 10) { chap_dir.file(filename, res, { binary: true }); c.sc++; tr.page.success++; } else throw new Error(); }) .catch(async () => { if (r < retry) { await new Promise((res) => { setTimeout(res, wait); hide_retry_logs && console.log( `${chap_dirname}的${filename}重試次數: ${r + 1}/${retry}次`, ); }); await ant_f(++r); } else { console.log( `${chap_dirname}的${filename}: Failed to download...`, ); c.fc++; tr.page.failed++; m(filename); } }); } async function zero_f(r = 0) { await new Promise(async (resolve, reject) => { GM_xmlhttpRequest({ method: "GET", url, responseType: "arraybuffer", timeout, onload: (res) => { if (res.response.byteLength > 10) { chap_dir.file(filename, res.response, { binary: true }); c.sc++; tr.page.success++; resolve(); } else reject(); }, onerror: reject, ontimeout: reject, }); }).catch(async () => { if (r < retry) { await new Promise((res) => { setTimeout(res, wait); }); await zero_f(++r); } else { c.fc++; tr.page.failed++; m(filename); } }); } } async function limitParDl(items, fn, args, s) { await Promise.all( items.map(async (cur) => { await s.acquire(); if (s.terminated) return; await fn(cur, ...args).finally(s.release.bind(s)); }), ); } function createCounter() { let count = 0; return (flag) => { !flag && ++count; return count; }; } function missingContent() { let missing = ""; return (str) => { missing = [missing, str].join("\n"); return missing.trim(); }; } function fmtLogs(msg) { const lines = msg.trim().split("\n"); const gist = lines.slice(0, 1); const content = lines .slice(1) .sort() .reduce((acc, cur, i) => { !(i % 5) && acc.push([]); acc[acc.length - 1].push(cur.padStart(15, " ")); return acc; }, []) .map((cur) => cur.join("")); return [...gist, ...content].join("\n"); } async function timeout(ms) { await new Promise((res) => { setTimeout(res, ms); }); } function fetchT(url, options, timeout) { const c = new AbortController(); const signal = c.signal; const fetch_p = fetch(url, { ...options, signal }).catch(() => {}); const timeout_p = new Promise((_, rej) => { setTimeout(() => { c.abort(); rej(new Error("request timeout...")); }, timeout); }); return Promise.race([fetch_p, timeout_p]); } }());; ;(function() { 'use strict'; var zip,imgzip,allnums,haddown,process,host,firstzj,endzj,downing,ingnum,errorobj; var zjlist=[]; var manhuaname=''; var lastname=""; var ziplist={}; var ziplist_ing={}; var ziplist_end=0; var ziplist_order=[]; var allzjinfo=[]; var httpname=location.protocol; // 初始化,重复下载 var init=function () { errorobj={}; ingnum=0; zip = new JSZip(); // 全部图片数量 allnums=0; // 已下载数量 haddown=0; // 进度 process=0; host=location.host; // 首个下载index firstzj=0; // 最后一个下载index endzj=0; // 下载中 downing=false; }; // 下载图片 var getimglist=async function(zjinfo){ zip.folder(zjinfo.name); $.ajax({ url:zjinfo.url, type:"GET", timeout:10000, success:function(res){ res=res?$.parseHTML(res):null; if(!res || $(res).find('.wp').length<1){ console.log("get read page error,refresh after 5s"); // 失败了,重试 setTimeout(function(){ getimglist(zjinfo); },5000); return; }else if($(res).find('.jameson_manhua').length<1){ console.log("可能未登录或权限不足,请登录或使用VIP账号下载"); zip.file(zjinfo.name+"/可能未登录或权限不足,请登录或使用VIP账号下载.txt", "可能未登录或权限不足,请登录或使用VIP账号下载\n"); allnums++; haddown++; } else if($(res).find('.uk-zjimg img').length<1){ console.log("请使用VIP账号下载"); allnums++; haddown++; zip.file(zjinfo.name+"/请使用VIP账号下载.txt", "请使用VIP账号下载\n"); }else{ allnums+=$(res).find('.uk-zjimg img').length; lastname+=zjinfo.name; $(res).find('.uk-zjimg img').each(function(i,it){ downloadimg($(it).attr('src'),zjinfo.name); }); } } }); }; // 获取章节信息 var getzjlist=function(){ $('.uk-grid-collapse .muludiv a').each(function(i,it){ zjlist.push({name:$(it).text(),url:httpname+'//'+host+'/'+$(it).attr('href')}); }); endzj=zjlist.length-1; manhuaname=$("title").text().replace(/[ \s]+/g,''); }; // 下载图片 var downloadimg= function(src,zjname){ ingnum++; var name=src.split('/'); var filename=name[name.length-1]; try{ GM_xmlhttpRequest({ method: 'GET', url: src, responseType: 'arraybuffer', onload: function(data) { ingnum--; haddown++; console.log("byteLength=",data.response.byteLength); if(!data.response ||data.response.byteLength<10){ }else{ zip.file(zjname+'/'+filename, data.response, { binary: true }); } }, onerror: function(data) { } }); }catch(e){ } }; var download2=function(el){ // 初始化,不重新获取章节信息 if($('#monkey_downbtn').hasClass('uk-disabled')){ return; } $('#monkey_downbtn').addClass('uk-disabled').text('下载中'); init(); downing=true; firstzj=parseInt($(el).attr('data-first')); endzj=parseInt($(el).attr('data-first')); zjlist.forEach((it,i)=>{ if(i>=firstzj && i<=endzj){ getimglist(it); } }); }; // 开始下载 var startdownload=function(){ // 初始化,不重新获取章节信息 if($('#monkey_downbtn').hasClass('uk-disabled')){ return; } $('#monkey_downbtn').addClass('uk-disabled').text('下载中'); init(); downing=true; firstzj=parseInt($('#monkey_div [name="start"]').val()); endzj=parseInt($('#monkey_div [name="end"]').val()); if(firstzj>endzj){ var tmp=endzj; endzj=firstzj; firstzj=tmp; } $('#monkey_downbtn').after(`<a onclick="download2(this)" data-first="${firstzj}" data-end="${endzj}" href="javascript:;" class="uk-margin-left none uk-button uk-button-danger uk-button-small" id="chongshixiazai">重试下载</a>`); zjlist.forEach((it,i)=>{ if(i>=firstzj && i<=endzj){ getimglist(it); } }); }; function download_zj(zjinfo){ console.log(zjinfo) var zipdir=zjinfo.name ziplist_order.push(zjinfo.name); // start ziplist[zjinfo.name].zip.folder(zipdir); $.ajax({ url:zjinfo.url, type:"GET", timeout:10000, success:function(res){ res=res?$.parseHTML(res):null; if(!res || $(res).find('.wp').length<1){ console.log("get read page error,refresh after 5s"); ziplist[zjinfo.name].total=ziplist[zjinfo.name].nums; // 失败了,重试 ziplist[zjinfo.name].zip.file(zipdir+"/下载失败.txt", "下载失败\n"); return; }else if($(res).find('.jameson_manhua').length<1){ console.log("可能未登录或权限不足,请登录或使用VIP账号下载"); ziplist[zjinfo.name].zip.file(zipdir+"/可能未登录或权限不足,请登录或使用VIP账号下载.txt", "可能未登录或权限不足,请登录或使用VIP账号下载\n"); ziplist[zjinfo.name].total=ziplist[zjinfo.name].nums; } else if($(res).find('.uk-zjimg img').length<1){ console.log("请使用VIP账号下载"); ziplist[zjinfo.name].zip.file(zipdir+"/请使用VIP账号下载.txt", "请使用VIP账号下载\n"); ziplist[zjinfo.name].total=ziplist[zjinfo.name].nums; }else{ // 该章节总图片 ziplist[zjinfo.name].total=$(res).find('.uk-zjimg img').length $(res).find('.uk-zjimg img').each(function(i,it){ //await downloadimg($(it).attr('src'),zjinfo.name); console.log(`第${i}图,共${ziplist[zjinfo.name].total}张图`) var src=$(it).attr('src'); var name=src.split('/'); var filename=name[name.length-1]; GM_xmlhttpRequest({ method: 'GET', url: src, responseType: 'arraybuffer', onload: function(data) { ziplist[zjinfo.name].nums++; if(!data.response ||data.response.byteLength<10){ ziplist[zjinfo.name].zip.file(zipdir+'/'+`${filename}下载失败.txt`,'下载失败') }else{ ziplist[zjinfo.name].zip.file(zipdir+'/'+filename, data.response, { binary: true }); } }, onerror: function(data) { ziplist[zjinfo.name].nums++; } }); // e }); } }, error:function(e){ console.log(e); console.log("下载失败"+zjinfo.name); ziplist[zjinfo.name].total=ziplist[zjinfo.name].nums; // 失败了,重试 ziplist[zjinfo.name].zip.file(zipdir+"/下载失败.txt", "下载失败\n"); } }); // end } // 按照章节下载 var startdownload_zj=function(){ // 初始化,不重新获取章节信息 if($('#monkey_downbtn_zj').hasClass('uk-disabled')){ $('#monkey_downbtn_zj').text('还在下载中请稍等..'); return; } $('#monkey_downbtn_zj').addClass('uk-disabled').text('下载中'); ziplist={}; ziplist_ing={}; var firstzj=parseInt($('#monkey_div [name="start"]').val()); var endzj=parseInt($('#monkey_div [name="end"]').val()); if(firstzj>endzj){ var tmp=endzj; endzj=firstzj; firstzj=tmp; } // 遍历所有章节 $('#monkey_process').css('width','1%'); zjlist.forEach((zjinfo,i)=>{ if(i>=firstzj && i<=endzj){ allzjinfo.push(zjinfo); ziplist[zjinfo.name]={nums:0,total:1,zip:new JSZip()} } }); ziplist_end=allzjinfo.length; if(allzjinfo.length>0){ download_zj(allzjinfo.shift()); } }; // 设置 进度 var setprocess=function(num){ if(haddown==0||allnums==0||num===-1){ $('#monkey_process').css('width','0').text(''); }else{ var percent=(Math.round(haddown*100/allnums,2)); percent=percent<5?5:percent; var text=allnums>500?`${allnums}张图,打包时间较长,完成后自动弹出,请稍等`:'打包中,完成后将自动弹出,请稍等'; $('#monkey_process').css('width',percent+'%').text(percent>=100?text:`${percent}%`); } }; // 生成按钮 var createbtn=function(){ init(); getzjlist(); var start=[]; var end=[]; zjlist.forEach((it,i)=>{ start.push(`<option value="${i}" ${i===0?"selected":""}>${it.name}</option>`); end.push(`<option value="${i}" ${i===(zjlist.length-1)?"selected":""}>${it.name}</option>`); }); start=start.join("\n"); end=end.join("\n"); var html=`<div id="monkey_div" style="display:inline-block;font-size:12px;padding:3px 8px"> 开始:<select name="start" style="width:80px;height:35px;line-height:35px" class="uk-select"> ${start} </select> 结束:<select name="end" style="width:80px;height:35px;line-height:35px" class="uk-select"> ${end} </select> <a href="javascript:;" class="uk-button uk-button-danger uk-button-small" id="monkey_downbtn">打包下载</a> <a href="javascript:;" class="uk-button uk-button-secondary uk-button-small none" id="chongshixiazai">重试下载</a> <a href="javascript:;" class="uk-button uk-button-primary uk-button-small" id="monkey_downbtn_zj" >按话下载</a> <a href="https://www.bilibili.com/read/cv13585336/" class="uk-button uk-text-primary" target="_blank">第一次下载点击查看</a> <div class="uk-position-relative uk-width uk-background-muted" style="height:5px;bottom:-5px;"> <div id="monkey_process" class="uk-position-absolute uk-background-primary uk-flex uk-flex-center" style="height:5px;width:0;align-items:center"></div> </div> </div>`; $("h3.uk-heading-divider").append(html); $("#monkey_downbtn").on('click', () => { startdownload(); }); $("#monkey_downbtn_zj").on('click', () => { startdownload_zj(); }); }; createbtn(); var total_time=0 window.mytimeid=setInterval(function(){ total_time+=0.5 if(downing && total_time>300){ $('#chongshixiazai').removeClass('none'); } if(downing && allnums>0 && allnums==haddown){ downing=false; setprocess(); zip.generateAsync({ type: "blob", compression: "DEFLATE" }).then((zipFile) => { console.log('11total_time='+total_time) saveAs(zipFile, manhuaname+".zip"); total_time=0 console.log('22total_time='+total_time) console.log('100%'); setTimeout(function(){ $('#monkey_downbtn').removeClass('uk-disabled').text('下载'); $('#monkey_process').css('width','0').text(''); init(); },3000); }); }else{ downing && setprocess(); } // var len=ziplist_end if(ziplist_order.length>0 && len>0){ var k=ziplist_order.shift(); var percent=(Math.round((len-allzjinfo.length)*100/len,2)); $('#monkey_process').css('width',(percent<1?1:percent)+'%'); console.log(k,percent,len) console.log(ziplist[k].nums,'===',ziplist[k].total) if(ziplist[k].nums>=ziplist[k].total){ ziplist[k].zip.generateAsync({ type: "blob", compression: "DEFLATE" }).then((zipFile) => { // 开始下一个 saveAs(zipFile, k+".zip"); if(allzjinfo.length>0){ download_zj(allzjinfo.shift()); }else{ $('#monkey_downbtn_zj').removeClass('uk-disabled').text('下载完毕打包中'); ziplist={}; ziplist_end=0; ziplist_ing={}; $('#monkey_downbtn_zj').removeClass('uk-disabled').text('下载完毕'); } }); }else{ ziplist_order.unshift(k); } } },500); })();