Pixiv Downloader

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 | 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.

As of 2022-10-24. See the latest version.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Pixiv Downloader
// @namespace    https://greasyfork.org/zh-CN/scripts/432150
// @version      0.5.5
// @description:en  Download the original images of Pixiv pages with one click. Supports:multiple illustrations, ugoira(animation), and batch downloads of artists' work. Ugoira support format conversion: Gif | Apng | 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 | Webm。下载的图片将保存到以画师名命名的单独文件夹(需要调整tampermonkey“下载”设置为“浏览器API”)。保留已下载图片的记录。
// @description:zh-TW  一鍵下載Pixiv各頁面原圖。支持多圖下載,動圖下載,畫師作品批次下載。動圖支持格式轉換:Gif | Apng | Webm。下載的圖片將保存到以畫師名命名的單獨文件夾(需要調整tampermonkey“下載”設置為“瀏覽器API”)。保留已下載圖片的紀錄。
// @author       ruaruarua
// @match        https://www.pixiv.net/*
// @icon         https://www.pixiv.net/favicon.ico
// @noframes
// @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
// ==/UserScript==
(function () {
  'use strict';

  const style = `
@property --pdl-progress {
  syntax: '<percentage>';
  inherits: true;
  initial-value: 0%;
}
@keyframes pdl_loading {
  100% {
    transform: translate(-50%, -50%) rotate(360deg);
  }
}
.pdl-btn {
  position: relative;
  border-top-right-radius: 8px;
  background: no-repeat center/85%;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E %3Cpath fill='%233C3C3C' d='M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm-32-316v116h-67c-10.7 0-16 12.9-8.5 20.5l99 99c4.7 4.7 12.3 4.7 17 0l99-99c7.6-7.6 2.2-20.5-8.5-20.5h-67V140c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12z'%3E%3C/path%3E %3C/svg%3E");
  color: #01b468;
  display: inline-block;
  font-size: 13px;
  font-weight: bold;
  height: 32px;
  line-height: 32px;
  margin: 0;
  overflow: hidden;
  padding: 0;
  border: none;
  text-decoration: none!important;
  text-align: center;
  text-overflow: ellipsis;
  user-select: none;
  white-space: nowrap;
  width: 32px;
  z-index: 1;
  cursor: pointer;
}
.pdl-btn-main {
  margin: 0 0 0 10px;
}
.pdl-btn-sub {
  bottom: 0;
  background-color: rgba(255, 255, 255, .5);
  left: 0;
  position: absolute;
}
.pdl-btn-sub.artworks{
  position: sticky;
  top: 40px;
  border-radius: 4px;
}
.pdl-btn-sub.presentation{
  position: fixed;
  top: 50px;
  right: 16px;
  border-radius: 8px;
  left: auto;
}
.pdl-btn-sub-bookmark.pdl-btn-sub-bookmark {
  left: auto;
  right: 0;
  bottom: 34px;
  border-radius: 8px;
  border-top-right-radius: 0px;
  border-bottom-right-radius: 0px;
}
.pdl-error {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E %3Cpath fill='%23EA0000' d='M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm101.8-262.2L295.6 256l62.2 62.2c4.7 4.7 4.7 12.3 0 17l-22.6 22.6c-4.7 4.7-12.3 4.7-17 0L256 295.6l-62.2 62.2c-4.7 4.7-12.3 4.7-17 0l-22.6-22.6c-4.7-4.7-4.7-12.3 0-17l62.2-62.2-62.2-62.2c-4.7-4.7-4.7-12.3 0-17l22.6-22.6c4.7-4.7 12.3-4.7 17 0l62.2 62.2 62.2-62.2c4.7-4.7 12.3-4.7 17 0l22.6 22.6c4.7 4.7 4.7 12.3 0 17z'%3E%3C/path%3E %3C/svg%3E");
}
.pdl-complete {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E %3Cpath fill='%2301B468' d='M256 8C119.033 8 8 119.033 8 256s111.033 248 248 248 248-111.033 248-248S392.967 8 256 8zm0 48c110.532 0 200 89.451 200 200 0 110.532-89.451 200-200 200-110.532 0-200-89.451-200-200 0-110.532 89.451-200 200-200m140.204 130.267l-22.536-22.718c-4.667-4.705-12.265-4.736-16.97-.068L215.346 303.697l-59.792-60.277c-4.667-4.705-12.265-4.736-16.97-.069l-22.719 22.536c-4.705 4.667-4.736 12.265-.068 16.971l90.781 91.516c4.667 4.705 12.265 4.736 16.97.068l172.589-171.204c4.704-4.668 4.734-12.266.067-16.971z'%3E%3C/path%3E %3C/svg%3E");
}
.pdl-progress {
  background-image: none;
  cursor: default;
}
.pdl-progress:after{
  content: '';
  display: inline-block;
  position: absolute;
  top: 50%;
  left: 50%;
  width: 27px;
  height: 27px;
  transform: translate(-50%, -50%);
  -webkit-mask: radial-gradient(transparent, transparent 54%, #000 57%, #000);
  mask: radial-gradient(transparent, transparent 54%, #000 57%, #000);
  border-radius: 50%;
}
.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-progress:empty:after {
  background: conic-gradient(#01B468 0, #01B468 25%, #01B46833 25%, #01B46833);
  animation: 1.5s infinite linear pdl_loading;
}
.pdl-nav-placeholder {
  flex-grow: 1;
  height: 42px;
  line-height: 42px;
  text-align: right;
  font-weight: bold;
  font-size: 16px;
  color: rgb(133, 133, 133);
  border-top: 4px solid transparent;
  cursor: default;
  white-space: nowrap;
}
.pdl-btn-all,
.pdl-stop {
  background-color: transparent;
  border: none;
}
.pdl-btn-all::before,
.pdl-stop::before {
  content: '';
  height: 24px;
  width: 24px;
  transition: background-image 0.2s ease 0s;
  background: no-repeat center/85%;
}
.pdl-btn-all::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E %3Cpath fill='%23858585' d='M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm-32-316v116h-67c-10.7 0-16 12.9-8.5 20.5l99 99c4.7 4.7 12.3 4.7 17 0l99-99c7.6-7.6 2.2-20.5-8.5-20.5h-67V140c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12z'%3E%3C/path%3E %3C/svg%3E");
}
.pdl-stop::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E %3Cpath fill='%23858585' d='M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm101.8-262.2L295.6 256l62.2 62.2c4.7 4.7 4.7 12.3 0 17l-22.6 22.6c-4.7 4.7-12.3 4.7-17 0L256 295.6l-62.2 62.2c-4.7 4.7-12.3 4.7-17 0l-22.6-22.6c-4.7-4.7-4.7-12.3 0-17l62.2-62.2-62.2-62.2c-4.7-4.7-4.7-12.3 0-17l22.6-22.6c4.7-4.7 12.3-4.7 17 0l62.2 62.2 62.2-62.2c4.7-4.7 12.3-4.7 17 0l22.6 22.6c4.7 4.7 4.7 12.3 0 17z'%3E%3C/path%3E %3C/svg%3E");
}
.pdl-btn-all:hover::before{
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E %3Cpath fill='%231F1F1F' d='M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm-32-316v116h-67c-10.7 0-16 12.9-8.5 20.5l99 99c4.7 4.7 12.3 4.7 17 0l99-99c7.6-7.6 2.2-20.5-8.5-20.5h-67V140c0-6.6-5.4-12-12-12h-40c-6.6 0-12 5.4-12 12z'%3E%3C/path%3E %3C/svg%3E");
}
.pdl-stop:hover::before {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E %3Cpath fill='%231F1F1F' d='M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm0 448c-110.5 0-200-89.5-200-200S145.5 56 256 56s200 89.5 200 200-89.5 200-200 200zm101.8-262.2L295.6 256l62.2 62.2c4.7 4.7 4.7 12.3 0 17l-22.6 22.6c-4.7 4.7-12.3 4.7-17 0L256 295.6l-62.2 62.2c-4.7 4.7-12.3 4.7-17 0l-22.6-22.6c-4.7-4.7-4.7-12.3 0-17l62.2-62.2-62.2-62.2c-4.7-4.7-4.7-12.3 0-17l22.6-22.6c4.7-4.7 12.3-4.7 17 0l62.2 62.2 62.2-62.2c4.7-4.7 12.3-4.7 17 0l22.6 22.6c4.7 4.7 4.7 12.3 0 17z'%3E%3C/path%3E %3C/svg%3E");
}
.pdl-hide {
  display: none!important;
}
.pdl-wrap {
  text-align: right;
  padding-right: 24px;
  font-weight: bold;
  font-size: 14px;
  line-height: 14px;
  color: rgb(133, 133, 133);
  transition: color 0.2s ease 0s;
}
.pdl-wrap:hover {
  color: rgb(31, 31, 31);
}
.pdl-wrap label {
  padding-left: 8px;
  cursor: pointer;
}
.pdl-wrap input {
  vertical-align: top;
  appearance: none;
  position: relative;
  box-sizing: border-box;
  width: 28px;
  border: 2px solid transparent;
  cursor: pointer;
  border-radius: 14px;
  height: 14px;
  background-color: rgba(133, 133, 133);
  transition: background-color 0.2s ease 0s, box-shadow 0.2s ease 0s;
}
.pdl-wrap input:hover {
  background-color: rgba(31, 31, 31);
}
.pdl-wrap input::after {
  content: "";
  position: absolute;
  display: block;
  top: 0px;
  left: 0px;
  width: 10px;
  height: 10px;
  transform: translateX(0px);
  background-color: rgb(255, 255, 255);
  border-radius: 10px;
  transition: transform 0.2s ease 0s;
}
.pdl-wrap input:checked {
  background-color: rgb(0, 150, 250);
}
.pdl-wrap input:checked::after {
  transform: translateX(14px);
}
.pdl-wrap-artworks {
  position: absolute;
  right: 8px;
  top: 0px;
  bottom: 0px;
  margin-top: 40px;
}
.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 {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  display: flex;
  z-index: 99;
  background-color: rgba(0, 0, 0, 0.32);
  user-select: none;
}
.pdl-dialog {
  position: relative;
  background-color: #fff;
  border-radius: 24px;
  margin: auto;
  padding: 20px 40px 30px 40px;
  max-width: 720px;
  font-size: 16px;
}
.pdl-dialog-header > h3 {
  font-weight: bold;
  font-size: 1.17em;
  margin: 1em 0;
}
.pdl-dialog p {
  margin: 1em 0px;
  overflow-wrap: break-word;
}
.pdl-dialog-close {
  position: absolute;
  top: 10px;
  right: 10px;
  margin: 0;
  padding: 0;
  width: 25px;
  height: 25px;
  border: none;
  cursor: pointer;
  border-radius: 50%;
  background-color: transparent;
  transform: rotate(45deg);
  transition: 0.25s background-color;
  background: linear-gradient(rgb(125, 125, 125) 0%, rgb(125, 125, 125) 100%) center/18px 2px no-repeat,
    linear-gradient(rgb(125, 125, 125) 0%, rgb(125, 125, 125) 100%) center/2px 18px no-repeat;
}
.pdl-dialog-close:hover {
  background-color: rgba(0, 0, 0, 0.05);
}
.pdl-dialog-content {
  user-select: text;
}`;
  function addStyle() {
    const sty = document.createElement("style");
    sty.innerHTML = style;
    document.head.appendChild(sty);
  }

  function debugLog(...msgs) {
  }

  const defaultSettings = {
    version: "0.5.5",
    ugoriaFormat: "zip",
    folderPattern: "{artist}",
    filenamePattern: "{artist}_{title}_{id}_p{page}",
    tagLang: "ja",
    showMsg: true,
    log: false,
  };
  function migraSettings(settings) {
    if (localStorage.pdlFormat) {
      settings.ugoriaFormat = localStorage.pdlFormat;
      localStorage.removeItem("pdlFormat");
    }
    if (localStorage.pdlFilename) {
      settings.filenamePattern = localStorage.pdlFilename.replace("{author}", "{artist}");
      localStorage.removeItem("pdlFilename");
    }
  }
  function getSettings() {
    let settings;
    if (!localStorage.pdlSetting) {
      settings = defaultSettings;
      migraSettings(settings);
      saveSettings(settings);
    } else {
      settings = JSON.parse(localStorage.pdlSetting);
      if (settings.version !== defaultSettings.version) {
        settings.version = defaultSettings.version;
        settings.showMsg = true;
        for (const key in defaultSettings) {
          if (!(key in settings)) {
            settings[key] = defaultSettings[key];
          }
        }
        saveSettings(settings);
      }
    }
    return settings;
  }
  function saveSettings(settingObj) {
    settingObj = settingObj || settings;
    localStorage.pdlSetting = JSON.stringify(settingObj);
  }
  function upgradeSettings(key, value) {
    if (key in settings) {
      if (settings[key] === value) return;
      settings[key] = value;
      saveSettings();
    }
  }
  const settings = getSettings();
  function createSetFormatFn(format) {
    return () => {
      if (settings.ugoriaFormat !== format) {
        upgradeSettings("ugoriaFormat", format);
      }
    };
  }

  const regexp = {
    artworksPage: /artworks\/(\d+)$/,
    userPage: /users\/(\d+)/,
    ppSearchPage: /\/tags\/.*\/(artworks|illustrations|manga)/,
    bookmarkPage: /users\/\d+\/bookmarks\/artworks/,
    suscribePage: /bookmark_new_illust/,
    activityHref: /illust_id=(\d+)/,
    originSrcPageNum: /(?<=_p)\d+/,
  };
  const artworkType = {
    ILLUSTS: 0,
    MANGA: 1,
    UGOIRA: 2,
  };
  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 text = {
    upgradeMsgTitle: `<h3>Pixiv Downloader ${settings.version}</h3>`,
    upgradeMsgContent: `<p>1. 现在支持在文件名中加入标签了,请使用 {tags} 来分辨AI作品吧。搜索想看的图也会更方便。</p><p>2. 标签支持选择语言翻译。但具体取决于Pixiv的数据,没有提供对应语言翻译的标签可能是其他语言。</p><p>请注意:FireFox浏览器 Tampermonkey 升级4.18.0后,需转换格式的动图无法保存到画师目录,请理解。</p>`,
    modalCreditFooter: `<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: rgb(0, 0, 238); text-decoration: underline">脚本还行?请我喝杯可乐吧!</summary>
  <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+umnbk1dfPzxx3rOk08+6dZkDtorBoM+A5xbJpV7JByiPqi1+fLLL6fU2+obcKx6JSzcAjgWGRQGdvr06UlXgZ8Y+NtfFwQcntapKRaRI1aie6QurrvuOj2Pcs0117i1tcEg9evXT88R68qtrYujjz5az0HRZkJkA4wL3CZcb+DAgSmDzfRfly5d9Pw999yzjrGDGuHtZxR6FPv6Qr0RFlzl/vvvVw/4euutp51CWX/99bVu/Pjxah1mAjgbXE6UXC1XXHFF6KIGvPbWn7XTTju5tbUBMW+88cZ6Dt73MO7x8MMPKzFw7ziZE6mAxSgi2nn77bcjORA6IgQv6oOW8847L9lGnnvixIka5yQqYPuZCcfz3n333VlPgjioF8KCqE488UQ1yxmsoIKjcezYsaEEkQrMTBuchtuEKf4MWJ8+ffQ8wjuk4gRh11131XNoc1AICOLDAYkzkxAKf+cCccT/PffcoxOI9jEx4UwAg+Tss89OOm2DCu6ZSy+9NJaozQb1QljEvNBpmF277LKLM2HCBEcUbc1UuOOOO3TWYw3SWRCXv2MhTEQmeVVRIH/KdiDXCcMJJ5yQPI+2BAHvPMchrKBlYLkiJIAYw7cWR0/6/PPP1b9F2+C89957r3vEce68804VyxzDqkZPxdf2wQcf6HnUWW6NHpdPz3zeCeutt97SGSSWn3POOedoqokfyP4zzzxTCYuO8TobETFXX321cpn99tsvMjUFztKtWzftOJTgr7/+2j1SGyjbEDrnocwHEQmDwbH77rsvp0TkB4P717/+VScXYaooMLG22morbTflggsuSIpgOHb79u21HukQxGXh4qNGjdJnp6+feeYZ90jukXfC2n///fVhIYoovxQEs+WWW+q5WHh2MGHvgwYN0npEFwp31ExD3+BcyujRo93a2hCT3llnnXX0HJT+oDgj3AOdLxe6UxQgcjg5bbnkkkvc2mCghNv46IABA2q121q7iPCojAw44/bbb6/nDh48OG8iMa+EhQhBcYT9ouhaYqFDCFs8+OCDSUWSY6eddprOJBL46ACLJ554wmnXrp12BmIgKigL6+/YsaOeS/JdkBjj2tYC6969u5rtuQTPgni76aabnIsvvtj57rvv3CO1AQFsuOGG2g7SoVHao4D+dfvtt2vA25uWzb1IxaHvUC3sZCCdCIMJkYiVafsfAkb1IGAdxtWzhdwrf4SFlcMMo7z66qturePceuutTrNmzVTZJhZmgV4EEdLZX331lVubEBdXXnmlDgAF300YUKSJO8IFYPnoUH7dhQEaMmRIMqMCrmE7PR0Q+8O1EfRbYpLoZ3BZxGmQ/mSfl2fCH5Yqj8rex3+tuXPn6gTmfvjfLCAqng+F3VuP8k99r169Yq0byAQQVt487zbGJ/eBePU7YMMNmV0aE/RuoiEzzf1WG5x7xhlnmIMOOkhz0v/85z+7R+qCHHEbNxQCMo899phmTXjBsvjttttOc684hrc77N5heOihh7Q95JHZLFIvyEiQCaLrFMmqEEJ3jyRApECIT6ML9ANtpu1BIDfrueeeS7bRfy3bz/SxEJ1+B6xMsv3sXUyb7rNmDGlQxhwLvQmrLQhE9FG6mbU4Jr26EfqC15GJrB82bJhyGnSAIH2MGY0Ca2duGGjPzjvvrJyA602ePNk9shrc34pXIdRY2RReYFDAdfEVhek0tCNsYxE4NeKP+2OtBel5cNbLL79cuRoqQFiuGn2HaIcD+732WJFED+gzCmOAcs+YbLHFFrVUDgvOoz+ycajKNTIXhVgiuA8OPfTQ0Ie2IgxHY5QO8c9//jO5iIFdW3i4bADRwu65HjqbX8xgFDCgHIfArC8oLvBfCTdUq9fqNOlg6NChem+K12XgBVaiTWfu1KlTpG6JbsV5iET6Mgy011rOGDpBbcdyx92CQzVu4qUfWREWpisNhCvgUkCx9DeUAcSJx3kQznvvveceWQ3ypNZdd12dcXRgutwjDFhO3Jfr4o32gyxRe/y2225za+MD4s9kAjAhbRYHHC/MMkNXpG2c95e//CVSB+Ma1qpGd33ttdfcI6vB5EHJ55pw2zDnMD5G67o49dRT3dr0kBVhwWZRtLFGaASNxfloRZztdIKkmPc8EIo8pjFugzFjxqgSDavnGGIzlWWUDlCurbd/9913rxN/gwMgEuzABSGKG2XCqRBvpGJzT4jr8ccfd4/UhSUULNco6832M8+DQk5fMtFF/1PuT0HNKC8v1+sxib3KvB+I9iOPPFJVklS+tTBkRVhg1qxZGiuzDkcKK2fGjRuXtF6Q6zg9Ca7C3ex53oJPhZkSZD1lAjobcxuuRUfjR/MTFoNswzvMUL+uRGQAfQQrystVaCOW3imnnJK2CBVFPKlbsbI5ijvDdYh7wtHjgHZBCGJUJCe7vxBj5JxU/YwbKM7qpzBkRVgMHgUWjb6BGLMPwEJOq4Db8xhYTHs8zdtuu62zww476KyaNGmSPgjn5Ar2WiwwIPCK+yLo+taxSPHOYgiJFGk4GlyZkJIFDlbEOoOHcg2BesHAMeH89+OaPDuEznW9ky8I/N5/7ShwPoW+xKuONGAc6OfDDz9cU7m9/qx8Qu6RHcfygswBvOTIcYKhYbpDYwLOQ8tF4by20xkAVrpQj0IM97J46qmnkusa0UHQIy2YONQj+ukPLyBIxBDHcd6y6npNBYSVMz8W6/3YEQWfCxmaIh61njVud955p5HB0r8bAiKOjYgUXYfoxSabbKJ7OwAhnqRPCh8XPighOv2tTBKtB8uWLUtmqLL6hnOA6Fzm1ltv1e8iVutkggrRJffoEp3OiKWs3+sTCxcuNP/4xz/Uh5Z35Ipj2dnuBWwZ5RPFHo97QwGzGW6BCEM8WmAZ2cAuFhoGCeBZTj/9dBV3KL1wKft8N998sz4Pv7nhhhuSSjz6ls08QPx4fXH49KzrAA7pbUN9ggwI2o5Rw8a8+UJOOZboDu631aBOxIYWy8HSBVxERJaZOHFich1fupBO1PWKcBUv58Tzzzo82sl36XSt52/pfOVG1Nl6AAezx+E6lmNNmzZNPd88p+hSetxCLDYjirpywvPOO083XWsIcH/WVvKsoue5tflB3pd/CRfQDTZYrAmBEUZ59dVXVQSxbIpBigJiVPQfHdzJkyfrAtd0wY54hEZE/9Ml9JYYAEvPWGQh5nWtZVxz5swxzz//vIpElqLRdgCBIu7FpDeih2nIBPCMr7zyihIQCza8i1h5ZlQCjrGki9BUQ4BJxZI1Fu1uttlmbm3uIUwr3vKvVOZpOsCRinkv+k2sICieZEx0RBnOxUwR5ndCxIUdoz7o2cPqo67FsVz2Y0PBa6yEQZ41tShkI1Z2BmbZuVeJzRTsBcoMRrn1K7hB2GuvvXRp10svvaTB3UwRxvrhmGHHqPdyN4uw+qhrcSzoN00BGCMywZVzY6RhAAj9uEdDkIpj4c+R0yI3ImM2xgWBz4ceekgDsXGoPwjc77dUGhKkPBET9ubS9+/fP9LHJm1O7cciRIMjU/SNJJvHYUhiHp7hTKLgiIRMO4zf8fvfUmlIEKYTTpskKpyuXr9eECCsSOVdzqmjXFN34oknmrvuukutIPKNjjjiCM0pItcKq8j/m7hAuUXZR/ENe0kS96f8lhAmQul/Xs8iUkDfduE1GDIBfi728kL1wcfHfTFszj//fLUoMaLYyYY8ryjI+GS2dwMhHGKCXMMWUmPI7iQ9OFOwrg6Wy7VZphQketc0jhXnecLAWgF8ZkQCiAJwrTgIO48FL/ju8O15Y4Xki5EqBOLcQ87JzI8Fd3rzzTd11znMboCviN312LFu7ty5WpcuRNQq18JDfvnll5vDDz9clf01DdL37rfa39MFv8WFQDQgHcMKicJvRcetdf+uXbvqGODn8nJJ/rbjHFsaCXVl7HmHeslKIDYIl8FLjXfbG7TFAw21x6F0YnRwPa+iiEwn9cbmI3GdoFm9JpcwcIwsCBZuBGWDekG/UcjiQCE/5JBDNCrCekw7Nnzi3onzSr0oyHVyG4QmDZjovm0oYQNyobAsUy04tWDZFYl5drtECnlchE+4LsXf8WtyCfOLxYEdB0BYiUxTxsOuGqeQiMl9cgm5b+4Iyw66Fyz7ovFYFXGT+LgGnQmns2sKKaxRpJ7j/s7PZeEeQfUNUWhLNoQFcOmMGzdOtx4gXVtEmfYnn7vttlvGyXxRkDHK745+ONZ4Lx8ymh2ACWWgCxBWwAIhrIBcD7N6iBPy9gfOP+WUU3SfdGl3VnpJKnDt2HpEPYD20D/ptgldVcSkZlywU7QQqF6DscDJecIJJ2hMk9hhriFtzv+OfswYrzONhDoi/cweXnvLnlbkKsGJwkAWqkW6HCtzDiS/q1klpTLgWP2WqL4Jw4033phMzabwna2Xpk6dqmIxn5D21rUKpdLMnz/f/St74P9gLZ8Ff2PJENYhAMwbGwjbXHLJJWrdAGmbflqk8ptEISvuY4elnuB/7mxAaAkuxSf7u+KPuuWWW4zoWLFf2ulHUPuQPGR81IEQUpJjsfSH/HCsO5TnkSNHqnwmryqXIJzDEiiyM2Xg7fApJwtaB+hFuhwru8K96u9+Ydw1DFjK+JuCOBpGEH3JHveZ7j0WBO7JIhCW+rM2EQuexReso7SAY9UiLHLD7W52tuAwI7aXa9BAQgM4Re0eBhQWMEShfgmrcRQ/ICYWZrCmkxhuPhRwwBiRwOhVRQjtwXi8NMKCWetABXUIi4dgcQPuAbuAlJJqH/VsgP6FW+L888/XZUreFdJwNlH+nTfeeMOt+Z2wRKSpNUcqEdY240O2az7A/hIiNtUjb4HfUsSr3pdjLGfzv8WjDmF5gVh87rnnMl4NmwvYvUVpvMVvnbDsNpYU0VdVZWGFcz7AUjLuwzI5LyAumE2YbxLCKho1atQFoues1q5dkDFJMHLdddd1a+ofdvHBWWedpQHu+gDKfqYlX/BeG2WcV+rheqFfLr74Yg1A5+P+bCxCYJut0vlusfbaa2tKt82qDcCqenszxbfffmtEXus7cPClACFu/QzrFHxeIir1Aayvi9/Y32UK+2vvXblmJv4iP6Q/3W+5g9fPhwVG/JQ+pF/CEgu94NlEnVCfIT6suOB33A+rPp1+kd8trRfCEr1JXQoEp8l1J/cc5xyvhyOXPc1Ga4kDrht0rpiiUmTAalYf41wyVVmmhSPXO2Ac81/L2w7Meghq+PDhOptzTVxewvKC+8fpO17/izuHxR/k66dDXJlA2rU0uMU5Bg9vBwofGbPniiuu0L2pbMcsWLDAvP322xlnRgTBSwheFDAgvmO0A9FL+8hx+uyzz8z06dM1a4M2UViUAbfgO2/P4DhiyZ5HpCGX8BJvEGzf0Sbe++hdgeSFPY9P+z3fqBeOxQxGDLIRGoslCdEgEh9//HGzzjrraIeMGDHCvPjii7qChNxqVuPwySoZL1J1dqagwy2h3HTTTZpSUlpSagqLCk1hgZRCGRQpYjvo89AGUaz1O8mNJD+i93Tr1i1nHMs+pxXR/G0Jg1VB9BeFkA0hHPqQCetdrgZ4lhdeeEH7mpVIYRwwV5B21o8o9IJOZ3azitgunWLGsWTK/95BiIz3+9FhiE5A59oOzyUYMJZ2jRs3TnfsIwpguaxTzf3kvgWO1BXrM9iBBhDWcccdZ0aOHKmRhVwRloWXEPCe83Ywoha//PKLW5vALrvsom9Es/3aUICw6kUUekEnYeF5Hx69i2X5EBAWjg0BEYRmdQ5rAusDtA2FGEsIwiGURCkpKzGlzcrke4mGQyAerXfbybNQ5yW2fIFkStZlWqKiDeisRx99tK6kykdQORPUO8cKA7McKxCxyMphcq9ZAAo3YJb+0X2vcz45FiutH3roYfPIow+br0WHKi0tc48m2oeIsZmacDPqsFoRfyeddJI54IADIsWMbTf3Sgfea7IqnMxdOD5xP3LQt9lmGyWodA2hfAGOpYQlnKH8wgsvVKUUC4IFEoAAI0vH6RAsJWYlM4TBZmbzcPl8EKwt7m05A+BvO0C5BgM4ZcpzZuzY682cb78xRYWY2QlCIGi+1VZbqdKOmKTO1tNfo0aNUr0xCrbd6faZn1jRr0gVziY4nwq01bYTyYFhw8TjO/dFV7Ppyh988IG59NJL1beF5BFaSaTNkPpLiEAupPtJ4e0lIMrWPgQZOcb+6WzPw65xbMPDjm9kevo9w4AwDd5gPPd4aYVg1WOeCnHPsR7pXBeuzersQYMGO336bKDP2r37OhrL5JlZvY2neyP5m33S+/btp3t6EoTlGUHQdbMpjEOcfkkF0pdYqkcCZdj1OM7e9GzvJJxZ60hAYMdDxp4QDuk3ZKCSDm1jiKSmC7E5a6+9ti6mkesnQjqswmBzLgKadscVHoj9x4XlM80CC7sTB2U+EOPjJrYRNIo1iF6Qo024iMal03GcGzQAuShcm8Ar+6uutVZ7JZ6BA/dTwiI9muwLCIy3aXXp3MVp224tp43UswEbgeGga+aiZAN2BCT2y9oB0cU0yYB9vILAjkAiHXQLBLs9JWEb/vaPPVt9WsIiCG430oOWIKxIHQufDoozPh18NBSheGX/cj3N6kTh9rNqdobBWYj+YYHoxA/Ep9xT91qHhY4ePVp1BsC1zznnHDX7cTNgFaIsH3roocp6Afel5AOwfgrPjIXYv/8AEcPF5pJLLhXrdBszc+YsscgeN7379DEbbriBaVHeQp2OGB/sP+91qoaBtlsRExfe/sVtgO6J8o5IZIwIt+Bm6N69u3vWatAuDCAvcOWQXeoHm6CQWUoYD1eQ3TsMp/H999+fHA+2OmCPLxtB8UOeMX4GKRyMvB5YKWyfV3kwwyl+2MR99ulk91/y1WGvdvbxG1ItpMOU8i14vQnt8hdmnAW/9c/oXBauz7ORUcGMRKwjIsjLYoXLpElP6r5anEtfIO6pz2e7vPCnNdkiBo57Rm3Qv0Igym0JYLM6x/umEC+4F/vCs0rHP66IRmjAC/85FlKfu8UUftAI5DoJYIhLf6NYbEmaLMdsA8moYBWJcLVanebdtI1z/R2f68I9yBVj52UL2g/BkdYDwTHJSKJjEzXOz2e7vDj22GNr9Q3qBmLIvzWlBWPAtpSoHkwExsO2N1+Qa+d3MUVcSFuS4kE6Utk7bgdEJ+IFJ6m1gDiXkm8gfnDYYgnh6uCeDzwwzgwdOkSPsfWkjfJ7258PeEUhHnYWC+M769Kli4on1IZ07p/v9sr169/zni3oFEoYctlpDCjuBfZGwJWAzgjBE/bBKUm2QD4HyMJLWE0BayRh5RLch0HFyBArSTmY6Clmzz33VG5VX+34nbBiQHQVLcx+0VWSn3i0bYHd45wV/UstUbZXJNQD6pOwLMfCInrkkUe0TbRj6NCh5sADD8x4tUu68BIWFiEE3rlzZxWHhJ9wVOJExvNundh84tSuD47qR84JC4Ih/QXPNCID/YTBgDggFnQmvmMm48H1EhMuDM6H0IjG84m+RcHdQGAY1Cdh4WYhc4ClU7SfZyNhEZcJbcLVEie3ifZmM8BewmKRL9EQdE8KBARBEQ0hrMMr9ajjk3DPmWeemdRP6XtcOUQQvBkQjBt9znPlghCThCXfy1NdMFXnQCxnn322+kzgNBAEv5HrJ9NL7Cf1lLi45pprtINAur/NBDznO++8o23Ff0a7AUSF4swxJsbAgQPNzjvvrMTFuUH9Y9uazYB5CYsdeCZMmOD+FQ2IDX2QFHPAPmZPPfWUxjUJ3QEmMz5H6zvEF0Z6DRyR52Iz4HQD2/LMCcKSL+XsEmw38YJImJ1QOE5Lsj+HDBlSi8r9INGMLYzgVn7QqcwuZg7FsmquR6P5zmwh9oiVA3u3qbcoydzfxgvzTVi0FQIitYedlO1sDwJcmb05xdxXCy1f7fISFuNBFqiVAvxtvzNu9L/l+JtvvrmKcUQm4HnQF8lzmzJlitaRgsNLPYPAfSFGlukzTkiWcePGaUYw3JoYKf2FtIF24JI4a6UuQViffPJJOWkX/IAf2xlqgUlNQzbccEO3pi4gyAsuuECzQvHYQiA8ELMAIkHeU4c+YIkKwuFvGkRhhkGA3o70I5+EZbkKxIKLgfYG3cuexycDChfbbbfd9Lks+J09L134fxvVHwBuafVWOBAF4iIqQL9aoJuRZLnrrrsmuRjjduqpp5qPP/44SZzQgAWrqImktGzZUrkfHBqvP4s4GG/G7I477lAOSJYH2RfC6RKed2J7BJrlOoGFhZE416Ig11HnGzEzmcn6nbibNFIdikKssZ1yUedxjGvlo3DtefPm6Tq5uPcBeLL5XdDxTIoQiRb7t0Xc/guDfSY/GCvGl4iKcDR9EwfbphPfxfFq7yuEp0vx8OITcbDXsjsBeoPQyrHkIcqR21A04oiCOQ17t7JWfp/xDEwXzD5mDso+XBRRg9wHtIOSD/B8KOtwUmKZce4DN0HJ5zeIAelP90jmsPe1/W05FvUo7l9++aXp16+fSgPUBrh8fQKOhQSiPbSRlJq7775b9TN0QOFi9e9uSAX0luuvv15zuWUGKatGzrOZLuBh4gx4phg/frym+CJG4tyHQaed6GSIz3xMPq8oRE/C68+EZ7IdfPDBmm/vFcMNDem3+k9NToVZs2bpBvVYXhgU6A1ePSFfgCCw9CBsBi0dwDHgriIa3Jr8gb6gTzAw6CN2NIZjeAGhc05Dot4IC2sFpTIVYO2kZcBW4QBXXXWVZidmg7gcDhYPkaRDyFwbyxGCRAzmg2N5wUZ0p512mvqiSG+BuyKWAAo71nufPn00BMUCj4cffjjQUs838ioK4QDobTgY+cRkJXUV/SAMDBQzEB0LPc8fYOV4XEJJB9yDJWoUcte99wi6n7dNDByWJA5JrN1cty/IKsTFgJqABU4fcQ7EjW7IXvkW9DmWGvlT9jo2Lw5LLx+Q58/Pjn7PPvus7p9ExiLWJlmJ3E8erNY7/jKBtDdpLeWycF32SSUtJl3wW3aBsa/sDbp+NiUuuDdWGTlXZLzS56J76V6wtl2kILEnP3u5X3LJJWoBC6dzr5C95QnkGrlPm2EpN1zJC2YKMwSF+MYbb1Sfj3fG+yFtc7/V5gyAY97juQJtnPTkk+app59WHw/3QCxyf45R+C69ZqTrTZVTY5xqKfwtBb2G/VZ5owN/5xLcG9Am2sD1bV0YSD1ipRN+Jl5/h3iHs7I+EwerF1izOLeJydpzgb2f/YwLOX9p6G4zFuledNKkSeqF5zc4VskEOOqoozSNmbRXYl12kMLAMQpeYgiRB0f3yivkfhXLl5v5IiYYDPQm66y17bFt5rOQIsdsQWzjPIyKTsSFv8+998UdIlxfnZhMAO4dBCYymRh4x23UgmfCIY3IpBB6A+iWOE4J96CmWFUFAwDXBvflemlgVSxRiJMTJ+B0z4sBwoCz7d5779WEfTJCyRDFQZoJeOsUjjc2ZbWQ9tYRFbkqlSKiEQs8byYlX23zYtttt9UFLjLQzuDBg9Wh6T8nCqghQlS6eyNikRU4pC4LMWjhhQQWvKZYJrTuBX/SSSfpixzijKX0Q+3UZDpGFG71wHr1IGS0zETVl0hzzTW4ryiUqqMwQBZ0XIcOHXQZmkW+Bs8W//X52xZvfapjuSxeDBw4UPUmSwiinCtxsHUkfWjbkw7EEHBIw2ZFjxd42e19KBD0+PHj3aMJiAFRZ6WW3L82YYkl5owYMUIVu0cffdStdXQzU3KrcdnbZUG5BB3CggmUfdY1WpDUzxIxOJ9FPgeRyRRU39DFC/Lu77vvPkdUjFqve2N8WOI1evTorF9ZYgE3ZEnXpptuqoyFezzyyCPuUUdz/uGgbKlO+M6iDmFhEYk+ow0dMmSI1jGQFRUVujUgaw75O9dgA1XW6nFf1jdGIZ+E1ViLH0wA4nZMeAZddKAkgYl+qGsfWSic7VjZsSf+i/WImPRakMSQuScLe2FKFvK72ltFEhbAmkBpJeJt37aO8oci7vcpZQIi6Dj5ePk2KTH4UlAMpT2qoLP9ITHK37Ea/j5HYWesyGY97LDDNGcMZRwl3Gbd4mAmxspYZgruy9hjkGBAYdVjAFiQ80/KNpYmhovn2OqtIhlYYNOEbQ5PrsHiR2J/WBy8b5pYFw/A/WV21mp4EDjPtvW3gjDLzwsIi9QX8qvI2uWllVtssUXWjCAMjAHXxoUBkXvHTY7VfxAaz/agQYN0ZrHiONVGGn40NsKyHZwO0v1NHMKygCkwQeEy3CPoPpm0OR3I9es/CE2yIHt9UtIlqsaKdAk900HFz4QD9thjj03uAuQH3IMwDsQYdR8kBqEz4ovptj8OckpYiNE4jUTM4lBcExDGFfIBUonI4iT3Ca7Pe4jQWdMF7RXLUrND2b4qL0AUCjFkDRyivDHhjDPOCLRkcgUsFWsteb83tKsgn/e3IJuTHXBk2LTgzyImi1M6XfTv31+vgaPVe49cQMYlu70bcGby4vF99tkn+bAUNpbIBOxNxbt0uGaYhzcXxNSYCDJO8YINSXBcQhC2v/E74nNKB7zejxeN3nPPPW6No+nVV111lboW6JdMkTVhsSEGr8OwD4iX/IQTTtCQQSYgbMB18M3gOwmClygaY8kHofpBfjoSgk3gbN/jU6Jv4oJz8Ul5f8M7ebgWu9KkWuMQBblm/LfYy/nut9XAB0V+PCBjgb2bSCumPgpcK+h6LEvCl8UqEpTQ3xEMEvv2339/8+ijj2pWAjor+2Clo+txLn3s/Q3LxQBrHGzgOlOkdDfIDNQ0WJyjQQSDQ5WEM6w9sgLiAKLCGmHPd3wuhxxySHITL/6mo8KuFUaUDQ3bJjtQfOaqnVHuBtwLJFTaTdKyBYtXWMZFFoQfrGEkAZPjUVkc8typE/2IgKMw8to3bzwoWxByIHJOO3jNbDqAfTe24hVdYfX+Y0El6LzGAFQTm7zJsrAoHUzanFoUwm6hYkxdZke2kPvqJ9yPBaGAUEE6gBs0tgJXsSWs3n8sqASdFwekFlaLupX4lH6m0gfq7HE+00lHhFPxShrys1B5kGRRSCkKyVVnn1A2wSAXPFXIJQy8fYIYFkRkCYpl2YQEWDyRLeQ5VCQgRjNtY2MGkQry6nnO/v37qxPUC1EQEp+Vy82qioVm1YpfTOWyn0xNNe9rdkxRSUtT2rKDKW3W1hRLKShupr8REtbfpQIOVfxn7FNKuAjdLgzCPOKFdHB8MlhxZ48XNAiud/PNN2vAkpUjOOVyqZzDBdlfgA1Zhw8froFZL3Ghz/GKEN6I4V/ahY6I0xGPNnqkBdz5/fffV+OELEwL6lnXhw7iXxTCwlUIgBid1RFpG/02Y8YM3eOA32QSGGZBMesHuRbxVl4e4MWqigXmp88nm4q575jlC6YbZ+UvcnN4E0LJ5V8Fxaa4RWdTttb6plXPvUz73nuZ4tL0FlTw/KnGDsKKZRVmSlSA1OKTTz5ZB4lBZDdiZl0csHSJVGcIOwoQ77XXXqs7AU+ePLmOyIZw2M8KTzOeaiuOAS9k4hhhEq8Xm5Ut1BMk9656oT1wbgoEZkG2BlYt9exhZe9BvxEfJUUbTsNayUxgNx0hH71lqwQx2FWMi2a9Yr58/Ejz/WuXmyVfv2mcFUuED5WYgsIyEavFUkq0SGNM5dIfzOJZU82cF880s6acZpb+9IWSHYINDpbgfKv7xwvuH5chxCKsdIlKlE/lEOxiwtZGpHMwS1kDx5q4OOv2kOeskeMabLoRBR6W+/DqD5Y5+R+eTSsQxeTQs8DAPg8ZAeTn0z6OzZw5U+sBxIAVBKeBsCyhsOLZ1vMbO0m4PvVMHF6U5CVuFpYSl2PBBZtneAk7LhA977z9jnle9BsbY61e9qOZPfUCM2PS0WbVohmmtEUHU9SstSkoEm4pBAWHqqpxzMqqai1VMi4FUl/SvI0pLW9rfv1qqvl84gFm3gd3moJVdu2hFap1kQ4dxCKsdEGHQxCwbMDCyquvvlo3T4taU+gFsUQWXtCJ5P2kwvHHH69KJcToFTWIJ3bjA+SUsce5BVs1MdgA0WYXbKCYwr0gGnLFEIe2U2mXFbMQkiUsBt6KUjiWl/vRF4haQKoQ7wrKBP027mc2ZzFKQaFZsWSucJxTzC+fPiL6luiVQlAgQbSJUlW5zJS27Wm6/+l8s96frzRt199XdC72KBMCk39l5e1N85JCM+/ta8yc10ebmiomA8+ZHiMJQl4Ii8FEMech2V8AgmKVTtzAM78jsYwVKWyfxMreVMCSYutGv9geM2aMcibAu429m94jmuE0AOK3viAIC0OAdthENwsIzV4fw8MSFsRvX3LAJmb0gQUGBaIWoO8hZjPhWoBfVS5faGZPOdlUfPu+KS5ra5zCZkoo/EMZr5GzqqtXiKgsNt33uMx02vSvpkOfAWa93S4wrXvsaqqEO+lbZrleYbkpKW1tFnw03nzz2hVSw++zR04IC7bP4NnOQuQhkthzi1egkcnIgHMOBIf7goJYsYPu7Wg7cHAQ69nPBIgg+0o6xCQ7oXgBZ4WAADvXWRGNTod4pE2IVa8FBvFCxIBzLGHRZvQrQB2rv71A/4IDcy0v4acLp3ql+fbN64Wo3jMl5WvJjWsPIb3I1WsqV5hW62xjWnfpZ37874Pm8ydHCrdaaTpscqj8pLm00aO3FhSJGG1nFvz7djP/v+NMgXC0bJE1YeGOgIAYNPQqBoOQACyfHWJIb0bn4u2qpLDaRZEsR+c7BIhC7PeLeDvfPxCcm8oIgNjZXAQrDQsNa9QfOfC+CgTl2oLfYsEBfuvV2XCVWMLCt+NtA8q5nQgYEd7JgkVKqgrqwUUXXZQWcSlnSXw1i4Wgfv7iGVPaMvFOxdVHErBXdZwaIbz2xhECWv7jf8yCjx80i6Y/b5p37Guate8hs2eFnGt/K5ar6F7NW3U0c9/9h1m+aLZb6796fGRFWKyoxb+F9YbCyiy1HY34YAAYJNwLI0aMUB0IpRjLCG6FuMCa4hjbB0Ew3sEIA0SK7sW+pGHnoyxjidIe8r/RrfxmvrXQEGPepEMmgrVE+Y03bublOP4kOTis3d3Z7mNlwXVwQ8C54uiMXnifcP7HE02BU2lqCspEZAUTJ+o377wuKipTAnOkD0plclQvX2AKy1qK+GxjatQVsRrco1rEorNqsfl55vNKkNkgY8JC/yEQyrZDDB6ciY6zs9mCWYrLAWWW3WPwN+EW4PfsJINegkhhw1V0jzgzGQJFN2J/TfScIEDoWG4An49dGOIFjj7ENi4HL/F4OZaa9x5OB0ey23AT1/RzTd4LDahHiQ8C4p+Vx8Rg48D2yK/zPjSLv3rVFJag8wnxJKp9gERExxLCKm3dVURnlalZ9av0a6EQS5VwJplcxeWmGiuxcqXUJQiMa2kpKjE/f/mcqVqxOFmXCVIusQ8D3Am2DmfCEhs7dqzOei9hocMMHz5cOxJfFk5SBhnvO6KBQSATEuLgXAaCzAY4TBT4PVYcyjhWpp8Y8WsdeeSRKqpoH2LZq6tZLoOlRgAcz7/3GhAZ+7tjMSLiyRywz8X14KxMKByxPIOXE/KdfqENXAdR7+eUqAhwaYgfP5nfix4EWvzjh3eYiu8+EI7TWgacfxC1o9ZctViANdWr5PsKU7VymSnvurVZZ4dTVdea/5/7jVNVYYqEU7Xtubtp3W1r0w7naMsupmL+Z/qbQghOrojFWbl0vilbq6cp75Sw4DMgrtWrdNyK2GBWw2Gw9EiZCcp8QKdCbMH+2SA1bMN9PPJwP1aZoK8ROojiXBCGHTg/hwRwSPxlnAeBwTWjrucHv0PMwUlps1fH4hhiEg6Mtef3meELQ3fEMoRgibH6l7OhGvAaOODdajwMENVKEWPTHx1qqhfPMUWldgWVcB2x8ApL25jmbbooR0KZb9ahr+m8zfGmebse5vuPJph5b44xJcVFprKmwHTa+gTTfoOBpqxNV1Mo5y+Y8az5+oVzTVmpcMHCRLRg1bIFpnXvP5veg+7Qe6cr1qSPMl9MQYeiEOPNDiIqRNVbb72lyu+wYcOSm4NhhUFkzGq7ERscCJHEzGaRBTHFKEAk3D+IqOAyiFgIAPcBg+gnKtqAbgeXDPLqcz4ikEnjJxyO8UxwQP8xgJ5ldSivn8wLDAWblkKohvOiQOsrF8811UvnSh+tTieCQzXr+kfT+6CJZoMhk80GQ5/Rsu6eo01hcXOxBseZH96+NsExC4pNUYFjfnjrWjN9Yn/zxcRBpmLRLNNxwwNEod9IRPdq46mwuMRULf5Kro+CnxkyJqxUYPAgHNg8+TuWCFD08ekgChhYwGBBXLgk4IRxdv7zA0Ki8JZ3dB9wzDHHqH/KC85BbBOuQcx5wzW5AFYjnIpnwU8GF/brYezGh74JmIBej38YVi37yTiiExV4JhNKeefNDjEt2vcyi2b80/z40QMi9u4zc1670syYNMLMff0qUyzEVFhULM9dJdytwrTrtYtp1WUTs+z7j8yyH7DiRbFvs45cbLUyX4jHvmKRqVq+yK1JH3kjLAsG0gvEAoSErhLE6TIF14TTodgjJhFhiGAbDLZAn8O/hShD1FklPVdgAkE4iGl0MZR02uMFInTkyJHqbIUrxnIcJ/vR8hA+xfIrFq4pX38UPeqbqRebb4WoFk0bb1b+MlvaUmCcQmmHcOUVi380HfodaNr3PcAs/1msYRR4wjhCWMWlLTzXF0hfJqKGtccuHeSNsAhvIBYYSILA1k81ePBgdZpCANZTjTjiHD6Z8Tatht/ce++9mjLL3lreMEkQEKOY+QAL1abaeoFCjw8M0Ea7zXeuwETCALHPQJwziAPDMXnZAr40uHVK1BljKshSTXDDwpLmpqRZay2kyJS139CUdugnivxSU1DWzvQYcJMQ1UHmuzeuFm40XwiyNGEh6jXIwNDLJJG4XSMkLHQQRA3EgTLtFTlYj15LjEAwSi4iA8XX6mN0PLsDIk5wTyBaooDVad0PKO92cL1ARFudho3L0t0h2QJxCyHbCWPBM2HV8vJOgGESJtqxjL2pOlEguFwoCrglpNVI9CH3JUyjnyLWSsrbmV4DbzI9B99lNjjwPrEIW5lZz55sKpctNMUlhICE27WQe4uyj9jzOi/4Pek0q42E9JFXUcgGH3AETHPCHXAlP9Av0LfgIhAC/ixLcKNGjUpuNc3udOhqYcBQsJ50fFaIwSAgduBkED7iyOu/Sge4Ogg833LLLW7NatB+tsmmvbzIyRufzATwjZIWHeS/lkm/U11In2m3FahOtfjr183MZ/9m2v5hB1Mx/1Mz+7nTTM2qJeoDSwi5QlNW3onGCrHNl4/VpFBTXWWKytsLcdXNe4+LvO/dQBAZFwLchsHErwWnwLsNR8LHhIhjluPwxM8FFyAcw443gAF64403dIaHAX8UCYVYaoRTCBuFAQ6C3mM5Y7pAdGAUQPToUxghdjJ4wX1Q4i3xBp0TBxAWsb3Zz55olsx8WdSjRBYGvqv1Btxo2vXc00yfdIxZ8t17ci/inRAOBLPQtOy2uan8VSxK0acKSsqlvsY4K381JW16mt5/ud9Urlxs/vfQX00ZUrEQK9cRQ2Gh6bTdiab7jmfpvdPlPtI/+d+7AecifilCKogg/DZwCrIhIR6ICmIiAwLdBLBjCi8BArgMMMmjiAoQZEanY79TXBdRwFLNlKgABGIzIbDqwkQd98F44PwoorIGDiEm/GdBIFGv7fp7m6qaKiUOMhjcI/o/3MhZschUr1go5WdTI9+LigrNsu/e1+Q+p3q5qVm+wFRjXRaVm7V3OMUUCxec/+G9ouRWqJIPOdaI9VjcqrPpsMEgZYCZTYV64Fh0GoVgMHlRcBXEHtYTSiued+KNWEjUsQc53mosN3532WWXmXPOOSelyMJNAQdBZwlaupRrMAkITdFm9KhU0YJUuP3221Wf5FnZfdn7vJaEqqsqzKcPDFACKSJnXYipx363mTY9djPfvD7aLP/hE9HDiA/S5wmvvCUM5WFCPM3ar286bnygad6ut1k04znz1T/PEQJM+AQ5t7JypWn1hx1Nr33HihGQGVnI/RvmXTpYf8xgxIQfODfPO+887Rx8PXjusx20dMG9QRSX4ZVqcFzENMYHEyNToBbwrBgD6GRwb68rhtZQ4E3ff3iP+f6ta01JaQvjVC0z7Tc/yqy9zfEarllNROFwCPmsWGyWzP1AXRNO5ZIEkeoz18jYrDK99rvFtFtvF/mL/K700WCEFQb0Md63jHhEV8IStEHd+gSZFliicA4SDv1gELAKMUYwTkgTyhZwaJzHcHC4oT++aLFSRNmMycea6oWfmQKx3ETNNq26bqOcKEEcQl7JCSGfLpVRhbW3aslcs2LBdP0slIOFbhiHE6ukrnW/Q0yPva7MmFuBRkVYiBP0MBvxv/zyy9WyAlGcIy4QlXfeeafqPYSY/I5TC9wRiGjcFhAWXno/GMA4bUoMdCLDA2OBuGVYvj/n0kbahVgKAyJt0eyXzcwnjjKlzRPLuKoqlwsnWmUcaRLpMom7EqTWHySBO6KosEh+wz0SmbaJs0TRX77QOCWtzSZHTjGlrbomfpshIKy8K+9xgPebmB5xNR6WiD8pLfrgMQYwDnAL4P4499xzNTkxDOSIQVxYpnwGIW6bOA8LlbRsAs24Qyyx+cG5NocNfTTMIEBrWkuswJ4Db9I3ZJCwV1ZSZsqatzTNm7U0zZq3ksJba/mUv8vlU0oZn/J3aVlz4Ybe9O0asRiXmpK2PUyfg+5XovJ7yjJBoyAsMkhJIRbuqdwCIstGZ/EDSws/lw2thA0uwMqziPKbxQX6E4AD0oagoLcXED3qAItDrA/PCxybkFfHjf5ieg64yRQ0byNGnVh6Ul0jYrFGh7RIzqAUu5+I1UThu8vLDKnKlUu+N2UdN5Jr3Whad95UjwQL4fTQ4IRFh+NasLnvOEtxhuYSrKbBlIegcDNEbaDh9e7HCrWkABEGK3bRo8LcCRbkaBHuIgOEpWpMtrpA3NWYdmIN9trvH6a02w5m+a/zVSRCesKOEp8BJcGpakSB/9WsWlFhWvUbYnrvf5dp0amvHOZYbtDghEX+N555wNvqsQijdAzA4OCURGxZjhAFjAFihABlOyroi1UGMWA0RDlZ44IoAIsoAOnYqTgWDlf0QJ4LhzGTwovE0CeIpLqg0LTovJnpd8gjwnFuMM1If6mGaJaqeMOB6lRWmBopfKeucuVS5Wwt19vZbHjQA6b3vteY4uZrCaHmjqgUKO8ykxsM7FwiFpBuM8n7XeKAjfOF6zjdu3d33n//fbc2HMINdbecPfbYw+HVHmGQvtBP0fV0dzv7d7bgdXX77ruvM2bMmFob8AdB9E1n+PDhKu+Ki4udCRMmuEeiUSP/VlUsdJb+MM354ZOJzjf/GuXMnHKCM+PpEVpmP3+KM+eNK52F059zlv30P6dqZWab48WB9Ft2O/rlAnQkb70QURV7IHfYYYeEoiFl6tSpbm00uI9wLt0iqL7Bc0HcYvW5NdFgr1EmG8/32GOPubXR4Klq5H96sKamyqmuWuFUV1Y41auWJYp8r6mS+0tb9Bz33HwAwmpUfqw4YGUQGRAA0YnvK9NAcrawS+rJkCCclEuQWoT4RjSHuUa8gAot+B4m2PzHciwAFUJb+XnDar6wYMGC5L6bLVu2dN599133SDTGjx+v4qgqiw1b/UCknXzyyc66667rDBs2LON9V4MQl3M3VsCxGoW7IS7Ym8ku6SItJs4+ECQKEvQmP9+mLOcCGAS4BrA22YuBGGeusNrHlAAuEH/eV2NHkyEsnIbE0HCm4uMiiTCVrwsLDG87zkb2SQ1zOmYCnJn2/vjHaFeuITNfExYJGRFDtX64poAmQ1gQCcQlnFY5VZwYIu4Iy+FI7gtbfpYJ0HtI4IO7MOCEY3INuBS+L4iW5WRNiWs1GcLCscmsxStPVgFJg36R4QdZB5ZLkQ4Nl8kVMBggLHxuEFYqx2cm4B7kopFJSygqzr5ijQVNhrAgou22207zs3AipiIqHIzsuAeng7vAsVJZj+hNbBHOmsNUhAJBYQnyybn8Ni6wJmkX3DcVcLCSp8V2lameuTGhSSnv6YDBs555uBvp0KkGhjAKcTrep8jOOamAaIWwEIO4HdCJUsFuNYnul0q0NSVC8mONJSxEoOUicZZ5McjsTkPs0pZUIFMVwoJ4SbuOQ1iIczJPCdfk0phobFhjCQsuxSoaRCB5VamWWaEnseAV8USSIfG6VEDvg6vwG/9eWWFgFRLrKVlX2JR0prQhndFkHKTpQjiCviFfntGtCcfixYud3XffXZ2vOD2nTZvmHgnH66+/7gjX0t/wginCRnGAozdueKcpQvq7aTlI0wUcgRSZOLoKy95JC4ZTbb/99roRbiqwKJVd/BCJrDSKE3oBKP1xz22qaHKxwnwDJRyxGbTQIwzkcEXleP3WIExraYEorVVFYZn7v+N3ZABoCo7FZlS58xz+jt9hTMX/A/eR72+RaQ9jAAAAAElFTkSuQmCC"
  />
  <p style="text-align: center">祝你每天都能找到对的色图,就像我每天都能喝到香草味可乐</p>
</details>`,
    modalFeedback: `<a target="_blank" style="position: absolute; right: 0px; top: 0px; color: rgb(0, 0, 238); text-decoration: underline" href="https://greasyfork.org/zh-CN/scripts/432150-pixiv-downloader/feedback">有问题or想建议?这里反馈</a>`,
    filePathSettingTitle: `<h3>设置文件名</h3>`,
    filePathSettingContent: `<style>.pdl-dialog-content input[type="text"] {height: auto; padding: 0.5em; line-height: 1.5; margin: 0.6em 0 0.3em 0; font-size: 16px;}.pdl-dialog-content a{color: rgb(0, 0, 238); text-decoration: underline;} .tags-option label,.tags-option input {cursor: pointer;}</style><div style="display: flex; gap: 20px; justify-content: space-between;">
  <div>
    <label style="display: block; cursor: default;" for="pdlfolder">文件夹名:</label>
    <input type="text" id="pdlfolder" style="width: 200px;" placeholder="我不想保存到画师文件夹">
  </div>
  <div>
    <label style="display: block; cursor: default;" for="pdlfilename">文件名:</label>
    <input type="text" id="pdlfilename" style="width: 300px;" placeholder="你的名字?">
  </div>
</div>
<div class="tags-option" style="margin: 0.7em 0;">
  <span>标签翻译:</span>
  <input type="radio" name="lang" id="lang_ja" value="ja"/>
  <label for="lang_ja">日本語(不翻译)</label>
  <input type="radio" name="lang" id="lang_zh" value="zh" />
  <label for="lang_zh">简中</label>
  <input type="radio" name="lang" id="lang_zh_tw" value="zh_tw" />
  <label for="lang_zh_tw">繁中</label>
  <input type="radio" name="lang" id="lang_en" value="en" />
  <label for="lang_en">English</label>
</div>
<p style="font-size: 14px; margin: 0.5em 0">
  {artist}:作者, {artistID}:作者ID, {title}:作品标题, {id}:作品pixiv ID, {page}:页码, {tags}:作品标签。
</p>
<p style="font-size: 14px; margin: 0.5em 0">如果不想保存到画师目录,文件夹名留空即可。</p>
<p style="font-size: 14px; margin: 0.5em 0">请注意:标签翻译不一定是你选择的语言,部分<a href="https://crowdin.com/project/pixiv-tags" target="_blank">无对应语言翻译的标签</a>仍可能是其他语言。</p>
</div>`,
    modalOperationBar: `<style>
  .pdl-dialog-footer button {
    font-size: 16px;
    background-color: transparent;
    border: 1px solid;
    color: rgb(125,125,125);
    border-radius: 5px;
    padding: 0.5em 1.5em;
    cursor: pointer;
    transition: .2s opacity;
    line-height: 1.15;
  }
  .pdl-dialog-footer button:hover{
    opacity: 0.7;
  }
</style>
<div style="display: flex; justify-content: flex-end; margin-top: 1.5em; gap: 1.5em;">
  <button id="pdlcancel">取消</button><button id="pdlconfirm" style="border-color: #01b468; background-color: #01b468; color: #fff;">确认</button></div>`,
  };

  function initialDeps(urls) {
    return Promise.all([_getGifWS(urls.gifWorker), _getApngWS(urls.pako, urls.upng)]).then(
      ([gif, apng]) => {
        this._deps.gif = URL.createObjectURL(new Blob([gif], { type: "text/javascript" }));
        this._deps.apng = URL.createObjectURL(new Blob([apng], { type: "text/javascript" }));
        return this;
      }
    );
  }
  function _fetchDeps(url) {
    return fetch(url)
      .then((res) => {
        if (res.ok) return res.text();
        throw new Error(res.status + res.statusText);
      })
      .catch((err) => {
        console.log("[Pixiv Downloader]Fetch dependency failed.", url, err);
        return "";
      });
  }
  async function _getGifWS(url) {
    let gifWS;
    if (!(gifWS = await GM_getValue("gifWS"))) {
      gifWS = await _fetchDeps(url);
      if (!gifWS) throw new Error("[Pixiv Downloader]Can not fetch gif worker script.");
      GM_setValue("gifWS", gifWS);
    }
    return gifWS;
  }
  async function _getApngWS(pakoUrl, upngUrl) {
    let apngWS;
    if (!(apngWS = await GM_getValue("apngWS"))) {
      let pako = _fetchDeps(pakoUrl);
      let upng = _fetchDeps(upngUrl);
      pako = await pako;
      upng = await upng;
      if (!pako || !upng) throw new Error("[Pixiv Downloader]Can not fetch apng script.");
      upng = 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.log('Convert Apng failed.');
      postMessage(png);
    };`;
      apngWS = workerEvt + pako + upng;
      GM_setValue("apngWS", apngWS);
    }
    return apngWS;
  }
  function _createImgElements(zip) {
    const eles = [];
    zip.forEach((relativePath, file) => {
      eles.push(
        new Promise((resolve) => {
          const image = new Image();
          image.onload = () => {
            resolve(image);
          };
          file.async("blob").then((blob) => void (image.src = URL.createObjectURL(blob)));
        })
      );
    });
    return Promise.all(eles);
  }
  function createInstance() {
    const zip = new JSZip();
    const freeApngWorkers = [];
    const apngWorkers = [];
    const MAX_CONVERT = 2;
    let queue = [];
    let active = [];
    let isStop = false;
    const convertTo = {
      gif: (frames, convertMeta) => {
        return new Promise((resolve, reject) => {
          let gif = new GIF({
            workers: 2,
            quality: 10,
            workerScript: this._deps.gif,
          });
          convertMeta.abort = convertMeta._baseAbort.bind(null, gif.abort.bind(gif));
          debugLog("[Info]Start convert:", convertMeta.id);
          frames.forEach((frame, i) => {
            gif.addFrame(frame, { delay: convertMeta.framesInfo[i].delay });
          });
          gif.on(
            "progress",
            (() => {
              const type = "gif";
              return (progress) => {
                debugLog("[Info]Convert progress:", convertMeta.id);
                if (typeof convertMeta.onProgress === "function")
                  convertMeta.onProgress(progress, type);
              };
            })()
          );
          gif.on("finished", (gifBlob) => {
            gif = null;
            resolve(gifBlob);
          });
          gif.on("abort", () => {
            gif = null;
            reject("[Info]Convert stop: abort. " + convertMeta.id);
          });
          gif.render();
        });
      },
      png: (frames, convertMeta) => {
        return new Promise((resolve, reject) => {
          let 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 });
          const data = [];
          const delay = convertMeta.framesInfo.map((frameInfo) => {
            return Number(frameInfo.delay);
          });
          frames.forEach((frame) => {
            if (convertMeta.isAborted)
              throw "[Info]Convert stop manually, reject when drawImage. " + convertMeta.id;
            context.clearRect(0, 0, width, height);
            context.drawImage(frame, 0, 0, width, height);
            data.push(context.getImageData(0, 0, width, height).data);
          });
          canvas = null;
          debugLog("[Info]Start convert:", convertMeta.id);
          let worker;
          if (apngWorkers.length === MAX_CONVERT) {
            worker = freeApngWorkers.shift();
          } else {
            worker = new Worker(this._deps.apng);
            apngWorkers.push(worker);
          }
          convertMeta.abort = convertMeta._baseAbort.bind(null, () => {
            reject("[Info]Convert stop manually, reject when convert apng. " + convertMeta.id);
            worker.terminate();
            apngWorkers.splice(apngWorkers.indexOf(worker), 1);
          });
          worker.onmessage = function (e) {
            if (queue.length) {
              freeApngWorkers.push(worker);
            } else {
              worker.terminate();
              apngWorkers.splice(apngWorkers.indexOf(worker), 1);
            }
            if (!e.data) {
              return reject("[Error]apng data is null. " + 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 canvas = document.createElement("canvas");
          const width = (canvas.width = frames[0].naturalWidth);
          const height = (canvas.height = frames[0].naturalHeight);
          const context = canvas.getContext("2d");
          const stream = canvas.captureStream();
          const recorder = new MediaRecorder(stream, {
            mimeType: "video/webm",
            videoBitsPerSecond: 80000000,
          });
          const delay = convertMeta.framesInfo.map((frame) => {
            return Number(frame.delay);
          });
          let data = [];
          let frame = 0;
          const displayFrame = () => {
            context.clearRect(0, 0, width, height);
            context.drawImage(frames[frame], 0, 0);
            if (convertMeta.isAborted) {
              return recorder.stop();
            }
            setTimeout(() => {
              if (typeof convertMeta.onProgress === "function")
                convertMeta.onProgress((frame + 1) / frames.length, "webm");
              if (frame === frames.length - 1) {
                return recorder.stop();
              } else {
                frame++;
              }
              displayFrame();
            }, delay[frame]);
          };
          recorder.ondataavailable = (event) => {
            if (event.data && event.data.size) {
              data.push(event.data);
            }
          };
          recorder.onstop = () => {
            canvas = null;
            if (convertMeta.isAborted) {
              return reject(
                "[info]Convert stop manually, reject when convert webm." + convertMeta.id
              );
            }
            resolve(new Blob(data, { type: "video/webm" }));
          };
          displayFrame();
          recorder.start();
        });
      },
    };
    const convert = (convertMeta) => {
      const { id, data, convertResolve, convertReject } = convertMeta;
      let frames;
      active.push(convertMeta);
      if (typeof convertMeta.onProgress === "function") convertMeta.onProgress(0, "zip");
      zip
        .folder(id)
        .loadAsync(data)
        .then(_createImgElements)
        .then((imgEles) => {
          zip.remove(id);
          frames = imgEles;
          if (convertMeta.isAborted) throw "[Info]Convert stop manually, reject when unzip. " + id;
          return convertTo[convertMeta.format](frames, convertMeta);
        })
        .then(convertResolve)
        .catch(convertReject)
        .finally(() => {
          frames.forEach((frame) => URL.revokeObjectURL(frame.src));
          frames = null;
          active.splice(active.indexOf(convertMeta), 1);
          if (queue.length) convert(queue.shift());
        });
    };
    return {
      add: (convertMeta) => {
        debugLog("[Info]Converter add", convertMeta.id);
        return new Promise((convertResolve, convertReject) => {
          convertMeta.isAborted = false;
          convertMeta.convertResolve = convertResolve;
          convertMeta.convertReject = convertReject;
          convertMeta._baseAbort = (callBack) => {
            if (typeof callBack === "function") callBack();
            convertMeta.isAborted = true;
          };
          convertMeta.abort = convertMeta._baseAbort;
          queue.push(convertMeta);
          while (active.length < MAX_CONVERT && queue.length && !isStop) {
            convert(queue.shift());
          }
        });
      },
      del: (metas) => {
        if (!metas.length) return;
        isStop = true;
        active = active.filter((convertMeta) => {
          if (metas.find((meta) => meta.id === convertMeta.id)) {
            convertMeta.abort();
          } else {
            return true;
          }
        });
        queue = queue.filter((convertMeta) => !metas.find((meta) => meta.id === convertMeta.id));
        isStop = false;
        while (active.length < MAX_CONVERT && queue.length) {
          convert(queue.shift());
        }
      },
    };
  }
  const createConverter = {
    _deps: {
      gif: "",
      apng: "",
    },
    initialDeps,
    createInstance,
  };

  const _isBlobDlAvaliable = !(
    navigator.userAgent.includes("Firefox") &&
    GM_info.scriptHandler === "Tampermonkey" &&
    parseFloat(GM_info.version) > 4.17
  );
  const _isNeedConvert = (meta) => {
    return meta.illustType === artworkType.UGOIRA && settings.ugoriaFormat !== "zip";
  };
  const _ffSave = (blob, meta) => {
    const dlEle = document.createElement("a");
    dlEle.href = URL.createObjectURL(blob);
    dlEle.download = meta.path.slice(meta.path.indexOf("/") + 1);
    dlEle.click();
    URL.revokeObjectURL(dlEle.href);
    meta.resolve(meta);
  };
  const _normalSave = (blob, meta) => {
    const imgUrl = URL.createObjectURL(blob);
    const request = {
      url: imgUrl,
      name: meta.path,
      onerror: (error) => {
        console.log("[pixiv downloader]Error when saving", meta.path);
        URL.revokeObjectURL(imgUrl);
        meta.reject && meta.reject(error);
      },
      onload: () => {
        if (typeof meta.onLoad === "function") meta.onLoad();
        URL.revokeObjectURL(imgUrl);
        meta.resolve(meta);
      },
    };
    meta.abort = GM_download(request).abort;
  };
  function createDownloader(converter) {
    const MAX_DOWNLOAD = 5;
    const MAX_RETRY = 3;
    let isStop = false;
    let queue = [];
    let active = [];
    let save;
    if (_isBlobDlAvaliable) {
      save = _normalSave;
    } else {
      debugLog("[Info]Run at firefox && TM version:", GM_info.version);
      save = _ffSave;
    }
    const download = (meta) => {
      debugLog("[Info]Start download:", meta.path);
      active.push(meta);
      let abortObj;
      if (!_isBlobDlAvaliable && !_isNeedConvert(meta)) {
        abortObj = GM_download({
          url: meta.src,
          name: meta.path,
          headers: {
            referer: "https://www.pixiv.net",
          },
          ontimeout: errHandler.bind(null, meta),
          onerror: errHandler.bind(null, meta),
          onload: () => {
            debugLog("[Info]Download complete", meta.path);
            if (typeof meta.onLoad === "function") meta.onLoad();
            meta.resolve(meta);
          },
        });
      } else {
        const request = {
          url: meta.src,
          timeout: 20000,
          method: "GET",
          headers: {
            referer: "https://www.pixiv.net",
          },
          responseType: "blob",
          ontimeout: errHandler.bind(null, meta),
          onprogress: (e) => {
            if (e.lengthComputable && typeof meta.onProgress === "function") {
              meta.onProgress(e.loaded / e.total);
            }
          },
          onload: (e) => {
            debugLog("[Info]Download complete", meta.id);
            if (!meta.state) return debugLog("[Warning]But download was canceled.", meta.id);
            if (_isNeedConvert(meta)) {
              const convertMeta = {
                id: meta.id,
                data: e.response,
                format: settings.ugoriaFormat,
                framesInfo: meta.ugoiraMeta.frames,
                onProgress: meta.onProgress,
              };
              converter.add(convertMeta).then((blob) => {
                save(blob, meta);
              }, meta.reject);
            } else {
              save(e.response, meta);
            }
            active.splice(active.indexOf(meta), 1);
            if (queue.length && !isStop) download(queue.shift());
          },
          onerror: errHandler.bind(null, meta),
        };
        abortObj = GM_xmlhttpRequest(request);
      }
      meta.abort = () => {
        meta.state = 0;
        abortObj.abort();
        meta.reject("[Warning]xhr abort manually. " + meta.id);
      };
    };
    const add = (metas) => {
      if (metas.length < 1) return;
      const promises = [];
      metas.forEach((meta) => {
        promises.push(
          new Promise((resolve, reject) => {
            meta.state = 1;
            meta.resolve = resolve;
            meta.reject = reject;
          })
        );
      });
      queue = queue.concat(metas);
      while (active.length < MAX_DOWNLOAD && queue.length && !isStop) {
        download(queue.shift());
      }
      return Promise.all(promises);
    };
    const del = (metas) => {
      if (!metas.length) return;
      isStop = true;
      active = active.filter((meta) => {
        if (metas.includes(meta)) {
          meta.abort();
        } else {
          return true;
        }
      });
      queue = queue.filter((meta) => !metas.includes(meta));
      isStop = false;
      while (active.length < MAX_DOWNLOAD && queue.length) {
        download(queue.shift());
      }
    };
    const errHandler = (meta) => {
      debugLog("[Error]xmlhttpRequest timeout:", meta.src);
      if (!meta.retries) {
        meta.retries = 1;
      } else {
        meta.retries++;
      }
      if (meta.retries > MAX_RETRY) {
        meta.reject("[Error]xmlhttpRequest failed: " + meta.src);
        console.log("[pixiv downloader]Network error:", meta.path, meta.src);
        active.splice(active.indexOf(meta), 1);
        if (queue.length && !isStop) download(queue.shift());
      } else {
        debugLog("[Warning]retry xhr:", meta.retries, meta.src);
        download(meta);
      }
    };
    return {
      add: add,
      del: del,
    };
  }

  function createParser() {
    const replaceInvalidChar = (string) => {
      if (!string) return;
      const temp = document.createElement("div");
      temp.innerHTML = string;
      return temp.textContent
        .trim()
        .replace(/^\.|\.$/g, "")
        .replace(/[\u200b-\u200f\uFEFF\u202a-\u202e\\/:*?|]/g, "")
        .replace(/"/g, "'")
        .replace(/</g, "﹤")
        .replace(/>/g, "﹥");
    };
    const getFilePath = ({ user, userId, title, tags, illustId, page, ext }) => {
      const path = settings.folderPattern
        ? settings.folderPattern + "/" + settings.filenamePattern
        : settings.filenamePattern;
      return (
        path
          .replaceAll("{author}", user)
          .replaceAll("{artist}", user)
          .replaceAll("{artistID}", userId)
          .replaceAll("{title}", title)
          .replaceAll("{tags}", tags)
          .replaceAll("{page}", page)
          .replaceAll("{id}", illustId) + ext
      );
    };
    const makeTagsStr = (prev, cur, index, tagsArr) => {
      const tag = settings.tagLang === "jp" ? cur.tag : cur.translation?.["en"] || cur.tag;
      if (index < tagsArr.length - 1) {
        return prev + tag + "_";
      } else {
        return prev + tag;
      }
    };
    const parseByIllust = async (illustId) => {
      let params = "";
      if (settings.tagLang !== "jp") params = "?lang=" + settings.tagLang;
      const res = await fetch("https://www.pixiv.net/artworks/" + illustId + params);
      if (!res.ok) throw new Error("[Error]fetch artworksURL failed: " + res.status);
      const htmlText = await res.text();
      const preloadData = JSON.parse(htmlText.match(/"meta-preload-data" content='(.*)'>/)[1]);
      if (!preloadData.illust) throw new Error("[Error]Fail to parse meta preload data.");
      const illustInfo = preloadData.illust[illustId];
      const user = replaceInvalidChar(illustInfo.userName) || "userId-" + illustInfo.userId;
      const title = replaceInvalidChar(illustInfo.illustTitle) || "illustId-" + illustInfo.illustId;
      const tags = replaceInvalidChar(illustInfo.tags.tags.reduce(makeTagsStr, ""));
      const illustType = illustInfo.illustType;
      let metas = [];
      const pathInfo = {
        user,
        title,
        tags,
        illustId,
        userId: illustInfo.userId,
        ext: "",
        page: 0,
      };
      if (illustType === artworkType.ILLUSTS || illustType === artworkType.MANGA) {
        const firstImgSrc = illustInfo.urls.original;
        const srcPrefix = firstImgSrc.slice(0, firstImgSrc.indexOf("_") + 2);
        const srcSuffix = firstImgSrc.slice(-4);
        pathInfo.ext = srcSuffix;
        for (let i = 0; i < illustInfo.pageCount; i++) {
          pathInfo.page = i;
          metas.push({
            id: illustId,
            illustType: illustType,
            path: getFilePath(pathInfo),
            src: srcPrefix + i + srcSuffix,
          });
        }
      }
      if (illustType === artworkType.UGOIRA) {
        const ugoiraRes = await fetch(
          "https://www.pixiv.net/ajax/illust/" + illustId + "/ugoira_meta"
        );
        if (!ugoiraRes.ok) throw new Error("[Error]fetch ugoira meta failed: " + res.status);
        const ugoira = await ugoiraRes.json();
        pathInfo.ext = "." + settings.ugoriaFormat;
        metas.push({
          id: illustId,
          illustType: illustType,
          path: getFilePath(pathInfo),
          src: ugoira.body.originalSrc,
          ugoiraMeta: ugoira.body,
        });
      }
      return metas;
    };
    const parseByUser = async (userId, type) => {
      const res = await fetch("https://www.pixiv.net/ajax/user/" + userId + "/profile/all");
      if (!res.ok) throw new Error("fetch user profile failed: " + res.status);
      const profile = await res.json();
      let illustIds;
      if (type) {
        illustIds = Object.keys(profile.body[type]);
      } else {
        illustIds = Object.keys(profile.body.illusts).concat(Object.keys(profile.body.manga));
      }
      return illustIds;
    };
    return {
      id: parseByIllust,
      user: parseByUser,
    };
  }

  let converter;
  let downloader;
  let parser;
  async function initial() {
    converter = await createConverter
      .initialDeps(depsUrls)
      .then((createConverter) => createConverter.createInstance());
    parser = createParser();
    downloader = createDownloader(converter);
  }

  function add(ele) {
    this._records.add(ele);
  }
  function has(ele) {
    return this._records.has(ele);
  }
  function getHistory() {
    const storage = localStorage.pixivDownloader || "[]";
    return new Set(JSON.parse(storage));
  }
  function updateHistory() {
    Object.keys(localStorage).forEach((key) => {
      const matchResult = /pdlTemp-(\d+)/.exec(key);
      if (matchResult) {
        this._records.add(matchResult[1]);
        localStorage.removeItem(matchResult[0]);
      }
    });
    this.saveHistory();
  }
  function clearHistory() {
    const isConfirm = confirm("Do you really want to clear history?");
    if (!isConfirm) return;
    this.updateHistory();
    this._records = new Set();
    localStorage.pixivDownloader = "[]";
  }
  function saveHistory() {
    localStorage.pixivDownloader = JSON.stringify([...this._records]);
  }
  const pixivHistory = {
    _records: getHistory(),
    add,
    has,
    updateHistory,
    saveHistory,
    clearHistory,
  };

  function handleDownload(pdlBtn, illustId) {
    let pageCount,
      pageComplete = 0;
    const onProgress = (progress = 0, type = null) => {
      if (pageCount > 1) return;
      progress = Math.floor(progress * 100);
      switch (type) {
        case null:
          pdlBtn.style.setProperty("--pdl-progress", progress + "%");
        case "gif":
        case "webm":
          pdlBtn.textContent = progress;
          break;
        case "zip":
          pdlBtn.textContent = "";
          break;
      }
    };
    const onLoad = function () {
      if (pageCount < 2) return;
      const progress = Math.floor((++pageComplete / pageCount) * 100);
      pdlBtn.textContent = progress;
      pdlBtn.style.setProperty("--pdl-progress", progress + "%");
    };
    pdlBtn.classList.add("pdl-progress");
    parser
      .id(illustId)
      .then((metas) => {
        let shouldDownloadPage;
        if ((shouldDownloadPage = pdlBtn.getAttribute("should-download"))) {
          metas = [metas[shouldDownloadPage]];
        }
        pageCount = metas.length;
        metas.forEach((meta) => {
          meta.onProgress = onProgress;
          meta.onLoad = onLoad;
        });
        return downloader.add(metas);
      })
      .then(() => {
        pixivHistory.add(illustId);
        localStorage.setItem(`pdlTemp-${illustId}`, "");
        pdlBtn.classList.remove("pdl-error");
        pdlBtn.classList.add("pdl-complete");
      })
      .catch((err) => {
        if (err) console.log(err);
        pdlBtn.classList.remove("pdl-complete");
        pdlBtn.classList.add("pdl-error");
      })
      .finally(() => {
        pdlBtn.innerHTML = "";
        pdlBtn.style.removeProperty("--pdl-progress");
        pdlBtn.classList.remove("pdl-progress");
      });
  }
  function handleDownloadAll(userId, type = "") {
    let worksCount = 0,
      worksComplete = 0,
      failed = [];
    let isCanceled = false;
    let metasRecord = [];
    const timers = [];
    const placeholder = document.querySelector(".pdl-nav-placeholder");
    const control = document.querySelector(".pdl-stop");
    const isExcludeDled = document.querySelector("#pdl-filter").checked;
    return new Promise((resolve, reject) => {
      control.onclick = () => {
        isCanceled = true;
        for (const timer of timers) {
          if (timer) clearTimeout(timer);
        }
        if (metasRecord.length) {
          downloader.del(metasRecord);
          converter.del(metasRecord);
          metasRecord = [];
        }
        control.onclick = null;
        reject("Download stopped");
      };
      const onProgressCB = (illustId) => {
        placeholder.textContent = `Downloading: ${++worksComplete} / ${worksCount}`;
        if (worksComplete === worksCount - failed.length) {
          if (failed.length) {
            placeholder.textContent = `Complete. Failed: ${failed.length} / ${worksCount}. See console.`;
            console.log("Failed: ", failed.join(", "));
          } else {
            placeholder.textContent = "Complete";
          }
          resolve();
        }
      };
      placeholder.textContent = "Download...";
      parser
        .user(userId, type)
        .then((illustIds) => {
          if (isCanceled) throw "Download stopped";
          if (isExcludeDled) {
            pixivHistory.updateHistory();
            debugLog("Before filter", illustIds.length);
            illustIds = illustIds.filter((illustId) => !pixivHistory.has(illustId));
            debugLog("After filter", illustIds.length);
          }
          if (!illustIds.length) throw "All Exclude";
          worksCount = illustIds.length;
          illustIds.forEach((illustId, idx) => {
            if (isCanceled) throw "[Warning]Download stopped";
            let timer = setTimeout(() => {
              timer = null;
              parser
                .id(illustId)
                .then((metas) => {
                  if (isCanceled) {
                    throw "[Warning]Download stop manually: " + metas[0].id;
                  }
                  metasRecord = metasRecord.concat(metas);
                  return downloader.add(metas);
                })
                .then((metas) => {
                  pixivHistory.add(illustId);
                  localStorage.setItem(`pdlTemp-${illustId}`, "");
                  if (isCanceled) {
                    return;
                  }
                  metasRecord = metasRecord.filter((meta) => !metas.includes(meta));
                  onProgressCB();
                })
                .catch((err) => {
                  failed.push(illustId);
                });
            }, idx * 600);
            timers.push(timer);
          });
        })
        .catch((err) => {
          reject(err);
        });
    });
  }
  function toggleDlAll(evt) {
    const target = evt.target;
    if (target.classList.contains("pdl-btn-all")) {
      evt.preventDefault();
      evt.stopPropagation();
      const dlBarsBtn = target.parentElement.querySelectorAll("[pdl-userid]");
      const placeholder = document.querySelector(".pdl-nav-placeholder");
      const userId = target.getAttribute("pdl-userid");
      dlBarsBtn.forEach((ele) => {
        ele.classList.toggle("pdl-hide");
      });
      handleDownloadAll(userId, target.getAttribute("pdl-type"))
        .catch((err) => {
          placeholder.textContent = err;
        })
        .finally(() => {
          dlBarsBtn.forEach((ele) => {
            ele.classList.toggle("pdl-hide");
          });
        });
    }
  }

  function createModal({ header, content, footer = "" }, option = { closeOnClickModal: true }) {
    const modal = document.createElement("div");
    const dialog = document.createElement("div");
    modal.classList.add("pdl-modal");
    dialog.classList.add("pdl-dialog");
    if (option.closeOnClickModal) {
      dialog.onclick = (e) => {
        e.stopPropagation();
      };
      modal.onclick = () => {
        modal.remove();
      };
    }
    dialog.innerHTML = `  <header class="pdl-dialog-header">${header}</header>
  <div class="pdl-dialog-content">${content}</div>
  <footer class="pdl-dialog-footer">${footer}</footer>`;
    const closeBtn = document.createElement("button");
    closeBtn.classList.add("pdl-dialog-close");
    closeBtn.onclick = () => {
      modal.remove();
    };
    dialog.insertBefore(closeBtn, dialog.firstChild);
    modal.appendChild(dialog);
    return modal;
  }
  function showUpgradeMsg() {
    document.body.appendChild(
      createModal({
        header: text.upgradeMsgTitle,
        content: text.upgradeMsgContent,
        footer: text.modalCreditFooter + text.modalFeedback,
      })
    );
  }
  function showFilePathSetting() {
    if (document.querySelector("#pdlfolder")) return;
    const modal = createModal(
      {
        header: text.filePathSettingTitle,
        content: text.filePathSettingContent,
        footer: text.modalOperationBar,
      },
      { closeOnClickModal: false }
    );
    const folder = modal.querySelector("#pdlfolder");
    const filename = modal.querySelector("#pdlfilename");
    modal.querySelector("#pdlcancel").onclick = () => {
      modal.remove();
    };
    modal.querySelector("#pdlconfirm").onclick = () => {
      if (filename.value === "") return;
      const folderPattern = folder.value
        .trim()
        .replace(/^\.|[\u200b-\u200f\uFEFF\u202a-\u202e\\/:*?"|<>]/g, "");
      const filenamePattern = filename.value
        .trim()
        .replace(/^\.|[\u200b-\u200f\uFEFF\u202a-\u202e\\/:*?"|<>]/g, "");
      if (filenamePattern === "") return;
      upgradeSettings("tagLang", modal.querySelector(".tags-option [name='lang']:checked").value);
      upgradeSettings("folderPattern", folderPattern);
      upgradeSettings("filenamePattern", filenamePattern);
      modal.remove();
    };
    modal.querySelector(`.tags-option [value="${settings.tagLang}"]`).checked = true;
    folder.value = settings.folderPattern;
    filename.value = settings.filenamePattern;
    document.body.appendChild(modal);
  }
  function getIllustId(node) {
    const isLinkToArtworksPage = regexp.artworksPage.exec(node.href);
    if (isLinkToArtworksPage) {
      if (
        node.getAttribute("data-gtm-value") ||
        node.classList.contains("gtm-illust-recommend-node-node") ||
        node.classList.contains("gtm-discover-user-recommend-node") ||
        node.classList.contains("work")
      ) {
        return isLinkToArtworksPage[1];
      }
    } else {
      const isActivityThumb = regexp.activityHref.exec(node.href);
      if (isActivityThumb && node.classList.contains("work")) {
        return isActivityThumb[1];
      }
    }
    return "";
  }
  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 (!evt.currentTarget.classList.contains("pdl-progress")) {
          handleDownload(ele, ele.getAttribute("pdl-id"));
        }
      });
    }
    return ele;
  }
  function createMainBtn(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"],
      };
      if (pixivHistory.has(id)) attrs.classList.push("pdl-complete");
      pdlBtnWrap.appendChild(createPdlBtn(attrs));
      handleBar.appendChild(pdlBtnWrap);
    }
  }
  function createDownloadBar(userId) {
    const nav = document.querySelector("nav");
    if (!nav || document.querySelector(".pdl-nav-placeholder")) return;
    const fragment = document.createDocumentFragment();
    const placeholder = document.createElement("div");
    placeholder.classList.add("pdl-nav-placeholder");
    fragment.appendChild(placeholder);
    const baseClasses = nav.querySelector("a:not([aria-current])").classList;
    fragment.appendChild(
      createPdlBtn(
        {
          attrs: { "pdl-userId": userId },
          classList: [...baseClasses, "pdl-stop", "pdl-hide"],
        },
        "Stop",
        { addEvent: false }
      )
    );
    fragment.appendChild(
      createPdlBtn(
        {
          attrs: { "pdl-userId": userId },
          classList: [...baseClasses, "pdl-btn-all"],
        },
        "All",
        { addEvent: false }
      )
    );
    if (nav.querySelector("a[href$=illustrations]") && nav.querySelector("a[href$=manga]")) {
      fragment.appendChild(
        createPdlBtn(
          {
            attrs: { "pdl-userid": userId, "pdl-type": "illusts" },
            classList: [...baseClasses, "pdl-btn-all"],
          },
          "Illusts",
          { addEvent: false }
        )
      );
      fragment.appendChild(
        createPdlBtn(
          {
            attrs: { "pdl-userid": userId, "pdl-type": "manga" },
            classList: [...baseClasses, "pdl-btn-all"],
          },
          "Manga",
          { addEvent: false }
        )
      );
    }
    const wrapper = document.createElement("div");
    const checkbox = document.createElement("input");
    const label = document.createElement("label");
    wrapper.classList.add("pdl-wrap");
    checkbox.id = "pdl-filter";
    checkbox.type = "checkbox";
    label.setAttribute("for", "pdl-filter");
    label.textContent = "Exclude downloaded";
    wrapper.appendChild(checkbox);
    wrapper.appendChild(label);
    nav.parentElement.insertBefore(wrapper, nav);
    nav.appendChild(fragment);
    nav.addEventListener("click", toggleDlAll);
  }
  function createSubBtn(nodes) {
    const isBookmarkPage = regexp.bookmarkPage.test(location.pathname);
    nodes.forEach((e) => {
      if (e.childElementCount !== 0) {
        const illustId = getIllustId(e);
        if (illustId) {
          const attrs = {
            attrs: { "pdl-id": illustId },
            classList: ["pdl-btn", "pdl-btn-sub"],
          };
          if (pixivHistory.has(illustId)) attrs.classList.push("pdl-complete");
          if (isBookmarkPage) attrs.classList.push("pdl-btn-sub-bookmark");
          e.appendChild(createPdlBtn(attrs));
        }
      }
    });
  }
  function createMultyWorksBtn(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": 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);
      const containers = btn.parentElement;
      const attrs = {
        attrs: {
          "pdl-id": btn.getAttribute("pdl-id"),
          "should-download": pageNum,
        },
        classList: ["pdl-btn", "pdl-btn-sub", "presentation"],
      };
      btn.remove();
      btn = createPdlBtn(attrs);
      containers.appendChild(btn);
    }
    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("img");
      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);
      observer = new MutationObserver(cb);
      observer.observe(img.parentElement, { childList: true, subtree: true });
    };
  })();
  function createPreviewModalBtn() {
    const illustModalBtn = document.querySelectorAll(".gtm-manga-viewer-preview-modal-open");
    const mangaModalBtn = document.querySelectorAll(".gtm-manga-viewer-open-preview");
    let mangaViewerModalBtn = document.querySelectorAll(".gtm-manga-viewer-close-icon")?.[1];
    if (!illustModalBtn.length && !mangaModalBtn.length) return;
    const btns = [...illustModalBtn, ...mangaModalBtn];
    if (mangaViewerModalBtn) btns.push(mangaViewerModalBtn);
    btns.forEach((node) => {
      node.addEventListener("click", handleModalClick);
    });
  }
  function handleModalClick() {
    const timer = setInterval(() => {
      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": idx },
          classList: ["pdl-btn", "pdl-btn-sub"],
        };
        node.appendChild(createPdlBtn(attrs));
      });
    }, 300);
  }
  function compatPixivPreviewer(nodes) {
    const isPpSearchPage = regexp.ppSearchPage.test(location.pathname);
    if (!isPpSearchPage) return;
    nodes.forEach((node) => {
      const pdlEle = node.querySelector(".pdl-btn");
      if (!pdlEle) return false;
      pdlEle.remove();
    });
  }
  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) {
      createSubBtn(document.querySelectorAll("a"));
      firstRun = false;
    } else {
      compatPixivPreviewer(addedNodes);
      const thunmnails = addedNodes.reduce((prev, current) => {
        return prev.concat(Array.from(current.querySelectorAll("a")));
      }, []);
      createSubBtn(thunmnails);
    }
    const isArtworksPage = regexp.artworksPage.exec(location.pathname);
    const isUserPage = regexp.userPage.exec(location.pathname);
    if (isArtworksPage) {
      const id = isArtworksPage[1];
      createMainBtn(id);
      createMultyWorksBtn(id);
      createPresentationBtn(id);
      createPreviewModalBtn();
    } else if (isUserPage) {
      createDownloadBar(isUserPage[1]);
    }
  }

  addStyle();
  pixivHistory.updateHistory();
  GM_registerMenuCommand("Apng", createSetFormatFn("png"), "a");
  GM_registerMenuCommand("Gif", createSetFormatFn("gif"), "g");
  GM_registerMenuCommand("Zip", createSetFormatFn("zip"), "z");
  GM_registerMenuCommand("Webm", createSetFormatFn("webm"), "w");
  GM_registerMenuCommand("Clear history", pixivHistory.clearHistory.bind(pixivHistory), "c");
  GM_registerMenuCommand("Edit filename", showFilePathSetting, "e");
  initial().then(() => {
    if (settings.showMsg) {
      showUpgradeMsg();
      upgradeSettings("showMsg", false);
    }
    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", { bubbles: true }));
          }
        }
      }
    });
  });

})();