comic-walker 漫画下载

下载comic-walker网站的免费漫画

当前为 2022-06-06 提交的版本,查看 最新版本

// ==UserScript==
// @name         comic-walker 漫画下载
// @namespace    shadows
// @version      0.1.5
// @description  下载comic-walker网站的免费漫画
// @author       shadows
// @license      MIT License
// @copyright    Copyright (c) 2021 shadows
// @icon         https://dimg04.c-ctrip.com/images/0391j120008r0n8a84D94.png
// @icon64       https://static.yximgs.com/bs2/adInnovationResource/367c797d005b4b1ab180f0a361a7ef43.png
// @match        https://comic-walker.com/contents/detail/*
// @require      https://gcore.jsdelivr.net/npm/[email protected]/dist/jszip.min.js
// @require      https://gcore.jsdelivr.net/npm/[email protected]/dist/FileSaver.min.js
// ==/UserScript==
"use strict";
addButton()

function addButton() {
    let targets = document.querySelectorAll(".acBacknumber-item-leftbox");
    console.log(targets);
    for (let elem of targets) {
        if (elem.parentNode.querySelector(".download-button")) continue;
        let name = elem.querySelector(".acBacknumber-title").textContent;
        let subtitle = elem.querySelector(".acBacknumber-subtitle").textContent
        if ( subtitle != "") name += ( " " + subtitle );
        let href = new URL(elem.parentNode.querySelector('a').href);
        let params = href.searchParams;
        let cid = params.get('cid');
        elem.after(creatButton(name, cid));
    }
}

function creatButton(name, cid) {
    let button = document.createElement('button');
    button.classList.add("download-button");
    button.textContent = "Download";
    button.style.cssText = `z-index: 2;
    background: linear-gradient(135deg, #6e8efb, #a777e3);
    color: white;
    padding: 3px 3px;
    margin: 4px 0px;
    text-align: center;
    border-radius: 3px;
    border-width: 0px;`;
    button.dataset.cid = cid;
    button.dataset.name = name;
    button.onclick = download;
    return button;
}

async function download(event) {
    event.stopPropagation();
    event.preventDefault();
    let elem = event.target;
    let name = elem.dataset.name;
    let cid = elem.dataset.cid;
    let imagesData = await getImageSData(cid);
    let images = await downloadImages(imagesData, name);
    let zip = new JSZip();
    let targetLength = images.length.toString().length;
    for (let image of images) {
        let decrypted = decryptImage(image);
        zip.file(`${image.id.toString().padStart(targetLength,'0')}.jpg`, decrypted);
    }
    zip.generateAsync({ type: "blob", base64: true }).then(content => saveAs(content, `${name}.zip`));
}

async function getImageSData(cid) {
    return fetch(`https://comicwalker-api.nicomanga.jp/api/v1/comicwalker/episodes/${cid}/frames`)
        .then(resp => resp.json())
        .then(json => {
            let dataArray = json.data.result;
            return dataArray.map((item, index) => ({ drm_hash: item.meta.drm_hash, source_url: item.meta.source_url, id: index + 1 }))
        });
}


function downloadImages(imagesData, name) {

    async function downloadSingleImage(item) {
        return fetch(item.source_url).then(resp => resp.arrayBuffer()).then(arrayBuffer => {
            console.log(`${name}-${item.id} have downloaded.`);
            //返回包含id drm_hash与图片数据的对象
            return { id: item.id, drm_hash: item.drm_hash, content: arrayBuffer };
        });
    }

    let images = asyncPool(10, imagesData, downloadSingleImage);
    return images;
}

/**
 * 解密图片
 * @param {Object} image
 * @param {String} image.drm_hash 解密密钥
 * @param {ArrayBuffer} image.content 图片数据
 * @returns {Blob} 已解密的图片Blob对象
 */
function decryptImage({ drm_hash, content }) {
    if (drm_hash == null) return new Blob([content], { type: "image/jpeg" });
    let key = generateKey(drm_hash);
    let contentUint8Array = arrayBufferToUint8(content);
    let decryptedUin8Array = xor(contentUint8Array, key);
    return new Blob([decryptedUin8Array], { type: "image/jpeg" });
}

function generateKey(drm_hash) {
    drm_hash = drm_hash.slice(0, 16).match(/[\da-f]{2}/gi);
    return new Uint8Array(drm_hash.map(i => parseInt(i, 16)));
}


/**
 * xor解密
 * @param {Uint8Array} content 待解密的内容
 * @param {Uint8Array} key 密钥
 * @returns Uint8Array 结果
 */
function xor(content, key) {
    let result = new Uint8Array(content.length);
    let keyLength = key.length;
    for (let i = 0; i < content.length; i++) {
        result[i] = content[i] ^ key[i % keyLength];
    }
    return result;
}

function arrayBufferToUint8(arrayBuffer) {
    return new Uint8Array(arrayBuffer);
}

/**
 * @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))
}

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址