网页漫画下载为pdf格式

将网页漫画下载为pdf方便阅读,目前仅适用于如漫画[http://www.rumanhua1.com/]

当前为 2025-05-04 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         网页漫画下载为pdf格式
// @namespace    http://tampermonkey.net/
// @version      2.2.1
// @description  将网页漫画下载为pdf方便阅读,目前仅适用于如漫画[http://www.rumanhua1.com/]
// @author       MornLight
// @match        http://m.rumanhua1.com/*
// @match        http://www.rumanhua1.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=greasyfork.org
// @grant        GM_xmlhttpRequest
// @grant        GM_openInTab
// @grant        GM_setValue
// @grant        GM_getValue
// @connect      *
// @require      https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js
// @run-at       document-end
// @license      MIT
// @supportURL   https://github.com/duanmorningsir/ComicDownloader
// ==/UserScript==


(function () {
    'use strict';

    // 1. 样式配置
    const STYLES = {
        container: {
            position: 'fixed',
            bottom: '20px',
            right: '20px',
            zIndex: '9999',
            display: 'flex',
            flexDirection: 'column',
            gap: '16px',
            backgroundColor: '#f8fafc',
            padding: '20px',
            borderRadius: '18px',
            boxShadow: '0 4px 24px 0 rgba(60,60,100,0.13)',
            maxHeight: '80vh',
            overflowY: 'auto',
            minWidth: '260px',
            border: '1px solid #e0e6ef',
            boxSizing: 'border-box',
            '@media (max-width: 768px)': {
                bottom: '8px',
                right: '8px',
                left: '8px',
                minWidth: 'auto',
                width: 'calc(100% - 16px)',
                padding: '8px',
                gap: '6px',
                borderRadius: '10px',
            }
        },
        button: {
            padding: '12px 0',
            color: '#fff',
            border: 'none',
            borderRadius: '24px',
            cursor: 'pointer',
            backgroundColor: '#4CAF50',
            boxShadow: '0 2px 8px 0 rgba(76,175,80,0.08)',
            fontWeight: 'bold',
            fontSize: '17px',
            letterSpacing: '1px',
            transition: 'background 0.3s, box-shadow 0.2s, transform 0.1s',
            margin: '2px 0',
            outline: 'none',
            ':hover': {
                backgroundColor: '#43e97b',
                boxShadow: '0 4px 16px 0 rgba(67,233,123,0.18)',
                transform: 'translateY(-2px) scale(1.03)'
            },
            '@media (max-width: 768px)': {
                padding: '5px 0',
                fontSize: '12px',
                width: '100%',
                borderRadius: '8px',
                margin: '2px 0',
            }
        },
        cancelButton: {
            backgroundColor: '#f44336',
            display: 'none',
            fontWeight: 'bold',
            fontSize: '17px',
            borderRadius: '24px',
            boxShadow: '0 2px 8px 0 rgba(244,67,54,0.08)',
            transition: 'background 0.3s, box-shadow 0.2s, transform 0.1s',
            ':hover': {
                backgroundColor: '#ff7e5f',
                boxShadow: '0 4px 16px 0 rgba(255,126,95,0.18)',
                transform: 'translateY(-2px) scale(1.03)'
            },
            '@media (max-width: 768px)': {
                padding: '5px 0',
                fontSize: '12px',
                width: '100%',
                borderRadius: '8px',
                margin: '2px 0',
            }
        },
        progressContainer: {
            display: 'none',
            background: 'rgba(245,247,250,0.85)',
            borderRadius: '10px',
            padding: '8px 0 2px 0',
            margin: '4px 0',
            boxShadow: '0 1px 4px 0 rgba(60,60,100,0.06)',
            '@media (max-width: 768px)': {
                width: '100%',
                padding: '4px 0 1px 0',
                borderRadius: '7px',
            }
        },
        infoText: {
            color: '#4a5568',
            fontSize: '15px',
            textAlign: 'center',
            marginBottom: '10px',
            fontWeight: '500',
            letterSpacing: '0.5px',
            '@media (max-width: 768px)': {
                fontSize: '12px',
                marginBottom: '5px',
            }
        },
        chapterListContainer: {
            marginTop: '10px',
            display: 'none',
            maxHeight: '50vh',
            overflowY: 'auto',
            paddingRight: '18px',
            boxSizing: 'border-box',
            '@media (max-width: 768px)': {
                maxHeight: '60vh',
                paddingRight: '0',
            }
        }
    };

    // 添加响应式样式应用函数
    function applyResponsiveStyles(element, styles) {
        Object.assign(element.style, styles);

        // 检查是否是移动设备
        if (window.innerWidth <= 768) {
            const mobileStyles = styles['@media (max-width: 768px)'];
            if (mobileStyles) {
                Object.assign(element.style, mobileStyles);
            }
        }
    }

    // 2. 站点适配器相关代码
    class SiteAdapter {
        isChapterPage() {
            throw new Error('必须实现 isChapterPage 方法');
        }

        isDirectoryPage() {
            throw new Error('必须实现 isDirectoryPage 方法');
        }
        getChapterLinks() {
            throw new Error('必须实现 getChapterLinks 方法');
        }
        getChapterName() { throw new Error('必须实现 getChapterName 方法'); }
        getImageElements() { throw new Error('必须实现 getImageElements 方法'); }
        getImageUrl(imgElement) { throw new Error('必须实现 getImageUrl 方法'); }
    }

    class RumanhuaAdapter extends SiteAdapter {
        isChapterPage() {
            const url = window.location.href;
            const chapterPagePattern = /http:\/\/m\.rumanhua1\.com\/[^\/]+\/[^\/]+\.html/;
            return chapterPagePattern.test(url);
        }

        isDirectoryPage() {
            const url = window.location.href;
            const directoryPagePattern = /http:\/\/m\.rumanhua1\.com\/[^\/]+\/?$/;
            return directoryPagePattern.test(url);
        }

        async getChapterLinks() {
            const waitForChapterList = () => {
                return new Promise((resolve, reject) => {
                    let attempts = 0;
                    const maxAttempts = 10;

                    const checkForList = () => {
                        const selectors = [
                            '.cartoon-box .chaplist-box ul',
                            '.chaplist-box ul',
                            '.chapterlistload ul',
                            '.chapter-list ul',
                            '.chapterlist ul'
                        ];

                        for (const selector of selectors) {
                            const element = document.querySelector(selector);
                            if (element) {
                                resolve(element);
                                return;
                            }
                        }

                        attempts++;
                        if (attempts >= maxAttempts) {
                            reject(new Error('未找到章节列表'));
                            return;
                        }

                        setTimeout(checkForList, 500);
                    };

                    checkForList();
                });
            };

            try {
                const chapterListElement = await waitForChapterList();
                const chapterElements = chapterListElement.querySelectorAll('a');
                const baseUrl = window.location.origin;
                const links = Array.from(chapterElements).map(element => {
                    const href = element.getAttribute('href');
                    const url = href.startsWith('http') ? href : baseUrl + href;
                    const name = element.textContent.trim();
                    return { url, name };
                });
                return links;
            } catch (error) {
                throw error;
            }
        }

        getChapterName() {
            const chapterNameElement = document.querySelector('.chaphead-name h1');
            return chapterNameElement ? chapterNameElement.textContent.trim() : '未知章节';
        }

        getImageElements() {
            return document.querySelectorAll('.chapter-img-box img');
        }

        getImageUrl(imgElement) {
            if (!imgElement) return null;

            const src = imgElement.src || imgElement.dataset.src;
            if (!src) return null;

            let imageUrl = src.includes('/static/images/load.gif') ? imgElement.dataset.src : src;

            if (imageUrl.startsWith('blob:')) {
                return imageUrl;
            }

            if (imageUrl.startsWith('http:')) {
                imageUrl = imageUrl.replace('http:', 'https:');
            }

            return imageUrl;
        }
    }

    class RumanhuaPCAdapter extends SiteAdapter {
        isChapterPage() {
            const url = window.location.href;
            const chapterPagePattern = /http:\/\/www\.rumanhua1\.com\/[^\/]+\/[^\/]+\.html/;
            return chapterPagePattern.test(url);
        }

        isDirectoryPage() {
            const url = window.location.href;
            const directoryPagePattern = /http:\/\/www\.rumanhua1\.com\/[^\/]+\/?$/;
            return directoryPagePattern.test(url);
        }

        async getChapterLinks() {
            // 参考手机版,支持多种选择器
            const waitForChapterList = () => {
                return new Promise((resolve, reject) => {
                    let attempts = 0;
                    const maxAttempts = 10;

                    const checkForList = () => {
                        const selectors = [
                            '.chapterlistload ul',
                            '.cartoon-box .chaplist-box ul',
                            '.chaplist-box ul',
                            '.chapter-list ul',
                            '.chapterlist ul'
                        ];
                        for (const selector of selectors) {
                            const element = document.querySelector(selector);
                            if (element) {
                                resolve(element);
                                return;
                            }
                        }
                        attempts++;
                        if (attempts >= maxAttempts) {
                            reject(new Error('未找到章节列表'));
                            return;
                        }
                        setTimeout(checkForList, 500);
                    };
                    checkForList();
                });
            };

            try {
                const chapterListElement = await waitForChapterList();
                const chapterElements = chapterListElement.querySelectorAll('a');
                const baseUrl = window.location.origin;
                const links = Array.from(chapterElements).map(element => {
                    const href = element.getAttribute('href');
                    const url = href.startsWith('http') ? href : baseUrl + href;
                    const name = element.textContent.trim();
                    return { url, name };
                });
                return links;
            } catch (error) {
                throw error;
            }
        }

        getChapterName() {
            const chapterName = document.querySelector('.headwrap .chaptername_title')?.textContent || '未知章节';
            return chapterName;
        }

        getImageElements() {
            return document.querySelectorAll('div.chapter-img-box img');
        }

        getImageUrl(imgElement) {
            if (!imgElement) return null;

            const src = imgElement.src || imgElement.dataset.src;
            if (!src) return null;

            let imageUrl = src.includes('/static/images/load.gif') ? imgElement.dataset.src : src;

            if (imageUrl.startsWith('blob:')) {
                return imageUrl;
            }

            if (imageUrl.startsWith('http:')) {
                imageUrl = imageUrl.replace('http:', 'https:');
            }

            return imageUrl;
        }
    }

    // 3. 获取适配器的工厂函数
    function getSiteAdapter() {
        const url = window.location.href;
        switch (true) {
            case url.includes('http://www.rumanhua1.com/'):
                return new RumanhuaPCAdapter();
            case url.includes('http://m.rumanhua1.com/'):
                return new RumanhuaAdapter();
            default:
                throw new Error('不支持的页面格式');
        }
    }

    // 4. UI类
    // 4.1 普通下载器UI
    class DownloaderUI {
        constructor(totalPages, onDownload, onCancel) {
            this.totalPages = totalPages;
            this.onDownload = onDownload;
            this.onCancel = onCancel;
            this.currentPage = 0;
            this.createUI();
        }

        createUI() {
            const container = this.createContainer();

            // 添加页数信息
            this.infoText = this.createElement('div', STYLES.infoText, `本章节共 ${this.totalPages} 页`);
            container.appendChild(this.infoText);

            // 添加长图模式切换按钮
            this.longPageModeButton = this.createElement('button', {
                ...STYLES.button,
                backgroundColor: '#2196F3',
                marginBottom: '10px'
            }, '切换长图模式');
            this.longPageModeButton.addEventListener('click', () => this.toggleLongPageMode());
            container.appendChild(this.longPageModeButton);

            this.downloadButton = this.createButton('下载本章节', () => this.onDownload(1, this.totalPages));
            this.cancelButton = this.createButton('取消下载', () => {
                this.onCancel();
                this.infoText.textContent = '下载已取消';
                setTimeout(() => {
                    this.infoText.textContent = `本章节共 ${this.totalPages} 页`;
                }, 2000);
            }, true);

            // 创建进度容器
            this.progressContainer = this.createElement('div', STYLES.progressContainer);
            this.progressBar = this.createProgressBar();
            this.progressText = this.createElement('span', {
                marginLeft: '10px',
                '@media (max-width: 768px)': {
                    fontSize: '12px',
                    marginLeft: '5px'
                }
            });
            this.progressContainer.appendChild(this.progressBar);
            this.progressContainer.appendChild(this.progressText);

            container.appendChild(this.downloadButton);
            container.appendChild(this.cancelButton);
            container.appendChild(this.progressContainer);
            document.body.appendChild(container);

            // 添加窗口大小变化监听
            window.addEventListener('resize', () => {
                this.updateResponsiveStyles();
            });
        }

        setLoading(isLoading, showCancel = false) {
            this.downloadButton.disabled = isLoading;
            this.downloadButton.style.backgroundColor = isLoading ? '#999' : '#4CAF50';
            this.downloadButton.style.cursor = isLoading ? 'not-allowed' : 'pointer';
            this.downloadButton.textContent = isLoading ? '下载中...' : '下载本章节';
            this.downloadButton.style.display = isLoading ? 'none' : 'block'; // 修改:控制下载按钮显示
            this.cancelButton.style.display = showCancel ? 'block' : 'none';
            this.progressContainer.style.display = isLoading ? 'block' : 'none';
            this.infoText.style.display = isLoading ? 'none' : 'block';
        }


        updateProgress(currentPage) {
            this.currentPage = currentPage;
            this.progressBar.value = currentPage;
            const percent = ((currentPage / this.totalPages) * 100).toFixed(2);
            this.progressText.textContent = `${currentPage}/${this.totalPages} (${percent}%)`;
        }

        createButton(text, onClick, isCancel = false) {
            const button = document.createElement('button');
            Object.assign(button.style, STYLES.button); // 应用基础按钮样式
            if (isCancel) {
                Object.assign(button.style, STYLES.cancelButton); // 如果是取消按钮,额外应用取消按钮样式
            }
            button.textContent = text;
            button.addEventListener('click', onClick);
            return button;
        }

        createContainer() {
            const container = document.createElement('div');
            Object.assign(container.style, STYLES.container);
            return container;
        }

        createProgressBar() {
            const progressBar = document.createElement('progress');
            progressBar.max = this.totalPages;
            progressBar.value = 0;
            progressBar.style.width = '100%';
            return progressBar;
        }

        toggleLongPageMode() {
            this.isLongPageMode = !this.isLongPageMode;
            this.longPageModeButton.textContent = this.isLongPageMode ? '切换普通模式' : '切换长图模式';
            this.longPageModeButton.style.backgroundColor = this.isLongPageMode ? '#4CAF50' : '#2196F3';
        }

        // 添加响应式样式更新方法
        updateResponsiveStyles() {
            const elements = {
                container: this.container,
                infoText: this.infoText,
                longPageModeButton: this.longPageModeButton,
                downloadButton: this.downloadButton,
                cancelButton: this.cancelButton,
                progressContainer: this.progressContainer,
                progressText: this.progressText
            };

            for (const [key, element] of Object.entries(elements)) {
                if (element && STYLES[key]) {
                    applyResponsiveStyles(element, STYLES[key]);
                }
            }
        }

        // 修改 createElement 方法
        createElement(type, styles, textContent = '') {
            const element = document.createElement(type);
            if (type === 'input' && styles.type) {
                element.type = styles.type;
                delete styles.type;
            }
            if (typeof styles === 'string') {
                element.className = styles;
            } else {
                applyResponsiveStyles(element, styles);
            }
            if (textContent) element.textContent = textContent;
            return element;
        }
    }

    // 4.2 章节选择器UI
    class ChapterSelectorUI {
        constructor(onDownloadSelected, adapter) {
            this.onDownloadSelected = onDownloadSelected;
            this.adapter = adapter;  // 添加 adapter 参数
            this.selectedChapters = new Set();
            this.isSelectionMode = false;
            this.isLongPageMode = false;
            this.createUI();
        }

        // 添加 createElement 方法
        createElement(type, styles, textContent = '') {
            const element = document.createElement(type);
            if (type === 'input' && styles.type) {
                element.type = styles.type;
                delete styles.type;
            }
            if (typeof styles === 'string') {
                element.className = styles;
            } else {
                applyResponsiveStyles(element, styles);
            }
            if (textContent) element.textContent = textContent;
            return element;
        }

        createUI() {
            // 创建容器
            this.container = this.createElement('div', {
                ...STYLES.container,
                '@media (min-width: 769px)': {
                    width: '300px',
                    right: '20px',
                    left: 'auto'
                }
            });
            document.body.appendChild(this.container);

            // 创建【选择章节下载】按钮
            this.selectButton = this.createElement('button', {
                ...STYLES.button,
                position: 'sticky',
                top: '0',
                zIndex: '10',
                width: '100%',
                marginBottom: '10px',
                fontWeight: 'bold',
                '@media (max-width: 768px)': {
                    padding: '12px',
                    fontSize: '14px'
                }
            }, '选择章节下载');
            this.selectButton.addEventListener('click', () => this.toggleSelectionMode());
            this.container.appendChild(this.selectButton);

            // 添加长图模式切换按钮
            this.longPageModeButton = this.createElement('button', {
                ...STYLES.button,
                backgroundColor: '#2196F3',
                position: 'sticky',
                top: '40px',
                zIndex: '10',
                width: '100%',
                marginBottom: '10px',
                '@media (max-width: 768px)': {
                    top: '50px',
                    padding: '12px',
                    fontSize: '14px'
                }
            }, '切换长图模式');
            this.longPageModeButton.addEventListener('click', () => this.toggleLongPageMode());
            this.container.appendChild(this.longPageModeButton);

            // 添加取消按钮
            this.cancelSelectionButton = this.createElement('button', {
                ...STYLES.button,
                backgroundColor: '#f44336',
                position: 'sticky',
                top: '80px',
                zIndex: '10',
                width: '100%',
                marginBottom: '10px',
                display: 'none',
                '@media (max-width: 768px)': {
                    top: '90px',
                    padding: '12px',
                    fontSize: '14px'
                }
            }, '返回');
            this.cancelSelectionButton.addEventListener('click', () => this.cancelSelectionMode());
            this.container.appendChild(this.cancelSelectionButton);

            // 创建章节列表容器
            this.chapterListContainer = this.createElement('div', STYLES.chapterListContainer);
            this.container.appendChild(this.chapterListContainer);

            // 添加进度显示区域
            this.progressContainer = this.createElement('div', {
                marginTop: '10px',
                display: 'none',
                position: 'sticky',
                bottom: '0',
                backgroundColor: 'rgba(255, 255, 255, 0.9)',
                padding: '5px 0',
                zIndex: '2',
                '@media (max-width: 768px)': {
                    padding: '8px 0'
                }
            });

            this.progressText = this.createElement('div', {
                marginBottom: '5px',
                fontSize: '14px',
                color: '#666',
                '@media (max-width: 768px)': {
                    fontSize: '12px',
                    marginBottom: '3px'
                }
            });

            this.progressBar = document.createElement('progress');
            this.progressBar.style.width = '100%';

            this.progressContainer.appendChild(this.progressText);
            this.progressContainer.appendChild(this.progressBar);
            this.container.appendChild(this.progressContainer);

            // 添加窗口大小变化监听
            window.addEventListener('resize', () => {
                this.updateResponsiveStyles();
            });
        }

        async initChapterList() {
            this.chapterListContainer.innerHTML = '';
            this.selectedChapters.clear();

            try {
                const chapterLinks = await this.adapter.getChapterLinks();

                if (!chapterLinks || chapterLinks.length === 0) {
                    return;
                }

                // 添加简单的控制区
                const controlsContainer = this.createElement('div', {
                    display: 'flex',
                    justifyContent: 'space-between',
                    marginBottom: '10px',
                    position: 'sticky',
                    top: '0',
                    backgroundColor: 'rgba(255, 255, 255, 0.9)',
                    padding: '5px 0',
                    zIndex: '1'
                });

                // 添加章节数量显示
                const chapterCountLabel = this.createElement('span', {
                    fontSize: '12px',
                    color: '#666',
                    alignSelf: 'center'
                }, `共 ${chapterLinks.length} 章`);

                // 创建按钮容器
                const buttonsContainer = this.createElement('div', {
                    display: 'flex',
                    gap: '5px'
                });

                // 添加刷新按钮
                const refreshBtn = this.createElement('button', {
                    padding: '3px 8px',
                    fontSize: '12px',
                    backgroundColor: '#2196F3',
                    color: 'white',
                    border: 'none',
                    borderRadius: '3px',
                    cursor: 'pointer'
                }, '刷新列表');

                refreshBtn.addEventListener('click', () => this.refreshChapterList());

                // 添加清除选择按钮
                const deselectAllBtn = this.createElement('button', {
                    padding: '3px 8px',
                    fontSize: '12px',
                    backgroundColor: '#f44336',
                    color: 'white',
                    border: 'none',
                    borderRadius: '3px',
                    cursor: 'pointer'
                }, '清除选择');

                deselectAllBtn.addEventListener('click', () => this.deselectAll());

                buttonsContainer.appendChild(refreshBtn);
                buttonsContainer.appendChild(deselectAllBtn);
                controlsContainer.appendChild(chapterCountLabel);
                controlsContainer.appendChild(buttonsContainer);
                this.chapterListContainer.appendChild(controlsContainer);

                // 添加章节列表
                chapterLinks.forEach((chapter, index) => {
                    const chapterItem = this.createElement('div', {
                        display: 'flex',
                        alignItems: 'center',
                        marginBottom: '5px',
                        padding: '5px',
                        borderRadius: '3px',
                        transition: 'background-color 0.2s',
                        ':hover': {
                            backgroundColor: 'rgba(0, 0, 0, 0.05)'
                        }
                    });

                    const chapterNameSpan = this.createElement('span', {
                        flex: 1,
                        marginRight: '10px',
                        overflow: 'hidden',
                        textOverflow: 'ellipsis',
                        whiteSpace: 'nowrap'
                    }, chapter.name);

                    // 复选框
                    const checkbox = this.createElement('input', {
                        type: 'checkbox',
                        marginLeft: '10px',
                        cursor: 'pointer',
                        width: '16px',
                        height: '16px'
                    });

                    checkbox.addEventListener('change', () => this.toggleChapterSelection(index, checkbox));
                    chapterItem.appendChild(chapterNameSpan);
                    chapterItem.appendChild(checkbox);

                    this.chapterListContainer.appendChild(chapterItem);
                });
            } catch (error) {
                console.error('初始化章节列表失败:', error);
            }
        }

        // 添加取消选择模式的方法
        cancelSelectionMode() {
            this.isSelectionMode = false;
            this.chapterListContainer.style.display = 'none';
            this.cancelSelectionButton.style.display = 'none';
            this.selectButton.textContent = '选择章节下载';
            this.selectedChapters.clear();
        }

        // 保留取消全选方法
        deselectAll() {
            const checkboxes = this.chapterListContainer.querySelectorAll('input[type="checkbox"]');
            checkboxes.forEach(checkbox => {
                checkbox.checked = false;
            });
            this.selectedChapters.clear();
            this.selectButton.textContent = '选择章节下载';
        }

        // 移除 selectAll、selectPrevious5Chapters、selectNext5Chapters 方法
        selectAll(count) {
            const checkboxes = this.chapterListContainer.querySelectorAll('input[type="checkbox"]');
            checkboxes.forEach((checkbox, index) => {
                // 跳过第一行的控制按钮
                if (index > 0) {
                    checkbox.checked = true;
                    this.selectedChapters.add(index - 1); // 减1是因为索引从0开始
                }
            });
            this.selectButton.textContent = `下载选中章节 (${count})`;
        }

        // 添加取消全选方法
        // 添加取消全选方法
        deselectAll() {
            const checkboxes = this.chapterListContainer.querySelectorAll('input[type="checkbox"]');
            checkboxes.forEach(checkbox => {
                checkbox.checked = false;
            });
            this.selectedChapters.clear();
            this.selectButton.textContent = '下载选中章节';
        }

        // 添加刷新章节列表的方法
        refreshChapterList() {
            console.log('刷新章节列表');
            // 保存当前选中的章节
            const selectedChapters = new Set(this.selectedChapters);

            // 重新初始化章节列表
            this.initChapterList();

            // 尝试恢复之前选中的章节(如果它们仍然存在)
            const checkboxes = this.chapterListContainer.querySelectorAll('input[type="checkbox"]');
            const maxIndex = checkboxes.length - 1;

            selectedChapters.forEach(index => {
                if (index <= maxIndex) {
                    // 跳过控制区域,所以索引需要+1
                    const checkbox = checkboxes[index + 1];
                    if (checkbox) {
                        checkbox.checked = true;
                        this.selectedChapters.add(index);
                    }
                }
            });

            // 更新按钮文本
            if (this.selectedChapters.size > 0) {
                this.selectButton.textContent = `下载选中章节 (${this.selectedChapters.size})`;
            } else {
                this.selectButton.textContent = '下载选中章节';
            }

            // 显示刷新成功提示
            const chapterCountLabel = this.chapterListContainer.querySelector('span');
            const originalText = chapterCountLabel.textContent;
            chapterCountLabel.textContent = '刷新成功!';
            setTimeout(() => {
                chapterCountLabel.textContent = originalText;
            }, 1500);
        }

        // 修改切换选择方法,添加checkbox参数
        toggleChapterSelection(index, checkbox) {
            if (this.selectedChapters.has(index)) {
                this.selectedChapters.delete(index);
                if (checkbox) checkbox.checked = false;
            } else {
                this.selectedChapters.add(index);
                if (checkbox) checkbox.checked = true;
            }

            // 更新按钮文本
            if (this.selectedChapters.size > 0) {
                this.selectButton.textContent = `下载选中章节 (${this.selectedChapters.size})`;
            } else {
                this.selectButton.textContent = '下载选中章节';
            }
        }

        // 添加 toggleSelectionMode 方法
        toggleSelectionMode() {
            console.log('toggleSelectionMode 被调用');
            this.isSelectionMode = !this.isSelectionMode;

            if (this.isSelectionMode) {
                console.log('进入选择模式,准备初始化章节列表');
                this.initChapterList();
                this.chapterListContainer.style.display = 'block';
                this.cancelSelectionButton.style.display = 'block';
                this.selectButton.textContent = '下载选中章节';
            } else if (this.selectedChapters.size > 0) {
                console.log('退出选择模式,开始下载选中章节');
                this.chapterListContainer.style.display = 'none';
                this.cancelSelectionButton.style.display = 'none';
                this.onDownloadSelected();
            } else {
                console.log('退出选择模式,无选中章节');
                this.chapterListContainer.style.display = 'none';
                this.cancelSelectionButton.style.display = 'none';
                this.selectButton.textContent = '选择章节下载';
            }
        }

        // 添加长图模式切换方法
        toggleLongPageMode() {
            this.isLongPageMode = !this.isLongPageMode;
            this.longPageModeButton.textContent = this.isLongPageMode ? '切换普通模式' : '切换长图模式';
            this.longPageModeButton.style.backgroundColor = this.isLongPageMode ? '#4CAF50' : '#2196F3';
        }

        // 修改 setLoading 方法,在加载时隐藏长图模式按钮
        setLoading(isLoading, totalChapters = 0) {
            this.selectButton.disabled = isLoading;
            this.selectButton.style.backgroundColor = isLoading ? '#999' : '#4CAF50';
            this.selectButton.style.cursor = isLoading ? 'not-allowed' : 'pointer';
            this.longPageModeButton.style.display = isLoading ? 'none' : 'block'; // 添加这行

            if (isLoading) {
                this.selectButton.textContent = '下载中...';
                this.chapterListContainer.style.display = 'none';
                this.progressContainer.style.display = 'block';

                this.progressBar.max = totalChapters;
                this.progressBar.value = 0;
                this.progressText.textContent = `准备下载 ${totalChapters} 个章节...`;
            } else {
                this.selectButton.textContent = '选择章节下载';
                this.progressContainer.style.display = 'none';
            }
        }

        // 添加更新进度的方法
        updateProgress(current, total) {
            if (this.progressBar) {
                this.progressBar.value = current;
                const percent = ((current / total) * 100).toFixed(2);
                this.progressText.textContent = `下载进度: ${current}/${total} (${percent}%)`;
            }
        }

        // 添加响应式样式更新方法
        updateResponsiveStyles() {
            const elements = {
                container: this.container,
                selectButton: this.selectButton,
                longPageModeButton: this.longPageModeButton,
                cancelSelectionButton: this.cancelSelectionButton,
                chapterListContainer: this.chapterListContainer,
                progressContainer: this.progressContainer,
                progressText: this.progressText
            };

            for (const [key, element] of Object.entries(elements)) {
                if (element && STYLES[key]) {
                    applyResponsiveStyles(element, STYLES[key]);
                }
            }
        }

        // 添加获取章节名称的方法
        getChapterNameFromUrl(url) {
            try {
                // 尝试从URL中提取章节名称
                const urlObj = new URL(url);
                const pathParts = urlObj.pathname.split('/');
                const lastPart = pathParts[pathParts.length - 1];

                // 移除.html后缀
                const nameWithoutExt = lastPart.replace('.html', '');

                // 尝试解码URL编码的章节名
                try {
                    return decodeURIComponent(nameWithoutExt);
                } catch {
                    return nameWithoutExt;
                }
            } catch (error) {
                console.error('解析章节名称失败:', error);
                return `第${chapterLinks.length - index}章`;
            }
        }
    }

    // 5. 下载器类
    class ComicDownloader {
        constructor() {
            try {
                this.adapter = getSiteAdapter();
                this.isLongPageMode = GM_getValue('isLongPageMode', false);

                if (this.adapter.isChapterPage()) {
                    const imageElements = this.adapter.getImageElements();
                    this.totalPages = imageElements.length;
                    this.chapterName = this.adapter.getChapterName();

                    if (this.totalPages > 0) {
                        this.ui = new DownloaderUI(this.totalPages, this.handleDownload.bind(this), this.handleCancel.bind(this));
                        if (this.isLongPageMode) {
                            this.ui.isLongPageMode = true;
                            this.ui.longPageModeButton.textContent = '切换普通模式';
                            this.ui.longPageModeButton.style.backgroundColor = '#4CAF50';
                        }
                    }
                } else if (this.adapter.isDirectoryPage()) {
                    this.ui = new ChapterSelectorUI(this.handleDownloadSelected.bind(this), this.adapter);
                }
            } catch (error) {
                console.error('初始化失败:', error);
            }
        }

        // 处理下载选中章节
        // 在 ComicDownloader 类中添加新方法
        async loadChapterHtml(url) {
            return new Promise((resolve, reject) => {
                const tab = GM_openInTab(url, { active: false, insert: true });

                // 创建一个消息监听器
                const messageHandler = function (event) {
                    if (event.data.type === 'chapterData' && event.data.url === url) {
                        window.removeEventListener('message', messageHandler);
                        tab.close();
                        resolve(event.data.html);
                    }
                };

                window.addEventListener('message', messageHandler);

                // 5秒后超时
                setTimeout(() => {
                    window.removeEventListener('message', messageHandler);
                    tab.close();
                    reject(new Error('加载章节超时'));
                }, 5000);
            });
        }

        async handleDownloadSelected() {
            const selectedChapters = this.ui.selectedChapters;
            if (selectedChapters.size === 0) {
                this.ui.selectButton.textContent = '请选择至少一个章节';
                setTimeout(() => {
                    this.ui.selectButton.textContent = '选择章节下载';
                }, 2000);
                return;
            }

            try {
                const chapterLinks = await this.adapter.getChapterLinks();
                const selectedChapterUrls = Array.from(selectedChapters).map(index => chapterLinks[index].url);

                this.ui.setLoading(true, selectedChapterUrls.length);
                this.isLongPageMode = this.ui.isLongPageMode;

                for (let i = 0; i < selectedChapterUrls.length; i++) {
                    const url = selectedChapterUrls[i];
                    try {
                        const sessionId = Date.now().toString();
                        GM_setValue('autoDownload', true);
                        GM_setValue('sessionId', sessionId);
                        GM_setValue('downloadStatus', 'pending');
                        GM_setValue('isLongPageMode', this.isLongPageMode);

                        const tab = GM_openInTab(url, {
                            active: true,
                            insert: true,
                            setParent: true
                        });

                        await new Promise((resolve, reject) => {
                            const maxRetries = 3;
                            let retryCount = 0;
                            let timeout;

                            const checkStatus = () => {
                                const status = GM_getValue('downloadStatus', '');
                                if (status === 'complete') {
                                    clearTimeout(timeout);
                                    GM_setValue('downloadStatus', '');
                                    GM_setValue('autoDownload', false);
                                    resolve();
                                    return true;
                                }
                                return false;
                            };

                            const startCheck = () => {
                                timeout = setTimeout(() => {
                                    if (!checkStatus() && retryCount < maxRetries) {
                                        retryCount++;
                                        tab.activate();
                                        startCheck();
                                    } else if (retryCount >= maxRetries) {
                                        GM_setValue('downloadStatus', '');
                                        reject(new Error('下载超时,已达到最大重试次数'));
                                    }
                                }, 30000);
                            };

                            startCheck();

                            const checkInterval = setInterval(() => {
                                if (checkStatus()) {
                                    clearInterval(checkInterval);
                                }
                            }, 1000);
                        });

                        tab.close();
                        this.ui.updateProgress(i + 1, selectedChapterUrls.length);
                    } catch (error) {
                        console.error(`章节下载失败: ${url}`, error);
                    }
                }

                this.ui.setLoading(false);
                this.ui.selectButton.textContent = '下载完成!';
                setTimeout(() => {
                    this.ui.selectButton.textContent = '选择章节下载';
                }, 3000);
            } catch (error) {
                console.error('批量下载失败:', error);
                this.ui.setLoading(false);
                this.ui.selectButton.textContent = '下载失败,请查看控制台';
                setTimeout(() => {
                    this.ui.selectButton.textContent = '选择章节下载';
                }, 3000);
            }
        }

        // 从文档中提取图片URL
        extractImageUrlsFromDoc(doc) {
            const imageElements = doc.querySelectorAll('div.chapter-img-box img');
            return Array.from(imageElements).map(img => img.src || img.dataset.src);
        }

        // 下载单个章节的所有图片
        async downloadChapterImages(imageUrls) {
            const images = [];
            for (const url of imageUrls) {
                try {
                    const imageData = await this.downloadImage(url);
                    images.push(imageData);
                } catch (error) {
                    console.error(`图片下载失败: ${url}`, error);
                }
            }
            return images;
        }
        async handleDownload() {
            if (this.isDownloading) {
                alert('当前正在下载,请稍后再试');
                return;
            }

            try {
                this.isDownloading = true;
                this.abortController = new AbortController();
                this.ui.setLoading(true, true);
                // 传递长图模式状态
                this.isLongPageMode = this.ui.isLongPageMode;
                await this.downloadComic();
            } catch (error) {
                if (error.name === 'AbortError') {
                    console.log('下载已取消');
                    alert('下载已取消');
                } else {
                    this.handleError(error, '下载失败');
                }
            } finally {
                this.isDownloading = false;
                this.abortController = null;
                this.ui.setLoading(false, false);
            }
        }

        handleCancel() {
            if (this.abortController) {
                this.abortController.abort(); // 中断下载
                this.isDownloading = false; // 重置下载状态
                this.ui.setLoading(false, false); // 重置UI状态
                // 显示取消消息,然后恢复下载按钮
                setTimeout(() => {
                    this.ui.downloadButton.style.display = 'block';
                    this.ui.downloadButton.disabled = false;
                }, 2000);
            }
        }

        async downloadComic() {
            const images = await this.downloadImages(1, this.totalPages);
            await this.generatePDF(images);
        }

        async downloadImages(start, end) {
            const imageElements = this.adapter.getImageElements();
            const downloadResults = new Array(end - start + 1);
            const downloadPromises = [];

            for (let i = 0; i < imageElements.length; i++) {
                const pageNumber = i + 1;
                if (pageNumber >= start && pageNumber <= end) {
                    this.addDownloadPromise(imageElements[i], pageNumber, start, downloadResults, downloadPromises);
                }
            }

            await Promise.all(downloadPromises);
            return downloadResults;
        }

        addDownloadPromise(element, pageNumber, start, downloadResults, downloadPromises) {
            const imgUrl = this.adapter.getImageUrl(element);
            if (imgUrl) {
                const arrayIndex = pageNumber - start;
                downloadPromises.push(
                    this.downloadImage(imgUrl)
                        .then(imgData => {
                            downloadResults[arrayIndex] = imgData;
                            this.ui.updateProgress(pageNumber);
                        })
                        .catch(error => {
                            console.error(`第 ${pageNumber} 页下载失败:`, error);
                            downloadResults[arrayIndex] = null;
                        })
                );
            }
        }

        downloadImage(url) {
            return new Promise((resolve, reject) => {
                if (this.abortController?.signal?.aborted) {
                    reject(new DOMException('下载已取消', 'AbortError'));
                    return;
                }

                const request = GM_xmlhttpRequest({
                    method: 'GET',
                    url: url,
                    responseType: 'blob',
                    onload: response => this.handleImageResponse(response, resolve, reject),
                    onerror: error => reject(error)
                });

                // 监听中断信号
                this.abortController?.signal?.addEventListener('abort', () => {
                    request.abort();  // 中断请求
                    reject(new DOMException('下载已取消', 'AbortError'));
                });
            });
        }

        handleImageResponse(response, resolve, reject) {
            try {
                const blob = response.response;
                const reader = new FileReader();
                reader.onload = event => resolve(event.target.result);
                reader.onerror = error => reject(error);
                reader.readAsDataURL(blob);
            } catch (error) {
                reject(error);
            }
        }

        async generatePDF(images) {
            const pdf = new jspdf.jsPDF();
            const sizes = await this.getImageSizes(images);

            if (this.isLongPageMode) {
                // 长图模式:将所有图片垂直拼接
                await this.generateLongPagePDF(pdf, images, sizes);
            } else {
                // 普通模式:每页一张图片
                for (let i = 0; i < images.length; i++) {
                    await this.addImageToPdf(pdf, images[i], i, sizes[i]);
                    this.ui.updateProgress(i + 1);
                }
            }

            pdf.save(`${this.chapterName}.pdf`);
        }

        async generateLongPagePDF(pdf, images, sizes) {
            // 计算所有图片的总高度
            const A4_width = 210;
            let totalHeight = 0;
            let maxWidth = 0;

            // 计算缩放比例和总高度
            for (let i = 0; i < sizes.length; i++) {
                const scaleFactor = A4_width / sizes[i].width;
                const scaledHeight = sizes[i].height * scaleFactor;
                totalHeight += scaledHeight;
                maxWidth = Math.max(maxWidth, sizes[i].width * scaleFactor);
            }

            // 设置PDF页面大小
            pdf.internal.pageSize.width = A4_width;
            pdf.internal.pageSize.height = totalHeight;

            // 垂直拼接所有图片
            let currentY = 0;
            for (let i = 0; i < images.length; i++) {
                const img = new Image();
                img.src = images[i];

                await new Promise(resolve => {
                    img.onload = () => {
                        const scaleFactor = A4_width / sizes[i].width;
                        const scaledHeight = sizes[i].height * scaleFactor;

                        pdf.addImage(
                            images[i],
                            'JPEG',
                            0,
                            currentY,
                            A4_width,
                            scaledHeight
                        );

                        currentY += scaledHeight;
                        this.ui.updateProgress(i + 1);
                        resolve();
                    };
                });
            }
        }

        async getImageSizes(images) {
            return Promise.all(images.map(imgData => {
                return new Promise(resolve => {
                    const img = new Image();
                    img.src = imgData;
                    img.onload = () => resolve({ width: img.width, height: img.height });
                });
            }));
        }

        async addImageToPdf(pdf, imgData, index, size) {
            return new Promise(resolve => {
                const img = new Image();
                img.src = imgData;
                img.onload = () => {
                    if (index > 0) pdf.addPage();

                    const A4_width = 210;
                    const A4_height = 297;
                    const scaleFactor = A4_width / size.width;
                    let finalWidth = A4_width;
                    let finalHeight = size.height * scaleFactor;

                    if (finalHeight > A4_height) {
                        finalHeight = A4_height;
                        finalWidth = size.width * (A4_height / size.height);
                    }

                    pdf.internal.pageSize.width = finalWidth;
                    pdf.internal.pageSize.height = finalHeight;
                    pdf.addImage(imgData, 'JPEG', 0, 0, finalWidth, finalHeight);
                    resolve();
                };
            });
        }

        handleError(error, message = '下载失败') {
            console.error(message, error);
            alert(`${message},请查看控制台了解详情`);
        }
    }

    // 6. 初始化
    function initialize() {
        console.log('开始初始化下载器...');
        try {
            // 创建下载器实例
            window.comicDownloader = new ComicDownloader();

            // 检查是否需要自动下载
            const autoDownload = GM_getValue('autoDownload', false);
            console.log('自动下载标志:', autoDownload);

            // 如果是章节页面且需要自动下载
            if (autoDownload && window.comicDownloader.adapter.isChapterPage()) {
                console.log('检测到是从目录页面打开的章节页面,准备自动下载');
                // 读取长图模式状态
                window.comicDownloader.isLongPageMode = GM_getValue('isLongPageMode', false);

                setTimeout(async () => {
                    try {
                        console.log('开始自动下载...');
                        await window.comicDownloader.handleDownload();
                        console.log('自动下载完成,设置状态为 complete');

                        GM_setValue('downloadStatus', 'complete');
                    } catch (error) {
                        console.error('自动下载失败:', error);
                        GM_setValue('downloadStatus', 'complete');
                    }
                }, 2000);
            }
        } catch (error) {
            console.error('初始化失败:', error);
        }
    }

    // 确保在页面完全加载后再初始化
    if (document.readyState === 'complete') {
        initialize();
    } else {
        window.addEventListener('load', initialize);
    }
})();