bk

用于下载bk的书籍图片,下载下来之后需要自己用pdf软件合并

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

Advertisement:

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

Advertisement:

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         bk
// @version      0.7
// @author       Fructose
// @match        https://wk6.bookan.com.cn/*
// @grant        GM_addStyle
// @require      https://code.jquery.com/jquery-3.1.0.js
// @license      MIT
// @description  用于下载bk的书籍图片,下载下来之后需要自己用pdf软件合并
// @namespace https://greasyfork.org/users/1261551
// ==/UserScript==
'use strict';

/* 用于页面添加下载按钮 */
GM_addStyle(`
    #downloadMain { position: fixed; z-index: 999; right: 0; bottom: 20px; background: #fff; }
    #downloadMain-bd { box-sizing: border-box; height: 60px; padding: 10px; border: 1px solid #ccc; }
    #downloadMainBtn { font-size: 13px; line-height: 39px; display: inline-block; width: 60px; height: 40px; margin-left: -5px; text-align: center; text-decoration: none; color: #fff; background: #4d90fe; }
    #zizhuPopupMainInput { display: inline-block; box-sizing: border-box; width: 210px; height: 40px; padding: 0 10px; border: 1px solid #ccc; outline: none; }
`);

// 添加内容
var smallCnt = `
    <div id="downloadMain">
        <input type="text" name="" placeholder="与真实页面的偏移量,没啥用填0" id="zizhuPopupMainInput" />
        <a href="javascript:void(0);" id="downloadMainBtn">下载</a>
    </div>`;
// 添加到 body
var odom = document.createElement("div");
odom.id = "downloadMain";
odom.innerHTML = smallCnt;
document.body.appendChild(odom);

/* 这是一个封装好的下载文件的函数 */
const downFile = (url, name, type) => {
    return new Promise((resolve, reject) => {
        fileAjax(url, function (xhr) {
            downloadFile(xhr.response, name.concat(type));
            resolve(); // 下载完成后解析Promise
        }, {
            responseType: 'blob'
        }, reject);
    });
};

function fileAjax(url, callback, options, reject) {
    let xhr = new XMLHttpRequest();
    xhr.open('get', url, true);
    if (options.responseType) {
        xhr.responseType = options.responseType;
    }
    xhr.onreadystatechange = function () {
        if (xhr.readyState === 4 && xhr.status === 200) {
            callback(xhr);
        } else if (xhr.readyState === 4) {
            reject(new Error('请求失败'));
        }
    };
    xhr.send();
}

function downloadFile(content, filename) {
    window.URL = window.URL || window.webkitURL;
    let a = document.createElement('a');
    let blob = new Blob([content]);
    // 通过二进制文件创建url
    let url = window.URL.createObjectURL(blob);
    a.href = url;
    a.download = filename;
    a.click();
    // 销毁创建的url
    window.URL.revokeObjectURL(url);
}

/* 实现sleep函数,减慢速度 */
const sleep = (time) => {
    return new Promise(resolve => setTimeout(resolve, time));
};

/* 等待页面翻页变化的函数 */
const waitForPageChange = async (currentPageNum) => {
    console.log(`等待页面从 ${currentPageNum} 翻页...`);

    // 尝试更长的等待时间(20秒),并分段检测
    for (let i = 1; i <= 20; i++) {
        await sleep(1000); // 每秒检查一次

        const newPageNum = page("num");
        console.log(`第 ${i} 秒检查,当前页码:${newPageNum}`);

        // 关键修改:只有当newPageNum是有效数字且不等于currentPageNum时才认为是成功翻页
        if (newPageNum && newPageNum !== currentPageNum) {
            console.log(`页面已成功翻页!新页码:${newPageNum}`);
            return newPageNum;
        }

        // 如果返回null,说明图片还没加载好,继续等待
        if (newPageNum === null) {
            console.log(`图片元素未找到,继续等待...`);
        }
    }

    console.log("等待超时,页码未变化");
    return currentPageNum; // 返回原页码表示失败
};

