Greasy Fork is available in English.

Diyibanzhu Downloader

纯小白请勿下载,第一版主网下载器,因为网址并不固定,所以不做域名匹配

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         Diyibanzhu Downloader
// @namespace    http://tampermonkey.net/
// @version      4.0.1
// @supportURL   https://github.com/LanluZ/Diyibanzhu-Download
// @homepageURL  https://github.com/LanluZ/Diyibanzhu-Download
// @description  纯小白请勿下载,第一版主网下载器,因为网址并不固定,所以不做域名匹配
// @author       LanluZ
// @match        http://*/*
// @match        https://*/*
// @grant        unsafeWindow
// @grant        GM_download
// @grant        GM_xmlhttpRequest
// @grant        unsafeWindow
// @license      MIT
// @require      https://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js
// @require
// ==/UserScript==

class DiyibanzhuDownloader {
    constructor() {
        this.title = '';
        this.info = '';
        this.pdfOptions = {
            margin: 1,
            image: { type: 'jpeg', quality: 0.95 }, // 使用JPEG格式,稍微降低质量以提高速度
            enableLinks: false, // 禁用链接以提高速度
            html2canvas: {
                scale: 2, // 提高清晰度
                useCORS: true, // 允许跨域图片
                logging: false, // 禁用日志以提高性能
                letterRendering: true
            },
            jsPDF: {
                unit: 'pt',
                format: 'a4',
                compress: true // 启用压缩以减小文件大小
            }
        };
    }

    // 初始化页面
    init() {
        this.title = this.getTitle();
        this.info = this.getInfo();
        
        if (this.existHome()) {
            this.layDownloadButton();
            this.laySearchButton();
            this.layCheckbox();
        }
        
        if (this.existContent()) {
            this.layCopyContentButton();
        }
    }

