Sleazy Fork is available in English.

Pixiv Downloader

Pixiv | Danbooru | Rule34. 一键下载各页面原图。批量下载画师作品,按作品标签下载。转换动图格式:Gif | Apng | Webp | Webm。自定义图片文件名,保存路径。保留 / 导出下载历史。

// ==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/jszip@3.7.1/dist/jszip.min.js
// @require      https://unpkg.com/gif.js@0.2.0/dist/gif.js
// @require      https://unpkg.com/dayjs@1.11.11/dayjs.min.js
// @require      https://unpkg.com/dexie@3.2.5/dist/dexie.min.js
// @require      https://greasyfork.org/scripts/455256-toanimatedwebp/code/toAnimatedWebp.js?version=1120088
// @resource pako https://unpkg.com/pako@2.0.4/dist/pako.min.js
// @resource upng https://unpkg.com/upng-js@2.1.0/UPNG.js
// @resource gifWorker https://unpkg.com/gif.js@0.2.0/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);