/* 带重试的翻页函数 */
const turnPageWithRetry = async (retryCount = 2) => {
    for (let attempt = 1; attempt <= retryCount; attempt++) {
        console.log(`翻页尝试 ${attempt}/${retryCount}`);

        const beforePageNum = page("num");

        // 点击翻页(使用原有的点击逻辑)
        const pageWrappers = document.querySelectorAll("div.page-wrapper");
        let clicked = false;
        for (let wrapper of pageWrappers) {
            const styleAttr = wrapper.getAttribute('style') || '';
            if (!styleAttr.includes('display: none')) {
                // 尝试多种点击方式
                if (typeof wrapper.click === 'function') {
                    wrapper.click();
                } else {
                    const event = new MouseEvent('click', {
                        view: window,
                        bubbles: true,
                        cancelable: true
                    });
                    wrapper.dispatchEvent(event);
                }
                clicked = true;
                console.log('已点击翻页');
                break;
            }
        }

        if (!clicked) {
            console.log('没有找到可点击的页面');
            break;
        }

        // 等待页面变化
        const afterPageNum = await waitForPageChange(beforePageNum);

        if (afterPageNum !== beforePageNum) {
            return afterPageNum; // 翻页成功
        }

        if (attempt < retryCount) {
            console.log(`翻页尝试 ${attempt} 失败,等待2秒后重试`);
            await sleep(2000); // 等待2秒后重试
        }
    }

    console.log(`翻页失败,已尝试 ${retryCount} 次`);
    return null;
};

/* 处理URL,确保使用HTTPS */
const processUrl = (url) => {
    if (url.startsWith('http://')) {
        return 'https://' + url.substring(7);
    }
    return url;
};

/* 以下为针对本页面的逻辑 */
/* 以下为针对本页面获取 提取该页面的图片地址、页数(不是页码)、#id */
const page = (type) => {
    // 获取当前可见的图片(从没有 display:none 的 page-wrapper 中查找)
    const pageWrappers = document.querySelectorAll("div.page-wrapper");
    let currentImg = null;

    // 查找可见的 page-wrapper(没有 display: none 样式)
    for (let wrapper of pageWrappers) {
        // 检查 style 属性,排除 display: none 的
        const styleAttr = wrapper.getAttribute('style') || '';
        if (!styleAttr.includes('display: none')) {
            // 更精确地查找图片:在 slide page 元素内查找
            const slidePage = wrapper.querySelector('.slide.page');
            if (slidePage) {
                currentImg = slidePage.querySelector('img[id^="bkImg_"]');
            }
            // 如果没找到,回退到原来的方法
            if (!currentImg) {
                currentImg = wrapper.querySelector('img');
            }
            if (currentImg && currentImg.src && currentImg.src.includes('bookan.com.cn')) {
                break;
            }
        }
    }

    if (!currentImg) {
        console.log('未找到可见的图片元素');
        return null;
    }

    switch (type) {
        case "src":
            return currentImg.src;
        case "num":
            // 尝试从多个来源获取页码
            const imgId = currentImg.id;
            const imgTitle = currentImg.getAttribute('title');
            const pageWrapper = currentImg.closest('.page-wrapper');
            const wrapperPage = pageWrapper ? pageWrapper.getAttribute('page') : null;

            // 优先级:1. wrapper的page属性 2. img的title 3. img的id
            if (wrapperPage) {
                return wrapperPage;
            } else if (imgTitle) {
                return imgTitle;
            } else if (imgId && imgId.startsWith('bkImg_')) {
                return imgId.substring(6);
            }
            return "0";
        case "id":
            return currentImg.id;
    }
};

const downloadedFiles = []; // 存储已下载文件名

const downloadStart = async () => {
    const offset = Number(document.getElementById("zizhuPopupMainInput").value);
    let currentPageNum = page("num");

    console.log(`起始页码:${currentPageNum}`);

    while (currentPageNum && parseInt(currentPageNum) > 0) {
        const fileName = `${String(currentPageNum - offset)}.jpg`;
        console.log(`处理第 ${currentPageNum} 页,文件名:${fileName}`);

        if (!downloadedFiles.includes(fileName)) {
            const currentSrc = page("src");
            if (currentSrc) {
                console.log(`准备下载 ${fileName}, 图片地址:${currentSrc}`);
                try {
                    await downFile(processUrl(currentSrc), fileName, ".jpg");
                    downloadedFiles.push(fileName); // 记录已下载的文件名
                    console.log(`${fileName} 下载成功`);
                } catch (error) {
                    console.error("下载失败:", error);
                }
            } else {
                console.log(`未找到图片地址,跳过 ${fileName}`);
            }
        } else {
            console.log(`${fileName} 已经下载过,跳过`);
        }

        // 使用带重试的翻页函数
        const newPageNum = await turnPageWithRetry(2); // 最多重试2次

        if (!newPageNum) {
            console.log("翻页失败,停止下载");
            break;
        }

        if (newPageNum === currentPageNum) {
            console.log("页码没有变化,可能已经翻到最后一页了");
            break;
        }

        currentPageNum = newPageNum;
        console.log(`翻页成功!下一页码:${currentPageNum}`);
    }

    console.log("下载完成!");
};

document.getElementById('downloadMainBtn').addEventListener('click', downloadStart); // 监听按钮是否被点击,如果点击则开始下载