- // ==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.3
- // @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.substring(1) : 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));
- }
- })
- .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);
- });
- }
- });