您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Manga downloader for comic-fuz.com
// ==UserScript== // @name ComicFuzDownloader // @namespace https://github.com/Timesient/manga-download-scripts // @version 0.9 // @license GPL-3.0 // @author Timesient // @description Manga downloader for comic-fuz.com // @icon https://comic-fuz.com/favicons/favicon-32x32.png // @homepageURL https://gf.qytechs.cn/scripts/451863-comicfuzdownloader // @supportURL https://github.com/Timesient/manga-download-scripts/issues // @match https://comic-fuz.com/* // @require https://unpkg.com/[email protected]/dist/axios.min.js // @require https://unpkg.com/[email protected]/dist/jszip.min.js // @require https://unpkg.com/[email protected]/dist/FileSaver.min.js // @require https://update.gf.qytechs.cn/scripts/451810/1398192/ImageDownloaderLib.js // @grant GM_info // @grant GM_xmlhttpRequest // ==/UserScript== (async function(axios, JSZip, saveAs, ImageDownloader) { 'use strict'; // if not in specific manga chapter const re4Path = /https:\/\/comic-fuz\.com\/manga\/\d+.*/; const timer4Path = setInterval(() => { if (re4Path.test(window.location.href)) { const currentTitle = document.querySelector('[class*=Chapter_chapter__isReading] h3[class*=Chapter_chapter__name]')?.textContent; const chapters = __NEXT_DATA__?.props?.pageProps?.chapters?.reduce((acc, cur) => acc.concat(cur.chapters), []); if (currentTitle && chapters) { for (const chapter of chapters) { if (chapter.chapterMainName === currentTitle) { clearInterval(timer4Path) window.location.href = `https://comic-fuz.com/manga/viewer/${chapter.chapterId}`; } } } } }, 200); // reload page when enter or leave chapter const re = /https:\/\/comic-fuz\.com\/(manga|book|magazine)\/(viewer|\d+).*/; const oldHref = window.location.href; const timer = setInterval(() => { const newHref = window.location.href; if (newHref === oldHref) return; if (re.test(newHref) || re.test(oldHref)) { clearInterval(timer); window.location.reload(); } }, 200); // return if not reading chapter now if (!re.test(oldHref)) return; // get type and id const { type, id } = window.location.pathname.match(/\/(?<type>.*)\/viewer\/(?<id>.*)/).groups; // set the endpoint of API request const endpoint = ({ 'manga': 'manga_viewer', 'book': 'book_viewer_2', 'magazine': 'magazine_viewer_2', })[type]; // set the body of API request const requestBody = ({ 'manga': { deviceInfo: { deviceType: 2 }, chapterId: id, useTicket: false, consumePoint: { event: 0, paid: 0 } }, 'book': { deviceInfo: { deviceType: 2 }, bookIssueId: id, purchaseRequest: false, consumePaidPoint: 0 }, 'magazine': { deviceInfo: { deviceType: 2 }, magazineIssueId: id, purchaseRequest: false, consumePaidPoint: 0 }, })[type]; // get config data from API const url = `https://api.comic-fuz.com/v1/${endpoint}`; const config = await fetch(url, { method: 'POST', body: createInfoSchema().encodeInfo(requestBody), credentials: 'include', }).then(res => res.text()); // get encrypted image data const imageDataRegexp = /(\/[fkh].*&e=\d{10}).*([0-9a-z]{32})"@([0-9a-z]{64})/gm; const encryptedImageData = Array.from(config.matchAll(imageDataRegexp)).map(item => ({ url: 'https://img.comic-fuz.com' + item[1], iv: item[2], key: item[3] })); // setup ImageDownloader ImageDownloader.init({ maxImageAmount: encryptedImageData.length, getImagePromises, title: `comic-fuz-${type}-${id}` }); // collect promises of image function getImagePromises(startNum, endNum) { return encryptedImageData .slice(startNum - 1, endNum) .map(data => getDecryptedImage(data) .then(ImageDownloader.fulfillHandler) .catch(ImageDownloader.rejectHandler) ); } // get promise of decrypted image async function getDecryptedImage(data) { function En(e) { const t = e.match(/.{1,2}/g); return new Uint8Array(t.map((function (e) { return parseInt(e, 16) }))); } const encryptedImage = await axios.get(data.url, { responseType: 'arraybuffer' }).then(res => res.data); const key = await crypto.subtle.importKey('raw', En(data.key), "AES-CBC", false, ['decrypt']); const decryptedImage = await crypto.subtle.decrypt({ name: 'AES-CBC', iv: En(data.iv) }, key, encryptedImage); return decryptedImage; } // I copy these code from somewhere else before, but I cant find the source now. // But surely they are related to 'protobuf' and 'protobufjs' function createInfoSchema() { const exports = {}; exports.encodeInfo = function (message) { var bb = popByteBuffer(); _encodeInfo(message, bb); return toUint8Array(bb); } function _encodeInfo(message, bb) { // optional DeviceInfo deviceInfo = 1; var $deviceInfo = message.deviceInfo; if ($deviceInfo !== undefined) { writeVarint32(bb, 10); var nested = popByteBuffer(); _encodeDeviceInfo($deviceInfo, nested); writeVarint32(bb, nested.limit); writeByteBuffer(bb, nested); pushByteBuffer(nested); } // optional uint32 chapterId = 2; var $chapterId = message.chapterId; if ($chapterId !== undefined) { writeVarint32(bb, 16); writeVarint32(bb, $chapterId); } // I try this and it works! var $bookIssueId = message.bookIssueId; if ($bookIssueId !== undefined) { writeVarint32(bb, 16); writeVarint32(bb, $bookIssueId); } // I try this and it works! var $magazineIssueId = message.magazineIssueId; if ($magazineIssueId !== undefined) { writeVarint32(bb, 16); writeVarint32(bb, $magazineIssueId); } // optional bool useTicket = 3; var $useTicket = message.useTicket; if ($useTicket !== undefined) { writeVarint32(bb, 24); writeByte(bb, $useTicket ? 1 : 0); } // optional ConsumePoint consumePoint = 4; var $consumePoint = message.consumePoint; if ($consumePoint !== undefined) { writeVarint32(bb, 34); var nested = popByteBuffer(); _encodeConsumePoint($consumePoint, nested); writeVarint32(bb, nested.limit); writeByteBuffer(bb, nested); pushByteBuffer(nested); } }; exports.decodeInfo = function (binary) { return _decodeInfo(wrapByteBuffer(binary)); } function _decodeInfo(bb) { var message = {}; end_of_message: while (!isAtEnd(bb)) { var tag = readVarint32(bb); switch (tag >>> 3) { case 0: break end_of_message; // optional DeviceInfo deviceInfo = 1; case 1: { var limit = pushTemporaryLength(bb); message.deviceInfo = _decodeDeviceInfo(bb); bb.limit = limit; break; } // optional uint32 chapterId = 2; case 2: { message.chapterId = readVarint32(bb) >>> 0; break; } // optional bool useTicket = 3; case 3: { message.useTicket = !!readByte(bb); break; } // optional ConsumePoint consumePoint = 4; case 4: { var limit = pushTemporaryLength(bb); message.consumePoint = _decodeConsumePoint(bb); bb.limit = limit; break; } default: skipUnknownField(bb, tag & 7); } } return message; }; exports.encodeDeviceInfo = function (message) { var bb = popByteBuffer(); _encodeDeviceInfo(message, bb); return toUint8Array(bb); } function _encodeDeviceInfo(message, bb) { // optional uint32 deviceType = 3; var $deviceType = message.deviceType; if ($deviceType !== undefined) { writeVarint32(bb, 24); writeVarint32(bb, $deviceType); } }; exports.decodeDeviceInfo = function (binary) { return _decodeDeviceInfo(wrapByteBuffer(binary)); } function _decodeDeviceInfo(bb) { var message = {}; end_of_message: while (!isAtEnd(bb)) { var tag = readVarint32(bb); switch (tag >>> 3) { case 0: break end_of_message; // optional uint32 deviceType = 3; case 3: { message.deviceType = readVarint32(bb) >>> 0; break; } default: skipUnknownField(bb, tag & 7); } } return message; }; exports.encodeConsumePoint = function (message) { var bb = popByteBuffer(); _encodeConsumePoint(message, bb); return toUint8Array(bb); } function _encodeConsumePoint(message, bb) { // optional uint32 event = 1; var $event = message.event; if ($event !== undefined) { writeVarint32(bb, 8); writeVarint32(bb, $event); } // optional uint32 paid = 2; var $paid = message.paid; if ($paid !== undefined) { writeVarint32(bb, 16); writeVarint32(bb, $paid); } }; exports.decodeConsumePoint = function (binary) { return _decodeConsumePoint(wrapByteBuffer(binary)); } function _decodeConsumePoint(bb) { var message = {}; end_of_message: while (!isAtEnd(bb)) { var tag = readVarint32(bb); switch (tag >>> 3) { case 0: break end_of_message; // optional uint32 event = 1; case 1: { message.event = readVarint32(bb) >>> 0; break; } // optional uint32 paid = 2; case 2: { message.paid = readVarint32(bb) >>> 0; break; } default: skipUnknownField(bb, tag & 7); } } return message; }; function pushTemporaryLength(bb) { var length = readVarint32(bb); var limit = bb.limit; bb.limit = bb.offset + length; return limit; } function skipUnknownField(bb, type) { switch (type) { case 0: while (readByte(bb) & 0x80) { } break; case 2: skip(bb, readVarint32(bb)); break; case 5: skip(bb, 4); break; case 1: skip(bb, 8); break; default: throw new Error("Unimplemented type: " + type); } } // The code below was modified from https://github.com/protobufjs/bytebuffer.js // which is under the Apache License 2.0. var f32 = new Float32Array(1); var f32_u8 = new Uint8Array(f32.buffer); var f64 = new Float64Array(1); var f64_u8 = new Uint8Array(f64.buffer); var bbStack = []; function popByteBuffer() { const bb = bbStack.pop(); if (!bb) return { bytes: new Uint8Array(64), offset: 0, limit: 0 }; bb.offset = bb.limit = 0; return bb; } function pushByteBuffer(bb) { bbStack.push(bb); } function wrapByteBuffer(bytes) { return { bytes, offset: 0, limit: bytes.length }; } function toUint8Array(bb) { var bytes = bb.bytes; var limit = bb.limit; return bytes.length === limit ? bytes : bytes.subarray(0, limit); } function skip(bb, offset) { if (bb.offset + offset > bb.limit) { throw new Error('Skip past limit'); } bb.offset += offset; } function isAtEnd(bb) { return bb.offset >= bb.limit; } function grow(bb, count) { var bytes = bb.bytes; var offset = bb.offset; var limit = bb.limit; var finalOffset = offset + count; if (finalOffset > bytes.length) { var newBytes = new Uint8Array(finalOffset * 2); newBytes.set(bytes); bb.bytes = newBytes; } bb.offset = finalOffset; if (finalOffset > limit) { bb.limit = finalOffset; } return offset; } function advance(bb, count) { var offset = bb.offset; if (offset + count > bb.limit) { throw new Error('Read past limit'); } bb.offset += count; return offset; } function writeByteBuffer(bb, buffer) { var offset = grow(bb, buffer.limit); var from = bb.bytes; var to = buffer.bytes; // This for loop is much faster than subarray+set on V8 for (var i = 0, n = buffer.limit; i < n; i++) { from[i + offset] = to[i]; } } function readByte(bb) { return bb.bytes[advance(bb, 1)]; } function writeByte(bb, value) { var offset = grow(bb, 1); bb.bytes[offset] = value; } function readVarint32(bb) { var c = 0; var value = 0; var b; do { b = readByte(bb); if (c < 32) value |= (b & 0x7F) << c; c += 7; } while (b & 0x80); return value; } function writeVarint32(bb, value) { value >>>= 0; while (value >= 0x80) { writeByte(bb, (value & 0x7f) | 0x80); value >>>= 7; } writeByte(bb, value); } return exports; } })(axios, JSZip, saveAs, ImageDownloader);
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址