您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
一键下载图片 (压缩下载/单图下载) , 页面数据创建 json 下载 , 一键开启当前所有帖子
当前为
// ==UserScript== // @name Kemer 下載器 // @name:zh-TW Kemer 下載器 // @name:zh-CN Kemer 下载器 // @name:ja Kemer ダウンローダー // @name:en Kemer Downloader // @version 0.0.21-Beta5 // @author Canaan HS // @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 // @connect * // @match *://kemono.su/* // @match *://coomer.su/* // @match *://nekohouse.su/* // @match *://*.kemono.su/* // @match *://*.coomer.su/* // @match *://*.nekohouse.su/* // @license MIT // @namespace https://greasyfork.org/users/989635 // @icon https://cdn-icons-png.flaticon.com/512/2381/2381981.png // @run-at document-start // @grant window.close // @grant GM_info // @grant GM_setValue // @grant GM_getValue // @grant GM_download // @grant GM_openInTab // @grant GM_addElement // @grant GM_notification // @grant GM_getResourceURL // @grant GM_xmlhttpRequest // @grant GM_getResourceText // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @require https://update.greasyfork.org/scripts/473358/1237031/JSZip.js // @require https://update.greasyfork.org/scripts/495339/1456526/ObjectSyntax_min.js // @require https://cdnjs.cloudflare.com/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js // @resource json-processing https://cdn-icons-png.flaticon.com/512/2582/2582087.png // @resource font-awesome https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/svg-with-js.min.css // ==/UserScript== (async () => { const Config = { Dev: false, // 顯示請求資訊, 與錯誤資訊 NotiFication: true, // 操作時 系統通知 ContainsVideo: false, // 下載時包含影片 CompleteClose: false, // 下載完成後關閉 ExperimeDownload: true, // 實驗功能 [json 下載] ConcurrentDelay: 3, // 下載線程延遲 (秒) [壓縮下載] ConcurrentQuantity: 5, // 下載線程數量 [壓縮下載] BatchOpenDelay: 500, // 一鍵開啟帖子的延遲 (ms) }; /** --------------------- * 暫時的 檔名修改方案 * * 根據要添加的元素修改字串 * 中間的間隔可用任意字符 * * ! 不限制大小寫, 但一定要有 {}, 不能用於命名的符號會被移除 * * {Time} 發表時間 * {Title} 標題 * {Artist} 作者 | 繪師 ... * {Source} 來源 => (Pixiv Fanbox) 之類的標籤 * * {Fill} 填充 => ! 只適用於檔名, 位置隨意 但 必須存在該值, 不然會出錯 */ const FileName = { FillValue: { Filler: "0", // 填充元素 / 填料 Amount: "Auto", // 填充數量 [輸入 auto 或 任意數字] }, CompressName: "({Artist}) {Title}", // 壓縮檔案名稱 FolderName: "{Title}", // 資料夾名稱 (用空字串, 就直接沒資料夾) FillName: "{Artist} {Fill}", // 檔案名稱 [! 可以移動位置, 但不能沒有 {Fill}] }; /** --------------------- * 設置 json 輸出格式 * * Mode * 排除模式: "FilterMode" -> 預設為全部使用, 設置排除的項目 * 僅有模式: "OnlyMode" -> 預設為全部不使用, 設置使用的項目 * * ---------------------- * * Settings * 原始連結: "orlink" * 圖片數量: "imgnb" * 影片數量: "videonb" * 連結數量: "dllink" */ const JsonFormat = { Use: false, Mode: "OnlyMode", Settings: ["orlink", "dllink"], }; /* --------------------- */ let lock = false; const Lang = Language(Syn.Device.Lang); const IsNeko = Syn.Device.Host === "nekohouse.su"; class Download { constructor(CM, MD, BT) { this.Button = BT; this.ModeDisplay = MD; this.CompressMode = CM; this.ForceDownload = false; this.Named_Data = null; this.OriginalTitle = () => { const cache = document.title; return cache.startsWith("✓ ") ? cache.slice(2) : cache; }; this.videoFormat = new Set(["MP4", "MOV", "AVI", "WMV", "FLV"]); this.isVideo = str => this.videoFormat.has(str.toUpperCase()); this.worker = Syn.WorkerCreation(` let queue = [], processing=false; onmessage = function(e) { queue.push(e.data); !processing && (processing=true, processQueue()); } async function processQueue() { if (queue.length > 0) { const {index, url} = queue.shift(); XmlRequest(index, url); processQueue(); } else {processing = false} } async function XmlRequest(index, url) { let xhr = new XMLHttpRequest(); xhr.responseType = "blob"; xhr.open("GET", url, true); xhr.onload = function() { if (xhr.readyState === 4 && xhr.status === 200) { postMessage({ index, url: url, blob: xhr.response, error: false }); } else { FetchRequest(index, url); } } xhr.onerror = function() { FetchRequest(index, url); } xhr.send(); } async function FetchRequest(index, url) { try { const response = await fetch(url); if (response.readyState === 4 && response.status === 200) { const blob = await response.blob(); postMessage({ index, url: url, blob, error: false }); } else { postMessage({ index, url: url, blob: "", error: true }); } } catch { postMessage({ index, url: url, blob: "", error: true }); } } `); } NameAnalysis(format) { if (typeof format == "string") { return format.split(/{([^}]+)}/g).filter(Boolean).map(data => { const LowerData = data.toLowerCase().trim(); const isWord = /^[a-zA-Z]+$/.test(LowerData); return isWord ? this.Named_Data[LowerData]?.() ?? "None" : data; }).join(""); } else if (typeof format == "object") { const filler = String(format.Filler) || "0"; const amount = parseInt(format.Amount) || "auto"; return [amount, filler]; } else { } } DownloadTrigger() { Syn.WaitMap([".post__title, .scrape__title", ".post__files, .scrape__files", ".post__user-name, .scrape__user-name, fix_name"], found => { const [title, files, artist] = found; this.Button.disabled = lock = true; const DownloadData = new Map(); this.Named_Data = { fill: () => "fill", title: () => Syn.$$("span", { root: title }).textContent.trim(), artist: () => artist.textContent.trim(), source: () => title.querySelector(":nth-child(2)").textContent.trim(), time: () => { if (IsNeko) return ""; let published = Syn.$$(".post__published").cloneNode(true); published.firstElementChild.remove(); return published.textContent.trim().split(" ")[0]; } }; const [compress_name, folder_name, fill_name] = Object.keys(FileName).slice(1).map(key => this.NameAnalysis(FileName[key])); const data = [...files.children].map(child => Syn.$$(IsNeko ? "div, rc, img" : "a, rc, img", { root: child })).filter(Boolean), video = Syn.$$(".post__attachment a, .scrape__attachment a", { all: true }), final_data = Config.ContainsVideo ? [...data, ...video] : data; for (const [index, file] of final_data.entries()) { const Uri = file.src || file.href || file.getAttribute("src") || file.getAttribute("href"); if (Uri) { DownloadData.set(index, Uri.startsWith("http") ? Uri : `${Syn.Device.Orig}${Uri}`); } } Syn.Log("Get Data", { FolderName: folder_name, DownloadData: DownloadData }, { dev: Config.Dev, collapsed: false }); this.CompressMode ? this.PackDownload(compress_name, folder_name, fill_name, DownloadData) : this.SeparDownload(fill_name, DownloadData); }, { raf: true }); } async PackDownload(CompressName, FolderName, FillName, Data) { let show, extension, progress = 0, Total = Data.size; const Self = this, Zip = new JSZip(), TitleCache = this.OriginalTitle(); const FillValue = this.NameAnalysis(FileName.FillValue), Filler = FillValue[1], Amount = FillValue[0] == "auto" ? Syn.GetFill(Total) : FillValue[0]; async function ForceDownload() { Self.worker.terminate(); Self.Compression(CompressName, Zip, TitleCache); } Syn.Menu({ [Lang.Transl("📥 強制壓縮下載")]: { func: () => ForceDownload(), hotkey: "d" } }, "Enforce"); FolderName = FolderName != "" ? `${FolderName}/` : ""; function Request_update(index, url, blob, retry = false) { if (Self.ForceDownload) return; requestAnimationFrame(() => { Data.delete(index); if (retry) { Data.set(index, url); } else { extension = Syn.ExtensionName(url); Self.isVideo(extension) ? Zip.file(`${FolderName}${decodeURIComponent(url.split("?f=")[1])}`, blob) : Zip.file(`${FolderName}${FillName.replace("fill", Syn.Mantissa(index, Amount, Filler))}.${extension}`, blob); } show = `[${++progress}/${Total}]`; document.title = show; Self.Button.textContent = `${Lang.Transl("下載進度")} ${show}`; if (progress == Total) { Total = Data.size; if (Total == 0) { Self.worker.terminate(); Self.Compression(CompressName, Zip, TitleCache); } else { show = "Wait for failed re download"; progress = 0; document.title = show; Self.Button.textContent = show; setTimeout(() => { for (const [index, url] of Data.entries()) { Self.worker.postMessage({ index: index, url: url }); } }, 1500); } } }); } async function Request(index, url) { if (Self.ForceDownload) return; GM_xmlhttpRequest({ url: url, method: "GET", responseType: "blob", onload: response => { if (response.status == 429) { Request_update(index, url, "", true); return; } const blob = response.response; blob instanceof Blob && blob.size > 0 ? Request_update(index, url, blob) : Request_update(index, url, "", true); }, onerror: () => { Request_update(index, url, "", true); } }); } const Batch = Config.ConcurrentQuantity; const Delay = Config.ConcurrentDelay * 1e3; Self.Button.textContent = `${Lang.Transl("請求進度")} [${Total}/${Total}]`; for (let i = 0; i < Total; i += Batch) { setTimeout(() => { for (let j = i; j < i + Batch && j < Total; j++) { this.worker.postMessage({ index: j, url: Data.get(j) }); } }, i / Batch * Delay); } this.worker.onmessage = e => { const { index, url, blob, error } = e.data; error ? (Request(index, url), Syn.Log("Download Failed", url, { dev: Config.Dev, collapsed: false })) : (Request_update(index, url, blob), Syn.Log("Download Successful", url, { dev: Config.Dev, collapsed: false })); }; } async Compression(Name, Data, Title) { this.ForceDownload = true; GM_unregisterMenuCommand("Enforce-1"); Data.generateAsync({ type: "blob", compression: "DEFLATE", compressionOptions: { level: 5 } }, progress => { document.title = `${progress.percent.toFixed(1)} %`; this.Button.textContent = `${Lang.Transl("封裝進度")}: ${progress.percent.toFixed(1)} %`; }).then(zip => { saveAs(zip, `${Name}.zip`); document.title = `✓ ${Title}`; this.Button.textContent = Lang.Transl("下載完成"); setTimeout(() => { this.ResetButton(); }, 3e3); }).catch(result => { document.title = Title; const ErrorShow = Lang.Transl("壓縮封裝失敗"); this.Button.textContent = ErrorShow; Syn.Log(ErrorShow, result, { dev: Config.Dev, type: "error", collapsed: false }); setTimeout(() => { this.Button.disabled = false; this.Button.textContent = this.ModeDisplay; }, 6e3); }); } async SeparDownload(FillName, Data) { let show, link, filename, extension, stop = false, progress = 0; const Self = this, Process = [], Promises = [], Total = Data.size, ShowTracking = {}, DownloadTracking = {}, TitleCache = this.OriginalTitle(); const FillValue = this.NameAnalysis(FileName.FillValue), Filler = FillValue[1], Amount = FillValue[0] == "auto" ? Syn.GetFill(Total) : FillValue[0]; async function Stop() { stop = true; Process.forEach(process => process.abort()); } Syn.Menu({ [Lang.Transl("⛔️ 終止下載")]: { func: () => Stop(), hotkey: "s" } }, "Abort"); async function Request(index) { if (stop) return; link = Data.get(index); extension = Syn.ExtensionName(link); filename = Self.isVideo(extension) ? decodeURIComponent(link.split("?f=")[1]) : `${FillName.replace("fill", Syn.Mantissa(index, Amount, Filler))}.${extension}`; return new Promise((resolve, reject) => { const completed = () => { if (!ShowTracking[index]) { ShowTracking[index] = true; Syn.Log("Download Successful", link, { dev: Config.Dev, collapsed: false }); show = `[${++progress}/${Total}]`; document.title = show; Self.Button.textContent = `${Lang.Transl("下載進度")} ${show}`; resolve(); } }; const download = GM_download({ url: link, name: filename, conflictAction: "overwrite", onload: () => { completed(); }, onprogress: progress => { }, onerror: () => { Syn.Log("Download Error", link, { dev: Config.Dev, collapsed: false }); setTimeout(() => { reject(); Request(index); }, 1500); } }); Process.push(download); }); } for (let i = 0; i < Total; i++) { Promises.push(Request(i)); await Syn.Sleep(1e3); } await Promise.allSettled(Promises); GM_unregisterMenuCommand("Abort-1"); document.title = `✓ ${TitleCache}`; this.Button.textContent = Lang.Transl("下載完成"); setTimeout(() => { this.ResetButton(); }, 3e3); } async ResetButton() { Config.CompleteClose && window.close(); lock = false; const Button = Syn.$$("#ExDB button"); Button.disabled = false; Button.textContent = `✓ ${this.ModeDisplay}`; } } class DataToJson { constructor() { this.JsonDict = {}; this.Genmode = true; this.SortMap = new Map(); this.Source = document.URL; this.TitleCache = document.title; this.Section = Syn.$$("section"); this.Pages = this.progress = this.filtercache = null; this.Author = Syn.$$("span[itemprop='name'], fix_name").textContent; this.JsonMode = { orlink: "set_1", imgnb: "set_2", videonb: "set_3", dllink: "set_4" }; this.GenerateBox = (ol, pn, vn, lb) => { if (this.Genmode) { return { ...this.JsonMode.hasOwnProperty("orlink") ? { [Lang.Transl("原始連結")]: ol } : {}, ...this.JsonMode.hasOwnProperty("imgnb") ? { [Lang.Transl("圖片數量")]: pn } : {}, ...this.JsonMode.hasOwnProperty("videonb") ? { [Lang.Transl("影片數量")]: vn } : {}, ...this.JsonMode.hasOwnProperty("dllink") ? { [Lang.Transl("下載連結")]: lb || {} } : {} }; } else { return { ...this.JsonMode.hasOwnProperty("orlink") ? { [Lang.Transl("原始連結")]: ol } : {}, ...this.JsonMode.hasOwnProperty("imgnb") && pn > 0 && vn == 0 ? { [Lang.Transl("圖片數量")]: pn } : {}, ...this.JsonMode.hasOwnProperty("videonb") && vn > 0 && pn <= 10 ? { [Lang.Transl("影片數量")]: vn } : {}, ...this.JsonMode.hasOwnProperty("dllink") && Object.keys(lb).length > 0 ? { [Lang.Transl("下載連結")]: lb } : {} }; } }; this.ToJsonSet = async (mode = "FilterMode", set = []) => { try { switch (mode) { case "FilterMode": this.Genmode = true; set.forEach(key => { delete this.JsonMode[key]; }); break; case "OnlyMode": this.Genmode = false; this.filtercache = Object.keys(this.JsonMode).reduce((obj, key) => { if (set.includes(key)) { obj[key] = this.JsonMode[key]; } return obj; }, {}); this.JsonMode = this.filtercache; break; } } catch (error) { console.error(error); } }; this.MegaAnalysis = data => { let title_box = [], link_box = [], result = {}, pass; for (let i = 0; i < data.length; i++) { const str = data[i].textContent.trim(); if (str.startsWith("Pass")) { const ps = data[i].innerHTML.match(/Pass:([^<]*)/); try { pass = `Pass : ${ps[1].trim()}`; } catch { pass = str; } } else if (str.toUpperCase() == "MEGA") { link_box.push(data[i].parentElement.href); } else { title_box.push(str.replace(":", "").trim()); } } for (let i = 0; i < title_box.length; i++) { result[title_box[i]] = link_box[i]; } return { pass: pass, result: result }; }; this.ToJson = async () => { const Json_data = Object.assign({ ["Meta-Data"]: { [Lang.Transl("作者")]: this.Author, [Lang.Transl("時間")]: Syn.GetDate("{year}-{month}-{date} {hour}:{minute}:{second}"), [Lang.Transl("來源")]: this.Source } }, this.JsonDict); Syn.OutputJson(Json_data, this.Author, () => { if (Config.NotiFication) { GM_notification({ title: Lang.Transl("數據處理完成"), text: Lang.Transl("Json 數據下載"), image: GM_getResourceURL("json-processing"), timeout: 2e3 }); } lock = false; this.worker.terminate(); document.title = this.TitleCache; }); }; this.worker = Syn.WorkerCreation(` let queue = [], processing=false; onmessage = function(e) { queue.push(e.data); !processing && (processing=true, processQueue()); } async function processQueue() { if (queue.length > 0) { const {index, title, url} = queue.shift(); XmlRequest(index, title, url); processQueue(); } else {processing = false} } async function XmlRequest(index, title, url) { let xhr = new XMLHttpRequest(); xhr.responseType = "text"; xhr.open("GET", url, true); xhr.onload = function() { if (xhr.readyState === 4 && xhr.status === 200) { postMessage({ index, title, url, text: xhr.response, error: false }); } else { FetchRequest(index, title, url); } } xhr.onerror = function() { FetchRequest(index, title, url); } xhr.send(); } async function FetchRequest(index, title, url) { fetch(url).then(response => { if (response.ok) { response.text().then(text => { postMessage({ index, title, url, text, error: false }); }); } else { postMessage({ index, title, url, text: "", error: true }); } }) .catch(error => { postMessage({ index, title, url, text: "", error: true }); }); } `); } async GetData() { if (this.Section) { lock = true; this.Pages = 1; for (const page of Syn.$$(".pagination-button-disabled b", { all: true })) { const number = Number(page.textContent); if (number) { this.Pages = number; break; } } this.GetPageData(this.Section); this.DataAnalysis(); } else { alert(Lang.Transl("未取得數據")); } } async GetNextPage(NextPage) { GM_xmlhttpRequest({ method: "GET", url: NextPage, nocache: false, onload: response => { this.GetPageData(Syn.$$("section", { root: response.responseXML })); } }); } async GetPageData(section) { let title, link; const item = Syn.$$(".card-list__items article", { all: true, root: section }); if (Config.NotiFication) { GM_notification({ title: Lang.Transl("數據處理中"), text: `${Lang.Transl("當前處理頁數")} : ${this.Pages}`, image: GM_getResourceURL("json-processing"), timeout: 800 }); } this.progress = 0; for (const [index, card] of item.entries()) { link = Syn.$$("a", { root: card }).href; title = Syn.$$(".post-card__header", { root: card }).textContent.trim() || `Untitled_${String(this.progress + 1).padStart(2, "0")}`; if (Config.ExperimeDownload) { this.worker.postMessage({ index: index, title: title, url: link }); } else { this.JsonDict[`${link}`] = title; } await Syn.Sleep(10); } const menu = Syn.$$("a.pagination-button-after-current", { root: section }); if (Config.ExperimeDownload) { const ILength = item.length, wait = setInterval(() => { if (ILength == this.SortMap.size) { clearInterval(wait); for (let i = 0; i < ILength; i++) { const data = this.SortMap.get(i); this.JsonDict[data.title] = data.box; } this.Pages++; this.SortMap.clear(); menu ? this.GetNextPage(menu.href) : this.ToJson(); } }, 500); } else { this.Pages++; await Syn.Sleep(500); menu ? this.GetNextPage(menu.href) : this.ToJson(); } } async DataAnalysis() { this.worker.onmessage = async e => { const data_box = {}, { index, title, url, text, error } = e.data; if (!error) { const DOM = Syn.DomParse(text); const original_link = url, pictures_number = Syn.$$("post__thumbnail, .scrape__thumbnail", { all: true, root: DOM }).length, video_number = Syn.$$(".post__body li video, .scrape__files video", { all: true, root: DOM }).length, mega_link = Syn.$$(".post__content strong, .scrape__content strong", { all: true, root: DOM }); Syn.$$("a.post__attachment-link, a.scrape__attachment-link", { all: true, root: DOM }).forEach(link => { const analyze = decodeURIComponent(link.href).split("?f="), download_link = analyze[0], download_name = analyze[1]; data_box[download_name] = download_link; }); if (mega_link.length > 0) { try { const { pass, result } = this.MegaAnalysis(mega_link); pass != unSynined ? data_box[pass] = result : null; } catch { } } const box = this.GenerateBox(original_link, pictures_number, video_number, data_box); if (Object.keys(box).length !== 0) { this.SortMap.set(index, { title: title, box: box }); } Syn.Log("Request Successful", this.SortMap, { dev: Config.Dev, collapsed: false }); document.title = `(${this.Pages} - ${++this.progress})`; } else { Syn.Log("Request Failed", { title: title, url: url }, { dev: Config.Dev, collapsed: false }); await Syn.Sleep(1500); this.worker.postMessage({ index: index, title: title, url: url }); } }; } } new class Main { constructor() { this.URL = Syn.Device.Url; this.Page = { Content: /^(https?:\/\/)?(www\.)?.+\/.+\/user\/.+\/post\/.+$/.test(this.URL), Preview: /^(https?:\/\/)?(www\.)?.+\/posts\/?(\?.*)?$/.test(this.URL) || /^(https?:\/\/)?(www\.)?.+\/.+\/user\/[^\/]+(\?.*)?$/.test(this.URL) || /^(https?:\/\/)?(www\.)?.+\/dms\/?(\?.*)?$/.test(this.URL) }; this.AddStyle = async () => { Syn.AddStyle(` ${GM_getResourceText("font-awesome")} .File_Span { padding: 1rem; font-size: 20% !important; } .Setting_Button { cursor: pointer; } .Download_Button { color: hsl(0, 0%, 45%); padding: 6px; margin: 10px; 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: Synault; } `, "Download-button-style", false); }; GM_info.downloadMode = "browser"; GM_info.isIncognito = true; } async ButtonCreation() { Syn.$$("section").setAttribute("Download-Button-Created", true); this.AddStyle(); let Button, Files; const IntervalFind = setInterval(() => { Files = Syn.$$(IsNeko ? "div.scrape__body h2" : "div.post__body h2", { all: true }); if (Files.length > 0) { clearInterval(IntervalFind); try { const CompressMode = Syn.Storage("Compression", { type: localStorage, error: true }); const ModeDisplay = CompressMode ? Lang.Transl("壓縮下載") : Lang.Transl("單圖下載"); Files = Array.from(Files).filter(file => file.textContent.trim() == "Files"); if (Files.length == 0) { return; } const spanElement = GM_addElement(Files[0], "span", { class: "File_Span", id: "ExDB" }); const setting = GM_addElement(spanElement, "svg", { class: "Setting_Button" }); setting.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" height="1.3rem" viewBox="0 0 512 512"><style>svg {fill: hsl(0, 0%, 45%);}</style> <path d="M495.9 166.6c3.2 8.7 .5 18.4-6.4 24.6l-43.3 39.4c1.1 8.3 1.7 16.8 1.7 25.4s-.6 17.1-1.7 25.4l43.3 39.4c6.9 6.2 9.6 15.9 6.4 24.6c-4.4 11.9-9.7 23.3-15.8 34.3l-4.7 8.1c-6.6 11-14 21.4-22.1 31.2c-5.9 7.2-15.7 9.6-24.5 6.8l-55.7-17.7c-13.4 10.3-28.2 18.9-44 25.4l-12.5 57.1c-2 9.1-9 16.3-18.2 17.8c-13.8 2.3-28 3.5-42.5 3.5s-28.7-1.2-42.5-3.5c-9.2-1.5-16.2-8.7-18.2-17.8l-12.5-57.1c-15.8-6.5-30.6-15.1-44-25.4L83.1 425.9c-8.8 2.8-18.6 .3-24.5-6.8c-8.1-9.8-15.5-20.2-22.1-31.2l-4.7-8.1c-6.1-11-11.4-22.4-15.8-34.3c-3.2-8.7-.5-18.4 6.4-24.6l43.3-39.4C64.6 273.1 64 264.6 64 256s.6-17.1 1.7-25.4L22.4 191.2c-6.9-6.2-9.6-15.9-6.4-24.6c4.4-11.9 9.7-23.3 15.8-34.3l4.7-8.1c6.6-11 14-21.4 22.1-31.2c5.9-7.2 15.7-9.6 24.5-6.8l55.7 17.7c13.4-10.3 28.2-18.9 44-25.4l12.5-57.1c2-9.1 9-16.3 18.2-17.8C227.3 1.2 241.5 0 256 0s28.7 1.2 42.5 3.5c9.2 1.5 16.2 8.7 18.2 17.8l12.5 57.1c15.8 6.5 30.6 15.1 44 25.4l55.7-17.7c8.8-2.8 18.6-.3 24.5 6.8c8.1 9.8 15.5 20.2 22.1 31.2l4.7 8.1c6.1 11 11.4 22.4 15.8 34.3zM256 336a80 80 0 1 0 0-160 80 80 0 1 0 0 160z"/></svg>`; Syn.Listen(setting, "click", () => { alert("Currently Invalid"); }, { capture: true, passive: true }); Button = GM_addElement(spanElement, "button", { class: "Download_Button", textContent: lock ? Lang.Transl("下載中鎖定") : ModeDisplay }); Button.disabled = lock; Syn.Listen(Button, "click", () => { let Instantiate = null; Instantiate = new Download(CompressMode, ModeDisplay, Button); Instantiate.DownloadTrigger(); }, { capture: true, passive: true }); } catch { Button.disabled = true; Button.textContent = Lang.Transl("無法下載"); } } }); } async OpenAllPages() { const card = Syn.$$("article.post-card a", { all: true }); if (card.length == 0) { throw new Error("No links found"); } let scope = prompt(`(${Lang.Transl("當前帖子數")}: ${card.length})${Lang.Transl("開帖說明")}`); if (scope != null) { scope = scope == "" ? "1-50" : scope; for (const link of Syn.ScopeParsing(scope, card)) { GM_openInTab(link.href, { insert: false, setParent: false }); await Syn.Sleep(Config.BatchOpenDelay); } } } async DownloadModeSwitch() { if (Syn.Storage("Compression", { type: localStorage, error: true })) { Syn.Storage("Compression", { type: localStorage, value: false }); if (Config.NotiFication) { GM_notification({ title: Lang.Transl("模式切換"), text: Lang.Transl("單圖下載模式"), timeout: 1500 }); } } else { Syn.Storage("Compression", { type: localStorage, value: true }); if (Config.NotiFication) { GM_notification({ title: Lang.Transl("模式切換"), text: Lang.Transl("壓縮下載模式"), timeout: 1500 }); } } Syn.$$("#ExDB").remove(); this.ButtonCreation(); } async Injection() { Syn.Observer(document, () => { try { this.Page.Content && !Syn.$$("section").hasAttribute("Download-Button-Created") && this.ButtonCreation(); } catch { } }, { throttle: 300 }); if (this.Page.Content) { Syn.Menu({ [Lang.Transl("🔁 切換下載模式")]: { func: () => this.DownloadModeSwitch(), close: false, hotkey: "c" } }); } else if (this.Page.Preview) { Syn.Menu({ [Lang.Transl("📑 獲取 Json 數據")]: { func: () => { if (!lock) { let Instantiate = null; Instantiate = new DataToJson(); JsonFormat.Use && Instantiate.ToJsonSet(JsonFormat.Mode, JsonFormat.Settings); Instantiate.GetData(); } } }, [Lang.Transl("📃 開啟當前頁面帖子")]: { func: () => this.OpenAllPages() } }); } } }().Injection(); function Language(lang) { const Word = { Traditional: { "開帖說明": "\n\n!! 不輸入直接確認, 將會開啟當前頁面所有帖子\n輸入開啟範圍(說明) =>\n單個: 1, 2, 3\n範圍: 1~5, 6-10\n排除: !5, -10" }, Simplified: { "🔁 切換下載模式": "🔁 切换下载模式", "📑 獲取 Json 數據": "📑 获取 Json 数据", "📃 開啟當前頁面帖子": "📃 打开当前页面帖子", "📥 強制壓縮下載": "📥 强制压缩下载", "⛔️ 終止下載": "⛔️ 终止下载", "壓縮下載模式": "压缩下载模式", "單圖下載模式": "单图下载模式", "壓縮下載": "压缩下载", "單圖下載": "单图下载", "開始下載": "开始下载", "無法下載": "无法下载", "下載進度": "下载进度", "封裝進度": "打包进度", "壓縮封裝失敗": "压缩打包失败", "下載完成": "下载完成", "請求進度": "请求进度", "下載中鎖定": "下载中锁定", "原始連結": "原始链接", "圖片數量": "图片数量", "影片數量": "视频数量", "下載連結": "下载链接", "作者": "作者", "時間": "时间", "來源": "来源", "未取得數據": "未取得数据", "模式切換": "模式切换", "數據處理中": "数据处理中", "當前處理頁數": "当前处理页数", "數據處理完成": "数据处理完成", "Json 數據下載": "Json 数据下载", "當前帖子數": "当前帖子数", "開帖說明": "\n\n!! 不输入直接确认, 将会打开当前页面所有帖子\n输入开启范围(说明) =>\n单个: 1, 2, 3\n范围: 1~5, 6-10\n排除: !5, -10" }, Japan: { "🔁 切換下載模式": "🔁 ダウンロードモードの切り替え", "📑 獲取 Json 數據": "📑 Json データの取得", "📃 開啟當前頁面帖子": "📃 現在のページの投稿を開く", "📥 強制壓縮下載": "📥 強制的に圧縮してダウンロード", "⛔️ 終止下載": "⛔️ ダウンロードを中止", "壓縮下載模式": "圧縮ダウンロードモード", "單圖下載模式": "単一画像ダウンロードモード", "壓縮下載": "圧縮ダウンロード", "單圖下載": "単一画像ダウンロード", "開始下載": "ダウンロードを開始", "無法下載": "ダウンロードできません", "下載進度": "ダウンロードの進行状況", "封裝進度": "パッケージングの進行状況", "壓縮封裝失敗": "圧縮パッケージングに失敗しました", "下載完成": "ダウンロードが完了しました", "請求進度": "リクエストの進行状況", "下載中鎖定": "ダウンロード中にロック", "原始連結": "元のリンク", "圖片數量": "画像の数", "影片數量": "動画の数", "下載連結": "ダウンロードリンク", "作者": "著者", "時間": "時間", "來源": "ソース", "未取得數據": "データを取得できませんでした", "模式切換": "モードの切り替え", "數據處理中": "データ処理中", "當前處理頁數": "現在処理中のページ数", "數據處理完成": "データ処理が完了しました", "Json 數據下載": "Json データのダウンロード", "當前帖子數": "現在の投稿数", "開帖說明": "\n\n!! 直接確認を入力しないと、現在のページのすべての投稿が開きます\n開始範囲を入力してください (説明) =>\n単一: 1, 2, 3\n範囲: 1~5, 6-10\n除外: !5, -10" }, English: { "🔁 切換下載模式": "🔁 Switch Download Mode", "📑 獲取 Json 數據": "📑 Get Json Data", "📃 開啟當前頁面帖子": "📃 Open Current Page Post", "📥 強制壓縮下載": "📥 Force Compress Download", "⛔️ 終止下載": "⛔️ Terminate download", "壓縮下載模式": "Compress Download Mode", "單圖下載模式": "Single Image Download Mode", "壓縮下載": "Compress Download", "單圖下載": "Single Image Download", "開始下載": "Start Download", "無法下載": "Unable to Download", "下載進度": "Download Progress", "封裝進度": "Packaging Progress", "壓縮封裝失敗": "Compress Packaging Failed", "下載完成": "Download Complete", "請求進度": "Request Progress", "下載中鎖定": "Download Locked", "原始連結": "Original Link", "圖片數量": "Image Count", "影片數量": "Video Count", "下載連結": "Download Link", "作者": "Author", "時間": "Time", "來源": "Source", "未取得數據": "No Data", "模式切換": "Mode Switch", "數據處理中": "Data Processing", "當前處理頁數": "Current Processing Page", "數據處理完成": "Data Processing Complete", "Json 數據下載": "Json Data Download", "當前帖子數": "Current Post Count", "開帖說明": "\n\n!! If you do not enter a direct confirmation, all posts on the current page will be opened\nEnter the start range (說明) =>\nSingle: 1, 2, 3\nRange: 1~5, 6-10\nExclude: !5, -10" } }, Match = { "zh-TW": Word.Traditional, "zh-HK": Word.Traditional, "zh-MO": Word.Traditional, "zh-CN": Word.Simplified, "zh-SG": Word.Simplified, "en-US": Word.English, ja: Word.Japan }, ML = Match[lang] ?? Match["en-US"]; return { Transl: Str => ML[Str] ?? Str }; } })();