您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
点击按钮下载帖子中的所有图片,文件名包含作者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); })();