Greasy Fork is available in English.

Pixiv Downloader

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

// ==UserScript==
// @name         Pixiv Downloader
// @namespace    https://greasyfork.org/zh-CN/scripts/432150
// @version      0.10.0
// @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.
// @description  一键下载Pixiv各页面原图。支持多图下载,动图下载,按作品标签下载,画师作品批量下载。动图支持格式转换:Gif | Apng | Webp | Webm。下载的图片将保存到以画师名命名的单独文件夹(需要调整tampermonkey“下载”设置为“浏览器API”)。保留已下载图片的记录。
// @description:zh-TW  一鍵下載Pixiv各頁面原圖。支持多圖下載,動圖下載,按作品標籤下載,畫師作品批次下載。動圖支持格式轉換:Gif | Apng | Webp | Webm。下載的圖片將保存到以畫師名命名的單獨文件夾(需要調整tampermonkey“下載”設置為“瀏覽器API”)。保留已下載圖片的紀錄。
// @author       ruaruarua
// @match        https://www.pixiv.net/*
// @icon         https://www.pixiv.net/favicon.ico
// @noframes
// @grant        unsafeWindow
// @grant        GM_xmlhttpRequest
// @grant        GM_download
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_info
// @grant        GM_registerMenuCommand
// @connect      i.pximg.net
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/gif.js/0.2.0/gif.js
// @require      https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js
// @require      https://unpkg.com/dexie@3.2.5/dist/dexie.min.js
// @require      https://greasyfork.org/scripts/455256-toanimatedwebp/code/toAnimatedWebp.js?version=1120088
// ==/UserScript==
(function (workerChunk, GIF, JSZip, dayjs, Dexie) {
  'use strict';

  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }

  var workerChunk__default = /*#__PURE__*/_interopDefaultLegacy(workerChunk);
  var GIF__default = /*#__PURE__*/_interopDefaultLegacy(GIF);
  var JSZip__default = /*#__PURE__*/_interopDefaultLegacy(JSZip);
  var dayjs__default = /*#__PURE__*/_interopDefaultLegacy(dayjs);
  var Dexie__default = /*#__PURE__*/_interopDefaultLegacy(Dexie);

  var e=[],t$1=[];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$1[u]={}),a=t$1[u]&&t$1[u][s]?t$1[u][s]:t$1[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}}

  var css$5 = ".pdl-hide{display:none!important}.pdl-unavailable{cursor:not-allowed!important;opacity:.5!important;pointer-events:none!important}.pdl-spacer{flex:1;margin:0;padding:0}:root{--pdl-btn-top:100;--pdl-btn-left:0;--pdl-btn-self-bookmark-top:75;--pdl-btn-self-bookmark-left:100}:root,:root[data-theme=default]{--pdl-bg1:#fff;--pdl-bg2-hover:rgba(0,0,0,.05);--pdl-bg3-hover:#1f1f1f;--pdl-btn1:rgba(0,0,0,.04);--pdl-btn1-hover:rgba(0,0,0,.12);--pdl-border1:rgba(0,0,0,.1);--pdl-text1:#1f1f1f}@media (prefers-color-scheme:light){:root{--pdl-bg1:#fff;--pdl-bg2-hover:rgba(0,0,0,.05);--pdl-bg3-hover:#1f1f1f;--pdl-btn1:rgba(0,0,0,.04);--pdl-btn1-hover:rgba(0,0,0,.12);--pdl-border1:rgba(0,0,0,.1);--pdl-text1:#1f1f1f}}:root[data-theme=dark]{--pdl-bg1:#1f1f1f;--pdl-bg2-hover:hsla(0,0%,100%,.1);--pdl-bg3-hover:#9b9b9b;--pdl-btn1:hsla(0,0%,100%,.4);--pdl-btn1-hover:hsla(0,0%,100%,.6);--pdl-border1:hsla(0,0%,100%,.3);--pdl-text1:#d6d6d6}:root[data-theme=dark] .pdl-btn-main{background-image:url(\"data:image/svg+xml;charset=utf-8,%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/svg%3E\")}:root[data-theme=dark] .pdl-dlbar .pdl-btn-all:before{background-image:url(\"data:image/svg+xml;charset=utf-8,%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/svg%3E\")}:root[data-theme=dark] .pdl-dlbar .pdl-btn-all:hover:before{background-image:url(\"data:image/svg+xml;charset=utf-8,%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/svg%3E\")}:root[data-theme=dark] .pdl-dlbar .pdl-stop:before{background-image:url(\"data:image/svg+xml;charset=utf-8,%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/svg%3E\")}:root[data-theme=dark] .pdl-dlbar .pdl-stop:hover:before{background-image:url(\"data:image/svg+xml;charset=utf-8,%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/svg%3E\")}@media (prefers-color-scheme:dark){:root{--pdl-bg1:#1f1f1f;--pdl-bg2-hover:hsla(0,0%,100%,.1);--pdl-bg3-hover:#9b9b9b;--pdl-btn1:hsla(0,0%,100%,.4);--pdl-btn1-hover:hsla(0,0%,100%,.6);--pdl-border1:hsla(0,0%,100%,.3);--pdl-text1:#d6d6d6}:root .pdl-btn-main{background-image:url(\"data:image/svg+xml;charset=utf-8,%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/svg%3E\")}:root .pdl-dlbar .pdl-btn-all:before{background-image:url(\"data:image/svg+xml;charset=utf-8,%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/svg%3E\")}:root .pdl-dlbar .pdl-btn-all:hover:before{background-image:url(\"data:image/svg+xml;charset=utf-8,%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/svg%3E\")}:root .pdl-dlbar .pdl-stop:before{background-image:url(\"data:image/svg+xml;charset=utf-8,%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/svg%3E\")}:root .pdl-dlbar .pdl-stop:hover:before{background-image:url(\"data:image/svg+xml;charset=utf-8,%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/svg%3E\")}}";
  n(css$5,{});

  var css$4 = ".pdl-checkbox{appearance:none;background-color:#858585!important;border:2px solid transparent!important;border-radius:14px!important;box-sizing:border-box;cursor:pointer;height:14px;margin:0!important;padding:0!important;position:relative;transition:background-color .2s ease 0s,box-shadow .2s ease 0s;vertical-align:top;width:28px}.pdl-checkbox:hover{background-color:var(--pdl-bg3-hover)!important}.pdl-checkbox:checked{background-color:#0096fa!important}.pdl-checkbox:after{background-color:#fff;border-radius:10px;content:\"\";display:block;height:10px;left:0;position:absolute;top:0;transform:translateX(0);transition:transform .2s ease 0s;width:10px}.pdl-checkbox:checked:after{transform:translateX(14px)}";
  n(css$4,{});

  var css$3 = "@property --pdl-progress{syntax:\"<percentage>\";inherits:true;initial-value:0}@keyframes pdl_loading{to{transform:translate(-50%,-50%) rotate(1turn)}}.pdl-btn{background:no-repeat 50%/85%;background-image:url(\"data:image/svg+xml;charset=utf-8,%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/svg%3E\");border:none;border-radius:4px;color:#01b468;cursor:pointer;display:inline-block;font-size:13px;font-weight:700;height:32px;line-height:32px;margin:0;overflow:hidden;padding:0;position:relative;text-align:center;text-decoration:none!important;text-overflow:ellipsis;user-select:none;white-space:nowrap;width:32px}.pdl-btn.pdl-btn-main{margin:0 0 0 10px}.pdl-btn.pdl-btn-sub{background-color:hsla(0,0%,100%,.5);left:calc((100% - 32px)*var(--pdl-btn-left)/100);position:absolute;top:calc((100% - 32px)*var(--pdl-btn-top)/100)}.pdl-btn.pdl-btn-sub.presentation{border-radius:8px;left:auto;position:fixed;right:20px;top:50px;z-index:1}.pdl-btn.pdl-btn-sub.manga-viewer{border-radius:8px;left:auto;right:4px;top:80%}.pdl-btn.pdl-btn-sub.self-bookmark{left:calc((100% - 32px)*var(--pdl-btn-self-bookmark-left)/100);top:calc((100% - 32px)*var(--pdl-btn-self-bookmark-top)/100)}.ranking-image-item .pdl-btn.pdl-btn-sub{z-index:1}.pdl-btn.pdl-error{background-image:url(\"data:image/svg+xml;charset=utf-8,%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/svg%3E\")!important}.pdl-btn.pdl-complete{background-image:url(\"data:image/svg+xml;charset=utf-8,%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.267-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/svg%3E\")!important}.pdl-btn.pdl-progress{background-image:none!important;cursor:default!important}.pdl-btn.pdl-progress:after{border-radius:50%;content:\"\";display:inline-block;height:27px;left:50%;-webkit-mask:radial-gradient(transparent,transparent 54%,#000 57%,#000);mask:radial-gradient(transparent,transparent 54%,#000 57%,#000);position:absolute;top:50%;transform:translate(-50%,-50%);width:27px}.pdl-btn.pdl-progress:not(:empty):after{background:conic-gradient(#01b468 0,#01b468 var(--pdl-progress),transparent var(--pdl-progress),transparent);transition:--pdl-progress .2s ease}.pdl-btn.pdl-progress:empty:after{animation:pdl_loading 1.5s linear infinite;background:conic-gradient(#01b468 0,#01b468 25%,rgba(1,180,104,.2) 25%,rgba(1,180,104,.2))}.pdl-btn.pdl-tag{background-color:var(--pdl-btn1);border-bottom-right-radius:4px;border-top-right-radius:4px;height:auto;left:-1px;transition:background-image .5s}.pdl-btn.pdl-tag:hover{background-color:var(--pdl-btn1-hover)}.pdl-btn.pdl-modal-tag{background-color:var(--pdl-btn1);background-origin:content-box;border-radius:4px;height:50px;padding:5px;position:absolute;right:65px;top:6px;transition:background-color .25s;width:42px}.pdl-btn.pdl-modal-tag:hover{background-color:var(--pdl-btn1-hover)}.pdl-btn.pdl-tag-hide{background-image:url(\"data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'/%3E\")!important;pointer-events:none!important}.pdl-wrap-artworks{bottom:0;margin-top:40px;position:absolute;right:8px;top:0;z-index:1}.pdl-wrap-artworks .pdl-btn-sub.artworks{left:0;position:sticky;top:40px}";
  n(css$3,{});

  var css$2 = ".pdl-modal *{font-family:'win-bug-omega, system-ui, -apple-system, \"Segoe UI\", Roboto, Ubuntu, Cantarell, \"Noto Sans\", \"Hiragino Kaku Gothic ProN\", Meiryo, sans-serif';line-height:1.15}.pdl-modal{background-color:rgba(0,0,0,.32);display:flex;height:100%;left:0;position:fixed;top:0;user-select:none;width:100%;z-index:99}.pdl-modal .pdl-dialog{background-color:var(--pdl-bg1);border-radius:24px;font-size:16px;margin:auto;max-width:1000px;min-width:600px;padding:20px 40px 30px;position:relative;width:50vw}.pdl-modal .pdl-dialog .pdl-dialog-header>h3{font-size:1.17em;font-weight:700;margin:1em 0}.pdl-modal .pdl-dialog .pdl-dialog-close{background-color:transparent;background:linear-gradient(#7d7d7d,#7d7d7d) 50%/18px 2px no-repeat,linear-gradient(#7d7d7d,#7d7d7d) 50%/2px 18px no-repeat;border:none;border-radius:50%;cursor:pointer;height:25px;margin:0;padding:0;position:absolute;right:10px;top:10px;transform:rotate(45deg);transition:background-color .25s;width:25px}.pdl-modal .pdl-dialog .pdl-dialog-close:hover{background-color:rgba(0,0,0,.05)}.pdl-modal .pdl-dialog .pdl-dialog-content a{color:#0096fa;text-decoration:underline}.pdl-modal .pdl-dialog .pdl-dialog-content input[type=radio],.pdl-modal .pdl-dialog .pdl-dialog-content input[type=radio]+label{cursor:pointer}.pdl-modal .pdl-dialog .pdl-dialog-content hr{border:none;border-top:1px solid var(--pdl-border1);height:0!important;margin:0}.pdl-modal .pdl-dialog .pdl-dialog-content hr.sub{margin-inline-start:1.5em}.pdl-modal .pdl-dialog .pdl-dialog-content hr.vertical{border:none;border-left:1px solid var(--pdl-border1);height:1.15em!important}.pdl-modal .pdl-dialog .pdl-dialog-content .pdl-dialog-button{background-color:#fff;border:1px solid #7d7d7d;border-radius:5px;cursor:pointer;font-size:16px;line-height:1.15;padding:.5em 1.5em;transition:opacity .2s}.pdl-modal .pdl-dialog .pdl-dialog-content .pdl-dialog-button[disabled]{background-color:#fff;border-color:#e4e7ed;color:#c0c4cc;cursor:not-allowed!important;opacity:1!important}.pdl-modal .pdl-dialog .pdl-dialog-content .pdl-dialog-button:hover{opacity:.7}.pdl-modal .pdl-dialog .pdl-dialog-content .pdl-dialog-button.primary{background-color:#0096fa;border-color:#0096fa;color:#fff}.pdl-modal .pdl-dialog .pdl-dialog-content .pdl-dialog-button.primary[disabled]{background-color:#a0cfff;border-color:#a0cfff}.pdl-modal .pdl-dialog .pdl-dialog-content .pdl-dialog-button.icon{padding:.5em .8em}.pdl-changelog h4{font-weight:700!important;margin:1em 0}.pdl-changelog ul{padding-inline-start:40px!important}.pdl-changelog ul li{line-height:2;list-style-type:disc!important}.pdl-changelog p{margin:.5em 0}.pdl-tabs-nav{align-items:center;border-bottom:1px solid #dcdfe6;display:flex;position:relative}.pdl-tabs-nav .pdl-tabs__active-bar{background-color:#0096fa;bottom:-1px;height:2px;left:0;position:absolute;transition:width .2s,transform .2s;z-index:1}.pdl-tabs-nav .pdl-tab-item{cursor:pointer;line-height:2.5;padding:0 16px;transition:color .2s}.pdl-tabs-nav .pdl-tab-item:hover{color:#0096fa}.pdl-tabs-nav .pdl-tab-item.active{color:#0096fa;font-weight:700}.pdl-tabs-nav .pdl-tab-item:nth-child(2){padding-left:0}.pdl-tabs-nav .pdl-tab-item:last-child{padding-right:0}.pdl-tabs-content{min-height:200px;padding:16px}.pdl-tabs-content .option-header{font-weight:700}.pdl-tabs-content #pdl-setting-filename .pdl-input-wrap,.pdl-tabs-content #pdl-setting-filename .tags-option{align-items:center;display:flex;gap:12px;margin:12px 0}.pdl-tabs-content #pdl-setting-filename .pdl-input-wrap label,.pdl-tabs-content #pdl-setting-filename .tags-option .tags-title{cursor:default;font-weight:700;min-width:5em}.pdl-tabs-content #pdl-setting-filename .pdl-input-wrap input[type=text]{border:1px solid #333;flex:1;font-size:16px;height:auto;line-height:1.5;padding:.5em}.pdl-tabs-content #pdl-setting-filename .pdl-input-wrap input[type=text]:focus{background-color:#fff!important}.pdl-tabs-content #pdl-setting-filename .pdl-input-wrap button{line-height:1.5}.pdl-tabs-content #pdl-setting-filename .tags-option .tags-content{display:flex;flex:1;gap:20px}.pdl-tabs-content #pdl-setting-filename .pdl-options{align-items:center;cursor:pointer;display:flex;justify-content:space-between;padding:.6em 0}.pdl-tabs-content #pdl-setting-filename .pdl-options:hover{background-color:var(--pdl-bg2-hover)}.pdl-tabs-content #pdl-setting-ugoira #pdl-ugoira-format-wrap{display:flex;flex-wrap:nowrap;justify-content:space-between;margin:1.5em 1em}.pdl-tabs-content #pdl-setting-ugoira #pdl-ugoira-format-wrap .pdl-ugoira-format-item label{padding-left:4px}.pdl-tabs-content #pdl-setting-history div{margin:1em 0;text-align:center}.pdl-tabs-content #pdl-setting-history div .btn-history{width:80%}.pdl-tabs-content #pdl-setting-others .pdl-options{align-items:center;border-radius:4px;cursor:pointer;display:flex;gap:20px;padding:1em .5em;transition:background-color .2s}.pdl-tabs-content #pdl-setting-others .pdl-options:hover{background-color:var(--pdl-bg2-hover)}.pdl-tabs-content #pdl-setting-others .pdl-options.sub-option{padding:.5em;padding-inline-start:2em}.pdl-tabs-content #pdl-setting-donate{text-align:center}.pdl-tabs-content #pdl-setting-donate p{margin:.5em 0}.pdl-tabs-content .pdl-adjust-button{display:flex;gap:32px;justify-content:space-between}.pdl-tabs-content .pdl-adjust-button .pdl-adjust-content{flex:2}.pdl-tabs-content .pdl-adjust-button .pdl-adjust-content .pdl-adjust-item{margin-bottom:1em}.pdl-tabs-content .pdl-adjust-button .pdl-adjust-content .pdl-adjust-item .pdl-adjust-title{font-weight:700;margin-bottom:.8em}.pdl-tabs-content .pdl-adjust-button .pdl-adjust-content .pdl-adjust-item .pdl-adjust-select{align-items:center;display:flex;gap:20px;margin:.6em 0;padding:0 .4em}.pdl-tabs-content .pdl-adjust-button .pdl-adjust-content .pdl-adjust-item .pdl-adjust-select input[type=range]{flex:1 1;max-width:450px}.pdl-tabs-content .pdl-adjust-button .pdl-adjust-preview{align-self:center;display:flex;flex:1}.pdl-tabs-content .pdl-adjust-button .pdl-adjust-preview .pdl-thumbnail-sample{background-color:rgba(0,150,250,.15);border-radius:4px;height:184px;position:relative;width:184px}";
  n(css$2,{});

  var css$1 = ".pdl-dlbar{display:flex;flex-grow:1}.pdl-dlbar.pdl-dlbar-follow_latest{padding:0 8px}.pdl-dlbar .pdl-dlbar-status_bar{color:#858585;cursor:default;flex-grow:1;font-size:16px;font-weight:700;height:46px;line-height:46px;padding-right:8px;text-align:right;white-space:nowrap}.pdl-dlbar .pdl-btn-all,.pdl-dlbar .pdl-stop{background-color:transparent;border:none;padding:0 8px}.pdl-dlbar .pdl-btn-all:hover,.pdl-dlbar .pdl-stop:hover{color:var(--pdl-text1)}.pdl-dlbar .pdl-btn-all:before,.pdl-dlbar .pdl-stop:before{background:no-repeat 50%/85%;content:\"\";height:24px;transition:background-image .2s ease 0s;width:24px}.pdl-dlbar .pdl-btn-all:before{background-image:url(\"data:image/svg+xml;charset=utf-8,%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/svg%3E\")}.pdl-dlbar .pdl-btn-all:hover:before{background-image:url(\"data:image/svg+xml;charset=utf-8,%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/svg%3E\")}.pdl-dlbar .pdl-stop:before{background-image:url(\"data:image/svg+xml;charset=utf-8,%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/svg%3E\")}.pdl-dlbar .pdl-stop:hover:before{background-image:url(\"data:image/svg+xml;charset=utf-8,%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/svg%3E\")}.pdl-filter-wrap{color:#858585;display:flex;font-size:14px;font-weight:700;gap:12px;justify-content:flex-end;line-height:14px;margin:4px 0;transition:color .2s ease 0s}.pdl-filter-wrap .pdl-filter:hover{color:var(--pdl-text1)}.pdl-filter-wrap .pdl-filter label{cursor:pointer;padding-left:8px}";
  n(css$1,{});

  const regexp = {
      preloadData: /"meta-preload-data" content='(.*?)'>/,
      globalData: /"meta-global-data" content='(.*?)'>/,
      artworksPage: /artworks\/(\d+)$/,
      userPage: /users\/(\d+)/,
      bookmarkPage: /users\/(\d+)\/bookmarks\/artworks/,
      userPageTags: /users\/\d+\/(artworks|illustrations|manga|bookmarks(?!artworks))/,
      searchPage: /\/tags\/.*\/(artworks|illustrations|manga)/,
      suscribePage: /bookmark_new_illust/,
      activityHref: /illust_id=(\d+)/,
      originSrcPageNum: /(?<=_p)\d+/,
      followLatest: /\/bookmark_new_illust(?:_r18)?\.php/,
      historyPage: /\/history\.php/,
      historyThumbnailsId: /\d+(?=_)/
  };
  const depsUrls = {
      gifWorker: 'https://cdnjs.cloudflare.com/ajax/libs/gif.js/0.2.0/gif.worker.js',
      pako: 'https://cdnjs.cloudflare.com/ajax/libs/pako/2.0.4/pako.min.js',
      upng: 'https://cdnjs.cloudflare.com/ajax/libs/upng-js/2.1.0/UPNG.min.js'
  };
  const creditCode = `<img style="display: block; margin: 1em auto; width: 200px"
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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"
/>`;

  function getSelfId() {
      return document.querySelector('#qualtrics_user-id')?.textContent ?? '';
  }

  function getIllustId(node) {
      const isLinkToArtworksPage = regexp.artworksPage.exec(node.getAttribute('href') || '');
      if (isLinkToArtworksPage) {
          if (node.getAttribute('data-gtm-value') ||
              [
                  'gtm-illust-recommend-node-node',
                  'gtm-discover-user-recommend-node',
                  'work',
                  '_history-item',
                  '_history-related-item'
              ].some((className) => node.classList.contains(className))) {
              return isLinkToArtworksPage[1];
          }
      }
      else if (node.className.includes('_history-item')) {
          const result = regexp.historyThumbnailsId.exec(node.getAttribute('style') || '');
          if (result)
              return result[0];
      }
      else {
          const isActivityThumb = regexp.activityHref.exec(node.getAttribute('href') || '');
          if (isActivityThumb && node.classList.contains('work')) {
              return isActivityThumb[1];
          }
      }
      return '';
  }

  function getLogger() {
      const methods = ['info', 'warn', 'error'];
      const style = ['color: green;', 'color: orange;', 'color: red;'];
      const logLevel = 2 ;
      const namePrefix = '[Pixiv Downlaoder] ';
      function log(level, args) {
          if (logLevel <= level)
              console[methods[level]]('%c[Pixiv Downloader]', style[level], ...args);
      }
      return {
          info(...args) {
              log(0 , args);
          },
          warn(...args) {
              log(1 , args);
          },
          error(...args) {
              log(2 , args);
          },
          time(label) {
              console.time(namePrefix + label);
          },
          timeLog(label) {
              console.timeLog(namePrefix + label);
          },
          timeEnd(label) {
              console.timeEnd(namePrefix + label);
          }
      };
  }
  const logger = getLogger();

  var IllustType;
  (function (IllustType) {
      IllustType[IllustType["illusts"] = 0] = "illusts";
      IllustType[IllustType["manga"] = 1] = "manga";
      IllustType[IllustType["ugoira"] = 2] = "ugoira";
  })(IllustType || (IllustType = {}));
  var BookmarkRestrict;
  (function (BookmarkRestrict) {
      BookmarkRestrict[BookmarkRestrict["public"] = 0] = "public";
      BookmarkRestrict[BookmarkRestrict["private"] = 1] = "private";
  })(BookmarkRestrict || (BookmarkRestrict = {}));

  function sleep(delay) {
      return new Promise((resolve) => {
          setTimeout(resolve, delay);
      });
  }
  function wakeableSleep(delay) {
      let wake = () => void {};
      const sleep = new Promise((r) => {
          setTimeout(r, delay);
          wake = r;
      });
      return {
          wake,
          sleep
      };
  }
  function replaceInvalidChar(str) {
      if (typeof str !== 'string')
          throw new TypeError('expect string but got ' + typeof str);
      if (!str)
          return '';
      return str
          .replace(/\p{C}/gu, '')
          .replace(/\\/g, '\')
          .replace(/\//g, '/')
          .replace(/:/g, ':')
          .replace(/\*/g, '*')
          .replace(/\?/g, '?')
          .replace(/\|/g, '|')
          .replace(/"/g, '"')
          .replace(/</g, '﹤')
          .replace(/>/g, '﹥')
          .replace(/~/g, '~')
          .trim()
          .replace(/^\.|\.$/g, '.');
  }
  function unescapeHtml(str) {
      if (typeof str !== 'string')
          throw new TypeError('expect string but got ' + typeof str);
      if (!str)
          return '';
      const el = document.createElement('p');
      el.innerHTML = str;
      return el.innerText;
  }
  function stringToFragment(string) {
      const renderer = document.createElement('template');
      renderer.innerHTML = string;
      return renderer.content;
  }

  function loadConfig() {
      const defaultConfig = Object.freeze({
          version: "0.10.0",
          ugoiraFormat: 'zip',
          folderPattern: 'pixiv/{artist}',
          filenamePattern: '{artist}_{title}_{id}_p{page}',
          tagLang: 'ja',
          showMsg: true,
          filterExcludeDownloaded: false,
          filterIllusts: true,
          filterManga: true,
          filterUgoira: true,
          bundleIllusts: false,
          bundleManga: false,
          addBookmark: false,
          addBookmarkWithTags: false,
          privateR18: false,
          useFileSystemAccess: false,
          fileSystemFilenameConflictAction: 'uniquify',
          showPopupButton: true
      });
      const config = (() => {
          if (!localStorage.pdlSetting)
              return {};
          let config;
          try {
              config = JSON.parse(localStorage.pdlSetting);
          }
          catch (error) {
              console.log(error);
              return {};
          }
          if (config.version !== defaultConfig.version) {
              config.version = defaultConfig.version;
              config.showMsg = true;
          }
          return config;
      })();
      return {
          get(key) {
              return config[key] ?? defaultConfig[key];
          },
          set(key, value) {
              if (config[key] !== value) {
                  config[key] = value;
                  localStorage.pdlSetting = JSON.stringify(config);
                  logger.info('Config set:', key, value);
              }
          }
      };
  }
  const config = loadConfig();

  const handleWorker = `
let webpApi = {};
Module.onRuntimeInitialized = () => {
  webpApi = {
    init: Module.cwrap('init', '', ['number', 'number', 'number']),
    createBuffer: Module.cwrap('createBuffer', 'number', ['number']),
    addFrame: Module.cwrap('addFrame', 'number', ['number', 'number', 'number']),
    generate: Module.cwrap('generate', 'number', []),
    freeResult: Module.cwrap('freeResult', '', []),
    getResultPointer: Module.cwrap('getResultPointer', 'number', []),
    getResultSize: Module.cwrap('getResultSize', 'number', []),
  };

  postMessage('ok');
};

onmessage = (evt) => {
  const { data, delays, lossless = 1, quality = 75, method = 4} = evt.data;

  webpApi.init(lossless, quality, method);
  data.forEach((u8a, idx) => {
    const pointer = webpApi.createBuffer(u8a.length);
    Module.HEAPU8.set(u8a, pointer);
    webpApi.addFrame(pointer, u8a.length, delays[idx]);
    postMessage(idx);
  });

  webpApi.generate();
  const resultPointer = webpApi.getResultPointer();
  const resultSize = webpApi.getResultSize();
  const result = new Uint8Array(Module.HEAP8.buffer, resultPointer, resultSize);
  postMessage(result);
  webpApi.freeResult();
};`;

  class RequestError extends Error {
      response;
      constructor(message, response) {
          super(message);
          this.name = 'RequestError';
          this.response = response;
      }
  }
  class CancelError extends Error {
      constructor() {
          super('User aborted');
          this.name = 'CancelError';
      }
  }
  class JsonDataError extends Error {
      constructor(msg) {
          super(msg);
          this.name = 'JsonDataError';
      }
  }

  function createService() {
      async function _requestJson(url, init) {
          logger.info('fetch url:', url);
          const res = await fetch(url, init);
          if (!res.ok)
              throw new RequestError('Request ' + url + ' failed with status code ' + res.status, res);
          const data = await res.json();
          if (data.error)
              throw new JsonDataError(data.message);
          return data.body;
      }
      async function _getDeps(url) {
          return fetch(url).then((res) => {
              if (res.ok)
                  return res.text();
              throw new RequestError(`Fetch dependency ${url} failed with status code ${res.status}`, res);
          });
      }
      return {
          async getJson(url) {
              let json;
              let retry = 0;
              const MAX_RETRY = 3;
              do {
                  try {
                      json = await _requestJson(url);
                  }
                  catch (error) {
                      logger.error(error);
                      retry++;
                      if (retry === MAX_RETRY)
                          throw error;
                      sleep(3000);
                  }
              } while (!json);
              return json;
          },
          async getArtworkHtml(illustId) {
              logger.info('Fetch illust:', illustId);
              let params = '';
              const tagLang = config.get('tagLang');
              if (tagLang !== 'ja')
                  params = '?lang=' + tagLang;
              const res = await fetch('https://www.pixiv.net/artworks/' + illustId + params);
              if (!res.ok)
                  throw new RequestError('Request failed with status code ' + res.status, res);
              return await res.text();
          },
          async getGifWS() {
              let gifWS;
              if (!(gifWS = await GM_getValue('gifWS'))) {
                  gifWS = await _getDeps(depsUrls.gifWorker);
                  GM_setValue('gifWS', gifWS);
              }
              return gifWS;
          },
          async getApngWS() {
              let apngWS;
              if (!(apngWS = await GM_getValue('apngWS'))) {
                  const [pako, upng] = await Promise.all([_getDeps(depsUrls.pako), _getDeps(depsUrls.upng)]);
                  const upngScript = upng.replace('window.UPNG', 'UPNG').replace('window.pako', 'pako');
                  const workerEvt = `onmessage = (evt) => {
          const {data, width, height, delay } = evt.data;
          const png = UPNG.encode(data, width, height, 0, delay, {loop: 0});
          if (!png) console.error('Convert Apng failed.');
          postMessage(png);
        };`;
                  apngWS = workerEvt + pako + upngScript;
                  GM_setValue('apngWS', apngWS);
              }
              return apngWS;
          },
          getWebpWS() {
              return workerChunk__default["default"] + handleWorker;
          },
          addBookmark(illustId, token, tags = [], restrict = BookmarkRestrict.public) {
              return _requestJson('/ajax/illusts/bookmarks/add', {
                  method: 'POST',
                  headers: {
                      accept: 'application/json',
                      'content-type': 'application/json; charset=utf-8',
                      'x-csrf-token': token
                  },
                  body: JSON.stringify({
                      illust_id: illustId,
                      restrict,
                      comment: '',
                      tags
                  })
              });
          },
          getFollowLatestWorks(page, mode = 'all') {
              return _requestJson(`/ajax/follow_latest/illust?p=${page}&mode=${mode}&lang=jp`);
          },
          getUserAllProfile(userId) {
              return _requestJson('/ajax/user/' + userId + '/profile/all');
          },
          getUgoiraMeta(illustId) {
              return _requestJson('/ajax/illust/' + illustId + '/ugoira_meta');
          },
          getArtworkDetail(illustId) {
              let params = '';
              const tagLang = config.get('tagLang');
              if (tagLang !== 'ja')
                  params = '?lang=' + tagLang;
              return _requestJson('/ajax/illust/' + illustId + params);
          }
      };
  }
  const api = createService();

  function addBookmark(pdlBtn, illustId, token, tags) {
      if (!config.get('addBookmark'))
          return;
      api
          .addBookmark(illustId, token, config.get('addBookmarkWithTags') ? tags : [], config.get('privateR18') && tags.includes('R-18') ? BookmarkRestrict.private : BookmarkRestrict.public)
          .then(() => {
          const bookmarkBtnRef = findBookmarkBtn(pdlBtn);
          if (!bookmarkBtnRef)
              return;
          switch (bookmarkBtnRef.kind) {
              case "main" : {
                  const pathBorder = bookmarkBtnRef.button.querySelector('svg g path');
                  pathBorder && (pathBorder.style.color = 'rgb(255, 64, 96)');
                  break;
              }
              case "sub" : {
                  const pathBorder = bookmarkBtnRef.button.querySelector('path');
                  pathBorder && (pathBorder.style.color = 'rgb(255, 64, 96)');
                  break;
              }
              case "rank" : {
                  bookmarkBtnRef.button.style.backgroundColor = 'rgb(255, 64, 96)';
                  break;
              }
          }
      })
          .catch((reason) => {
          logger.error(reason.message);
      });
  }
  function findBookmarkBtn(pdlBtn) {
      const bookmarkBtnRef = {};
      if (pdlBtn.classList.contains('pdl-btn-sub')) {
          const btn = pdlBtn.parentElement?.nextElementSibling?.querySelector('button[type="button"]');
          if (btn) {
              bookmarkBtnRef.kind = "sub" ;
              bookmarkBtnRef.button = btn;
          }
          else {
              const btn = pdlBtn.parentElement?.querySelector('div._one-click-bookmark');
              if (btn) {
                  bookmarkBtnRef.kind = "rank" ;
                  bookmarkBtnRef.button = btn;
              }
          }
      }
      else if (pdlBtn.classList.contains('pdl-btn-main')) {
          const btn = pdlBtn.parentElement?.parentElement?.querySelector('button.gtm-main-bookmark');
          if (btn) {
              bookmarkBtnRef.kind = "main" ;
              bookmarkBtnRef.button = btn;
          }
      }
      else {
          return logger.error(new Error('Can not find bookmark button.'));
      }
      return bookmarkBtnRef;
  }

  const env = {
      isFirefox() {
          return navigator.userAgent.includes('Firefox');
      },
      isViolentmonkey() {
          return GM_info.scriptHandler === 'Violentmonkey';
      },
      isTampermonkey() {
          return GM_info.scriptHandler === 'Tampermonkey';
      },
      isBlobDlAvaliable() {
          return !this.isFirefox() || (this.isFirefox() && this.isTampermonkey() && parseFloat(GM_info.version ?? '') < 4.18);
      },
      isSupportSubpath() {
          return this.isBrowserDownloadMode();
      },
      isBrowserDownloadMode() {
          return GM_info.downloadMode === 'browser';
      },
      isConflictActionEnable() {
          return this.isTampermonkey() && parseFloat(GM_info.version ?? '') >= 4.18 && this.isBrowserDownloadMode();
      },
      isConflictActionPromptEnable() {
          return !this.isFirefox() && this.isConflictActionEnable();
      },
      isFileSystemAccessAvaliable() {
          return (typeof unsafeWindow.showDirectoryPicker === 'function' && typeof unsafeWindow.showSaveFilePicker === 'function');
      }
  };

  const offscreenCanvasWorker = `
let canvas;
let ctx;
let frames;
let delays;
let idx;

function drawFrame(image) {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.drawImage(image, 0, 0);
}

function displayUgoira() {
  drawFrame(frames[idx]);

  setTimeout(() => {
    if (idx === frames.length - 1) {
      postMessage('end');
    } else {
      idx++;
      displayUgoira();
    }
  }, delays[idx]);

  postMessage(idx);
}

onmessage = (evt) => {
  canvas = evt.data.canvas;
  frames = evt.data.frames;
  delays = evt.data.delays;
  idx = 0;
  ctx = canvas.getContext('2d');
  postMessage('start');
  displayUgoira();
};
`;

  function createConverter() {
      const framesData = {};
      const freeApngWorkers = [];
      const freeWebpWorkers = [];
      const freeWebmWorkers = [];
      const MAX_CONVERT = 2;
      let isStop = false;
      let queue = [];
      let active = [];
      const cachedQueue = {
          gif: [],
          png: []
      };
      const depsUrl = {
          gif: '',
          png: '',
          webp: URL.createObjectURL(new Blob([api.getWebpWS()], { type: 'text/javascript' })),
          webm: URL.createObjectURL(new Blob([offscreenCanvasWorker], { type: 'text/javascript' }))
      };
      let LoadStatus;
      (function (LoadStatus) {
          LoadStatus[LoadStatus["unloaded"] = 0] = "unloaded";
          LoadStatus[LoadStatus["loading"] = 1] = "loading";
          LoadStatus[LoadStatus["loaded"] = 2] = "loaded";
      })(LoadStatus || (LoadStatus = {}));
      const depsStatus = {
          gif: {
              loaded: LoadStatus.unloaded,
              load() {
                  logger.info('开始加载gif依赖');
                  this.loaded = LoadStatus.loading;
                  return api.getGifWS().then((str) => {
                      depsUrl.gif = URL.createObjectURL(new Blob([str], { type: 'text/javascript' }));
                      this.loaded = LoadStatus.loaded;
                      logger.info('加载gif依赖完成');
                  });
              }
          },
          png: {
              loaded: LoadStatus.unloaded,
              load() {
                  logger.info('开始加载png依赖');
                  this.loaded = LoadStatus.loading;
                  return api.getApngWS().then((str) => {
                      depsUrl.png = URL.createObjectURL(new Blob([str], { type: 'text/javascript' }));
                      this.loaded = LoadStatus.loaded;
                      logger.info('加载png依赖完成');
                  });
              }
          }
      };
      const convertTo = {
          webp: (frames, convertMeta) => {
              return new Promise((resolve, reject) => {
                  let worker;
                  let reuse = false;
                  logger.time(convertMeta.id);
                  if (freeWebpWorkers.length) {
                      logger.info('Reuse webp workers.');
                      worker = freeWebpWorkers.shift();
                      reuse = true;
                  }
                  else {
                      worker = new Worker(depsUrl.webp);
                  }
                  convertMeta.abort = () => {
                      logger.timeEnd(convertMeta.id);
                      logger.warn('Convert stop manually.' + convertMeta.id);
                      reject(new CancelError());
                      convertMeta.isAborted = true;
                      worker.terminate();
                  };
                  const workerLoad = new Promise((onLoaded) => {
                      if (reuse)
                          return onLoaded();
                      worker.onmessage = (evt) => {
                          if (evt.data === 'ok') {
                              logger.info('Webp worker loaded.');
                              onLoaded();
                          }
                      };
                  });
                  const delays = convertMeta.source.delays;
                  const data = [];
                  let completed = 0;
                  frames.forEach((frame, idx) => {
                      const canvas = document.createElement('canvas');
                      const width = (canvas.width = frame.naturalWidth);
                      const height = (canvas.height = frame.naturalHeight);
                      const context = canvas.getContext('2d');
                      if (!context)
                          return;
                      context.drawImage(frame, 0, 0, width, height);
                      data.push(new Promise((onFulfilled, onRejected) => {
                          canvas.toBlob((blob) => {
                              if (!blob)
                                  return onRejected(new TypeError('Convert failed when invoke canvas.toBlob() ' + idx));
                              blob.arrayBuffer().then((buffer) => {
                                  const u8a = new Uint8Array(buffer);
                                  onFulfilled(u8a);
                                  convertMeta.onProgress?.((++completed / frames.length) * 0.5, 'webp');
                              });
                          }, 'image/webp', 1);
                      }));
                  });
                  workerLoad
                      .then(() => Promise.all(data))
                      .then((u8arrs) => {
                      if (convertMeta.isAborted)
                          return;
                      logger.timeLog(convertMeta.id);
                      worker.onmessage = (evt) => {
                          const data = evt.data;
                          if (typeof data !== 'object') {
                              convertMeta.onProgress?.(0.5 + (evt.data / frames.length) * 0.5, 'webp');
                          }
                          else {
                              logger.timeEnd(convertMeta.id);
                              freeWebpWorkers.push(worker);
                              resolve(new Blob([evt.data], { type: 'image/webp' }));
                          }
                      };
                      worker.postMessage({ data: u8arrs, delays });
                  }, (reason) => {
                      logger.timeLog(convertMeta.id);
                      reject(reason);
                  });
              });
          },
          gif: (frames, convertMeta) => {
              return new Promise((resolve, reject) => {
                  const gif = new GIF__default["default"]({
                      workers: 2,
                      quality: 10,
                      workerScript: depsUrl.gif
                  });
                  convertMeta.abort = () => {
                      gif.abort();
                  };
                  logger.info('Start convert:', convertMeta.id);
                  logger.time(convertMeta.id);
                  frames.forEach((frame, i) => {
                      gif.addFrame(frame, {
                          delay: convertMeta.source.delays[i]
                      });
                  });
                  gif.on('progress', (progress) => {
                      if (typeof convertMeta.onProgress === 'function')
                          convertMeta.onProgress(progress, 'gif');
                  });
                  gif.on('finished', (gifBlob) => {
                      logger.timeEnd(convertMeta.id);
                      resolve(gifBlob);
                  });
                  gif.on('abort', () => {
                      logger.timeEnd(convertMeta.id);
                      logger.warn('Convert stop manually. ' + convertMeta.id);
                      convertMeta.isAborted = true;
                      reject(new CancelError());
                  });
                  gif.render();
              });
          },
          png: (frames, convertMeta) => {
              return new Promise((resolve, reject) => {
                  const canvas = document.createElement('canvas');
                  const width = (canvas.width = frames[0].naturalWidth);
                  const height = (canvas.height = frames[0].naturalHeight);
                  const context = canvas.getContext('2d', { willReadFrequently: true });
                  if (!context)
                      return reject(new TypeError('Can not get canvas context'));
                  const data = [];
                  const delay = convertMeta.source.delays;
                  logger.info('Start convert:', convertMeta.id);
                  logger.time(convertMeta.id);
                  for (const frame of frames) {
                      if (convertMeta.isAborted) {
                          logger.timeEnd(convertMeta.id);
                          logger.warn('Convert stop manually. ' + convertMeta.id);
                          return reject(new CancelError());
                      }
                      context.clearRect(0, 0, width, height);
                      context.drawImage(frame, 0, 0, width, height);
                      data.push(context.getImageData(0, 0, width, height).data);
                  }
                  logger.timeLog(convertMeta.id);
                  let worker;
                  if (freeApngWorkers.length) {
                      worker = freeApngWorkers.shift();
                      logger.info('Reuse apng workers.');
                  }
                  else {
                      worker = new Worker(depsUrl.png);
                  }
                  convertMeta.abort = () => {
                      logger.timeEnd(convertMeta.id);
                      logger.warn('Convert stop manually. ' + convertMeta.id);
                      reject(new CancelError());
                      convertMeta.isAborted = true;
                      worker.terminate();
                  };
                  worker.onmessage = function (e) {
                      freeApngWorkers.push(worker);
                      logger.timeEnd(convertMeta.id);
                      if (!e.data) {
                          return reject(new TypeError('Failed to get png data. ' + convertMeta.id));
                      }
                      const pngBlob = new Blob([e.data], { type: 'image/png' });
                      resolve(pngBlob);
                  };
                  const cfg = { data, width, height, delay };
                  worker.postMessage(cfg);
              });
          },
          webm: (frames, convertMeta) => {
              return new Promise((resolve, reject) => {
                  let worker;
                  if (freeWebmWorkers.length) {
                      logger.info('Reuse webm workers.');
                      worker = freeWebmWorkers.shift();
                  }
                  else {
                      worker = new Worker(depsUrl.webm);
                  }
                  const canvas = document.createElement('canvas');
                  canvas.width = frames[0].naturalWidth;
                  canvas.height = frames[0].naturalHeight;
                  const stream = canvas.captureStream();
                  const recorder = new MediaRecorder(stream, {
                      mimeType: 'video/webm',
                      videoBitsPerSecond: 80000000
                  });
                  const delays = convertMeta.source.delays;
                  const data = [];
                  recorder.ondataavailable = (event) => {
                      if (event.data && event.data.size) {
                          data.push(event.data);
                      }
                  };
                  recorder.onstop = () => {
                      freeWebmWorkers.push(worker);
                      if (convertMeta.isAborted) {
                          logger.warn('Convert stop manually.' + convertMeta.id);
                          return reject(new CancelError());
                      }
                      resolve(new Blob(data, { type: 'video/webm' }));
                  };
                  const offscreenCanvas = canvas.transferControlToOffscreen();
                  const bitmaps = frames.map((frame) => createImageBitmap(frame));
                  worker.onmessage = (evt) => {
                      switch (evt.data) {
                          case 'start':
                              recorder.start();
                              logger.time(convertMeta.id);
                              break;
                          case 'end':
                              recorder.stop();
                              logger.timeEnd(convertMeta.id);
                              break;
                          default:
                              if (typeof convertMeta.onProgress === 'function')
                                  convertMeta.onProgress((evt.data + 1) / frames.length, 'webm');
                              break;
                      }
                  };
                  Promise.all(bitmaps).then((frameBitmaps) => {
                      logger.info(`ugoira时长${delays.reduce((total, curDelay) => (total += curDelay), 0)}毫秒`);
                      worker.postMessage({ canvas: offscreenCanvas, frames: frameBitmaps, delays }, [
                          offscreenCanvas,
                          ...frameBitmaps
                      ]);
                  });
              });
          }
      };
      const doConvert = (convertMeta) => {
          const { id, format, source, resolve, reject } = convertMeta;
          let frames;
          if (typeof convertMeta.onProgress === 'function')
              convertMeta.onProgress(0, 'zip');
          active.push(convertMeta);
          delete framesData[id];
          const imagePromises = source.data.map((blob) => {
              return new Promise((resolve) => {
                  const image = new Image();
                  image.onload = () => {
                      resolve(image);
                  };
                  image.src = URL.createObjectURL(blob);
              });
          });
          Promise.all(imagePromises)
              .then((imgEles) => {
              frames = imgEles;
              if (convertMeta.isAborted) {
                  logger.warn('Convert stop manually.' + id);
                  throw new CancelError();
              }
              return convertTo[format](frames, convertMeta);
          })
              .then(resolve, reject)
              .finally(() => {
              frames.forEach((frame) => URL.revokeObjectURL(frame.src));
              active.splice(active.indexOf(convertMeta), 1);
              if (queue.length)
                  doConvert(queue.shift());
          });
      };
      return {
          del: (taskIds) => {
              if (!taskIds.length)
                  return;
              logger.info('Converter del, active:', active.map((meta) => meta.id), 'queue:', queue.map((meta) => meta.id));
              isStop = true;
              active = active.filter((convertMeta) => {
                  if (taskIds.includes(convertMeta.id)) {
                      convertMeta.abort();
                  }
                  else {
                      return true;
                  }
              });
              queue = queue.filter((convertMeta) => !taskIds.includes(convertMeta.id));
              isStop = false;
              while (active.length < MAX_CONVERT && queue.length) {
                  doConvert(queue.shift());
              }
          },
          addFrame(taskId, data, delay, order) {
              if (!(taskId in framesData)) {
                  framesData[taskId] = {
                      id: taskId,
                      data: [],
                      delays: []
                  };
              }
              if (order === undefined) {
                  framesData[taskId]['data'].push(data);
                  framesData[taskId]['delays'].push(delay);
              }
              else {
                  framesData[taskId]['data'][order] = data;
                  framesData[taskId]['delays'][order] = delay;
              }
          },
          removeFrames(taskId) {
              if (taskId in framesData)
                  delete framesData[taskId];
          },
          framesCount(taskId) {
              return taskId in framesData ? framesData[taskId]['delays'].filter((delay) => delay !== undefined).length : 0;
          },
          convert(taskId, format, handler) {
              return new Promise((resolve, reject) => {
                  const meta = {
                      id: taskId,
                      format,
                      source: framesData[taskId],
                      isAborted: false,
                      onProgress: handler?.onProgress,
                      resolve,
                      reject,
                      abort() {
                          this.isAborted = true;
                      }
                  };
                  if ((format === 'gif' || format === 'png') && depsStatus[format].loaded !== LoadStatus.loaded) {
                      switch (depsStatus[format].loaded) {
                          case LoadStatus.unloaded:
                              cachedQueue[format].push(meta);
                              depsStatus[format].load().then(() => {
                                  logger.info(`添加${cachedQueue[format].length}个任务`);
                                  queue.push(...cachedQueue[format]);
                                  cachedQueue[format].length = 0;
                                  while (active.length < MAX_CONVERT && queue.length && !isStop) {
                                      doConvert(queue.shift());
                                  }
                              }, (reason) => {
                                  cachedQueue[format].forEach((meta) => {
                                      meta.reject(reason);
                                  });
                                  depsStatus[format].loaded = LoadStatus.unloaded;
                                  cachedQueue[format].length = 0;
                              });
                              break;
                          case LoadStatus.loading:
                              cachedQueue[format].push(meta);
                              break;
                          default:
                              throw new RangeError('Invalid deps status.');
                      }
                  }
                  else {
                      logger.info('Converter add', taskId);
                      queue.push(meta);
                      while (active.length < MAX_CONVERT && queue.length && !isStop) {
                          doConvert(queue.shift());
                      }
                  }
              });
          }
      };
  }
  const converter = createConverter();

  function createCompressor() {
      const zip = new JSZip__default["default"]();
      return {
          add(id, name, data) {
              zip.folder(id)?.file(name, data);
          },
          bundle(id) {
              const folder = zip.folder(id);
              if (!folder)
                  throw new TypeError('no such folder:' + id);
              return folder.generateAsync({ type: 'blob' });
          },
          remove(ids) {
              if (typeof ids === 'string') {
                  zip.remove(ids);
              }
              else {
                  const dirs = zip.filter((_, file) => file.dir).map((dir) => dir.name);
                  const dirsToDel = ids.filter((id) => dirs.some((dir) => dir.includes(id)));
                  dirsToDel.forEach((dir) => zip.remove(dir));
                  logger.info('Compressor: Remove', zip);
              }
          },
          fileCount(id) {
              let count = 0;
              zip.folder(id)?.forEach(() => count++);
              return count;
          },
          async unzip(data) {
              const id = Math.random().toString(36);
              let folder = zip.folder(id);
              if (!folder)
                  throw TypeError('Can not get new root folder');
              const filesPromises = [];
              folder = await folder.loadAsync(data);
              folder.forEach((_, file) => {
                  filesPromises.push(file.async('blob'));
              });
              const files = await Promise.all(filesPromises);
              zip.remove(id);
              return files;
          }
      };
  }
  const compressor = createCompressor();

  const updateDirHandleChannel = new BroadcastChannel('update_dir_channel');
  updateDirHandleChannel.onmessage = (evt) => {
      const data = evt.data;
      switch (data.kind) {
          case 1 :
              dirHandleStatus = 1 ;
              logger.info('正在选择目录');
              break;
          case 0 :
              logger.warn('取消更新dirHandle');
              if (dirHandle) {
                  dirHandleStatus = 2 ;
                  processCachedSave();
              }
              else {
                  dirHandleStatus = 0 ;
                  rejectCachedSave();
              }
              break;
          case 2 :
              dirHandleStatus = 2 ;
              dirHandle = data.handle;
              logger.info('更新dirHandle', dirHandle);
              processCachedSave();
              break;
          case 'request':
              if (dirHandle) {
                  updateDirHandleChannel.postMessage({
                      kind: 'response',
                      handle: dirHandle
                  });
                  logger.info('响应请求dirHandle');
              }
              break;
          case 'response':
              if (!dirHandle) {
                  if (dirHandleStatus === 0 )
                      dirHandleStatus = 2 ;
                  dirHandle = data.handle;
                  logger.info('首次获取dirHandle', dirHandle);
              }
              break;
          default:
              throw new Error('Invalid data kind.');
      }
  };
  updateDirHandleChannel.postMessage({ kind: 'request' });
  async function getDirHandleRecursive(dirs) {
      let handler = dirHandle;
      if (typeof dirs === 'string') {
          if (dirs.indexOf('/') === -1)
              return await handler.getDirectoryHandle(dirs, { create: true });
          dirs = dirs.split('/').filter((dir) => !!dir);
      }
      for await (const dir of dirs) {
          handler = await handler.getDirectoryHandle(dir, { create: true });
      }
      return handler;
  }
  const duplicateFilenameCached = {};
  async function getFilenameHandle(dirHandle, filename) {
      const conflictAction = config.get('fileSystemFilenameConflictAction');
      if (conflictAction === 'overwrite')
          return await dirHandle.getFileHandle(filename, { create: true });
      if (!(filename in duplicateFilenameCached)) {
          duplicateFilenameCached[filename] = [];
          try {
              await dirHandle.getFileHandle(filename);
              logger.warn('存在同名文件', filename);
          }
          catch (error) {
              return await dirHandle.getFileHandle(filename, { create: true });
          }
      }
      const extIndex = filename.lastIndexOf('.');
      const ext = filename.slice(extIndex + 1);
      const name = filename.slice(0, extIndex);
      if (conflictAction === 'prompt') {
          return await unsafeWindow.showSaveFilePicker({
              suggestedName: filename,
              types: [{ description: 'Image file', accept: { ['image/' + ext]: ['.' + ext] } }]
          });
      }
      else {
          for (let suffix = 1; suffix < 1000; suffix++) {
              const newName = `${name} (${suffix}).${ext}`;
              try {
                  await dirHandle.getFileHandle(newName);
              }
              catch (error) {
                  if (duplicateFilenameCached[filename].includes(newName)) {
                      continue;
                  }
                  else {
                      duplicateFilenameCached[filename].push(newName);
                  }
                  logger.info('使用文件名:', newName);
                  return await dirHandle.getFileHandle(newName, { create: true });
              }
          }
          throw new RangeError('Oops, you have too many duplicate files.');
      }
  }
  function clearFilenameCached(duplicateName, actualName) {
      if (!(duplicateName in duplicateFilenameCached))
          return;
      const usedNameArr = duplicateFilenameCached[duplicateName];
      logger.info('清理重名文件名', usedNameArr, actualName);
      if (usedNameArr.length === 0) {
          delete duplicateFilenameCached[duplicateName];
          return;
      }
      const index = usedNameArr.indexOf(actualName);
      if (index === -1)
          return;
      usedNameArr.splice(index, 1);
      if (usedNameArr.length === 0)
          delete duplicateFilenameCached[duplicateName];
  }
  async function updateDirHandle() {
      try {
          dirHandleStatus = 1 ;
          updateDirHandleChannel.postMessage({ kind: 1  });
          dirHandle = await unsafeWindow.showDirectoryPicker({ id: 'pdl', mode: 'readwrite' });
          logger.info('更新dirHandle', dirHandle);
          dirHandleStatus = 2 ;
          updateDirHandleChannel.postMessage({
              kind: 2 ,
              handle: dirHandle
          });
          processCachedSave();
          return true;
      }
      catch (error) {
          logger.warn(error);
          updateDirHandleChannel.postMessage({ kind: 0  });
          if (dirHandle) {
              dirHandleStatus = 2 ;
              processCachedSave();
          }
          else {
              dirHandleStatus = 0 ;
              rejectCachedSave();
          }
          return false;
      }
  }
  let dirHandleStatus = 0 ;
  let dirHandle;
  const cachedSaveProcess = [];
  async function saveWithFileSystemAccess(blob, downloadMeta) {
      try {
          if (downloadMeta.state === 0 )
              return;
          if (dirHandleStatus === 1 ) {
              cachedSaveProcess.push([blob, downloadMeta]);
              return;
          }
          if (dirHandleStatus === 0 ) {
              const isSuccess = await updateDirHandle();
              if (!isSuccess)
                  throw new TypeError('Failed to get dir handle.');
          }
          let currenDirHandle;
          let filename;
          const path = downloadMeta.source.path;
          const index = path.lastIndexOf('/');
          if (index === -1) {
              filename = path;
              currenDirHandle = dirHandle;
          }
          else {
              filename = path.slice(index + 1);
              currenDirHandle = await getDirHandleRecursive(path.slice(0, index));
          }
          const fileHandle = await getFilenameHandle(currenDirHandle, filename);
          const writableStream = await fileHandle.createWritable();
          await writableStream.write(blob);
          await writableStream.close();
          clearFilenameCached(filename, fileHandle.name);
          downloadMeta.resolve(downloadMeta.taskId);
          logger.info('Download complete:', downloadMeta.source.path);
      }
      catch (error) {
          downloadMeta.reject(error);
          logger.error(error);
      }
      downloadMeta.state = 2 ;
  }
  function processCachedSave() {
      cachedSaveProcess.forEach((args) => saveWithFileSystemAccess(...args));
      logger.info(`执行${cachedSaveProcess.length}个缓存任务`);
      cachedSaveProcess.length = 0;
  }
  function rejectCachedSave() {
      cachedSaveProcess.forEach(([, downloadMeta]) => downloadMeta.reject(new CancelError()));
      logger.info(`取消${cachedSaveProcess.length}个缓存任务`);
      cachedSaveProcess.length = 0;
  }
  function getCurrentDirName() {
      return dirHandle?.name ?? '';
  }
  function isShouldGetDirHandle() {
      return isUseFileSystemAccess() && dirHandleStatus === 0 ;
  }
  function isUseFileSystemAccess() {
      return env.isFileSystemAccessAvaliable() && config.get('useFileSystemAccess');
  }

  const _saveWithoutSubpath = (blob, downloadMeta) => {
      const dlEle = document.createElement('a');
      dlEle.href = URL.createObjectURL(blob);
      dlEle.download = downloadMeta.source.path;
      dlEle.click();
      URL.revokeObjectURL(dlEle.href);
      downloadMeta.state = 2 ;
      downloadMeta.resolve(downloadMeta.taskId);
  };
  const _saveWithSubpath = (blob, downloadMeta) => {
      const imgUrl = URL.createObjectURL(blob);
      const request = {
          url: imgUrl,
          name: downloadMeta.source.path,
          onerror: (error) => {
              if (downloadMeta.state !== 0 ) {
                  downloadMeta.reject(new Error(`Download error when saving ${downloadMeta.source.path} because ${error.error} ${error.details ?? ''} `));
              }
              URL.revokeObjectURL(imgUrl);
          },
          onload: () => {
              if (typeof downloadMeta.onLoad === 'function')
                  downloadMeta.onLoad();
              URL.revokeObjectURL(imgUrl);
              downloadMeta.state = 2 ;
              downloadMeta.resolve(downloadMeta.taskId);
              logger.info('Download complete:', downloadMeta.source.path);
          }
      };
      downloadMeta.abort = GM_download(request).abort;
  };
  function createDownloader() {
      const MAX_DOWNLOAD = 5;
      const MAX_RETRY = 3;
      const INTERVAL = 500;
      const TIMEOUT = 30000;
      let isStop = false;
      let queue = [];
      let active = [];
      let save;
      if (env.isBlobDlAvaliable() && env.isSupportSubpath()) {
          save = _saveWithSubpath;
      }
      else {
          logger.warn('Download function is not fully supported:', GM_info.scriptHandler, GM_info.version);
          save = _saveWithoutSubpath;
      }
      const download = (downloadMeta) => {
          const { taskId, source } = downloadMeta;
          logger.info('Start download:', source.src);
          active.push(downloadMeta);
          let fileSaveFn = save;
          const isUseFSA = isUseFileSystemAccess();
          if (isUseFSA) {
              fileSaveFn = saveWithFileSystemAccess;
          }
          let abortObj;
          const { ontimeout, onerror } = errorHandlerFactory(downloadMeta);
          if ((!env.isBlobDlAvaliable() || (env.isViolentmonkey() && !isUseFSA)) && !('kind' in source)) {
              abortObj = GM_download({
                  url: source.src,
                  name: source.path,
                  headers: {
                      referer: 'https://www.pixiv.net'
                  },
                  ontimeout,
                  onerror,
                  onload: async () => {
                      logger.info('Download complete:', taskId, source.path);
                      if (typeof downloadMeta.onLoad === 'function')
                          downloadMeta.onLoad();
                      downloadMeta.resolve(taskId);
                      await sleep(INTERVAL);
                      active.splice(active.indexOf(downloadMeta), 1);
                      if (queue.length && !isStop)
                          download(queue.shift());
                  }
              });
          }
          else {
              abortObj = GM_xmlhttpRequest({
                  url: source.src,
                  timeout: TIMEOUT,
                  method: 'GET',
                  headers: {
                      referer: 'https://www.pixiv.net'
                  },
                  responseType: 'blob',
                  ontimeout,
                  onerror,
                  onprogress: (e) => {
                      if (e.lengthComputable && typeof downloadMeta.onProgress === 'function') {
                          downloadMeta.onProgress(e.loaded / e.total);
                      }
                  },
                  onload: async (e) => {
                      logger.info('Xhr complete:', source.src);
                      if (downloadMeta.state === 0 )
                          return logger.warn('Download was canceled.', taskId, source.path);
                      if (!('kind' in source)) {
                          fileSaveFn(e.response, downloadMeta);
                      }
                      else if (source.kind === 'convert') {
                          if (typeof downloadMeta.onLoad === 'function')
                              downloadMeta.onLoad();
                          converter.addFrame(taskId, e.response, source.ugoiraMeta.frames[source.order]['delay'], source.order);
                          if (converter.framesCount(taskId) === source.pageCount) {
                              converter.convert(taskId, source.format, { onProgress: downloadMeta.onProgress }).then((blob) => {
                                  fileSaveFn(blob, downloadMeta);
                              }, downloadMeta.reject);
                          }
                          else {
                              downloadMeta.resolve(taskId);
                          }
                      }
                      else if (source.kind === 'bundle') {
                          compressor.add(taskId, source.filename, e.response);
                          if (compressor.fileCount(taskId) === source.pageCount) {
                              compressor.bundle(taskId).then((blob) => {
                                  fileSaveFn(blob, downloadMeta);
                                  compressor.remove(taskId);
                              });
                          }
                          else {
                              downloadMeta.resolve(taskId);
                              if (typeof downloadMeta.onLoad === 'function')
                                  downloadMeta.onLoad();
                          }
                      }
                      await sleep(INTERVAL);
                      active.splice(active.indexOf(downloadMeta), 1);
                      if (queue.length && !isStop)
                          download(queue.shift());
                  }
              });
          }
          downloadMeta.abort = abortObj.abort;
      };
      function errorHandlerFactory(downloadMeta) {
          return {
              ontimeout(error) {
                  const { taskId, source, state } = downloadMeta;
                  if (state === 0 )
                      return;
                  downloadMeta.retry++;
                  logger.error('Download timeout', downloadMeta.retry, ':', source.src, error);
                  if (downloadMeta.retry > MAX_RETRY) {
                      downloadMeta.reject(new Error(`Download failed: ${taskId} | ${source.src}`));
                      active.splice(active.indexOf(downloadMeta), 1);
                      if (queue.length && !isStop)
                          download(queue.shift());
                  }
                  else {
                      logger.info('Download retry:', downloadMeta.retry, source.src);
                      download(downloadMeta);
                  }
              },
              onerror(error) {
                  const { taskId, source, state } = downloadMeta;
                  if (state === 0 )
                      return;
                  logger.error('Download ' + taskId + ' error', error.error ? ' with reason: ' + error.error : '', 'details' in error ? error.details : error);
                  if ('status' in error && error.status === 429) {
                      downloadMeta.reject(new RequestError('Too many request', error));
                      active.splice(active.indexOf(downloadMeta), 1);
                      return;
                  }
                  downloadMeta.retry++;
                  if (downloadMeta.retry > MAX_RETRY) {
                      downloadMeta.reject(new Error(`Download failed: ${taskId} | ${source.src}`));
                      active.splice(active.indexOf(downloadMeta), 1);
                      if (queue.length && !isStop)
                          download(queue.shift());
                  }
                  else {
                      logger.info('Download retry:', downloadMeta.retry, source.src);
                      download(downloadMeta);
                  }
              }
          };
      }
      return {
          add(metas, handler = {}) {
              logger.info('Downloader add:', metas);
              if (metas.length < 1)
                  return Promise.resolve('');
              const promises = [];
              metas.forEach((source) => {
                  promises.push(new Promise((resolve, reject) => {
                      const downloadMeta = {
                          taskId: source.taskId,
                          source,
                          state: 1 ,
                          retry: 0,
                          onProgress: handler.onProgress,
                          onLoad: handler.onLoad,
                          resolve,
                          reject
                      };
                      queue.push(downloadMeta);
                  }));
              });
              while (active.length < MAX_DOWNLOAD && queue.length && !isStop) {
                  download(queue.shift());
              }
              return Promise.all(promises).then(([taskId]) => taskId, (err) => {
                  metas.forEach((source) => {
                      if ('kind' in source) {
                          if (source.kind === 'bundle') {
                              compressor.remove(source.taskId);
                          }
                          else {
                              converter.removeFrames(source.taskId);
                          }
                      }
                  });
                  throw err;
              });
          },
          del(taskIds) {
              if (!taskIds.length)
                  return;
              isStop = true;
              logger.info('Downloader delete. active:', active.length, 'queue', queue.length);
              active = active.filter((downloadMeta) => {
                  if (taskIds.includes(downloadMeta.taskId) && downloadMeta.state !== 2 ) {
                      downloadMeta.abort?.();
                      downloadMeta.state = 0 ;
                      downloadMeta.reject(new CancelError());
                      logger.warn('Download abort manually.', downloadMeta.source.path);
                  }
                  else {
                      return true;
                  }
              });
              converter.del(taskIds);
              compressor.remove(taskIds);
              queue = queue.filter((downloadMeta) => !taskIds.includes(downloadMeta.taskId));
              isStop = false;
              while (active.length < MAX_DOWNLOAD && queue.length) {
                  download(queue.shift());
              }
          }
      };
  }
  const downloader = createDownloader();

  class HistoryDb extends Dexie__default["default"] {
      history;
      constructor() {
          super('PdlHistory');
          this.version(1).stores({
              history: 'pid, user, title, *tags'
          });
      }
  }
  function createHistoryDb() {
      const db = new HistoryDb();
      let record;
      function migrateFromLocalStorage() {
          if (localStorage.pixivDownloader) {
              const datas = JSON.parse(localStorage.pixivDownloader).map((pid) => ({
                  pid: Number(pid)
              }));
              const tempKeys = Object.keys(localStorage).filter((key) => /(?<=^pdlTemp-)\d+$/.test(key));
              if (tempKeys.length) {
                  tempKeys.forEach((key) => {
                      const [id] = /(?<=^pdlTemp-)\d+$/.exec(key);
                      datas.push({ pid: Number(id) });
                      localStorage.removeItem(key);
                  });
              }
              db.history.bulkPut(datas).then(() => {
                  localStorage.removeItem('pixivDownloader');
              });
          }
      }
      migrateFromLocalStorage();
      logger.time('loadDb');
      db.history.toArray().then((datas) => {
          record = new Set(datas.map((data) => data.pid));
          logger.timeEnd('loadDb');
      });
      return {
          async add(historyData) {
              if (!(await this.has(historyData.pid))) {
                  db.history.put(historyData);
                  record.add(historyData.pid);
              }
          },
          bulkAdd(historyDatas) {
              const result = db.history.bulkPut(historyDatas);
              historyDatas.forEach((data) => {
                  record.add(data.pid);
              });
              return result;
          },
          async has(pid) {
              if (typeof pid === 'string')
                  pid = Number(pid);
              if (record) {
                  return record.has(pid);
              }
              else {
                  return !!(await db.history.get(pid));
              }
          },
          getAll() {
              return db.history.toArray();
          },
          clear() {
              record && (record = new Set());
              return db.history.clear();
          }
      };
  }
  const historyDb = createHistoryDb();

  const isNeedBundle = (type) => {
      return ((type === IllustType.manga && config.get('bundleManga')) ||
          (type === IllustType.illusts && config.get('bundleIllusts')) ||
          (type === IllustType.ugoira && config.get('ugoiraFormat') === 'zip'));
  };
  const getFilePath = ({ user, userId, title, tagStr, illustId, createDate, page, ext }, option = { needBundle: false, needConvert: false }) => {
      let pathPattern;
      const folderPattern = config.get('folderPattern');
      const filenamePattern = config.get('filenamePattern');
      const isUseFSA = isUseFileSystemAccess();
      const shouldAddFolder = !!folderPattern &&
          !option.needBundle &&
          ((env.isSupportSubpath() && (!option.needConvert || env.isBlobDlAvaliable())) || isUseFSA);
      if (shouldAddFolder) {
          pathPattern = folderPattern + '/' + filenamePattern;
      }
      else {
          pathPattern = filenamePattern;
      }
      if (option.needBundle && !filenamePattern.includes('{page}')) {
          pathPattern += '_{page}';
      }
      function replaceDate(match, p1) {
          const format = p1 || 'YYYY-MM-DD';
          return dayjs__default["default"](createDate).format(format);
      }
      return (pathPattern
          .replaceAll(/\{date\((.*?)\)\}|\{date\}/g, replaceDate)
          .replaceAll('{artist}', user)
          .replaceAll('{artistID}', userId)
          .replaceAll('{title}', title)
          .replaceAll('{tags}', tagStr)
          .replaceAll('{page}', String(page))
          .replaceAll('{id}', illustId) + ext);
  };
  const makeTagsStr = (prev, cur, index, tagsArr) => {
      const tag = config.get('tagLang') === 'ja' ? cur.tag : cur.translation?.['en'] || cur.tag;
      if (index < tagsArr.length - 1) {
          return prev + tag + '_';
      }
      else {
          return prev + tag;
      }
  };
  function isValidIllustType(illustType, option) {
      switch (illustType) {
          case IllustType.illusts:
              if (option.filterIllusts)
                  return true;
              break;
          case IllustType.manga:
              if (option.filterManga)
                  return true;
              break;
          case IllustType.ugoira:
              if (option.filterUgoira)
                  return true;
              break;
          default:
              throw new Error('Invalid filter type');
      }
      return false;
  }
  async function filterWorks(works, option) {
      const obj = {
          unavaliable: [],
          avaliable: [],
          invalid: []
      };
      for (const work of works) {
          if (!work.isBookmarkable) {
              obj.unavaliable.push(work.id);
          }
          else if (option.filterExcludeDownloaded && (await historyDb.has(work.id))) {
              obj.invalid.push(work.id);
          }
          else if (!isValidIllustType(work.illustType, option)) {
              obj.invalid.push(work.id);
          }
          else {
              obj.avaliable.push(work.id);
          }
      }
      return obj;
  }
  async function getFollowLatestGenerator(filterOption, mode, page) {
      const MAX_PAGE = 34;
      const MAX_ILLUSTS_PER_PAGE = 60;
      let lastId;
      let total;
      let data;
      let cache;
      function findLastId(ids) {
          return Math.min(...ids.map((id) => Number(id)));
      }
      if (page === undefined) {
          data = await api.getFollowLatestWorks(1, mode);
          const ids = data.page.ids;
          total = ids.length;
          lastId = findLastId(ids);
          if (total === MAX_ILLUSTS_PER_PAGE) {
              const secondPageData = await api.getFollowLatestWorks(2, mode);
              const secondIds = secondPageData.page.ids;
              const secondLastId = findLastId(secondIds);
              if (secondLastId < lastId) {
                  lastId = secondLastId;
                  cache = secondPageData;
                  total += secondIds.length;
              }
          }
      }
      else {
          data = await api.getFollowLatestWorks(page, mode);
          total = data.page.ids.length;
      }
      async function* generateIds() {
          yield await filterWorks(data.thumbnails.illust, filterOption);
          if (page === undefined) {
              if (total === MAX_ILLUSTS_PER_PAGE)
                  return;
              if (total < MAX_ILLUSTS_PER_PAGE * 2) {
                  yield await filterWorks(cache.thumbnails.illust, filterOption);
                  return;
              }
              let currentPage = 3;
              while (currentPage <= MAX_PAGE) {
                  const data = await api.getFollowLatestWorks(currentPage, mode);
                  const ids = data.page.ids;
                  const pageLastId = findLastId(ids);
                  if (pageLastId >= lastId) {
                      logger.info('getFollowLatestGenerator: got duplicate works');
                      yield await filterWorks(cache.thumbnails.illust, filterOption);
                      break;
                  }
                  lastId = pageLastId;
                  total += ids.length;
                  yield { ...(await filterWorks(cache.thumbnails.illust, filterOption)), total };
                  cache = data;
                  currentPage++;
                  await sleep(3000);
              }
          }
      }
      return {
          total,
          generator: generateIds()
      };
  }
  async function getChunksGenerator(userId, category, tag, rest, filterOption) {
      const OFFSET = 48;
      let requestUrl;
      if (category === 'bookmarks') {
          requestUrl = `https://www.pixiv.net/ajax/user/${userId}/illusts/bookmarks?tag=${tag}&offset=0&limit=${OFFSET}&rest=${rest}&lang=ja`;
      }
      else {
          requestUrl = `https://www.pixiv.net/ajax/user/${userId}/${category}/tag?tag=${tag}&offset=0&limit=${OFFSET}&lang=ja`;
      }
      let head = 0;
      const firstPageData = await api.getJson(requestUrl);
      const total = firstPageData.total;
      async function* generateIds() {
          yield await filterWorks(firstPageData.works, filterOption);
          head += OFFSET;
          while (head < total) {
              const data = await api.getJson(requestUrl.replace('offset=0', 'offset=' + head));
              head += OFFSET;
              await sleep(3000);
              yield await filterWorks(data.works, filterOption);
          }
      }
      return {
          total,
          generator: generateIds()
      };
  }
  async function getAllWorksGenerator(userId, filterOption) {
      const profile = await api.getUserAllProfile(userId);
      let illustIds = [];
      let mangaIds = [];
      if ((filterOption.filterIllusts || filterOption.filterUgoira) && typeof profile.illusts === 'object') {
          illustIds.push(...Object.keys(profile.illusts).reverse());
      }
      if (filterOption.filterManga && typeof profile.manga === 'object') {
          mangaIds.push(...Object.keys(profile.manga).reverse());
      }
      if (filterOption.filterExcludeDownloaded) {
          const filteredIllustIds = [];
          for (const id of illustIds) {
              const isDownloaded = await historyDb.has(id);
              !isDownloaded && filteredIllustIds.push(id);
          }
          illustIds = filteredIllustIds;
          const filteredMangaIds = [];
          for (const id of mangaIds) {
              const isDownloaded = await historyDb.has(id);
              !isDownloaded && filteredMangaIds.push(id);
          }
          mangaIds = filteredMangaIds;
      }
      async function* generateIds() {
          const OFFSET = 48;
          const baseUrl = 'https://www.pixiv.net/ajax/user/' + userId + '/profile/illusts';
          let workCategory = 'illust';
          while (illustIds.length > 0) {
              let searchStr = '?';
              const chunk = illustIds.splice(0, OFFSET);
              searchStr +=
                  chunk.map((id) => 'ids[]=' + id).join('&') + `&work_category=${workCategory}&is_first_page=0&lang=ja`;
              const data = await api.getJson(baseUrl + searchStr);
              await sleep(3000);
              yield await filterWorks(Object.values(data.works).reverse(), filterOption);
          }
          workCategory = 'manga';
          while (mangaIds.length > 0) {
              let searchStr = '?';
              const chunk = mangaIds.splice(0, OFFSET);
              searchStr +=
                  chunk.map((id) => 'ids[]=' + id).join('&') + `&work_category=${workCategory}&is_first_page=0&lang=ja`;
              const data = await api.getJson(baseUrl + searchStr);
              await sleep(3000);
              yield await filterWorks(Object.values(data.works).reverse(), filterOption);
          }
      }
      return {
          total: illustIds.length + mangaIds.length,
          generator: generateIds()
      };
  }
  async function getArtworkData(illustId) {
      const htmlText = await api.getArtworkHtml(illustId);
      const preloadDataText = htmlText.match(regexp.preloadData);
      if (!preloadDataText)
          throw new Error('Fail to parse preload data.');
      const preloadData = JSON.parse(preloadDataText[1]);
      const illustData = preloadData.illust[illustId];
      const globalDataText = htmlText.match(regexp.globalData);
      if (!globalDataText)
          throw new Error('Fail to parse global data.');
      const globalData = JSON.parse(globalDataText[1]);
      let ugoiraMeta;
      if (illustData.illustType === IllustType.ugoira) {
          ugoiraMeta = await api.getUgoiraMeta(illustId);
      }
      return {
          illustData,
          globalData,
          ugoiraMeta
      };
  }
  async function getAjaxArtworkData(illustId) {
      const illustData = await api.getArtworkDetail(illustId);
      let ugoiraMeta;
      if (illustData.illustType === IllustType.ugoira) {
          ugoiraMeta = await api.getUgoiraMeta(illustId);
      }
      return {
          illustData,
          ugoiraMeta
      };
  }
  function getDownloadSource(artworkData, seletedPage) {
      const { illustData, ugoiraMeta } = artworkData;
      const { illustType, userName, userId, illustTitle, illustId, tags, pageCount, createDate } = illustData;
      const metas = [];
      const taskId = illustId + '_' + Math.random().toString(36).slice(2);
      const firstImgSrc = illustData.urls.original;
      const extendName = firstImgSrc.slice(-4);
      const pathInfo = {
          user: replaceInvalidChar(unescapeHtml(userName)) || 'userId-' + userId,
          title: replaceInvalidChar(unescapeHtml(illustTitle)) || 'illustId-' + illustId,
          tagStr: replaceInvalidChar(unescapeHtml(tags.tags.reduce(makeTagsStr, ''))),
          illustId,
          userId,
          createDate,
          ext: extendName,
          page: 0
      };
      if (illustType === IllustType.illusts || illustType === IllustType.manga) {
          const srcPrefix = firstImgSrc.slice(0, firstImgSrc.indexOf('_') + 2);
          if (pageCount > 1 && seletedPage === undefined) {
              if (isNeedBundle(illustType)) {
                  const path = getFilePath({ ...pathInfo, ext: '.zip', page: pageCount });
                  for (let i = 0; i < pageCount; i++) {
                      pathInfo.page = i;
                      metas.push({
                          kind: 'bundle',
                          taskId,
                          path,
                          src: srcPrefix + i + extendName,
                          filename: getFilePath(pathInfo, { needBundle: true }),
                          pageCount
                      });
                  }
              }
              else {
                  for (let i = 0; i < pageCount; i++) {
                      pathInfo.page = i;
                      metas.push({
                          taskId,
                          path: getFilePath(pathInfo),
                          src: srcPrefix + i + extendName
                      });
                  }
              }
          }
          else {
              let src = firstImgSrc;
              if (seletedPage !== undefined) {
                  src = srcPrefix + seletedPage + extendName;
                  pathInfo.page = seletedPage;
              }
              metas.push({
                  taskId,
                  path: getFilePath(pathInfo),
                  src
              });
          }
      }
      else if (illustType === IllustType.ugoira && ugoiraMeta) {
          const ugoiraPageCount = ugoiraMeta.frames.length;
          const ugoiraFormat = config.get('ugoiraFormat');
          if (ugoiraFormat !== 'zip') {
              pathInfo.ext = '.' + ugoiraFormat;
              const path = getFilePath(pathInfo, { needConvert: true });
              for (let i = 0; i < ugoiraPageCount; i++) {
                  pathInfo.page = i;
                  metas.push({
                      kind: 'convert',
                      format: ugoiraFormat,
                      ugoiraMeta,
                      taskId,
                      path,
                      src: firstImgSrc.replace('ugoira0', 'ugoira' + i),
                      order: i,
                      pageCount: ugoiraPageCount
                  });
              }
          }
          else {
              const path = getFilePath({ ...pathInfo, ext: '.zip', page: ugoiraPageCount });
              for (let i = 0; i < ugoiraPageCount; i++) {
                  pathInfo.page = i;
                  metas.push({
                      kind: 'bundle',
                      taskId,
                      path,
                      src: firstImgSrc.replace('ugoira0', 'ugoira' + i),
                      filename: getFilePath(pathInfo, { needBundle: true }),
                      pageCount: ugoiraPageCount
                  });
              }
          }
      }
      return metas;
  }
  const parser = {
      getChunksGenerator,
      getAllWorksGenerator,
      getFollowLatestGenerator,
      getArtworkData,
      getDownloadSource,
      getAjaxArtworkData
  };

  function handleDownload(pdlBtn, illustId) {
      if (isShouldGetDirHandle())
          updateDirHandle();
      let pageCount;
      let pageComplete = 0;
      let shouldDownloadPage;
      let downloading = true;
      let historyData;
      const pageAttr = pdlBtn.getAttribute('should-download');
      if (pageAttr) {
          shouldDownloadPage = Number(pageAttr);
      }
      const onProgress = (progress = 0, type = null) => {
          if ((pageCount > 1 && !type) || !downloading)
              return;
          progress = Math.floor(progress * 100);
          switch (type) {
              case null:
                  pdlBtn.style.setProperty('--pdl-progress', progress + '%');
              case 'gif':
              case 'webm':
              case 'webp':
                  pdlBtn.textContent = String(progress);
                  break;
              case 'zip':
                  pdlBtn.textContent = '';
                  break;
          }
      };
      const onLoad = function () {
          if (pageCount < 2 || !downloading)
              return;
          const progress = Math.floor((++pageComplete / pageCount) * 100);
          pdlBtn.textContent = String(progress);
          pdlBtn.style.setProperty('--pdl-progress', progress + '%');
      };
      pdlBtn.classList.add('pdl-progress');
      parser
          .getArtworkData(illustId)
          .then((artworkData) => {
          const { illustData, globalData } = artworkData;
          const { userName, title, illustId, tags, bookmarkData } = illustData;
          if (!bookmarkData) {
              const { token } = globalData;
              const tagsArr = tags.tags.map((item) => item.tag);
              addBookmark(pdlBtn, illustId, token, tagsArr);
          }
          historyData = { pid: Number(illustId), user: userName, title: title, tags: tags.tags.map((data) => data.tag) };
          return parser.getDownloadSource(artworkData, shouldDownloadPage);
      })
          .then((sources) => {
          pageCount = sources.length;
          return downloader.add(sources, { onLoad, onProgress });
      })
          .then(() => {
          historyDb.add(historyData);
          pdlBtn.classList.remove('pdl-error');
          pdlBtn.classList.add('pdl-complete');
      })
          .catch((err) => {
          if (err)
              logger.error(err);
          pdlBtn.classList.remove('pdl-complete');
          pdlBtn.classList.add('pdl-error');
      })
          .finally(() => {
          downloading = false;
          pdlBtn.innerHTML = '';
          pdlBtn.style.removeProperty('--pdl-progress');
          pdlBtn.classList.remove('pdl-progress');
      });
  }

  function createPdlBtn(attributes, textContent = '', { addEvent } = { addEvent: true }) {
      const ele = document.createElement('button');
      ele.textContent = textContent;
      if (!attributes)
          return ele;
      const { attrs, classList } = attributes;
      if (classList && classList.length > 0) {
          for (const cla of classList) {
              ele.classList.add(cla);
          }
      }
      if (attrs) {
          for (const key in attrs) {
              ele.setAttribute(key, attrs[key]);
          }
      }
      if (addEvent) {
          ele.addEventListener('click', (evt) => {
              evt.preventDefault();
              evt.stopPropagation();
              const ele = evt.currentTarget;
              if (!ele.classList.contains('pdl-progress')) {
                  handleDownload(ele, ele.getAttribute('pdl-id'));
              }
          });
      }
      return ele;
  }

  function createThumbnailsBtn(nodes) {
      let isSelfBookmark = false;
      const inBookmarkPage = regexp.bookmarkPage.exec(location.pathname);
      inBookmarkPage && inBookmarkPage[1] === getSelfId() && (isSelfBookmark = true);
      nodes.forEach((e) => {
          let illustId;
          if ((e.childElementCount !== 0 ||
              e.className.includes('_history-item') ||
              e.className.includes('_history-related-item')) &&
              !e.querySelector('.pdl-btn-sub') &&
              (illustId = getIllustId(e))) {
              const attrs = {
                  attrs: { 'pdl-id': illustId },
                  classList: ['pdl-btn', 'pdl-btn-sub']
              };
              if (isSelfBookmark)
                  attrs.classList.push('self-bookmark');
              if (e.className.includes('_history-related-item'))
                  e.style.position = 'relative';
              const el = createPdlBtn(attrs);
              historyDb.has(illustId).then((downloaded) => {
                  downloaded && el.classList.add('pdl-complete');
              });
              e.appendChild(el);
          }
      });
  }

  function fixPixivPreviewer(nodes) {
      const isPpSearchPage = regexp.searchPage.test(location.pathname);
      if (!isPpSearchPage)
          return;
      nodes.forEach((node) => {
          const pdlEle = node.querySelector('.pdl-btn');
          if (!pdlEle)
              return false;
          pdlEle.remove();
      });
  }

  const langZh = {
      button: {
          download_stop: '停止',
          download_works: '作品',
          download_bookmarks: '收藏',
          download_bookmarks_public: '公开',
          download_bookmarks_private: '不公开',
          download_all_one_page: '全部(单页)',
          download_all: '全部(批量)',
          download_r18_one_page: 'R-18(单页)',
          download_r18: 'R-18(批量)',
          fsa_change_dir: '更改',
          history_import: '导入记录(替换)',
          history_import_json: '导入记录(json)',
          history_import_txt: '导入旧记录(txt)',
          history_merge: '导入记录(合并)',
          history_export: '导出记录',
          history_clear: '清除记录'
      },
      checkbox: {
          filter_exclude_downloaded: '排除已下载图片',
          filter_illusts: '插画',
          filter_manga: '漫画',
          filter_ugoira: '动图'
      },
      radio: {
          filename_conflict_option_uniquify: '重命名',
          filename_conflict_option_overwrite: '覆盖',
          filename_conflict_option_prompt: '提示'
      },
      text: {
          feedback: '有问题or想建议?这里反馈',
          gm_menu: '设置',
          tab_title_filename: '文件名',
          tab_title_ugoira: '动图',
          tab_title_history: '历史记录',
          tab_title_button: '按钮',
          tab_title_others: '其它',
          tab_title_feedback: '反馈 / 赞赏',
          label_folder: '文件夹名:',
          label_filename: '文件名:',
          label_filename_conflict: '文件名重复时:',
          label_tag_lang: '标签语言:',
          label_fsa: '使用FileSystemAccess API',
          label_ugoira_format: '动图格式:',
          label_button_horizon: '水平:',
          label_button_vertical: '垂直:',
          title_button_preview: '预览图',
          title_button_preview_self_bookmark: '预览图(我的收藏)',
          placeholder_folder_subfolder_unused: '我不想保存到子文件夹',
          placeholder_folder_vm: 'Violentmonkey不支持',
          placeholder_folder_need_api: '需要Browser Api',
          placeholder_filename_requried: '你的名字?',
          placeholder_fsa_folder: '根文件夹名',
          tips_filename_pattern: '{artist}:作者, {artistID}:作者ID, {title}:作品标题, {id}:作品ID, {page}:页码, {tags}:作品标签,{date} / {date(占位符)}: 创建时间',
          tips_subfolder_unused: '如果不想保存到画师目录,文件夹名留空即可。',
          tips_tag_translation: '请注意:标签翻译不一定是你选择的语言,部分<a href="https://crowdin.com/project/pixiv-tags" target="_blank">无对应语言翻译的标签</a>仍可能是其他语言。',
          option_bundle_illusts: '将多页插图打包为.zip压缩包',
          option_bundle_manga: '将多页漫画作品打包为.zip压缩包',
          option_add_bookmark: '下载单个作品时收藏作品',
          option_add_bookmark_with_tags: '收藏时添加作品标签',
          option_add_bookmark_private_r18: '将R-18作品收藏到不公开类别',
          option_show_popup_button: '显示设置按钮',
          confirm_clear_history: '真的要清除历史记录吗?'
      }
  };
  const langEn = {
      button: {
          download_stop: 'Stop',
          download_works: 'Works',
          download_bookmarks: 'Bookmarks',
          download_bookmarks_public: 'Public',
          download_bookmarks_private: 'Private',
          download_all_one_page: 'All (one page)',
          download_all: 'All',
          download_r18_one_page: 'R-18 (one page)',
          download_r18: 'R-18',
          fsa_change_dir: 'Change',
          history_import: 'Import (Replace)',
          history_import_json: 'Import (json)',
          history_import_txt: 'Import (txt)',
          history_merge: 'Import (Merge)',
          history_export: 'Export',
          history_clear: 'Clear'
      },
      checkbox: {
          filter_exclude_downloaded: 'Exclude downloaded',
          filter_illusts: 'Illustrations',
          filter_manga: 'Manga',
          filter_ugoira: 'Ugoira'
      },
      radio: {
          filename_conflict_option_uniquify: 'Uniquify',
          filename_conflict_option_overwrite: 'Overwrite',
          filename_conflict_option_prompt: 'Prompt'
      },
      text: {
          feedback: 'Feedback',
          gm_menu: 'Setting',
          tab_title_filename: 'Filename',
          tab_title_ugoira: 'Ugoira',
          tab_title_history: 'History',
          tab_title_button: 'Button',
          tab_title_others: 'Others',
          tab_title_feedback: 'Feedback',
          label_folder: 'Folder:',
          label_filename: 'Filename:',
          label_filename_conflict: 'Conflict Action:',
          label_tag_lang: 'Tags language:',
          label_fsa: 'FileSystemAccess API',
          label_ugoira_format: 'Ugoira Format:',
          label_button_horizon: 'X:',
          label_button_vertical: 'Y:',
          title_button_preview: 'Thumbnail',
          title_button_preview_self_bookmark: 'Thumbnail(My bookmarks)',
          placeholder_folder_subfolder_unused: "I don't need subfolder",
          placeholder_folder_vm: "VM doesn't support",
          placeholder_folder_need_api: 'Need Browser Api',
          placeholder_filename_requried: 'Your Name?',
          placeholder_fsa_folder: 'Root directory',
          tips_filename_pattern: '{artist}, {artistID}, {title}, {id}, {page}, {tags}, {date} / {date(format)}',
          tips_subfolder_unused: "If you don't need a subfolder, just leave the folder name blank.",
          tips_tag_translation: '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.',
          option_bundle_illusts: 'Pack multi-page illustrations into a .zip archive',
          option_bundle_manga: 'Pack manga into a .zip archive',
          option_add_bookmark: 'Bookmark work when downloading a single work',
          option_add_bookmark_with_tags: 'Add works tags',
          option_add_bookmark_private_r18: 'Bookmark R-18 works to private category',
          option_show_popup_button: 'Show setting button',
          confirm_clear_history: 'Do you really want to clear history?'
      }
  };
  const messages = {
      'zh-cn': langZh,
      'zh-tw': langZh,
      zh: langZh,
      en: langEn
  };
  const curLang = document.documentElement.getAttribute('lang')?.toLowerCase() || 'en';
  const defaultLang = 'en';
  function t(key) {
      const lang = (curLang in messages ? curLang : defaultLang);
      const paths = key.split('.');
      let last = messages[lang];
      for (let i = 0; i < paths.length; i++) {
          const value = last[paths[i]];
          if (value === undefined || value === null)
              return null;
          last = value;
      }
      return last;
  }

  function getFilterOption() {
      return {
          filterExcludeDownloaded: config.get('filterExcludeDownloaded'),
          filterIllusts: config.get('filterIllusts'),
          filterManga: config.get('filterManga'),
          filterUgoira: config.get('filterUgoira')
      };
  }
  function downloadAndRetry(chunksGenerators) {
      useDownloadBar(chunksGenerators).then((failed) => {
          if (failed instanceof Array && failed.length) {
              const gen = async function* () {
                  yield {
                      avaliable: failed,
                      unavaliable: [],
                      invalid: []
                  };
              };
              console.log('[Pixiv Downloader] Retry...');
              useDownloadBar({ total: failed.length, generator: gen() });
          }
      });
  }
  function downloadWorks(evt) {
      evt.preventDefault();
      evt.stopPropagation();
      if (isDownloading)
          return;
      const btn = evt.target;
      const userId = btn.getAttribute('pdl-userid');
      const filterOption = getFilterOption();
      if (isShouldGetDirHandle())
          updateDirHandle();
      const ids = parser.getAllWorksGenerator(userId, filterOption);
      downloadAndRetry(ids);
  }
  async function downloadBookmarksOrTags(evt) {
      evt.preventDefault();
      evt.stopPropagation();
      if (isDownloading)
          return;
      const btn = evt.target;
      const userId = btn.getAttribute('pdl-userid');
      const category = btn.getAttribute('category');
      const tag = btn.getAttribute('tag') || '';
      const rest = (btn.getAttribute('rest') || 'show');
      if (isShouldGetDirHandle())
          updateDirHandle();
      const filterOption = getFilterOption();
      let idsGenerators;
      if (rest === 'all') {
          const idsShowPromise = parser.getChunksGenerator(userId, 'bookmarks', '', 'show', filterOption);
          const idsHidePromise = parser.getChunksGenerator(userId, 'bookmarks', '', 'hide', filterOption);
          idsGenerators = [idsShowPromise, idsHidePromise];
      }
      else {
          idsGenerators = parser.getChunksGenerator(userId, category, tag, rest, filterOption);
      }
      downloadAndRetry(idsGenerators);
  }
  function downloadFollowLatest(evt) {
      evt.preventDefault();
      evt.stopPropagation();
      if (isDownloading)
          return;
      const btn = evt.target;
      const mode = location.pathname.includes('r18') ? 'r18' : 'all';
      const filterOption = getFilterOption();
      let idsGenerators;
      if (btn.classList.contains('pdl-dl-all')) {
          idsGenerators = parser.getFollowLatestGenerator(filterOption, mode);
      }
      else {
          const params = new URLSearchParams(location.search);
          const page = Number(params.get('p')) || 1;
          idsGenerators = parser.getFollowLatestGenerator(filterOption, mode, page);
      }
      downloadAndRetry(idsGenerators);
  }
  async function downloadSearchResult(evt) {
      evt.preventDefault();
      evt.stopPropagation();
      if (isDownloading)
          return;
      const pdlNodes = document.querySelectorAll('section ul li button.pdl-btn-sub');
      if (!pdlNodes.length)
          return;
      let ids = Array.prototype.map.call(pdlNodes, (node) => node.getAttribute('pdl-id'));
      if (getFilterOption().filterExcludeDownloaded) {
          const filteredIds = [];
          for (const id of ids) {
              const isDownloaded = await historyDb.has(id);
              !isDownloaded && filteredIds.push(id);
          }
          ids = filteredIds;
      }
      const idsGenerators = {
          total: ids.length,
          generator: (async function* () {
              yield {
                  avaliable: ids,
                  unavaliable: [],
                  invalid: []
              };
          })()
      };
      downloadAndRetry(idsGenerators);
  }

  const dlBarRef = {
      filter: {
          filterExcludeDownloaded: undefined,
          filterIllusts: undefined,
          filterManga: undefined,
          filterUgoira: undefined
      },
      statusBar: undefined,
      abortBtn: undefined
  };
  function updateStatus(str) {
      dlBarRef.statusBar && (dlBarRef.statusBar.textContent = str);
  }
  function createFilterEl(id, filterType, text) {
      const checkbox = document.createElement('input');
      const label = document.createElement('label');
      checkbox.id = id;
      checkbox.type = 'checkbox';
      checkbox.classList.add('pdl-checkbox');
      checkbox.setAttribute('category', String(filterType));
      checkbox.checked = config.get(filterType);
      label.setAttribute('for', id);
      label.setAttribute('category', String(filterType));
      label.textContent = text;
      checkbox.addEventListener('change', (evt) => {
          const checkbox = evt.currentTarget;
          const category = checkbox.getAttribute('category');
          config.set(category, checkbox.checked);
      });
      dlBarRef.filter[filterType] = checkbox;
      const wrap = document.createElement('div');
      wrap.classList.add('pdl-filter');
      wrap.appendChild(checkbox);
      wrap.appendChild(label);
      return wrap;
  }
  function createFilter() {
      const wrapper = document.createElement('div');
      wrapper.classList.add('pdl-filter-wrap');
      wrapper.appendChild(createFilterEl('pdl-filter-exclude_downloaded', 'filterExcludeDownloaded', t('checkbox.filter_exclude_downloaded')));
      wrapper.appendChild(createFilterEl('pdl-filter-illusts', 'filterIllusts', t('checkbox.filter_illusts')));
      wrapper.appendChild(createFilterEl('pdl-filter-manga', 'filterManga', t('checkbox.filter_manga')));
      wrapper.appendChild(createFilterEl('pdl-filter-ugoira', 'filterUgoira', t('checkbox.filter_ugoira')));
      return wrapper;
  }
  function createExcludeDownloadedFilter() {
      const wrapper = document.createElement('div');
      wrapper.classList.add('pdl-filter-wrap');
      wrapper.appendChild(createFilterEl('pdl-filter-exclude_downloaded', 'filterExcludeDownloaded', t('checkbox.filter_exclude_downloaded')));
      return wrapper;
  }
  function createDownloadBar(userId) {
      const nav = document.querySelector('nav[class~="sc-192ftwf-0"]');
      if (!nav)
          return;
      const dlBtn = nav.querySelector('.pdl-btn-all');
      if (dlBtn) {
          if (dlBtn.getAttribute('pdl-userid') === userId)
              return;
          removeDownloadBar();
      }
      const dlBar = document.createElement('div');
      dlBar.classList.add('pdl-dlbar');
      const statusBar = document.createElement('div');
      statusBar.classList.add('pdl-dlbar-status_bar');
      dlBarRef.statusBar = dlBar.appendChild(statusBar);
      const baseClasses = nav.querySelector('a:not([aria-current])').classList;
      dlBarRef.abortBtn = dlBar.appendChild(createPdlBtn({
          attrs: { 'pdl-userId': userId },
          classList: [...baseClasses, 'pdl-stop', 'pdl-hide']
      }, t('button.download_stop'), { addEvent: false }));
      if (userId !== getSelfId()) {
          const hasWorks = ["a[href$='illustrations']", "a[href$='manga']"].some((selector) => !!nav.querySelector(selector));
          if (hasWorks) {
              const el = createPdlBtn({
                  attrs: { 'pdl-userid': userId },
                  classList: [...baseClasses, 'pdl-btn-all']
              }, t('button.download_works'), { addEvent: false });
              el.addEventListener('click', downloadWorks);
              dlBar.appendChild(el);
          }
          if (nav.querySelector("a[href*='bookmarks']")) {
              const el = createPdlBtn({
                  attrs: { 'pdl-userid': userId, category: 'bookmarks' },
                  classList: [...baseClasses, 'pdl-btn-all']
              }, t('button.download_bookmarks'), { addEvent: false });
              el.addEventListener('click', downloadBookmarksOrTags);
              dlBar.appendChild(el);
          }
      }
      else {
          if (nav.querySelector("a[href*='bookmarks']")) {
              dlBar.appendChild(createPdlBtn({
                  attrs: { 'pdl-userid': userId, category: 'bookmarks', rest: 'all' },
                  classList: [...baseClasses, 'pdl-btn-all']
              }, t('button.download_bookmarks'), { addEvent: false }));
              dlBar.appendChild(createPdlBtn({
                  attrs: {
                      'pdl-userid': userId,
                      category: 'bookmarks',
                      rest: 'show'
                  },
                  classList: [...baseClasses, 'pdl-btn-all']
              }, t('button.download_bookmarks_public'), { addEvent: false }));
              dlBar.appendChild(createPdlBtn({
                  attrs: {
                      'pdl-userid': userId,
                      category: 'bookmarks',
                      rest: 'hide'
                  },
                  classList: [...baseClasses, 'pdl-btn-all']
              }, t('button.download_bookmarks_private'), { addEvent: false }));
              dlBar.querySelectorAll('.pdl-btn-all').forEach((node) => {
                  node.addEventListener('click', downloadBookmarksOrTags);
              });
          }
      }
      const filter = createFilter();
      nav.parentElement.insertBefore(filter, nav);
      nav.appendChild(dlBar);
  }
  function removeDownloadBar() {
      const dlBarWrap = document.querySelector('.pdl-dlbar');
      if (dlBarWrap) {
          dlBarWrap.remove();
          document.querySelector('.pdl-filter-wrap')?.remove();
      }
  }
  function updateFollowLatestDownloadBarBtnText(prevDlBtn, prevDlAllBtn) {
      if (location.pathname.includes('r18') && prevDlBtn.textContent !== t('button.download_r18_one_page')) {
          prevDlBtn.textContent = t('button.download_r18_one_page');
          prevDlAllBtn.textContent = t('button.download_r18');
      }
      else if (!location.pathname.includes('r18') && prevDlBtn.textContent !== t('button.download_all_one_page')) {
          prevDlBtn.textContent = t('button.download_all_one_page');
          prevDlAllBtn.textContent = t('button.download_all');
      }
  }
  function createFollowLatestDownloadBar() {
      const prevDlBtn = document.querySelector('.pdl-btn-all');
      if (prevDlBtn) {
          const prevDlAllBtn = document.querySelector('.pdl-dl-all');
          updateFollowLatestDownloadBarBtnText(prevDlBtn, prevDlAllBtn);
          return;
      }
      const nav = document.querySelector('nav');
      if (!nav || nav.parentElement.childElementCount === 1)
          return;
      const navBar = nav.parentElement;
      const modeSwitch = nav.nextElementSibling;
      const filter = createFilter();
      navBar.parentElement.insertBefore(filter, navBar);
      const dlBar = document.createElement('div');
      dlBar.classList.add('pdl-dlbar');
      dlBar.classList.add('pdl-dlbar-follow_latest');
      const statusBar = document.createElement('div');
      statusBar.classList.add('pdl-dlbar-status_bar');
      dlBarRef.statusBar = dlBar.appendChild(statusBar);
      const baseClasses = nav.querySelector('a:not([aria-current])').classList;
      dlBarRef.abortBtn = dlBar.appendChild(createPdlBtn({
          attrs: { 'pdl-userid': '' },
          classList: [...baseClasses, 'pdl-stop', 'pdl-hide']
      }, t('button.download_stop'), { addEvent: false }));
      const dlBtn = createPdlBtn({
          attrs: { 'pdl-userid': '' },
          classList: [...baseClasses, 'pdl-btn-all']
      }, t('button.download_works'), { addEvent: false });
      dlBtn.addEventListener('click', downloadFollowLatest);
      dlBar.appendChild(dlBtn);
      const dlAllBtn = createPdlBtn({
          attrs: { 'pdl-userid': '' },
          classList: [...baseClasses, 'pdl-btn-all', 'pdl-dl-all']
      }, t('button.download_works'), { addEvent: false });
      dlAllBtn.addEventListener('click', downloadFollowLatest);
      dlBar.appendChild(dlAllBtn);
      navBar.insertBefore(dlBar, modeSwitch);
  }
  function createSearchDownloadbar() {
      if (document.querySelector('.pdl-dlbar'))
          return;
      const sections = document.querySelectorAll('section');
      const worksSection = sections[sections.length - 1];
      const styleRefEle = document.querySelector('nav a:not([aria-current])');
      if (!worksSection || !styleRefEle)
          return;
      const dlBarContainer = worksSection.firstElementChild.firstElementChild;
      const dlBar = document.createElement('div');
      dlBar.classList.add('pdl-dlbar');
      dlBar.classList.add('pdl-dlbar-search');
      const statusBar = document.createElement('div');
      statusBar.classList.add('pdl-dlbar-status_bar');
      dlBarRef.statusBar = dlBar.appendChild(statusBar);
      const baseClasses = styleRefEle.classList;
      dlBarRef.abortBtn = dlBar.appendChild(createPdlBtn({
          attrs: { 'pdl-userid': '' },
          classList: [...baseClasses, 'pdl-stop', 'pdl-hide']
      }, t('button.download_stop'), { addEvent: false }));
      const dlBtn = createPdlBtn({
          attrs: { 'pdl-userid': '' },
          classList: [...baseClasses, 'pdl-btn-all']
      }, t('button.download_all_one_page'), { addEvent: false });
      dlBtn.addEventListener('click', downloadSearchResult);
      const filter = createExcludeDownloadedFilter();
      dlBarContainer.parentElement.insertBefore(filter, dlBarContainer);
      dlBar.appendChild(dlBtn);
      dlBarContainer.appendChild(dlBar);
  }
  function changeDlbarDisplay() {
      document.querySelectorAll('.pdl-dlbar .pdl-btn-all').forEach((ele) => {
          ele.classList.toggle('pdl-hide');
      });
      document.querySelector('.pdl-dlbar .pdl-stop')?.classList.toggle('pdl-hide');
      document.querySelectorAll('.pdl-tag').forEach((ele) => {
          ele.classList.toggle('pdl-tag-hide');
      });
      document.querySelector('.pdl-filter-wrap')?.classList.toggle('pdl-unavailable');
  }

  function onProgressCB(progressData) {
      if (typeof progressData === 'string') {
          updateStatus(progressData);
      }
      else {
          logger.info('Update progress by', progressData.illustId, ', completed: ', progressData.completed);
          updateStatus(`Downloading: ${progressData.completed} / ${progressData.avaliable}`);
      }
  }
  async function downloadByIds(total, idsGenerators, signal, onProgress) {
      signal.throwIfAborted();
      const failed = [];
      const unavaliable = [];
      const invalid = [];
      const tasks = [];
      let completed = 0;
      let tooManyRequests = false;
      let wakeTooManyRequest;
      let wakeInterval;
      let resolve;
      let reject;
      const done = new Promise((r, j) => {
          resolve = r;
          reject = j;
      });
      signal.addEventListener('abort', () => {
          if (tasks.length) {
              downloader.del(tasks);
              tasks.length = 0;
          }
          wakeTooManyRequest?.();
          wakeInterval?.();
          reject(signal.aborted ? signal.reason : 'unexpected generator error');
      }, { once: true });
      const afterEach = (illustId) => {
          const avaliable = total - failed.length - unavaliable.length - invalid.length;
          onProgress({
              illustId,
              avaliable,
              completed
          });
          if (completed === avaliable) {
              resolve({ failed, unavaliable });
          }
      };
      onProgress('Downloading...');
      try {
          for (const idsGenerator of idsGenerators) {
              for await (const ids of idsGenerator) {
                  logger.info('Got ids:', ids);
                  signal.throwIfAborted();
                  if (ids.unavaliable.length) {
                      unavaliable.push(...ids.unavaliable);
                  }
                  if (ids.invalid.length) {
                      invalid.push(...ids.invalid);
                  }
                  if (typeof ids.total === 'number' && !Number.isNaN(ids.total)) {
                      total = ids.total;
                  }
                  if (ids.avaliable.length) {
                      for (const id of ids.avaliable) {
                          signal.throwIfAborted();
                          if (tooManyRequests) {
                              onProgress('Too many requests, wait 30s');
                              const { wake, sleep } = wakeableSleep(30000);
                              wakeTooManyRequest = wake;
                              await sleep;
                              signal.throwIfAborted();
                              tooManyRequests = false;
                              onProgress('Downloading...');
                          }
                          let historyData;
                          parser
                              .getAjaxArtworkData(id)
                              .then((data) => {
                              signal.throwIfAborted();
                              const sources = parser.getDownloadSource(data);
                              tasks.push(sources[0].taskId);
                              const { illustId, userName, title, tags } = data.illustData;
                              historyData = {
                                  pid: Number(illustId),
                                  user: userName,
                                  title,
                                  tags: tags.tags.map((tagData) => tagData.tag)
                              };
                              return downloader.add(sources);
                          })
                              .then((taskId) => {
                              historyDb.add(historyData);
                              if (!signal.aborted) {
                                  tasks.splice(tasks.indexOf(taskId), 1);
                                  completed++;
                                  afterEach(id);
                              }
                          }, (reason) => {
                              if (!signal.aborted) {
                                  reason && logger.error(reason);
                                  if (reason instanceof RequestError && reason.response.status === 429) {
                                      tooManyRequests = true;
                                  }
                                  if (reason instanceof JsonDataError) {
                                      unavaliable.push(id);
                                  }
                                  else {
                                      failed.push(id);
                                  }
                                  afterEach(id);
                              }
                          });
                          const { wake, sleep } = wakeableSleep(1000);
                          wakeInterval = wake;
                          await sleep;
                      }
                  }
                  else {
                      afterEach('no avaliable id');
                  }
              }
          }
      }
      catch (error) {
          if (!signal.aborted) {
              done.catch((reason) => {
                  logger.info('catch unexpected abort: ', reason);
              });
              signal.dispatchEvent(new Event('abort'));
              throw error;
          }
      }
      return done;
  }
  let isDownloading = false;
  async function useDownloadBar(chunksGenerators) {
      if (!dlBarRef.abortBtn)
          return;
      let total = 0;
      let failedResult;
      const idsGenerators = [];
      !Array.isArray(chunksGenerators) && (chunksGenerators = [chunksGenerators]);
      isDownloading = true;
      changeDlbarDisplay();
      try {
          await Promise.all(chunksGenerators).then((gens) => {
              gens.forEach((val) => {
                  total += val.total;
                  idsGenerators.push(val.generator);
              });
          });
      }
      catch (error) {
          logger.error(error);
          updateStatus('Network error, see console');
          changeDlbarDisplay();
          isDownloading = false;
          return;
      }
      if (total === 0) {
          updateStatus('No works');
      }
      else {
          try {
              logger.info('Total works:', total);
              const controller = new AbortController();
              const signal = controller.signal;
              !signal.throwIfAborted &&
                  (signal.throwIfAborted = function () {
                      if (this.aborted) {
                          throw this.reason;
                      }
                  });
              if (!('reason' in signal)) {
                  const abort = controller.abort;
                  controller.abort = function (reason) {
                      this.signal.reason = reason ? reason : new DOMException('signal is aborted without reason');
                      abort.apply(this);
                  };
              }
              dlBarRef.abortBtn?.addEventListener('click', () => {
                  controller.abort();
              }, { once: true });
              const { failed, unavaliable } = await downloadByIds(total, idsGenerators, signal, onProgressCB);
              if (failed.length || unavaliable.length) {
                  updateStatus(`Failed: ${failed.length + unavaliable.length}. See console.`);
                  console.log('[Pixiv Downloader] Failed: ', failed.join(', '));
                  console.log('[Pixiv Downloader] Unavaliable: ', unavaliable.join(', '));
                  if (failed.length)
                      failedResult = failed;
              }
              else {
                  console.log('[Pixiv Downloader] Download complete');
                  updateStatus('Complete');
              }
          }
          catch (error) {
              if (error instanceof DOMException) {
                  updateStatus('Stop');
              }
              else {
                  updateStatus('Error, see console');
                  logger.error(error);
              }
          }
      }
      changeDlbarDisplay();
      isDownloading = false;
      return failedResult;
  }

  function createTagsBtn(userId, category) {
      const tagsEles = document.querySelectorAll('section> div:nth-child(2) > div > div');
      if (!tagsEles.length)
          return;
      let cate;
      if (category === 'illustrations' || category === 'artworks') {
          cate = 'illusts';
      }
      else {
          cate = category;
      }
      let rest = 'show';
      if (userId === getSelfId() && category === 'bookmarks' && location.search.includes('rest=hide'))
          rest = 'hide';
      tagsEles.forEach((ele) => {
          const tagBtn = ele.querySelector('.pdl-btn');
          if (tagBtn) {
              const btnRest = tagBtn.getAttribute('rest');
              if (rest !== btnRest)
                  tagBtn.setAttribute('rest', rest);
              return;
          }
          let tag;
          const tagLink = ele.querySelector('a');
          if (!tagLink)
              return;
          if (tagLink.getAttribute('status') !== 'active') {
              if (rest === 'hide') {
                  tag = tagLink.href.slice(tagLink.href.lastIndexOf('/') + 1, tagLink.href.lastIndexOf('?'));
              }
              else {
                  tag = tagLink.href.slice(tagLink.href.lastIndexOf('/') + 1);
              }
          }
          else {
              const tagTextEles = ele.querySelectorAll('div[title]');
              if (!tagTextEles.length)
                  return logger.info('No Tags Element found.');
              tag = tagTextEles[tagTextEles.length - 1].getAttribute('title').slice(1);
          }
          const attrs = {
              attrs: { 'pdl-userId': userId, category: cate, tag, rest },
              classList: ['pdl-btn', 'pdl-tag']
          };
          if (isDownloading)
              attrs.classList.push('pdl-tag-hide');
          const dlBtn = createPdlBtn(attrs, '', { addEvent: false });
          if (!(tagLink.href.includes('bookmarks') && tagLink.getAttribute('status') !== 'active')) {
              dlBtn.style.backgroundColor = tagLink.getAttribute('color') + '80';
          }
          dlBtn.addEventListener('click', downloadBookmarksOrTags);
          ele.appendChild(dlBtn);
      });
      let modalTagsEles;
      let modal;
      if (category === 'bookmarks') {
          modal = document.querySelector('div[role="presentation"]');
          if (!modal)
              return;
          modalTagsEles = modal.querySelectorAll('a');
      }
      else {
          const charcoalTokens = document.querySelectorAll('.charcoal-token');
          modal = charcoalTokens[charcoalTokens.length - 1];
          if (!modal)
              return;
          modalTagsEles = modal.querySelectorAll('a');
      }
      if (!regexp.userPageTags.exec(modalTagsEles[0]?.href))
          return;
      modalTagsEles.forEach((ele) => {
          if (ele.querySelector('.pdl-btn'))
              return;
          let tag;
          if (rest === 'hide') {
              tag = ele.href.slice(ele.href.lastIndexOf('/') + 1, ele.href.lastIndexOf('?'));
          }
          else {
              tag = ele.href.slice(ele.href.lastIndexOf('/') + 1);
          }
          const attrs = {
              attrs: { 'pdl-userId': userId, category: cate, tag, rest },
              classList: ['pdl-btn', 'pdl-modal-tag']
          };
          if (isDownloading)
              attrs.classList.push('pdl-tag-hide');
          const dlBtn = createPdlBtn(attrs, '', { addEvent: false });
          dlBtn.addEventListener('click', (evt) => {
              modal.querySelector('svg').parentElement.click();
              downloadBookmarksOrTags(evt);
          });
          ele.appendChild(dlBtn);
      });
  }

  function createToolbarBtn(id) {
      if (document.querySelector('.pdl-btn-main'))
          return;
      const handleBar = document.querySelector('main section section');
      if (handleBar) {
          const pdlBtnWrap = handleBar.lastElementChild.cloneNode();
          const attrs = {
              attrs: { 'pdl-id': id },
              classList: ['pdl-btn', 'pdl-btn-main']
          };
          const el = createPdlBtn(attrs);
          historyDb.has(id).then((downloaded) => {
              downloaded && el.classList.add('pdl-complete');
          });
          pdlBtnWrap.appendChild(el);
          handleBar.appendChild(pdlBtnWrap);
      }
  }

  function createWorkScrollBtn(id) {
      const works = document.querySelectorAll("[role='presentation'] > a");
      if (works.length < 2)
          return;
      const containers = Array.from(works).map((node) => node.parentElement.parentElement);
      if (containers[0].querySelector('.pdl-btn'))
          return;
      containers.forEach((node, idx) => {
          const wrapper = document.createElement('div');
          wrapper.classList.add('pdl-wrap-artworks');
          const attrs = {
              attrs: { 'pdl-id': id, 'should-download': String(idx) },
              classList: ['pdl-btn', 'pdl-btn-sub', 'artworks']
          };
          wrapper.appendChild(createPdlBtn(attrs));
          node.appendChild(wrapper);
      });
  }

  const createPresentationBtn = (() => {
      let observer, btn;
      function cb(mutationList) {
          const newImg = mutationList[1]['addedNodes'][0];
          const [pageNum] = regexp.originSrcPageNum.exec(newImg.src) ?? [];
          if (!pageNum)
              throw new Error('[Error]Invalid Image Element.');
          btn?.setAttribute('should-download', String(pageNum));
      }
      return (id) => {
          const containers = document.querySelector("body > [role='presentation'] > div");
          if (!containers) {
              if (observer) {
                  observer.disconnect();
                  observer = null;
                  btn = null;
              }
              return;
          }
          if (containers.querySelector('.pdl-btn'))
              return;
          const img = containers.querySelector('div > img');
          if (!img)
              return;
          const isOriginImg = regexp.originSrcPageNum.exec(img.src);
          if (!isOriginImg)
              return;
          const [pageNum] = isOriginImg;
          const attrs = {
              attrs: { 'pdl-id': id, 'should-download': pageNum },
              classList: ['pdl-btn', 'pdl-btn-sub', 'presentation']
          };
          btn = createPdlBtn(attrs);
          containers.appendChild(btn);
          if (!img.parentElement)
              return;
          observer = new MutationObserver(cb);
          observer.observe(img.parentElement, { childList: true, subtree: true });
      };
  })();

  function createPreviewModalBtn() {
      const illustModalBtn = document.querySelector('.gtm-manga-viewer-preview-modal-open:not(.pdl-listened)');
      const mangaModalBtn = document.querySelector('.gtm-manga-viewer-open-preview:not(.pdl-listened)');
      const mangaViewerModalBtn = document.querySelectorAll('.gtm-manga-viewer-close-icon:not(.pdl-listened)')?.[1];
      if (!illustModalBtn && !mangaModalBtn && !mangaViewerModalBtn)
          return;
      [illustModalBtn, mangaModalBtn, mangaViewerModalBtn].forEach((node) => {
          if (node) {
              node.classList.add('pdl-listened');
              node.addEventListener('click', handleModalClick);
          }
      });
  }
  function handleModalClick() {
      const timer = setInterval(() => {
          logger.info('Start to find modal.');
          const ulList = document.querySelectorAll('ul');
          const previewList = ulList[ulList.length - 1];
          if (getComputedStyle(previewList).display !== 'grid')
              return;
          clearInterval(timer);
          const [, id] = regexp.artworksPage.exec(location.pathname) ?? [];
          previewList.childNodes.forEach((node, idx) => {
              node.style.position = 'relative';
              const attrs = {
                  attrs: { 'pdl-id': id, 'should-download': String(idx) },
                  classList: ['pdl-btn', 'pdl-btn-sub']
              };
              node.appendChild(createPdlBtn(attrs));
          });
      }, 300);
  }

  function createMangaViewerBtn(id) {
      const mangaViewerBackBtn = document.querySelector('.gtm-manga-viewer-close-icon');
      if (!mangaViewerBackBtn)
          return;
      const container = mangaViewerBackBtn.parentElement;
      if (container.querySelector('.pdl-btn'))
          return;
      container.appendChild(createPdlBtn({
          attrs: { 'pdl-id': id },
          classList: ['pdl-btn', 'pdl-btn-sub', 'manga-viewer']
      }));
  }

  function pageActions() {
      const pathname = location.pathname;
      let param;
      switch (true) {
          case !!(param = regexp.artworksPage.exec(pathname)): {
              const id = param[1];
              createToolbarBtn(id);
              createWorkScrollBtn(id);
              createPresentationBtn(id);
              createPreviewModalBtn();
              createMangaViewerBtn(id);
              break;
          }
          case !!(param = regexp.userPage.exec(pathname)): {
              const id = param[1];
              createDownloadBar(id);
              const matchTag = regexp.userPageTags.exec(pathname);
              if (matchTag) {
                  createTagsBtn(id, matchTag[1]);
              }
              break;
          }
          case regexp.followLatest.test(pathname): {
              createFollowLatestDownloadBar();
              break;
          }
          case regexp.searchPage.test(pathname): {
              createSearchDownloadbar();
              break;
          }
          case regexp.historyPage.test(pathname): {
              createThumbnailsBtn(document.querySelectorAll('span[style]._history-item'));
              break;
          }
          default:
              removeDownloadBar();
              break;
      }
  }
  let firstRun = true;
  function observerCallback(records) {
      const addedNodes = [];
      records.forEach((record) => {
          if (!record.addedNodes.length)
              return;
          record.addedNodes.forEach((node) => {
              if (node.nodeType === Node.ELEMENT_NODE && node.tagName !== 'BUTTON' && node.tagName !== 'IMG') {
                  addedNodes.push(node);
              }
          });
      });
      if (!addedNodes.length)
          return;
      if (firstRun) {
          createThumbnailsBtn(document.querySelectorAll('a'));
          firstRun = false;
      }
      else {
          fixPixivPreviewer(addedNodes);
          const thunmnails = addedNodes.reduce((prev, current) => {
              return prev.concat(current instanceof HTMLAnchorElement ? [current] : Array.from(current.querySelectorAll('a')));
          }, []);
          createThumbnailsBtn(thunmnails);
      }
      pageActions();
  }

  function createModal(args, option = { closeOnClickModal: false }) {
      const modalHtml = `
<div class="pdl-modal">
  <div class="pdl-dialog">
    <button class="pdl-dialog-close"></button>
    <header class="pdl-dialog-header"></header>
    <div class="pdl-dialog-content"></div>
    <footer class="pdl-dialog-footer"></footer>
  </div>
</div>`;
      const fragment = stringToFragment(modalHtml);
      const modal = fragment.querySelector('.pdl-modal');
      const dialog = modal.querySelector('.pdl-dialog');
      const closeBtn = modal.querySelector('.pdl-dialog-close');
      const header = modal.querySelector('.pdl-dialog-header');
      const content = modal.querySelector('.pdl-dialog-content');
      const footer = modal.querySelector('.pdl-dialog-footer');
      if (option.closeOnClickModal) {
          dialog.addEventListener('click', (e) => {
              e.stopPropagation();
          });
          modal.addEventListener('click', () => {
              modal.remove();
          });
      }
      closeBtn.addEventListener('click', () => {
          modal.remove();
      });
      args.header && header.appendChild(args.header);
      args.footer && footer.appendChild(args.footer);
      content.appendChild(args.content);
      return fragment;
  }

  function showUpgradeMsg() {
      const headerHtml = `<h3>Pixiv Downloader ${config.get('version')}</h3>`;
      const contentHtml = `
<div class="pdl-changelog">
  <p>给大家拜个晚年,愿各位身体健康,家庭美满,学业有成,工作顺利!</p>
  <ul>
    <li>使用IndexedDB替换localStorage,避免历史记录大小超过限额。</li>
    <li>修正部分按钮被遮挡无法点击的问题。</li>
    <li>修正排行榜页设置页面的样式问题。</li>
    <br/>
    <li>Replace localStorage with IndexedDB to avoid exceeding quota.</li>
    <li>Fix: some download buttons can not be clicked.</li>
    <li>Fix: styling issues on rankings page.</li>
  </ul>
</div>`;
      const footerHtml = `
<style>
  .pdl-dialog-footer {
    position: relative;
    font-size: 12px;
  }
</style>
<details style="margin-top: 1.5em">
  <summary style="display: inline-block; list-style: none; cursor: pointer; color: #0096fa; text-decoration: underline">
    脚本还行?请我喝杯可乐吧!
  </summary>
  ${creditCode}
  <p style="text-align: center">愿你每天都能找到对的色图,就像我每天都能喝到香草味可乐</p>
</details>
<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"
  >${t('text.feedback')}
</a>`;
      document.body.appendChild(createModal({
          header: stringToFragment(headerHtml),
          content: stringToFragment(contentHtml),
          footer: stringToFragment(footerHtml)
      }));
  }

  function createTabUgoira() {
      const tabHtml = `<div class="pdl-tab-item">${t('text.tab_title_ugoira')}</div>`;
      const paneHtml = `
<div class="pdl-tab-pane">
  <div id="pdl-setting-ugoira">
    <p class="option-header">${t('text.label_ugoira_format')}</p>
    <div id="pdl-ugoira-format-wrap">
      <div class="pdl-ugoira-format-item">
        <input type="radio" id="pdl-ugoira-zip" value="zip" name="format" /><label for="pdl-ugoira-zip">Zip</label>
      </div>
      <div class="pdl-ugoira-format-item">
        <input type="radio" id="pdl-ugoira-gif" value="gif" name="format" /><label for="pdl-ugoira-gif">Gif</label>
      </div>
      <div class="pdl-ugoira-format-item">
        <input type="radio" id="pdl-ugoira-apng" value="png" name="format" /><label for="pdl-ugoira-apng">Png</label>
      </div>
      <div class="pdl-ugoira-format-item">
        <input type="radio" id="pdl-ugoira-webm" value="webm" name="format" /><label for="pdl-ugoira-webm">Webm</label>
      </div>
      <div class="pdl-ugoira-format-item">
        <input type="radio" id="pdl-ugoira-webp" value="webp" name="format" /><label for="pdl-ugoira-webp">Webp</label>
      </div>
    </div>
  </div>
</div>`;
      const tab = stringToFragment(tabHtml);
      const pane = stringToFragment(paneHtml);
      const ugoiraFormat = config.get('ugoiraFormat');
      pane.querySelectorAll('.pdl-ugoira-format-item input[type="radio"]').forEach((el) => {
          if (ugoiraFormat === el.value)
              el.checked = true;
          el.addEventListener('change', (ev) => {
              config.set('ugoiraFormat', ev.currentTarget.value);
          });
      });
      return {
          tab,
          pane
      };
  }

  function createTabFilename() {
      const tabHtml = `<div class="pdl-tab-item">${t('text.tab_title_filename')}</div>`;
      const paneHtml = `
<div class="pdl-tab-pane">
  <div id="pdl-setting-filename">
    <div>
      <div class="pdl-input-wrap">
        <label for="pdlfolder">${t('text.label_folder')}</label>
        <input type="text" id="pdlfolder" maxlength="100" />
        <button id="pdl-filename-folder-reset" class="pdl-dialog-button icon" disabled>↺</button>
        <button id="pdl-filename-folder-confirm" class="pdl-dialog-button icon primary" disabled>✓</button>
      </div>
      <div class="pdl-input-wrap">
        <label for="pdlfilename">${t('text.label_filename')}</label>
        <input type="text" id="pdlfilename" placeholder="${t('text.placeholder_folder_subfolder_unused')}" required maxlength="100" />
        <button id="pdl-filename-filename-reset" class="pdl-dialog-button icon" disabled>↺</button>
        <button id="pdl-filename-filename-confirm" class="pdl-dialog-button icon primary" disabled>✓</button>
      </div>
    </div>
    <div class="tags-option">
      <span class="tags-title">${t('text.label_tag_lang')}</span>
      <div class="tags-content">
        <div class="tags-item">
          <input class="pdl-option-tag" type="radio" name="lang" id="lang_ja" value="ja" />
          <label for="lang_ja">日本語(default)</label>
        </div>
        <div class="tags-item">
          <input class="pdl-option-tag" type="radio" name="lang" id="lang_zh" value="zh" />
          <label for="lang_zh">简中</label>
        </div>
        <div class="tags-item">
          <input class="pdl-option-tag" type="radio" name="lang" id="lang_zh_tw" value="zh_tw" />
          <label for="lang_zh_tw">繁中</label>
        </div>
        <div class="tags-item">
          <input class="pdl-option-tag" type="radio" name="lang" id="lang_en" value="en" />
          <label for="lang_en">English</label>
        </div>
      </div>
    </div>
    <p style="font-size: 14px; margin: 0.5em 0">${t('text.tips_filename_pattern')}</p>
    <p style="font-size: 14px; margin: 0.5em 0">${t('text.tips_subfolder_unused')}</p>
    <p style="font-size: 14px; margin: 0.5em 0">${t('text.tips_tag_translation')}</p>
    <hr />
    <div ${env.isFileSystemAccessAvaliable() ? '' : 'class="pdl-unavailable"'}>
      <div style="display: flex; justify-content: space-between; align-items: center; margin: 12px 0; gap: 12px">
        <label class="pdl-options" style="padding: 0.6em 4px">
          <span style="font-weight: 700; margin-right: 8px">${t('text.label_fsa')}</span>
          <input id="pdl-options-file-system-access" type="checkbox" class="pdl-checkbox"/>
        </label>
        <hr class="vertical" />
        <div class="pdl-input-wrap" style="flex: 1; margin: 0">
          <input id="pdl-fsa-show-directory" type="text" placeholder="${t('text.placeholder_fsa_folder')}" style="font-size: 14px; padding: 8px 0.5em; line-height: 1.15" disabled/>
        </div>
        <button id="pdl-fsa-change-directory" class="pdl-dialog-button primary">${t('button.fsa_change_dir')}</button>
      </div>
      <div class="tags-option">
        <span class="tags-title">${t('text.label_filename_conflict')}</span>
        <div class="tags-content">
          <div class="tags-item">
            <input class="pdl-option-conflict" type="radio" name="conflict_action" id="action_uniquify" value="uniquify"/>
            <label for="action_uniquify">${t('radio.filename_conflict_option_uniquify')}</label>
          </div>
          <div class="tags-item">
            <input class="pdl-option-conflict" type="radio" name="conflict_action" id="action_overwrite" value="overwrite"/>
            <label for="action_overwrite">${t('radio.filename_conflict_option_overwrite')}</label>
          </div>
          <div class="tags-item">
            <input class="pdl-option-conflict" type="radio" name="conflict_action" id="action_prompt" value="prompt"/>
            <label for="action_prompt">${t('radio.filename_conflict_option_prompt')}</label>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>`;
      const tab = stringToFragment(tabHtml);
      const pane = stringToFragment(paneHtml);
      const folder = pane.querySelector('#pdlfolder');
      const folderReset = pane.querySelector('#pdl-filename-folder-reset');
      const folderUpdate = pane.querySelector('#pdl-filename-folder-confirm');
      const filename = pane.querySelector('#pdlfilename');
      const filenameReset = pane.querySelector('#pdl-filename-filename-reset');
      const filenameUpdate = pane.querySelector('#pdl-filename-filename-confirm');
      const filenamePattern = config.get('filenamePattern');
      const folderPattern = config.get('folderPattern');
      if (!folder || !filename)
          throw new Error('[Error]Can not create modal.');
      filename.value = filenamePattern;
      if (!env.isSupportSubpath()) {
          folder.setAttribute('disabled', '');
          folder.value = '';
      }
      else {
          folder.value = folderPattern;
      }
      folder.placeholder = env.isViolentmonkey()
          ? t('text.placeholder_folder_vm')
          : !env.isSupportSubpath()
              ? t('text.placeholder_folder_need_api')
              : t('text.placeholder_folder_subfolder_unused');
      folder.addEventListener('input', () => {
          folderReset?.removeAttribute('disabled');
          folderUpdate?.removeAttribute('disabled');
      });
      folderReset?.addEventListener('click', () => {
          folder.value = config.get('folderPattern');
          folderReset?.setAttribute('disabled', '');
          folderUpdate?.setAttribute('disabled', '');
      });
      folderUpdate?.addEventListener('click', () => {
          const folderPattern = folder.value
              .split('/')
              .map(replaceInvalidChar)
              .filter((path) => !!path)
              .join('/');
          config.set('folderPattern', folderPattern);
          folder.value = folderPattern;
          folderReset?.setAttribute('disabled', '');
          folderUpdate?.setAttribute('disabled', '');
      });
      filename.addEventListener('input', () => {
          filenameReset?.removeAttribute('disabled');
          filenameUpdate?.removeAttribute('disabled');
      });
      filenameReset?.addEventListener('click', () => {
          filename.value = config.get('filenamePattern');
          filenameReset?.setAttribute('disabled', '');
          filenameUpdate?.setAttribute('disabled', '');
      });
      filenameUpdate?.addEventListener('click', () => {
          const filenamePattern = replaceInvalidChar(filename.value);
          if (filenamePattern === '')
              return filenameReset?.click();
          config.set('filenamePattern', filenamePattern);
          filename.value = filenamePattern;
          filenameReset?.setAttribute('disabled', '');
          filenameUpdate?.setAttribute('disabled', '');
      });
      const tagLang = config.get('tagLang');
      pane.querySelectorAll('.tags-content .tags-item input.pdl-option-tag').forEach((input) => {
          if (tagLang === input.value)
              input.checked = true;
          input.addEventListener('change', (ev) => {
              config.set('tagLang', ev.currentTarget.value);
          });
      });
      if (env.isFileSystemAccessAvaliable()) {
          const enableFSA = pane.querySelector('#pdl-options-file-system-access');
          const showDir = pane.querySelector('#pdl-fsa-show-directory');
          const changeDirBtn = pane.querySelector('#pdl-fsa-change-directory');
          const actionInput = pane.querySelectorAll('.tags-content .tags-item input.pdl-option-conflict');
          const interactElems = [changeDirBtn, ...actionInput];
          const isUseFSA = config.get('useFileSystemAccess');
          const conflictAction = config.get('fileSystemFilenameConflictAction');
          if (isUseFSA) {
              folder.placeholder = t('text.placeholder_folder_subfolder_unused');
              folder.removeAttribute('disabled');
              folder.value = folderPattern;
          }
          enableFSA.checked = isUseFSA;
          if (!isUseFSA) {
              interactElems.forEach((el) => el.setAttribute('disabled', ''));
          }
          enableFSA.addEventListener('change', (ev) => {
              const isEnabled = ev.target.checked;
              config.set('useFileSystemAccess', isEnabled);
              if (isEnabled) {
                  folder.placeholder = t('text.placeholder_folder_subfolder_unused');
                  if (folder.hasAttribute('disabled')) {
                      folder.removeAttribute('disabled');
                      folder.value = config.get('folderPattern');
                  }
                  interactElems.forEach((el) => el.removeAttribute('disabled'));
              }
              else {
                  if (env.isViolentmonkey()) {
                      folder.placeholder = t('text.placeholder_folder_vm');
                      folder.setAttribute('disabled', '');
                      folder.value = '';
                  }
                  else if (!env.isSupportSubpath()) {
                      folder.placeholder = t('text.placeholder_folder_need_api');
                      folder.setAttribute('disabled', '');
                      folder.value = '';
                  }
                  interactElems.forEach((el) => el.setAttribute('disabled', ''));
              }
          });
          showDir.value = getCurrentDirName();
          changeDirBtn.addEventListener('click', async () => {
              await updateDirHandle();
              showDir.value = getCurrentDirName();
          });
          actionInput.forEach((input) => {
              if (conflictAction === input.value)
                  input.checked = true;
              input.addEventListener('change', (ev) => {
                  config.set('fileSystemFilenameConflictAction', ev.currentTarget.value);
              });
          });
      }
      return {
          tab,
          pane
      };
  }

  function createTabAdjustButtonPosition() {
      const style = getComputedStyle(document.documentElement);
      const tabHtml = `<div class="pdl-tab-item">${t('text.tab_title_button')}</div>`;
      const paneHtml = `
<div class="pdl-tab-pane">
  <div class="pdl-adjust-button">
    <div class="pdl-adjust-content">
      <datalist id="pdl-adjust-tickmarks">
        <option value="0"></option>
        <option value="25"></option>
        <option value="50"></option>
        <option value="75"></option>
        <option value="100"></option>
      </datalist>
      <div class="pdl-adjust-item">
        <p class="pdl-adjust-title">${t('text.title_button_preview_self_bookmark')}</p>
        <div class="pdl-adjust-select">
          <span>${t('text.label_button_horizon')}</span
          ><input
            id="pdl-btn-self-bookmark-left"
            type="range"
            max="100"
            min="0"
            step="1"
            list="pdl-adjust-tickmarks"
            value="${style.getPropertyValue('--pdl-btn-self-bookmark-left')}"
          />
        </div>
        <div class="pdl-adjust-select">
          <span>${t('text.label_button_vertical')}</span
          ><input
            id="pdl-btn-self-bookmark-top"
            type="range"
            max="100"
            min="0"
            step="1"
            list="pdl-adjust-tickmarks"
            value="${style.getPropertyValue('--pdl-btn-self-bookmark-top')}"
          />
        </div>
      </div>
      <div class="pdl-adjust-item">
        <p class="pdl-adjust-title">${t('text.title_button_preview')}</p>
        <div class="pdl-adjust-select">
          <span>${t('text.label_button_horizon')}</span
          ><input
            id="pdl-btn-left"
            type="range"
            max="100"
            min="0"
            step="1"
            list="pdl-adjust-tickmarks"
            value="${style.getPropertyValue('--pdl-btn-left')}"
          />
        </div>
        <div class="pdl-adjust-select">
          <span>${t('text.label_button_vertical')}</span
          ><input
            id="pdl-btn-top"
            type="range"
            max="100"
            min="0"
            step="1"
            list="pdl-adjust-tickmarks"
            value="${style.getPropertyValue('--pdl-btn-top')}"
          />
        </div>
      </div>
    </div>
    <div class="pdl-adjust-preview">
      <div class="pdl-spacer"></div>
      <div class="pdl-thumbnail-sample">
        <button class="pdl-btn pdl-btn-sub"></button>
        <button class="pdl-btn pdl-btn-sub self-bookmark pdl-complete"></button>
      </div>
      <div class="pdl-spacer"></div>
    </div>
  </div>
</div>`;
      const tab = stringToFragment(tabHtml);
      const pane = stringToFragment(paneHtml);
      pane.querySelectorAll('.pdl-adjust-select input[type="range"]').forEach((el) => {
          el.addEventListener('input', (ev) => {
              const el = ev.target;
              const val = el.value;
              document.documentElement.style.setProperty('--' + el.id, val);
          });
          el.addEventListener('change', (ev) => {
              const el = ev.target;
              const key = el.id;
              config.set(key, el.value);
          });
      });
      return {
          tab,
          pane
      };
  }

  function createTabHistory() {
      const tabHtml = `<div class="pdl-tab-item">${t('text.tab_title_history')}</div>`;
      const paneHtml = `
<div class="pdl-tab-pane">
  <div id="pdl-setting-history">
    <div>
      <button id="pdl-export" class="btn-history pdl-dialog-button primary">
        ${t('button.history_export')}
      </button>
    </div>
    <div>
      <input type="file" id="pdl-import-json" accept=".json" style="display: none" />
      <button id="pdl-import-json-btn" class="btn-history pdl-dialog-button primary">
        ${t('button.history_import_json')}
      </button>
    </div>
    <div>
      <input type="file" id="pdl-import-txt" accept=".txt" style="display: none" />
      <button id="pdl-import-txt-btn" class="btn-history pdl-dialog-button primary">
        ${t('button.history_import_txt')}
      </button>
    </div>
    <div>
      <button id="pdl-clear-history" class="btn-history pdl-dialog-button primary">
        ${t('button.history_clear')}
      </button>
    </div>
  </div>
</div>`;
      const tab = stringToFragment(tabHtml);
      const pane = stringToFragment(paneHtml);
      function readHistoryFile(type, file) {
          return new Promise((resolve) => {
              if (file.type !== type)
                  throw new Error('Invalid file');
              const reader = new FileReader();
              reader.readAsText(file);
              reader.onload = (readEvt) => {
                  const text = readEvt.target?.result;
                  if (typeof text !== 'string')
                      throw new Error('Invalid file');
                  const history = JSON.parse(text);
                  if (!(history instanceof Array))
                      throw new Error('Invalid file');
                  resolve(history);
              };
          });
      }
      const importJSON = pane.querySelector('#pdl-import-json');
      importJSON?.addEventListener('change', (evt) => {
          const file = evt.currentTarget.files?.[0];
          if (!file)
              return;
          readHistoryFile('application/json', file)
              .then((data) => historyDb.bulkAdd(data))
              .then(() => location.reload())
              .catch((err) => alert(err?.message));
      });
      const importTxt = pane.querySelector('#pdl-import-txt');
      importTxt?.addEventListener('change', (evt) => {
          const file = evt.currentTarget.files?.[0];
          if (!file)
              return;
          readHistoryFile('text/plain', file)
              .then((data) => historyDb.bulkAdd(data.map((pid) => ({ pid: Number(pid) }))))
              .then(() => location.reload())
              .catch((err) => alert(err?.message));
      });
      const importJsonBtn = pane.querySelector('#pdl-import-json-btn');
      importJsonBtn?.addEventListener('click', () => importJSON?.click());
      const importTxtBtn = pane.querySelector('#pdl-import-txt-btn');
      importTxtBtn?.addEventListener('click', () => importTxt?.click());
      const exportBtn = pane.querySelector('#pdl-export');
      exportBtn?.addEventListener('click', () => {
          historyDb.getAll().then((datas) => {
              const str = JSON.stringify(datas);
              const dlEle = document.createElement('a');
              dlEle.href = URL.createObjectURL(new Blob([str], { type: 'application/json' }));
              dlEle.download = 'Pixiv Downloader ' + new Date().toLocaleString() + '.json';
              dlEle.click();
              URL.revokeObjectURL(dlEle.href);
          });
      });
      const clearBtn = pane.querySelector('#pdl-clear-history');
      clearBtn?.addEventListener('click', () => {
          const isConfirm = confirm(t('text.confirm_clear_history'));
          if (!isConfirm)
              return;
          historyDb.clear().then(() => location.reload());
      });
      return {
          tab,
          pane
      };
  }

  var css = ".pdl-popup-button{background-color:rgba(0,150,250,.5);border:none;border-radius:50%;bottom:100px;color:#fff;cursor:pointer;line-height:0;margin:0;opacity:.32;padding:12px;position:fixed;right:28px;transition:opacity .3s ease 0s;z-index:1}.pdl-popup-button:hover{opacity:1}.pdl-popup-button svg{fill:currentColor;height:24px;width:24px}";
  n(css,{});

  function showPopupBtn() {
      if (document.querySelector('.pdl-popup-button'))
          return;
      const btn = document.createElement('button');
      btn.className = 'pdl-popup-button';
      btn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z" /></svg>`;
      btn.addEventListener('click', () => {
          showSettings();
      });
      document.body.appendChild(btn);
  }
  function removePopupBtn() {
      document.querySelector('button.pdl-popup-button')?.remove();
  }

  function createTabOthers() {
      const tabHtml = `<div class="pdl-tab-item">${t('text.tab_title_others')}</div>`;
      const paneHtml = `
<div class="pdl-tab-pane">
  <div id="pdl-setting-others">
    <div>
      <label class="pdl-options">
        <input id="pdl-options-bundle-illusts" type="checkbox" class="pdl-checkbox" />
        <span>${t('text.option_bundle_illusts')}</span>
      </label>
    </div>
    <hr />
    <div>
      <label class="pdl-options">
        <input id="pdl-options-bundle-manga" type="checkbox" class="pdl-checkbox" />
        <span>${t('text.option_bundle_manga')}</span>
      </label>
    </div>
    <hr />
    <div>
      <label class="pdl-options">
        <input id="pdl-options-add-bookmark" type="checkbox" class="pdl-checkbox" />
        <span>${t('text.option_add_bookmark')}</span>
      </label>
    </div>
    <hr />
    <div>
      <label class="pdl-options sub-option">
        <input id="pdl-options-add-bookmark-tags" type="checkbox" class="pdl-checkbox" />
        <span>${t('text.option_add_bookmark_with_tags')}</span>
      </label>
    </div>
    <hr class="sub" />
    <div>
      <label class="pdl-options sub-option">
        <input id="pdl-options-add-bookmark-private-r18" type="checkbox" class="pdl-checkbox" />
        <span>${t('text.option_add_bookmark_private_r18')}</span>
      </label>
    </div>
    <hr />
    <div>
      <label class="pdl-options">
        <input id="pdl-options-show-popup-button" type="checkbox" class="pdl-checkbox" />
        <span>${t('text.option_show_popup_button')}</span>
      </label>
    </div>
  </div>
</div>`;
      const tab = stringToFragment(tabHtml);
      const pane = stringToFragment(paneHtml);
      [
          { selector: '#pdl-options-bundle-illusts', settingKey: 'bundleIllusts' },
          { selector: '#pdl-options-bundle-manga', settingKey: 'bundleManga' },
          { selector: '#pdl-options-add-bookmark', settingKey: 'addBookmark' },
          {
              selector: '#pdl-options-add-bookmark-tags',
              settingKey: 'addBookmarkWithTags'
          },
          {
              selector: '#pdl-options-add-bookmark-private-r18',
              settingKey: 'privateR18'
          },
          { selector: '#pdl-options-show-popup-button', settingKey: 'showPopupButton' }
      ].forEach(({ selector, settingKey }) => {
          const optionEl = pane.querySelector(selector);
          if (!optionEl)
              return;
          optionEl.checked = config.get(settingKey);
          optionEl.addEventListener('change', (ev) => {
              config.set(settingKey, ev.currentTarget.checked);
          });
      });
      pane.querySelector('#pdl-options-show-popup-button').addEventListener('change', (ev) => {
          if (ev.currentTarget.checked) {
              showPopupBtn();
          }
          else {
              removePopupBtn();
          }
      });
      return {
          tab,
          pane
      };
  }

  function createTabFeedback() {
      const tabHtml = `<div class="pdl-tab-item">${t('text.tab_title_feedback')}</div>`;
      const paneHtml = `
<div class="pdl-tab-pane">
  <div id="pdl-setting-donate">
    ${creditCode}
    <p>如果脚本有帮助到你,欢迎扫码请我喝杯可乐 ^_^</p>
    <p>
      <a
        target="_blank"
        style="color: #0096fa; text-decoration: underline"
        href="https://greasyfork.org/zh-CN/scripts/432150-pixiv-downloader/feedback"
        >${t('text.feedback')}</a
      >
    </p>
  </div>
</div>`;
      return {
          tab: stringToFragment(tabHtml),
          pane: stringToFragment(paneHtml)
      };
  }

  function showSettings() {
      if (document.querySelector('.pdl-modal'))
          return;
      const contentHtml = `
<div>
  <div class="pdl-tabs-nav">
    <div class="pdl-tabs__active-bar"></div>
  </div>
  <div class="pdl-tabs-content"></div>
</div>`;
      const modal = createModal({
          content: stringToFragment(contentHtml)
      });
      const tabsNav = modal.querySelector('.pdl-tabs-nav');
      const tabContent = modal.querySelector('.pdl-tabs-content');
      [
          createTabFilename(),
          createTabUgoira(),
          createTabHistory(),
          createTabAdjustButtonPosition(),
          createTabOthers(),
          createTabFeedback()
      ].forEach(({ tab, pane }) => {
          tabsNav.appendChild(tab);
          tabContent.appendChild(pane);
      });
      const panes = Array.from(tabContent.querySelectorAll('.pdl-tab-pane'));
      panes.forEach((el) => {
          el.style.setProperty('display', 'none');
      });
      const activeBar = tabsNav.querySelector('.pdl-tabs__active-bar');
      const tabs = Array.from(modal.querySelectorAll('.pdl-tabs-nav .pdl-tab-item'));
      tabs.forEach((el) => {
          el.addEventListener('click', (ev) => {
              const tab = ev.currentTarget;
              if (!tab)
                  return;
              tabs.forEach((tab) => tab.classList.remove('active'));
              tab.classList.add('active');
              activeBar.style.width = getComputedStyle(tab).width;
              activeBar.style.transform = `translateX(${tab.offsetLeft + parseFloat(getComputedStyle(tab).paddingLeft)}px)`;
              panes.forEach((pane) => pane.style.setProperty('display', 'none'));
              panes[tabs.indexOf(tab)].style.removeProperty('display');
          });
      });
      tabs[0].classList.add('active');
      panes[0].style.removeProperty('display');
      document.body.appendChild(modal);
      activeBar.style.width = getComputedStyle(tabs[0]).width;
      activeBar.style.transform = `translateX(${tabs[0].offsetLeft + parseFloat(getComputedStyle(tabs[0]).paddingLeft)}px)`;
  }

  GM_registerMenuCommand(t('text.gm_menu'), showSettings, 's');
  if (config.get('showMsg')) {
      showUpgradeMsg();
      config.set('showMsg', false);
  }
  if (config.get('showPopupButton')) {
      showPopupBtn();
  }
  ['pdl-btn-self-bookmark-left', 'pdl-btn-self-bookmark-top', 'pdl-btn-left', 'pdl-btn-top'].forEach((key) => {
      let val;
      if ((val = config.get(key)) !== undefined) {
          document.documentElement.style.setProperty('--' + key, val);
      }
  });
  new MutationObserver(observerCallback).observe(document.body, {
      childList: true,
      subtree: true
  });
  document.addEventListener('keydown', (e) => {
      if (e.ctrlKey && e.key === 'q') {
          const pdlMainBtn = document.querySelector('.pdl-btn-main');
          if (pdlMainBtn) {
              e.preventDefault();
              if (!e.repeat) {
                  pdlMainBtn.dispatchEvent(new MouseEvent('click'));
              }
          }
      }
  });

})(workerChunk, GIF, JSZip, dayjs, Dexie);