    // 发送HTTP请求
    sendRequest(url) {
        return new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest();
            xhr.open("GET", url);
            xhr.responseType = "document";
            xhr.onload = () => xhr.status === 200 ? resolve(xhr.response) : reject(new Error(`HTTP ${xhr.status}`));
            xhr.onerror = () => reject(new Error('Network Error'));
            xhr.send();
        });
    }

    // 下载处理
    async downloadButtonClicked() {
        try {
            const catalogueInfoList = this.getCatalogueInfo(document);
            const checkboxList = document.getElementsByClassName("downloadCheckbox");
            
            for (let i = 0; i < catalogueInfoList.length; i++) {
                if (!checkboxList[i].checked) continue;
                
                console.log(`开始处理章节: ${catalogueInfoList[i].text}`);
                
                try {
                    const catalogPage = await this.sendRequest(catalogueInfoList[i].href);
                    const contentInfoList = this.getContentInfo(catalogPage);
                    
                    for (const contentInfo of contentInfoList) {
                        try {
                            console.log(`> 开始下载: ${catalogueInfoList[i].text}-${contentInfo.text}`);
                            const contentPage = await this.sendRequest(contentInfo.href);
                            
                            // 预处理文档
                            const cleanedDoc = this.removeElement(contentPage, "neirong");
                            console.log(`>> 解析完成: ${catalogueInfoList[i].text}-${contentInfo.text}`);
                            
                            // 配置PDF选项
                            const options = {
                                ...this.pdfOptions,
                                filename: `${this.title},${catalogueInfoList[i].text},${contentInfo.text}.pdf`
                            };
                            
                            // 异步生成PDF
                            await this.generatePDF(cleanedDoc.body, options);
                            console.log(`>>> PDF生成完成: ${options.filename}`);
                        } catch (error) {
                            console.error(`下载内容页失败: ${contentInfo.text}`, error);
                        }
                    }
                } catch (error) {
                    console.error(`下载目录页失败: ${catalogueInfoList[i].text}`, error);
                }
            }
        } catch (error) {
            console.error('下载过程出错:', error);
        }
    }

    // 生成PDF
    async generatePDF(element, options) {
        return html2pdf().set(options).from(element).save();
    }

    // 搜索功能
    searchButtonClicked() {
        const encodedTitle = encodeURIComponent(this.title);
        window.open(`https://www.google.com/search?q=${encodedTitle}`);
    }

    // 复制内容
    copyContentButtonClicked() {
        const div = document.getElementById('nr1');
        const text = div.innerText || div.textContent;
        navigator.clipboard.writeText(text)
            .then(() => console.log('文本已复制'))
            .catch(err => console.error('复制失败:', err));
    }

    // 清理文档元素
    removeElement(document, className) {
        try {
            // 寻找目标标签
            const aimTag = document.querySelector('.' + className);
            if (!aimTag) {
                // 如果没有找到目标标签,尝试查找 nr1 元素
                const nr1Tag = document.getElementById('nr1');
                if (nr1Tag) {
                    return document;
                }
                console.log("未找到目标标签");
                return document;
            }

            // 寻找目标标签之前标签
            let prevTags = [];
            let currentNode = aimTag;
            while (currentNode.previousElementSibling || currentNode.parentElement) {
                if (currentNode.previousElementSibling) {
                    currentNode = currentNode.previousElementSibling;
                    prevTags.unshift(currentNode);
                } else if (currentNode.parentElement) {
                    currentNode = currentNode.parentElement;
                }
            }

            // 寻找目标标签之后标签
            let nextTags = [];
            currentNode = aimTag;
            while (currentNode.nextElementSibling || currentNode.parentElement) {
                if (currentNode.nextElementSibling) {
                    currentNode = currentNode.nextElementSibling;
                    nextTags.push(currentNode);
                } else if (currentNode.parentElement) {
                    currentNode = currentNode.parentElement;
                }
            }

            // 寻找目标标签父标签
            let parentTags = [];
            currentNode = aimTag;
            while (currentNode.parentElement) {
                parentTags.push(currentNode.parentElement);
                currentNode = currentNode.parentElement;
            }

            // 删除前标签中非父标签和非head标签
            for (let i = 0; i < prevTags.length; i++) {
                if (!parentTags.includes(prevTags[i]) && !prevTags[i].classList.contains('head')) {
                    prevTags[i].remove();
                }
            }

            // 删除后标签
            for (let i = 0; i < nextTags.length; i++) {
                nextTags[i].remove();
            }

        } catch (e) {
            console.error("解析错误:", e);
        }
        return document;
    }

    //获取指定内容页章节标题链接
    getContentInfo(contentDocument) {
        const result = [];
        const currentUrl = contentDocument.URL;
        
        // 总是添加当前页面作为第一页
        result.push({
            href: currentUrl,
            text: '[1]'
        });

        // 检查是否有分页
        const catalogueList = contentDocument.getElementsByClassName("chapterPages")[0];
        if (!catalogueList) {
            return result;
        }

        // 获取分页链接
        const pageLinks = catalogueList.getElementsByTagName("a");
        for (let i = 0; i < pageLinks.length; i++) {
            // 跳过当前页面的重复链接
            if (pageLinks[i].href !== currentUrl) {
                result.push({
                    href: pageLinks[i].href,
                    text: `[${i + 2}]`  // 从[2]开始编号
                });
            }
        }

        return result;
    }

    // 获取目录信息
    getCatalogueInfo(catalogueDocument) {
        const catalogueList = catalogueDocument.getElementsByClassName("list")[1];
        return Array.from(catalogueList.getElementsByTagName("li")).map(li => {
            const a = li.getElementsByTagName("a")[0];
            return {
                href: a.href,
                text: a.innerText
            };
        });
    }

    // 检查是否为主页
    existHome() {
        try {
            return document.getElementsByClassName("read start")[0].innerHTML === "从头开始阅读";
        } catch (e) {
            return false;
        }
    }

    // 检查是否为内容页
    existContent() {
        return !!document.getElementById("nr1");
    }

    // 获取标题
    getTitle() {
        return document.getElementsByTagName("h1")[0].innerHTML;
    }

    // 获取信息
    getInfo() {
        return document.getElementsByClassName("info")[0].innerHTML.replace(/<br>/g, "");
    }

    // UI相关方法
    layDownloadButton() {
        this.createButton("下载", () => this.downloadButtonClicked());
    }

    laySearchButton() {
        this.createButton("搜索", () => this.searchButtonClicked());
    }

    layCopyContentButton() {
        const ftNode = document.getElementsByClassName("page-title")[0];
        const button = document.createElement("div");
        button.innerHTML = "<tr><td style='width: 50px'><button>复制内容</button></td></tr>";
        button.onclick = () => this.copyContentButtonClicked();
        ftNode.appendChild(button);

        const nr1 = document.getElementById("nr1");
        if (nr1) nr1.style.userSelect = "text";
    }

    createButton(text, onClick) {
        const ft = document.getElementsByClassName("ft")[0];
        if (!ft) return;

        const button = document.createElement("div");
        button.innerHTML = `<tr><td style='width: 50px'><a class='read start'>${text}</a></td></tr>`;
        button.onclick = onClick;
        ft.childNodes[1].childNodes[1].appendChild(button);
    }

    layCheckbox() {
        // 使用与getCatalogueInfo相同的目录列表
        const catalogueList = document.getElementsByClassName("list")[1];
        if (!catalogueList) {
            console.log("未找到目录列表");
            return;
        }

        const items = catalogueList.getElementsByTagName("li");
        Array.from(items).forEach(item => {
            const checkbox = document.createElement("input");
            checkbox.className = "downloadCheckbox";
            checkbox.type = "checkbox";
            item.insertBefore(checkbox, item.firstChild);
        });
    }
}

// 初始化脚本
(function() {
    'use strict';
    const downloader = new DiyibanzhuDownloader();
    downloader.init();
})();