WQHarvester 文泉收割机

下载文泉书局已购电子书,自动合并阅读器中的书页切片并下载为完整页面图片,需结合仓库里的另一个 Python 脚本使用。

// ==UserScript==
// @name         WQHarvester 文泉收割机
// @namespace    http://tampermonkey.net/
// @version      1.5
// @description  下载文泉书局已购电子书,自动合并阅读器中的书页切片并下载为完整页面图片,需结合仓库里的另一个 Python 脚本使用。
// @author       zetaloop
// @homepage     https://github.com/zetaloop/WQHarvester
// @match        https://wqbook.wqxuetang.com/deep/read/pdf*
// @match        *://wqbook.wqxuetang.com/deep/read/pdf*
// @grant        none
// @license      The Unlicense
// ==/UserScript==

(function () {
    "use strict";

    console.log("文泉收割机已加载");

    // 跟踪每页的切片加载情况,key为页面index,值为Map {leftValue -> {img, count}}
    const pageSlices = {};

    // 当前处理的最小页面
    let currentMinPage = Infinity;

    // 起始页面
    let startPage = 1;

    // 已完成(合并保存)的页面集合
    const completedPages = new Set();

    // 待合并的页面集合(切片已加载完成但尚未合并)
    const pendingPages = new Set();

    // 处理中的页面集合(等待切片加载中)
    const processingPages = new Set();

    // 当前活动页面(用于控制只合并当前页)
    let activePage = null;

    // 是否正在运行
    let isRunning = false;

    // 脚本是否已初始化
    let isInitialized = false;

    // 是否有面板已创建
    let panelCreated = false;

    // DOM观察器引用
    let observer = null;

    // 页面跳转定时器(确保同时只有一个跳转等待)
    let jumpTimeout = null;

    // 面板各元素引用
    let mainPanel,
        statusDisplay,
        progressDisplay,
        currentPageInfo,
        mergedProgressDisplay,
        completionNotice;

    // 消息定时器
    let noticeTimer = null;

    // 自动点击“重新加载本页”按钮的定时器
    let reloadInterval = null;

    // 全局合并用的画布,复用以提升效率
    let mergeCanvas = null;

    // 提取书籍ID
    function getBookId() {
        const urlParams = new URLSearchParams(window.location.search);
        return urlParams.get("bid") || "unknown";
    }

    // 新增:保存目录信息为 JSON 文件({bookid}_toc.json)
    // JSON 结构:数组,每个节点包含 name(目录名称)、page(对应页码)和 children(子节点数组)
    function saveTOC() {
        // 查找目录容器(目录区域一般带有 class "catalogue left-scroll")
        const tocContainer = document.querySelector(".catalogue.left-scroll");
        if (!tocContainer) {
            console.log("未找到目录容器,跳过保存目录。");
            return;
        }
        // 在目录容器中查找目录树(一般带有 class "el-tree book-tree")
        const treeRoot = tocContainer.querySelector(".el-tree.book-tree");
        if (!treeRoot) {
            console.log("未找到目录树,跳过保存目录。");
            return;
        }
        // 递归解析目录树
        function parseTreeItems(container) {
            let items = [];
            const treeItems = container.querySelectorAll(
                ':scope > [role="treeitem"]'
            );
            treeItems.forEach((item) => {
                let obj = {};
                const node = item.querySelector(".tree-node");
                if (node) {
                    const leftSpan = node.querySelector(".node-left");
                    const rightSpan = node.querySelector(".node-right");
                    if (leftSpan) {
                        obj.name = leftSpan.textContent.trim();
                    }
                    if (rightSpan) {
                        const pageSpan = rightSpan.querySelector("span");
                        if (pageSpan) {
                            obj.page = pageSpan.textContent.trim();
                        } else {
                            obj.page = null;
                        }
                    } else {
                        obj.page = null;
                    }
                }
                const childrenGroup = item.querySelector(
                    ':scope > [role="group"]'
                );
                if (childrenGroup) {
                    obj.children = parseTreeItems(childrenGroup);
                } else {
                    obj.children = [];
                }
                items.push(obj);
            });
            return items;
        }
        const toc = parseTreeItems(treeRoot);
        const tocJson = JSON.stringify(toc, null, 2);
        const bookid = getBookId();
        const filename = `${bookid}_toc.json`;
        const blob = new Blob([tocJson], { type: "application/json" });
        const a = document.createElement("a");
        a.href = URL.createObjectURL(blob);
        a.download = filename;
        a.style.display = "none";
        document.body.appendChild(a);
        a.click();
        setTimeout(() => {
            URL.revokeObjectURL(a.href);
            document.body.removeChild(a);
        }, 100);
        console.log(`目录已保存为 ${filename}`);
    }

    // 自动检测并点击“重新加载本页”按钮(每秒检测一次)
    function checkReloadButton() {
        const reloadButtons = document.querySelectorAll(".reload_image");
        reloadButtons.forEach((btn) => {
            if (btn.offsetParent !== null) {
                // 如果元素可见
                const pageBox = btn.closest(".page-img-box");
                if (pageBox) {
                    const pageIndex = pageBox.getAttribute("index");
                    if (!completedPages.has(pageIndex)) {
                        console.log(
                            `检测到页面 ${pageIndex} 的“重新加载本页”按钮,自动点击`
                        );
                        updateStatusDisplay(
                            `检测到页面 ${pageIndex} 重载按钮,正在点击...`
                        );
                        btn.click();
                    }
                }
            }
        });
    }

    // 显示临时通知消息
    function showNotice(message, duration = 500) {
        if (!completionNotice) return;
        if (noticeTimer) clearTimeout(noticeTimer);
        completionNotice.textContent = message;
        completionNotice.style.opacity = "1";
        noticeTimer = setTimeout(() => {
            completionNotice.style.opacity = "0";
        }, duration);
    }

    // 更新状态信息显示
    function updateStatusDisplay(message) {
        if (statusDisplay) {
            statusDisplay.textContent = message;
        }
    }

    // 更新当前页面加载进度显示(针对切片加载进度,显示当前页及加载的切片数量)
    function updateCurrentPageInfo(message) {
        if (currentPageInfo) {
            currentPageInfo.innerHTML = message;
        }
    }

    // 更新当前页面的加载进度条(基于切片加载情况,按left区间分6块;颜色根据加载次数)
    function updateProgressBar(pageIndex, slices) {
        if (!progressDisplay) return;
        progressDisplay.innerHTML = "";
        if (!slices || slices.size === 0) {
            progressDisplay.innerHTML = `
                <div class="progress-container">
                    <div class="progress-item"></div>
                    <div class="progress-item"></div>
                    <div class="progress-item"></div>
                    <div class="progress-item"></div>
                    <div class="progress-item"></div>
                    <div class="progress-item"></div>
                </div>
            `;
            return;
        }
        // 获取所有切片条目,并转换left为数字,保留count信息
        const sliceEntries = Array.from(slices.entries()).map(([left, obj]) => [
            parseFloat(left),
            obj,
        ]);
        sliceEntries.sort((a, b) => a[0] - b[0]);
        const minLeft = sliceEntries[0][0];
        const maxLeft = sliceEntries[sliceEntries.length - 1][0];
        const range = maxLeft - minLeft;
        const interval = range / 5; // 分成6段

        const container = document.createElement("div");
        container.className = "progress-container";

        // 为每个区间创建一个进度块
        for (let i = 0; i < 6; i++) {
            const lowerBound =
                i === 0 ? minLeft - 0.1 : minLeft + interval * (i - 0.01);
            const upperBound =
                i === 5 ? maxLeft + 0.1 : minLeft + interval * (i + 1.01);
            const progressItem = document.createElement("div");
            progressItem.className = "progress-item";

            // 找到落在该区间的切片,计算最大加载次数
            const entriesInInterval = sliceEntries.filter(
                ([left, obj]) => left >= lowerBound && left <= upperBound
            );
            if (entriesInInterval.length > 0) {
                const maxCount = Math.max(
                    ...entriesInInterval.map((e) => e[1].count)
                );
                // 第一次加载(count==1)使用淡绿色,否则使用深绿色
                if (maxCount === 1) {
                    progressItem.classList.add("loaded-light");
                } else {
                    progressItem.classList.add("loaded-dark");
                }
            }
            container.appendChild(progressItem);
        }

        progressDisplay.appendChild(container);
    }

    // 更新合并进度显示(显示已合并页数 / 总页数)
    function updateMergedProgress() {
        if (!mergedProgressDisplay) return;
        // 将 completedPages 中的所有索引统一转为字符串去重
        const mergedIndexes = new Set();
        completedPages.forEach((index) => mergedIndexes.add(String(index)));
        const totalPages = document.querySelectorAll(".page-img-box").length;
        mergedProgressDisplay.textContent = `合并进度:已合并 ${mergedIndexes.size} / ${totalPages} 页`;
    }

    // 获取当前视口中最“可见”的页面索引
    function getCurrentVisiblePage() {
        const pageElements = document.querySelectorAll(".page-img-box");
        if (!pageElements || pageElements.length === 0) return null;
        const windowHeight = window.innerHeight;
        const scrollTop = window.scrollY || document.documentElement.scrollTop;
        let bestVisiblePage = null,
            bestVisibility = 0;
        pageElements.forEach((page) => {
            const rect = page.getBoundingClientRect();
            const pageTop = rect.top + scrollTop;
            const pageBottom = rect.bottom + scrollTop;
            const visibleTop = Math.max(pageTop, scrollTop);
            const visibleBottom = Math.min(
                pageBottom,
                scrollTop + windowHeight
            );
            const visibleHeight = Math.max(0, visibleBottom - visibleTop);
            if (visibleHeight > bestVisibility) {
                bestVisibility = visibleHeight;
                bestVisiblePage = parseInt(page.getAttribute("index"));
            }
        });
        return bestVisiblePage;
    }

    // 修改后的跳转函数:滚动后立即尝试合并,500ms后检查视口位置,如未到位则重试滚动
    function jumpToPage(pageIndex, isRetry = false) {
        const pageBox = document.querySelector(
            `.page-img-box[index="${pageIndex}"]`
        );
        if (!pageBox) {
            console.log(`找不到第${pageIndex}页元素`);
            updateStatusDisplay(`找不到第${pageIndex}页元素`);
            return;
        }
        pageBox.scrollIntoView({ behavior: "smooth", block: "end" });
        console.log(`正在跳转第${pageIndex}页${isRetry ? "(重试)" : ""}`);
        updateStatusDisplay(`正在跳转第${pageIndex}页...`);

        // 立即检查:如果该页已标记为待合并,则立刻开始合并
        if (pendingPages.has(pageIndex.toString()) && isRunning) {
            console.log(`当前活动页面${pageIndex}切片已加载,立即开始合并...`);
            mergeAndSavePage(getBookId(), pageIndex.toString());
        }

        // 500ms后检查当前视口是否正确,如有偏差则重试滚动,并强制调用页面完成检测
        if (jumpTimeout) clearTimeout(jumpTimeout);
        jumpTimeout = setTimeout(() => {
            jumpTimeout = null;
            const currentPage = getCurrentVisiblePage();
            console.log(`跳转后检测: 目标=${pageIndex}, 当前=${currentPage}`);
            if (
                currentPage !== null &&
                Math.abs(currentPage - pageIndex) > 2 &&
                !isRetry
            ) {
                console.log(`跳转偏差过大,再次尝试跳转到第${pageIndex}页`);
                jumpToPage(pageIndex, true);
            } else {
                updateStatusDisplay(`正在转到第${pageIndex}页...`);
                activePage = pageIndex;
                if (pageSlices[pageIndex]) {
                    updateProgressBar(pageIndex, pageSlices[pageIndex]);
                    updateCurrentPageInfo(
                        `当前页面:<b>第${pageIndex}页</b> (加载切片 ${pageSlices[pageIndex].size} 个)`
                    );
                } else {
                    updateProgressBar(pageIndex, null);
                    updateCurrentPageInfo(
                        `当前页面:<b>第${pageIndex}页</b> (尚未加载切片)`
                    );
                }
                // 强制再次检查页面是否已完成加载
                checkPageCompletion(getBookId(), pageIndex);
            }
        }, 500);
    }

    // 处理并记录单个切片图片(同一 left 值如果重复,则累加 count)
    function processSliceImage(imgElement, bookId, pageIndex, leftValue) {
        if (!isRunning || parseInt(pageIndex) < startPage) return;
        if (!pageSlices[pageIndex]) {
            pageSlices[pageIndex] = new Map();
            processingPages.add(pageIndex);
        }
        if (pageSlices[pageIndex].has(leftValue)) {
            // 重复加载,累加计数
            let entry = pageSlices[pageIndex].get(leftValue);
            entry.count++;
            pageSlices[pageIndex].set(leftValue, entry);
        } else {
            pageSlices[pageIndex].set(leftValue, { img: imgElement, count: 1 });
        }
        if (activePage == pageIndex) {
            updateProgressBar(pageIndex, pageSlices[pageIndex]);
            updateCurrentPageInfo(
                `当前页面:<b>第${pageIndex}页</b> (加载切片 ${pageSlices[pageIndex].size} 个)`
            );
        }
        if (
            parseInt(pageIndex) < currentMinPage &&
            !completedPages.has(pageIndex)
        ) {
            currentMinPage = parseInt(pageIndex);
            jumpToPage(currentMinPage);
        }
        checkPageCompletion(bookId, pageIndex);
    }

    // 检查页面切片是否全部加载完成
    function checkPageCompletion(bookId, pageIndex) {
        const pageBox = document.querySelector(
            `.page-img-box[index="${pageIndex}"]`
        );
        if (!pageBox) return;
        const plgContainer = pageBox.querySelector(".plg");
        if (!plgContainer) return;
        const totalSlices = plgContainer.querySelectorAll("img").length;
        const currentSlices = pageSlices[pageIndex]
            ? pageSlices[pageIndex].size
            : 0;
        if (
            totalSlices > 0 &&
            currentSlices >= totalSlices &&
            !completedPages.has(pageIndex) &&
            !pendingPages.has(pageIndex)
        ) {
            console.log(`第${pageIndex}页的所有切片已加载,标记为待合并`);
            pendingPages.add(pageIndex);
            if (activePage == pageIndex && isRunning) {
                console.log(`当前活动页面${pageIndex}切片已加载,开始合并...`);
                mergeAndSavePage(bookId, pageIndex);
            }
        }
    }

    // 合并切片并保存为完整页面(保存文件名不带 _complete)——优化点:复用全局画布,优先使用 OffscreenCanvas
    function mergeAndSavePage(bookId, pageIndex) {
        if (
            !pageSlices[pageIndex] ||
            pageSlices[pageIndex].size === 0 ||
            completedPages.has(pageIndex) ||
            !isRunning
        )
            return;
        pendingPages.delete(pageIndex);
        updateStatusDisplay(`正在合并第${pageIndex}页...`);
        try {
            const sortedSlices = Array.from(
                pageSlices[pageIndex].entries()
            ).sort((a, b) => parseFloat(a[0]) - parseFloat(b[0]));
            let totalWidth = 0,
                maxHeight = 0;
            sortedSlices.forEach(([left, entry]) => {
                totalWidth += entry.img.naturalWidth;
                maxHeight = Math.max(maxHeight, entry.img.naturalHeight);
            });
            // 初始化或复用全局画布
            if (!mergeCanvas) {
                if (typeof OffscreenCanvas !== "undefined") {
                    mergeCanvas = new OffscreenCanvas(totalWidth, maxHeight);
                } else {
                    mergeCanvas = document.createElement("canvas");
                }
            }
            mergeCanvas.width = totalWidth;
            mergeCanvas.height = maxHeight;
            const ctx = mergeCanvas.getContext("2d");
            let currentX = 0;
            sortedSlices.forEach(([left, entry]) => {
                ctx.drawImage(entry.img, currentX, 0);
                currentX += entry.img.naturalWidth;
            });
            // 文件名格式:{bookid}_page{pageIndex}.webp
            const filename = `${bookId}_page${pageIndex}.webp`;
            // 如果使用 OffscreenCanvas 优先使用 convertToBlob
            if (
                mergeCanvas instanceof OffscreenCanvas &&
                mergeCanvas.convertToBlob
            ) {
                mergeCanvas
                    .convertToBlob({ type: "image/webp", quality: 0.95 })
                    .then((blob) => {
                        saveBlob(blob, filename, pageIndex);
                    });
            } else {
                mergeCanvas.toBlob(
                    function (blob) {
                        saveBlob(blob, filename, pageIndex);
                    },
                    "image/webp",
                    0.95
                );
            }
        } catch (error) {
            console.error(`合并第${pageIndex}页失败:`, error);
            updateStatusDisplay(`合并第${pageIndex}页时出错:${error.message}`);
        }
    }
    // 将生成的 Blob 保存为下载文件
    function saveBlob(blob, filename, pageIndex) {
        if (!saveBlob.savedFiles) {
            saveBlob.savedFiles = new Set();
        }
        if (saveBlob.savedFiles.has(filename)) {
            console.log(`文件 ${filename} 已经保存,跳过重复保存`);
            setTimeout(() => {
                completedPages.add(pageIndex);
                processingPages.delete(pageIndex);
                showNotice(`✓ 第${pageIndex}页已保存为 ${filename}`);
                updateStatusDisplay(`合并完成,继续处理...`);
                updateMergedProgress();
                console.log("查找下一个未合并页面...");
                findAndJumpToNextPage();
            }, 100);
            return;
        }
        saveBlob.savedFiles.add(filename);
        const link = document.createElement("a");
        link.href = URL.createObjectURL(blob);
        link.download = filename;
        link.style.display = "none";
        document.body.appendChild(link);
        link.click();
        setTimeout(() => {
            URL.revokeObjectURL(link.href);
            document.body.removeChild(link);
            console.log(`已保存合并页面:${filename}`);
            completedPages.add(pageIndex);
            processingPages.delete(pageIndex);
            showNotice(`✓ 第${pageIndex}页已保存为 ${filename}`);
            updateStatusDisplay(`合并完成,继续处理...`);
            updateMergedProgress();
            console.log("查找下一个未合并页面...");
            findAndJumpToNextPage();
        }, 100);
    }

    // 查找并跳转到下一个未合并页面
    function findAndJumpToNextPage() {
        if (!isRunning) return;
        console.log("查找下一个未合并页面...");
        const allPages = document.querySelectorAll(".page-img-box");
        const allPageIndices = [];
        allPages.forEach((page) => {
            const idx = parseInt(page.getAttribute("index"));
            if (idx >= startPage) allPageIndices.push(idx);
        });
        allPageIndices.sort((a, b) => a - b);
        let nextPage = null;
        for (let i = 0; i < allPageIndices.length; i++) {
            const pageIdx = allPageIndices[i].toString();
            if (
                !completedPages.has(pageIdx) &&
                parseInt(pageIdx) >= startPage
            ) {
                nextPage = parseInt(pageIdx);
                break;
            }
        }
        if (nextPage !== null) {
            currentMinPage = nextPage;
            console.log(`跳转到下一未合并页面:${nextPage}`);
            jumpToPage(nextPage);
        } else {
            updateStatusDisplay(`所有页面处理完成!`);
            showNotice(`✓ 所有页面处理完成!`, 2000);
            const cancelButton = document.getElementById("cancelButton");
            const startButton = document.getElementById("startButton");
            if (cancelButton) cancelButton.style.display = "none";
            if (startButton) {
                startButton.disabled = false;
                startButton.textContent = "重新开始";
                startButton.style.backgroundColor = "#4CAF50";
                startButton.style.display = "block";
            }
            isRunning = false;
        }
    }

    // 处理页面中已有的图片
    function processExistingImages() {
        if (!isRunning) return;
        const bookId = getBookId();
        console.log(`检测到书籍ID:${bookId}`);
        document.querySelectorAll(".page-img-box").forEach((pageBox) => {
            const pageIndex = pageBox.getAttribute("index");
            if (parseInt(pageIndex) < startPage) return;
            const plgContainer = pageBox.querySelector(".plg");
            if (!plgContainer) return;
            const sliceImages = plgContainer.querySelectorAll("img");
            sliceImages.forEach((img) => {
                if (img.complete && img.naturalHeight !== 0) {
                    const leftValue = parseFloat(img.style.left) || 0;
                    processSliceImage(img, bookId, pageIndex, leftValue);
                } else {
                    img.addEventListener("load", function () {
                        if (!isRunning) return;
                        const leftValue = parseFloat(img.style.left) || 0;
                        processSliceImage(img, bookId, pageIndex, leftValue);
                    });
                }
            });
        });
    }

    // 设置DOM观察器监控新添加的图片
    function setupObserver() {
        if (observer) {
            observer.disconnect();
        }
        const bookId = getBookId();
        observer = new MutationObserver((mutations) => {
            if (!isRunning) return;
            mutations.forEach((mutation) => {
                if (mutation.addedNodes.length) {
                    mutation.addedNodes.forEach((node) => {
                        if (
                            node.nodeName === "IMG" &&
                            node.parentElement &&
                            node.parentElement.classList.contains("plg")
                        ) {
                            const pageBox = node.closest(".page-img-box");
                            if (pageBox) {
                                const pageIndex = pageBox.getAttribute("index");
                                if (parseInt(pageIndex) < startPage) return;
                                if (node.complete && node.naturalHeight !== 0) {
                                    const leftValue =
                                        parseFloat(node.style.left) || 0;
                                    processSliceImage(
                                        node,
                                        bookId,
                                        pageIndex,
                                        leftValue
                                    );
                                } else {
                                    node.addEventListener("load", function () {
                                        if (!isRunning) return;
                                        const leftValue =
                                            parseFloat(node.style.left) || 0;
                                        processSliceImage(
                                            node,
                                            bookId,
                                            pageIndex,
                                            leftValue
                                        );
                                    });
                                }
                            }
                        }
                    });
                }
            });
        });
        const config = {
            childList: true,
            subtree: true,
            attributes: true,
            attributeFilter: ["src", "style"],
        };
        observer.observe(document.body, config);
    }

    // 停止所有处理
    function stopProcessing() {
        isRunning = false;
        if (observer) {
            observer.disconnect();
            observer = null;
        }
        updateStatusDisplay("已停止处理");
        showNotice("已取消处理", 1000);
        const startButton = document.getElementById("startButton");
        const cancelButton = document.getElementById("cancelButton");
        if (startButton) {
            startButton.disabled = false;
            startButton.textContent = "重新开始";
            startButton.style.backgroundColor = "#4CAF50";
            startButton.style.display = "block";
        }
        if (cancelButton) {
            cancelButton.style.display = "none";
        }
        if (reloadInterval) {
            clearInterval(reloadInterval);
            reloadInterval = null;
        }
    }

    // 添加增强的交互界面(包括进度显示、按钮、以及自动点击重载按钮)
    function addEnhancedUI() {
        if (panelCreated) return;
        const style = document.createElement("style");
        style.textContent = `
            #wqSlicerPanel {
                position: fixed;
                top: 100px;
                right: 10px;
                background-color: rgba(255,255,255,0.97);
                color: #333;
                padding: 12px;
                border-radius: 8px;
                z-index: 9999;
                width: 300px;
                font-family: Arial, sans-serif;
                box-shadow: 0 0 15px rgba(0,0,0,0.3);
                transition: all 0.3s ease;
            }
            #wqSlicerPanel .panel-header {
                font-weight: bold;
                margin-bottom: 12px;
                font-size: 15px;
                border-bottom: 1px solid #ddd;
                padding-bottom: 8px;
                display: flex;
                justify-content: space-between;
                align-items: center;
            }
            #wqSlicerPanel .panel-section {
                margin-bottom: 12px;
                padding-bottom: 8px;
                border-bottom: 1px solid #eee;
            }
            #wqSlicerPanel .buttons-container {
                display: flex;
                justify-content: space-between;
                margin-bottom: 10px;
            }
            #wqSlicerPanel button {
                padding: 6px 12px;
                border: none;
                border-radius: 4px;
                cursor: pointer;
                font-weight: bold;
                transition: all 0.2s;
            }
            #wqSlicerPanel button:hover { opacity: 0.9; }
            #wqSlicerPanel button:active { transform: scale(0.98); }
            #startButton { background: #4CAF50; color: white; flex-grow: 1; }
            #cancelButton { background: #f44336; color: white; flex-grow: 1; display: none; }
            #currentPageInfo { font-size: 13px; margin-bottom: 10px; color: #333; }
            #progressDisplay { margin: 10px 0; }
            .progress-container {
                display: flex;
                justify-content: space-between;
                height: 12px;
                margin: 5px 0;
                background: #f0f0f0;
                border-radius: 6px;
                overflow: hidden;
            }
            .progress-item {
                flex-grow: 1;
                height: 100%;
                background: #f0f0f0;
                margin: 0 1px;
                transition: all 0.3s ease;
            }
            .progress-item.loaded-light { background: #a8d5a2; }
            .progress-item.loaded-dark { background: #4CAF50; }
            #statusDisplay, #mergedProgressDisplay, #completionNotice { font-size: 13px; color: #555; min-height: 20px; }
            #mergedProgressDisplay { margin-top: 5px; }
            #completionNotice { color: #4CAF50; margin-top: 8px; font-weight: bold; opacity: 0; transition: opacity 0.5s ease; }
        `;
        document.head.appendChild(style);

        const oldPanel = document.getElementById("wqSlicerPanel");
        if (oldPanel) oldPanel.remove();

        const panel = document.createElement("div");
        panel.id = "wqSlicerPanel";
        panel.innerHTML = `
            <div class="panel-header">
                <span>文泉收割机</span>
            </div>
            <div class="panel-section">
                <div class="buttons-container">
                    <button id="startButton">开始处理</button>
                    <button id="cancelButton">取消处理</button>
                </div>
            </div>
            <div class="panel-section">
                <div id="currentPageInfo">当前页面:等待开始...</div>
                <div id="progressDisplay"></div>
            </div>
            <div class="panel-section">
                <div id="mergedProgressDisplay">合并进度:0 页</div>
                <div id="statusDisplay">点击“开始处理”启动工具</div>
                <div id="completionNotice"></div>
            </div>
        `;
        document.body.appendChild(panel);
        panelCreated = true;

        mainPanel = panel;
        statusDisplay = document.getElementById("statusDisplay");
        progressDisplay = document.getElementById("progressDisplay");
        currentPageInfo = document.getElementById("currentPageInfo");
        mergedProgressDisplay = document.getElementById(
            "mergedProgressDisplay"
        );
        completionNotice = document.getElementById("completionNotice");

        const startButton = document.getElementById("startButton");
        startButton.addEventListener("click", function () {
            // 首先尝试点击目录区域的 <small> 标签展开全部目录
            const expandSmall = document.querySelector(
                ".catalogue.left-scroll small"
            );
            if (expandSmall) {
                expandSmall.click();
                console.log("点击展开全部目录");
            }
            // 等待100ms后保存目录
            setTimeout(() => {
                saveTOC();
                showNotice("✓ 目录已保存");
                // 再等待100ms后继续原有流程
                setTimeout(() => {
                    if (!isInitialized || !isRunning) {
                        startButton.disabled = true;
                        startButton.textContent = "处理中...";
                        startButton.style.backgroundColor = "#888";
                        startButton.style.display = "none";
                        const cancelButton =
                            document.getElementById("cancelButton");
                        if (cancelButton) cancelButton.style.display = "block";
                        if (isInitialized) {
                            isRunning = true;
                            initScript(false);
                        } else {
                            isRunning = true;
                            initScript(true);
                        }
                        // 启动自动点击重载按钮的检测,每秒执行一次
                        if (!reloadInterval) {
                            reloadInterval = setInterval(
                                checkReloadButton,
                                1000
                            );
                        }
                    }
                }, 100);
            }, 100);
        });

        document
            .getElementById("cancelButton")
            .addEventListener("click", function () {
                stopProcessing();
            });

        updateProgressBar(null, null);
        updateMergedProgress();
    }

    // 初始化脚本,询问起始页面
    function initScript(isFirstTime = true) {
        if (isFirstTime) {
            currentMinPage = Infinity;
            pendingPages.clear();
            processingPages.clear();
            activePage = null;
            const userStartPage = prompt("请输入要开始处理的页码:");
            // 如果用户取消或未输入页码,则取消操作并恢复开始按钮状态
            if (!userStartPage) {
                stopProcessing();
                const startButton = document.getElementById("startButton");
                if (startButton) {
                    startButton.disabled = false;
                    startButton.textContent = "开始处理";
                    startButton.style.backgroundColor = "#4CAF50";
                    startButton.style.display = "block";
                }
                updateStatusDisplay("操作已取消");
                return;
            }
            // 如果输入的内容不是有效数字,则同样取消操作
            if (isNaN(parseInt(userStartPage))) {
                stopProcessing();
                const startButton = document.getElementById("startButton");
                if (startButton) {
                    startButton.disabled = false;
                    startButton.textContent = "开始处理";
                    startButton.style.backgroundColor = "#4CAF50";
                    startButton.style.display = "block";
                }
                updateStatusDisplay("无效的页码,操作已取消");
                return;
            }
            startPage = parseInt(userStartPage);
            currentMinPage = startPage;
            jumpToPage(currentMinPage);
            console.log(`开始处理,起始页为:${startPage}`);
            updateStatusDisplay(`开始处理,起始页:第${startPage}页`);
        } else {
            findAndJumpToNextPage();
        }
        processExistingImages();
        setupObserver();
        isInitialized = true;
    }

    // 页面加载完成后执行
    window.addEventListener("load", function () {
        console.log("页面已加载,添加交互界面");
        addEnhancedUI();
    });

    // 尝试立即添加交互界面
    setTimeout(addEnhancedUI, 500);
})();

QingJ © 2025

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