您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
一键下载图片 (压缩下载/单图下载) , 页面数据创建 json 下载 , 一键开启当前所有帖子
当前为
// ==UserScript== // @name Kemono 下載工具 // @name:zh-TW Kemono 下載工具 // @name:zh-CN Kemono 下载工具 // @name:ja Kemono ダウンロードツール // @name:en Kemono DownloadTool // @version 0.0.9 // @author HentiSaru // @description 一鍵下載圖片 (壓縮下載/單圖下載) , 頁面數據創建 json 下載 , 一鍵開啟當前所有帖子 // @description:zh-TW 一鍵下載圖片 (壓縮下載/單圖下載) , 頁面數據創建 json 下載 , 一鍵開啟當前所有帖子 // @description:zh-CN 一键下载图片 (压缩下载/单图下载) , 页面数据创建 json 下载 , 一键开启当前所有帖子 // @description:ja 画像をワンクリックでダウンロード(圧縮ダウンロード/単一画像ダウンロード)、ページデータを作成してjsonでダウンロード、現在のすべての投稿をワンクリックで開く // @description:en One-click download of images (compressed download/single image download), create page data for json download, one-click open all current posts // @match *://kemono.su/* // @match *://*.kemono.su/* // @match *://kemono.party/* // @match *://*.kemono.party/* // @icon https://cdn-icons-png.flaticon.com/512/2381/2381981.png // @license MIT // @namespace https://greasyfork.org/users/989635 // @run-at document-end // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_download // @grant GM_addElement // @grant GM_notification // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.9.1/jszip.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js // ==/UserScript== const regex = /^https:\/\/[^/]+/, pattern = /^(https?:\/\/)?(www\.)?kemono\..+\/.+\/user\/.+\/post\/.+$/, language = display_language(navigator.language); var CompressMode = GM_getValue("壓縮下載", []), parser = new DOMParser(), url = window.location.href.match(regex), dict = {}, Pages=0, OriginalTitle = document.title, ModeDisplay; (function() { const observer = new MutationObserver(() => { if (pattern.test(window.location.href) && !document.querySelector("#DBExist")) {ButtonCreation()} }); if (pattern.test(window.location.href)) { observer.observe(document.head, {childList: true, subtree: true}); } GM_registerMenuCommand(language.RM_01, function() {DownloadModeSwitch()}, "C") GM_registerMenuCommand(language.RM_02, function() { const section = document.querySelector("section"); if (section) { GetPageData(section); } }, "J") GM_registerMenuCommand(language.RM_03, function() {OpenData()}, "O") })(); async function DownloadModeSwitch() { if (GM_getValue("壓縮下載", [])){ GM_setValue("壓縮下載", false); GM_notification({ title: language.NF_01, text: language.DM_02, timeout: 1500 }); } else { GM_setValue("壓縮下載", true); GM_notification({ title: language.NF_01, text: language.DM_01, timeout: 1500 }); } location.reload(); } async function ButtonCreation() { GM_addStyle(` .File_Span { padding: 1rem; font-size: 20% !important; } .Download_Button { color: hsl(0, 0%, 45%); padding: 6px; border-radius: 8px; border: 2px solid rgba(59, 62, 68, 0.7); background-color: rgba(29, 31, 32, 0.8); font-family: Arial, sans-serif; } .Download_Button:hover { color: hsl(0, 0%, 95%); background-color: hsl(0, 0%, 45%); font-family: Arial, sans-serif; } .Download_Button:disabled { color: hsl(0, 0%, 95%); background-color: hsl(0, 0%, 45%); cursor: default; } `); let download_button; try { const Files = document.querySelectorAll("div.post__body h2") const spanElement = GM_addElement(Files[Files.length - 1], "span", {class: "File_Span"}); download_button = GM_addElement(spanElement, "button", { class: "Download_Button", id: "DBExist" }); if (CompressMode) { ModeDisplay = language.DS_01; } else { ModeDisplay = language.DS_02; } download_button.textContent = ModeDisplay; download_button.addEventListener("click", function() { DownloadTrigger(download_button); }); } catch { download_button.textContent = language.DS_04; download_button.disabled = true; } } function IllegalFilter(Name) { return Name.replace(/[\/\?<>\\:\*\|":]/g, ''); } function Conversion(Name) { return Name.replace(/[\[\]]/g, ''); } function GetExtension(link) { const match = link.match(/\.([^.]+)$/); if (match) {return match[1].toLowerCase()} return "png"; } function DownloadTrigger(button) { let data = new Map(), link; let interval = setInterval(function() { let imgdata = document.querySelectorAll("div.post__files a"); let title = document.querySelector("h1.post__title").textContent.trim(); let user = document.querySelector("a.post__user-name").textContent.trim(); if (imgdata && title && user) { imgdata.forEach((files, index) => { link = files.href || files.querySelector("img").src; data.set(index, link.split("?f=")[0]); }); if (CompressMode) { ZipDownload(`[${user}] ${title}`, data, button); } else { ImageDownload(`[${user}] ${title}`, data, button) } button.textContent = language.DS_03; button.disabled = true; clearInterval(interval); } }, 500); } async function ZipDownload(Folder, ImgData, Button) { const zip = new JSZip(), File = Conversion(Folder), Total = ImgData.size, name = IllegalFilter(Folder.split(" ")[1]); let pool = [], poolSize = 5, progress = 1, mantissa, link, extension, BackgroundWork; function Request(index, retry) { link = ImgData.get(index); extension = GetExtension(link); return new Promise((resolve) => { GM_xmlhttpRequest({ method: "GET", url: link, responseType: "blob", headers : {"user-agent": navigator.userAgent}, onload: response => { if (response.status === 200 && response.response instanceof Blob && response.response.size > 0) { mantissa = (index + 1).toString().padStart(3, '0'); zip.file(`${File}/${name}_${mantissa}.${extension}`, response.response); document.title = `[${progress}/${Total}]`; Button.textContent = `${language.DS_05} [${progress}/${Total}]`; progress++; resolve(); } else { if (retry > 0) { Request(index, retry-1); console.log(`[${retry}] : ${link}`); } else { console.log(`[error] : ${link}`); resolve(); } } }, onerror: error => { if (retry > 0) { Request(index, retry-1); console.log(`[${retry}] : ${link}`); } else { console.log(`[error] : ${link}`); resolve(); } } }); }); } for (let i = 0; i < Total; i++) { let promise = Request(i, 10); pool.push(promise); if (pool.length >= poolSize) { await Promise.allSettled(pool); pool = []; } } if (pool.length > 0) {await Promise.allSettled(pool)} Compression(); async function Compression() { if (typeof(Worker) !== "undefined" && typeof(BackgroundWork) === "undefined") { BackgroundWork = new Worker(BackWorkerCreation()); BackgroundWork.postMessage([ await zip.generateAsync({ // 不await 無法序列化, 不太會 Worker type: "blob", compression: "DEFLATE", compressionOptions: { level: 5 // 壓縮級別,範圍從 0(無壓縮)到 9(最大壓縮) } }, (progress) => { document.title = `${progress.percent.toFixed(1)} %`; Button.textContent = `${language.DS_06}: ${progress.percent.toFixed(1)} %`; }).then(zip => { saveAs(zip, `${Folder}.zip`); Button.textContent = language.DS_08; document.title = OriginalTitle; setTimeout(() => {Button.textContent = ModeDisplay}, 4000); Button.disabled = false; }).catch(result => { Button.textContent = language.DS_07; document.title = OriginalTitle; setTimeout(() => {Button.textContent = ModeDisplay}, 6000); Button.disabled = false; }) ]); } } } async function ImageDownload(Folder, ImgData, Button) { const name = IllegalFilter(Folder.split(" ")[1]), Total = ImgData.size; let progress = 1, link, extension; for (let i = 0; i < Total; i++) { link = ImgData.get(i); extension = GetExtension(link); GM_download({ url: link, name: `${name}_${(progress+i).toString().padStart(3, '0')}.${extension}`, onload: () => { document.title = `[${progress}/${Total}]`; Button.textContent = `${language.DS_05} [${progress}/${Total}]`; progress++; }, onerror: () => { i--; } }); } Button.textContent = language.DS_08; setTimeout(() => {Button.textContent = ModeDisplay}, 4000); document.title = OriginalTitle; Button.disabled = false; } async function GetPageData(section) { const menu = section.querySelector("a.pagination-button-after-current"); const item = section.querySelectorAll(".card-list__items article"); let title, link; item.forEach(card => { title = card.querySelector(".post-card__header").textContent.trim() link = card.querySelector("a").href dict[`${link}`] = title; }) try { // 當沒有下一頁連結就會發生例外 let NextPage = menu.href; if (NextPage) { Pages++; GM_notification({ title: language.NF_02, text: `${language.NF_03} : ${Pages}`, image: "https://cdn-icons-png.flaticon.com/512/2582/2582087.png", timeout: 800 }); GM_xmlhttpRequest({ method: "GET", url: NextPage, nocache: false, ontimeout: 8000, onload: response => { const DOM = parser.parseFromString(response.responseText, "text/html"); GetPageData(DOM.querySelector("section")); } }); } } catch { try { // 進行簡單排序 Object.keys(dict).sort(); const author = document.querySelector('span[itemprop="name"]').textContent; const json = document.createElement("a"); json.href = "data:application/json;charset=utf-8," + encodeURIComponent(JSON.stringify(dict, null, 4)); json.download = `${author}.json`; json.click(); json.remove(); GM_notification({ title: language.NF_04, text: language.NF_05, image: "https://cdn-icons-png.flaticon.com/512/2582/2582087.png", timeout: 2000 }); } catch { alert(language.NF_06); } } } async function OpenData() { try { let content = document.querySelector('.card-list__items').querySelectorAll('article.post-card'); content.forEach(function(content) { let link = content.querySelector('a').getAttribute('href'); setTimeout(() => { window.open("https://kemono.party" + link , "_blank"); }, 300); }); } catch { alert(language.NF_07); } } function display_language(language) { let display = { "zh-TW": [{ "RM_01" : "🔁 切換下載模式", "RM_02" : "📑 獲取所有帖子 Json 數據", "RM_03" : "📃 開啟當前頁面所有帖子", "DM_01" : "壓縮下載模式", "DM_02" : "單圖下載模式", "DS_01" : "壓縮下載", "DS_02" : "單圖下載", "DS_03" : "開始下載", "DS_04" : "無法下載", "DS_05" : "下載進度", "DS_06" : "封裝進度", "DS_07" : "壓縮封裝失敗", "DS_08" : "下載完成", "NF_01" : "模式切換", "NF_02" : "數據處理中", "NF_03" : "當前處理頁數", "NF_04" : "數據處理完成", "NF_05" : "Json 數據下載", "NF_06" : "錯誤的請求頁面", "NF_07" : "錯誤的開啟頁面" }], "zh-CN": [{ "RM_01" : "🔁 切换下载模式", "RM_02" : "📑 获取所有帖子 Json 数据", "RM_03" : "📃 打开当前页面所有帖子", "DM_01" : "压缩下载模式", "DM_02" : "单图下载模式", "DS_01" : "压缩下载", "DS_02" : "单图下载", "DS_03" : "开始下载", "DS_04" : "无法下载", "DS_05" : "下载进度", "DS_06" : "封装进度", "DS_07" : "压缩封装失败", "DS_08" : "下载完成", "NF_01" : "模式切换", "NF_02" : "数据处理中", "NF_03" : "当前处理页数", "NF_04" : "数据处理完成", "NF_05" : "Json 数据下载", "NF_06" : "错误的请求页面", "NF_07" : "错误的打开页面" }], "ja": [{ "RM_01" : '🔁 ダウンロードモードの切り替え', "RM_02" : '📑 すべての投稿のJsonデータを取得する', "RM_03" : '📃 現在のページのすべての投稿を開く', "DM_01" : '圧縮ダウンロードモード', "DM_02" : 'シングル画像ダウンロードモード', "DS_01" : '圧縮ダウンロード', "DS_02" : 'シングル画像ダウンロード', "DS_03" : 'ダウンロードを開始する', "DS_04" : 'ダウンロードできません', "DS_05" : 'ダウンロードの進行状況', "DS_06" : 'パッケージング中', "DS_07" : '圧縮パッケージングに失敗しました', "DS_08" : 'ダウンロードが完了しました', "NF_01" : 'モード切り替え', "NF_02" : 'データ処理中', "NF_03" : '現在の処理ページ数', "NF_04" : 'データ処理が完了しました', "NF_05" : 'Jsonデータのダウンロード', "NF_06" : '間違ったリクエストページ', "NF_07" : '間違ったページを開く' }], "en": [{ "RM_01" : '🔁 Switch download mode', "RM_02" : '📑 Get all post Json data', "RM_03" : '📃 Open all posts on the current page', "DM_01" : 'Compressed download mode', "DM_02" : 'Single image download mode', "DS_01" : 'Compressed download', "DS_02" : 'Single image download', "DS_03" : 'Start downloading', "DS_04" : 'Unable to download', "DS_05" : 'Download progress', "DS_06" : 'Packaging', "DS_07" : 'Compression packaging failed', "DS_08" : 'Download completed', "NF_01" : 'Mode switch', "NF_02" : 'Data processing', "NF_03" : 'Current processing page number', "NF_04" : 'Data processing completed', "NF_05" : 'Json data download', "NF_06" : 'Wrong request page', "NF_07" : 'Wrong page to open' }] }; return display[language][0] || display["en"][0]; } function BackWorkerCreation() { let blob = new Blob([""], {type: "application/javascript"}); return URL.createObjectURL(blob); }