Poipiku下载器

从Poipiku下载图片或文字

  1. // ==UserScript==
  2. // @name Poipiku Downloader
  3. // @name:zh-CN Poipiku下载器
  4. // @name:ja ポイピク ダウンローダー
  5. // @description Download images or text from Poipiku
  6. // @description:zh-CN 从Poipiku下载图片或文字
  7. // @description:ja Download images or text from Poipiku
  8. // @author calary
  9. // @namespace http://tampermonkey.net/
  10. // @version 0.4.4
  11. // @license GPL-3.0
  12. // @include http*://poipiku.com*
  13. // @match https://poipiku.com/
  14. // @connect img.poipiku.com
  15. // @connect img-org.poipiku.com
  16. // @icon https://poipiku.com/favicon.ico
  17. // @require https://cdn.bootcdn.net/ajax/libs/jquery/2.2.4/jquery.min.js
  18. // @require https://cdn.bootcdn.net/ajax/libs/jszip/3.7.1/jszip.min.js
  19. // @require https://cdn.bootcdn.net/ajax/libs/FileSaver.js/2.0.5/FileSaver.min.js
  20. // @grant GM.xmlHttpRequest
  21. // @grant GM_xmlhttpRequest
  22. // @run-at document-end
  23. // ==/UserScript==
  24.  
  25. jQuery(function ($) {
  26. const lang = (
  27. window.navigator.language ||
  28. window.navigator.browserLanguage ||
  29. "en-us"
  30. ).toLowerCase();
  31.  
  32. const i18nMap = {
  33. "en-us": {
  34. ui_logined: "Logined",
  35. ui_password: "Password",
  36. ui_qualitytrue: "You can download high quality images.",
  37. ui_qualityfalse: "You cannot download high quality images.",
  38. ui_mode: "Rename image with page id",
  39. btn_downloadimages: "Save images (.zip)",
  40. btn_downloadimageseperately: "Save images Seperately",
  41. btn_downloadtext: "Save text (.txt)",
  42. error_default: "Something went wrong",
  43. error_fetch: "Fetch content error. Entered wrong password?",
  44. error_noimage: "No Images",
  45. error_zip: "Failed to create zip. Please try to save images seperagtely.",
  46. txt_title: "Title: ",
  47. txt_author: "Author: ",
  48. txt_twitter: "Twitter: ",
  49. txt_link: "Link: ",
  50. },
  51. "zh-cn": {
  52. ui_logined: "登录(不可用)状态",
  53. ui_password: "密码",
  54. ui_qualitytrue: "可以下载高质量图片。",
  55. ui_qualityfalse: "不能下载高质量图片。",
  56. ui_mode: "图片命名包含当页ID",
  57. btn_downloadimages: "图片打包为(.zip)",
  58. btn_downloadimageseperately: "独立保存图片",
  59. btn_downloadtext: "保存文字为(.txt)",
  60. error_default: "出错了",
  61. error_fetch: "请求失败。是否输入密码有误?",
  62. error_noimage: "没有图片",
  63. error_zip: "打包失败,请尝试独立保存",
  64. txt_title: "标题:",
  65. txt_author: "作者:",
  66. txt_twitter: "推特:",
  67. txt_link: "地址:",
  68. },
  69. ja: {
  70. ui_logined: "ログイン",
  71. ui_password: "パスワード",
  72. ui_qualitytrue: "高品質の画像を保存できます。",
  73. ui_qualityfalse: "高品質の画像を保存することはできません。",
  74. ui_mode: "IDをファイル名に入れます",
  75. btn_downloadimages: "画像を保存(.zip)",
  76. btn_downloadimageseperately: "画像を個別に保存",
  77. btn_downloadtext: "テキストを保存(.txt)",
  78. error_default: "問題が発生しました",
  79. error_fetch:
  80. "コンテンツの取得エラー。間違ったパスワードを入力しましたか?",
  81. error_zip: "ZIPの作成に失敗しました。 画像を個別に保存してみてください。",
  82. error_noimage: "画像なし",
  83. txt_title: "タイトル:",
  84. txt_author: "ユーザー:",
  85. txt_twitter: "Twitter:",
  86. txt_link: "URL:",
  87. },
  88. };
  89. const i18n = (key) =>
  90. (i18nMap[lang] && i18nMap[lang][key]) || i18nMap["en-us"][key];
  91.  
  92. const website = "poipiku";
  93. const logined = $(".LoginButton").length === 0;
  94. const fontFamily = "Arial, 'Microsoft Yahei', Helvetica, sans-serif";
  95.  
  96. class PageInfo {
  97. authorId = "";
  98. workId = "";
  99. title = "";
  100. author = "";
  101. twiter = "";
  102. saveFilename = "";
  103. isText = false;
  104. hasPassword = false;
  105.  
  106. constructor(url) {
  107. this.url = url;
  108. this.saveImages = this.saveImages.bind(this);
  109. this.saveText = this.saveText.bind(this);
  110. this.downloadImages = this.downloadImages.bind(this);
  111. this.downloadImagesAsZip = this.downloadImagesAsZip.bind(this);
  112. this.downloadImagesSeperately = this.downloadImagesSeperately.bind(this);
  113. this.downloadText = this.downloadText.bind(this);
  114. this.downloadAppendPage = this.downloadAppendPage.bind(this);
  115. this.init();
  116. }
  117.  
  118. init() {
  119. if (this.initPromise) {
  120. return this.initPromise;
  121. }
  122.  
  123. const url = this.url;
  124. const execResult = /\/(\d+)\/(\d+)/.exec(url);
  125. const authorId = execResult && execResult[1];
  126. const workId = execResult && execResult[2];
  127.  
  128. this.authorId = authorId;
  129. this.workId = workId;
  130.  
  131. let promise;
  132.  
  133. if (this.url === window.location.href) {
  134. promise = Promise.resolve(document.body.innerHTML);
  135. } else {
  136. promise = request({ url: url });
  137. }
  138.  
  139. this.initPromise = promise.then((payload) => this.load(payload));
  140. return this.initPromise;
  141. }
  142.  
  143. load(payload) {
  144. let html = payload;
  145. html = html.replace(/^.+<body>/, "");
  146. html = html.replace(/<\/body>.+$/, "");
  147.  
  148. const $html = $(`<div>${html}</div>`);
  149. const twitter = $html.find(".UserInfoProgile a").html();
  150. const username = $html.find(".UserInfoUserName a").html();
  151. const username2 =
  152. (twitter &&
  153. (twitter.charAt(0) === "@"
  154. ? twitter.substring(1)
  155. : twitter.split("/").slice(-1).join(""))) ||
  156. username;
  157. const desc = $html.find(".IllustItemDesc").text().substring(0, 20);
  158.  
  159. this.saveFilename = filterFilename(
  160. `[${username2}][${website}][${this.authorId}_${this.workId}]${desc}`
  161. );
  162. this.title = $html.find(".IllustItemDesc").text();
  163. this.author = $html.find(".UserInfoUserName a").html();
  164. this.twitter = $html.find(".UserInfoProgile a").prop("href");
  165.  
  166. this.isText = $html.find(".IllustItem").hasClass("Text");
  167. this.hasPassword = $html.find(".IllustItem").hasClass("Password");
  168.  
  169. this.existingHtml =
  170. $html.find(".IllustItemThumb").eq(0).prop("outerHTML") +
  171. $html.find(".IllustItemText").eq(0).prop("outerHTML");
  172. }
  173.  
  174. // 生成保存文件名
  175. getSaveFilename() {
  176. return this.saveFilename;
  177. }
  178.  
  179. // 生成保存图片文件名
  180. // 默认:序号.后缀名
  181. // 选中:网站_作品id_序号.后缀名
  182. getSaveImageFilename(src, index) {
  183. let suffix = src.split(".").splice(-1);
  184. const mode = $saveFileMode.is(":checked");
  185.  
  186. if (mode) {
  187. return `${website}_${this.workId}_${index + 1}.${suffix}`;
  188. }
  189.  
  190. return `${index + 1}.${suffix}`;
  191. }
  192.  
  193. // 批量下载图片的默认方法
  194. saveImages(list, saveAsZip, $status) {
  195. let finishehCount = 0;
  196. let zip;
  197. let folder;
  198.  
  199. if (saveAsZip) {
  200. try {
  201. zip = new JSZip();
  202. folder = zip.folder(this.saveFilename);
  203. } catch (e) {
  204. alert(e);
  205. }
  206. }
  207.  
  208. $status = $status || $("<div></div>");
  209. $status.text(`0/${list.length}`);
  210.  
  211. let promises = list.map((src, index) => {
  212. return getBlob(src).then((blob) => {
  213. finishehCount++;
  214. $status.text(`${finishehCount}/${list.length}`);
  215.  
  216. if (zip) {
  217. folder.file(this.getSaveImageFilename(src, index), blob, {
  218. binary: true,
  219. });
  220. } else {
  221. let suffix = src.split(".").splice(-1);
  222. saveAs(
  223. new Blob([blob]),
  224. `${this.saveFilename}_${index + 1}.${suffix}`
  225. );
  226. }
  227. });
  228. });
  229.  
  230. Promise.all(promises)
  231. .then(() => {
  232. if (zip) {
  233. return zip
  234. .generateAsync({ type: "blob", base64: true })
  235. .then((content) => saveAs(content, this.saveFilename + ".zip"));
  236. }
  237. })
  238. .catch((e) => {
  239. alert(i18n("error_zip"));
  240. });
  241. }
  242.  
  243. // 保存文字的默认方法
  244. saveText(option) {
  245. let str = "";
  246.  
  247. if (option.title) {
  248. str += `${i18n("txt_title")}${option.title}\n`;
  249. }
  250. if (option.author) {
  251. str += `${i18n("txt_author")}${option.author}\n`;
  252. }
  253. if (option.twitter) {
  254. str += `${i18n("txt_twitter")}${option.twitter}\n`;
  255. }
  256. str += `${i18n("txt_link")}${window.location.href}\n`;
  257. str += `\n\n`;
  258. str += option.content;
  259.  
  260. saveAs(
  261. new Blob([str], { type: "text/plain;charset=UTF-8" }),
  262. this.saveFilename + ".txt"
  263. );
  264. }
  265.  
  266. // 下载图片
  267. downloadImages(saveAsZip, $status) {
  268. this.init()
  269. .then(this.downloadAppendPage)
  270. .then(($page) => {
  271. if (logined) {
  272. return request({
  273. url: "/f/ShowIllustDetailF.jsp",
  274. type: "POST",
  275. data: {
  276. ID: this.authorId,
  277. TD: this.workId,
  278. AD: "-1",
  279. PAS: $password.val(),
  280. },
  281. dataType: "json",
  282. }).then((payload) => {
  283. if (!payload.html) {
  284. throw new Error(i18n("error_fetch"));
  285. }
  286. return $(payload.html);
  287. });
  288. }
  289.  
  290. return $page;
  291. })
  292. .then(($page) => {
  293. let list = [];
  294.  
  295. $page
  296. .find(logined ? ".DetailIllustItemImage" : ".IllustItemThumbImg")
  297. .each(function () {
  298. const src = $(this).attr("src");
  299.  
  300. if (src && !/^\/img/.test(src)) {
  301. list.push(window.location.protocol + src);
  302. }
  303. });
  304.  
  305. if (list.length) {
  306. this.saveImages(list, saveAsZip, $status);
  307. } else {
  308. throw new Error(i18n("error_noimage"));
  309. }
  310. })
  311. .catch((e) => {
  312. alert(e.message || i18n("error_default"));
  313. });
  314. }
  315.  
  316. // 打包图片
  317. downloadImagesAsZip($btn) {
  318. this.downloadImages(true, $btn && $btn.find(".status"));
  319. }
  320.  
  321. // 独立下载图片
  322. downloadImagesSeperately($btn) {
  323. this.downloadImages(false, $btn && $btn.find(".status"));
  324. }
  325.  
  326. // 下载文字
  327. downloadText() {
  328. this.init()
  329. .then(this.downloadAppendPage)
  330. .then(($page) => {
  331. this.saveText({
  332. title: this.title,
  333. author: this.author,
  334. twitter: this.twitter,
  335. content: $page.find(".NovelSection").text(),
  336. });
  337. })
  338. .catch((e) => {
  339. alert(e.message || i18n("error_default"));
  340. });
  341. }
  342.  
  343. downloadAppendPage() {
  344. return request({
  345. url: "/f/ShowAppendFileF.jsp",
  346. type: "POST",
  347. data: {
  348. UID: this.authorId,
  349. IID: this.workId,
  350. PAS: $password.val(),
  351. MD: 0,
  352. TWF: -1,
  353. },
  354. dataType: "json",
  355. }).then((payload) => {
  356. if (payload.result_num < 0) {
  357. throw new Error(payload.html);
  358. }
  359.  
  360. return $(`<div>${this.existingHtml}${payload.html}</div>`);
  361. });
  362. }
  363. }
  364.  
  365. const pageInfo = new PageInfo(window.location.href);
  366.  
  367. $(".IllustThumb").each(function () {
  368. const $this = $(this);
  369. const isText = /文字/.test($this.find(".Num").text());
  370. const hasPassword =
  371. /pass\.png/.test($this.find(".IllustThumbImg").css("background-image")) ||
  372. /pass\.png/.test($this.find(".Publish").css("background-image"));
  373.  
  374. if (hasPassword) {
  375. return;
  376. }
  377.  
  378. if (isText) {
  379. $(`<button>${i18n("btn_downloadtext")}</button>`)
  380. .on("click", downloadTextFromList)
  381. .css({
  382. position: "absolute",
  383. left: 4,
  384. top: 110,
  385. zIndex: 1,
  386. fontFamily: fontFamily,
  387. })
  388. .appendTo($this);
  389. } else {
  390. $(
  391. `<button>${i18n(
  392. "btn_downloadimageseperately"
  393. )} <b class='status'></b></button>`
  394. )
  395. .on("click", downloadImagesSeperatelyFromList)
  396. .css({
  397. position: "absolute",
  398. left: 4,
  399. top: 110,
  400. zIndex: 1,
  401. fontFamily: fontFamily,
  402. })
  403. .appendTo($this);
  404.  
  405. $(
  406. `<button>${i18n("btn_downloadimages")} <b class='status'></b></button>`
  407. )
  408. .on("click", downloadImagesAsZipFromList)
  409. .css({
  410. position: "absolute",
  411. left: 4,
  412. top: 140,
  413. zIndex: 1,
  414. fontFamily: fontFamily,
  415. })
  416. .appendTo($this);
  417. }
  418. });
  419.  
  420. const $panel = $(`<div>
  421. <div>${i18n("ui_logined")}: <b style="color:red">${logined}</b>.</div>
  422. <div class="line-qualitytip" >${
  423. logined ? i18n("ui_qualitytrue") : i18n("ui_qualityfalse")
  424. }</div>
  425. <div class="line-password">${i18n(
  426. "ui_password"
  427. )} <input type='text' class="password"></div>
  428. <div class="line-mode" >${i18n(
  429. "ui_mode"
  430. )} <input type='checkbox' class="saveFileMode"></div>
  431. <div class="line-images">
  432. <button class="btn-downloadImagesSeperately" style="font-size:20px">${i18n(
  433. "btn_downloadimageseperately"
  434. )} <b class='status'></b></button></button><br>
  435. <button class="btn-downloadImages" style="font-size:20px">${i18n(
  436. "btn_downloadimages"
  437. )} <b class='status'></b></button>
  438. </div>
  439. <div class="line-text"><button class="btn-downloadText" style="font-size:20px">${i18n(
  440. "btn_downloadtext"
  441. )}</button></div>
  442. </div>`)
  443. .css({
  444. position: "fixed",
  445. left: 0,
  446. bottom: 50,
  447. zIndex: 999999,
  448. background: "#fff",
  449. color: "#333",
  450. fontSize: 18,
  451. fontFamily: fontFamily,
  452. padding: 10,
  453. })
  454. .appendTo($("body"));
  455.  
  456. const $password = $panel.find(".password");
  457. const $saveFileMode = $panel.find(".saveFileMode");
  458. $panel.find("button").css({
  459. fontFamily: fontFamily,
  460. });
  461.  
  462. pageInfo.init().then(function () {
  463. if (!pageInfo.workId) {
  464. $panel.find(".line-password").hide();
  465. $panel.find(".line-images").hide();
  466. $panel.find(".line-mode").hide();
  467. $panel.find(".line-text").hide();
  468. return;
  469. }
  470.  
  471. if (!pageInfo.hasPassword) {
  472. $panel.find(".line-password").hide();
  473. }
  474. if (pageInfo.isText) {
  475. $panel.find(".line-images").hide();
  476. $panel.find(".line-qualitytip").hide();
  477. $panel.find(".line-mode").hide();
  478. } else {
  479. $panel.find(".line-text").hide();
  480. }
  481. $panel.find(".btn-downloadImages").on("click", function () {
  482. pageInfo.downloadImagesAsZip($(this));
  483. });
  484. $panel.find(".btn-downloadImagesSeperately").on("click", function () {
  485. pageInfo.downloadImagesSeperately($(this));
  486. });
  487. $panel.find(".btn-downloadText").on("click", function () {
  488. pageInfo.downloadText($(this));
  489. });
  490. });
  491.  
  492. function request(config) {
  493. return new Promise((resolve, reject) => {
  494. $.ajax({
  495. ...config,
  496. success: (response) => {
  497. resolve(response);
  498. },
  499. error: () => {
  500. reject(new Error(i18n("error_default")));
  501. },
  502. });
  503. });
  504. }
  505.  
  506. function getMimeType(suffix) {
  507. let map = {
  508. png: "image/png",
  509. jpg: "image/jpeg",
  510. jpeg: "image/jpeg",
  511. gif: "image/gif",
  512. };
  513.  
  514. return map[suffix] || "text/plain";
  515. }
  516.  
  517. function getBlob(url) {
  518. // return fetch(url).then((response) => response.blob());
  519.  
  520. return new Promise((resolve, reject) => {
  521. GM.xmlHttpRequest({
  522. method: "GET",
  523. url: url,
  524. responseType: "blob",
  525. headers: { referer: window.location.href },
  526. onload: (payload) => {
  527. resolve(payload.response);
  528. },
  529. onerror: () => {
  530. reject(new Error(i18n("error_default")));
  531. },
  532. });
  533. });
  534.  
  535. // return new Promise((resolve, reject) => {
  536. // GM.xmlHttpRequest({
  537. // method: "GET",
  538. // url: url,
  539. // overrideMimeType: "text/plain; charset=x-user-defined",
  540. // headers: { referer: window.location.href },
  541. // onload: (xhr) => {
  542. // let r = xhr.responseText;
  543. // let data = new Uint8Array(r.length);
  544. // let i = 0;
  545. // while (i < r.length) {
  546. // data[i] = r.charCodeAt(i);
  547. // i++;
  548. // }
  549. // let suffix = url.split(".").splice(-1);
  550. // let blob = new Blob([data], { type: getMimeType(suffix) });
  551.  
  552. // resolve(blob);
  553. // },
  554. // onerror: () => {
  555. // reject(new Error(i18n("error_default")));
  556. // },
  557. // });
  558. // });
  559. }
  560.  
  561. // 过滤文件名非法字符
  562. function filterFilename(filename) {
  563. return filename.replace(/\?|\*|\:|\"|\<|\>|\\|\/|\|/g, "");
  564. }
  565.  
  566. function getPageInfo($btn) {
  567. const url = $btn.siblings(".IllustThumbImg").prop("href");
  568. return new PageInfo(url);
  569. }
  570.  
  571. function downloadImagesAsZipFromList() {
  572. const $this = $(this);
  573. const pageInfo = getPageInfo($this);
  574. pageInfo.init().then(() => {
  575. pageInfo.downloadImagesAsZip($this);
  576. });
  577. }
  578.  
  579. function downloadImagesSeperatelyFromList() {
  580. const $this = $(this);
  581. const pageInfo = getPageInfo($this);
  582. pageInfo.init().then(() => {
  583. pageInfo.downloadImagesSeperately($this);
  584. });
  585. }
  586.  
  587. function downloadTextFromList() {
  588. const $this = $(this);
  589. const pageInfo = getPageInfo($this);
  590. pageInfo.init().then(() => {
  591. pageInfo.downloadText($this);
  592. });
  593. }
  594. });

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址