您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
从Poipiku下载图片或文字
// ==UserScript== // @name Poipiku Downloader // @name:zh-CN Poipiku下载器 // @name:ja ポイピク ダウンローダー // @description Download images or text from Poipiku // @description:zh-CN 从Poipiku下载图片或文字 // @description:ja Download images or text from Poipiku // @author calary // @namespace http://tampermonkey.net/ // @version 0.4.4 // @license GPL-3.0 // @include http*://poipiku.com* // @match https://poipiku.com/ // @connect img.poipiku.com // @connect img-org.poipiku.com // @icon https://poipiku.com/favicon.ico // @require https://cdn.bootcdn.net/ajax/libs/jquery/2.2.4/jquery.min.js // @require https://cdn.bootcdn.net/ajax/libs/jszip/3.7.1/jszip.min.js // @require https://cdn.bootcdn.net/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js // @grant GM.xmlHttpRequest // @grant GM_xmlhttpRequest // @run-at document-end // ==/UserScript== jQuery(function ($) { const lang = ( window.navigator.language || window.navigator.browserLanguage || "en-us" ).toLowerCase(); const i18nMap = { "en-us": { ui_logined: "Logined", ui_password: "Password", ui_qualitytrue: "You can download high quality images.", ui_qualityfalse: "You cannot download high quality images.", ui_mode: "Rename image with page id", btn_downloadimages: "Save images (.zip)", btn_downloadimageseperately: "Save images Seperately", btn_downloadtext: "Save text (.txt)", error_default: "Something went wrong", error_fetch: "Fetch content error. Entered wrong password?", error_noimage: "No Images", error_zip: "Failed to create zip. Please try to save images seperagtely.", txt_title: "Title: ", txt_author: "Author: ", txt_twitter: "Twitter: ", txt_link: "Link: ", }, "zh-cn": { ui_logined: "登录状态", ui_password: "密码", ui_qualitytrue: "可以下载高质量图片。", ui_qualityfalse: "不能下载高质量图片。", ui_mode: "图片命名包含当页ID", btn_downloadimages: "图片打包为(.zip)", btn_downloadimageseperately: "独立保存图片", btn_downloadtext: "保存文字为(.txt)", error_default: "出错了", error_fetch: "请求失败。是否输入密码有误?", error_noimage: "没有图片", error_zip: "打包失败,请尝试独立保存", txt_title: "标题:", txt_author: "作者:", txt_twitter: "推特:", txt_link: "地址:", }, ja: { ui_logined: "ログイン", ui_password: "パスワード", ui_qualitytrue: "高品質の画像を保存できます。", ui_qualityfalse: "高品質の画像を保存することはできません。", ui_mode: "IDをファイル名に入れます", btn_downloadimages: "画像を保存(.zip)", btn_downloadimageseperately: "画像を個別に保存", btn_downloadtext: "テキストを保存(.txt)", error_default: "問題が発生しました", error_fetch: "コンテンツの取得エラー。間違ったパスワードを入力しましたか?", error_zip: "ZIPの作成に失敗しました。 画像を個別に保存してみてください。", error_noimage: "画像なし", txt_title: "タイトル:", txt_author: "ユーザー:", txt_twitter: "Twitter:", txt_link: "URL:", }, }; const i18n = (key) => (i18nMap[lang] && i18nMap[lang][key]) || i18nMap["en-us"][key]; const website = "poipiku"; const logined = $(".LoginButton").length === 0; const fontFamily = "Arial, 'Microsoft Yahei', Helvetica, sans-serif"; class PageInfo { authorId = ""; workId = ""; title = ""; author = ""; twiter = ""; saveFilename = ""; isText = false; hasPassword = false; constructor(url) { this.url = url; this.saveImages = this.saveImages.bind(this); this.saveText = this.saveText.bind(this); this.downloadImages = this.downloadImages.bind(this); this.downloadImagesAsZip = this.downloadImagesAsZip.bind(this); this.downloadImagesSeperately = this.downloadImagesSeperately.bind(this); this.downloadText = this.downloadText.bind(this); this.downloadAppendPage = this.downloadAppendPage.bind(this); this.init(); } init() { if (this.initPromise) { return this.initPromise; } const url = this.url; const execResult = /\/(\d+)\/(\d+)/.exec(url); const authorId = execResult && execResult[1]; const workId = execResult && execResult[2]; this.authorId = authorId; this.workId = workId; let promise; if (this.url === window.location.href) { promise = Promise.resolve(document.body.innerHTML); } else { promise = request({ url: url }); } this.initPromise = promise.then((payload) => this.load(payload)); return this.initPromise; } load(payload) { let html = payload; html = html.replace(/^.+<body>/, ""); html = html.replace(/<\/body>.+$/, ""); const $html = $(`<div>${html}</div>`); const twitter = $html.find(".UserInfoProgile a").html(); const username = $html.find(".UserInfoUserName a").html(); const username2 = (twitter && (twitter.charAt(0) === "@" ? twitter.substring(1) : twitter.split("/").slice(-1).join(""))) || username; const desc = $html.find(".IllustItemDesc").text().substring(0, 20); this.saveFilename = filterFilename( `[${username2}][${website}][${this.authorId}_${this.workId}]${desc}` ); this.title = $html.find(".IllustItemDesc").text(); this.author = $html.find(".UserInfoUserName a").html(); this.twitter = $html.find(".UserInfoProgile a").prop("href"); this.isText = $html.find(".IllustItem").hasClass("Text"); this.hasPassword = $html.find(".IllustItem").hasClass("Password"); this.existingHtml = $html.find(".IllustItemThumb").eq(0).prop("outerHTML") + $html.find(".IllustItemText").eq(0).prop("outerHTML"); } // 生成保存文件名 getSaveFilename() { return this.saveFilename; } // 生成保存图片文件名 // 默认:序号.后缀名 // 选中:网站_作品id_序号.后缀名 getSaveImageFilename(src, index) { let suffix = src.split(".").splice(-1); const mode = $saveFileMode.is(":checked"); if (mode) { return `${website}_${this.workId}_${index + 1}.${suffix}`; } return `${index + 1}.${suffix}`; } // 批量下载图片的默认方法 saveImages(list, saveAsZip, $status) { let finishehCount = 0; let zip; let folder; if (saveAsZip) { try { zip = new JSZip(); folder = zip.folder(this.saveFilename); } catch (e) { alert(e); } } $status = $status || $("<div></div>"); $status.text(`0/${list.length}`); let promises = list.map((src, index) => { return getBlob(src).then((blob) => { finishehCount++; $status.text(`${finishehCount}/${list.length}`); if (zip) { folder.file(this.getSaveImageFilename(src, index), blob, { binary: true, }); } else { let suffix = src.split(".").splice(-1); saveAs( new Blob([blob]), `${this.saveFilename}_${index + 1}.${suffix}` ); } }); }); Promise.all(promises) .then(() => { if (zip) { return zip .generateAsync({ type: "blob", base64: true }) .then((content) => saveAs(content, this.saveFilename + ".zip")); } }) .catch((e) => { alert(i18n("error_zip")); }); } // 保存文字的默认方法 saveText(option) { let str = ""; if (option.title) { str += `${i18n("txt_title")}${option.title}\n`; } if (option.author) { str += `${i18n("txt_author")}${option.author}\n`; } if (option.twitter) { str += `${i18n("txt_twitter")}${option.twitter}\n`; } str += `${i18n("txt_link")}${window.location.href}\n`; str += `\n\n`; str += option.content; saveAs( new Blob([str], { type: "text/plain;charset=UTF-8" }), this.saveFilename + ".txt" ); } // 下载图片 downloadImages(saveAsZip, $status) { this.init() .then(this.downloadAppendPage) .then(($page) => { if (logined) { return request({ url: "/f/ShowIllustDetailF.jsp", type: "POST", data: { ID: this.authorId, TD: this.workId, AD: "-1", PAS: $password.val(), }, dataType: "json", }).then((payload) => { if (!payload.html) { throw new Error(i18n("error_fetch")); } return $(payload.html); }); } return $page; }) .then(($page) => { let list = []; $page .find(logined ? ".DetailIllustItemImage" : ".IllustItemThumbImg") .each(function () { const src = $(this).attr("src"); if (src && !/^\/img/.test(src)) { list.push(window.location.protocol + src); } }); if (list.length) { this.saveImages(list, saveAsZip, $status); } else { throw new Error(i18n("error_noimage")); } }) .catch((e) => { alert(e.message || i18n("error_default")); }); } // 打包图片 downloadImagesAsZip($btn) { this.downloadImages(true, $btn && $btn.find(".status")); } // 独立下载图片 downloadImagesSeperately($btn) { this.downloadImages(false, $btn && $btn.find(".status")); } // 下载文字 downloadText() { this.init() .then(this.downloadAppendPage) .then(($page) => { this.saveText({ title: this.title, author: this.author, twitter: this.twitter, content: $page.find(".NovelSection").text(), }); }) .catch((e) => { alert(e.message || i18n("error_default")); }); } downloadAppendPage() { return request({ url: "/f/ShowAppendFileF.jsp", type: "POST", data: { UID: this.authorId, IID: this.workId, PAS: $password.val(), MD: 0, TWF: -1, }, dataType: "json", }).then((payload) => { if (payload.result_num < 0) { throw new Error(payload.html); } return $(`<div>${this.existingHtml}${payload.html}</div>`); }); } } const pageInfo = new PageInfo(window.location.href); $(".IllustThumb").each(function () { const $this = $(this); const isText = /文字/.test($this.find(".Num").text()); const hasPassword = /pass\.png/.test($this.find(".IllustThumbImg").css("background-image")) || /pass\.png/.test($this.find(".Publish").css("background-image")); if (hasPassword) { return; } if (isText) { $(`<button>${i18n("btn_downloadtext")}</button>`) .on("click", downloadTextFromList) .css({ position: "absolute", left: 4, top: 110, zIndex: 1, fontFamily: fontFamily, }) .appendTo($this); } else { $( `<button>${i18n( "btn_downloadimageseperately" )} <b class='status'></b></button>` ) .on("click", downloadImagesSeperatelyFromList) .css({ position: "absolute", left: 4, top: 110, zIndex: 1, fontFamily: fontFamily, }) .appendTo($this); $( `<button>${i18n("btn_downloadimages")} <b class='status'></b></button>` ) .on("click", downloadImagesAsZipFromList) .css({ position: "absolute", left: 4, top: 140, zIndex: 1, fontFamily: fontFamily, }) .appendTo($this); } }); const $panel = $(`<div> <div>${i18n("ui_logined")}: <b style="color:red">${logined}</b>.</div> <div class="line-qualitytip" >${ logined ? i18n("ui_qualitytrue") : i18n("ui_qualityfalse") }</div> <div class="line-password">${i18n( "ui_password" )} <input type='text' class="password"></div> <div class="line-mode" >${i18n( "ui_mode" )} <input type='checkbox' class="saveFileMode"></div> <div class="line-images"> <button class="btn-downloadImagesSeperately" style="font-size:20px">${i18n( "btn_downloadimageseperately" )} <b class='status'></b></button></button><br> <button class="btn-downloadImages" style="font-size:20px">${i18n( "btn_downloadimages" )} <b class='status'></b></button> </div> <div class="line-text"><button class="btn-downloadText" style="font-size:20px">${i18n( "btn_downloadtext" )}</button></div> </div>`) .css({ position: "fixed", left: 0, bottom: 50, zIndex: 999999, background: "#fff", color: "#333", fontSize: 18, fontFamily: fontFamily, padding: 10, }) .appendTo($("body")); const $password = $panel.find(".password"); const $saveFileMode = $panel.find(".saveFileMode"); $panel.find("button").css({ fontFamily: fontFamily, }); pageInfo.init().then(function () { if (!pageInfo.workId) { $panel.find(".line-password").hide(); $panel.find(".line-images").hide(); $panel.find(".line-mode").hide(); $panel.find(".line-text").hide(); return; } if (!pageInfo.hasPassword) { $panel.find(".line-password").hide(); } if (pageInfo.isText) { $panel.find(".line-images").hide(); $panel.find(".line-qualitytip").hide(); $panel.find(".line-mode").hide(); } else { $panel.find(".line-text").hide(); } $panel.find(".btn-downloadImages").on("click", function () { pageInfo.downloadImagesAsZip($(this)); }); $panel.find(".btn-downloadImagesSeperately").on("click", function () { pageInfo.downloadImagesSeperately($(this)); }); $panel.find(".btn-downloadText").on("click", function () { pageInfo.downloadText($(this)); }); }); function request(config) { return new Promise((resolve, reject) => { $.ajax({ ...config, success: (response) => { resolve(response); }, error: () => { reject(new Error(i18n("error_default"))); }, }); }); } function getMimeType(suffix) { let map = { png: "image/png", jpg: "image/jpeg", jpeg: "image/jpeg", gif: "image/gif", }; return map[suffix] || "text/plain"; } function getBlob(url) { // return fetch(url).then((response) => response.blob()); return new Promise((resolve, reject) => { GM.xmlHttpRequest({ method: "GET", url: url, responseType: "blob", headers: { referer: window.location.href }, onload: (payload) => { resolve(payload.response); }, onerror: () => { reject(new Error(i18n("error_default"))); }, }); }); // return new Promise((resolve, reject) => { // GM.xmlHttpRequest({ // method: "GET", // url: url, // overrideMimeType: "text/plain; charset=x-user-defined", // headers: { referer: window.location.href }, // onload: (xhr) => { // let r = xhr.responseText; // let data = new Uint8Array(r.length); // let i = 0; // while (i < r.length) { // data[i] = r.charCodeAt(i); // i++; // } // let suffix = url.split(".").splice(-1); // let blob = new Blob([data], { type: getMimeType(suffix) }); // resolve(blob); // }, // onerror: () => { // reject(new Error(i18n("error_default"))); // }, // }); // }); } // 过滤文件名非法字符 function filterFilename(filename) { return filename.replace(/\?|\*|\:|\"|\<|\>|\\|\/|\|/g, ""); } function getPageInfo($btn) { const url = $btn.siblings(".IllustThumbImg").prop("href"); return new PageInfo(url); } function downloadImagesAsZipFromList() { const $this = $(this); const pageInfo = getPageInfo($this); pageInfo.init().then(() => { pageInfo.downloadImagesAsZip($this); }); } function downloadImagesSeperatelyFromList() { const $this = $(this); const pageInfo = getPageInfo($this); pageInfo.init().then(() => { pageInfo.downloadImagesSeperately($this); }); } function downloadTextFromList() { const $this = $(this); const pageInfo = getPageInfo($this); pageInfo.init().then(() => { pageInfo.downloadText($this); }); } });