Pixiv Downloader

一键下载Pixiv各页面原图。支持多图下载,动图下载,按作品标签下载,画师作品批量下载。动图支持格式转换:Gif | Apng | Webp | Webm。下载的图片将保存到以画师名命名的单独文件夹(需要调整tampermonkey“下载”设置为“浏览器API”)。保留已下载图片的记录。

As of 2023-05-29. See the latest version.

  1. // ==UserScript==
  2. // @name Pixiv Downloader
  3. // @namespace https://greasyfork.org/zh-CN/scripts/432150
  4. // @version 0.8.8
  5. // @description:en Download the original images of Pixiv pages with one click. Supports:multiple illustrations, ugoira(animation), and batch downloads of artists' work. Ugoira support format conversion: Gif | Apng | Webp | Webm. The downloaded images will be saved in a separate folder named after the artist (you need to adjust the tampermonkey "Download" setting to "Browser API"). A record of downloaded images is kept.
  6. // @description 一键下载Pixiv各页面原图。支持多图下载,动图下载,按作品标签下载,画师作品批量下载。动图支持格式转换:Gif | Apng | Webp | Webm。下载的图片将保存到以画师名命名的单独文件夹(需要调整tampermonkey“下载”设置为“浏览器API”)。保留已下载图片的记录。
  7. // @description:zh-TW 一鍵下載Pixiv各頁面原圖。支持多圖下載,動圖下載,按作品標籤下載,畫師作品批次下載。動圖支持格式轉換:Gif | Apng | Webp | Webm。下載的圖片將保存到以畫師名命名的單獨文件夾(需要調整tampermonkey“下載”設置為“瀏覽器API”)。保留已下載圖片的紀錄。
  8. // @author ruaruarua
  9. // @match https://www.pixiv.net/*
  10. // @icon https://www.pixiv.net/favicon.ico
  11. // @noframes
  12. // @grant GM_xmlhttpRequest
  13. // @grant GM_download
  14. // @grant GM_setValue
  15. // @grant GM_getValue
  16. // @grant GM_info
  17. // @grant GM_registerMenuCommand
  18. // @connect i.pximg.net
  19. // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js
  20. // @require https://cdnjs.cloudflare.com/ajax/libs/gif.js/0.2.0/gif.js
  21. // @require https://greasyfork.org/scripts/455256-toanimatedwebp/code/toAnimatedWebp.js?version=1120088
  22. // ==/UserScript==
  23. (function (workerChunk, JSZip, GIF) {
  24. 'use strict';
  25.  
  26. function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
  27.  
  28. var workerChunk__default = /*#__PURE__*/_interopDefaultLegacy(workerChunk);
  29. var JSZip__default = /*#__PURE__*/_interopDefaultLegacy(JSZip);
  30. var GIF__default = /*#__PURE__*/_interopDefaultLegacy(GIF);
  31.  
  32. var e=[],t=[];function n(n,r){if(n&&"undefined"!=typeof document){var a,s=!0===r.prepend?"prepend":"append",d=!0===r.singleTag,i="string"==typeof r.container?document.querySelector(r.container):document.getElementsByTagName("head")[0];if(d){var u=e.indexOf(i);-1===u&&(u=e.push(i)-1,t[u]={}),a=t[u]&&t[u][s]?t[u][s]:t[u][s]=c();}else a=c();65279===n.charCodeAt(0)&&(n=n.substring(1)),a.styleSheet?a.styleSheet.cssText+=n:a.appendChild(document.createTextNode(n));}function c(){var e=document.createElement("style");if(e.setAttribute("type","text/css"),r.attributes)for(var t=Object.keys(r.attributes),n=0;n<t.length;n++)e.setAttribute(t[n],r.attributes[t[n]]);var a="prepend"===s?"afterbegin":"beforeend";return i.insertAdjacentElement(a,e),e}}
  33.  
  34. var css$3 = "@property --pdl-progress {\n syntax: '<percentage>';\n inherits: true;\n initial-value: 0%;\n}\n@keyframes pdl_loading {\n 100% {\n transform: translate(-50%, -50%) rotate(360deg);\n }\n}\n.pdl-btn {\n position: relative;\n border-top-right-radius: 8px;\n background: no-repeat center/85%;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E %3Cpath fill='%233C3C3C' d='M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm-32-316v116h-67c-10.7 0-16 12.9-8.5 20.5l99 99c4.7 4.7 12.3 4.7 17 0l99-99c7.6-7.6 2.2-20.5-8.5-20.5h-67V140c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12z'%3E%3C/path%3E %3C/svg%3E\");\n color: #01b468;\n display: inline-block;\n font-size: 13px;\n font-weight: bold;\n height: 32px;\n line-height: 32px;\n margin: 0;\n overflow: hidden;\n padding: 0;\n border: none;\n text-decoration: none !important;\n text-align: center;\n text-overflow: ellipsis;\n user-select: none;\n white-space: nowrap;\n width: 32px;\n z-index: 1;\n cursor: pointer;\n}\n.pdl-btn-main {\n margin: 0 0 0 10px;\n}\n.pdl-btn-sub {\n bottom: 0;\n background-color: rgba(255, 255, 255, 0.5);\n left: 0;\n position: absolute;\n}\n.pdl-btn-sub.artworks {\n position: sticky;\n top: 40px;\n border-radius: 4px;\n}\n.pdl-btn-sub.presentation {\n position: fixed;\n top: 50px;\n right: 16px;\n border-radius: 8px;\n left: auto;\n}\n.pdl-btn-sub-bookmark.pdl-btn-sub-bookmark {\n left: auto;\n right: 0;\n bottom: 34px;\n border-radius: 8px;\n border-top-right-radius: 0px;\n border-bottom-right-radius: 0px;\n}\n.pdl-error {\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E %3Cpath fill='%23EA0000' d='M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm101.8-262.2L295.6 256l62.2 62.2c4.7 4.7 4.7 12.3 0 17l-22.6 22.6c-4.7 4.7-12.3 4.7-17 0L256 295.6l-62.2 62.2c-4.7 4.7-12.3 4.7-17 0l-22.6-22.6c-4.7-4.7-4.7-12.3 0-17l62.2-62.2-62.2-62.2c-4.7-4.7-4.7-12.3 0-17l22.6-22.6c4.7-4.7 12.3-4.7 17 0l62.2 62.2 62.2-62.2c4.7-4.7 12.3-4.7 17 0l22.6 22.6c4.7 4.7 4.7 12.3 0 17z'%3E%3C/path%3E %3C/svg%3E\") !important;\n}\n.pdl-complete {\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E %3Cpath fill='%2301B468' d='M256 8C119.033 8 8 119.033 8 256s111.033 248 248 248 248-111.033 248-248S392.967 8 256 8zm0 48c110.532 0 200 89.451 200 200 0 110.532-89.451 200-200 200-110.532 0-200-89.451-200-200 0-110.532 89.451-200 200-200m140.204 130.267l-22.536-22.718c-4.667-4.705-12.265-4.736-16.97-.068L215.346 303.697l-59.792-60.277c-4.667-4.705-12.265-4.736-16.97-.069l-22.719 22.536c-4.705 4.667-4.736 12.265-.068 16.971l90.781 91.516c4.667 4.705 12.265 4.736 16.97.068l172.589-171.204c4.704-4.668 4.734-12.266.067-16.971z'%3E%3C/path%3E %3C/svg%3E\") !important;\n}\n.pdl-progress {\n background-image: none !important;\n cursor: default !important;\n}\n.pdl-progress:after {\n content: '';\n display: inline-block;\n position: absolute;\n top: 50%;\n left: 50%;\n width: 27px;\n height: 27px;\n transform: translate(-50%, -50%);\n -webkit-mask: radial-gradient(transparent, transparent 54%, #000 57%, #000);\n mask: radial-gradient(transparent, transparent 54%, #000 57%, #000);\n border-radius: 50%;\n}\n.pdl-progress:not(:empty):after {\n background: conic-gradient(\n #01b468 0,\n #01b468 var(--pdl-progress),\n transparent var(--pdl-progress),\n transparent\n );\n transition: --pdl-progress 0.2s ease;\n}\n.pdl-progress:empty:after {\n background: conic-gradient(#01b468 0, #01b468 25%, #01b46833 25%, #01b46833);\n animation: 1.5s infinite linear pdl_loading;\n}\n\n.pdl-btn.pdl-tag {\n height: auto;\n border-top-right-radius: 4px;\n border-bottom-right-radius: 4px;\n left: -1px;\n background-color: rgba(0, 0, 0, 0.12);\n transition: background-image 0.5s;\n}\n\n.pdl-btn.pdl-tag.pdl-tag-hide,\n.pdl-btn.pdl-modal-tag.pdl-tag-hide {\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E %3C/svg%3E\");\n pointer-events: none;\n}\n\n.pdl-btn.pdl-modal-tag {\n position: absolute;\n right: 65px;\n top: 6px;\n background-origin: content-box;\n border-radius: 4px;\n padding: 5px;\n width: 42px;\n height: 50px;\n background-color: rgba(0, 0, 0, 0.04);\n transition: 0.25s background-color;\n}\n\n.pdl-btn.pdl-modal-tag:not(.pdl-tag-hide):hover {\n background-color: rgba(0, 0, 0, 0.12);\n}\n\n.pdl-wrap-artworks {\n position: absolute;\n right: 8px;\n top: 0px;\n bottom: 0px;\n margin-top: 40px;\n}\n";
  35. n(css$3,{});
  36.  
  37. var css$2 = ".pdl-modal * {\n font-family: 'win-bug-omega, system-ui, -apple-system, \"Segoe UI\", Roboto, Ubuntu, Cantarell, \"Noto Sans\", \"Hiragino Kaku Gothic ProN\", Meiryo, sans-serif';\n line-height: 1.15;\n}\n\n.pdl-modal {\n position: fixed;\n top: 0;\n left: 0;\n width: 100%;\n height: 100%;\n display: flex;\n z-index: 99;\n background-color: rgba(0, 0, 0, 0.32);\n user-select: none;\n}\n\n.pdl-dialog {\n position: relative;\n background-color: #fff;\n border-radius: 24px;\n margin: auto;\n padding: 20px 40px 30px 40px;\n width: 600px;\n font-size: 16px;\n}\n\n.pdl-dialog-header > h3 {\n font-weight: bold;\n font-size: 1.17em;\n margin: 1em 0;\n}\n\n.pdl-dialog-close {\n position: absolute;\n top: 10px;\n right: 10px;\n margin: 0;\n padding: 0;\n width: 25px;\n height: 25px;\n border: none;\n cursor: pointer;\n border-radius: 50%;\n background-color: transparent;\n transform: rotate(45deg);\n transition: 0.25s background-color;\n background: linear-gradient(rgb(125, 125, 125) 0%, rgb(125, 125, 125) 100%)\n center/18px 2px no-repeat,\n linear-gradient(rgb(125, 125, 125) 0%, rgb(125, 125, 125) 100%) center/2px\n 18px no-repeat;\n}\n\n.pdl-dialog-close:hover {\n background-color: rgba(0, 0, 0, 0.05);\n}\n\n.pdl-dialog-content hr {\n height: 0 !important;\n margin: 0;\n border: none;\n border-top: 1px solid rgba(0, 0, 0, 0.1);\n}\n\n.pdl-dialog-content hr.sub {\n margin-inline-start: 1.5em;\n}\n\n/* button */\n.pdl-dialog-button {\n font-size: 16px;\n background-color: #fff;\n border: 1px solid rgb(125, 125, 125);\n border-radius: 5px;\n padding: 0.5em 1.5em;\n cursor: pointer;\n transition: 0.2s opacity;\n line-height: 1.15;\n}\n.pdl-dialog-button:hover {\n opacity: 0.7;\n}\n\n.pdl-dialog-button.primary {\n color: #fff;\n background-color: #0096fa;\n border-color: #0096fa;\n}\n\n.pdl-dialog-button.icon {\n padding: 0.5em 0.8em;\n}\n\n.pdl-dialog-button[disabled] {\n cursor: not-allowed !important;\n color: #c0c4cc;\n background-color: #fff;\n border-color: #e4e7ed;\n opacity: 1 !important;\n}\n\n.pdl-dialog-button[disabled]:hover {\n opacity: 1 !important;\n}\n\n.pdl-dialog-button.primary[disabled] {\n color: #fff;\n background-color: #a0cfff;\n border-color: #a0cfff;\n}\n\n/* tabs */\n.pdl-tabs-nav {\n display: flex;\n align-items: center;\n border-bottom: 1px solid #dcdfe6;\n position: relative;\n}\n\n.pdl-tabs-nav .pdl-tab-item {\n padding: 0px 16px;\n line-height: 2.5;\n cursor: pointer;\n transition: 0.2s color;\n}\n\n.pdl-tabs-nav .pdl-tab-item:nth-child(2) {\n padding-left: 0px;\n}\n\n.pdl-tabs-nav .pdl-tab-item:last-child {\n padding-right: 0px;\n}\n\n.pdl-tab-item:hover,\n.pdl-tab-item.active {\n color: #0096fa;\n}\n\n.pdl-tab-item.active {\n font-weight: 700;\n}\n\n.pdl-tabs-content {\n padding: 16px;\n min-height: 200px;\n}\n\n.pdl-tabs-nav .pdl-tabs__active-bar {\n position: absolute;\n bottom: 0;\n left: 0;\n height: 2px;\n background-color: #0096fa;\n z-index: 1;\n transition: width 0.2s, transform 0.2s;\n}\n\n/* filename */\n\n#pdl-setting-filename a {\n color: #0096fa;\n text-decoration: underline;\n}\n#pdl-setting-filename .tags-option label,\n#pdl-setting-filename .tags-option input {\n cursor: pointer;\n}\n\n#pdl-setting-filename .tags-content {\n flex: 1;\n display: flex;\n gap: 20px;\n}\n\n#pdl-setting-filename .pdl-input-wrap,\n#pdl-setting-filename .tags-option {\n display: flex;\n align-items: center;\n margin: 12px 0;\n gap: 12px;\n}\n\n#pdl-setting-filename .pdl-input-wrap label,\n#pdl-setting-filename .tags-option .tags-title {\n cursor: default;\n font-weight: 700;\n width: 6em;\n}\n\n#pdl-setting-filename .pdl-input-wrap input[type='text'] {\n height: auto;\n padding: 0.5em;\n font-size: 16px;\n line-height: 1.5;\n flex: 1;\n border: 1px solid #333;\n}\n\n#pdl-setting-filename .pdl-input-wrap input[type='text']:focus {\n background-color: #fff !important;\n}\n\n#pdl-setting-filename .pdl-input-wrap button {\n line-height: 1.5;\n}\n\n/* ugoria */\n#pdl-setting-ugoria p.option-header {\n font-weight: 700;\n}\n\n#pdl-setting-ugoria #pdl-ugoria-format-wrap {\n display: flex;\n justify-content: space-between;\n flex-wrap: nowrap;\n margin: 1.5em 1em;\n}\n\n#pdl-ugoria-format-wrap .pdl-ugoria-format-item,\n#pdl-ugoria-format-wrap .pdl-ugoria-format-item label,\n#pdl-ugoria-format-wrap .pdl-ugoria-format-item input {\n cursor: pointer;\n}\n\n#pdl-ugoria-format-wrap .pdl-ugoria-format-item label {\n padding-left: 4px;\n}\n\n/* history */\n#pdl-setting-history div {\n text-align: center;\n margin: 1em 0;\n}\n\n#pdl-setting-history .btn-history {\n width: 80%;\n}\n\n/* others setting */\n#pdl-setting-others .pdl-options {\n display: flex;\n align-items: center;\n gap: 20px;\n cursor: pointer;\n transition: 0.2s background-color;\n padding: 1em 0.5em;\n border-radius: 4px;\n}\n\n#pdl-setting-others .pdl-options:hover {\n background-color: rgba(0, 0, 0, 0.05);\n}\n\n#pdl-setting-others .pdl-options.sub-option {\n padding: 0.5em;\n padding-inline-start: 2em;\n}\n\n/* donate */\n#pdl-setting-donate {\n text-align: center;\n}\n\n#pdl-setting-donate p {\n margin: 0.5em 0;\n}\n\n/* upgrade msg */\n.pdl-changelog h4 {\n margin: 1em 0;\n font-weight: bold !important;\n}\n\n.pdl-changelog ul {\n padding-inline-start: 40px !important;\n}\n\n.pdl-changelog li {\n line-height: 2;\n list-style-type: disc !important;\n}\n\n.pdl-changelog p {\n margin: 0.5em 0;\n}\n";
  38. n(css$2,{});
  39.  
  40. var css$1 = ".pdl-dlbar-status_bar {\n flex-grow: 1;\n height: 46px;\n line-height: 46px;\n padding-right: 8px;\n text-align: right;\n font-weight: bold;\n font-size: 16px;\n color: rgb(133, 133, 133);\n cursor: default;\n white-space: nowrap;\n}\n\n.pdl-btn-all.pdl-btn-all,\n.pdl-stop.pdl-stop {\n background-color: transparent;\n border: none;\n padding: 0 10px;\n}\n\n.pdl-btn-all.pdl-btn-all:hover,\n.pdl-stop.pdl-stop:hover {\n color: rgb(31, 31, 31);\n}\n\n.pdl-btn-all::before,\n.pdl-stop::before {\n content: '';\n height: 24px;\n width: 24px;\n transition: background-image 0.2s ease 0s;\n background: no-repeat center/85%;\n}\n\n.pdl-btn-all::before {\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E %3Cpath fill='%23858585' d='M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm-32-316v116h-67c-10.7 0-16 12.9-8.5 20.5l99 99c4.7 4.7 12.3 4.7 17 0l99-99c7.6-7.6 2.2-20.5-8.5-20.5h-67V140c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12z'%3E%3C/path%3E %3C/svg%3E\");\n}\n\n.pdl-stop::before {\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E %3Cpath fill='%23858585' d='M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm101.8-262.2L295.6 256l62.2 62.2c4.7 4.7 4.7 12.3 0 17l-22.6 22.6c-4.7 4.7-12.3 4.7-17 0L256 295.6l-62.2 62.2c-4.7 4.7-12.3 4.7-17 0l-22.6-22.6c-4.7-4.7-4.7-12.3 0-17l62.2-62.2-62.2-62.2c-4.7-4.7-4.7-12.3 0-17l22.6-22.6c4.7-4.7 12.3-4.7 17 0l62.2 62.2 62.2-62.2c4.7-4.7 12.3-4.7 17 0l22.6 22.6c4.7 4.7 4.7 12.3 0 17z'%3E%3C/path%3E %3C/svg%3E\");\n}\n\n.pdl-btn-all:hover::before {\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E %3Cpath fill='%231F1F1F' d='M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm-32-316v116h-67c-10.7 0-16 12.9-8.5 20.5l99 99c4.7 4.7 12.3 4.7 17 0l99-99c7.6-7.6 2.2-20.5-8.5-20.5h-67V140c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12z'%3E%3C/path%3E %3C/svg%3E\");\n}\n\n.pdl-stop:hover::before {\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E %3Cpath fill='%231F1F1F' d='M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm101.8-262.2L295.6 256l62.2 62.2c4.7 4.7 4.7 12.3 0 17l-22.6 22.6c-4.7 4.7-12.3 4.7-17 0L256 295.6l-62.2 62.2c-4.7 4.7-12.3 4.7-17 0l-22.6-22.6c-4.7-4.7-4.7-12.3 0-17l62.2-62.2-62.2-62.2c-4.7-4.7-4.7-12.3 0-17l22.6-22.6c4.7-4.7 12.3-4.7 17 0l62.2 62.2 62.2-62.2c4.7-4.7 12.3-4.7 17 0l22.6 22.6c4.7 4.7 4.7 12.3 0 17z'%3E%3C/path%3E %3C/svg%3E\");\n}\n\n.pdl-hide {\n display: none !important;\n}\n\n.pdl-filter-wrap {\n display: flex;\n justify-content: flex-end;\n gap: 12px;\n margin: 4px 0;\n font-weight: bold;\n font-size: 14px;\n line-height: 14px;\n color: rgb(133, 133, 133);\n transition: color 0.2s ease 0s;\n}\n\n.pdl-filter-wrap.unavailable {\n pointer-events: none !important;\n opacity: 0.5 !important;\n}\n\n.pdl-filter-wrap .pdl-filter:hover {\n color: rgb(31, 31, 31);\n}\n\n.pdl-filter-wrap label {\n padding-left: 8px;\n cursor: pointer;\n}\n\n.pdl-checkbox.pdl-checkbox {\n vertical-align: top;\n appearance: none;\n position: relative;\n box-sizing: border-box;\n width: 28px;\n border: 2px solid transparent;\n cursor: pointer;\n border-radius: 14px;\n height: 14px;\n background-color: rgba(133, 133, 133);\n transition: background-color 0.2s ease 0s, box-shadow 0.2s ease 0s;\n}\n\n.pdl-checkbox:hover {\n background-color: rgba(31, 31, 31);\n}\n\n.pdl-checkbox::after {\n content: '';\n position: absolute;\n display: block;\n top: 0px;\n left: 0px;\n width: 10px;\n height: 10px;\n transform: translateX(0px);\n background-color: rgb(255, 255, 255);\n border-radius: 10px;\n transition: transform 0.2s ease 0s;\n}\n\n.pdl-checkbox:checked {\n background-color: rgb(0, 150, 250);\n}\n\n.pdl-checkbox:checked::after {\n transform: translateX(14px);\n}\n\n.pdl-dlbar {\n display: flex;\n flex-grow: 1;\n}\n\n.pdl-dlbar-follow_latest {\n padding: 0 8px;\n}\n";
  41. n(css$1,{});
  42.  
  43. var css = "[data-theme='dark'] .pdl-btn-all::before {\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E %3Cpath fill='%23858585' d='M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm-32-316v116h-67c-10.7 0-16 12.9-8.5 20.5l99 99c4.7 4.7 12.3 4.7 17 0l99-99c7.6-7.6 2.2-20.5-8.5-20.5h-67V140c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12z'%3E%3C/path%3E %3C/svg%3E\");\n}\n\n[data-theme='dark'] .pdl-btn-main,\n[data-theme='dark'] .pdl-btn-all:hover::before {\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E %3Cpath fill='%23D6D6D6' d='M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm-32-316v116h-67c-10.7 0-16 12.9-8.5 20.5l99 99c4.7 4.7 12.3 4.7 17 0l99-99c7.6-7.6 2.2-20.5-8.5-20.5h-67V140c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12z'%3E%3C/path%3E %3C/svg%3E\");\n}\n\n[data-theme='dark'] .pdl-stop::before {\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E %3Cpath fill='%23858585' d='M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm101.8-262.2L295.6 256l62.2 62.2c4.7 4.7 4.7 12.3 0 17l-22.6 22.6c-4.7 4.7-12.3 4.7-17 0L256 295.6l-62.2 62.2c-4.7 4.7-12.3 4.7-17 0l-22.6-22.6c-4.7-4.7-4.7-12.3 0-17l62.2-62.2-62.2-62.2c-4.7-4.7-4.7-12.3 0-17l22.6-22.6c4.7-4.7 12.3-4.7 17 0l62.2 62.2 62.2-62.2c4.7-4.7 12.3-4.7 17 0l22.6 22.6c4.7 4.7 4.7 12.3 0 17z'%3E%3C/path%3E %3C/svg%3E\");\n}\n\n[data-theme='dark'] .pdl-stop:hover::before {\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E %3Cpath fill='%23D6D6D6' d='M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm101.8-262.2L295.6 256l62.2 62.2c4.7 4.7 4.7 12.3 0 17l-22.6 22.6c-4.7 4.7-12.3 4.7-17 0L256 295.6l-62.2 62.2c-4.7 4.7-12.3 4.7-17 0l-22.6-22.6c-4.7-4.7-4.7-12.3 0-17l62.2-62.2-62.2-62.2c-4.7-4.7-4.7-12.3 0-17l22.6-22.6c4.7-4.7 12.3-4.7 17 0l62.2 62.2 62.2-62.2c4.7-4.7 12.3-4.7 17 0l22.6 22.6c4.7 4.7 4.7 12.3 0 17z'%3E%3C/path%3E %3C/svg%3E\");\n}\n\n[data-theme='dark'] .pdl-checkbox:not(:checked):hover {\n background-color: rgba(155, 155, 155);\n}\n\n[data-theme='dark'] .pdl-btn.pdl-tag {\n background-color: rgba(255, 255, 255, 0.4);\n}\n\n[data-theme='dark'] .pdl-btn.pdl-modal-tag {\n background-color: rgba(255, 255, 255, 0.4);\n}\n\n[data-theme='dark'] .pdl-btn.pdl-modal-tag:hover {\n background-color: rgba(255, 255, 255, 0.6);\n}\n\n[data-theme='dark'] .pdl-wrap .pdl-filter:hover,\n[data-theme='dark'] .pdl-stop.pdl-stop:hover,\n[data-theme='dark'] .pdl-btn-all.pdl-btn-all:hover {\n color: rgb(214, 214, 214);\n}\n\n/* modal */\n[data-theme='dark'] .pdl-dialog {\n background-color: rgb(31, 31, 31);\n}\n\n[data-theme='dark'] .pdl-dialog-footer button {\n background-color: rgb(245, 245, 245);\n}\n\n[data-theme='dark'] .pdl-dialog-content hr {\n border-top: 1px solid rgba(255, 255, 255, 0.3);\n}\n\n/* others setting */\n[data-theme='dark'] #pdl-setting-others .pdl-options:hover {\n background-color: rgba(255, 255, 255, 0.1);\n}\n";
  44. n(css,{});
  45.  
  46. var IllustType;
  47. (function (IllustType) {
  48. IllustType[IllustType["illusts"] = 0] = "illusts";
  49. IllustType[IllustType["manga"] = 1] = "manga";
  50. IllustType[IllustType["ugoira"] = 2] = "ugoira";
  51. })(IllustType || (IllustType = {}));
  52. var BookmarkRestrict;
  53. (function (BookmarkRestrict) {
  54. BookmarkRestrict[BookmarkRestrict["public"] = 0] = "public";
  55. BookmarkRestrict[BookmarkRestrict["private"] = 1] = "private";
  56. })(BookmarkRestrict || (BookmarkRestrict = {}));
  57.  
  58. const defaultSettings = Object.freeze({
  59. version: '0.8.8',
  60. ugoriaFormat: 'zip',
  61. folderPattern: 'pixiv/{artist}',
  62. filenamePattern: '{artist}_{title}_{id}_p{page}',
  63. tagLang: 'ja',
  64. showMsg: true,
  65. log: false,
  66. filter: {
  67. exclude_downloaded: false,
  68. [IllustType.illusts]: true,
  69. [IllustType.manga]: true,
  70. [IllustType.ugoira]: true
  71. },
  72. bundleIllusts: false,
  73. bundleManga: false,
  74. addBookmark: false,
  75. addBookmarkWithTags: false,
  76. privateR18: false
  77. });
  78. const regexp = {
  79. preloadData: /"meta-preload-data" content='(.*?)'>/,
  80. globalData: /"meta-global-data" content='(.*?)'>/,
  81. artworksPage: /artworks\/(\d+)$/,
  82. userPage: /users\/(\d+)/,
  83. bookmarkPage: /users\/\d+\/bookmarks\/artworks/,
  84. userPageTags: /users\/\d+\/(artworks|illustrations|manga|bookmarks(?!artworks))/,
  85. ppSearchPage: /\/tags\/.*\/(artworks|illustrations|manga)/,
  86. suscribePage: /bookmark_new_illust/,
  87. activityHref: /illust_id=(\d+)/,
  88. originSrcPageNum: /(?<=_p)\d+/,
  89. followLatest: /\/bookmark_new_illust(?:_r18)?\.php/
  90. };
  91. const depsUrls = {
  92. gifWorker: 'https://cdnjs.cloudflare.com/ajax/libs/gif.js/0.2.0/gif.worker.js',
  93. pako: 'https://cdnjs.cloudflare.com/ajax/libs/pako/2.0.4/pako.min.js',
  94. upng: 'https://cdnjs.cloudflare.com/ajax/libs/upng-js/2.1.0/UPNG.min.js'
  95. };
  96. const creditCode = `<img style="display: block; margin: 1em auto; width: 200px"
  97. src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJYAAACWCAYAAAA8AXHiAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAACxMAAAsTAQCanBgAADzWSURBVHhe7Z0HnBRF9sdrMyxZoiCegKCCOZ3xzKcCgp56CoqKohj+6pmznBGMGM+cQDALBgwneuZ4nieGU5KKIoqAIrCEDf1/3zddQ29vd09P2oD++BQzW93TXV316uWqLvhZUCowv8P8+OOP5uOPPzY9evQwvXr1MoWFhe6RYDiOY2bPnm2+/vprs+WWW5p27dq5R37bWCEwNTU1K6WDfvNYvny5M2LECKd58+bOjjvu6Hz77bfukXB88803zuabb+60aNHCueqqq5xVq1a5R+JB+t79tmZBnms5U7IqQWdrHhYvXmymTZvm/hWNlStXmpkzZxohMOVCcK9UKCkpMT/88INZtmyZqa6uNgUFBe6RaHzxxRdm6dKl7l9rJKqieX0Txi+//GKGDh1qhKOYs846y1RVRc+fsrIy0759eyWOyspK2Ll7JBxrr722ef31182ECRPMyJEjTXFxsXskHOeee67p27ev+b//+79Y92iqWGMJCy4yb9481YNeeOGFlBwINbNbt26qV8G94g567969lYDXWmsttyYatIM2iWqLGuLWrnlYYwmrdevWyoHATz/9ZL777jv9HgYIqmPHjqaoqEg5FmIUAsg1xo4dax588EFz/fXXm/Lycrd2zUOTIiy4CDqNKMluTThatmxpNt54YxVPiMXvv/8+JaF07dpVCYvrw1nywVHatm1rDjvsMLU64+hk6G60Jc4zNyY0GcJCPI0ePdoMGjTIiAWmXCUKDNr+++9vNthgA7PeeuupmEs1kB06dFDCgqDgcg0tqrj/zTffbAYPHmzGjRuXUk9sVJDGL5OZ3OghHMfZZpttYDmO6DXOl19+6R4JhwyEI7PdmTNnjn5PhX/9619Oq1at9B4nn3yyuiDiQvrR/ZY7VFRUOMJFtT177723s2TJEvdI44b0xZImw7FQjvv166cc5auvvjJTp05VMREFzu3UqZPp3r27fk8FxJQ9LxXH4t633Xab2XrrrVVfSsVBM0GzZs3MlVdeaf70pz+Z4cOHm+bNm7tHmgCaCscC77//vtOmTRudwZtuuqkjg+8eiQYz/7PPPnNk8N2aYMyePdvp3LmzXn+PPfZwfv31V/dIXcyfP9/Zbrvt9Ny+ffsqV4xC3DYEAU4lhOz+1fjRpDiWtNdstdVWZt9999W/P/30UzNlyhT9ngrXXHON2XHHHdWHFAUh2mRYZtGiRZEcCwcnliOAy6XyYV133XXahlGjRrk18dGiRYuU4aVGh8bAscTaiz0jZ8yYoSEUabrqWmLxuUfCIaJEzxcCcObNm+fW1gXtsHrcH/7wh0iO+MUXXzg9evTQc//4xz86QojukbrgulZXEmPCrQ0HoaFrr73WkYngiBUcW3+jD9PRC/OFRsGx3nvvPXPUUUeZ++67L5aesv7665tjjjlGvwuRmXvuuUe/R0EGXj/Ri15++WX9HgS87zvttJN+h3NFcSFcHzhhgYjPSP1HjAJ1kwARsfoZhaefflq565gxY8xTTz0Vyy1BKOqGG25QVwb6Z4OjoTnWgQce6EjHafD3zTffdGuj8c477ziiaMfiFkBEpiNEo+eLEhzKHeEMIt6cCy+8UC3EKC767rvvOq1bt9ZrCqG7tcE47rjj9DzKs88+69YGg/tvttlmei5t5j5xMHPmTGejjTbSvtxnn30y0uVyhUbBsbB40FGYccOGDdMwTCrgm8IaYyb/73//M//+97/dI8HgfOl09XSjR4VxAOrx2F922WVm1113jeQUePJFIdfv1sMfBvxjOGy32GILs+2227q1wXj44YfNl19+qd+HDBlixEjR76lAG0jd6dmzp+qhcazgvKKhORbWkrDv5Iw++OCDI60xi0cffVR1LazEl156ya0NBj6s//znP8ot0tFZoiDErHqTEIxytygsXLjQefHFF9UqTKVL3nXXXcq9hRidV155Ja22LliwQP176HQNCThWAYQlM7NBg1b//e9/zX777adcoFWrVkaIxuy99956LIxr4IV+5plnVNfBw17fPh70tY8++kitVThFrjgEz/PEE08ohxswYECsjInGBumTpQ3OsZiRzOKLLrrIKSkpUa5Fot3PP//snpFbYOkxo9PhBL8jPcCxGlzHgiPhozn99NONKPJah2ed2GBcEGT+8MMPk7E0eTb99IPcKeJuf//732OnxdQnPvnkkzUnAbChOZYX0qnO3/72N+eBBx5QX04criImv3PEEUeolYjfB+4X9jtSj+WRndLSUmfatGlubV288MILzvHHH+98+umnbk1d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"
  98. />`;
  99.  
  100. const lang = document.documentElement.getAttribute('lang')?.toLowerCase() ||
  101. 'en';
  102. const i18nLib = {
  103. en: {
  104. bookmarks: 'Bookmarks',
  105. bookmarks_public: 'Public',
  106. bookmarks_private: 'Private',
  107. stop: 'Stop',
  108. dlbar_category_works: 'Works',
  109. dlbar_filter_illusts: 'Illustrations',
  110. dlbar_filter_manga: 'Manga',
  111. dlbar_filter_ugoria: 'Ugoria',
  112. dlbar_filter_exclude_downloaded: 'Exclude downloaded',
  113. 'dlbar.follow_latest.category_all.single': 'All (page)',
  114. 'dlbar.follow_latest.category_all.all': 'All (batch)',
  115. 'dlbar.follow_latest.category_r18.single': 'R-18 (page)',
  116. 'dlbar.follow_latest.category_r18.all': 'R-18 (batch)',
  117. feedback: 'Feedback',
  118. gm_menu_setting: 'Settings',
  119. tabs_nav_filename: 'Filename',
  120. tags_lang: 'Tags language: ',
  121. tags_tips: '{artist}, {artistID}, {title}, {id}, {page}, {tags}',
  122. tags_tips2: 'Note: Tags language may not be the language you selected, <a href="https://crowdin.com/project/pixiv-tags" target="_blank">some tags without translations</a> may still be in other languages.',
  123. folder: 'Folder:',
  124. folder_tips: "I don't need subfolder",
  125. folder_tips2: "If you don't need a subfolder, just leave the folder name blank",
  126. folder_vm_tips: "VM doesn't support",
  127. folder_api_tips: 'Need Browser Api',
  128. filename: 'FileName:',
  129. filename_tips: 'Your Name?',
  130. tabs_nav_history: 'History',
  131. 'modal_history_content.import_btn': 'Import',
  132. 'modal_history_content.export_btn': 'Export',
  133. 'modal_history_content.clear_btn': 'Clear',
  134. clear_history_tips: 'Do you really want to clear history?',
  135. tabs_nav_ugoria: 'Ugoria',
  136. modal_ugoria_format_title: 'Ugoria Format:',
  137. tabs_nav_others: 'Others',
  138. 'modal_others_content.bundle_illusts': 'Pack multi-page illustrations into a .zip archive',
  139. 'modal_others_content.bundle_manga': 'Pack manga into a .zip archive',
  140. 'modal_others_content.add_bookmark': 'Bookmark work when downloading a single work',
  141. 'modal_others_content.add_bookmark_tags': 'Add works tags',
  142. 'modal_others_content.add_bookmark_private_r18': 'Bookmark R-18 works to private category',
  143. tabs_nav_donate: 'Feedback / Donate'
  144. },
  145. 'zh-cn': {
  146. bookmarks: '收藏',
  147. bookmarks_public: '公开',
  148. bookmarks_private: '不公开',
  149. stop: '停止',
  150. dlbar_category_works: '作品',
  151. dlbar_filter_illusts: '插画',
  152. dlbar_filter_manga: '漫画',
  153. dlbar_filter_ugoria: '动图',
  154. dlbar_filter_exclude_downloaded: '排除已下载图片',
  155. 'dlbar.follow_latest.category_all.single': '全部(单页)',
  156. 'dlbar.follow_latest.category_all.all': '全部(批量)',
  157. 'dlbar.follow_latest.category_r18.single': 'R-18(单页)',
  158. 'dlbar.follow_latest.category_r18.all': 'R-18(批量)',
  159. feedback: '有问题or想建议?这里反馈',
  160. gm_menu_setting: '设置',
  161. tabs_nav_filename: '文件名',
  162. tags_lang: '标签语言:',
  163. tags_tips: '{artist}:作者, {artistID}:作者ID, {title}:作品标题, {id}:作品pixiv ID, {page}:页码, {tags}:作品标签。',
  164. tags_tips2: '请注意:标签翻译不一定是你选择的语言,部分<a href="https://crowdin.com/project/pixiv-tags" target="_blank">无对应语言翻译的标签</a>仍可能是其他语言。',
  165. folder: '文件夹名:',
  166. folder_tips: '我不想保存到子文件夹',
  167. folder_tips2: '如果不想保存到画师目录,文件夹名留空即可。',
  168. folder_vm_tips: 'Violentmonkey不支持',
  169. folder_api_tips: '需要Browser Api',
  170. filename: '文件名:',
  171. filename_tips: '你的名字?',
  172. tabs_nav_history: '历史记录',
  173. 'modal_history_content.import_btn': '导入记录',
  174. 'modal_history_content.export_btn': '导出记录',
  175. 'modal_history_content.clear_btn': '清除记录',
  176. clear_history_tips: '真的要清除历史记录吗?',
  177. tabs_nav_ugoria: '动图',
  178. modal_ugoria_format_title: '动图格式:',
  179. tabs_nav_others: '其它',
  180. 'modal_others_content.bundle_illusts': '将多页插图打包为.zip压缩包',
  181. 'modal_others_content.bundle_manga': '将多页漫画作品打包为.zip压缩包',
  182. 'modal_others_content.add_bookmark': '下载单个作品时收藏作品',
  183. 'modal_others_content.add_bookmark_tags': '收藏时添加作品标签',
  184. 'modal_others_content.add_bookmark_private_r18': '将R-18作品收藏到不公开类别',
  185. tabs_nav_donate: '反馈 / 赞赏'
  186. },
  187. zh: {},
  188. ja: {},
  189. ko: {},
  190. 'zh-tw': {}
  191. };
  192. i18nLib.en = Object.create(i18nLib['zh-cn'], Object.getOwnPropertyDescriptors(i18nLib.en));
  193. i18nLib.ja = Object.create(i18nLib.en);
  194. i18nLib.ko = Object.create(i18nLib.en);
  195. i18nLib['zh-tw'] = Object.create(i18nLib['zh-cn']);
  196. i18nLib.zh = i18nLib['zh-cn'];
  197. const i18n = (key) => i18nLib[lang]?.[key] || `i18n[${lang}][${key}] not found`;
  198. const modalHtml = {
  199. upgradeMsgTitle: `<h3>Pixiv Downloader ${defaultSettings.version}</h3>`,
  200. upgradeMsgContent: ` <div class="pdl-changelog">
  201. <p>可乐涨价,我的暑假结束了...</p>
  202. <h4>新增</h4>
  203. <ul>
  204. <li>现在可以批量下载“已关注用户的新作”。</li>
  205. <li>——可以选择下载单页或全部(最多34页)</li>
  206. <li>——点击“全部”或“R-18”按钮切换需要下载的作品类别</li>
  207. </ul>
  208. <h4>其它</h4>
  209. <ul>
  210. <li>稍微提高了webp转换速度</li>
  211. <li>一些其它调整</li>
  212. </ul>
  213. </div>`,
  214. modalCreditFooter: `<style>.pdl-dialog-footer {
  215. position: relative;
  216. font-size: 12px;
  217. }</style><details style="margin-top: 1.5em;">
  218. <summary style="display: inline-block; list-style: none; cursor: pointer; color: #0096fa; text-decoration: underline">脚本还行?请我喝杯可乐吧!</summary>
  219. ${creditCode}
  220. <p style="text-align: center">愿你每天都能找到对的色图,就像我每天都能喝到香草味可乐</p>
  221. </details>`,
  222. modalFeedback: `<a target="_blank" style="position: absolute; right: 0px; top: 0px; color: #0096fa; text-decoration: underline" href="https://greasyfork.org/zh-CN/scripts/432150-pixiv-downloader/feedback">${i18n('feedback')}</a>`,
  223. modalSettingsContent: ` <div>
  224. <div class="pdl-tabs-nav">
  225. <div class="pdl-tabs__active-bar"></div>
  226. </div>
  227. <div class="pdl-tabs-content">
  228. </div>
  229. </div>`,
  230. modalSettingFilename: ` <div id="pdl-setting-filename">
  231. <div>
  232. <div class="pdl-input-wrap">
  233. <label for="pdlfolder">${i18n('folder')}</label>
  234. <input type="text" id="pdlfolder" maxlength="100" />
  235. <button id="pdl-filename-folder-reset" class="pdl-dialog-button icon" disabled>↺</button>
  236. <button id="pdl-filename-folder-confirm" class="pdl-dialog-button icon primary" disabled>✓</button>
  237. </div>
  238. <div class="pdl-input-wrap">
  239. <label for="pdlfilename">${i18n('filename')}</label>
  240. <input
  241. type="text"
  242. id="pdlfilename"
  243. placeholder="${i18n('filename_tips')}"
  244. required
  245. maxlength="100"
  246. />
  247. <button id="pdl-filename-filename-reset" class="pdl-dialog-button icon" disabled>↺</button>
  248. <button id="pdl-filename-filename-confirm" class="pdl-dialog-button icon primary" disabled>✓</button>
  249. </div>
  250. </div>
  251. <div class="tags-option">
  252. <span class="tags-title">${i18n('tags_lang')}</span>
  253. <div class="tags-content">
  254. <div class="tags-item">
  255. <input type="radio" name="lang" id="lang_ja" value="ja" />
  256. <label for="lang_ja">日本語(default)</label>
  257. </div>
  258. <div class="tags-item">
  259. <input type="radio" name="lang" id="lang_zh" value="zh" />
  260. <label for="lang_zh">简中</label>
  261. </div>
  262. <div class="tags-item">
  263. <input type="radio" name="lang" id="lang_zh_tw" value="zh_tw" />
  264. <label for="lang_zh_tw">繁中</label>
  265. </div>
  266. <div class="tags-item">
  267. <input type="radio" name="lang" id="lang_en" value="en" />
  268. <label for="lang_en">English</label>
  269. </div>
  270. </div>
  271. </div>
  272. <p style="font-size: 14px; margin: 0.5em 0">
  273. ${i18n('tags_tips')}
  274. </p>
  275. <p style="font-size: 14px; margin: 0.5em 0">
  276. ${i18n('folder_tips2')}
  277. </p>
  278. <p style="font-size: 14px; margin: 0.5em 0">
  279. ${i18n('tags_tips2')}
  280. </p>
  281. </div>`,
  282. modalSettingUgoria: ` <div id="pdl-setting-ugoria">
  283. <p class="option-header">${i18n('modal_ugoria_format_title')}</p>
  284. <div id="pdl-ugoria-format-wrap">
  285. <div class="pdl-ugoria-format-item">
  286. <input
  287. type="radio"
  288. id="pdl-ugoria-zip"
  289. value="zip"
  290. name="format"
  291. /><label for="pdl-ugoria-zip">Zip</label>
  292. </div>
  293. <div class="pdl-ugoria-format-item">
  294. <input
  295. type="radio"
  296. id="pdl-ugoria-gif"
  297. value="gif"
  298. name="format"
  299. /><label for="pdl-ugoria-gif">Gif</label>
  300. </div>
  301. <div class="pdl-ugoria-format-item">
  302. <input
  303. type="radio"
  304. id="pdl-ugoria-apng"
  305. value="png"
  306. name="format"
  307. /><label for="pdl-ugoria-apng">Png</label>
  308. </div>
  309. <div class="pdl-ugoria-format-item">
  310. <input
  311. type="radio"
  312. id="pdl-ugoria-webm"
  313. value="webm"
  314. name="format"
  315. /><label for="pdl-ugoria-webm">Webm</label>
  316. </div>
  317. <div class="pdl-ugoria-format-item">
  318. <input
  319. type="radio"
  320. id="pdl-ugoria-webp"
  321. value="webp"
  322. name="format"
  323. /><label for="pdl-ugoria-webp">Webp</label>
  324. </div>
  325. </div>
  326. </div>`,
  327. modalSettingHistory: ` <div id="pdl-setting-history">
  328. <div>
  329. <button id="pdl-export" class="btn-history pdl-dialog-button primary">
  330. ${i18n('modal_history_content.export_btn')}
  331. </button>
  332. </div>
  333. <div>
  334. <input
  335. type="file"
  336. id="pdl-import"
  337. accept=".txt"
  338. style="display: none"
  339. /><button id="pdl-import-btn" class="btn-history pdl-dialog-button primary">
  340. ${i18n('modal_history_content.import_btn')}
  341. </button>
  342. </div>
  343. <div>
  344. <button id="pdl-clear-history" class="btn-history pdl-dialog-button primary">
  345. ${i18n('modal_history_content.clear_btn')}
  346. </button>
  347. </div>
  348. </div>`,
  349. modalSettingOthers: ` <div id="pdl-setting-others">
  350. <div>
  351. <label class="pdl-options"
  352. ><input
  353. id="pdl-options-bundle-illusts"
  354. type="checkbox"
  355. class="pdl-checkbox"
  356. /><span
  357. >${i18n('modal_others_content.bundle_illusts')}</span
  358. ></label
  359. >
  360. </div>
  361. <hr />
  362. <div>
  363. <label class="pdl-options"
  364. ><input
  365. id="pdl-options-bundle-manga"
  366. type="checkbox"
  367. class="pdl-checkbox"
  368. /><span
  369. >${i18n('modal_others_content.bundle_manga')}</span
  370. ></label
  371. >
  372. </div>
  373. <hr />
  374. <div>
  375. <label class="pdl-options"
  376. ><input
  377. id="pdl-options-add-bookmark"
  378. type="checkbox"
  379. class="pdl-checkbox"
  380. /><span
  381. >${i18n('modal_others_content.add_bookmark')}</span
  382. ></label
  383. >
  384. </div>
  385. <hr />
  386. <div>
  387. <label class="pdl-options sub-option"
  388. ><input
  389. id="pdl-options-add-bookmark-tags"
  390. type="checkbox"
  391. class="pdl-checkbox"
  392. /><span
  393. >${i18n('modal_others_content.add_bookmark_tags')}</span
  394. ></label
  395. >
  396. </div>
  397. <hr class="sub"/>
  398. <div>
  399. <label class="pdl-options sub-option"
  400. ><input
  401. id="pdl-options-add-bookmark-private-r18"
  402. type="checkbox"
  403. class="pdl-checkbox"
  404. /><span
  405. >${i18n('modal_others_content.add_bookmark_private_r18')}</span
  406. ></label
  407. >
  408. </div>
  409. </div>`,
  410. modalSettingDonate: ` <div id="pdl-setting-donate">
  411. ${creditCode}
  412. <p>如果脚本有帮助到你,欢迎扫码请我喝杯可乐 ^_^</p>
  413. <p><a target="_blank" style="color: #0096fa; text-decoration: underline" href="https://greasyfork.org/zh-CN/scripts/432150-pixiv-downloader/feedback">${i18n('feedback')}</a></p>
  414. </div>`
  415. };
  416.  
  417. function add$1(pixivId) {
  418. this._records.add(pixivId);
  419. localStorage.setItem(`pdlTemp-${pixivId}`, '');
  420. }
  421. function has(pixivId) {
  422. return this._records.has(pixivId);
  423. }
  424. function getHistory() {
  425. const storage = localStorage.pixivDownloader || '[]';
  426. return new Set(JSON.parse(storage));
  427. }
  428. function updateHistory() {
  429. Object.keys(localStorage).forEach((key) => {
  430. const matchResult = /pdlTemp-(\d+)/.exec(key);
  431. if (matchResult) {
  432. this._records.add(matchResult[1]);
  433. localStorage.removeItem(matchResult[0]);
  434. }
  435. });
  436. this.saveHistory();
  437. }
  438. function clearHistory() {
  439. const isConfirm = confirm(i18n('clear_history_tips'));
  440. if (!isConfirm)
  441. return;
  442. this.updateHistory();
  443. this._records = new Set();
  444. localStorage.pixivDownloader = '[]';
  445. location.reload();
  446. }
  447. function saveHistory(historyArr) {
  448. if (historyArr) {
  449. localStorage.pixivDownloader = JSON.stringify(historyArr);
  450. }
  451. else {
  452. localStorage.pixivDownloader = JSON.stringify([...this._records]);
  453. }
  454. }
  455. function getAll() {
  456. return [...this._records];
  457. }
  458. const pixivHistory = {
  459. _records: getHistory(),
  460. add: add$1,
  461. has,
  462. getAll,
  463. updateHistory,
  464. saveHistory,
  465. clearHistory
  466. };
  467.  
  468. function debugLog(...msgs) {
  469. }
  470.  
  471. function sleep(delay) {
  472. return new Promise((resolve) => {
  473. setTimeout(resolve, delay);
  474. });
  475. }
  476. function wakeableSleep(delay) {
  477. let wake = () => void {};
  478. const sleep = new Promise((r) => {
  479. setTimeout(r, delay);
  480. wake = r;
  481. });
  482. return {
  483. wake,
  484. sleep
  485. };
  486. }
  487. function getSelfId() {
  488. return document.querySelector('#qualtrics_user-id')
  489. ?.textContent;
  490. }
  491. const env = {
  492. isViolentmonkey: GM_info.scriptHandler === 'Violentmonkey',
  493. isBlobDlAvaliable: !(navigator.userAgent.includes('Firefox') &&
  494. GM_info.scriptHandler === 'Tampermonkey' &&
  495. parseFloat(GM_info.version ?? '') > 4.17),
  496. isSupportSubpath: GM_info.downloadMode && GM_info.downloadMode === 'browser'
  497. };
  498.  
  499. function getSettings() {
  500. let settings;
  501. if (!localStorage.pdlSetting) {
  502. settings = { ...defaultSettings };
  503. saveSettings(settings);
  504. }
  505. else {
  506. settings = JSON.parse(localStorage.pdlSetting);
  507. if (settings.version !== defaultSettings.version) {
  508. settings.version = defaultSettings.version;
  509. settings.showMsg = true;
  510. settings = { ...defaultSettings, ...settings };
  511. saveSettings(settings);
  512. }
  513. }
  514. return settings;
  515. }
  516. function saveSettings(settingObj) {
  517. settingObj = settingObj || settings;
  518. localStorage.pdlSetting = JSON.stringify(settingObj);
  519. }
  520. function upgradeSettings(key, value) {
  521. if (key in settings && settings[key] !== value) {
  522. settings[key] = value;
  523. saveSettings();
  524. }
  525. }
  526. const settings = getSettings();
  527.  
  528. const handleWorker = `
  529. let webpApi = {};
  530. Module.onRuntimeInitialized = () => {
  531. webpApi = {
  532. init: Module.cwrap('init', '', ['number', 'number', 'number']),
  533. createBuffer: Module.cwrap('createBuffer', 'number', ['number']),
  534. addFrame: Module.cwrap('addFrame', 'number', ['number', 'number', 'number']),
  535. generate: Module.cwrap('generate', 'number', []),
  536. freeResult: Module.cwrap('freeResult', '', []),
  537. getResultPointer: Module.cwrap('getResultPointer', 'number', []),
  538. getResultSize: Module.cwrap('getResultSize', 'number', []),
  539. };
  540.  
  541. postMessage('ok');
  542. };
  543.  
  544. onmessage = (evt) => {
  545. const { data, delays, lossless = 1, quality = 75, method = 4} = evt.data;
  546.  
  547. webpApi.init(lossless, quality, method);
  548. data.forEach((u8a, idx) => {
  549. const pointer = webpApi.createBuffer(u8a.length);
  550. Module.HEAPU8.set(u8a, pointer);
  551. webpApi.addFrame(pointer, u8a.length, delays[idx]);
  552. postMessage(idx);
  553. });
  554.  
  555. webpApi.generate();
  556. const resultPointer = webpApi.getResultPointer();
  557. const resultSize = webpApi.getResultSize();
  558. const result = new Uint8Array(Module.HEAP8.buffer, resultPointer, resultSize);
  559. postMessage(result);
  560. webpApi.freeResult();
  561. };`;
  562.  
  563. async function _requestJson(url, init) {
  564. const res = await fetch(url, init);
  565. if (!res.ok)
  566. throw new Error('[Error]fail to fetch:' + url + ', code:' + res.status);
  567. const data = await res.json();
  568. if (data.error)
  569. throw new Error('[Error]JSON return error: ' + data.message);
  570. return data.body;
  571. }
  572. async function getJson(url) {
  573. let json;
  574. let retry = 0;
  575. const MAX_RETRY = 3;
  576. do {
  577. try {
  578. debugLog('[Info]fetch url:', url);
  579. json = await _requestJson(url);
  580. }
  581. catch (error) {
  582. retry++;
  583. if (retry === MAX_RETRY)
  584. throw error;
  585. sleep(3000);
  586. }
  587. } while (!json);
  588. return json;
  589. }
  590. async function getArtworkHtml(illustId) {
  591. let params = '';
  592. if (settings.tagLang !== 'ja')
  593. params = '?lang=' + settings.tagLang;
  594. const res = await fetch('https://www.pixiv.net/artworks/' + illustId + params);
  595. if (!res.ok)
  596. throw new Error(String(res.status));
  597. return await res.text();
  598. }
  599. async function _getDeps(url) {
  600. return fetch(url).then((res) => {
  601. if (res.ok)
  602. return res.text();
  603. throw new Error(`[Pixiv Downloader]Fetch dependency ${url} failed: ${res.status} ${res.statusText}`);
  604. });
  605. }
  606. async function getGifWS() {
  607. let gifWS;
  608. if (!(gifWS = await GM_getValue('gifWS'))) {
  609. gifWS = await _getDeps(depsUrls.gifWorker);
  610. GM_setValue('gifWS', gifWS);
  611. }
  612. return gifWS;
  613. }
  614. async function getApngWS() {
  615. let apngWS;
  616. if (!(apngWS = await GM_getValue('apngWS'))) {
  617. const [pako, upng] = await Promise.all([
  618. _getDeps(depsUrls.pako),
  619. _getDeps(depsUrls.upng)
  620. ]);
  621. const upngScript = upng
  622. .replace('window.UPNG', 'UPNG')
  623. .replace('window.pako', 'pako');
  624. const workerEvt = `onmessage = (evt) => {
  625. const {data, width, height, delay } = evt.data;
  626. const png = UPNG.encode(data, width, height, 0, delay, {loop: 0});
  627. if (!png) console.log('Convert Apng failed.');
  628. postMessage(png);
  629. };`;
  630. apngWS = workerEvt + pako + upngScript;
  631. GM_setValue('apngWS', apngWS);
  632. }
  633. return apngWS;
  634. }
  635. function getWebpWS() {
  636. return workerChunk__default["default"] + handleWorker;
  637. }
  638. function addBookmark$1(illustId, token, tags, restrict) {
  639. return _requestJson('/ajax/illusts/bookmarks/add', {
  640. method: 'POST',
  641. headers: {
  642. accept: 'application/json',
  643. 'content-type': 'application/json; charset=utf-8',
  644. 'x-csrf-token': token
  645. },
  646. body: JSON.stringify({
  647. illust_id: illustId,
  648. restrict,
  649. comment: '',
  650. tags
  651. })
  652. });
  653. }
  654. function getFollowLatestWorks(page, mode) {
  655. return _requestJson(`/ajax/follow_latest/illust?p=${page}&mode=${mode}&lang=jp`);
  656. }
  657. const api = {
  658. getJson,
  659. getArtworkHtml,
  660. getGifWS,
  661. getApngWS,
  662. getWebpWS,
  663. getFollowLatestWorks,
  664. addBookmark: addBookmark$1
  665. };
  666.  
  667. function initialDeps() {
  668. return Promise.all([api.getGifWS(), api.getApngWS(), api.getWebpWS()]).then(([gif, apng, webp]) => {
  669. this._deps.gif = URL.createObjectURL(new Blob([gif], { type: 'text/javascript' }));
  670. this._deps.apng = URL.createObjectURL(new Blob([apng], { type: 'text/javascript' }));
  671. this._deps.webp = URL.createObjectURL(new Blob([webp], { type: 'text/javascript' }));
  672. return this;
  673. });
  674. }
  675. function _createImgElements(zip) {
  676. const eles = [];
  677. zip.forEach((_, file) => {
  678. eles.push(new Promise((resolve) => {
  679. const image = new Image();
  680. image.onload = () => {
  681. resolve(image);
  682. };
  683. file
  684. .async('blob')
  685. .then((blob) => void (image.src = URL.createObjectURL(blob)));
  686. }));
  687. });
  688. return Promise.all(eles);
  689. }
  690. function createInstance() {
  691. const zip = new JSZip__default["default"]();
  692. const freeApngWorkers = [];
  693. const freeWebpWorkers = [];
  694. const MAX_CONVERT = 2;
  695. let queue = [];
  696. let active = [];
  697. let isStop = false;
  698. const convertTo = {
  699. webp: (frames, convertMeta) => {
  700. return new Promise((resolve, reject) => {
  701. let worker;
  702. let reuse = false;
  703. if (freeWebpWorkers.length) {
  704. worker = freeWebpWorkers.shift();
  705. reuse = true;
  706. }
  707. else {
  708. worker = new Worker(createConverter._deps.webp);
  709. }
  710. convertMeta.abort = () => {
  711. reject('[Info]Convert stop manually, reject when convert webp. ' +
  712. convertMeta.id);
  713. convertMeta.isAborted = true;
  714. worker.terminate();
  715. };
  716. const workerLoad = new Promise((resolve) => {
  717. if (reuse)
  718. return resolve();
  719. worker.onmessage = (evt) => {
  720. if (evt.data === 'ok') {
  721. resolve();
  722. }
  723. };
  724. });
  725. const delays = convertMeta.source.framesInfo.map((frameInfo) => {
  726. return Number(frameInfo.delay);
  727. });
  728. const data = [];
  729. let completed = 0;
  730. frames.forEach((frame, idx) => {
  731. const canvas = document.createElement('canvas');
  732. const width = (canvas.width = frame.naturalWidth);
  733. const height = (canvas.height = frame.naturalHeight);
  734. const context = canvas.getContext('2d');
  735. if (!context)
  736. return;
  737. context.drawImage(frame, 0, 0, width, height);
  738. data.push(new Promise((resolve, reject) => {
  739. canvas.toBlob((blob) => {
  740. if (!blob)
  741. return reject('[Pixiv Downloader]toBlob failed: ' + idx);
  742. blob.arrayBuffer().then((buffer) => {
  743. const u8a = new Uint8Array(buffer);
  744. resolve(u8a);
  745. convertMeta.onProgress?.((++completed / frames.length) * 0.5, 'webp');
  746. });
  747. }, 'image/webp', 1);
  748. }));
  749. });
  750. workerLoad
  751. .then(() => Promise.all(data))
  752. .then((u8arrs) => {
  753. if (convertMeta.isAborted)
  754. return;
  755. worker.onmessage = (evt) => {
  756. if (typeof evt.data !== 'object') {
  757. debugLog('[Info]Webp convert phrase 2:', convertMeta.id, evt.data);
  758. convertMeta.onProgress?.(0.5 + (evt.data / frames.length) * 0.5, 'webp');
  759. }
  760. else {
  761. freeWebpWorkers.push(worker);
  762. resolve(new Blob([evt.data], { type: 'image/webp' }));
  763. }
  764. };
  765. worker.postMessage({ data: u8arrs, delays });
  766. })
  767. .catch(console.error);
  768. });
  769. },
  770. gif: (frames, convertMeta) => {
  771. return new Promise((resolve, reject) => {
  772. const gif = new GIF__default["default"]({
  773. workers: 2,
  774. quality: 10,
  775. workerScript: createConverter._deps.gif
  776. });
  777. convertMeta.abort = () => {
  778. gif.abort();
  779. convertMeta.isAborted = true;
  780. };
  781. debugLog('[Info]Start convert:', convertMeta.id);
  782. frames.forEach((frame, i) => {
  783. gif.addFrame(frame, {
  784. delay: convertMeta.source.framesInfo[i].delay
  785. });
  786. });
  787. gif.on('progress', (progress) => {
  788. debugLog('[Info]Convert progress:', convertMeta.id);
  789. if (typeof convertMeta.onProgress === 'function')
  790. convertMeta.onProgress(progress, 'gif');
  791. });
  792. gif.on('finished', (gifBlob) => {
  793. resolve(gifBlob);
  794. });
  795. gif.on('abort', () => {
  796. reject('[Info]Convert stop: abort. ' + convertMeta.id);
  797. });
  798. gif.render();
  799. });
  800. },
  801. png: (frames, convertMeta) => {
  802. return new Promise((resolve, reject) => {
  803. const canvas = document.createElement('canvas');
  804. const width = (canvas.width = frames[0].naturalWidth);
  805. const height = (canvas.height = frames[0].naturalHeight);
  806. const context = canvas.getContext('2d', { willReadFrequently: true });
  807. if (!context)
  808. return reject('[Error]Can not get canvas context');
  809. const data = [];
  810. const delay = convertMeta.source.framesInfo.map((frameInfo) => {
  811. return Number(frameInfo.delay);
  812. });
  813. frames.forEach((frame) => {
  814. if (convertMeta.isAborted)
  815. throw ('[Info]Convert stop manually, reject when drawImage. ' +
  816. convertMeta.id);
  817. context.clearRect(0, 0, width, height);
  818. context.drawImage(frame, 0, 0, width, height);
  819. data.push(context.getImageData(0, 0, width, height).data);
  820. });
  821. debugLog('[Info]Start convert:', convertMeta.id);
  822. let worker;
  823. if (freeApngWorkers.length) {
  824. worker = freeApngWorkers.shift();
  825. }
  826. else {
  827. worker = new Worker(createConverter._deps.apng);
  828. }
  829. convertMeta.abort = () => {
  830. reject('[Info]Convert stop manually, reject when convert apng. ' +
  831. convertMeta.id);
  832. convertMeta.isAborted = true;
  833. worker.terminate();
  834. };
  835. worker.onmessage = function (e) {
  836. freeApngWorkers.push(worker);
  837. if (!e.data) {
  838. return reject('[Error]apng data is null. ' + convertMeta.id);
  839. }
  840. const pngBlob = new Blob([e.data], { type: 'image/png' });
  841. resolve(pngBlob);
  842. };
  843. const cfg = { data, width, height, delay };
  844. worker.postMessage(cfg);
  845. });
  846. },
  847. webm: (frames, convertMeta) => {
  848. return new Promise((resolve, reject) => {
  849. const canvas = document.createElement('canvas');
  850. const width = (canvas.width = frames[0].naturalWidth);
  851. const height = (canvas.height = frames[0].naturalHeight);
  852. const context = canvas.getContext('2d');
  853. if (!context)
  854. return reject('[Error]Can not get canvas context');
  855. const stream = canvas.captureStream();
  856. const recorder = new MediaRecorder(stream, {
  857. mimeType: 'video/webm',
  858. videoBitsPerSecond: 80000000
  859. });
  860. const delay = convertMeta.source.framesInfo.map((frame) => {
  861. return Number(frame.delay);
  862. });
  863. const data = [];
  864. let frame = 0;
  865. const displayFrame = () => {
  866. context.clearRect(0, 0, width, height);
  867. context.drawImage(frames[frame], 0, 0);
  868. if (convertMeta.isAborted) {
  869. return recorder.stop();
  870. }
  871. setTimeout(() => {
  872. if (typeof convertMeta.onProgress === 'function')
  873. convertMeta.onProgress((frame + 1) / frames.length, 'webm');
  874. if (frame === frames.length - 1) {
  875. return recorder.stop();
  876. }
  877. else {
  878. frame++;
  879. }
  880. displayFrame();
  881. }, delay[frame]);
  882. };
  883. recorder.ondataavailable = (event) => {
  884. if (event.data && event.data.size) {
  885. data.push(event.data);
  886. }
  887. };
  888. recorder.onstop = () => {
  889. if (convertMeta.isAborted) {
  890. return reject('[info]Convert stop manually, reject when convert webm.' +
  891. convertMeta.id);
  892. }
  893. resolve(new Blob(data, { type: 'video/webm' }));
  894. };
  895. displayFrame();
  896. recorder.start();
  897. });
  898. }
  899. };
  900. const convert = (convertMeta) => {
  901. const { id, source, resolve, reject } = convertMeta;
  902. let frames;
  903. active.push(convertMeta);
  904. if (typeof convertMeta.onProgress === 'function')
  905. convertMeta.onProgress(0, 'zip');
  906. const newFolder = zip.folder(id);
  907. if (!newFolder)
  908. throw '[Error]Can not get new root folder';
  909. newFolder
  910. .loadAsync(source.data)
  911. .then(_createImgElements)
  912. .then((imgEles) => {
  913. zip.remove(id);
  914. frames = imgEles;
  915. if (convertMeta.isAborted)
  916. throw '[Info]Convert stop manually, reject when unzip. ' + id;
  917. return convertTo[source.format](frames, convertMeta);
  918. })
  919. .then(resolve)
  920. .catch(reject)
  921. .finally(() => {
  922. frames.forEach((frame) => URL.revokeObjectURL(frame.src));
  923. active.splice(active.indexOf(convertMeta), 1);
  924. if (queue.length)
  925. convert(queue.shift());
  926. });
  927. };
  928. return {
  929. add: (convertSource, handler) => {
  930. debugLog('[Info]Converter add', convertSource.id);
  931. return new Promise((resolve, reject) => {
  932. queue.push({
  933. id: convertSource.id,
  934. isAborted: false,
  935. source: convertSource,
  936. onProgress: handler?.onProgress,
  937. resolve,
  938. reject,
  939. abort() {
  940. this.isAborted = true;
  941. }
  942. });
  943. while (active.length < MAX_CONVERT && queue.length && !isStop) {
  944. convert(queue.shift());
  945. }
  946. });
  947. },
  948. del: (taskIds) => {
  949. if (!taskIds.length)
  950. return;
  951. isStop = true;
  952. active = active.filter((convertMeta) => {
  953. if (taskIds.includes(convertMeta.id)) {
  954. convertMeta.abort();
  955. }
  956. else {
  957. return true;
  958. }
  959. });
  960. queue = queue.filter((convertMeta) => !taskIds.includes(convertMeta.id));
  961. isStop = false;
  962. while (active.length < MAX_CONVERT && queue.length) {
  963. convert(queue.shift());
  964. }
  965. }
  966. };
  967. }
  968. const createConverter = {
  969. _deps: {
  970. gif: '',
  971. apng: '',
  972. webp: ''
  973. },
  974. initialDeps,
  975. createInstance
  976. };
  977.  
  978. const zip = new JSZip__default["default"]();
  979. function add(id, name, data) {
  980. zip.folder(id)?.file(name, data);
  981. }
  982. function bundle(id) {
  983. const folder = zip.folder(id);
  984. if (!folder)
  985. throw new Error('[Error]no folder:' + id);
  986. return folder.generateAsync({ type: 'blob' });
  987. }
  988. function remove(ids) {
  989. if (typeof ids === 'string') {
  990. zip.remove(ids);
  991. }
  992. else {
  993. const dirs = zip.filter((_, file) => file.dir).map((dir) => dir.name);
  994. const dirsToDel = ids.filter((id) => dirs.some((dir) => dir.includes(id)));
  995. dirsToDel.forEach((dir) => zip.remove(dir));
  996. }
  997. }
  998. function fileCount(id) {
  999. let count = 0;
  1000. zip.folder(id)?.forEach(() => count++);
  1001. return count;
  1002. }
  1003. const compressor = {
  1004. add,
  1005. bundle,
  1006. remove,
  1007. fileCount
  1008. };
  1009.  
  1010. const _saveWithoutSubpath = (blob, downloadMeta) => {
  1011. const dlEle = document.createElement('a');
  1012. dlEle.href = URL.createObjectURL(blob);
  1013. dlEle.download = downloadMeta.source.path;
  1014. dlEle.click();
  1015. URL.revokeObjectURL(dlEle.href);
  1016. downloadMeta.resolve(downloadMeta.taskId);
  1017. };
  1018. const _saveWithSubpath = (blob, downloadMeta) => {
  1019. const imgUrl = URL.createObjectURL(blob);
  1020. const request = {
  1021. url: imgUrl,
  1022. name: downloadMeta.source.path,
  1023. onerror: (error) => {
  1024. console.log('[pixiv downloader]Error when saving', downloadMeta.source.path);
  1025. URL.revokeObjectURL(imgUrl);
  1026. downloadMeta.reject(error);
  1027. },
  1028. onload: () => {
  1029. if (typeof downloadMeta.onLoad === 'function')
  1030. downloadMeta.onLoad();
  1031. URL.revokeObjectURL(imgUrl);
  1032. downloadMeta.resolve(downloadMeta.taskId);
  1033. }
  1034. };
  1035. downloadMeta.abort = GM_download(request).abort;
  1036. };
  1037. function createDownloader(converter) {
  1038. const MAX_DOWNLOAD = 5;
  1039. const MAX_RETRY = 3;
  1040. const INTERVAL = 500;
  1041. const TIMEOUT = 20000;
  1042. let isStop = false;
  1043. let queue = [];
  1044. let active = [];
  1045. let save;
  1046. if (env.isBlobDlAvaliable && env.isSupportSubpath) {
  1047. save = _saveWithSubpath;
  1048. }
  1049. else {
  1050. debugLog('[Info]scriptHandler:', GM_info.scriptHandler, GM_info.version);
  1051. save = _saveWithoutSubpath;
  1052. }
  1053. const download = (downloadMeta) => {
  1054. const { taskId, source } = downloadMeta;
  1055. debugLog('[Info]Start download:', source.path);
  1056. active.push(downloadMeta);
  1057. let abortObj;
  1058. const errorHandler = errorHandlerFactory(downloadMeta);
  1059. if ((!env.isBlobDlAvaliable || env.isViolentmonkey) &&
  1060. !('kind' in source)) {
  1061. abortObj = GM_download({
  1062. url: source.src,
  1063. name: source.path,
  1064. headers: {
  1065. referer: 'https://www.pixiv.net'
  1066. },
  1067. ontimeout: errorHandler,
  1068. onerror: errorHandler,
  1069. onload: async () => {
  1070. debugLog('[Info]Download complete', source.path);
  1071. if (typeof downloadMeta.onLoad === 'function')
  1072. downloadMeta.onLoad();
  1073. downloadMeta.resolve(taskId);
  1074. await sleep(INTERVAL);
  1075. active.splice(active.indexOf(downloadMeta), 1);
  1076. if (queue.length && !isStop)
  1077. download(queue.shift());
  1078. }
  1079. });
  1080. }
  1081. else {
  1082. abortObj = GM_xmlhttpRequest({
  1083. url: source.src,
  1084. timeout: TIMEOUT,
  1085. method: 'GET',
  1086. headers: {
  1087. referer: 'https://www.pixiv.net'
  1088. },
  1089. responseType: 'blob',
  1090. ontimeout: errorHandler,
  1091. onerror: errorHandler,
  1092. onprogress: (e) => {
  1093. if (e.lengthComputable &&
  1094. typeof downloadMeta.onProgress === 'function') {
  1095. downloadMeta.onProgress(e.loaded / e.total);
  1096. }
  1097. },
  1098. onload: async (e) => {
  1099. if (downloadMeta.state === 0 )
  1100. return debugLog();
  1101. if (!('kind' in source)) {
  1102. save(e.response, downloadMeta);
  1103. }
  1104. else if (source.kind === 'convert') {
  1105. const convertSource = {
  1106. id: taskId,
  1107. data: e.response,
  1108. format: settings.ugoriaFormat,
  1109. framesInfo: source.ugoiraMeta?.frames
  1110. };
  1111. converter
  1112. .add(convertSource, { onProgress: downloadMeta.onProgress })
  1113. .then((blob) => {
  1114. save(blob, downloadMeta);
  1115. }, downloadMeta.reject);
  1116. }
  1117. else if (source.kind === 'bundle') {
  1118. compressor.add(taskId, source.filename, e.response);
  1119. if (compressor.fileCount(taskId) === source.pageCount) {
  1120. compressor.bundle(taskId).then((blob) => {
  1121. save(blob, downloadMeta);
  1122. compressor.remove(taskId);
  1123. });
  1124. }
  1125. else {
  1126. downloadMeta.resolve(taskId);
  1127. if (typeof downloadMeta.onLoad === 'function')
  1128. downloadMeta.onLoad();
  1129. }
  1130. }
  1131. await sleep(INTERVAL);
  1132. active.splice(active.indexOf(downloadMeta), 1);
  1133. if (queue.length && !isStop)
  1134. download(queue.shift());
  1135. }
  1136. });
  1137. }
  1138. downloadMeta.abort = () => {
  1139. downloadMeta.state = 0 ;
  1140. abortObj.abort();
  1141. downloadMeta.reject('[Warning]xhr abort manually. ' + taskId);
  1142. };
  1143. };
  1144. function errorHandlerFactory(downloadMeta) {
  1145. return function () {
  1146. const { source } = downloadMeta;
  1147. debugLog('[Error]xmlhttpRequest timeout:', source.src);
  1148. downloadMeta.retry++;
  1149. if (downloadMeta.retry > MAX_RETRY) {
  1150. downloadMeta.reject('[Error]xmlhttpRequest failed: ' + source.src);
  1151. console.log('[pixiv downloader]Network error:', source.path, source.src);
  1152. active.splice(active.indexOf(downloadMeta), 1);
  1153. if (queue.length && !isStop)
  1154. download(queue.shift());
  1155. }
  1156. else {
  1157. debugLog('[Warning]retry xhr:', downloadMeta.retry, source.src);
  1158. download(downloadMeta);
  1159. }
  1160. };
  1161. }
  1162. function add(metas, handler = {}) {
  1163. if (metas.length < 1)
  1164. return Promise.resolve('');
  1165. const promises = [];
  1166. metas.forEach((source) => {
  1167. let downloadMeta;
  1168. promises.push(new Promise((resolve, reject) => {
  1169. downloadMeta = {
  1170. taskId: source.taskId,
  1171. source,
  1172. state: 1 ,
  1173. retry: 0,
  1174. onProgress: handler.onProgress,
  1175. onLoad: handler.onLoad,
  1176. resolve,
  1177. reject
  1178. };
  1179. queue.push(downloadMeta);
  1180. }));
  1181. });
  1182. while (active.length < MAX_DOWNLOAD && queue.length && !isStop) {
  1183. download(queue.shift());
  1184. }
  1185. return Promise.all(promises).then(([taskId]) => taskId);
  1186. }
  1187. function del(taskIds) {
  1188. if (!taskIds.length)
  1189. return;
  1190. isStop = true;
  1191. active = active.filter((downloadMeta) => {
  1192. if (taskIds.includes(downloadMeta.taskId)) {
  1193. downloadMeta.abort?.();
  1194. }
  1195. else {
  1196. return true;
  1197. }
  1198. });
  1199. queue = queue.filter((downloadMeta) => !taskIds.includes(downloadMeta.taskId));
  1200. isStop = false;
  1201. while (active.length < MAX_DOWNLOAD && queue.length) {
  1202. download(queue.shift());
  1203. }
  1204. }
  1205. return {
  1206. add,
  1207. del
  1208. };
  1209. }
  1210.  
  1211. const needBundle = (type) => {
  1212. return ((type === IllustType.manga && settings.bundleManga) ||
  1213. (type === IllustType.illusts && settings.bundleIllusts));
  1214. };
  1215. const replaceInvalidChar = (string) => {
  1216. if (!string)
  1217. return '';
  1218. const temp = document.createElement('div');
  1219. temp.innerHTML = string;
  1220. if (!temp.textContent)
  1221. return '';
  1222. return temp.textContent
  1223. .trim()
  1224. .replace(/^\.|\.$/g, '')
  1225. .replace(/[\u200b-\u200f\uFEFF\u202a-\u202e\\/:*?|]/g, '')
  1226. .replace(/"/g, "'")
  1227. .replace(/</g, '﹤')
  1228. .replace(/>/g, '﹥');
  1229. };
  1230. const getFilePath = ({ user, userId, title, tagStr, illustId, page, ext }, option = { needBundle: false, needConvert: false }) => {
  1231. let pathPattern;
  1232. if (settings.folderPattern &&
  1233. env.isSupportSubpath &&
  1234. (!option.needConvert || env.isBlobDlAvaliable) &&
  1235. !option.needBundle) {
  1236. pathPattern = settings.folderPattern + '/' + settings.filenamePattern;
  1237. }
  1238. else {
  1239. pathPattern = settings.filenamePattern;
  1240. }
  1241. if (option.needBundle && !settings.filenamePattern.includes('{page}')) {
  1242. pathPattern += '_{page}';
  1243. }
  1244. return (pathPattern
  1245. .replaceAll('{artist}', user)
  1246. .replaceAll('{artistID}', userId)
  1247. .replaceAll('{title}', title)
  1248. .replaceAll('{tags}', tagStr)
  1249. .replaceAll('{page}', String(page))
  1250. .replaceAll('{id}', illustId) + ext);
  1251. };
  1252. const makeTagsStr = (prev, cur, index, tagsArr) => {
  1253. const tag = settings.tagLang === 'ja' ? cur.tag : cur.translation?.['en'] || cur.tag;
  1254. if (index < tagsArr.length - 1) {
  1255. return prev + tag + '_';
  1256. }
  1257. else {
  1258. return prev + tag;
  1259. }
  1260. };
  1261. function filterWorks(works, option) {
  1262. const obj = {
  1263. unavaliable: [],
  1264. avaliable: [],
  1265. invalid: []
  1266. };
  1267. works.forEach((work) => {
  1268. if (!work.isBookmarkable) {
  1269. obj.unavaliable.push(work.id);
  1270. }
  1271. else if (option.exclude_downloaded && pixivHistory.has(work.id)) {
  1272. obj.invalid.push(work.id);
  1273. }
  1274. else if (!option[work.illustType]) {
  1275. obj.invalid.push(work.id);
  1276. }
  1277. else {
  1278. obj.avaliable.push(work.id);
  1279. }
  1280. });
  1281. return obj;
  1282. }
  1283. async function getFollowLatestGenerator(filterOption, mode, page) {
  1284. const MAX_PAGE = 34;
  1285. const MAX_ILLUSTS_PER_PAGE = 60;
  1286. let lastId;
  1287. let total;
  1288. let data;
  1289. let cache;
  1290. function findLastId(ids) {
  1291. return Math.min(...ids.map((id) => Number(id)));
  1292. }
  1293. if (page === undefined) {
  1294. data = await api.getFollowLatestWorks(1, mode);
  1295. const ids = data.page.ids;
  1296. total = ids.length;
  1297. lastId = findLastId(ids);
  1298. if (total === MAX_ILLUSTS_PER_PAGE) {
  1299. const secondPageData = await api.getFollowLatestWorks(2, mode);
  1300. const secondIds = secondPageData.page.ids;
  1301. const secondLastId = findLastId(secondIds);
  1302. if (secondLastId < lastId) {
  1303. lastId = secondLastId;
  1304. cache = secondPageData;
  1305. total += secondIds.length;
  1306. }
  1307. }
  1308. }
  1309. else {
  1310. data = await api.getFollowLatestWorks(page, mode);
  1311. total = data.page.ids.length;
  1312. }
  1313. async function* generateIds() {
  1314. yield filterWorks(data.thumbnails.illust, filterOption);
  1315. if (page === undefined) {
  1316. if (total === MAX_ILLUSTS_PER_PAGE)
  1317. return;
  1318. if (total < MAX_ILLUSTS_PER_PAGE * 2) {
  1319. yield filterWorks(cache.thumbnails.illust, filterOption);
  1320. return;
  1321. }
  1322. let currentPage = 3;
  1323. while (currentPage <= MAX_PAGE) {
  1324. const data = await api.getFollowLatestWorks(currentPage, mode);
  1325. const ids = data.page.ids;
  1326. const pageLastId = findLastId(ids);
  1327. if (pageLastId >= lastId) {
  1328. yield filterWorks(cache.thumbnails.illust, filterOption);
  1329. break;
  1330. }
  1331. lastId = pageLastId;
  1332. total += ids.length;
  1333. yield { ...filterWorks(cache.thumbnails.illust, filterOption), total };
  1334. cache = data;
  1335. currentPage++;
  1336. await sleep(3000);
  1337. }
  1338. }
  1339. }
  1340. return {
  1341. total,
  1342. generator: generateIds()
  1343. };
  1344. }
  1345. async function getChunksGenerator(userId, category, tag, rest, filterOption) {
  1346. const OFFSET = 48;
  1347. let requestUrl;
  1348. if (category === 'bookmarks') {
  1349. requestUrl = `https://www.pixiv.net/ajax/user/${userId}/illusts/bookmarks?tag=${tag}&offset=0&limit=${OFFSET}&rest=${rest}&lang=ja`;
  1350. }
  1351. else {
  1352. requestUrl = `https://www.pixiv.net/ajax/user/${userId}/${category}/tag?tag=${tag}&offset=0&limit=${OFFSET}&lang=ja`;
  1353. }
  1354. let head = 0;
  1355. const firstPageData = await api.getJson(requestUrl);
  1356. const total = firstPageData.total;
  1357. async function* generateIds() {
  1358. yield filterWorks(firstPageData.works, filterOption);
  1359. head += OFFSET;
  1360. while (head < total) {
  1361. const data = await api.getJson(requestUrl.replace('offset=0', 'offset=' + head));
  1362. head += OFFSET;
  1363. await sleep(3000);
  1364. yield filterWorks(data.works, filterOption);
  1365. }
  1366. }
  1367. return {
  1368. total,
  1369. generator: generateIds()
  1370. };
  1371. }
  1372. async function getAllWorksGenerator(userId, filterOption) {
  1373. const requestUrl = 'https://www.pixiv.net/ajax/user/' + userId + '/profile/all';
  1374. const profile = await api.getJson(requestUrl);
  1375. let illustIds = [];
  1376. let mangaIds = [];
  1377. if ((filterOption[IllustType.illusts] || filterOption[IllustType.ugoira]) &&
  1378. typeof profile.illusts === 'object') {
  1379. illustIds.push(...Object.keys(profile.illusts).reverse());
  1380. }
  1381. if (filterOption[IllustType.manga] && typeof profile.manga === 'object') {
  1382. mangaIds.push(...Object.keys(profile.manga).reverse());
  1383. }
  1384. if (filterOption.exclude_downloaded) {
  1385. illustIds = illustIds.filter((id) => !pixivHistory.has(id));
  1386. mangaIds = mangaIds.filter((id) => !pixivHistory.has(id));
  1387. }
  1388. async function* generateIds() {
  1389. const OFFSET = 48;
  1390. const baseUrl = 'https://www.pixiv.net/ajax/user/' + userId + '/profile/illusts';
  1391. let workCategory = 'illust';
  1392. while (illustIds.length > 0) {
  1393. let searchStr = '?';
  1394. const chunk = illustIds.splice(0, OFFSET);
  1395. searchStr +=
  1396. chunk.map((id) => 'ids[]=' + id).join('&') +
  1397. `&work_category=${workCategory}&is_first_page=0&lang=ja`;
  1398. const data = await api.getJson(baseUrl + searchStr);
  1399. await sleep(3000);
  1400. yield filterWorks(Object.values(data.works), filterOption);
  1401. }
  1402. workCategory = 'manga';
  1403. while (mangaIds.length > 0) {
  1404. let searchStr = '?';
  1405. const chunk = mangaIds.splice(0, OFFSET);
  1406. searchStr +=
  1407. chunk.map((id) => 'ids[]=' + id).join('&') +
  1408. `&work_category=${workCategory}&is_first_page=0&lang=ja`;
  1409. const data = await api.getJson(baseUrl + searchStr);
  1410. await sleep(3000);
  1411. yield filterWorks(Object.values(data.works), filterOption);
  1412. }
  1413. }
  1414. return {
  1415. total: illustIds.length + mangaIds.length,
  1416. generator: generateIds()
  1417. };
  1418. }
  1419. async function getArtworkData(illustId) {
  1420. const htmlText = await api.getArtworkHtml(illustId);
  1421. const preloadDataText = htmlText.match(regexp.preloadData);
  1422. if (!preloadDataText)
  1423. throw new Error('[Error]Fail to parse preload data.');
  1424. const preloadData = JSON.parse(preloadDataText[1]);
  1425. const illustData = preloadData.illust[illustId];
  1426. const globalDataText = htmlText.match(regexp.globalData);
  1427. if (!globalDataText)
  1428. throw new Error('[Error]Fail to parse global data.');
  1429. const globalData = JSON.parse(globalDataText[1]);
  1430. let ugoiraMeta;
  1431. if (illustData.illustType === IllustType.ugoira) {
  1432. ugoiraMeta = await api.getJson('/ajax/illust/' + illustId + '/ugoira_meta');
  1433. }
  1434. return {
  1435. illustData,
  1436. globalData,
  1437. ugoiraMeta
  1438. };
  1439. }
  1440. function getDownloadSource(artworkData, seletedPage) {
  1441. const { illustData, ugoiraMeta } = artworkData;
  1442. const { illustType, userName, userId, illustTitle, illustId, tags, pageCount } = illustData;
  1443. const pathInfo = {
  1444. user: replaceInvalidChar(userName) || 'userId-' + userId,
  1445. title: replaceInvalidChar(illustTitle) || 'illustId-' + illustId,
  1446. tagStr: replaceInvalidChar(tags.tags.reduce(makeTagsStr, '')),
  1447. illustId,
  1448. userId,
  1449. ext: '',
  1450. page: 0
  1451. };
  1452. const metas = [];
  1453. const taskId = illustId + '_' + Math.random().toString(36).slice(2);
  1454. if (illustType === IllustType.illusts || illustType === IllustType.manga) {
  1455. const firstImgSrc = illustData.urls.original;
  1456. const srcPrefix = firstImgSrc.slice(0, firstImgSrc.indexOf('_') + 2);
  1457. const extendName = firstImgSrc.slice(-4);
  1458. pathInfo.ext = extendName;
  1459. if (pageCount > 1 && seletedPage === undefined) {
  1460. if (needBundle(illustType)) {
  1461. const path = getFilePath({ ...pathInfo, ext: '.zip', page: pageCount });
  1462. for (let i = 0; i < pageCount; i++) {
  1463. pathInfo.page = i;
  1464. metas.push({
  1465. kind: 'bundle',
  1466. taskId,
  1467. path,
  1468. src: srcPrefix + i + extendName,
  1469. filename: getFilePath(pathInfo, { needBundle: true }),
  1470. pageCount
  1471. });
  1472. }
  1473. }
  1474. else {
  1475. for (let i = 0; i < pageCount; i++) {
  1476. pathInfo.page = i;
  1477. metas.push({
  1478. taskId,
  1479. path: getFilePath(pathInfo),
  1480. src: srcPrefix + i + extendName
  1481. });
  1482. }
  1483. }
  1484. }
  1485. else {
  1486. let src = firstImgSrc;
  1487. if (seletedPage !== undefined) {
  1488. src = srcPrefix + seletedPage + extendName;
  1489. pathInfo.page = seletedPage;
  1490. }
  1491. metas.push({
  1492. taskId,
  1493. path: getFilePath(pathInfo),
  1494. src
  1495. });
  1496. }
  1497. }
  1498. else if (illustType === IllustType.ugoira && ugoiraMeta) {
  1499. pathInfo.ext = '.' + settings.ugoriaFormat;
  1500. if (settings.ugoriaFormat !== 'zip') {
  1501. metas.push({
  1502. kind: 'convert',
  1503. ugoiraMeta,
  1504. taskId,
  1505. src: ugoiraMeta.originalSrc,
  1506. path: getFilePath(pathInfo, { needConvert: true })
  1507. });
  1508. }
  1509. else {
  1510. metas.push({
  1511. taskId,
  1512. src: ugoiraMeta.originalSrc,
  1513. path: getFilePath(pathInfo)
  1514. });
  1515. }
  1516. }
  1517. return metas;
  1518. }
  1519. const parser = {
  1520. getChunksGenerator,
  1521. getAllWorksGenerator,
  1522. getFollowLatestGenerator,
  1523. getArtworkData,
  1524. getDownloadSource
  1525. };
  1526.  
  1527. let converter;
  1528. let downloader;
  1529. async function initial() {
  1530. converter = await createConverter
  1531. .initialDeps()
  1532. .then((createConverter) => createConverter.createInstance());
  1533. downloader = createDownloader(converter);
  1534. }
  1535.  
  1536. function addBookmark(pdlBtn, illustId, token, tags) {
  1537. if (!settings.addBookmark)
  1538. return;
  1539. api
  1540. .addBookmark(illustId, token, settings.addBookmarkWithTags ? tags : [], settings.privateR18 && tags.includes('R-18')
  1541. ? BookmarkRestrict.private
  1542. : BookmarkRestrict.public)
  1543. .then(() => {
  1544. const bookmarkBtnRef = findBookmarkBtn(pdlBtn);
  1545. if (!bookmarkBtnRef)
  1546. return;
  1547. switch (bookmarkBtnRef.kind) {
  1548. case "main" : {
  1549. const pathBorder = bookmarkBtnRef.button.querySelector('svg g path');
  1550. pathBorder && (pathBorder.style.color = 'rgb(255, 64, 96)');
  1551. break;
  1552. }
  1553. case "sub" : {
  1554. const pathBorder = bookmarkBtnRef.button.querySelector('path');
  1555. pathBorder && (pathBorder.style.color = 'rgb(255, 64, 96)');
  1556. break;
  1557. }
  1558. case "rank" : {
  1559. bookmarkBtnRef.button.style.backgroundColor = 'rgb(255, 64, 96)';
  1560. break;
  1561. }
  1562. }
  1563. })
  1564. .catch((reason) => {
  1565. console.error(reason.message);
  1566. });
  1567. }
  1568. function findBookmarkBtn(pdlBtn) {
  1569. const bookmarkBtnRef = {};
  1570. if (pdlBtn.classList.contains('pdl-btn-sub')) {
  1571. const btn = pdlBtn.parentElement?.nextElementSibling?.querySelector('button[type="button"]');
  1572. if (btn) {
  1573. bookmarkBtnRef.kind = "sub" ;
  1574. bookmarkBtnRef.button = btn;
  1575. }
  1576. else {
  1577. const btn = pdlBtn.parentElement?.querySelector('div._one-click-bookmark');
  1578. if (btn) {
  1579. bookmarkBtnRef.kind = "rank" ;
  1580. bookmarkBtnRef.button = btn;
  1581. }
  1582. }
  1583. }
  1584. else if (pdlBtn.classList.contains('pdl-btn-main')) {
  1585. const btn = pdlBtn.parentElement?.parentElement?.querySelector('button.gtm-main-bookmark');
  1586. if (btn) {
  1587. bookmarkBtnRef.kind = "main" ;
  1588. bookmarkBtnRef.button = btn;
  1589. }
  1590. }
  1591. else {
  1592. return debugLog();
  1593. }
  1594. return bookmarkBtnRef;
  1595. }
  1596.  
  1597. function handleDownload(pdlBtn, illustId) {
  1598. let pageCount, pageComplete = 0, shouldDownloadPage;
  1599. const pageAttr = pdlBtn.getAttribute('should-download');
  1600. if (pageAttr) {
  1601. shouldDownloadPage = Number(pageAttr);
  1602. }
  1603. const onProgress = (progress = 0, type = null) => {
  1604. if (pageCount > 1)
  1605. return;
  1606. progress = Math.floor(progress * 100);
  1607. switch (type) {
  1608. case null:
  1609. pdlBtn.style.setProperty('--pdl-progress', progress + '%');
  1610. case 'gif':
  1611. case 'webm':
  1612. case 'webp':
  1613. pdlBtn.textContent = String(progress);
  1614. break;
  1615. case 'zip':
  1616. pdlBtn.textContent = '';
  1617. break;
  1618. }
  1619. };
  1620. const onLoad = function () {
  1621. if (pageCount < 2)
  1622. return;
  1623. const progress = Math.floor((++pageComplete / pageCount) * 100);
  1624. pdlBtn.textContent = String(progress);
  1625. pdlBtn.style.setProperty('--pdl-progress', progress + '%');
  1626. };
  1627. pdlBtn.classList.add('pdl-progress');
  1628. parser
  1629. .getArtworkData(illustId)
  1630. .then((artworkData) => {
  1631. const { illustData, globalData } = artworkData;
  1632. const { illustId, tags, bookmarkData } = illustData;
  1633. if (!bookmarkData) {
  1634. const { token } = globalData;
  1635. const tagsArr = tags.tags.map((item) => item.tag);
  1636. addBookmark(pdlBtn, illustId, token, tagsArr);
  1637. }
  1638. return parser.getDownloadSource(artworkData, shouldDownloadPage);
  1639. })
  1640. .then((sources) => {
  1641. pageCount = sources.length;
  1642. return downloader.add(sources, { onLoad, onProgress });
  1643. })
  1644. .then(() => {
  1645. pixivHistory.add(illustId);
  1646. pdlBtn.classList.remove('pdl-error');
  1647. pdlBtn.classList.add('pdl-complete');
  1648. })
  1649. .catch((err) => {
  1650. if (err)
  1651. console.log(err);
  1652. pdlBtn.classList.remove('pdl-complete');
  1653. pdlBtn.classList.add('pdl-error');
  1654. })
  1655. .finally(() => {
  1656. pdlBtn.innerHTML = '';
  1657. pdlBtn.style.removeProperty('--pdl-progress');
  1658. pdlBtn.classList.remove('pdl-progress');
  1659. });
  1660. }
  1661.  
  1662. function createPdlBtn(attributes, textContent = '', { addEvent } = { addEvent: true }) {
  1663. const ele = document.createElement('button');
  1664. ele.textContent = textContent;
  1665. if (!attributes)
  1666. return ele;
  1667. const { attrs, classList } = attributes;
  1668. if (classList && classList.length > 0) {
  1669. for (const cla of classList) {
  1670. ele.classList.add(cla);
  1671. }
  1672. }
  1673. if (attrs) {
  1674. for (const key in attrs) {
  1675. ele.setAttribute(key, attrs[key]);
  1676. }
  1677. }
  1678. if (addEvent) {
  1679. ele.addEventListener('click', (evt) => {
  1680. evt.preventDefault();
  1681. evt.stopPropagation();
  1682. const ele = evt.currentTarget;
  1683. if (!ele.classList.contains('pdl-progress')) {
  1684. handleDownload(ele, ele.getAttribute('pdl-id'));
  1685. }
  1686. });
  1687. }
  1688. return ele;
  1689. }
  1690. function getIllustId(node) {
  1691. const isLinkToArtworksPage = regexp.artworksPage.exec(node.href);
  1692. if (isLinkToArtworksPage) {
  1693. if (node.getAttribute('data-gtm-value') ||
  1694. node.classList.contains('gtm-illust-recommend-node-node') ||
  1695. node.classList.contains('gtm-discover-user-recommend-node') ||
  1696. node.classList.contains('work')) {
  1697. return isLinkToArtworksPage[1];
  1698. }
  1699. }
  1700. else {
  1701. const isActivityThumb = regexp.activityHref.exec(node.href);
  1702. if (isActivityThumb && node.classList.contains('work')) {
  1703. return isActivityThumb[1];
  1704. }
  1705. }
  1706. return '';
  1707. }
  1708. function createThunbnailsBtn(nodes) {
  1709. const isBookmarkPage = regexp.bookmarkPage.test(location.pathname);
  1710. nodes.forEach((e) => {
  1711. if (e.childElementCount !== 0 &&
  1712. !e.querySelector('.pdl-btn-sub')) {
  1713. const illustId = getIllustId(e);
  1714. if (illustId) {
  1715. const attrs = {
  1716. attrs: { 'pdl-id': illustId },
  1717. classList: ['pdl-btn', 'pdl-btn-sub']
  1718. };
  1719. if (pixivHistory.has(illustId))
  1720. attrs.classList.push('pdl-complete');
  1721. if (isBookmarkPage)
  1722. attrs.classList.push('pdl-btn-sub-bookmark');
  1723. e.appendChild(createPdlBtn(attrs));
  1724. }
  1725. }
  1726. });
  1727. }
  1728. function compatPixivPreviewer(nodes) {
  1729. const isPpSearchPage = regexp.ppSearchPage.test(location.pathname);
  1730. if (!isPpSearchPage)
  1731. return;
  1732. nodes.forEach((node) => {
  1733. const pdlEle = node.querySelector('.pdl-btn');
  1734. if (!pdlEle)
  1735. return false;
  1736. pdlEle.remove();
  1737. });
  1738. }
  1739.  
  1740. function createMainBtn(id) {
  1741. if (document.querySelector('.pdl-btn-main'))
  1742. return;
  1743. const handleBar = document.querySelector('main section section');
  1744. if (handleBar) {
  1745. const pdlBtnWrap = handleBar.lastElementChild.cloneNode();
  1746. const attrs = {
  1747. attrs: { 'pdl-id': id },
  1748. classList: ['pdl-btn', 'pdl-btn-main']
  1749. };
  1750. if (pixivHistory.has(id))
  1751. attrs.classList.push('pdl-complete');
  1752. pdlBtnWrap.appendChild(createPdlBtn(attrs));
  1753. handleBar.appendChild(pdlBtnWrap);
  1754. }
  1755. }
  1756. function createMultyWorksBtn(id) {
  1757. const works = document.querySelectorAll("[role='presentation'] > a");
  1758. if (works.length < 2)
  1759. return;
  1760. const containers = Array.from(works).map((node) => node.parentElement.parentElement);
  1761. if (containers[0].querySelector('.pdl-btn'))
  1762. return;
  1763. containers.forEach((node, idx) => {
  1764. const wrapper = document.createElement('div');
  1765. wrapper.classList.add('pdl-wrap-artworks');
  1766. const attrs = {
  1767. attrs: { 'pdl-id': id, 'should-download': String(idx) },
  1768. classList: ['pdl-btn', 'pdl-btn-sub', 'artworks']
  1769. };
  1770. wrapper.appendChild(createPdlBtn(attrs));
  1771. node.appendChild(wrapper);
  1772. });
  1773. }
  1774. const createPresentationBtn = (() => {
  1775. let observer, btn;
  1776. function cb(mutationList) {
  1777. const newImg = mutationList[1]['addedNodes'][0];
  1778. const [pageNum] = regexp.originSrcPageNum.exec(newImg.src) ?? [];
  1779. if (!pageNum)
  1780. throw new Error('[Error]Invalid Image Element.');
  1781. btn?.setAttribute('should-download', String(pageNum));
  1782. }
  1783. return (id) => {
  1784. const containers = document.querySelector("body > [role='presentation'] > div");
  1785. if (!containers) {
  1786. if (observer) {
  1787. observer.disconnect();
  1788. observer = null;
  1789. btn = null;
  1790. }
  1791. return;
  1792. }
  1793. if (containers.querySelector('.pdl-btn'))
  1794. return;
  1795. const img = containers.querySelector('img');
  1796. if (!img)
  1797. return;
  1798. const isOriginImg = regexp.originSrcPageNum.exec(img.src);
  1799. if (!isOriginImg)
  1800. return;
  1801. const [pageNum] = isOriginImg;
  1802. const attrs = {
  1803. attrs: { 'pdl-id': id, 'should-download': pageNum },
  1804. classList: ['pdl-btn', 'pdl-btn-sub', 'presentation']
  1805. };
  1806. btn = createPdlBtn(attrs);
  1807. containers.appendChild(btn);
  1808. if (!img.parentElement)
  1809. return;
  1810. observer = new MutationObserver(cb);
  1811. observer.observe(img.parentElement, { childList: true, subtree: true });
  1812. };
  1813. })();
  1814. function createPreviewModalBtn() {
  1815. const illustModalBtn = document.querySelectorAll('.gtm-manga-viewer-preview-modal-open');
  1816. const mangaModalBtn = document.querySelectorAll('.gtm-manga-viewer-open-preview');
  1817. const mangaViewerModalBtn = document.querySelectorAll('.gtm-manga-viewer-close-icon')?.[1];
  1818. if (!illustModalBtn.length && !mangaModalBtn.length)
  1819. return;
  1820. const btns = [...illustModalBtn, ...mangaModalBtn];
  1821. if (mangaViewerModalBtn)
  1822. btns.push(mangaViewerModalBtn);
  1823. btns.forEach((node) => {
  1824. node.addEventListener('click', handleModalClick);
  1825. });
  1826. }
  1827. function handleModalClick() {
  1828. const timer = setInterval(() => {
  1829. const ulList = document.querySelectorAll('ul');
  1830. const previewList = ulList[ulList.length - 1];
  1831. if (getComputedStyle(previewList).display !== 'grid')
  1832. return;
  1833. clearInterval(timer);
  1834. const [, id] = regexp.artworksPage.exec(location.pathname) ?? [];
  1835. previewList.childNodes.forEach((node, idx) => {
  1836. node.style.position = 'relative';
  1837. const attrs = {
  1838. attrs: { 'pdl-id': id, 'should-download': String(idx) },
  1839. classList: ['pdl-btn', 'pdl-btn-sub']
  1840. };
  1841. node.appendChild(createPdlBtn(attrs));
  1842. });
  1843. }, 300);
  1844. }
  1845.  
  1846. function changeDlbarDisplay() {
  1847. document.querySelectorAll('.pdl-dlbar .pdl-btn-all').forEach((ele) => {
  1848. ele.classList.toggle('pdl-hide');
  1849. });
  1850. document.querySelector('.pdl-dlbar .pdl-stop')?.classList.toggle('pdl-hide');
  1851. document.querySelectorAll('.pdl-tag').forEach((ele) => {
  1852. ele.classList.toggle('pdl-tag-hide');
  1853. });
  1854. document.querySelector('.pdl-filter-wrap')?.classList.toggle('unavailable');
  1855. }
  1856. let isDownloading = false;
  1857. const dlBarRef = {
  1858. filter: {
  1859. exclude_downloaded: undefined,
  1860. [IllustType.illusts]: undefined,
  1861. [IllustType.manga]: undefined,
  1862. [IllustType.ugoira]: undefined
  1863. },
  1864. statusBar: undefined,
  1865. abortBtn: undefined
  1866. };
  1867. function getFilterOption() {
  1868. return {
  1869. exclude_downloaded: dlBarRef.filter.exclude_downloaded?.checked ??
  1870. defaultSettings.filter.exclude_downloaded,
  1871. [IllustType.illusts]: dlBarRef.filter[IllustType.illusts]?.checked ??
  1872. defaultSettings.filter[IllustType.illusts],
  1873. [IllustType.manga]: dlBarRef.filter[IllustType.manga]?.checked ??
  1874. defaultSettings.filter[IllustType.manga],
  1875. [IllustType.ugoira]: dlBarRef.filter[IllustType.ugoira]?.checked ??
  1876. defaultSettings.filter[IllustType.ugoira]
  1877. };
  1878. }
  1879. function updateStatus(str) {
  1880. dlBarRef.statusBar && (dlBarRef.statusBar.textContent = str);
  1881. }
  1882. function onProgressCB(progressData) {
  1883. if (typeof progressData === 'string') {
  1884. updateStatus(progressData);
  1885. }
  1886. else {
  1887. debugLog('update progress by', progressData.illustId);
  1888. updateStatus(`Downloading: ${progressData.completed} / ${progressData.avaliable}`);
  1889. }
  1890. }
  1891. async function useDownload(chunksGenerators) {
  1892. if (!dlBarRef.abortBtn)
  1893. return;
  1894. isDownloading = true;
  1895. changeDlbarDisplay();
  1896. let total = 0;
  1897. let failedResult;
  1898. const idsGenerators = [];
  1899. try {
  1900. if (chunksGenerators instanceof Array) {
  1901. await Promise.all(chunksGenerators).then((chunksGenerator) => {
  1902. chunksGenerator.forEach((val) => {
  1903. total += val.total;
  1904. idsGenerators.push(val.generator);
  1905. });
  1906. });
  1907. }
  1908. else {
  1909. await chunksGenerators.then((val) => {
  1910. total = val.total;
  1911. idsGenerators.push(val.generator);
  1912. });
  1913. }
  1914. }
  1915. catch (error) {
  1916. console.error(error);
  1917. updateStatus('Network error, see console.');
  1918. changeDlbarDisplay();
  1919. isDownloading = false;
  1920. return;
  1921. }
  1922. try {
  1923. if (total === 0) {
  1924. throw 'No works.';
  1925. }
  1926. debugLog('[Info]Total:', total);
  1927. const { failed, unavaliable } = await downloadByIds(total, idsGenerators, dlBarRef.abortBtn, onProgressCB);
  1928. if (failed.length || unavaliable.length) {
  1929. updateStatus(`Failed: ${failed.length + unavaliable.length}. See console.`);
  1930. console.log('[Pixiv Downloader]Failed: ', failed.join(', '));
  1931. console.log('[Pixiv Downloader]Unavaliable: ', unavaliable.join(', '));
  1932. if (failed.length)
  1933. failedResult = failed;
  1934. }
  1935. else {
  1936. updateStatus('Complete');
  1937. }
  1938. }
  1939. catch (error) {
  1940. updateStatus(error);
  1941. }
  1942. changeDlbarDisplay();
  1943. isDownloading = false;
  1944. return failedResult;
  1945. }
  1946. async function downloadByIds(total, idsGenerators, abortBtn, onProgress) {
  1947. let resolve;
  1948. let reject;
  1949. const done = new Promise((r, j) => {
  1950. resolve = r;
  1951. reject = j;
  1952. });
  1953. let wakeFn;
  1954. let completed = 0;
  1955. const failed = [], unavaliable = [], invalid = [];
  1956. let isCanceled = false;
  1957. const tasks = [];
  1958. let tooManyRequests = false;
  1959. abortBtn.onclick = () => {
  1960. isCanceled = true;
  1961. abortBtn.onclick = null;
  1962. reject(`Stopped. ${completed} / ${total - failed.length - unavaliable.length - invalid.length}`);
  1963. wakeFn && wakeFn();
  1964. if (tasks.length) {
  1965. downloader.del(tasks);
  1966. converter.del(tasks);
  1967. compressor.remove(tasks);
  1968. tasks.length = 0;
  1969. }
  1970. };
  1971. const afterEach = (illustId) => {
  1972. const avaliable = total - failed.length - unavaliable.length - invalid.length;
  1973. onProgress({
  1974. illustId,
  1975. avaliable,
  1976. completed
  1977. });
  1978. if (completed === avaliable) {
  1979. resolve({ failed, unavaliable });
  1980. }
  1981. };
  1982. onProgress('Downloading...');
  1983. try {
  1984. for (const idsGenerator of idsGenerators) {
  1985. if (isCanceled)
  1986. return done;
  1987. for await (const ids of idsGenerator) {
  1988. debugLog('[Info]ids:', ids);
  1989. if (isCanceled)
  1990. return done;
  1991. if (ids.unavaliable.length) {
  1992. unavaliable.push(...ids.unavaliable);
  1993. }
  1994. if (ids.invalid.length) {
  1995. invalid.push(...ids.invalid);
  1996. }
  1997. if (typeof ids.total === 'number' && !Number.isNaN(ids.total)) {
  1998. total = ids.total;
  1999. }
  2000. if (ids.avaliable.length) {
  2001. for (const id of ids.avaliable) {
  2002. if (isCanceled)
  2003. return done;
  2004. if (tooManyRequests) {
  2005. onProgress('Too many requests, wait 30s');
  2006. const { wake, sleep } = wakeableSleep(30000);
  2007. wakeFn = wake;
  2008. await sleep;
  2009. tooManyRequests = false;
  2010. if (isCanceled)
  2011. return done;
  2012. onProgress('Downloading...');
  2013. }
  2014. parser
  2015. .getArtworkData(id)
  2016. .then(parser.getDownloadSource)
  2017. .then((sources) => {
  2018. if (isCanceled) {
  2019. throw '[Warning]Download stop manually: ' + id;
  2020. }
  2021. tasks.push(sources[0].taskId);
  2022. return downloader.add(sources);
  2023. })
  2024. .then((taskId) => {
  2025. pixivHistory.add(id);
  2026. if (!isCanceled) {
  2027. tasks.splice(tasks.indexOf(taskId), 1);
  2028. completed++;
  2029. afterEach(id);
  2030. }
  2031. }, (reason) => {
  2032. if (!isCanceled) {
  2033. if (reason.message && reason.message === '429')
  2034. tooManyRequests = true;
  2035. if (reason.message &&
  2036. reason.message === '[Error]Fail to parse preload data.') {
  2037. unavaliable.push(id);
  2038. }
  2039. else {
  2040. failed.push(id);
  2041. }
  2042. afterEach(id);
  2043. }
  2044. });
  2045. await sleep(1000);
  2046. }
  2047. }
  2048. else {
  2049. afterEach();
  2050. }
  2051. }
  2052. }
  2053. }
  2054. catch (error) {
  2055. console.error(error);
  2056. reject('Network Error, see console');
  2057. abortBtn.click();
  2058. }
  2059. return done;
  2060. }
  2061. function downloadWorks(evt) {
  2062. evt.preventDefault();
  2063. evt.stopPropagation();
  2064. if (isDownloading)
  2065. return;
  2066. const btn = evt.target;
  2067. const userId = btn.getAttribute('pdl-userid');
  2068. const filterOption = getFilterOption();
  2069. const ids = parser.getAllWorksGenerator(userId, filterOption);
  2070. useDownload(ids).then((failed) => {
  2071. if (failed instanceof Array && failed.length) {
  2072. const gen = async function* () {
  2073. yield {
  2074. avaliable: failed,
  2075. unavaliable: [],
  2076. invalid: []
  2077. };
  2078. };
  2079. useDownload(Promise.resolve({ total: failed.length, generator: gen() }));
  2080. }
  2081. });
  2082. }
  2083. async function downloadBookmarksOrTags(evt) {
  2084. evt.preventDefault();
  2085. evt.stopPropagation();
  2086. if (isDownloading)
  2087. return;
  2088. const btn = evt.target;
  2089. const userId = btn.getAttribute('pdl-userid');
  2090. const category = btn.getAttribute('category');
  2091. const tag = btn.getAttribute('tag') || '';
  2092. const rest = (btn.getAttribute('rest') || 'show');
  2093. const filterOption = getFilterOption();
  2094. let idsGenerators;
  2095. if (rest === 'all') {
  2096. const idsShowPromise = parser.getChunksGenerator(userId, 'bookmarks', '', 'show', filterOption);
  2097. const idsHidePromise = parser.getChunksGenerator(userId, 'bookmarks', '', 'hide', filterOption);
  2098. idsGenerators = [idsShowPromise, idsHidePromise];
  2099. }
  2100. else {
  2101. idsGenerators = parser.getChunksGenerator(userId, category, tag, rest, filterOption);
  2102. }
  2103. useDownload(idsGenerators).then((failed) => {
  2104. if (failed instanceof Array && failed.length) {
  2105. const gen = async function* () {
  2106. yield {
  2107. avaliable: failed,
  2108. unavaliable: [],
  2109. invalid: []
  2110. };
  2111. };
  2112. useDownload(Promise.resolve({ total: failed.length, generator: gen() }));
  2113. }
  2114. });
  2115. }
  2116. function downloadFollowLatest(evt) {
  2117. evt.preventDefault();
  2118. evt.stopPropagation();
  2119. if (isDownloading)
  2120. return;
  2121. const btn = evt.target;
  2122. let idsGenerators;
  2123. const mode = location.pathname.includes('r18') ? 'r18' : 'all';
  2124. const filterOption = getFilterOption();
  2125. if (btn.classList.contains('pdl-dl-all')) {
  2126. idsGenerators = parser.getFollowLatestGenerator(filterOption, mode);
  2127. }
  2128. else {
  2129. const params = new URLSearchParams(location.search);
  2130. const page = Number(params.get('p')) || 1;
  2131. idsGenerators = parser.getFollowLatestGenerator(filterOption, mode, page);
  2132. }
  2133. useDownload(idsGenerators).then((failed) => {
  2134. if (failed instanceof Array && failed.length) {
  2135. const gen = async function* () {
  2136. yield {
  2137. avaliable: failed,
  2138. unavaliable: [],
  2139. invalid: []
  2140. };
  2141. };
  2142. useDownload(Promise.resolve({ total: failed.length, generator: gen() }));
  2143. }
  2144. });
  2145. }
  2146.  
  2147. function createFilterEl(id, filterType, text) {
  2148. const checkbox = document.createElement('input');
  2149. const label = document.createElement('label');
  2150. checkbox.id = id;
  2151. checkbox.type = 'checkbox';
  2152. checkbox.classList.add('pdl-checkbox');
  2153. checkbox.setAttribute('category', String(filterType));
  2154. checkbox.checked = settings.filter[filterType];
  2155. label.setAttribute('for', id);
  2156. label.setAttribute('category', String(filterType));
  2157. label.textContent = text;
  2158. checkbox.addEventListener('change', (evt) => {
  2159. const checkbox = evt.currentTarget;
  2160. const category = checkbox.getAttribute('category');
  2161. upgradeSettings('filter', {
  2162. ...settings.filter,
  2163. [category]: checkbox.checked
  2164. });
  2165. });
  2166. dlBarRef.filter[filterType] = checkbox;
  2167. const wrap = document.createElement('div');
  2168. wrap.classList.add('pdl-filter');
  2169. wrap.appendChild(checkbox);
  2170. wrap.appendChild(label);
  2171. return wrap;
  2172. }
  2173. function createFilter() {
  2174. const wrapper = document.createElement('div');
  2175. wrapper.classList.add('pdl-filter-wrap');
  2176. wrapper.appendChild(createFilterEl('pdl-filter-exclude_downloaded', 'exclude_downloaded', i18n('dlbar_filter_exclude_downloaded')));
  2177. wrapper.appendChild(createFilterEl('pdl-filter-illusts', IllustType.illusts, i18n('dlbar_filter_illusts')));
  2178. wrapper.appendChild(createFilterEl('pdl-filter-manga', IllustType.manga, i18n('dlbar_filter_manga')));
  2179. wrapper.appendChild(createFilterEl('pdl-filter-ugoria', IllustType.ugoira, i18n('dlbar_filter_ugoria')));
  2180. return wrapper;
  2181. }
  2182. function createDownloadBar(userId) {
  2183. const nav = document.querySelector('nav[class~="sc-192ftwf-0"]');
  2184. if (!nav)
  2185. return;
  2186. const dlBtn = nav.querySelector('.pdl-btn-all');
  2187. if (dlBtn) {
  2188. if (dlBtn.getAttribute('pdl-userid') === userId)
  2189. return;
  2190. removeDownloadBar();
  2191. }
  2192. const dlBar = document.createElement('div');
  2193. dlBar.classList.add('pdl-dlbar');
  2194. const statusBar = document.createElement('div');
  2195. statusBar.classList.add('pdl-dlbar-status_bar');
  2196. dlBarRef.statusBar = dlBar.appendChild(statusBar);
  2197. const baseClasses = nav.querySelector('a:not([aria-current])').classList;
  2198. dlBarRef.abortBtn = dlBar.appendChild(createPdlBtn({
  2199. attrs: { 'pdl-userId': userId },
  2200. classList: [...baseClasses, 'pdl-stop', 'pdl-hide']
  2201. }, i18n('stop'), { addEvent: false }));
  2202. if (userId !== getSelfId()) {
  2203. const hasWorks = ["a[href$='illustrations']", "a[href$='manga']"].some((selector) => !!nav.querySelector(selector));
  2204. if (hasWorks) {
  2205. const el = createPdlBtn({
  2206. attrs: { 'pdl-userid': userId },
  2207. classList: [...baseClasses, 'pdl-btn-all']
  2208. }, i18n('dlbar_category_works'), { addEvent: false });
  2209. el.addEventListener('click', downloadWorks);
  2210. dlBar.appendChild(el);
  2211. }
  2212. if (nav.querySelector("a[href*='bookmarks']")) {
  2213. const el = createPdlBtn({
  2214. attrs: { 'pdl-userid': userId, category: 'bookmarks' },
  2215. classList: [...baseClasses, 'pdl-btn-all']
  2216. }, i18n('bookmarks'), { addEvent: false });
  2217. el.addEventListener('click', downloadBookmarksOrTags);
  2218. dlBar.appendChild(el);
  2219. }
  2220. }
  2221. else {
  2222. if (nav.querySelector("a[href*='bookmarks']")) {
  2223. dlBar.appendChild(createPdlBtn({
  2224. attrs: { 'pdl-userid': userId, category: 'bookmarks', rest: 'all' },
  2225. classList: [...baseClasses, 'pdl-btn-all']
  2226. }, i18n('bookmarks'), { addEvent: false }));
  2227. dlBar.appendChild(createPdlBtn({
  2228. attrs: {
  2229. 'pdl-userid': userId,
  2230. category: 'bookmarks',
  2231. rest: 'show'
  2232. },
  2233. classList: [...baseClasses, 'pdl-btn-all']
  2234. }, i18n('bookmarks_public'), { addEvent: false }));
  2235. dlBar.appendChild(createPdlBtn({
  2236. attrs: {
  2237. 'pdl-userid': userId,
  2238. category: 'bookmarks',
  2239. rest: 'hide'
  2240. },
  2241. classList: [...baseClasses, 'pdl-btn-all']
  2242. }, i18n('bookmarks_private'), { addEvent: false }));
  2243. dlBar
  2244. .querySelectorAll('.pdl-btn-all')
  2245. .forEach((node) => {
  2246. node.addEventListener('click', downloadBookmarksOrTags);
  2247. });
  2248. }
  2249. }
  2250. const filter = createFilter();
  2251. nav.parentElement.insertBefore(filter, nav);
  2252. nav.appendChild(dlBar);
  2253. }
  2254. function removeDownloadBar() {
  2255. const dlBarWrap = document.querySelector('.pdl-dlbar');
  2256. if (dlBarWrap) {
  2257. dlBarWrap.remove();
  2258. document.querySelector('.pdl-filter-wrap')?.remove();
  2259. }
  2260. }
  2261. function createTagsBtn(userId, category) {
  2262. const tagsEles = document.querySelectorAll('section> div:nth-child(2) > div > div');
  2263. if (!tagsEles.length)
  2264. return;
  2265. let cate;
  2266. if (category === 'illustrations' || category === 'artworks') {
  2267. cate = 'illusts';
  2268. }
  2269. else {
  2270. cate = category;
  2271. }
  2272. let rest = 'show';
  2273. if (userId === getSelfId() &&
  2274. category === 'bookmarks' &&
  2275. location.search.includes('rest=hide'))
  2276. rest = 'hide';
  2277. tagsEles.forEach((ele) => {
  2278. const tagBtn = ele.querySelector('.pdl-btn');
  2279. if (tagBtn) {
  2280. const btnRest = tagBtn.getAttribute('rest');
  2281. if (rest !== btnRest)
  2282. tagBtn.setAttribute('rest', rest);
  2283. return;
  2284. }
  2285. let tag;
  2286. const tagLink = ele.querySelector('a');
  2287. if (!tagLink)
  2288. return;
  2289. if (tagLink.getAttribute('status') !== 'active') {
  2290. if (rest === 'hide') {
  2291. tag = tagLink.href.slice(tagLink.href.lastIndexOf('/') + 1, tagLink.href.lastIndexOf('?'));
  2292. }
  2293. else {
  2294. tag = tagLink.href.slice(tagLink.href.lastIndexOf('/') + 1);
  2295. }
  2296. }
  2297. else {
  2298. const tagTextEles = ele.querySelectorAll('div[title]');
  2299. if (!tagTextEles.length)
  2300. return console.log('[Info]No Tags Element found.');
  2301. tag = tagTextEles[tagTextEles.length - 1].getAttribute('title').slice(1);
  2302. }
  2303. const attrs = {
  2304. attrs: { 'pdl-userId': userId, category: cate, tag, rest },
  2305. classList: ['pdl-btn', 'pdl-tag']
  2306. };
  2307. if (isDownloading)
  2308. attrs.classList.push('pdl-tag-hide');
  2309. const dlBtn = createPdlBtn(attrs, '', { addEvent: false });
  2310. if (!(tagLink.href.includes('bookmarks') &&
  2311. tagLink.getAttribute('status') !== 'active')) {
  2312. dlBtn.style.backgroundColor = tagLink.getAttribute('color') + '80';
  2313. }
  2314. dlBtn.addEventListener('click', downloadBookmarksOrTags);
  2315. ele.appendChild(dlBtn);
  2316. });
  2317. let modalTagsEles;
  2318. let modal;
  2319. if (category === 'bookmarks') {
  2320. modal = document.querySelector('div[role="presentation"]');
  2321. if (!modal)
  2322. return;
  2323. modalTagsEles = modal.querySelectorAll('a');
  2324. }
  2325. else {
  2326. const charcoalTokens = document.querySelectorAll('.charcoal-token');
  2327. modal = charcoalTokens[charcoalTokens.length - 1];
  2328. if (!modal)
  2329. return;
  2330. modalTagsEles = modal.querySelectorAll('a');
  2331. }
  2332. if (!regexp.userPageTags.exec(modalTagsEles[0]?.href))
  2333. return;
  2334. modalTagsEles.forEach((ele) => {
  2335. if (ele.querySelector('.pdl-btn'))
  2336. return;
  2337. let tag;
  2338. if (rest === 'hide') {
  2339. tag = ele.href.slice(ele.href.lastIndexOf('/') + 1, ele.href.lastIndexOf('?'));
  2340. }
  2341. else {
  2342. tag = ele.href.slice(ele.href.lastIndexOf('/') + 1);
  2343. }
  2344. const attrs = {
  2345. attrs: { 'pdl-userId': userId, category: cate, tag, rest },
  2346. classList: ['pdl-btn', 'pdl-modal-tag']
  2347. };
  2348. if (isDownloading)
  2349. attrs.classList.push('pdl-tag-hide');
  2350. const dlBtn = createPdlBtn(attrs, '', { addEvent: false });
  2351. dlBtn.addEventListener('click', (evt) => {
  2352. modal.querySelector('svg').parentElement.click();
  2353. downloadBookmarksOrTags(evt);
  2354. });
  2355. ele.appendChild(dlBtn);
  2356. });
  2357. }
  2358.  
  2359. function createFollowLatestDownloadBar() {
  2360. const prevDlBtn = document.querySelector('.pdl-btn-all');
  2361. if (prevDlBtn) {
  2362. const prevDlAllBtn = document.querySelector('.pdl-dl-all');
  2363. if (location.pathname.includes('r18')) {
  2364. prevDlBtn.textContent !==
  2365. i18n('dlbar.follow_latest.category_r18.single') &&
  2366. (prevDlBtn.textContent = i18n('dlbar.follow_latest.category_r18.single'));
  2367. prevDlAllBtn.textContent !==
  2368. i18n('dlbar.follow_latest.category_r18.all') &&
  2369. (prevDlAllBtn.textContent = i18n('dlbar.follow_latest.category_r18.all'));
  2370. }
  2371. else {
  2372. prevDlBtn.textContent !==
  2373. i18n('dlbar.follow_latest.category_all.single') &&
  2374. (prevDlBtn.textContent = i18n('dlbar.follow_latest.category_all.single'));
  2375. prevDlAllBtn.textContent !==
  2376. i18n('dlbar.follow_latest.category_all.all') &&
  2377. (prevDlAllBtn.textContent = i18n('dlbar.follow_latest.category_all.all'));
  2378. }
  2379. return;
  2380. }
  2381. const nav = document.querySelector('nav');
  2382. if (!nav || nav.parentElement.childElementCount === 1)
  2383. return;
  2384. const navBar = nav.parentElement;
  2385. const modeSwitch = nav.nextElementSibling;
  2386. const filter = createFilter();
  2387. navBar.parentElement.insertBefore(filter, navBar);
  2388. const dlBar = document.createElement('div');
  2389. dlBar.classList.add('pdl-dlbar');
  2390. dlBar.classList.add('pdl-dlbar-follow_latest');
  2391. const statusBar = document.createElement('div');
  2392. statusBar.classList.add('pdl-dlbar-status_bar');
  2393. dlBarRef.statusBar = dlBar.appendChild(statusBar);
  2394. const baseClasses = nav.querySelector('a:not([aria-current])').classList;
  2395. dlBarRef.abortBtn = dlBar.appendChild(createPdlBtn({
  2396. attrs: { 'pdl-userid': '' },
  2397. classList: [...baseClasses, 'pdl-stop', 'pdl-hide']
  2398. }, i18n('stop'), { addEvent: false }));
  2399. const dlBtn = createPdlBtn({
  2400. attrs: { 'pdl-userid': '' },
  2401. classList: [...baseClasses, 'pdl-btn-all']
  2402. },
  2403. i18n('dlbar_category_works'), { addEvent: false });
  2404. dlBtn.addEventListener('click', downloadFollowLatest);
  2405. dlBar.appendChild(dlBtn);
  2406. const dlAllBtn = createPdlBtn({
  2407. attrs: { 'pdl-userid': '' },
  2408. classList: [...baseClasses, 'pdl-btn-all', 'pdl-dl-all']
  2409. },
  2410. i18n('dlbar_category_works'), { addEvent: false });
  2411. dlAllBtn.addEventListener('click', downloadFollowLatest);
  2412. dlBar.appendChild(dlAllBtn);
  2413. navBar.insertBefore(dlBar, modeSwitch);
  2414. }
  2415.  
  2416. let firstRun = true;
  2417. function observerCallback(records) {
  2418. const addedNodes = [];
  2419. records.forEach((record) => {
  2420. if (!record.addedNodes.length)
  2421. return;
  2422. record.addedNodes.forEach((node) => {
  2423. if (node.nodeType === Node.ELEMENT_NODE &&
  2424. node.tagName !== 'BUTTON' &&
  2425. node.tagName !== 'IMG') {
  2426. addedNodes.push(node);
  2427. }
  2428. });
  2429. });
  2430. if (!addedNodes.length) {
  2431. return;
  2432. }
  2433. if (firstRun) {
  2434. createThunbnailsBtn(document.querySelectorAll('a'));
  2435. firstRun = false;
  2436. }
  2437. else {
  2438. compatPixivPreviewer(addedNodes);
  2439. const thunmnails = addedNodes.reduce((prev, current) => {
  2440. return prev.concat(Array.from(current.querySelectorAll('a')));
  2441. }, []);
  2442. createThunbnailsBtn(thunmnails);
  2443. }
  2444. const isArtworksPage = regexp.artworksPage.exec(location.pathname);
  2445. const isUserPage = regexp.userPage.exec(location.pathname);
  2446. const isTagsPage = regexp.userPageTags.exec(location.pathname);
  2447. if (isArtworksPage) {
  2448. const id = isArtworksPage[1];
  2449. createMainBtn(id);
  2450. createMultyWorksBtn(id);
  2451. createPresentationBtn(id);
  2452. createPreviewModalBtn();
  2453. }
  2454. else if (isUserPage) {
  2455. createDownloadBar(isUserPage[1]);
  2456. if (isTagsPage) {
  2457. createTagsBtn(isUserPage[1], isTagsPage[1]);
  2458. }
  2459. }
  2460. else if (regexp.followLatest.test(location.pathname)) {
  2461. createFollowLatestDownloadBar();
  2462. }
  2463. else {
  2464. removeDownloadBar();
  2465. }
  2466. }
  2467.  
  2468. function createModal({ header = '', content, footer = '' }, option) {
  2469. const modal = document.createElement('div');
  2470. const dialog = document.createElement('div');
  2471. modal.classList.add('pdl-modal');
  2472. dialog.classList.add('pdl-dialog');
  2473. if (option.closeOnClickModal) {
  2474. dialog.onclick = (e) => {
  2475. e.stopPropagation();
  2476. };
  2477. modal.onclick = () => {
  2478. modal.remove();
  2479. };
  2480. }
  2481. dialog.innerHTML = ` <header class="pdl-dialog-header">${header}</header>
  2482. <div class="pdl-dialog-content">${content}</div>
  2483. <footer class="pdl-dialog-footer">${footer}</footer>`;
  2484. const closeBtn = document.createElement('button');
  2485. closeBtn.classList.add('pdl-dialog-close');
  2486. closeBtn.onclick = () => {
  2487. modal.remove();
  2488. };
  2489. dialog.insertBefore(closeBtn, dialog.firstChild);
  2490. modal.appendChild(dialog);
  2491. return modal;
  2492. }
  2493.  
  2494. function showUpgradeMsg() {
  2495. document.body.appendChild(createModal({
  2496. header: modalHtml.upgradeMsgTitle,
  2497. content: modalHtml.upgradeMsgContent,
  2498. footer: modalHtml.modalCreditFooter + modalHtml.modalFeedback
  2499. }, { closeOnClickModal: false }));
  2500. }
  2501.  
  2502. function createTabsPart({ tabName, paneHtml }) {
  2503. const tab = document.createElement('div');
  2504. tab.classList.add('pdl-tab-item');
  2505. tab.textContent = tabName;
  2506. const pane = document.createElement('div');
  2507. pane.classList.add('pdl-tab-pane');
  2508. pane.innerHTML = paneHtml;
  2509. return {
  2510. tab,
  2511. pane
  2512. };
  2513. }
  2514. function createTabFilename({ tabName, paneHtml }) {
  2515. const { tab, pane } = createTabsPart({ tabName, paneHtml });
  2516. const folder = pane.querySelector('#pdlfolder');
  2517. const folderReset = pane.querySelector('#pdl-filename-folder-reset');
  2518. const folderUpdate = pane.querySelector('#pdl-filename-folder-confirm');
  2519. const filename = pane.querySelector('#pdlfilename');
  2520. const filenameReset = pane.querySelector('#pdl-filename-filename-reset');
  2521. const filenameUpdate = pane.querySelector('#pdl-filename-filename-confirm');
  2522. if (!folder || !filename)
  2523. throw new Error('[Error]Can not create modal.');
  2524. filename.value = settings.filenamePattern;
  2525. if (!env.isSupportSubpath) {
  2526. folder.setAttribute('disabled', '');
  2527. folder.value = '';
  2528. }
  2529. else {
  2530. folder.value = settings.folderPattern;
  2531. }
  2532. folder.placeholder = env.isViolentmonkey
  2533. ? i18n('folder_vm_tips')
  2534. : !env.isSupportSubpath
  2535. ? i18n('folder_api_tips')
  2536. : i18n('folder_tips');
  2537. folder.addEventListener('input', () => {
  2538. folderReset?.removeAttribute('disabled');
  2539. folderUpdate?.removeAttribute('disabled');
  2540. });
  2541. folderReset?.addEventListener('click', () => {
  2542. folder.value = settings.folderPattern;
  2543. folderReset?.setAttribute('disabled', '');
  2544. folderUpdate?.setAttribute('disabled', '');
  2545. });
  2546. folderUpdate?.addEventListener('click', () => {
  2547. const folderPattern = folder.value
  2548. .split('/')
  2549. .map((path) => path
  2550. .trim()
  2551. .replace(/^\.+|\.+$|[\u200b-\u200f\uFEFF\u202a-\u202e\\:*?"|<>]/g, ''))
  2552. .filter((path) => !!path)
  2553. .join('/') ?? '';
  2554. upgradeSettings('folderPattern', folderPattern);
  2555. folder.value = folderPattern;
  2556. folderReset?.setAttribute('disabled', '');
  2557. folderUpdate?.setAttribute('disabled', '');
  2558. });
  2559. filename.addEventListener('input', () => {
  2560. filenameReset?.removeAttribute('disabled');
  2561. filenameUpdate?.removeAttribute('disabled');
  2562. });
  2563. filenameReset?.addEventListener('click', () => {
  2564. filename.value = settings.filenamePattern;
  2565. filenameReset?.setAttribute('disabled', '');
  2566. filenameUpdate?.setAttribute('disabled', '');
  2567. });
  2568. filenameUpdate?.addEventListener('click', () => {
  2569. const filenamePattern = filename.value
  2570. .trim()
  2571. .replace(/^\.+|[\u200b-\u200f\uFEFF\u202a-\u202e\\/:*?"|<>]/g, '') ??
  2572. '';
  2573. if (filenamePattern === '')
  2574. return filenameReset?.click();
  2575. upgradeSettings('filenamePattern', filenamePattern);
  2576. filename.value = filenamePattern;
  2577. filenameReset?.setAttribute('disabled', '');
  2578. filenameUpdate?.setAttribute('disabled', '');
  2579. });
  2580. pane
  2581. .querySelectorAll('.tags-content .tags-item input')
  2582. .forEach((input) => {
  2583. if (settings.tagLang === input.value)
  2584. input.checked = true;
  2585. input.addEventListener('change', (ev) => {
  2586. upgradeSettings('tagLang', ev.currentTarget.value);
  2587. });
  2588. });
  2589. return {
  2590. tab,
  2591. pane
  2592. };
  2593. }
  2594. function createTabUgoria({ tabName, paneHtml }) {
  2595. const { tab, pane } = createTabsPart({ tabName, paneHtml });
  2596. pane
  2597. .querySelectorAll('.pdl-ugoria-format-item input[type="radio"]')
  2598. .forEach((el) => {
  2599. if (settings.ugoriaFormat === el.value)
  2600. el.checked = true;
  2601. el.addEventListener('change', (ev) => {
  2602. upgradeSettings('ugoriaFormat', ev.currentTarget.value);
  2603. });
  2604. });
  2605. return {
  2606. tab,
  2607. pane
  2608. };
  2609. }
  2610. function createTabHistory({ tabName, paneHtml }) {
  2611. const { tab, pane } = createTabsPart({ tabName, paneHtml });
  2612. const file = pane.querySelector('#pdl-import');
  2613. file?.addEventListener('change', (evt) => {
  2614. const file = evt.currentTarget.files?.[0];
  2615. if (!file)
  2616. return;
  2617. if (file.type === 'text/plain') {
  2618. const reader = new FileReader();
  2619. reader.readAsText(file);
  2620. reader.onload = (readEvt) => {
  2621. const text = readEvt.target?.result;
  2622. try {
  2623. if (typeof text !== 'string')
  2624. throw new Error('Invalid file');
  2625. const history = JSON.parse(text);
  2626. if (!(history instanceof Array))
  2627. throw new Error('Invalid file');
  2628. if (history.length &&
  2629. !history.every((id) => typeof id === 'string')) {
  2630. throw new Error('Invalid id type');
  2631. }
  2632. pixivHistory.saveHistory(history);
  2633. location.reload();
  2634. }
  2635. catch (error) {
  2636. alert(error.message);
  2637. }
  2638. };
  2639. }
  2640. else {
  2641. alert('Invalid file');
  2642. }
  2643. });
  2644. const importBtn = pane.querySelector('#pdl-import-btn');
  2645. importBtn?.addEventListener('click', () => file?.click());
  2646. const exportBtn = pane.querySelector('#pdl-export');
  2647. exportBtn?.addEventListener('click', () => {
  2648. const dlEle = document.createElement('a');
  2649. const history = JSON.stringify(pixivHistory.getAll());
  2650. dlEle.href = URL.createObjectURL(new Blob([history], { type: 'text/plain' }));
  2651. dlEle.download = 'Pixiv Downloader ' + new Date().toLocaleString() + '.txt';
  2652. dlEle.click();
  2653. URL.revokeObjectURL(dlEle.href);
  2654. });
  2655. const clearBtn = pane.querySelector('#pdl-clear-history');
  2656. clearBtn?.addEventListener('click', () => pixivHistory.clearHistory());
  2657. return {
  2658. tab,
  2659. pane
  2660. };
  2661. }
  2662. function createTabOthers({ tabName, paneHtml }) {
  2663. const { tab, pane } = createTabsPart({ tabName, paneHtml });
  2664. [
  2665. { selector: '#pdl-options-bundle-illusts', settingKey: 'bundleIllusts' },
  2666. { selector: '#pdl-options-bundle-manga', settingKey: 'bundleManga' },
  2667. { selector: '#pdl-options-add-bookmark', settingKey: 'addBookmark' },
  2668. {
  2669. selector: '#pdl-options-add-bookmark-tags',
  2670. settingKey: 'addBookmarkWithTags'
  2671. },
  2672. {
  2673. selector: '#pdl-options-add-bookmark-private-r18',
  2674. settingKey: 'privateR18'
  2675. }
  2676. ].forEach(({ selector, settingKey }) => {
  2677. const optionEl = pane.querySelector(selector);
  2678. optionEl && (optionEl.checked = settings[settingKey]);
  2679. optionEl?.addEventListener('change', (ev) => {
  2680. upgradeSettings(settingKey, ev.currentTarget.checked);
  2681. });
  2682. });
  2683. return {
  2684. tab,
  2685. pane
  2686. };
  2687. }
  2688. function showSettings() {
  2689. if (document.querySelector('.pdl-modal'))
  2690. return;
  2691. const modal = createModal({
  2692. content: modalHtml.modalSettingsContent
  2693. }, { closeOnClickModal: false });
  2694. const tabsNav = modal.querySelector('.pdl-tabs-nav');
  2695. const tabContent = modal.querySelector('.pdl-tabs-content');
  2696. [
  2697. createTabFilename({
  2698. tabName: i18n('tabs_nav_filename'),
  2699. paneHtml: modalHtml.modalSettingFilename
  2700. }),
  2701. createTabUgoria({
  2702. tabName: i18n('tabs_nav_ugoria'),
  2703. paneHtml: modalHtml.modalSettingUgoria
  2704. }),
  2705. createTabHistory({
  2706. tabName: i18n('tabs_nav_history'),
  2707. paneHtml: modalHtml.modalSettingHistory
  2708. }),
  2709. createTabOthers({
  2710. tabName: i18n('tabs_nav_others'),
  2711. paneHtml: modalHtml.modalSettingOthers
  2712. }),
  2713. createTabsPart({
  2714. tabName: i18n('tabs_nav_donate'),
  2715. paneHtml: modalHtml.modalSettingDonate
  2716. })
  2717. ].forEach(({ tab, pane }) => {
  2718. tabsNav.appendChild(tab);
  2719. tabContent.appendChild(pane);
  2720. });
  2721. const panes = Array.from(tabContent.querySelectorAll('.pdl-tab-pane'));
  2722. panes.forEach((el) => {
  2723. el.style.setProperty('display', 'none');
  2724. });
  2725. const activeBar = tabsNav.querySelector('.pdl-tabs__active-bar');
  2726. const tabs = Array.from(modal.querySelectorAll('.pdl-tabs-nav .pdl-tab-item'));
  2727. tabs.forEach((el) => {
  2728. el.addEventListener('click', (ev) => {
  2729. const tab = ev.currentTarget;
  2730. if (!tab)
  2731. return;
  2732. tabs.forEach((tab) => tab.classList.remove('active'));
  2733. tab.classList.add('active');
  2734. activeBar.style.width = getComputedStyle(tab).width;
  2735. activeBar.style.transform = `translateX(${tab.offsetLeft + parseFloat(getComputedStyle(tab).paddingLeft)}px)`;
  2736. panes.forEach((pane) => pane.style.setProperty('display', 'none'));
  2737. panes[tabs.indexOf(tab)].style.removeProperty('display');
  2738. });
  2739. });
  2740. tabs[0].classList.add('active');
  2741. panes[0].style.removeProperty('display');
  2742. document.body.appendChild(modal);
  2743. activeBar.style.width = getComputedStyle(tabs[0]).width;
  2744. activeBar.style.transform = `translateX(${tabs[0].offsetLeft + parseFloat(getComputedStyle(tabs[0]).paddingLeft)}px)`;
  2745. }
  2746.  
  2747. pixivHistory.updateHistory();
  2748. GM_registerMenuCommand(i18n('gm_menu_setting'), showSettings, 's');
  2749. initial()
  2750. .then(() => {
  2751. if (settings.showMsg) {
  2752. showUpgradeMsg();
  2753. upgradeSettings('showMsg', false);
  2754. }
  2755. new MutationObserver(observerCallback).observe(document.body, {
  2756. childList: true,
  2757. subtree: true
  2758. });
  2759. document.addEventListener('keydown', (e) => {
  2760. if (e.ctrlKey && e.key === 'q') {
  2761. const pdlMainBtn = document.querySelector('.pdl-btn-main');
  2762. if (pdlMainBtn) {
  2763. e.preventDefault();
  2764. if (!e.repeat) {
  2765. pdlMainBtn.dispatchEvent(new MouseEvent('click'));
  2766. }
  2767. }
  2768. }
  2769. });
  2770. })
  2771. .catch(console.error);
  2772.  
  2773. })(workerChunk, JSZip, GIF);