Pixiv Downloader

Pixiv | Danbooru | Rule34. Download artworks with one click. Batch download artworks or download by tags. Convert ugoira formats: Gif | Apng | Webp | Webm. Customize image file name, save path. Save / export download history.

As of 2024-06-29. 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.11.1
// @description:en  Pixiv | Danbooru | Rule34. Download artworks with one click. Batch download artworks or download by tags. Convert ugoira formats: Gif | Apng | Webp | Webm. Customize image file name, save path. Save / export download history.
// @description  Pixiv | Danbooru | Rule34. 一键下载各页面原图。批量下载画师作品,按作品标签下载。转换动图格式:Gif | Apng | Webp | Webm。自定义图片文件名,保存路径。保留 / 导出下载历史。
// @description:zh-TW  Pixiv | Danbooru | Rule34. 一鍵下載各頁面原圖。批次下載畫師作品,按作品標籤下載。轉換動圖格式:Gif | Apng | Webp | Webm。自定義圖片檔名,儲存路徑。保留 / 匯出下載歷史。
// @author       ruaruarua
// @match        https://www.pixiv.net/*
// @match        https://rule34.xxx/*
// @match        https://danbooru.donmai.us/*
// @icon         https://www.pixiv.net/favicon.ico
// @noframes
// @grant        unsafeWindow
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_download
// @grant        GM_info
// @grant        GM_registerMenuCommand
// @grant        GM_getResourceText
// @connect      i.pximg.net
// @connect      rule34.xxx
// @connect      donmai.us
// @require      https://unpkg.com/[email protected]/dist/jszip.min.js
// @require      https://unpkg.com/[email protected]/dist/gif.js
// @require      https://unpkg.com/[email protected]/dayjs.min.js
// @require      https://unpkg.com/[email protected]/dist/dexie.min.js
// @require      https://greasyfork.org/scripts/455256-toanimatedwebp/code/toAnimatedWebp.js?version=1120088
// @resource pako https://unpkg.com/[email protected]/dist/pako.min.js
// @resource upng https://unpkg.com/[email protected]/UPNG.js
// @resource gifWorker https://unpkg.com/[email protected]/dist/gif.worker.js
// ==/UserScript==


