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

QingJ © 2025

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