Kemono Download Button

点击按钮下载帖子中的所有图片,文件名包含作者ID、帖子标题和作品编号,仅在kemono.su的帖子页面上运行

// ==UserScript==
// @name         Kemono Download Button
// @namespace    http://tampermonkey.net/
// @version      2.2
// @description  点击按钮下载帖子中的所有图片,文件名包含作者ID、帖子标题和作品编号,仅在kemono.su的帖子页面上运行
// @author       hoami_523
// @match        https://kemono.su/*
// @grant        GM_download
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    // 动态加载JSZip库
    function loadJSZip() {
        var script = document.createElement('script');
        script.src = "https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js";
        script.onload = function() {
            console.log("JSZip库已加载");
        };
        document.head.appendChild(script);
    }

    loadJSZip();  // 加载 JSZip 库

    // 创建下载按钮们
    function createDownloadButtons() {
        if (document.querySelector('button[aria-label="下载所有图片"]')) return;
        if (document.querySelector('button[aria-label="下载为ZIP"]')) return;

        const actionsDiv = document.querySelector('.post__actions');
        const favButton = actionsDiv ? actionsDiv.querySelector('button.post__fav') : null;

        if (!favButton) {
            console.warn('未找到“Favorite”按钮,无法将下载按钮放置在其右侧');
            return;
        }

        // 创建下载所有图片按钮
        const downloadButton = document.createElement('button');
        downloadButton.textContent = '下载所有图片';
        downloadButton.style.marginLeft = '10px';
        downloadButton.style.padding = '10px 15px';
        downloadButton.style.backgroundColor = '#007bff';
        downloadButton.style.color = '#fff';
        downloadButton.style.border = 'none';
        downloadButton.style.borderRadius = '5px';
        downloadButton.style.cursor = 'pointer';
        downloadButton.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.1)';
        downloadButton.setAttribute('aria-label', '下载所有图片');

        // 创建ZIP下载按钮
        const zipButton = document.createElement('button');
        zipButton.textContent = '下载为ZIP';
        zipButton.style.marginLeft = '10px';
        zipButton.style.padding = '10px 15px';
        zipButton.style.backgroundColor = '#28a745';
        zipButton.style.color = '#fff';
        zipButton.style.border = 'none';
        zipButton.style.borderRadius = '5px';
        zipButton.style.cursor = 'pointer';
        zipButton.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.1)';
        zipButton.setAttribute('aria-label', '下载为ZIP');

        actionsDiv.insertBefore(zipButton, favButton.nextSibling);
        actionsDiv.insertBefore(downloadButton, zipButton);

        // 下载为ZIP按钮点击事件
        zipButton.addEventListener('click', function () {
            const titleElement = document.querySelector('h1.post__title');
            const authorElement = document.querySelector('a.post__user-name');

            if (!titleElement || !authorElement) {
                alert('未找到帖子标题或作者信息!');
                return;
            }

            const postTitle = titleElement.textContent.trim().replace(/[\\/:*?"<>|]/g, '').replace(/\s+/g, '');
            const authorID = authorElement.textContent.trim();
            const postIDMatch = window.location.pathname.match(/\/post\/(\d+)/);
            const postID = postIDMatch ? postIDMatch[1] : 'UnknownPostID';

            const imageLinks = document.querySelectorAll('.post__files .post__thumbnail a.fileThumb');
            if (imageLinks.length === 0) {
                alert('未找到任何图片链接!');
                return;
            }

            const zip = new JSZip();
            const totalImages = imageLinks.length;
            let downloadedImages = 0;

            // 更新按钮文本
            const updateProgress = (downloaded) => {
                zipButton.textContent = `下载中... ${downloaded}/${totalImages}`;
            };

            const imagePromises = Array.from(imageLinks).map((link, index) => {
                let imageUrl = link.href;
                if (imageUrl.startsWith('/')) {
                    imageUrl = window.location.origin + imageUrl;
                }

                if (!/\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(imageUrl)) {
                    console.warn('跳过非图片文件:', imageUrl);
                    return Promise.resolve(); // 如果图片不是有效格式,跳过
                }

                const imageNumber = index + 1;
                const fileExtension = imageUrl.split('.').pop().split('?')[0];
                const fileName = `p${String(imageNumber).padStart(3, '0')}.${fileExtension}`;

                return new Promise((resolve, reject) => {
                    GM_xmlhttpRequest({
                        method: "GET",
                        url: imageUrl,
                        responseType: "arraybuffer",
                        onload: function (response) {
                            if (response.status === 200) {
                                zip.file(fileName, response.response);
                                downloadedImages++;
                                updateProgress(downloadedImages);  // 更新进度
                                resolve();
                            } else {
                                reject(`下载失败:${fileName}`);
                            }
                        },
                        onerror: function () {
                            reject(`下载失败:${fileName}`);
                        }
                    });
                });
            });

            // 确保所有图片下载完成后再生成ZIP
            Promise.all(imagePromises)  // 使用 Promise.all() 确保所有请求都在最终生成ZIP之前完成
                .then(() => {
                    zip.generateAsync({ type: "blob" })
                        .then(function (content) {
                            const zipFileName = `${authorID}_${postTitle}_${postID}.zip`;
                            GM_download({
                                url: URL.createObjectURL(content),
                                name: zipFileName,
                                onload: function () {
                                    console.log(`成功下载ZIP:${zipFileName}`);
                                    zipButton.textContent = '下载成功';
                                },
                                onerror: function (err) {
                                    console.error(`下载失败:${zipFileName}`, err);
                                    zipButton.textContent = '下载失败';
                                }
                            });
                        });
                })
                .catch((error) => {
                    console.error(error);
                    alert('下载ZIP包失败!');
                });
        });

        // 每张图片单独下载按钮点击事件
        downloadButton.addEventListener('click', function () {
            const titleElement = document.querySelector('h1.post__title');
            const authorElement = document.querySelector('a.post__user-name');

            if (!titleElement || !authorElement) {
                alert('未找到帖子标题或作者信息!');
                return;
            }

            const postTitle = titleElement.textContent.trim().replace(/[\\/:*?"<>|]/g, '').replace(/\s+/g, '');
            const authorID = authorElement.textContent.trim();
            const postIDMatch = window.location.pathname.match(/\/post\/(\d+)/);
            const postID = postIDMatch ? postIDMatch[1] : 'UnknownPostID';

            const imageLinks = document.querySelectorAll('.post__files .post__thumbnail a.fileThumb');
            if (imageLinks.length === 0) {
                alert('未找到任何图片链接!');
                return;
            }

            imageLinks.forEach((link, index) => {
                let imageUrl = link.href;
                if (imageUrl.startsWith('/')) {
                    imageUrl = window.location.origin + imageUrl;
                }

                if (!/\.(jpg|jpeg|png|gif|bmp|webp)$/i.test(imageUrl)) {
                    console.warn('跳过非图片文件:', imageUrl);
                    return;  // 跳过非图片文件
                }

                const imageNumber = index + 1;
                const fileExtension = imageUrl.split('.').pop().split('?')[0];
                const fileName = `${authorID}_${postTitle}_${postID}_p${imageNumber}.${fileExtension}`;

                GM_download({
                    url: imageUrl,
                    name: fileName,
                    onload: function() {
                        console.log(`成功下载:${fileName}`);
                    },
                    onerror: function (err) {
                        console.error(`下载失败:${fileName}`, err);
                    }
                });
            });

            alert(`${imageLinks.length} 张图片已开始下载!`);
        });
    }

    // 监听页面变化并重置按钮文本
    let lastUrl = window.location.href;

    // 监听页面 URL 变化并重置 ZIP 按钮文本
    function resetButtonIfUrlChanged() {
        const currentUrl = window.location.href;
        if (currentUrl !== lastUrl) {
            console.log("检测到 URL 变化,重置 ZIP 按钮文本");
            const zipButton = document.querySelector('button[aria-label="下载为ZIP"]');
            if (zipButton) {
                zipButton.textContent = '下载为ZIP';  // 重置为初始文本
            }
            lastUrl = currentUrl;  // 更新 URL 记录
            createDownloadButtons(); // 重新添加按钮(如果需要)
        }
    }
     // 监听 `popstate` 事件(浏览器前进/后退)
    window.addEventListener('popstate', resetButtonIfUrlChanged);

    // 劫持 `pushState` 和 `replaceState` 以监听单页面应用的 URL 变化
    const originalPushState = history.pushState;
    history.pushState = function() {
        originalPushState.apply(this, arguments);
        resetButtonIfUrlChanged();
    };

    const originalReplaceState = history.replaceState;
    history.replaceState = function() {
        originalReplaceState.apply(this, arguments);
        resetButtonIfUrlChanged();
    };
    // 监听页面变化
    const observer = new MutationObserver(function (mutations) {
        mutations.forEach(function (mutation) {
            if (mutation.addedNodes.length) {
                const actionsDiv = document.querySelector('.post__actions');
                const favButton = actionsDiv ? actionsDiv.querySelector('button.post__fav') : null;

                if (favButton) {
                    console.log('检测到“Favorite”按钮,准备添加下载按钮...');
                    createDownloadButtons();
                }
            }
        });
    });

    observer.observe(document.body, { childList: true, subtree: true });

    window.addEventListener('load', createDownloadButtons);
})();