(function (Dexie, dayjs, JSZip, GIF, workerChunk) {
    'use strict';

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

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

    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();

    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 generateCsv(sheetData) {
        const sheetStr = sheetData
            .map((row) => {
            return row
                .map((cell) => {
                return '"' + cell.replace(/"/g, '""') + '"';
            })
                .join(',');
        })
            .join('\r\n');
        return new Blob(['\ufeff' + sheetStr], { type: 'text/csv' });
    }
    function evalScript(script) {
        const el = document.createElement('script');
        el.text = script;
        document.head.appendChild(el).parentNode.removeChild(el);
    }

    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';
        }
    }

    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');
        },
        isPixiv() {
            return location.hostname === 'www.pixiv.net';
        }
    };

    class FileSystemAccessHandler {
        filenameConflictAction = 'uniquify';
        updateDirHandleChannel;
        dirHandle = undefined;
        dirHandleStatus = 0 ;
        cachedTasks = [];
        duplicateFilenameCached = {};
        constructor(channelName) {
            this.updateDirHandleChannel = new BroadcastChannel(channelName);
            this.updateDirHandleChannel.onmessage = (evt) => {
                const data = evt.data;
                switch (data.kind) {
                    case 1 :
                        this.dirHandleStatus = 1 ;
                        logger.info('正在选择目录');
                        break;
                    case 0 :
                        logger.warn('取消更新dirHandle');
                        if (this.dirHandle) {
                            this.dirHandleStatus = 2 ;
                            this.processCachedTasks();
                        }
                        else {
                            this.dirHandleStatus = 0 ;
                            this.rejectCachedTasks();
                        }
                        break;
                    case 2 :
                        this.dirHandleStatus = 2 ;
                        this.dirHandle = data.handle;
                        logger.info('更新dirHandle', this.dirHandle);
                        this.processCachedTasks();
                        break;
                    case 'request':
                        if (this.dirHandle) {
                            this.updateDirHandleChannel.postMessage({
                                kind: 'response',
                                handle: this.dirHandle
                            });
                            logger.info('响应请求dirHandle');
                        }
                        break;
                    case 'response':
                        if (!this.dirHandle) {
                            if (this.dirHandleStatus === 0 )
                                this.dirHandleStatus = 2 ;
                            this.dirHandle = data.handle;
                            logger.info('首次获取dirHandle', this.dirHandle);
                        }
                        break;
                    default:
                        throw new Error('Invalid data kind.');
                }
            };
            this.updateDirHandleChannel.postMessage({ kind: 'request' });
        }
        async getDirHandleRecursive(dirs) {
            if (!this.dirHandle)
                throw new Error('未选择保存文件夹');
            let handler = this.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;
        }
        processCachedTasks() {
            const { length } = this.cachedTasks;
            for (let i = 0; i < length; i++) {
                const [blob, downloadMeta, onSaveFullfilled, onSaveRejected] = this.cachedTasks[i];
                this.saveFile(blob, downloadMeta).then(onSaveFullfilled, onSaveRejected);
            }
            logger.info(`执行${length}个缓存任务`);
            if (this.cachedTasks.length > length) {
                this.cachedTasks = this.cachedTasks.slice(length);
            }
            else {
                this.cachedTasks.length = 0;
            }
        }
        rejectCachedTasks() {
            this.cachedTasks.forEach(([, , , onSaveRejected]) => onSaveRejected(new CancelError()));
            this.cachedTasks.length = 0;
            logger.info(`取消${this.cachedTasks.length}个缓存任务`);
        }
        async getFilenameHandle(dirHandle, filename) {
            if (this.filenameConflictAction === 'overwrite')
                return await dirHandle.getFileHandle(filename, { create: true });
            if (!(filename in this.duplicateFilenameCached)) {
                this.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 (this.filenameConflictAction === '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 (this.duplicateFilenameCached[filename].includes(newName)) {
                            continue;
                        }
                        else {
                            this.duplicateFilenameCached[filename].push(newName);
                        }
                        logger.info('使用文件名:', newName);
                        return await dirHandle.getFileHandle(newName, { create: true });
                    }
                }
                throw new RangeError('Oops, you have too many duplicate files.');
            }
        }
        clearFilenameCached(duplicateName, actualName) {
            if (!(duplicateName in this.duplicateFilenameCached))
                return;
            const usedNameArr = this.duplicateFilenameCached[duplicateName];
            logger.info('清理重名文件名', usedNameArr, actualName);
            if (usedNameArr.length === 0) {
                delete this.duplicateFilenameCached[duplicateName];
                return;
            }
            const index = usedNameArr.indexOf(actualName);
            if (index === -1)
                return;
            usedNameArr.splice(index, 1);
            if (usedNameArr.length === 0)
                delete this.duplicateFilenameCached[duplicateName];
        }
        async updateDirHandle() {
            try {
                this.dirHandleStatus = 1 ;
                this.updateDirHandleChannel.postMessage({ kind: 1  });
                this.dirHandle = await unsafeWindow.showDirectoryPicker({ id: 'pdl', mode: 'readwrite' });
                logger.info('更新dirHandle', this.dirHandle);
                this.dirHandleStatus = 2 ;
                this.updateDirHandleChannel.postMessage({
                    kind: 2 ,
                    handle: this.dirHandle
                });
                this.processCachedTasks();
                return true;
            }
            catch (error) {
                logger.warn(error);
                this.updateDirHandleChannel.postMessage({ kind: 0  });
                if (this.dirHandle) {
                    this.dirHandleStatus = 2 ;
                    this.processCachedTasks();
                }
                else {
                    this.dirHandleStatus = 0 ;
                    this.rejectCachedTasks();
                }
                return false;
            }
        }
        getCurrentDirName() {
            return this.dirHandle?.name ?? '';
        }
        isDirHandleNotSet() {
            return this.dirHandleStatus === 0 ;
        }
        setFilenameConflictAction(action) {
            this.filenameConflictAction = action;
        }
        async saveFile(blob, downloadMeta) {
            if (downloadMeta.isAborted)
                throw new CancelError();
            if (this.dirHandleStatus === 1 ) {
                let onSaveFullfilled;
                let onSaveRejected;
                const promiseExcutor = new Promise((resolve, reject) => {
                    onSaveFullfilled = resolve;
                    onSaveRejected = reject;
                });
                this.cachedTasks.push([blob, downloadMeta, onSaveFullfilled, onSaveRejected]);
                return promiseExcutor;
            }
            if (this.dirHandleStatus === 0 ) {
                const isSuccess = await this.updateDirHandle();
                if (!isSuccess)
                    throw new TypeError('Failed to get dir handle.');
            }
            let currenDirHandle;
            let filename;
            const path = downloadMeta.config.path;
            const index = path.lastIndexOf('/');
            if (index === -1) {
                filename = path;
                currenDirHandle = this.dirHandle;
            }
            else {
                filename = path.slice(index + 1);
                currenDirHandle = await this.getDirHandleRecursive(path.slice(0, index));
            }
            if (downloadMeta.isAborted)
                throw new CancelError();
            const fileHandle = await this.getFilenameHandle(currenDirHandle, filename);
            const writableStream = await fileHandle.createWritable();
            await writableStream.write(blob);
            await writableStream.close();
            this.clearFilenameCached(filename, fileHandle.name);
        }
    }
    const fsaHandler = new FileSystemAccessHandler('update_dir_channel');

    function gmDownload(blob, downloadMeta) {
        return new Promise((resolve, reject) => {
            if (downloadMeta.isAborted)
                return reject(new CancelError());
            const imgUrl = URL.createObjectURL(blob);
            const request = {
                url: URL.createObjectURL(blob),
                name: downloadMeta.config.path,
                onerror: (error) => {
                    URL.revokeObjectURL(imgUrl);
                    if (downloadMeta.isAborted) {
                        resolve();
                    }
                    else {
                        reject(new Error(`FileSave error: ${downloadMeta.config.path} because ${error.error} ${error.details ?? ''} `));
                    }
                },
                onload: () => {
                    URL.revokeObjectURL(imgUrl);
                    resolve();
                }
            };
            downloadMeta.abort = GM_download(request).abort;
        });
    }

    function aDownload(blob, downloadMeta) {
        if (downloadMeta.isAborted)
            return Promise.reject(new CancelError());
        let path = downloadMeta.config.path;
        const separaterIndex = path.lastIndexOf('/');
        if (separaterIndex !== -1)
            path = path.slice(separaterIndex + 1);
        const dlEle = document.createElement('a');
        dlEle.href = URL.createObjectURL(blob);
        dlEle.download = path;
        dlEle.click();
        URL.revokeObjectURL(dlEle.href);
        return Promise.resolve();
    }

    function loadConfig(siteConfig) {
        const defaultConfig = Object.freeze({
            version: "0.11.1",
            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,
            ...siteConfig
        });
        let config;
        if (!localStorage.pdlSetting) {
            config = Object.assign({}, defaultConfig);
        }
        else {
            try {
                config = JSON.parse(localStorage.pdlSetting);
            }
            catch (error) {
                logger.error('Use default config because: ', error);
                config = Object.assign({}, defaultConfig);
            }
        }
        if (config.version !== defaultConfig.version) {
            config = {
                ...defaultConfig,
                ...config,
                version: defaultConfig.version,
                showMsg: true
            };
            localStorage.pdlSetting = JSON.stringify(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 hostname = location.hostname;
    let siteConfig;
    if (hostname === 'rule34.xxx') {
        siteConfig = {
            folderPattern: 'rule34/{artist}',
            filenamePattern: '{id}_{artist}_{character}'
        };
    }
    else if (hostname === 'danbooru.donmai.us') {
        siteConfig = {
            folderPattern: 'danbooru/{artist}',
            filenamePattern: '{id}_{artist}_{character}'
        };
    }
    const config = loadConfig(siteConfig);

    let saveFile;
    if (env.isBlobDlAvaliable() && env.isSupportSubpath()) {
        saveFile = gmDownload;
    }
    else {
        saveFile = aDownload;
        logger.warn('Download function is not fully supported:', GM_info.scriptHandler, GM_info.version);
    }
    const fileSaveAdapters = {
        getAdapter() {
            if (this.isFileSystemAccessEnable()) {
                fsaHandler.setFilenameConflictAction(config.get('fileSystemFilenameConflictAction'));
                return fsaHandler.saveFile.bind(fsaHandler);
            }
            else {
                return saveFile;
            }
        },
        isFileSystemAccessEnable() {
            return env.isFileSystemAccessAvaliable() && config.get('useFileSystemAccess');
        },
        dirHandleCheck() {
            if (this.isFileSystemAccessEnable() && fsaHandler.isDirHandleNotSet())
                fsaHandler.updateDirHandle();
        }
    };

    function createDownloader() {
        const MAX_DOWNLOAD = 5;
        const MAX_RETRY = 3;
        const DOWNLOAD_INTERVAL = 500;
        let queue = [];
        let active = [];
        const cleanAndStartNext = (removeMeta, nextMeta) => {
            sleep(DOWNLOAD_INTERVAL).then(() => {
                active.splice(active.indexOf(removeMeta), 1);
                if (nextMeta) {
                    active.push(nextMeta);
                    dispatchDownload(nextMeta);
                }
                else if (queue.length) {
                    const meta = queue.shift();
                    active.push(meta);
                    dispatchDownload(meta);
                }
            });
        };
        const errorHandlerFactory = (downloadMeta) => {
            return {
                ontimeout(error) {
                    const { taskId, config, isAborted } = downloadMeta;
                    if (isAborted)
                        return;
                    downloadMeta.retry++;
                    logger.error('Download timeout', downloadMeta.retry, ':', config.src, error);
                    if (downloadMeta.retry > MAX_RETRY) {
                        const err = new Error(`Download failed: ${taskId} | ${config.src}`);
                        config.onError?.(err, config);
                        downloadMeta.reject(err);
                        cleanAndStartNext(downloadMeta);
                    }
                    else {
                        logger.info('Download retry:', downloadMeta.retry, config.src);
                        cleanAndStartNext(downloadMeta, downloadMeta);
                    }
                },
                onerror(error) {
                    const { taskId, config, isAborted } = downloadMeta;
                    if (isAborted)
                        return;
                    let err;
                    if ('status' in error && error.status === 429) {
                        err = new RequestError('Too many request', error);
                    }
                    else {
                        err = new Error(`Download failed. ID: ${taskId}. Reason: ${error.error}.`);
                        logger.error(error);
                    }
                    config.onError?.(err, config);
                    downloadMeta.reject(err);
                    cleanAndStartNext(downloadMeta);
                }
            };
        };
        const gmDownload = (downloadMeta, errHandler) => {
            const { taskId, config } = downloadMeta;
            const { ontimeout, onerror } = errHandler;
            return GM_download({
                url: config.src,
                name: config.path,
                headers: config.headers,
                ontimeout,
                onerror,
                onprogress(res) {
                    if (res.loaded > 0 && res.total > 0) {
                        const progress = Math.floor((res.loaded / res.total) * 100);
                        config.onProgress?.(progress, config);
                    }
                },
                onload() {
                    cleanAndStartNext(downloadMeta);
                    config.onFileSaved?.(config);
                    downloadMeta.resolve(taskId);
                    logger.info('Download complete:', taskId, config.path);
                }
            });
        };
        const xhr = (downloadMeta, errHandler) => {
            const { taskId, config, timeout } = downloadMeta;
            const { ontimeout, onerror } = errHandler;
            const saveFile = fileSaveAdapters.getAdapter();
            return GM_xmlhttpRequest({
                url: config.src,
                timeout,
                method: 'GET',
                headers: config.headers,
                responseType: 'blob',
                ontimeout,
                onerror,
                onprogress: (res) => {
                    if (res.loaded > 0 && res.total > 0) {
                        const progress = Math.floor((res.loaded / res.total) * 100);
                        config.onProgress?.(progress, config);
                    }
                },
                onload: async (e) => {
                    logger.info('Xhr complete:', config.src);
                    cleanAndStartNext(downloadMeta);
                    if (downloadMeta.isAborted)
                        return logger.warn('Download was canceled.', taskId, config.path);
                    config.onXhrLoaded?.(config);
                    try {
                        let modRes;
                        if (typeof config.beforeFileSave === 'function') {
                            modRes = await config.beforeFileSave(e.response, config);
                            if (modRes && !downloadMeta.isAborted) {
                                await saveFile(modRes, downloadMeta);
                                config.onFileSaved?.(config);
                                logger.info('Download complete:', config.path);
                            }
                        }
                        else {
                            await saveFile(e.response, downloadMeta);
                            config.onFileSaved?.(config);
                            logger.info('Download complete:', config.path);
                        }
                        downloadMeta.resolve(downloadMeta.taskId);
                    }
                    catch (error) {
                        config.onError?.(error, config);
                        downloadMeta.reject(error);
                    }
                }
            });
        };
        const isDirectSaveConfig = (downloadMeta) => {
            return !!downloadMeta.config.directSave;
        };
        const dispatchDownload = (downloadMeta) => {
            logger.info('Start download:', downloadMeta.config.src);
            let abortObj;
            const errHandler = errorHandlerFactory(downloadMeta);
            if (isDirectSaveConfig(downloadMeta)) {
                abortObj = gmDownload(downloadMeta, errHandler);
            }
            else {
                abortObj = xhr(downloadMeta, errHandler);
            }
            downloadMeta.abort = abortObj.abort;
        };
        return {
            get fileSystemAccessEnabled() {
                return fileSaveAdapters.isFileSystemAccessEnable();
            },
            dirHandleCheck() {
                fileSaveAdapters.dirHandleCheck();
            },
            async download(configs) {
                logger.info('Downloader add:', configs);
                if (!Array.isArray(configs))
                    configs = [configs];
                if (configs.length < 1)
                    return Promise.resolve([]);
                const promises = [];
                configs.forEach((config) => {
                    promises.push(new Promise((resolve, reject) => {
                        const downloadMeta = {
                            taskId: config.taskId,
                            config,
                            isAborted: false,
                            retry: 0,
                            timeout: config.timeout,
                            resolve,
                            reject
                        };
                        queue.push(downloadMeta);
                    }));
                });
                while (active.length < MAX_DOWNLOAD && queue.length) {
                    const meta = queue.shift();
                    active.push(meta);
                    dispatchDownload(meta);
                }
                return await Promise.all(promises);
            },
            abort(taskIds) {
                if (typeof taskIds === 'string')
                    taskIds = [taskIds];
                if (!taskIds.length)
                    return;
                logger.info('Downloader delete, active:', active.length, 'queue', queue.length);
                active = active.filter((downloadMeta) => {
                    if (taskIds.includes(downloadMeta.taskId) && !downloadMeta.isAborted) {
                        downloadMeta.isAborted = true;
                        downloadMeta.abort?.();
                        downloadMeta.config.onAbort?.(downloadMeta.config);
                        downloadMeta.reject(new CancelError());
                        logger.warn('Download aborted:', downloadMeta.config.path);
                    }
                    else {
                        return true;
                    }
                });
                queue = queue.filter((downloadMeta) => !taskIds.includes(downloadMeta.taskId));
                while (active.length < MAX_DOWNLOAD && queue.length) {
                    const meta = queue.shift();
                    active.push(meta);
                    dispatchDownload(meta);
                }
            }
        };
    }
    const downloader = createDownloader();

    class HistoryDb extends Dexie__default["default"] {
        history;
        constructor() {
            super('PdlHistory');
            this.version(2).stores({
                history: 'pid, userId, 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 rule34Parser = {
        async parse(id) {
            const res = await fetch('index.php?page=post&s=view&id=' + id);
            if (!res.ok)
                throw new RequestError('Request failed with status code ' + res.status, res);
            const html = await res.text();
            const doc = new DOMParser().parseFromString(html, 'text/html');
            const src = doc.querySelector('#gelcomVideoPlayer > source')?.src ||
                doc.querySelector('meta[property="og:image"]').getAttribute('content');
            const imageNameMatch = /(?<=\/)\w+\.\w+(?=\?)/.exec(src);
            if (!imageNameMatch)
                throw new Error('Can not parse image name from src.');
            const imageName = imageNameMatch[0];
            const [title, extendName] = imageName.split('.');
            const artists = [];
            const characters = [];
            const tags = [];
            const tagEls = doc.querySelectorAll('li[class*="tag-type"]');
            tagEls.forEach((tagEl) => {
                const tagTypeMatch = /(?<=tag-type-)\w+/.exec(tagEl.className);
                if (!tagTypeMatch)
                    throw new Error('Unknown tag: ' + tagEl.className);
                const tagType = tagTypeMatch[0];
                const tag = tagEl.querySelector('a[href*="page=post"]')?.textContent || '';
                if (tagType === 'artist') {
                    artists.push(tag);
                }
                else if (tagType === 'character') {
                    characters.push(tag);
                }
                tags.push(tagType + ':' + tag);
            });
            const uploaderEl = doc.querySelector('a[href*="page=account&s=profile"]');
            const postDateStr = uploaderEl?.parentElement?.firstChild?.nodeValue;
            const postDate = postDateStr ? postDateStr.split(': ')[1] : '';
            const sourceEl = uploaderEl?.parentElement?.nextElementSibling?.nextElementSibling;
            if (sourceEl && sourceEl.textContent?.toLowerCase().includes('source')) {
                const sourceLink = sourceEl.querySelector('a');
                if (sourceLink) {
                    tags.push('source:' + sourceLink.href);
                }
                else {
                    tags.push('source:' + sourceEl.textContent.replace('Source: ', ''));
                }
            }
            return {
                id,
                src,
                extendName,
                artist: artists.join(',') || 'UnknownArtist',
                character: characters.join(',') || 'UnknownCharacter',
                title,
                tags,
                createDate: postDate
            };
        }
    };

    class DownloadConfigBuilder {
        meta;
        constructor(meta) {
            this.meta = meta;
        }
        normalizeString(str) {
            return replaceInvalidChar(unescapeHtml(str));
        }
        getFolderPattern() {
            return config.get('folderPattern');
        }
        getFilenamePattern() {
            return config.get('filenamePattern');
        }
        getFullpathPattern() {
            const folder = this.getFolderPattern();
            const filename = this.getFilenamePattern() + '.' + this.meta.extendName;
            return folder ? folder + '/' + filename : filename;
        }
        isBrowserApi() {
            return env.isBrowserDownloadMode();
        }
        isFsaEnable() {
            return downloader.fileSystemAccessEnabled;
        }
        supportSubpath() {
            return this.isBrowserApi() || this.isFsaEnable();
        }
        isImage() {
            return /bmp|jp(e)?g|png|tif|gif|exif|svg|webp/i.test(this.meta.extendName);
        }
        buildFilePath() {
            const path = this.getFullpathPattern();
            const { id, createDate } = this.meta;
            let { artist, title, tags } = this.meta;
            artist = this.normalizeString(artist);
            title = this.normalizeString(title);
            tags = tags.map((tag) => this.normalizeString(tag));
            const replaceDate = (match, p1) => {
                const format = p1 || 'YYYY-MM-DD';
                return dayjs__default["default"](createDate).format(format);
            };
            return path
                .replaceAll(/\{date\((.*?)\)\}|\{date\}/g, replaceDate)
                .replaceAll('{artist}', artist)
                .replaceAll('{title}', title)
                .replaceAll('{tags}', tags.join('_'))
                .replaceAll('{id}', id);
        }
    }

    function artworkProgressFactory$1(btn) {
        if (!btn)
            return;
        return function onArtworkProgress(progress) {
            btn.setProgress(progress);
        };
    }
    class Rule34DownloadConfig extends DownloadConfigBuilder {
        meta;
        constructor(meta) {
            super(meta);
            this.meta = meta;
        }
        getDownloadConfig(btn) {
            return {
                taskId: Math.random().toString(36).slice(2),
                src: this.meta.src,
                path: this.buildFilePath(),
                source: this.meta,
                timeout: this.isImage() ? 60000 : undefined,
                directSave: downloader.fileSystemAccessEnabled ? false : true,
                onProgress: artworkProgressFactory$1(btn)
            };
        }
        buildFilePath() {
            const path = super.buildFilePath();
            return path.replaceAll('{character}', this.normalizeString(this.meta.character));
        }
    }

    function addBookmark$2(id) {
        unsafeWindow.addFav(id);
    }

    async function downloadArtwork$2(btn) {
        downloader.dirHandleCheck();
        const id = btn.getAttribute('pdl-id');
        const mediaMeta = await rule34Parser.parse(id);
        const { tags, artist, title } = mediaMeta;
        const downloadConfigs = new Rule34DownloadConfig(mediaMeta).getDownloadConfig(btn);
        config.get('addBookmark') && addBookmark$2(id);
        await downloader.download(downloadConfigs);
        const historyData = {
            pid: Number(id),
            user: artist,
            title,
            tags
        };
        historyDb.add(historyData);
    }

    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_export_csv: '导出CSV',
            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_rule34_filename_pattern: '{artist}:作者, {character}:角色, {id}:作品ID, {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_export_csv: 'Export as CSV',
            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_rule34_filename_pattern: '{artist}, {character}, {id}, {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 = navigator.language.toLowerCase();
    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;
    }

    var css$9 = ".pdl-modal{background-color:rgba(0,0,0,.32);color:var(--pdl-text1);display:flex;font-family:'win-bug-omega, system-ui, -apple-system, \"Segoe UI\", Roboto, Ubuntu, Cantarell, \"Noto Sans\", \"Hiragino Kaku Gothic ProN\", Meiryo, sans-serif';height:100%;left:0;line-height:1.15;position:fixed;top:0;user-select:text;width:100%;z-index:99}.pdl-modal .pdl-dialog{background-color:var(--pdl-bg1);border-radius:24px;font-size:16px;margin:auto;max-width:1080px;min-width:680px;position:relative;width:50vw}.pdl-modal .pdl-dialog>.container{margin:20px 40px 30px;overflow:hidden;transition:all .2s}.pdl-modal .pdl-dialog>.container>div{display:flex;flex-direction:column}.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-modal button,.pdl-modal input,.pdl-modal optgroup,.pdl-modal select,.pdl-modal textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}";

    var css$8 = ".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)}";

    var css$7 = ".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}";

    class PdlDialog extends HTMLElement {
        ob;
        controller = null;
        constructor() {
            super();
            this.render();
        }
        render() {
            const shadowRoot = this.attachShadow({ mode: 'open' });
            shadowRoot.innerHTML = `
    <style>${css$7 + css$9 + css$8}</style>
    <div class="pdl-modal">
      <div class="pdl-dialog">
        <button class="pdl-dialog-close"></button>
        <div class="container">
          <div>
            <header class="pdl-dialog-header"></header>
            <div class="pdl-dialog-content"></div>
            <footer class="pdl-dialog-footer"></footer>
          </div>
        </div>
      </div> 
    </div>`;
        }
        connectedCallback() {
            const shadowRoot = this.shadowRoot;
            const modal = shadowRoot.querySelector('.pdl-modal');
            const closeBtn = modal.querySelector('.pdl-dialog-close');
            this.controller = new AbortController();
            const signal = this.controller.signal;
            window.addEventListener('keydown', (evt) => {
                evt.stopPropagation();
            }, { capture: true, signal });
            modal.addEventListener('click', (evt) => {
                const closeOnClickModal = this.hasAttribute('close-on-click-modal');
                closeOnClickModal && evt.target === modal && this.remove();
            }, { signal });
            closeBtn.addEventListener('click', () => {
                this.remove();
            }, { signal });
            const container = shadowRoot.querySelector('.pdl-dialog > .container');
            const obEl = container.firstElementChild;
            this.ob = new ResizeObserver(() => {
                const height = obEl.offsetHeight;
                container.style.height = height + 'px';
            });
            this.ob.observe(obEl);
        }
        disconnectedCallback() {
            this.controller?.abort();
            this.controller = null;
            this.ob.disconnect();
        }
    }
    customElements.define('pdl-dialog', PdlDialog);
    function createModal(args, option = { closeOnClickModal: false }) {
        const el = document.createElement('pdl-dialog');
        if (option.closeOnClickModal) {
            el.setAttribute('close-on-click-modal', '');
        }
        const shadowRoot = el.shadowRoot;
        const header = shadowRoot.querySelector('.pdl-dialog-header');
        const content = shadowRoot.querySelector('.pdl-dialog-content');
        const footer = shadowRoot.querySelector('.pdl-dialog-footer');
        args.header && header.appendChild(args.header);
        args.footer && footer.appendChild(args.footer);
        content.appendChild(args.content);
        return el;
    }

    function createTabUgoira() {
        const tabHtml = `<div class="pdl-tab-item ${!env.isPixiv() && 'pdl-hide'}">${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>
    ${env.isPixiv()
        ? `<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_tag_translation')}</p>`
        : `<p style="font-size: 14px; margin: 0.5em 0">${t('text.tips_rule34_filename_pattern')}</p>`}
    <p style="font-size: 14px; margin: 0.5em 0">${t('text.tips_subfolder_unused')}</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 = fsaHandler.getCurrentDirName();
            changeDirBtn.addEventListener('click', async () => {
                await fsaHandler.updateDirHandle();
                showDir.value = fsaHandler.getCurrentDirName();
            });
            actionInput.forEach((input) => {
                if (conflictAction === input.value)
                    input.checked = true;
                input.addEventListener('change', (ev) => {
                    config.set('fileSystemFilenameConflictAction', ev.currentTarget.value);
                });
            });
        }
        return {
            tab,
            pane
        };
    }

    var css$6 = "@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-family:'win-bug-omega, system-ui, -apple-system, \"Segoe UI\", Roboto, Ubuntu, Cantarell, \"Noto Sans\", \"Hiragino Kaku Gothic ProN\", Meiryo, sans-serif';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);z-index:1}.pdl-btn.pdl-btn-sub.presentation{border-radius:8px;left:auto;position:fixed;right:20px;top:50px}.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)}._history-item>.pdl-btn.pdl-btn-sub{z-index:auto}.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.rule34{bottom:calc(22px + 1em)}.pdl-wrap-artworks .pdl-btn-sub.artworks{left:0;position:sticky;top:40px}:root .pdl-btn-main,:root[data-theme=default] .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='%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\")}@media (prefers-color-scheme:light){: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='%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\")}}: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\")}@media (prefers-color-scheme:dark){: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\")}}";

    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">
  <style>${css$6}</style>
  <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>
      ${env.isPixiv()
        ? `<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>
        ${env.isPixiv() ? `<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>
      <button id="pdl-export-csv" class="btn-history pdl-dialog-button primary">
        ${t('button.history_export_csv')}
      </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 exportCsvBtn = pane.querySelector('#pdl-export-csv');
        exportCsvBtn?.addEventListener('click', () => {
            historyDb.getAll().then((datas) => {
                const csvData = datas.map((historyData) => {
                    const { pid, userId = '', user = '', title = '', tags = '' } = historyData;
                    return [String(pid), String(userId), user, title, tags ? tags.join(',') : tags];
                });
                csvData.unshift(['id', 'userId', 'user', 'title', 'tags']);
                const csv = generateCsv(csvData);
                const dlEle = document.createElement('a');
                dlEle.href = URL.createObjectURL(csv);
                dlEle.download = 'Pixiv Downloader ' + new Date().toLocaleString() + '.csv';
                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
        };
    }

    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">
  ${env.isPixiv()
        ? `<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 />
    ${env.isPixiv()
        ? `<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) {
                dispatchEvent(new CustomEvent("popupBtn.show" ));
            }
            else {
                dispatchEvent(new CustomEvent("popupBtn.hide" ));
            }
        });
        return {
            tab,
            pane
        };
    }

    const regexp = {
        preloadData: /"meta-preload-data" content='(.*?)'>/,
        globalData: /"meta-global-data" content='(.*?)'>/,
        artworksPage: /artworks\/(\d+)$/,
        userPage: /\/users\/(\d+)$|\/users\/(\d+)\/(?!following|mypixiv|followers)/,
        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 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 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://sleazyfork.org/zh-CN/scripts/432150-pixiv-downloader/feedback"
        >${t('text.feedback')}</a
      >
    </p>
  </div>
</div>`;
        return {
            tab: stringToFragment(tabHtml),
            pane: stringToFragment(paneHtml)
        };
    }

    var css$5 = ".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}";

    function showSettings() {
        if (document.querySelector('pdl-dialog'))
            return;
        const contentHtml = `
<style>${css$5}</style>
<div>
  <div class="pdl-tabs-nav">
    <div class="pdl-tabs__active-bar"></div>
  </div>
  <div class="pdl-tabs-content"></div>
</div>`;
        const content = stringToFragment(contentHtml);
        const tabsNav = content.querySelector('.pdl-tabs-nav');
        const tabContent = content.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(content.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');
        const el = createModal({
            content
        });
        document.body.appendChild(el);
        activeBar.style.width = getComputedStyle(tabs[0]).width;
        activeBar.style.transform = `translateX(${tabs[0].offsetLeft + parseFloat(getComputedStyle(tabs[0]).paddingLeft)}px)`;
    }

    function createPopupBtn(isShow = false) {
        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>`;
        !isShow && (btn.style.display = 'none');
        const popupBtn = {
            show() {
                btn.style.display = 'block';
            },
            hide() {
                btn.style.display = 'none';
            }
        };
        btn.addEventListener('click', () => {
            dispatchEvent(new CustomEvent("popupBtn.click" ));
        });
        addEventListener("popupBtn.show" , popupBtn.show);
        addEventListener("popupBtn.hide" , popupBtn.hide);
        document.body.appendChild(btn);
    }

    function showUpgradeMsg() {
        const headerHtml = `<h3>Pixiv Downloader ${config.get('version')}</h3>`;
        const contentHtml = `
<div class="pdl-changelog">
<style>
  li {
    line-height: 2;
  }
  p {
    margin: 0.5em 0;
  }
</style>
  <ul>
    <li>现在可以在Danbooru上使用插件:<a href="https://danbooru.donmai.us/">Danbooru</a>。</li>
    <li>支持在Rule34的comment页和pool页上下载图片。</li>
    <li>对转换ugoira做了一些优化。</li>
    <li>修正打开插件设置时键盘按键会触发页面跳转的问题。</li>
  </ul>
</div>`;
        const footerHtml = `
<style>
  .pdl-dialog-footer {
    position: relative;
    font-size: 12px;
    margin-top: 1.5em;
  }
</style>
<details>
  <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://sleazyfork.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)
        }));
    }

    var css$4 = ":root{--pdl-btn-top:100;--pdl-btn-left:0;--pdl-btn-self-bookmark-top:75;--pdl-btn-self-bookmark-left:100}";

    var css$3 = ":root,:root body[data-current-user-theme=light],: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 body[data-current-user-theme=dark],: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:#f5f5f5}@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:#f5f5f5}}pdl-button{--pdl-green1:#01b468;--pdl-black1:#3c3c3c;--pdl-red1:#ea0000}pdl-button,pdl-button[status=init]{--pdl-fill-svg:var(--pdl-black1)}pdl-button[type=pixiv-toolbar]{--pdl-fill-svg:var(--pdl-text1)}pdl-button[status]{--pdl-fill-svg:var(--pdl-green1)}pdl-button[status=error]{--pdl-fill-svg:var(--pdl-red1)}";

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

    class SiteInject {
        constructor() {
            this.addStyle();
            this.init();
        }
        init() {
            GM_registerMenuCommand(t('text.gm_menu'), showSettings, 's');
            addEventListener("popupBtn.click" , showSettings);
            if (config.get('showMsg')) {
                showUpgradeMsg();
                config.set('showMsg', false);
            }
            createPopupBtn(config.get('showPopupButton'));
            ['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);
                }
            });
        }
        addStyle() {
            [css$7, css$4, css$3, css$6, css$2].forEach((style) => GM_addStyle(style));
        }
    }

    var css$1 = ".pdl-thumbnail{align-items:center;background-color:hsla(0,0%,100%,.5);border:none;border-radius:4px;color:var(--pdl-green1);cursor:pointer;display:flex;font-family:'win-bug-omega, system-ui, -apple-system, \"Segoe UI\", Roboto, Ubuntu, Cantarell, \"Noto Sans\", \"Hiragino Kaku Gothic ProN\", Meiryo, sans-serif';font-size:13px;font-weight:700;height:32px;justify-content:center;left:calc((100% - 32px)*var(--pdl-btn-left)/100);margin:0;overflow:hidden;padding:0;position:absolute;top:calc((100% - 32px)*var(--pdl-btn-top)/100);user-select:none;white-space:nowrap;width:32px;z-index:1}.pdl-thumbnail:disabled{cursor:default}.pdl-thumbnail>svg{fill:var(--pdl-fill-svg);stroke:var(--pdl-fill-svg);height:85%;position:absolute;width:85%}.pdl-thumbnail>span{opacity:0;transition:opacity .2s}.pdl-thumbnail>span.show{opacity:1}:host([type=gallery])::part(button){left:0;position:sticky;top:40px}:host([type=pixiv-my-bookmark])::part(button){left:calc((100% - 32px)*var(--pdl-btn-self-bookmark-left)/100);top:calc((100% - 32px)*var(--pdl-btn-self-bookmark-top)/100)}:host([type=pixiv-history])::part(button){z-index:auto}:host([type=pixiv-presentation])::part(button){left:auto;position:fixed;right:20px;top:50px}:host([type=pixiv-toolbar])::part(button){background-color:transparent;left:auto;position:relative;top:auto}:host([type=pixiv-manga-viewer])::part(button){left:auto;right:4px;top:80%}";

    var svgGroup = "<svg xmlns=\"http://www.w3.org/2000/svg\" style=\"display: none\">\n  <symbol id=\"pdl-download\" viewBox=\"0 0 512 512\">\n    <path\n      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\"\n    ></path>\n  </symbol>\n\n  <symbol id=\"pdl-loading\" viewBox=\"0 0 512 512\">\n    <style>\n      @keyframes pdl-loading {\n        0% {\n          transform: rotate3d(0, 0, 1, -90deg) rotate3d(1, 0, 0, 0deg);\n          stroke-dashoffset: 1407.43;\n        }\n\n        49.99% {\n          transform: rotate3d(0, 0, 1, 90deg) rotate3d(1, 0, 0, 0deg);\n        }\n\n        50% {\n          transform: rotate3d(0, 0, 1, 90deg) rotate3d(1, 0, 0, 180deg);\n          stroke-dashoffset: 0;\n        }\n\n        100% {\n          transform: rotate3d(0, 0, 1, 270deg) rotate3d(1, 0, 0, 180deg);\n          stroke-dashoffset: 1407.43;\n        }\n      }\n\n      circle.rotate {\n        transform-origin: 50% 50%;\n        animation: 2.5s infinite ease-in-out pdl-loading;\n      }\n    </style>\n    <circle\n      class=\"rotate\"\n      cx=\"256\"\n      cy=\"256\"\n      r=\"224\"\n      stroke-width=\"48\"\n      fill=\"none\"\n      stroke-dasharray=\"1407.43\"\n      stroke-dashoffset=\"1055.57\"\n      stroke-linecap=\"round\"\n    ></circle>\n  </symbol>\n\n  <symbol id=\"pdl-progress\" viewBox=\"0 0 512 512\">\n    <style>\n      circle.progress {\n        transition: stroke-dashoffset 0.2s ease;\n      }\n    </style>\n    <circle\n      class=\"progress\"\n      cx=\"256\"\n      cy=\"256\"\n      r=\"224\"\n      stroke-width=\"48\"\n      fill=\"none\"\n      stroke-dasharray=\"1407.43\"\n      stroke-linecap=\"round\"\n      transform=\"rotate(-90 256 256)\"\n    ></circle>\n  </symbol>\n\n  <symbol id=\"pdl-error\" viewBox=\"0 0 512 512\">\n    <path\n      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\"\n    ></path>\n  </symbol>\n\n  <symbol id=\"pdl-complete\" viewBox=\"0 0 512 512\">\n    <path\n      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\"\n    ></path>\n  </symbol>\n</svg>\n";

    const iconTypeMap = {
        init: '#pdl-download',
        loading: '#pdl-loading',
        progress: '#pdl-progress',
        complete: '#pdl-complete',
        error: '#pdl-error'
    };
    class ThumbnailButton extends HTMLElement {
        status;
        mediaId;
        page;
        type;
        onClick;
        constructor(props) {
            super();
            this.status = "init" ;
            this.mediaId = props.id;
            this.page = props.page;
            this.type = props.type;
            this.onClick = props.onClick;
            this.render();
        }
        static get observedAttributes() {
            return ['status', 'page', 'disabled'];
        }
        attributeChangedCallback(name, oldValue, newValue) {
            if (name === 'status') {
                this.updateIcon(newValue);
            }
            else if (name === 'page') {
                this.updatePage(newValue);
            }
            else {
                this.updateDisableStatus(newValue);
            }
        }
        updateDisableStatus(val) {
            const btn = this.shadowRoot.querySelector('button');
            if (typeof val === 'string') {
                btn.setAttribute('disabled', '');
            }
            else {
                btn.removeAttribute('disabled');
            }
        }
        updatePage(page) {
            const pageNum = Number(page);
            if (!Number.isNaN(pageNum) && pageNum >= 0) {
                this.page = pageNum;
            }
        }
        updateIcon(status) {
            if (status === null || !(status in iconTypeMap))
                return;
            const useEl = this.shadowRoot.querySelector('use');
            this.status = status;
            useEl.setAttribute('xlink:href', iconTypeMap[status]);
            useEl.animate([
                {
                    opacity: 0.5
                },
                {
                    opactiy: 1
                }
            ], {
                duration: 200
            });
        }
        render() {
            const shadowRoot = this.attachShadow({ mode: 'open' });
            shadowRoot.innerHTML = `    <style>${css$1}</style>${svgGroup}<button part="button" class="pdl-thumbnail">
      <svg xmlns="http://www.w3.org/2000/svg" class="pdl-icon">
        <use xlink:href="#pdl-download"></use>
      </svg>
      <span></span>
    </button>`;
            this.type !== "danbooru-pool"  &&
                historyDb.has(this.mediaId).then((downloaded) => {
                    downloaded && this.setStatus("complete" );
                });
            this.setAttribute('pdl-id', this.mediaId);
            this.page !== undefined && !Number.isNaN(this.page) && this.setAttribute('page', String(this.page));
            this.type && this.setAttribute('type', this.type);
        }
        connectedCallback() {
            this.shadowRoot.lastElementChild.addEventListener('click', (evt) => {
                evt.preventDefault();
                evt.stopPropagation();
                this.setAttribute('disabled', '');
                this.setStatus("loading" );
                Promise.resolve(this.onClick(this))
                    .then(() => {
                    this.setStatus("complete" );
                }, (err) => {
                    if (err)
                        logger.error(err);
                    this.setStatus("error" );
                })
                    .finally(() => {
                    this.removeAttribute('disabled');
                });
            });
        }
        setProgress(progress, updateProgressbar = true) {
            if (progress < 0 || progress > 100)
                throw new RangeError('Value "progress" must between 0-100');
            const shadowRoot = this.shadowRoot;
            const span = shadowRoot.querySelector('span');
            if (this.status !== "progress" ) {
                this.setAttribute('status', "progress" );
                span.classList.toggle('show');
            }
            span.textContent = String(Math.floor(progress));
            if (!updateProgressbar)
                return;
            const svg = shadowRoot.querySelector('svg.pdl-icon');
            const radius = 224;
            const circumference = 2 * Math.PI * radius;
            const offset = circumference - (progress / 100) * circumference;
            svg.style.strokeDashoffset = String(offset);
        }
        removeProgress() {
            const shadowRoot = this.shadowRoot;
            const span = shadowRoot.querySelector('span');
            const svg = shadowRoot.querySelector('svg.pdl-icon');
            span.classList.toggle('show');
            span.addEventListener('transitionend', () => {
                span.textContent = '';
            }, { once: true });
            svg.style.removeProperty('stroke-dashoffset');
            if (this.status === "progress" )
                this.setAttribute('status', "init" );
        }
        setStatus(status) {
            if (status !== this.status) {
                if (status === "progress" ) {
                    this.setProgress(0);
                    return;
                }
                if (this.status === "progress" ) {
                    this.removeProgress();
                }
                this.setAttribute('status', status);
            }
        }
    }
    customElements.define('pdl-button', ThumbnailButton);

    class Rule34 extends SiteInject {
        init() {
            super.init();
            this.pageAction();
        }
        createThumbnailBtn() {
            const btnContainers = document.querySelectorAll('.thumb:not(.blacklisted-image) > a:first-child');
            if (!btnContainers.length)
                return;
            btnContainers.forEach((el) => {
                el.style.display = 'inline-block';
                el.style.position = 'relative';
                const imgEl = el.querySelector('img');
                imgEl.style.boxSizing = 'border-box';
                let aspectRatio = imgEl.naturalHeight / imgEl.naturalWidth;
                aspectRatio > 1 && (el.style.height = 'inherit');
                imgEl.onload = () => {
                    aspectRatio = imgEl.naturalHeight / imgEl.naturalWidth;
                    aspectRatio > 1 && (el.style.height = 'inherit');
                };
                const idMathch = /(?<=&id=)\d+/.exec(el.href);
                if (!idMathch)
                    return;
                const id = idMathch[0];
                el.appendChild(new ThumbnailButton({
                    id,
                    onClick: downloadArtwork$2
                }));
            });
        }
        createArtworkBtn(id) {
            const btnContainer = document.querySelector('div.flexi > div');
            btnContainer.style.position = 'relative';
            const wrapper = document.createElement('div');
            wrapper.classList.add('pdl-wrap-artworks', 'rule34');
            const btn = new ThumbnailButton({
                id,
                type: "gallery" ,
                onClick: downloadArtwork$2
            });
            wrapper.appendChild(btn);
            btnContainer.appendChild(wrapper);
        }
        pageAction() {
            const query = location.search;
            if (!query)
                return;
            const searchParams = new URLSearchParams(query);
            const page = searchParams.get('page');
            const s = searchParams.get('s');
            if (page === 'post' && s === 'view') {
                if (!document.querySelector('#image, #gelcomVideoPlayer'))
                    return;
                const id = searchParams.get('id');
                this.createArtworkBtn(id);
            }
            else {
                this.createThumbnailBtn();
            }
        }
    }

    async function addBookmark$1(id) {
        try {
            const token = document.head.querySelector('meta[name="csrf-token"]')?.content;
            if (!token)
                throw new Error('Can not get csrf-token');
            const res = await fetch('/favorites?post_id=' + id, {
                method: 'POST',
                headers: {
                    'X-Csrf-Token': token
                }
            });
            if (!res.ok)
                throw new Error(res.status + ' ' + res.statusText);
            const galleryMatch = /(?<=^\/posts\/)\d+/.exec(location.pathname);
            if (galleryMatch && id !== galleryMatch[0]) {
                unsafeWindow.Danbooru.Utility.notice('You have favorited ' + id);
            }
            else {
                const script = await res.text();
                evalScript(script);
            }
        }
        catch (error) {
            logger.error(error);
        }
    }

    const danbooruParser = {
        async getDoc(url) {
            const res = await fetch(url);
            if (!res.ok)
                throw new RequestError('Request failed with status code ' + res.status, res);
            const html = await res.text();
            return new DOMParser().parseFromString(html, 'text/html');
        },
        async parse(id) {
            const doc = await this.getDoc('/posts/' + id);
            const src = doc.querySelector('a[download]')?.href;
            if (!src)
                throw new Error('Can not get media src');
            const ogImageMeta = doc.querySelector('meta[property="og:image"]');
            const mediaSrc = ogImageMeta.getAttribute('content');
            const title = mediaSrc.slice(mediaSrc.lastIndexOf('/') + 1).split('.')[0];
            const ogTypeMeta = doc.querySelector('meta[property="og:video:type"]') || doc.querySelector('meta[property="og:image:type"]');
            const mimeType = ogTypeMeta.getAttribute('content');
            const extendName = mimeType.slice(mimeType.lastIndexOf('/') + 1);
            const artists = [];
            const characters = [];
            const tags = [];
            const tagLists = doc.querySelectorAll('section#tag-list  ul[class*="-tag-list"]');
            if (tagLists.length) {
                tagLists.forEach((ul) => {
                    const tagTypeMatch = /[a-zA-Z]+(?=-tag-list)/.exec(ul.className);
                    if (!tagTypeMatch)
                        throw new Error('Unknown tag: ' + ul.className);
                    const tagType = tagTypeMatch[0];
                    const liEls = ul.children;
                    let tagRef;
                    if (tagType === 'artist') {
                        tagRef = artists;
                    }
                    else if (tagType === 'character') {
                        tagRef = characters;
                    }
                    for (let i = 0; i < liEls.length; i++) {
                        const tag = liEls[i].getAttribute('data-tag-name');
                        if (!tag)
                            continue;
                        tagRef && tagRef.push(tag);
                        tags.push(tagType + ':' + tag);
                    }
                });
            }
            const postDate = doc.querySelector('time')?.getAttribute('datetime') ?? '';
            const source = doc.querySelector('li#post-info-source > a')?.href;
            if (source)
                tags.push('source:' + source);
            return {
                id,
                src,
                extendName,
                artist: artists.join(',') || 'UnknownArtist',
                character: characters.join(',') || 'UnknownCharacter',
                title,
                tags,
                createDate: postDate
            };
        },
        async getPoolPostCount(poolId) {
            const doc = await this.getDoc(`/pools/${poolId}`);
            const nextEl = doc.querySelector('a.paginator-next');
            if (nextEl) {
                const lastPageEl = nextEl.previousElementSibling;
                const poolPageCount = Number(lastPageEl.textContent);
                const lastPageDoc = await this.getDoc(lastPageEl.href);
                const postPerPage = Number(lastPageDoc.body.getAttribute('data-current-user-per-page'));
                const lastPagePostCount = lastPageDoc.querySelectorAll('.posts-container article').length;
                return (poolPageCount - 1) * postPerPage + lastPagePostCount;
            }
            else {
                const imageContainers = doc.querySelectorAll('.posts-container article');
                return imageContainers.length;
            }
        },
        async *genIdByPool(poolId, filter) {
            let page = 0;
            let nextUrl;
            do {
                ++page > 1 && (await sleep(1000));
                const doc = await this.getDoc(`/pools/${poolId}?page=${page}`);
                const nextEl = doc.querySelector('a.paginator-next');
                nextUrl = nextEl?.getAttribute('href') ?? '';
                const imageContainers = doc.querySelectorAll('.posts-container article');
                const ids = Array.from(imageContainers).map((el) => el.getAttribute('data-id'));
                for (let i = 0; i < ids.length; i++) {
                    const id = ids[i];
                    const isValid = (await filter?.(id)) ?? true;
                    if (isValid) {
                        yield id;
                        i !== id.length - 1 && (await sleep(1000));
                    }
                }
            } while (nextUrl);
        }
    };

    function artworkProgressFactory(btn) {
        if (!btn)
            return;
        return function onArtworkProgress(progress) {
            btn.setProgress(progress);
        };
    }
    class DanbooruDownloadConfig extends DownloadConfigBuilder {
        meta;
        constructor(meta) {
            super(meta);
            this.meta = meta;
        }
        getDownloadConfig(btn) {
            return {
                taskId: Math.random().toString(36).slice(2),
                src: this.meta.src,
                path: this.buildFilePath(),
                source: this.meta,
                timeout: this.isImage() ? 60000 : undefined,
                directSave: downloader.fileSystemAccessEnabled ? false : true,
                onProgress: artworkProgressFactory(btn)
            };
        }
        buildFilePath() {
            const path = super.buildFilePath();
            return path.replaceAll('{character}', this.normalizeString(this.meta.character));
        }
    }

    async function downloadArtwork$1(btn) {
        downloader.dirHandleCheck();
        const id = btn.getAttribute('pdl-id');
        const mediaMeta = await danbooruParser.parse(id);
        const { tags, artist, title } = mediaMeta;
        const downloadConfigs = new DanbooruDownloadConfig(mediaMeta).getDownloadConfig(btn);
        config.get('addBookmark') && addBookmark$1(id);
        await downloader.download(downloadConfigs);
        const historyData = {
            pid: Number(id),
            user: artist,
            title,
            tags
        };
        historyDb.add(historyData);
    }

    async function downloadPoolArtwork(btn) {
        downloader.dirHandleCheck();
        const poolId = btn.getAttribute('pdl-id');
        const promises = [];
        const postCount = await danbooruParser.getPoolPostCount(poolId);
        let completed = 0;
        const filter = async (id) => !(await historyDb.has(id));
        const idGen = danbooruParser.genIdByPool(poolId, filter);
        for await (const id of idGen) {
            const mediaMeta = await danbooruParser.parse(id);
            const downloadConfigs = new DanbooruDownloadConfig(mediaMeta).getDownloadConfig();
            const p = downloader
                .download(downloadConfigs)
                .then(() => {
                completed++;
                btn.setProgress((completed / postCount) * 100);
            })
                .then(() => {
                const { tags, artist, title } = mediaMeta;
                const historyData = {
                    pid: Number(id),
                    user: artist,
                    title,
                    tags
                };
                historyDb.add(historyData);
            });
            promises.push(p);
        }
        const results = await Promise.allSettled(promises);
        const rejectedTasks = results.filter((result) => result.status === 'rejected');
        if (rejectedTasks.length) {
            rejectedTasks.length > 1 && logger.error(rejectedTasks);
            throw rejectedTasks[0].reason;
        }
    }

    class Danbooru extends SiteInject {
        init() {
            super.init();
            this.pageAction();
        }
        createThumbnailBtn() {
            const btnContainers = document.querySelectorAll('article a.post-preview-link');
            if (!btnContainers.length)
                return;
            btnContainers.forEach((el) => {
                const id = /(?<=\/posts\/)\d+/.exec(el.href)?.[0];
                if (!id)
                    return;
                const btn = new ThumbnailButton({
                    id,
                    onClick: downloadArtwork$1
                });
                el.appendChild(btn);
            });
        }
        createArtworkBtn(id) {
            const btnContainer = document.querySelector('section.image-container');
            const btn = new ThumbnailButton({
                id,
                type: "gallery" ,
                onClick: downloadArtwork$1
            });
            const wrapper = document.createElement('div');
            wrapper.classList.add('pdl-wrap-artworks');
            wrapper.appendChild(btn);
            btnContainer.appendChild(wrapper);
        }
        createPoolThumbnailBtn() {
            const btnContainers = document.querySelectorAll('article a.post-preview-link');
            if (!btnContainers.length)
                return;
            btnContainers.forEach((el) => {
                const poolId = /(?<=\/pools\/)\d+/.exec(el.href)?.[0];
                if (!poolId)
                    return;
                const btn = new ThumbnailButton({
                    id: poolId,
                    type: "danbooru-pool" ,
                    onClick: downloadPoolArtwork
                });
                el.appendChild(btn);
            });
        }
        pageAction() {
            const path = location.pathname;
            if (/^\/posts\/\d+/.test(path)) {
                const imageContainer = document.querySelector('section.image-container:not(.blacklisted-active)');
                if (!imageContainer)
                    return;
                const id = imageContainer.getAttribute('data-id');
                this.createArtworkBtn(id);
                this.createThumbnailBtn();
            }
            else if (/^\/pools\/gallery/.test(path)) {
                this.createPoolThumbnailBtn();
            }
            else {
                this.createThumbnailBtn();
            }
        }
    }

    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 '';
    }

    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 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;
        }
        return {
            async getJson(url) {
                return await _requestJson(url);
            },
            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();
            },
            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(btn, 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(btn);
            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(btn) {
        const bookmarkBtnRef = {};
        if (!btn.getAttribute('type')) {
            const favBtn = btn.parentElement?.nextElementSibling?.querySelector('button[type="button"]');
            if (favBtn) {
                bookmarkBtnRef.kind = "sub" ;
                bookmarkBtnRef.button = favBtn;
            }
            else {
                const favBtn = btn.parentElement?.querySelector('div._one-click-bookmark');
                if (favBtn) {
                    bookmarkBtnRef.kind = "rank" ;
                    bookmarkBtnRef.button = favBtn;
                }
            }
        }
        else if (btn.getAttribute('type') === "pixiv-toolbar" ) {
            const favBtn = btn.parentElement?.parentElement?.querySelector('button.gtm-main-bookmark');
            if (favBtn) {
                bookmarkBtnRef.kind = "main" ;
                bookmarkBtnRef.button = favBtn;
            }
        }
        else {
            return logger.warn(new Error('Can not find bookmark button.'));
        }
        return bookmarkBtnRef;
    }

    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;
    }
    const pixivParser = {
        async parse(illustId) {
            const htmlText = await api.getArtworkHtml(illustId);
            const preloadDataText = htmlText.match(regexp.preloadData);
            if (!preloadDataText)
                throw new Error('Fail to parse preload data.');
            const globalDataText = htmlText.match(regexp.globalData);
            if (!globalDataText)
                throw new Error('Fail to parse global data.');
            const preloadData = JSON.parse(preloadDataText[1]);
            const globalData = JSON.parse(globalDataText[1]);
            const illustData = preloadData.illust[illustId];
            const { illustType, userName, userId, illustTitle, tags, pageCount, createDate, urls, bookmarkData } = illustData;
            const { token } = globalData;
            const tagsArr = [];
            const tagsTranslatedArr = [];
            tags.tags.forEach((tagData) => {
                tagsArr.push(tagData.tag);
                tagsTranslatedArr.push(tagData.translation?.en || tagData.tag);
            });
            const meta = {
                id: illustId,
                src: urls.original,
                extendName: urls.original.slice(-3),
                artist: userName,
                title: illustTitle,
                tags: tagsArr,
                tagsTranslated: tagsTranslatedArr,
                userId,
                pageCount,
                bookmarkData,
                createDate,
                token
            };
            if (illustType === IllustType.ugoira) {
                return {
                    ...meta,
                    illustType,
                    ugoiraMeta: await api.getUgoiraMeta(illustId)
                };
            }
            else {
                return {
                    ...meta,
                    illustType
                };
            }
        },
        async 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 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 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()
            };
        }
    };

    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 workerUrl$2 = URL.createObjectURL(new Blob([GM_getResourceText('gifWorker')], { type: 'text/javascript' }));
    function gif(frames, convertMeta) {
        return Promise.all(frames.map((frame) => createImageBitmap(frame))).then((bitmaps) => {
            return new Promise((resolve, reject) => {
                logger.info('Start convert:', convertMeta.id);
                logger.time(convertMeta.id);
                const canvas = document.createElement('canvas');
                const width = (canvas.width = bitmaps[0].width);
                const height = (canvas.height = bitmaps[0].height);
                const ctx = canvas.getContext('2d', { willReadFrequently: true });
                const gif = new GIF__default["default"]({
                    workers: 2,
                    quality: 10,
                    width,
                    height,
                    workerScript: workerUrl$2
                });
                convertMeta.abort = () => {
                    gif.abort();
                };
                bitmaps.forEach((bitmap, i) => {
                    ctx.drawImage(bitmap, 0, 0);
                    gif.addFrame(ctx, {
                        copy: true,
                        delay: convertMeta.source.delays[i]
                    });
                });
                gif.on('progress', (progress) => {
                    convertMeta.onProgress?.(progress * 100);
                });
                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();
            });
        });
    }

    var pngWorkerFragment = "onmessage = async (evt) => {\n  const { frames, delay, cnum = 0 } = evt.data;\n  const bitmaps = await Promise.all(frames.map((blob) => createImageBitmap(blob)));\n\n  const width = bitmaps[0].width;\n  const height = bitmaps[0].height;\n  const canvas = new OffscreenCanvas(width, height);\n  const ctx = canvas.getContext('2d', { willReadFrequently: true });\n  const u8arrs = [];\n\n  for (let i = 0; i < bitmaps.length; i++) {\n    ctx?.drawImage(bitmaps[i], 0, 0);\n    u8arrs.push(ctx?.getImageData(0, 0, width, height).data);\n  }\n\n  const png = UPNG.encode(u8arrs, width, height, cnum, delay, { loop: 0 });\n  if (!png) console.error('Convert Apng failed.');\n  postMessage(png, [png]);\n};\n";

    const pako = GM_getResourceText('pako');
    const upng = GM_getResourceText('upng');
    const workerUrl$1 = URL.createObjectURL(new Blob([pngWorkerFragment + pako + upng.replace('window.UPNG', 'self.UPNG').replace('window.pako', 'self.pako')], {
        type: 'text/javascript'
    }));
    const freeApngWorkers = [];
    function png(frames, convertMeta) {
        return new Promise((resolve, reject) => {
            logger.info('Start convert:', convertMeta.id);
            logger.time(convertMeta.id);
            let worker;
            if (freeApngWorkers.length) {
                worker = freeApngWorkers.shift();
                logger.info('Reuse apng workers.');
            }
            else {
                worker = new Worker(workerUrl$1);
            }
            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 delay = convertMeta.source.delays;
            const cfg = { frames, delay };
            worker.postMessage(cfg);
        });
    }

    var webpWorkerFragment = "// Lossless encoding (0=lossy(default), 1=lossless).\n// quality: between 0 and 100. For lossy, 0 gives the smallest\n// size and 100 the largest. For lossless, this\n// parameter is the amount of effort put into the\n// compression: 0 is the fastest but gives larger\n// files compared to the slowest, but best, 100.\n// method: quality/speed trade-off (0=fast, 6=slower-better)\n\nlet webpApi = {};\nModule.onRuntimeInitialized = () => {\n  webpApi = {\n    init: Module.cwrap('init', '', ['number', 'number', 'number']),\n    createBuffer: Module.cwrap('createBuffer', 'number', ['number']),\n    addFrame: Module.cwrap('addFrame', 'number', ['number', 'number', 'number']),\n    generate: Module.cwrap('generate', 'number', []),\n    freeResult: Module.cwrap('freeResult', '', []),\n    getResultPointer: Module.cwrap('getResultPointer', 'number', []),\n    getResultSize: Module.cwrap('getResultSize', 'number', [])\n  };\n\n  postMessage('ok');\n};\n\nonmessage = async (evt) => {\n  const { frames, delays, lossless = 1, quality = 75, method = 4 } = evt.data;\n\n  webpApi.init(lossless, quality, method);\n\n  const bitmaps = await Promise.all(frames.map((blob) => createImageBitmap(blob)));\n  const width = bitmaps[0].width;\n  const height = bitmaps[0].height;\n  const canvas = new OffscreenCanvas(width, height);\n  const ctx = canvas.getContext('2d');\n\n  for (let i = 0; i < bitmaps.length; i++) {\n    ctx?.drawImage(bitmaps[i], 0, 0);\n    const webpBlob = await canvas.convertToBlob({ type: 'image/webp', quality: 1 });\n    const buffer = await webpBlob.arrayBuffer();\n    const u8a = new Uint8Array(buffer);\n    const pointer = webpApi.createBuffer(u8a.length);\n\n    Module.HEAPU8.set(u8a, pointer);\n    webpApi.addFrame(pointer, u8a.length, delays[i]);\n    postMessage(((i + 1) / bitmaps.length) * 100);\n  }\n\n  webpApi.generate();\n  const resultPointer = webpApi.getResultPointer();\n  const resultSize = webpApi.getResultSize();\n  const result = new Uint8Array(Module.HEAP8.buffer, resultPointer, resultSize);\n  postMessage(result);\n  webpApi.freeResult();\n};\n";

    const workerUrl = URL.createObjectURL(new Blob([workerChunk__default["default"] + webpWorkerFragment], { type: 'text/javascript' }));
    const freeWebpWorkers = [];
    function webp(frames, convertMeta) {
        return new Promise((resolve, reject) => {
            logger.time(convertMeta.id);
            let worker;
            if (freeWebpWorkers.length) {
                logger.info('Reuse webp workers.');
                worker = freeWebpWorkers.shift();
                resolve(worker);
            }
            else {
                worker = new Worker(workerUrl);
                worker.onmessage = (evt) => {
                    if (evt.data === 'ok') {
                        logger.info('Webp worker loaded.');
                        resolve(worker);
                    }
                    else {
                        reject(evt.data);
                    }
                };
            }
        }).then((worker) => {
            if (convertMeta.isAborted) {
                freeWebpWorkers.push(worker);
                logger.timeEnd(convertMeta.id);
                logger.warn('Convert stop manually.' + convertMeta.id);
                throw new CancelError();
            }
            return new Promise((resolve, reject) => {
                worker.onmessage = (evt) => {
                    if (convertMeta.isAborted) {
                        worker.terminate();
                        logger.timeEnd(convertMeta.id);
                        logger.warn('Convert stop manually.' + convertMeta.id);
                        reject(new CancelError());
                    }
                    else {
                        const data = evt.data;
                        if (typeof data !== 'object') {
                            convertMeta.onProgress?.(evt.data);
                        }
                        else {
                            logger.timeEnd(convertMeta.id);
                            freeWebpWorkers.push(worker);
                            resolve(new Blob([evt.data], { type: 'image/webp' }));
                        }
                    }
                };
                const delays = convertMeta.source.delays;
                worker.postMessage({ frames, delays });
            });
        });
    }

    function getDefaultExportFromCjs (x) {
    	return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
    }

    var WebMWriter$1 = {exports: {}};

    WebMWriter$1.exports;
    (function (module) {
    	(function() {
    	    function extend(base, top) {
    	        let
    	            target = {};
    	        [base, top].forEach(function(obj) {
    	            for (let prop in obj) {
    	                if (Object.prototype.hasOwnProperty.call(obj, prop)) {
    	                    target[prop] = obj[prop];
    	                }
    	            }
    	        });
    	        return target;
    	    }
    	    function decodeBase64WebPDataURL(url) {
    	        if (typeof url !== "string" || !url.match(/^data:image\/webp;base64,/i)) {
    	            throw new Error("Failed to decode WebP Base64 URL");
    	        }
    	        return window.atob(url.substring("data:image\/webp;base64,".length));
    	    }
    	    function renderAsWebP(canvas, quality) {
    	        let
    	            frame = typeof canvas === 'string' && /^data:image\/webp/.test(canvas)
    	                ? canvas
    	                : canvas.toDataURL('image/webp', quality);
    	        return decodeBase64WebPDataURL(frame);
    	    }
    	    function byteStringToUint32LE(string) {
    	        let
    	            a = string.charCodeAt(0),
    	            b = string.charCodeAt(1),
    	            c = string.charCodeAt(2),
    	            d = string.charCodeAt(3);
    	        return (a | (b << 8) | (c << 16) | (d << 24)) >>> 0;
    	    }
    	    function extractKeyframeFromWebP(webP) {
    	        let
    	            cursor = webP.indexOf('VP8', 12);
    	        if (cursor === -1) {
    	            throw new Error("Bad image format, does this browser support WebP?");
    	        }
    	        let
    	            hasAlpha = false;
    	        while (cursor < webP.length - 8) {
    	            let
    	                chunkLength, fourCC;
    	            fourCC = webP.substring(cursor, cursor + 4);
    	            cursor += 4;
    	            chunkLength = byteStringToUint32LE(webP.substring(cursor, cursor + 4));
    	            cursor += 4;
    	            switch (fourCC) {
    	                case "VP8 ":
    	                    return {
    	                        frame: webP.substring(cursor, cursor + chunkLength),
    	                        hasAlpha: hasAlpha
    	                    };
    	                case "ALPH":
    	                    hasAlpha = true;
    	                    break;
    	            }
    	            cursor += chunkLength;
    	            if ((chunkLength & 0x01) !== 0) {
    	                cursor++;
    	            }
    	        }
    	        throw new Error("Failed to find VP8 keyframe in WebP image, is this image mistakenly encoded in the Lossless WebP format?");
    	    }
    	    const
    	        EBML_SIZE_UNKNOWN = -1,
    	        EBML_SIZE_UNKNOWN_5_BYTES = -2;
    	    function EBMLFloat32(value) {
    	        this.value = value;
    	    }
    	    function EBMLFloat64(value) {
    	        this.value = value;
    	    }
    	    function writeEBML(buffer, bufferFileOffset, ebml) {
    	        if (Array.isArray(ebml)) {
    	            for (let i = 0; i < ebml.length; i++) {
    	                writeEBML(buffer, bufferFileOffset, ebml[i]);
    	            }
    	        } else if (typeof ebml === "string") {
    	            buffer.writeString(ebml);
    	        } else if (ebml instanceof Uint8Array) {
    	            buffer.writeBytes(ebml);
    	        } else if (ebml.id){
    	            ebml.offset = buffer.pos + bufferFileOffset;
    	            buffer.writeUnsignedIntBE(ebml.id);
    	            if (Array.isArray(ebml.data)) {
    	                let
    	                    sizePos, dataBegin, dataEnd;
    	                if (ebml.size === EBML_SIZE_UNKNOWN) {
    	                    buffer.writeByte(0xFF);
    	                } else if (ebml.size === EBML_SIZE_UNKNOWN_5_BYTES) {
    	                    sizePos = buffer.pos;
    	                    buffer.writeBytes([0x0F, 0xFF, 0xFF, 0xFF, 0xFF]);
    	                } else {
    	                    sizePos = buffer.pos;
    	                    buffer.writeBytes([0, 0, 0, 0]);
    	                }
    	                dataBegin = buffer.pos;
    	                ebml.dataOffset = dataBegin + bufferFileOffset;
    	                writeEBML(buffer, bufferFileOffset, ebml.data);
    	                if (ebml.size !== EBML_SIZE_UNKNOWN && ebml.size !== EBML_SIZE_UNKNOWN_5_BYTES) {
    	                    dataEnd = buffer.pos;
    	                    ebml.size = dataEnd - dataBegin;
    	                    buffer.seek(sizePos);
    	                    buffer.writeEBMLVarIntWidth(ebml.size, 4);
    	                    buffer.seek(dataEnd);
    	                }
    	            } else if (typeof ebml.data === "string") {
    	                buffer.writeEBMLVarInt(ebml.data.length);
    	                ebml.dataOffset = buffer.pos + bufferFileOffset;
    	                buffer.writeString(ebml.data);
    	            } else if (typeof ebml.data === "number") {
    	                if (!ebml.size) {
    	                    ebml.size = buffer.measureUnsignedInt(ebml.data);
    	                }
    	                buffer.writeEBMLVarInt(ebml.size);
    	                ebml.dataOffset = buffer.pos + bufferFileOffset;
    	                buffer.writeUnsignedIntBE(ebml.data, ebml.size);
    	            } else if (ebml.data instanceof EBMLFloat64) {
    	                buffer.writeEBMLVarInt(8);
    	                ebml.dataOffset = buffer.pos + bufferFileOffset;
    	                buffer.writeDoubleBE(ebml.data.value);
    	            } else if (ebml.data instanceof EBMLFloat32) {
    	                buffer.writeEBMLVarInt(4);
    	                ebml.dataOffset = buffer.pos + bufferFileOffset;
    	                buffer.writeFloatBE(ebml.data.value);
    	            } else if (ebml.data instanceof Uint8Array) {
    	                buffer.writeEBMLVarInt(ebml.data.byteLength);
    	                ebml.dataOffset = buffer.pos + bufferFileOffset;
    	                buffer.writeBytes(ebml.data);
    	            } else {
    	                throw new Error("Bad EBML datatype " + typeof ebml.data);
    	            }
    	        } else {
    	            throw new Error("Bad EBML datatype " + typeof ebml.data);
    	        }
    	    }
    	    let WebMWriter = function(ArrayBufferDataStream, BlobBuffer) {
    	        return function(options) {
    	            let
    	                MAX_CLUSTER_DURATION_MSEC = 5000,
    	                DEFAULT_TRACK_NUMBER = 1,
    	                writtenHeader = false,
    	                videoWidth = 0, videoHeight = 0,
    	                alphaBuffer = null,
    	                alphaBufferContext = null,
    	                alphaBufferData = null,
    	                clusterFrameBuffer = [],
    	                clusterStartTime = 0,
    	                clusterDuration = 0,
    	                optionDefaults = {
    	                    quality: 0.95,
    	                    transparent: false,
    	                    alphaQuality: undefined,
    	                    fileWriter: null,
    	                    fd: null,
    	                    frameDuration: null,
    	                    frameRate: null,
    	                },
    	                seekPoints = {
    	                    Cues: {id: new Uint8Array([0x1C, 0x53, 0xBB, 0x6B]), positionEBML: null},
    	                    SegmentInfo: {id: new Uint8Array([0x15, 0x49, 0xA9, 0x66]), positionEBML: null},
    	                    Tracks: {id: new Uint8Array([0x16, 0x54, 0xAE, 0x6B]), positionEBML: null},
    	                },
    	                ebmlSegment,
    	                segmentDuration = {
    	                    "id": 0x4489,
    	                    "data": new EBMLFloat64(0)
    	                },
    	                seekHead,
    	                cues = [],
    	                blobBuffer = new BlobBuffer(options.fileWriter || options.fd);
    	            function fileOffsetToSegmentRelative(fileOffset) {
    	                return fileOffset - ebmlSegment.dataOffset;
    	            }
    	            function convertAlphaToGrayscaleImage(source) {
    	                if (alphaBuffer === null || alphaBuffer.width !== source.width || alphaBuffer.height !== source.height) {
    	                    alphaBuffer = document.createElement("canvas");
    	                    alphaBuffer.width = source.width;
    	                    alphaBuffer.height = source.height;
    	                    alphaBufferContext = alphaBuffer.getContext("2d");
    	                    alphaBufferData = alphaBufferContext.createImageData(alphaBuffer.width, alphaBuffer.height);
    	                }
    	                let
    	                    sourceContext = source.getContext("2d"),
    	                    sourceData = sourceContext.getImageData(0, 0, source.width, source.height).data,
    	                    destData = alphaBufferData.data,
    	                    dstCursor = 0,
    	                    srcEnd = source.width * source.height * 4;
    	                for (let srcCursor = 3 ; srcCursor < srcEnd; srcCursor += 4) {
    	                    let
    	                        alpha = sourceData[srcCursor];
    	                    destData[dstCursor++] = alpha;
    	                    destData[dstCursor++] = alpha;
    	                    destData[dstCursor++] = alpha;
    	                    destData[dstCursor++] = 255;
    	                }
    	                alphaBufferContext.putImageData(alphaBufferData, 0, 0);
    	                return alphaBuffer;
    	            }
    	            function createSeekHead() {
    	                let
    	                    seekPositionEBMLTemplate = {
    	                        "id": 0x53AC,
    	                        "size": 5,
    	                        "data": 0
    	                    },
    	                    result = {
    	                        "id": 0x114D9B74,
    	                        "data": []
    	                    };
    	                for (let name in seekPoints) {
    	                    let
    	                        seekPoint = seekPoints[name];
    	                    seekPoint.positionEBML = Object.create(seekPositionEBMLTemplate);
    	                    result.data.push({
    	                         "id": 0x4DBB,
    	                         "data": [
    	                              {
    	                                  "id": 0x53AB,
    	                                  "data": seekPoint.id
    	                              },
    	                              seekPoint.positionEBML
    	                         ]
    	                    });
    	                }
    	                return result;
    	            }
    	            function writeHeader() {
    	                seekHead = createSeekHead();
    	                let
    	                    ebmlHeader = {
    	                        "id": 0x1a45dfa3,
    	                        "data": [
    	                            {
    	                                "id": 0x4286,
    	                                "data": 1
    	                            },
    	                            {
    	                                "id": 0x42f7,
    	                                "data": 1
    	                            },
    	                            {
    	                                "id": 0x42f2,
    	                                "data": 4
    	                            },
    	                            {
    	                                "id": 0x42f3,
    	                                "data": 8
    	                            },
    	                            {
    	                                "id": 0x4282,
    	                                "data": "webm"
    	                            },
    	                            {
    	                                "id": 0x4287,
    	                                "data": 2
    	                            },
    	                            {
    	                                "id": 0x4285,
    	                                "data": 2
    	                            }
    	                        ]
    	                    },
    	                    segmentInfo = {
    	                        "id": 0x1549a966,
    	                        "data": [
    	                            {
    	                                "id": 0x2ad7b1,
    	                                "data": 1e6
    	                            },
    	                            {
    	                                "id": 0x4d80,
    	                                "data": "webm-writer-js",
    	                            },
    	                            {
    	                                "id": 0x5741,
    	                                "data": "webm-writer-js"
    	                            },
    	                            segmentDuration
    	                        ]
    	                    },
    	                    videoProperties = [
    	                        {
    	                            "id": 0xb0,
    	                            "data": videoWidth
    	                        },
    	                        {
    	                            "id": 0xba,
    	                            "data": videoHeight
    	                        }
    	                    ];
    	                if (options.transparent) {
    	                    videoProperties.push(
    	                        {
    	                            "id": 0x53C0,
    	                            "data": 1
    	                        }
    	                    );
    	                }
    	                let
    	                    tracks = {
    	                        "id": 0x1654ae6b,
    	                        "data": [
    	                            {
    	                                "id": 0xae,
    	                                "data": [
    	                                    {
    	                                        "id": 0xd7,
    	                                        "data": DEFAULT_TRACK_NUMBER
    	                                    },
    	                                    {
    	                                        "id": 0x73c5,
    	                                        "data": DEFAULT_TRACK_NUMBER
    	                                    },
    	                                    {
    	                                        "id": 0x9c,
    	                                        "data": 0
    	                                    },
    	                                    {
    	                                        "id": 0x22b59c,
    	                                        "data": "und"
    	                                    },
    	                                    {
    	                                        "id": 0x86,
    	                                        "data": "V_VP8"
    	                                    },
    	                                    {
    	                                        "id": 0x258688,
    	                                        "data": "VP8"
    	                                    },
    	                                    {
    	                                        "id": 0x83,
    	                                        "data": 1
    	                                    },
    	                                    {
    	                                        "id": 0xe0,
    	                                        "data": videoProperties
    	                                    }
    	                                ]
    	                            }
    	                        ]
    	                    };
    	                ebmlSegment = {
    	                    "id": 0x18538067,
    	                    "size": EBML_SIZE_UNKNOWN_5_BYTES,
    	                    "data": [
    	                        seekHead,
    	                        segmentInfo,
    	                        tracks,
    	                    ]
    	                };
    	                let
    	                    bufferStream = new ArrayBufferDataStream(256);
    	                writeEBML(bufferStream, blobBuffer.pos, [ebmlHeader, ebmlSegment]);
    	                blobBuffer.write(bufferStream.getAsDataArray());
    	                seekPoints.SegmentInfo.positionEBML.data = fileOffsetToSegmentRelative(segmentInfo.offset);
    	                seekPoints.Tracks.positionEBML.data = fileOffsetToSegmentRelative(tracks.offset);
    		            writtenHeader = true;
    	            }
    	            function createBlockGroupForTransparentKeyframe(keyframe) {
    	                let
    	                    block, blockAdditions,
    	                    bufferStream = new ArrayBufferDataStream(1 + 2 + 1);
    	                if (!(keyframe.trackNumber > 0 && keyframe.trackNumber < 127)) {
    	                    throw new Error("TrackNumber must be > 0 and < 127");
    	                }
    	                bufferStream.writeEBMLVarInt(keyframe.trackNumber);
    	                bufferStream.writeU16BE(keyframe.timecode);
    	                bufferStream.writeByte(0);
    	                block = {
    	                    "id": 0xA1,
    	                    "data": [
    	                        bufferStream.getAsDataArray(),
    	                        keyframe.frame
    	                    ]
    	                };
    	                blockAdditions = {
    	                    "id": 0x75A1,
    	                    "data": [
    	                        {
    	                            "id": 0xA6,
    	                            "data": [
    	                                {
    	                                    "id": 0xEE,
    	                                    "data": 1
    	                                },
    	                                {
    	                                    "id": 0xA5,
    	                                    "data": keyframe.alpha
    	                                }
    	                            ]
    	                        }
    	                    ]
    	                };
    	                return {
    	                    "id": 0xA0,
    	                    "data": [
    	                        block,
    	                        blockAdditions
    	                    ]
    	                };
    	            }
    	            function createSimpleBlockForKeyframe(keyframe) {
    	                let
    	                    bufferStream = new ArrayBufferDataStream(1 + 2 + 1);
    	                if (!(keyframe.trackNumber > 0 && keyframe.trackNumber < 127)) {
    	                    throw new Error("TrackNumber must be > 0 and < 127");
    	                }
    	                bufferStream.writeEBMLVarInt(keyframe.trackNumber);
    	                bufferStream.writeU16BE(keyframe.timecode);
    	                bufferStream.writeByte(
    	                    1 << 7
    	                );
    	                return {
    	                    "id": 0xA3,
    	                    "data": [
    	                         bufferStream.getAsDataArray(),
    	                         keyframe.frame
    	                    ]
    	                };
    	            }
    	            function createContainerForKeyframe(keyframe) {
    	                if (keyframe.alpha) {
    	                    return createBlockGroupForTransparentKeyframe(keyframe);
    	                }
    	                return createSimpleBlockForKeyframe(keyframe);
    	            }
    	            function createCluster(cluster) {
    	                return {
    	                    "id": 0x1f43b675,
    	                    "data": [
    	                         {
    	                            "id": 0xe7,
    	                            "data": Math.round(cluster.timecode)
    	                         }
    	                    ]
    	                };
    	            }
    	            function addCuePoint(trackIndex, clusterTime, clusterFileOffset) {
    	                cues.push({
    	                    "id": 0xBB,
    	                    "data": [
    	                         {
    	                             "id": 0xB3,
    	                             "data": clusterTime
    	                         },
    	                         {
    	                             "id": 0xB7,
    	                             "data": [
    	                                  {
    	                                      "id": 0xF7,
    	                                      "data": trackIndex
    	                                  },
    	                                  {
    	                                      "id": 0xF1,
    	                                      "data": fileOffsetToSegmentRelative(clusterFileOffset)
    	                                  }
    	                             ]
    	                         }
    	                    ]
    	                });
    	            }
    	            function writeCues() {
    	                let
    	                    ebml = {
    	                        "id": 0x1C53BB6B,
    	                        "data": cues
    	                    },
    	                    cuesBuffer = new ArrayBufferDataStream(16 + cues.length * 32);
    	                writeEBML(cuesBuffer, blobBuffer.pos, ebml);
    	                blobBuffer.write(cuesBuffer.getAsDataArray());
    	                seekPoints.Cues.positionEBML.data = fileOffsetToSegmentRelative(ebml.offset);
    	            }
    	            function flushClusterFrameBuffer() {
    	                if (clusterFrameBuffer.length === 0) {
    	                    return;
    	                }
    	                let
    	                    rawImageSize = 0;
    	                for (let i = 0; i < clusterFrameBuffer.length; i++) {
    	                    rawImageSize += clusterFrameBuffer[i].frame.length + (clusterFrameBuffer[i].alpha ? clusterFrameBuffer[i].alpha.length : 0);
    	                }
    	                let
    	                    buffer = new ArrayBufferDataStream(rawImageSize + clusterFrameBuffer.length * 64),
    	                    cluster = createCluster({
    	                        timecode: Math.round(clusterStartTime),
    	                    });
    	                for (let i = 0; i < clusterFrameBuffer.length; i++) {
    	                    cluster.data.push(createContainerForKeyframe(clusterFrameBuffer[i]));
    	                }
    	                writeEBML(buffer, blobBuffer.pos, cluster);
    	                blobBuffer.write(buffer.getAsDataArray());
    	                addCuePoint(DEFAULT_TRACK_NUMBER, Math.round(clusterStartTime), cluster.offset);
    	                clusterFrameBuffer = [];
    	                clusterStartTime += clusterDuration;
    	                clusterDuration = 0;
    	            }
    	            function validateOptions() {
    	                if (!options.frameDuration) {
    	                    if (options.frameRate) {
    	                        options.frameDuration = 1000 / options.frameRate;
    	                    } else {
    	                        throw new Error("Missing required frameDuration or frameRate setting");
    	                    }
    	                }
    	                options.quality = Math.max(Math.min(options.quality, 0.99999), 0);
    	                if (options.alphaQuality === undefined) {
    	                    options.alphaQuality = options.quality;
    	                } else {
    	                    options.alphaQuality = Math.max(Math.min(options.alphaQuality, 0.99999), 0);
    	                }
    	            }
    	            function addFrameToCluster(frame) {
    	                frame.trackNumber = DEFAULT_TRACK_NUMBER;
    	                frame.timecode = Math.round(clusterDuration);
    	                clusterFrameBuffer.push(frame);
    	                clusterDuration += frame.duration;
    	                if (clusterDuration >= MAX_CLUSTER_DURATION_MSEC) {
    	                    flushClusterFrameBuffer();
    	                }
    	            }
    	            function rewriteSeekHead() {
    	                let
    	                    seekHeadBuffer = new ArrayBufferDataStream(seekHead.size),
    	                    oldPos = blobBuffer.pos;
    	                writeEBML(seekHeadBuffer, seekHead.dataOffset, seekHead.data);
    	                blobBuffer.seek(seekHead.dataOffset);
    	                blobBuffer.write(seekHeadBuffer.getAsDataArray());
    	                blobBuffer.seek(oldPos);
    	            }
    	            function rewriteDuration() {
    	                let
    	                    buffer = new ArrayBufferDataStream(8),
    	                    oldPos = blobBuffer.pos;
    	                buffer.writeDoubleBE(clusterStartTime);
    	                blobBuffer.seek(segmentDuration.dataOffset);
    	                blobBuffer.write(buffer.getAsDataArray());
    	                blobBuffer.seek(oldPos);
    	            }
    	            function rewriteSegmentLength() {
    	                let
    	                    buffer = new ArrayBufferDataStream(10),
    	                    oldPos = blobBuffer.pos;
    	                buffer.writeUnsignedIntBE(ebmlSegment.id);
    	                buffer.writeEBMLVarIntWidth(blobBuffer.pos - ebmlSegment.dataOffset, 5);
    	                blobBuffer.seek(ebmlSegment.offset);
    	                blobBuffer.write(buffer.getAsDataArray());
    	                blobBuffer.seek(oldPos);
    	            }
    	            this.addFrame = function(frame, alpha, overrideFrameDuration) {
    	                if (!writtenHeader) {
    	                    videoWidth = frame.width || 0;
    	                    videoHeight = frame.height || 0;
    	                    writeHeader();
    	                }
    	                let
    	                    keyframe = extractKeyframeFromWebP(renderAsWebP(frame, options.quality)),
    	                    frameDuration, frameAlpha = null;
    	                if (overrideFrameDuration) {
    	                    frameDuration = overrideFrameDuration;
    	                } else if (typeof alpha == "number") {
    	                    frameDuration = alpha;
    	                } else {
    	                    frameDuration = options.frameDuration;
    	                }
    	                if (options.transparent) {
    	                    if (alpha instanceof HTMLCanvasElement || typeof alpha === "string") {
    	                        frameAlpha = alpha;
    	                    } else if (keyframe.hasAlpha) {
    	                        frameAlpha = convertAlphaToGrayscaleImage(frame);
    	                    }
    	                }
    	                addFrameToCluster({
    	                    frame: keyframe.frame,
    	                    duration: frameDuration,
    	                    alpha: frameAlpha ? extractKeyframeFromWebP(renderAsWebP(frameAlpha, options.alphaQuality)).frame : null
    	                });
    	            };
    	            this.complete = function() {
    	            	if (!writtenHeader) {
    			            writeHeader();
    		            }
    	                flushClusterFrameBuffer();
    	                writeCues();
    	                rewriteSeekHead();
    	                rewriteDuration();
    	                rewriteSegmentLength();
    	                return blobBuffer.complete('video/webm');
    	            };
    	            this.getWrittenSize = function() {
    	                return blobBuffer.length;
    	            };
    	            options = extend(optionDefaults, options || {});
    	            validateOptions();
    	        };
    	    };
    	    {
    		    module.exports = WebMWriter;
    	    }
    	})();
    } (WebMWriter$1));
    var WebMWriterExports = WebMWriter$1.exports;
    getDefaultExportFromCjs(WebMWriterExports);

    var ArrayBufferDataStream = {exports: {}};

    ArrayBufferDataStream.exports;
    (function (module) {
    	(function(){
    	    let ArrayBufferDataStream = function(length) {
    	        this.data = new Uint8Array(length);
    	        this.pos = 0;
    	    };
    	    ArrayBufferDataStream.prototype.seek = function(toOffset) {
    	        this.pos = toOffset;
    	    };
    	    ArrayBufferDataStream.prototype.writeBytes = function(arr) {
    	        for (let i = 0; i < arr.length; i++) {
    	            this.data[this.pos++] = arr[i];
    	        }
    	    };
    	    ArrayBufferDataStream.prototype.writeByte = function(b) {
    	        this.data[this.pos++] = b;
    	    };
    	    ArrayBufferDataStream.prototype.writeU8 = ArrayBufferDataStream.prototype.writeByte;
    	    ArrayBufferDataStream.prototype.writeU16BE = function(u) {
    	        this.data[this.pos++] = u >> 8;
    	        this.data[this.pos++] = u;
    	    };
    	    ArrayBufferDataStream.prototype.writeDoubleBE = function(d) {
    	        let
    	            bytes = new Uint8Array(new Float64Array([d]).buffer);
    	        for (let i = bytes.length - 1; i >= 0; i--) {
    	            this.writeByte(bytes[i]);
    	        }
    	    };
    	    ArrayBufferDataStream.prototype.writeFloatBE = function(d) {
    	        let
    	            bytes = new Uint8Array(new Float32Array([d]).buffer);
    	        for (let i = bytes.length - 1; i >= 0; i--) {
    	            this.writeByte(bytes[i]);
    	        }
    	    };
    	    ArrayBufferDataStream.prototype.writeString = function(s) {
    	        for (let i = 0; i < s.length; i++) {
    	            this.data[this.pos++] = s.charCodeAt(i);
    	        }
    	    };
    	    ArrayBufferDataStream.prototype.writeEBMLVarIntWidth = function(i, width) {
    	        switch (width) {
    	            case 1:
    	                this.writeU8((1 << 7) | i);
    	            break;
    	            case 2:
    	                this.writeU8((1 << 6) | (i >> 8));
    	                this.writeU8(i);
    	            break;
    	            case 3:
    	                this.writeU8((1 << 5) | (i >> 16));
    	                this.writeU8(i >> 8);
    	                this.writeU8(i);
    	            break;
    	            case 4:
    	                this.writeU8((1 << 4) | (i >> 24));
    	                this.writeU8(i >> 16);
    	                this.writeU8(i >> 8);
    	                this.writeU8(i);
    	            break;
    	            case 5:
    	                this.writeU8((1 << 3) | ((i / 4294967296) & 0x7));
    	                this.writeU8(i >> 24);
    	                this.writeU8(i >> 16);
    	                this.writeU8(i >> 8);
    	                this.writeU8(i);
    	            break;
    	            default:
    	                throw new Error("Bad EBML VINT size " + width);
    	        }
    	    };
    	    ArrayBufferDataStream.prototype.measureEBMLVarInt = function(val) {
    	        if (val < (1 << 7) - 1) {
    	            return 1;
    	        } else if (val < (1 << 14) - 1) {
    	            return 2;
    	        } else if (val < (1 << 21) - 1) {
    	            return 3;
    	        } else if (val < (1 << 28) - 1) {
    	            return 4;
    	        } else if (val < 34359738367) {
    	            return 5;
    	        } else {
    	            throw new Error("EBML VINT size not supported " + val);
    	        }
    	    };
    	    ArrayBufferDataStream.prototype.writeEBMLVarInt = function(i) {
    	        this.writeEBMLVarIntWidth(i, this.measureEBMLVarInt(i));
    	    };
    	    ArrayBufferDataStream.prototype.writeUnsignedIntBE = function(u, width) {
    	        if (width === undefined) {
    	            width = this.measureUnsignedInt(u);
    	        }
    	        switch (width) {
    	            case 5:
    	                this.writeU8(Math.floor(u / 4294967296));
    	            case 4:
    	                this.writeU8(u >> 24);
    	            case 3:
    	                this.writeU8(u >> 16);
    	            case 2:
    	                this.writeU8(u >> 8);
    	            case 1:
    	                this.writeU8(u);
    	            break;
    	            default:
    	                throw new Error("Bad UINT size " + width);
    	        }
    	    };
    	    ArrayBufferDataStream.prototype.measureUnsignedInt = function(val) {
    	        if (val < (1 << 8)) {
    	            return 1;
    	        } else if (val < (1 << 16)) {
    	            return 2;
    	        } else if (val < (1 << 24)) {
    	            return 3;
    	        } else if (val < 4294967296) {
    	            return 4;
    	        } else {
    	            return 5;
    	        }
    	    };
    	    ArrayBufferDataStream.prototype.getAsDataArray = function() {
    	        if (this.pos < this.data.byteLength) {
    	            return this.data.subarray(0, this.pos);
    	        } else if (this.pos == this.data.byteLength) {
    	            return this.data;
    	        } else {
    	            throw new Error("ArrayBufferDataStream's pos lies beyond end of buffer");
    	        }
    	    };
    		{
    			module.exports = ArrayBufferDataStream;
    		}
    	}());
    } (ArrayBufferDataStream));
    var ArrayBufferDataStreamExports = ArrayBufferDataStream.exports;
    getDefaultExportFromCjs(ArrayBufferDataStreamExports);

    var BlobBuffer = {exports: {}};

    BlobBuffer.exports;
    (function (module) {
    	(function() {
    		let BlobBuffer = function(fs) {
    			return function(destination) {
    				let
    					buffer = [],
    					writePromise = Promise.resolve(),
    					fileWriter = null,
    					fd = null;
    				if (destination && destination.constructor.name === "FileWriter") {
    					fileWriter = destination;
    				} else if (fs && destination) {
    					fd = destination;
    				}
    				this.pos = 0;
    				this.length = 0;
    				function readBlobAsBuffer(blob) {
    					return new Promise(function (resolve, reject) {
    						let
    							reader = new FileReader();
    						reader.addEventListener("loadend", function () {
    							resolve(reader.result);
    						});
    						reader.readAsArrayBuffer(blob);
    					});
    				}
    				function convertToUint8Array(thing) {
    					return new Promise(function (resolve, reject) {
    						if (thing instanceof Uint8Array) {
    							resolve(thing);
    						} else if (thing instanceof ArrayBuffer || ArrayBuffer.isView(thing)) {
    							resolve(new Uint8Array(thing));
    						} else if (thing instanceof Blob) {
    							resolve(readBlobAsBuffer(thing).then(function (buffer) {
    								return new Uint8Array(buffer);
    							}));
    						} else {
    							resolve(readBlobAsBuffer(new Blob([thing])).then(function (buffer) {
    								return new Uint8Array(buffer);
    							}));
    						}
    					});
    				}
    				function measureData(data) {
    					let
    						result = data.byteLength || data.length || data.size;
    					if (!Number.isInteger(result)) {
    						throw new Error("Failed to determine size of element");
    					}
    					return result;
    				}
    				this.seek = function (offset) {
    					if (offset < 0) {
    						throw new Error("Offset may not be negative");
    					}
    					if (isNaN(offset)) {
    						throw new Error("Offset may not be NaN");
    					}
    					if (offset > this.length) {
    						throw new Error("Seeking beyond the end of file is not allowed");
    					}
    					this.pos = offset;
    				};
    				this.write = function (data) {
    					let
    						newEntry = {
    							offset: this.pos,
    							data: data,
    							length: measureData(data)
    						},
    						isAppend = newEntry.offset >= this.length;
    					this.pos += newEntry.length;
    					this.length = Math.max(this.length, this.pos);
    					writePromise = writePromise.then(function () {
    						if (fd) {
    							return new Promise(function(resolve, reject) {
    								convertToUint8Array(newEntry.data).then(function(dataArray) {
    									let
    										totalWritten = 0,
    										buffer = Buffer.from(dataArray.buffer),
    										handleWriteComplete = function(err, written, buffer) {
    											totalWritten += written;
    											if (totalWritten >= buffer.length) {
    												resolve();
    											} else {
    												fs.write(fd, buffer, totalWritten, buffer.length - totalWritten, newEntry.offset + totalWritten, handleWriteComplete);
    											}
    										};
    									fs.write(fd, buffer, 0, buffer.length, newEntry.offset, handleWriteComplete);
    								});
    							});
    						} else if (fileWriter) {
    							return new Promise(function (resolve, reject) {
    								fileWriter.onwriteend = resolve;
    								fileWriter.seek(newEntry.offset);
    								fileWriter.write(new Blob([newEntry.data]));
    							});
    						} else if (!isAppend) {
    							for (let i = 0; i < buffer.length; i++) {
    								let
    									entry = buffer[i];
    								if (!(newEntry.offset + newEntry.length <= entry.offset || newEntry.offset >= entry.offset + entry.length)) {
    									if (newEntry.offset < entry.offset || newEntry.offset + newEntry.length > entry.offset + entry.length) {
    										throw new Error("Overwrite crosses blob boundaries");
    									}
    									if (newEntry.offset == entry.offset && newEntry.length == entry.length) {
    										entry.data = newEntry.data;
    										return;
    									} else {
    										return convertToUint8Array(entry.data)
    											.then(function (entryArray) {
    												entry.data = entryArray;
    												return convertToUint8Array(newEntry.data);
    											}).then(function (newEntryArray) {
    												newEntry.data = newEntryArray;
    												entry.data.set(newEntry.data, newEntry.offset - entry.offset);
    											});
    									}
    								}
    							}
    						}
    						buffer.push(newEntry);
    					});
    				};
    				this.complete = function (mimeType) {
    					if (fd || fileWriter) {
    						writePromise = writePromise.then(function () {
    							return null;
    						});
    					} else {
    						writePromise = writePromise.then(function () {
    							let
    								result = [];
    							for (let i = 0; i < buffer.length; i++) {
    								result.push(buffer[i].data);
    							}
    							return new Blob(result, {type: mimeType});
    						});
    					}
    					return writePromise;
    				};
    			};
    		};
    		{
    			module.exports = BlobBuffer;
    		}
    	})();
    } (BlobBuffer));
    var BlobBufferExports = BlobBuffer.exports;
    getDefaultExportFromCjs(BlobBufferExports);

    var browser = WebMWriterExports(ArrayBufferDataStreamExports, BlobBufferExports(null));
    var WebMWriter = getDefaultExportFromCjs(browser);

    function readBlobAsDataUrl(blob) {
        return new Promise((resolve) => {
            const reader = new FileReader();
            reader.onload = () => {
                resolve(reader.result);
            };
            reader.readAsDataURL(blob);
        });
    }
    function webm(frames, convertMeta) {
        return Promise.all(frames.map((frame) => createImageBitmap(frame)))
            .then((bitmaps) => {
            if (convertMeta.isAborted)
                throw new CancelError();
            const width = bitmaps[0].width;
            const height = bitmaps[0].height;
            const canvas = new OffscreenCanvas(width, height);
            const ctx = canvas.getContext('2d');
            const dataUrls = [];
            for (let i = 0; i < frames.length; i++) {
                ctx.drawImage(bitmaps[i], 0, 0);
                const url = canvas.convertToBlob({ type: 'image/webp', quality: 0.95 }).then(readBlobAsDataUrl);
                dataUrls.push(url);
            }
            return Promise.all(dataUrls);
        })
            .then((dataUrls) => {
            if (convertMeta.isAborted)
                throw new CancelError();
            const videoWriter = new WebMWriter({
                quality: 0.95,
                frameRate: 30,
                transparent: false
            });
            const delays = convertMeta.source.delays;
            for (let i = 0; i < dataUrls.length; i++) {
                videoWriter.addFrame(dataUrls[i], delays[i]);
            }
            return videoWriter.complete();
        })
            .then((blob) => {
            if (convertMeta.isAborted)
                throw new CancelError();
            return blob;
        });
    }

    const adapter = {
        gif,
        png,
        webp,
        webm
    };
    const convertAdapter = {
        getAdapter(format) {
            return adapter[format];
        }
    };

    function createConverter() {
        const MAX_CONVERT = 2;
        const framesData = {};
        let isStop = false;
        let queue = [];
        let active = [];
        const doConvert = (convertMeta) => {
            const { id, format, source, resolve, reject } = convertMeta;
            active.push(convertMeta);
            convertMeta.onProgress?.(0);
            delete framesData[id];
            const adapter = convertAdapter.getAdapter(format);
            adapter(source.data, convertMeta)
                .then(resolve, reject)
                .finally(() => {
                active.splice(active.indexOf(convertMeta), 1);
                if (queue.length)
                    doConvert(queue.shift());
            });
        };
        return {
            del: (taskIds) => {
                if (typeof taskIds === 'string')
                    taskIds = [taskIds];
                if (!taskIds.length)
                    return;
                logger.info('Converter del, active:', active.map((meta) => meta.id), 'queue:', queue.map((meta) => meta.id));
                isStop = true;
                taskIds.forEach((taskId) => {
                    if (taskId in framesData)
                        delete framesData[taskId];
                });
                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;
                }
            },
            framesCount(taskId) {
                return taskId in framesData ? framesData[taskId]['delays'].filter((delay) => delay !== undefined).length : 0;
            },
            convert(taskId, format, onProgress) {
                return new Promise((resolve, reject) => {
                    const meta = {
                        id: taskId,
                        format,
                        source: framesData[taskId],
                        isAborted: false,
                        onProgress,
                        resolve,
                        reject,
                        abort() {
                            this.isAborted = true;
                        }
                    };
                    logger.info('Converter add', taskId);
                    queue.push(meta);
                    while (active.length < MAX_CONVERT && queue.length && !isStop) {
                        doConvert(queue.shift());
                    }
                });
            }
        };
    }
    const converter = createConverter();

    const pixivHooks = {
        download: {
            singleArtworkProgressFactory(btn, pageCount) {
                if (!btn || !pageCount)
                    return;
                return function onSingleArtworkProgress(progress) {
                    if (pageCount === 1) {
                        btn.setProgress(progress);
                    }
                };
            },
            mulityArtworksProgressFactory(btn, pageCount) {
                if (!btn || !pageCount)
                    return;
                let pageComplete = 0;
                return function onMulityArtworksProgress() {
                    if (pageCount < 2)
                        return;
                    const progress = Math.floor((++pageComplete / pageCount) * 100);
                    btn.setProgress(progress);
                };
            }
        },
        bundle: {
            async beforeFileSave(imgBlob, config) {
                const { taskId, source } = config;
                compressor.add(taskId, source.filename, imgBlob);
                if (compressor.fileCount(taskId) === source.pageCount) {
                    const zipData = await compressor.bundle(taskId);
                    compressor.remove(taskId);
                    return zipData;
                }
            },
            onError(err, config) {
                compressor.remove(config.taskId);
            },
            onAbort(config) {
                compressor.remove(config.taskId);
            }
        },
        convert: {
            convertProgressFactory(btn) {
                return function onConvertProgress(progress) {
                    if (progress > 0) {
                        btn.setProgress(progress, false);
                    }
                    else {
                        btn.setStatus("loading" );
                    }
                };
            },
            beforeFileSaveFactory(btn) {
                const onProgress = btn ? this.convertProgressFactory(btn) : undefined;
                return async function beforeFileSave(imgBlob, config) {
                    const { taskId, source } = config;
                    if (source.illustType === IllustType.ugoira) {
                        converter.addFrame(taskId, imgBlob, source.ugoiraMeta.frames[source.order]['delay'], source.order);
                        if (converter.framesCount(taskId) === source.pageCount) {
                            return await converter.convert(taskId, source.extendName, onProgress);
                        }
                    }
                };
            },
            onError(err, config) {
                converter.del(config.taskId);
            },
            onAbort(config) {
                converter.del(config.taskId);
            }
        }
    };
    const downloaderHooks = {
        getHooks(meta, downloadType, button) {
            switch (downloadType) {
                case 'download':
                    return {
                        onProgress: pixivHooks.download.singleArtworkProgressFactory(button, meta.pageCount),
                        onFileSaved: pixivHooks.download.mulityArtworksProgressFactory(button, meta.pageCount)
                    };
                case 'bundle':
                    return {
                        onXhrLoaded: pixivHooks.download.mulityArtworksProgressFactory(button, meta.pageCount),
                        beforeFileSave: pixivHooks.bundle.beforeFileSave,
                        onError: pixivHooks.bundle.onError
                    };
                case 'convert':
                    return {
                        onXhrLoaded: pixivHooks.download.mulityArtworksProgressFactory(button, meta.pageCount),
                        beforeFileSave: pixivHooks.convert.beforeFileSaveFactory(button),
                        onError: pixivHooks.convert.onError
                    };
            }
        }
    };
    class PixivDownloadConfig extends DownloadConfigBuilder {
        meta;
        downloadAll = true;
        headers = {
            referer: 'https://www.pixiv.net'
        };
        timeout = 60000;
        getImgSrc = () => '';
        constructor(meta) {
            super(meta);
            this.meta = meta;
            this.getImgSrc =
                this.meta.illustType === IllustType.ugoira
                    ? (page) => this.meta.src.replace('ugoira0', 'ugoira' + page)
                    : (page) => this.meta.src.replace('_p0', '_p' + page);
        }
        getConvertFormat() {
            return config.get('ugoiraFormat');
        }
        needConvert() {
            return this.meta.illustType === IllustType.ugoira && config.get('ugoiraFormat') !== 'zip';
        }
        needBundle() {
            const { pageCount, illustType } = this.meta;
            return (this.downloadAll &&
                ((illustType === IllustType.ugoira && this.getConvertFormat() === 'zip') ||
                    (pageCount > 1 &&
                        ((illustType === IllustType.manga && config.get('bundleManga')) ||
                            (illustType === IllustType.illusts && config.get('bundleIllusts'))))));
        }
        useTranslatedTags() {
            return config.get('tagLang') !== 'ja';
        }
        useDirectSave() {
            return (!!this.getFolderPattern() &&
                !this.needBundle() &&
                !this.needConvert() &&
                (!env.isBlobDlAvaliable() || (env.isViolentmonkey() && !this.isFsaEnable())));
        }
        supportSubpath() {
            return ((this.isBrowserApi() && ((!this.needConvert() && !this.needBundle()) || env.isBlobDlAvaliable())) ||
                this.isFsaEnable());
        }
        buildPattern(pattern, page) {
            const { id, userId, artist, title, tags, tagsTranslated, createDate, pageCount } = this.meta;
            const currPage = page === undefined ? pageCount : page;
            const useTags = this.useTranslatedTags() ? tagsTranslated : tags;
            const fArtist = this.normalizeString(artist);
            const fTitle = this.normalizeString(title);
            const fTags = this.normalizeString(useTags.join('_'));
            const replaceDate = (match, p1) => {
                const format = p1 || 'YYYY-MM-DD';
                return dayjs__default["default"](createDate).format(format);
            };
            return pattern
                .replaceAll(/\{date\((.*?)\)\}|\{date\}/g, replaceDate)
                .replaceAll('{artist}', fArtist)
                .replaceAll('{artistID}', userId)
                .replaceAll('{title}', fTitle)
                .replaceAll('{tags}', fTags)
                .replaceAll('{page}', String(currPage))
                .replaceAll('{id}', id);
        }
        getDownloadConfig(btn) {
            const { illustType, src, id, pageCount, extendName } = this.meta;
            const pageAttr = btn?.getAttribute('page');
            const downloadPage = pageAttr ? Number(pageAttr) : undefined;
            if (downloadPage && (downloadPage > pageCount - 1 || downloadPage < 0))
                throw new Error('Invalid downloadPage.');
            if (downloadPage !== undefined)
                this.downloadAll = false;
            const taskId = id + '_' + Math.random().toString(36).slice(2);
            const headers = this.headers;
            const directSave = this.useDirectSave();
            const supportSubPath = this.supportSubpath();
            const downloadConfigs = [];
            if ((pageCount === 1 || downloadPage !== undefined) && illustType !== IllustType.ugoira) {
                const imgSrc = downloadPage ? this.getImgSrc(downloadPage) : src;
                const pathPattern = supportSubPath ? this.getFullpathPattern() : this.getFilenamePattern() + '.' + extendName;
                const path = this.buildPattern(pathPattern, downloadPage ? downloadPage : 0);
                const filename = path.slice(path.lastIndexOf('/') + 1);
                const hooks = downloaderHooks.getHooks({ ...this.meta, pageCount: 1 }, 'download', btn);
                const source = {
                    ...this.meta,
                    pageCount: 1,
                    filename,
                    order: downloadPage ?? 0
                };
                const downloadConfig = {
                    taskId,
                    src: imgSrc,
                    path,
                    source,
                    directSave,
                    headers,
                    timeout: this.timeout,
                    ...hooks
                };
                downloadConfigs.push(downloadConfig);
            }
            else {
                const pathPatternNoExt = supportSubPath ? this.getFullpathPattern().slice(0, -4) : this.getFilenamePattern();
                if (this.needBundle()) {
                    const pathPattern = pathPatternNoExt + '.zip';
                    const filenamePattern = this.getFilenamePattern().includes('{page}')
                        ? this.getFilenamePattern() + '.' + extendName
                        : this.getFilenamePattern() + '_{page}' + '.' + extendName;
                    let path;
                    let imgCount;
                    if (illustType === IllustType.ugoira) {
                        path = this.buildPattern(pathPattern, 0);
                        imgCount = this.meta.ugoiraMeta.frames.length;
                    }
                    else {
                        path = this.buildPattern(pathPattern);
                        imgCount = pageCount;
                    }
                    const hooks = downloaderHooks.getHooks({ ...this.meta, pageCount: imgCount }, 'bundle', btn);
                    for (let page = 0; page < imgCount; page++) {
                        const filename = this.buildPattern(filenamePattern, page);
                        const imgSrc = this.getImgSrc(page);
                        const source = {
                            ...this.meta,
                            pageCount: imgCount,
                            extendName: 'zip',
                            filename,
                            order: page
                        };
                        const downloadConfig = {
                            taskId,
                            src: imgSrc,
                            path,
                            source,
                            headers,
                            timeout: this.timeout,
                            ...hooks
                        };
                        downloadConfigs.push(downloadConfig);
                    }
                }
                else if (this.needConvert()) {
                    const ext = this.getConvertFormat();
                    const pathPattern = pathPatternNoExt + '.' + ext;
                    const path = this.buildPattern(pathPattern, 0);
                    const filename = path.slice(path.lastIndexOf('/') + 1);
                    const imgCount = this.meta.ugoiraMeta.frames.length;
                    const hooks = downloaderHooks.getHooks({ ...this.meta, pageCount: imgCount }, 'convert', btn);
                    for (let page = 0; page < imgCount; page++) {
                        const imgSrc = this.getImgSrc(page);
                        const source = {
                            ...this.meta,
                            pageCount: imgCount,
                            extendName: ext,
                            filename,
                            order: page
                        };
                        const downloadConfig = {
                            taskId,
                            src: imgSrc,
                            path,
                            source,
                            headers,
                            timeout: this.timeout,
                            ...hooks
                        };
                        downloadConfigs.push(downloadConfig);
                    }
                }
                else {
                    const pathPattern = pathPatternNoExt + '.' + extendName;
                    const hooks = downloaderHooks.getHooks(this.meta, 'download', btn);
                    for (let page = 0; page < pageCount; page++) {
                        const path = this.buildPattern(pathPattern, page);
                        const filename = path.slice(path.lastIndexOf('/') + 1);
                        const imgSrc = this.getImgSrc(page);
                        const source = {
                            ...this.meta,
                            filename,
                            order: page
                        };
                        const downloadConfig = {
                            taskId,
                            src: imgSrc,
                            path,
                            source,
                            directSave,
                            headers,
                            timeout: this.timeout,
                            ...hooks
                        };
                        downloadConfigs.push(downloadConfig);
                    }
                }
            }
            !this.downloadAll && (this.downloadAll = true);
            return downloadConfigs;
        }
    }

    async function downloadArtwork(btn) {
        downloader.dirHandleCheck();
        const id = btn.getAttribute('pdl-id');
        const pixivMeta = await pixivParser.parse(id);
        const { bookmarkData, token, tags, artist, userId, title } = pixivMeta;
        if (!bookmarkData) {
            addBookmark(btn, id, token, tags);
        }
        const downloadConfigs = new PixivDownloadConfig(pixivMeta).getDownloadConfig(btn);
        await downloader.download(downloadConfigs);
        const historyData = {
            pid: Number(id),
            user: artist,
            userId: Number(userId),
            title,
            tags
        };
        historyDb.add(historyData);
    }

    function createThumbnailBtn(nodes) {
        let isSelfBookmark = false;
        const inBookmarkPage = regexp.bookmarkPage.exec(location.pathname);
        inBookmarkPage && inBookmarkPage[1] === getSelfId() && (isSelfBookmark = true);
        nodes.forEach((e) => {
            let illustId;
            let type;
            if ((e.childElementCount !== 0 ||
                e.className.includes('_history-item') ||
                e.className.includes('_history-related-item')) &&
                !e.querySelector('pdl-button') &&
                (illustId = getIllustId(e))) {
                if (isSelfBookmark) {
                    type = "pixiv-my-bookmark" ;
                }
                else if (e.className.includes('_history-related-item')) {
                    e.style.position = 'relative';
                    type = "pixiv-history" ;
                }
                else if (e.className.includes('_history-item')) {
                    type = "pixiv-history" ;
                }
                const btn = new ThumbnailButton({
                    id: illustId,
                    type,
                    onClick: downloadArtwork
                });
                e.appendChild(btn);
            }
        });
    }

    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();
        });
    }

    function createPdlBtn(option) {
        const { attrs, classList, textContent, downloadArtwork } = option;
        const ele = document.createElement('button');
        textContent && (ele.textContent = textContent);
        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 (downloadArtwork) {
            ele.addEventListener('click', (evt) => {
                evt.preventDefault();
                evt.stopPropagation();
                const btn = evt.currentTarget;
                if (!btn.classList.contains('pdl-progress')) {
                    btn.classList.add('pdl-progress');
                    const setProgress = (progress, updateProgressbar = true) => {
                        if (progress !== null) {
                            progress = Math.floor(progress);
                            btn.textContent = String(progress);
                            updateProgressbar && btn.style.setProperty('--pdl-progress', progress + '%');
                        }
                        else {
                            btn.textContent = '';
                            updateProgressbar && btn.style.removeProperty('--pdl-progress');
                        }
                    };
                    downloadArtwork(btn, setProgress)
                        .then(() => {
                        btn.classList.remove('pdl-error');
                        btn.classList.add('pdl-complete');
                    })
                        .catch((err) => {
                        if (err)
                            logger.error(err);
                        btn.classList.remove('pdl-complete');
                        btn.classList.add('pdl-error');
                    })
                        .finally(() => {
                        btn.innerHTML = '';
                        btn.style.removeProperty('--pdl-progress');
                        btn.classList.remove('pdl-progress');
                    });
                }
            });
        }
        return ele;
    }

    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');
        if (!nav || nav.previousElementSibling)
            return;
        const dlBtn = nav.querySelector('.pdl-btn-all');
        if (dlBtn) {
            if (dlBtn.getAttribute('pdl-userid') === userId)
                return;
            removeDownloadBar();
        }
        const doesRequestPageLoaded = ["a[href$='illustrations']", "a[href$='manga']", "a[href*='bookmarks']"].some((selector) => !!nav.querySelector(selector));
        if (!doesRequestPageLoaded)
            return;
        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'],
            textContent: t('button.download_stop')
        }));
        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'],
                    textContent: t('button.download_works')
                });
                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'],
                    textContent: t('button.download_bookmarks')
                });
                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'],
                    textContent: t('button.download_bookmarks')
                }));
                dlBar.appendChild(createPdlBtn({
                    attrs: {
                        'pdl-userid': userId,
                        category: 'bookmarks',
                        rest: 'show'
                    },
                    classList: [...baseClasses, 'pdl-btn-all'],
                    textContent: t('button.download_bookmarks_public')
                }));
                dlBar.appendChild(createPdlBtn({
                    attrs: {
                        'pdl-userid': userId,
                        category: 'bookmarks',
                        rest: 'hide'
                    },
                    classList: [...baseClasses, 'pdl-btn-all'],
                    textContent: t('button.download_bookmarks_private')
                }));
                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'],
            textContent: t('button.download_stop')
        }));
        const dlBtn = createPdlBtn({
            attrs: { 'pdl-userid': '' },
            classList: [...baseClasses, 'pdl-btn-all'],
            textContent: t('button.download_works')
        });
        dlBtn.addEventListener('click', downloadFollowLatest);
        dlBar.appendChild(dlBtn);
        const dlAllBtn = createPdlBtn({
            attrs: { 'pdl-userid': '' },
            classList: [...baseClasses, 'pdl-btn-all', 'pdl-dl-all'],
            textContent: t('button.download_works')
        });
        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'],
            textContent: t('button.download_stop')
        }));
        const dlBtn = createPdlBtn({
            attrs: { 'pdl-userid': '' },
            classList: [...baseClasses, 'pdl-btn-all'],
            textContent: t('button.download_all_one_page')
        });
        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');
    }

    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.abort(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;
                            pixivParser
                                .parse(id)
                                .then((pixivMeta) => {
                                const { id, tags, artist, userId, title } = pixivMeta;
                                historyData = {
                                    pid: Number(id),
                                    user: artist,
                                    userId: Number(userId),
                                    title,
                                    tags
                                };
                                const downloadConfigs = new PixivDownloadConfig(pixivMeta).getDownloadConfig();
                                tasks.push(downloadConfigs[0].taskId);
                                return downloader.download(downloadConfigs);
                            })
                                .then((taskId) => {
                                historyDb.add(historyData);
                                if (!signal.aborted) {
                                    tasks.splice(tasks.indexOf(taskId[0]), 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;
    }

    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 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 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() });
            }
        });
    }
    let isDownloading = false;
    function downloadWorks(evt) {
        evt.preventDefault();
        evt.stopPropagation();
        if (isDownloading)
            return;
        const btn = evt.target;
        const userId = btn.getAttribute('pdl-userid');
        const filterOption = getFilterOption();
        downloader.dirHandleCheck();
        const ids = pixivParser.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');
        downloader.dirHandleCheck();
        const filterOption = getFilterOption();
        let idsGenerators;
        if (rest === 'all') {
            const idsShowPromise = pixivParser.getChunksGenerator(userId, 'bookmarks', '', 'show', filterOption);
            const idsHidePromise = pixivParser.getChunksGenerator(userId, 'bookmarks', '', 'hide', filterOption);
            idsGenerators = [idsShowPromise, idsHidePromise];
        }
        else {
            idsGenerators = pixivParser.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 = pixivParser.getFollowLatestGenerator(filterOption, mode);
        }
        else {
            const params = new URLSearchParams(location.search);
            const page = Number(params.get('p')) || 1;
            idsGenerators = pixivParser.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);
    }

    function createTagsBtn(userId, category) {
        const tagsEles = Array.from(document.querySelectorAll('a[status]')).map((el) => el.parentElement);
        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);
            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);
            dlBtn.addEventListener('click', (evt) => {
                modal.querySelector('svg').parentElement.click();
                downloadBookmarksOrTags(evt);
            });
            ele.appendChild(dlBtn);
        });
    }

    function createToolbarBtn(id) {
        const toolbar = document.querySelector('main section section');
        if (!toolbar || toolbar.querySelector('pdl-button'))
            return;
        const btn = new ThumbnailButton({
            id,
            type: "pixiv-toolbar" ,
            onClick: downloadArtwork
        });
        const pdlBtnWrap = toolbar.lastElementChild.cloneNode();
        pdlBtnWrap.appendChild(btn);
        toolbar.appendChild(pdlBtnWrap);
    }

    function createWorkScrollBtn(id) {
        const works = document.querySelectorAll('figure a.gtm-expand-full-size-illust');
        if (!works.length)
            return;
        const containers = Array.from(works).map((node) => node.parentElement.parentElement);
        if (containers[0].querySelector('pdl-button'))
            return;
        containers.forEach((node, idx) => {
            const wrapper = document.createElement('div');
            wrapper.classList.add('pdl-wrap-artworks');
            wrapper.appendChild(new ThumbnailButton({
                id,
                page: idx,
                type: "gallery" ,
                onClick: downloadArtwork
            }));
            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('page', 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;
            btn = new ThumbnailButton({
                id,
                type: "pixiv-presentation" ,
                page: Number(pageNum),
                onClick: downloadArtwork
            });
            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';
                node.appendChild(new ThumbnailButton({
                    id,
                    page: idx,
                    onClick: downloadArtwork
                }));
            });
        }, 300);
    }

    function createMangaViewerBtn(id) {
        const mangaViewerBackBtn = document.querySelector('.gtm-manga-viewer-close-icon');
        if (!mangaViewerBackBtn)
            return;
        const container = mangaViewerBackBtn.parentElement;
        if (container.querySelector('pdl-button'))
            return;
        container.appendChild(new ThumbnailButton({
            id,
            type: "pixiv-manga-viewer" ,
            onClick: downloadArtwork
        }));
    }

    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] || param[2];
                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): {
                createThumbnailBtn(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 !== 'PDL-BUTTON' && node.tagName !== 'IMG') {
                    addedNodes.push(node);
                }
            });
        });
        if (!addedNodes.length)
            return;
        if (firstRun) {
            createThumbnailBtn(document.querySelectorAll('a'));
            firstRun = false;
        }
        else {
            fixPixivPreviewer(addedNodes);
            const thumbnails = addedNodes.reduce((prev, current) => {
                return prev.concat(current instanceof HTMLAnchorElement ? [current] : Array.from(current.querySelectorAll('a')));
            }, []);
            createThumbnailBtn(thumbnails);
        }
        pageActions();
    }

    var css = ".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\")}: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='%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\")}: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='%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\")}:root[data-theme=default] .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=default] .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\")}:root[data-theme=default] .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=default] .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\")}@media (prefers-color-scheme:light){: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='%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\")}: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='%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\")}}: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-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\")}}.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}";

    class Pixiv extends SiteInject {
        init() {
            super.init();
            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'));
                        }
                    }
                }
            });
        }
        addStyle() {
            super.addStyle();
            GM_addStyle(css$8);
            GM_addStyle(css);
        }
    }

    function getSiteInjector(host) {
        const sitesAdapter = {
            'danbooru.donmai.us': Danbooru,
            'www.pixiv.net': Pixiv,
            'rule34.xxx': Rule34
        };
        if (host in sitesAdapter) {
            return sitesAdapter[host];
        }
    }
    const siteInject = getSiteInjector(location.host);
    siteInject && new siteInject();

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