Poipiku下载器

从Poipiku下载图片或文字

目前为 2022-01-27 提交的版本。查看 最新版本

  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.3
  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 = twitter ? twitter.substring(1) : username;
  152. const desc = $html.find(".IllustItemDesc").text().substring(0, 20);
  153.  
  154. this.saveFilename = filterFilename(
  155. `[${username2}][${website}][${this.authorId}_${this.workId}]${desc}`
  156. );
  157. this.title = $html.find(".IllustItemDesc").text();
  158. this.author = $html.find(".UserInfoUserName a").html();
  159. this.twitter = $html.find(".UserInfoProgile a").prop("href");
  160.  
  161. this.isText = $html.find(".IllustItem").hasClass("Text");
  162. this.hasPassword = $html.find(".IllustItem").hasClass("Password");
  163.  
  164. this.existingHtml =
  165. $html.find(".IllustItemThumb").eq(0).prop("outerHTML") +
  166. $html.find(".IllustItemText").eq(0).prop("outerHTML");
  167. }
  168.  
  169. // 生成保存文件名
  170. getSaveFilename() {
  171. return this.saveFilename;
  172. }
  173.  
  174. // 生成保存图片文件名
  175. // 默认:序号.后缀名
  176. // 选中:网站_作品id_序号.后缀名
  177. getSaveImageFilename(src, index) {
  178. let suffix = src.split(".").splice(-1);
  179. const mode = $saveFileMode.is(":checked");
  180.  
  181. if (mode) {
  182. return `${website}_${this.workId}_${index + 1}.${suffix}`;
  183. }
  184.  
  185. return `${index + 1}.${suffix}`;
  186. }
  187.  
  188. // 批量下载图片的默认方法
  189. saveImages(list, saveAsZip, $status) {
  190. let finishehCount = 0;
  191. let zip;
  192. let folder;
  193.  
  194. if (saveAsZip) {
  195. try {
  196. zip = new JSZip();
  197. folder = zip.folder(this.saveFilename);
  198. } catch (e) {
  199. alert(e);
  200. }
  201. }
  202.  
  203. $status = $status || $("<div></div>");
  204. $status.text(`0/${list.length}`);
  205.  
  206. let promises = list.map((src, index) => {
  207. return getBlob(src).then((blob) => {
  208. finishehCount++;
  209. $status.text(`${finishehCount}/${list.length}`);
  210.  
  211. if (zip) {
  212. folder.file(this.getSaveImageFilename(src, index), blob, {
  213. binary: true,
  214. });
  215. } else {
  216. let suffix = src.split(".").splice(-1);
  217. saveAs(
  218. new Blob([blob]),
  219. `${this.saveFilename}_${index + 1}.${suffix}`
  220. );
  221. }
  222. });
  223. });
  224.  
  225. Promise.all(promises)
  226. .then(() => {
  227. if (zip) {
  228. return zip
  229. .generateAsync({ type: "blob", base64: true })
  230. .then((content) => saveAs(content, this.saveFilename));
  231. }
  232. })
  233. .catch((e) => {
  234. alert(i18n("error_zip"));
  235. });
  236. }
  237.  
  238. // 保存文字的默认方法
  239. saveText(option) {
  240. let str = "";
  241.  
  242. if (option.title) {
  243. str += `${i18n("txt_title")}${option.title}\n`;
  244. }
  245. if (option.author) {
  246. str += `${i18n("txt_author")}${option.author}\n`;
  247. }
  248. if (option.twitter) {
  249. str += `${i18n("txt_twitter")}${option.twitter}\n`;
  250. }
  251. str += `${i18n("txt_link")}${window.location.href}\n`;
  252. str += `\n\n`;
  253. str += option.content;
  254.  
  255. saveAs(
  256. new Blob([str], { type: "text/plain;charset=UTF-8" }),
  257. this.saveFilename + ".txt"
  258. );
  259. }
  260.  
  261. // 下载图片
  262. downloadImages(saveAsZip, $status) {
  263. this.init()
  264. .then(this.downloadAppendPage)
  265. .then(($page) => {
  266. if (logined) {
  267. return request({
  268. url: "/f/ShowIllustDetailF.jsp",
  269. type: "POST",
  270. data: {
  271. ID: this.authorId,
  272. TD: this.workId,
  273. AD: "-1",
  274. PAS: $password.val(),
  275. },
  276. dataType: "json",
  277. }).then((payload) => {
  278. if (!payload.html) {
  279. throw new Error(i18n("error_fetch"));
  280. }
  281. return $(payload.html);
  282. });
  283. }
  284.  
  285. return $page;
  286. })
  287. .then(($page) => {
  288. let list = [];
  289.  
  290. $page
  291. .find(logined ? ".DetailIllustItemImage" : ".IllustItemThumbImg")
  292. .each(function () {
  293. const src = $(this).attr("src");
  294.  
  295. if (src && !/^\/img/.test(src)) {
  296. list.push(window.location.protocol + src);
  297. }
  298. });
  299.  
  300. if (list.length) {
  301. this.saveImages(list, saveAsZip, $status);
  302. } else {
  303. throw new Error(i18n("error_noimage"));
  304. }
  305. })
  306. .catch((e) => {
  307. alert(e.message || i18n("error_default"));
  308. });
  309. }
  310.  
  311. // 打包图片
  312. downloadImagesAsZip($btn) {
  313. this.downloadImages(true, $btn && $btn.find(".status"));
  314. }
  315.  
  316. // 独立下载图片
  317. downloadImagesSeperately($btn) {
  318. this.downloadImages(false, $btn && $btn.find(".status"));
  319. }
  320.  
  321. // 下载文字
  322. downloadText() {
  323. this.init()
  324. .then(this.downloadAppendPage)
  325. .then(($page) => {
  326. this.saveText({
  327. title: this.title,
  328. author: this.author,
  329. twitter: this.twitter,
  330. content: $page.find(".NovelSection").text(),
  331. });
  332. })
  333. .catch((e) => {
  334. alert(e.message || i18n("error_default"));
  335. });
  336. }
  337.  
  338. downloadAppendPage() {
  339. return request({
  340. url: "/f/ShowAppendFileF.jsp",
  341. type: "POST",
  342. data: {
  343. UID: this.authorId,
  344. IID: this.workId,
  345. PAS: $password.val(),
  346. MD: 0,
  347. TWF: -1,
  348. },
  349. dataType: "json",
  350. }).then((payload) => {
  351. if (payload.result_num < 0) {
  352. throw new Error(payload.html);
  353. }
  354.  
  355. return $(`<div>${this.existingHtml}${payload.html}</div>`);
  356. });
  357. }
  358. }
  359.  
  360. const pageInfo = new PageInfo(window.location.href);
  361.  
  362. $(".IllustThumb").each(function () {
  363. const $this = $(this);
  364. const isText = /文字/.test($this.find(".Num").text());
  365. const hasPassword =
  366. /pass\.png/.test($this.find(".IllustThumbImg").css("background-image")) ||
  367. /pass\.png/.test($this.find(".Publish").css("background-image"));
  368.  
  369. if (hasPassword) {
  370. return;
  371. }
  372.  
  373. if (isText) {
  374. $(`<button>${i18n("btn_downloadtext")}</button>`)
  375. .on("click", downloadTextFromList)
  376. .css({
  377. position: "absolute",
  378. left: 4,
  379. top: 110,
  380. zIndex: 1,
  381. fontFamily: fontFamily,
  382. })
  383. .appendTo($this);
  384. } else {
  385. $(
  386. `<button>${i18n(
  387. "btn_downloadimageseperately"
  388. )} <b class='status'></b></button>`
  389. )
  390. .on("click", downloadImagesSeperatelyFromList)
  391. .css({
  392. position: "absolute",
  393. left: 4,
  394. top: 110,
  395. zIndex: 1,
  396. fontFamily: fontFamily,
  397. })
  398. .appendTo($this);
  399.  
  400. $(
  401. `<button>${i18n("btn_downloadimages")} <b class='status'></b></button>`
  402. )
  403. .on("click", downloadImagesAsZipFromList)
  404. .css({
  405. position: "absolute",
  406. left: 4,
  407. top: 140,
  408. zIndex: 1,
  409. fontFamily: fontFamily,
  410. })
  411. .appendTo($this);
  412. }
  413. });
  414.  
  415. const $panel = $(`<div>
  416. <div>${i18n("ui_logined")}: <b style="color:red">${logined}</b>.</div>
  417. <div class="line-qualitytip" >${
  418. logined ? i18n("ui_qualitytrue") : i18n("ui_qualityfalse")
  419. }</div>
  420. <div class="line-password">${i18n(
  421. "ui_password"
  422. )} <input type='text' class="password"></div>
  423. <div class="line-mode" >${i18n(
  424. "ui_mode"
  425. )} <input type='checkbox' class="saveFileMode"></div>
  426. <div class="line-images">
  427. <button class="btn-downloadImagesSeperately" style="font-size:20px">${i18n(
  428. "btn_downloadimageseperately"
  429. )} <b class='status'></b></button></button><br>
  430. <button class="btn-downloadImages" style="font-size:20px">${i18n(
  431. "btn_downloadimages"
  432. )} <b class='status'></b></button>
  433. </div>
  434. <div class="line-text"><button class="btn-downloadText" style="font-size:20px">${i18n(
  435. "btn_downloadtext"
  436. )}</button></div>
  437. </div>`)
  438. .css({
  439. position: "fixed",
  440. left: 0,
  441. bottom: 50,
  442. zIndex: 999999,
  443. background: "#fff",
  444. color: "#333",
  445. fontSize: 18,
  446. fontFamily: fontFamily,
  447. padding: 10,
  448. })
  449. .appendTo($("body"));
  450.  
  451. const $password = $panel.find(".password");
  452. const $saveFileMode = $panel.find(".saveFileMode");
  453. $panel.find("button").css({
  454. fontFamily: fontFamily,
  455. });
  456.  
  457. pageInfo.init().then(function () {
  458. if (!pageInfo.workId) {
  459. $panel.find(".line-password").hide();
  460. $panel.find(".line-images").hide();
  461. $panel.find(".line-mode").hide();
  462. $panel.find(".line-text").hide();
  463. return;
  464. }
  465.  
  466. if (!pageInfo.hasPassword) {
  467. $panel.find(".line-password").hide();
  468. }
  469. if (pageInfo.isText) {
  470. $panel.find(".line-images").hide();
  471. $panel.find(".line-qualitytip").hide();
  472. $panel.find(".line-mode").hide();
  473. } else {
  474. $panel.find(".line-text").hide();
  475. }
  476. $panel.find(".btn-downloadImages").on("click", function () {
  477. pageInfo.downloadImagesAsZip($(this));
  478. });
  479. $panel.find(".btn-downloadImagesSeperately").on("click", function () {
  480. pageInfo.downloadImagesSeperately($(this));
  481. });
  482. $panel.find(".btn-downloadText").on("click", function () {
  483. pageInfo.downloadText($(this));
  484. });
  485. });
  486.  
  487. function request(config) {
  488. return new Promise((resolve, reject) => {
  489. $.ajax({
  490. ...config,
  491. success: (response) => {
  492. resolve(response);
  493. },
  494. error: () => {
  495. reject(new Error(i18n("error_default")));
  496. },
  497. });
  498. });
  499. }
  500.  
  501. function getMimeType(suffix) {
  502. let map = {
  503. png: "image/png",
  504. jpg: "image/jpeg",
  505. jpeg: "image/jpeg",
  506. gif: "image/gif",
  507. };
  508.  
  509. return map[suffix] || "text/plain";
  510. }
  511.  
  512. function getBlob(url) {
  513. // return fetch(url).then((response) => response.blob());
  514.  
  515. return new Promise((resolve, reject) => {
  516. GM.xmlHttpRequest({
  517. method: "GET",
  518. url: url,
  519. responseType: "blob",
  520. headers: { referer: window.location.href },
  521. onload: (payload) => {
  522. resolve(payload.response);
  523. },
  524. onerror: () => {
  525. reject(new Error(i18n("error_default")));
  526. },
  527. });
  528. });
  529.  
  530. // return new Promise((resolve, reject) => {
  531. // GM.xmlHttpRequest({
  532. // method: "GET",
  533. // url: url,
  534. // overrideMimeType: "text/plain; charset=x-user-defined",
  535. // headers: { referer: window.location.href },
  536. // onload: (xhr) => {
  537. // let r = xhr.responseText;
  538. // let data = new Uint8Array(r.length);
  539. // let i = 0;
  540. // while (i < r.length) {
  541. // data[i] = r.charCodeAt(i);
  542. // i++;
  543. // }
  544. // let suffix = url.split(".").splice(-1);
  545. // let blob = new Blob([data], { type: getMimeType(suffix) });
  546.  
  547. // resolve(blob);
  548. // },
  549. // onerror: () => {
  550. // reject(new Error(i18n("error_default")));
  551. // },
  552. // });
  553. // });
  554. }
  555.  
  556. // 过滤文件名非法字符
  557. function filterFilename(filename) {
  558. return filename.replace(/\?|\*|\:|\"|\<|\>|\\|\/|\|/g, "");
  559. }
  560.  
  561. function getPageInfo($btn) {
  562. const url = $btn.siblings(".IllustThumbImg").prop("href");
  563. return new PageInfo(url);
  564. }
  565.  
  566. function downloadImagesAsZipFromList() {
  567. const $this = $(this);
  568. const pageInfo = getPageInfo($this);
  569. pageInfo.init().then(() => {
  570. pageInfo.downloadImagesAsZip($this);
  571. });
  572. }
  573.  
  574. function downloadImagesSeperatelyFromList() {
  575. const $this = $(this);
  576. const pageInfo = getPageInfo($this);
  577. pageInfo.init().then(() => {
  578. pageInfo.downloadImagesSeperately($this);
  579. });
  580. }
  581.  
  582. function downloadTextFromList() {
  583. const $this = $(this);
  584. const pageInfo = getPageInfo($this);
  585. pageInfo.init().then(() => {
  586. pageInfo.downloadText($this);
  587. });
  588. }
  589. });

QingJ © 2025

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