您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
一键下载Pixiv各页面原图。批量下载画师作品,按作品标签下载。转换动图格式:Gif | Apng | Webp | Webm。自定义图片文件名,保存路径。保留 / 导出下载历史。支持rule34xxx。
当前为
// ==UserScript== // @name Pixiv Downloader // @namespace https://greasyfork.org/zh-CN/scripts/432150 // @version 0.11.0 // @description:en Download pixiv 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. Support rule34xxx. // @description 一键下载Pixiv各页面原图。批量下载画师作品,按作品标签下载。转换动图格式:Gif | Apng | Webp | Webm。自定义图片文件名,保存路径。保留 / 导出下载历史。支持rule34xxx。 // @description:zh-TW 一鍵下載Pixiv各頁面原圖。批次下載畫師作品,按作品標籤下載。轉換動圖格式:Gif | Apng | Webp | Webm。自定義圖片檔名,儲存路徑。保留 / 匯出下載歷史。支援rule34xxx。 // @author ruaruarua // @match https://www.pixiv.net/* // @match https://rule34.xxx/* // @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 // @require https://unpkg.com/[email protected]/dist/jszip.min.js // @require https://unpkg.com/[email protected]/dist/gif.js // @require https://unpkg.com/[email protected]/dayjs.min.js // @require https://unpkg.com/[email protected]/dist/dexie.min.js // @require https://greasyfork.org/scripts/455256-toanimatedwebp/code/toAnimatedWebp.js?version=1120088 // @resource pako https://unpkg.com/[email protected]/dist/pako.min.js // @resource upng https://unpkg.com/[email protected]/UPNG.js // @resource gifWorker https://unpkg.com/[email protected]/dist/gif.worker.js // ==/UserScript== (function (Dexie, dayjs, JSZip, GIF, workerChunk) { 'use strict'; function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var Dexie__default = /*#__PURE__*/_interopDefaultLegacy(Dexie); var dayjs__default = /*#__PURE__*/_interopDefaultLegacy(dayjs); var JSZip__default = /*#__PURE__*/_interopDefaultLegacy(JSZip); var GIF__default = /*#__PURE__*/_interopDefaultLegacy(GIF); var workerChunk__default = /*#__PURE__*/_interopDefaultLegacy(workerChunk); function getLogger() { const methods = ['info', 'warn', 'error']; const style = ['color: green;', 'color: orange;', 'color: red;']; const logLevel = 2 ; const namePrefix = '[Pixiv Downlaoder] '; function log(level, args) { if (logLevel <= level) console[methods[level]]('%c[Pixiv Downloader]', style[level], ...args); } return { info(...args) { log(0 , args); }, warn(...args) { log(1 , args); }, error(...args) { log(2 , args); }, time(label) { console.time(namePrefix + label); }, timeLog(label) { console.timeLog(namePrefix + label); }, timeEnd(label) { console.timeEnd(namePrefix + label); } }; } const logger = getLogger(); 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(); 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; } 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' }); } 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.0", ugoiraFormat: 'zip', folderPattern: 'pixiv/{artist}', filenamePattern: '{artist}_{title}_{id}_p{page}', tagLang: 'ja', showMsg: true, filterExcludeDownloaded: false, filterIllusts: true, filterManga: true, filterUgoira: true, bundleIllusts: false, bundleManga: false, addBookmark: false, addBookmarkWithTags: false, privateR18: false, useFileSystemAccess: false, fileSystemFilenameConflictAction: 'uniquify', showPopupButton: true, ...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); } } }; } let siteConfig; if (location.hostname === 'rule34.xxx') { siteConfig = { folderPattern: 'rule34/{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; logger.error('Download ' + taskId + ' error', error.error ? ' with reason: ' + error.error : '', 'details' in error ? error.details : error); if ('status' in error && error.status === 429) { const err = new RequestError('Too many request', error); config.onError?.(err, config); downloadMeta.reject(err); active.splice(active.indexOf(downloadMeta), 1); return; } downloadMeta.retry++; 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); } } }; }; 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) { config.onProgress?.(res, 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: (e) => { config.onProgress?.(e, 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(); 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(setBtnProgress) { if (!setBtnProgress) return; return function onArtworkProgress(res) { if (res.loaded > 0 && res.total > 0) { const progress = Math.floor((res.loaded / res.total) * 100); setBtnProgress(progress); } }; } class Rule34DownloadConfig extends DownloadConfigBuilder { meta; constructor(meta) { super(meta); this.meta = meta; } getDownloadConfig(option) { 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(option?.setBtnProgress) }; } buildFilePath() { const path = super.buildFilePath(); return path.replaceAll('{character}', this.normalizeString(this.meta.character)); } } function addBookmark$1(id) { unsafeWindow.addFav(id); } async function downloadRule34Artwork(pdlBtn, setBtnProgress) { downloader.dirHandleCheck(); const id = pdlBtn.getAttribute('pdl-id'); const mediaMeta = await rule34Parser.parse(id); const { tags, artist, title } = mediaMeta; const downloadConfigs = new Rule34DownloadConfig(mediaMeta).getDownloadConfig({ setBtnProgress }); config.get('addBookmark') && addBookmark$1(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$8 = ".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$7 = ".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$6 = ".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; constructor() { super(); this.render(); } render() { const shadowRoot = this.attachShadow({ mode: 'open' }); shadowRoot.innerHTML = ` <style>${css$6 + css$8 + css$7}</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>`; const modal = shadowRoot.querySelector('.pdl-modal'); const closeBtn = modal.querySelector('.pdl-dialog-close'); modal.addEventListener('click', (evt) => { const closeOnClickModal = this.hasAttribute('close-on-click-modal'); closeOnClickModal && evt.target === modal && this.remove(); }); closeBtn.addEventListener('click', () => { this.remove(); }); } connectedCallback() { const container = this.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.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$5 = "@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$5}</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://greasyfork.org/zh-CN/scripts/432150-pixiv-downloader/feedback" >${t('text.feedback')}</a > </p> </div> </div>`; return { tab: stringToFragment(tabHtml), pane: stringToFragment(paneHtml) }; } var css$4 = ".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$4}</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>新增网站支持:rule34.xxx ,暂不支持批量下载。</li> <li>fix: converting ugoira to WEBM breaks framerate.</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://greasyfork.org/zh-CN/scripts/432150-pixiv-downloader/feedback" >${t('text.feedback')} </a>`; document.body.appendChild(createModal({ header: stringToFragment(headerHtml), content: stringToFragment(contentHtml), footer: stringToFragment(footerHtml) })); } var css$3 = ":root{--pdl-btn-top:100;--pdl-btn-left:0;--pdl-btn-self-bookmark-top:75;--pdl-btn-self-bookmark-left:100}"; var css$2 = ":root,:root[data-theme=default]{--pdl-bg1:#fff;--pdl-bg2-hover:rgba(0,0,0,.05);--pdl-bg3-hover:#1f1f1f;--pdl-btn1:rgba(0,0,0,.04);--pdl-btn1-hover:rgba(0,0,0,.12);--pdl-border1:rgba(0,0,0,.1);--pdl-text1:#1f1f1f}@media (prefers-color-scheme:light){:root{--pdl-bg1:#fff;--pdl-bg2-hover:rgba(0,0,0,.05);--pdl-bg3-hover:#1f1f1f;--pdl-btn1:rgba(0,0,0,.04);--pdl-btn1-hover:rgba(0,0,0,.12);--pdl-border1:rgba(0,0,0,.1);--pdl-text1:#1f1f1f}}:root[data-theme=dark]{--pdl-bg1:#1f1f1f;--pdl-bg2-hover:hsla(0,0%,100%,.1);--pdl-bg3-hover:#9b9b9b;--pdl-btn1:hsla(0,0%,100%,.4);--pdl-btn1-hover:hsla(0,0%,100%,.6);--pdl-border1:hsla(0,0%,100%,.3);--pdl-text1:#d6d6d6}@media (prefers-color-scheme:dark){:root{--pdl-bg1:#1f1f1f;--pdl-bg2-hover:hsla(0,0%,100%,.1);--pdl-bg3-hover:#9b9b9b;--pdl-btn1:hsla(0,0%,100%,.4);--pdl-btn1-hover:hsla(0,0%,100%,.6);--pdl-border1:hsla(0,0%,100%,.3);--pdl-text1:#d6d6d6}}"; var css$1 = ".pdl-popup-button{background-color:rgba(0,150,250,.5);border:none;border-radius:50%;bottom:100px;color:#fff;cursor:pointer;line-height:0;margin:0;opacity:.32;padding:12px;position:fixed;right:28px;transition:opacity .3s ease 0s;z-index:1}.pdl-popup-button:hover{opacity:1}.pdl-popup-button svg{fill:currentColor;height:24px;width:24px}"; 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$6, css$3, css$2, css$5, css$1].forEach((style) => GM_addStyle(style)); } } class Rule34 extends SiteInject { init() { super.init(); this.pageAction(); } createThumbnailsBtn() { const btnContainers = document.querySelectorAll('span.thumb:not(.blacklisted-image) > a:first-child'); 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 id = el.getAttribute('id').slice(1); const attrs = { attrs: { 'pdl-id': id }, classList: ['pdl-btn', 'pdl-btn-sub'], downloadArtwork: downloadRule34Artwork }; const btn = createPdlBtn(attrs); historyDb.has(id).then((downloaded) => { downloaded && btn.classList.add('pdl-complete'); }); el.appendChild(btn); }); } 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 attrs = { attrs: { 'pdl-id': id }, classList: ['pdl-btn', 'pdl-btn-sub', 'artworks'], downloadArtwork: downloadRule34Artwork }; const btn = createPdlBtn(attrs); historyDb.has(id).then((downloaded) => { downloaded && btn.classList.add('pdl-complete'); }); 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 === 'list') { this.createThumbnailsBtn(); } else if (page === 'post' && s === 'view') { if (!document.querySelector('#image, #gelcomVideoPlayer')) return; const id = searchParams.get('id'); this.createArtworkBtn(id); } else if (page === 'favorites' && s === 'view') { this.createThumbnailsBtn(); } } } 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(pdlBtn, illustId, token, tags) { if (!config.get('addBookmark')) return; api .addBookmark(illustId, token, config.get('addBookmarkWithTags') ? tags : [], config.get('privateR18') && tags.includes('R-18') ? BookmarkRestrict.private : BookmarkRestrict.public) .then(() => { const bookmarkBtnRef = findBookmarkBtn(pdlBtn); if (!bookmarkBtnRef) return; switch (bookmarkBtnRef.kind) { case "main" : { const pathBorder = bookmarkBtnRef.button.querySelector('svg g path'); pathBorder && (pathBorder.style.color = 'rgb(255, 64, 96)'); break; } case "sub" : { const pathBorder = bookmarkBtnRef.button.querySelector('path'); pathBorder && (pathBorder.style.color = 'rgb(255, 64, 96)'); break; } case "rank" : { bookmarkBtnRef.button.style.backgroundColor = 'rgb(255, 64, 96)'; break; } } }) .catch((reason) => { logger.error(reason.message); }); } function findBookmarkBtn(pdlBtn) { const bookmarkBtnRef = {}; if (pdlBtn.classList.contains('pdl-btn-sub')) { const btn = pdlBtn.parentElement?.nextElementSibling?.querySelector('button[type="button"]'); if (btn) { bookmarkBtnRef.kind = "sub" ; bookmarkBtnRef.button = btn; } else { const btn = pdlBtn.parentElement?.querySelector('div._one-click-bookmark'); if (btn) { bookmarkBtnRef.kind = "rank" ; bookmarkBtnRef.button = btn; } } } else if (pdlBtn.classList.contains('pdl-btn-main')) { const btn = pdlBtn.parentElement?.parentElement?.querySelector('button.gtm-main-bookmark'); if (btn) { bookmarkBtnRef.kind = "main" ; bookmarkBtnRef.button = btn; } } else { return logger.error(new Error('Can not find bookmark button.')); } return bookmarkBtnRef; } 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 new Promise((resolve, reject) => { const gif = new GIF__default["default"]({ workers: 2, quality: 10, workerScript: workerUrl$2 }); convertMeta.abort = () => { gif.abort(); }; logger.info('Start convert:', convertMeta.id); logger.time(convertMeta.id); frames.forEach((frame, i) => { gif.addFrame(frame, { delay: convertMeta.source.delays[i] }); }); gif.on('progress', (progress) => { convertMeta.onProgress?.(progress); }); 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(); }); } const pako = GM_getResourceText('pako'); const upng = GM_getResourceText('upng'); const workerEvt = `onmessage = (evt) => { const {data, width, height, delay } = evt.data; const png = UPNG.encode(data, width, height, 0, delay, {loop: 0}); if (!png) console.error('Convert Apng failed.'); postMessage(png); };`; const workerUrl$1 = URL.createObjectURL(new Blob([workerEvt + 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) => { const canvas = document.createElement('canvas'); const width = (canvas.width = frames[0].naturalWidth); const height = (canvas.height = frames[0].naturalHeight); const context = canvas.getContext('2d', { willReadFrequently: true }); if (!context) return reject(new TypeError('Can not get canvas context')); const data = []; const delay = convertMeta.source.delays; logger.info('Start convert:', convertMeta.id); logger.time(convertMeta.id); for (const frame of frames) { if (convertMeta.isAborted) { logger.timeEnd(convertMeta.id); logger.warn('Convert stop manually. ' + convertMeta.id); return reject(new CancelError()); } context.clearRect(0, 0, width, height); context.drawImage(frame, 0, 0, width, height); data.push(context.getImageData(0, 0, width, height).data); } logger.timeLog(convertMeta.id); let worker; if (freeApngWorkers.length) { worker = freeApngWorkers.shift(); logger.info('Reuse apng workers.'); } else { worker = new Worker(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 cfg = { data, width, height, delay }; worker.postMessage(cfg); }); } const handleWorker = ` let webpApi = {}; Module.onRuntimeInitialized = () => { webpApi = { init: Module.cwrap('init', '', ['number', 'number', 'number']), createBuffer: Module.cwrap('createBuffer', 'number', ['number']), addFrame: Module.cwrap('addFrame', 'number', ['number', 'number', 'number']), generate: Module.cwrap('generate', 'number', []), freeResult: Module.cwrap('freeResult', '', []), getResultPointer: Module.cwrap('getResultPointer', 'number', []), getResultSize: Module.cwrap('getResultSize', 'number', []), }; postMessage('ok'); }; onmessage = (evt) => { const { data, delays, lossless = 1, quality = 75, method = 4} = evt.data; webpApi.init(lossless, quality, method); data.forEach((u8a, idx) => { const pointer = webpApi.createBuffer(u8a.length); Module.HEAPU8.set(u8a, pointer); webpApi.addFrame(pointer, u8a.length, delays[idx]); postMessage(idx); }); webpApi.generate(); const resultPointer = webpApi.getResultPointer(); const resultSize = webpApi.getResultSize(); const result = new Uint8Array(Module.HEAP8.buffer, resultPointer, resultSize); postMessage(result); webpApi.freeResult(); };`; const workerUrl = URL.createObjectURL(new Blob([workerChunk__default["default"] + handleWorker], { type: 'text/javascript' })); const freeWebpWorkers = []; function webp(frames, convertMeta) { return new Promise((resolve, reject) => { let worker; let reuse = false; logger.time(convertMeta.id); if (freeWebpWorkers.length) { logger.info('Reuse webp workers.'); worker = freeWebpWorkers.shift(); reuse = true; } else { worker = new Worker(workerUrl); } convertMeta.abort = () => { logger.timeEnd(convertMeta.id); logger.warn('Convert stop manually.' + convertMeta.id); reject(new CancelError()); convertMeta.isAborted = true; worker.terminate(); }; const workerLoad = new Promise((onLoaded) => { if (reuse) return onLoaded(); worker.onmessage = (evt) => { if (evt.data === 'ok') { logger.info('Webp worker loaded.'); onLoaded(); } }; }); const delays = convertMeta.source.delays; const data = []; let completed = 0; frames.forEach((frame, idx) => { const canvas = document.createElement('canvas'); const width = (canvas.width = frame.naturalWidth); const height = (canvas.height = frame.naturalHeight); const context = canvas.getContext('2d'); if (!context) return; context.drawImage(frame, 0, 0, width, height); data.push(new Promise((onFulfilled, onRejected) => { canvas.toBlob((blob) => { if (!blob) return onRejected(new TypeError('Convert failed when invoke canvas.toBlob() ' + idx)); blob.arrayBuffer().then((buffer) => { const u8a = new Uint8Array(buffer); onFulfilled(u8a); convertMeta.onProgress?.((++completed / frames.length) * 0.5); }); }, 'image/webp', 1); })); }); workerLoad .then(() => Promise.all(data)) .then((u8arrs) => { if (convertMeta.isAborted) return; logger.timeLog(convertMeta.id); worker.onmessage = (evt) => { const data = evt.data; if (typeof data !== 'object') { convertMeta.onProgress?.(0.5 + (evt.data / frames.length) * 0.5); } else { logger.timeEnd(convertMeta.id); freeWebpWorkers.push(worker); resolve(new Blob([evt.data], { type: 'image/webp' })); } }; worker.postMessage({ data: u8arrs, delays }); }, (reason) => { logger.timeLog(convertMeta.id); reject(reason); }); }); } 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 webm(frames, convertMeta) { return new Promise((resolve, reject) => { convertMeta.abort = () => { convertMeta.isAborted = true; reject(new CancelError()); }; const canvas = document.createElement('canvas'); canvas.width = frames[0].naturalWidth; canvas.height = frames[0].naturalHeight; const ctx = canvas.getContext('2d'); const videoWriter = new WebMWriter({ quality: 0.95, frameRate: 30, transparent: false }); const delays = convertMeta.source.delays; for (let i = 0; i < frames.length; i++) { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(frames[i], 0, 0); videoWriter.addFrame(canvas, delays[i]); } videoWriter.complete().then(resolve, reject); }); } 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; let frames; active.push(convertMeta); convertMeta.onProgress?.(0); delete framesData[id]; const imagePromises = source.data.map((blob) => { return new Promise((resolve) => { const image = new Image(); image.onload = () => { resolve(image); }; image.src = URL.createObjectURL(blob); }); }); Promise.all(imagePromises) .then((imgEles) => { frames = imgEles; if (convertMeta.isAborted) { logger.warn('Convert stop manually.' + id); throw new CancelError(); } const adapter = convertAdapter.getAdapter(format); return adapter(frames, convertMeta); }) .then(resolve, reject) .finally(() => { frames.forEach((frame) => URL.revokeObjectURL(frame.src)); active.splice(active.indexOf(convertMeta), 1); if (queue.length) doConvert(queue.shift()); }); }; return { del: (taskIds) => { if (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(setBtnProgress, pageCount) { if (!setBtnProgress || !pageCount) return; return function onSingleArtworkProgress(res) { if (pageCount === 1 && res.loaded > 0 && res.total > 0) { const progress = Math.floor((res.loaded / res.total) * 100); setBtnProgress(progress); } }; }, mulityArtworksProgressFactory(setBtnProgress, pageCount) { if (!setBtnProgress || !pageCount) return; let pageComplete = 0; return function onMulityArtworksProgress() { if (pageCount < 2) return; const progress = Math.floor((++pageComplete / pageCount) * 100); setBtnProgress(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(setBtnProgress) { return function onConvertProgress(progress) { if (progress > 0) { progress = Math.floor(progress * 100); setBtnProgress(progress, false); } else { setBtnProgress(null, false); } }; }, beforeFileSaveFactory(setBtnProgress) { const onProgress = setBtnProgress ? this.convertProgressFactory(setBtnProgress) : 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, setBtnProgress) { switch (downloadType) { case 'download': return { onProgress: pixivHooks.download.singleArtworkProgressFactory(setBtnProgress, meta.pageCount), onFileSaved: pixivHooks.download.mulityArtworksProgressFactory(setBtnProgress, meta.pageCount) }; case 'bundle': return { onXhrLoaded: pixivHooks.download.mulityArtworksProgressFactory(setBtnProgress, meta.pageCount), beforeFileSave: pixivHooks.bundle.beforeFileSave, onError: pixivHooks.bundle.onError }; case 'convert': return { onXhrLoaded: pixivHooks.download.mulityArtworksProgressFactory(setBtnProgress, meta.pageCount), beforeFileSave: pixivHooks.convert.beforeFileSaveFactory(setBtnProgress), 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(option) { const { illustType, src, id, pageCount, extendName } = this.meta; const downloadPage = option?.downloadPage; 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', option?.setBtnProgress); 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', option?.setBtnProgress); 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', option?.setBtnProgress); 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', option?.setBtnProgress); 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 downloadPixivArtwork(pdlBtn, setBtnProgress) { downloader.dirHandleCheck(); let downloadPage; const pageAttr = pdlBtn.getAttribute('page'); if (pageAttr) downloadPage = Number(pageAttr); const id = pdlBtn.getAttribute('pdl-id'); const pixivMeta = await pixivParser.parse(id); const { bookmarkData, token, tags, artist, userId, title } = pixivMeta; if (!bookmarkData) { addBookmark(pdlBtn, id, token, tags); } const downloadConfigs = new PixivDownloadConfig(pixivMeta).getDownloadConfig({ setBtnProgress, downloadPage }); await downloader.download(downloadConfigs); const historyData = { pid: Number(id), user: artist, userId: Number(userId), title, tags }; historyDb.add(historyData); } function createPixivPdlBtn(option) { return createPdlBtn({ ...option, downloadArtwork: downloadPixivArtwork }); } function createThumbnailsBtn(nodes) { let isSelfBookmark = false; const inBookmarkPage = regexp.bookmarkPage.exec(location.pathname); inBookmarkPage && inBookmarkPage[1] === getSelfId() && (isSelfBookmark = true); nodes.forEach((e) => { let illustId; if ((e.childElementCount !== 0 || e.className.includes('_history-item') || e.className.includes('_history-related-item')) && !e.querySelector('.pdl-btn-sub') && (illustId = getIllustId(e))) { const attrs = { attrs: { 'pdl-id': illustId }, classList: ['pdl-btn', 'pdl-btn-sub'] }; if (isSelfBookmark) attrs.classList.push('self-bookmark'); if (e.className.includes('_history-related-item')) e.style.position = 'relative'; const el = createPixivPdlBtn(attrs); historyDb.has(illustId).then((downloaded) => { downloaded && el.classList.add('pdl-complete'); }); e.appendChild(el); } }); } function fixPixivPreviewer(nodes) { const isPpSearchPage = regexp.searchPage.test(location.pathname); if (!isPpSearchPage) return; nodes.forEach((node) => { const pdlEle = node.querySelector('.pdl-btn'); if (!pdlEle) return false; pdlEle.remove(); }); } const 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) { if (document.querySelector('.pdl-btn-main')) return; const handleBar = document.querySelector('main section section'); if (handleBar) { const pdlBtnWrap = handleBar.lastElementChild.cloneNode(); const attrs = { attrs: { 'pdl-id': id }, classList: ['pdl-btn', 'pdl-btn-main'] }; const el = createPixivPdlBtn(attrs); historyDb.has(id).then((downloaded) => { downloaded && el.classList.add('pdl-complete'); }); pdlBtnWrap.appendChild(el); handleBar.appendChild(pdlBtnWrap); } } function createWorkScrollBtn(id) { const works = document.querySelectorAll("[role='presentation'] > a"); if (works.length < 2) return; const containers = Array.from(works).map((node) => node.parentElement.parentElement); if (containers[0].querySelector('.pdl-btn')) return; containers.forEach((node, idx) => { const wrapper = document.createElement('div'); wrapper.classList.add('pdl-wrap-artworks'); const attrs = { attrs: { 'pdl-id': id, page: String(idx) }, classList: ['pdl-btn', 'pdl-btn-sub', 'artworks'] }; wrapper.appendChild(createPixivPdlBtn(attrs)); node.appendChild(wrapper); }); } const createPresentationBtn = (() => { let observer, btn; function cb(mutationList) { const newImg = mutationList[1]['addedNodes'][0]; const [pageNum] = regexp.originSrcPageNum.exec(newImg.src) ?? []; if (!pageNum) throw new Error('[Error]Invalid Image Element.'); btn?.setAttribute('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; const attrs = { attrs: { 'pdl-id': id, page: pageNum }, classList: ['pdl-btn', 'pdl-btn-sub', 'presentation'] }; btn = createPixivPdlBtn(attrs); containers.appendChild(btn); if (!img.parentElement) return; observer = new MutationObserver(cb); observer.observe(img.parentElement, { childList: true, subtree: true }); }; })(); function createPreviewModalBtn() { const illustModalBtn = document.querySelector('.gtm-manga-viewer-preview-modal-open:not(.pdl-listened)'); const mangaModalBtn = document.querySelector('.gtm-manga-viewer-open-preview:not(.pdl-listened)'); const mangaViewerModalBtn = document.querySelectorAll('.gtm-manga-viewer-close-icon:not(.pdl-listened)')?.[1]; if (!illustModalBtn && !mangaModalBtn && !mangaViewerModalBtn) return; [illustModalBtn, mangaModalBtn, mangaViewerModalBtn].forEach((node) => { if (node) { node.classList.add('pdl-listened'); node.addEventListener('click', handleModalClick); } }); } function handleModalClick() { const timer = setInterval(() => { logger.info('Start to find modal.'); const ulList = document.querySelectorAll('ul'); const previewList = ulList[ulList.length - 1]; if (getComputedStyle(previewList).display !== 'grid') return; clearInterval(timer); const [, id] = regexp.artworksPage.exec(location.pathname) ?? []; previewList.childNodes.forEach((node, idx) => { node.style.position = 'relative'; const attrs = { attrs: { 'pdl-id': id, page: String(idx) }, classList: ['pdl-btn', 'pdl-btn-sub'] }; node.appendChild(createPixivPdlBtn(attrs)); }); }, 300); } function createMangaViewerBtn(id) { const mangaViewerBackBtn = document.querySelector('.gtm-manga-viewer-close-icon'); if (!mangaViewerBackBtn) return; const container = mangaViewerBackBtn.parentElement; if (container.querySelector('.pdl-btn')) return; container.appendChild(createPixivPdlBtn({ attrs: { 'pdl-id': id }, classList: ['pdl-btn', 'pdl-btn-sub', 'manga-viewer'] })); } function pageActions() { const pathname = location.pathname; let param; switch (true) { case !!(param = regexp.artworksPage.exec(pathname)): { const id = param[1]; createToolbarBtn(id); createWorkScrollBtn(id); createPresentationBtn(id); createPreviewModalBtn(); createMangaViewerBtn(id); break; } case !!(param = regexp.userPage.exec(pathname)): { const id = param[1] || 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): { createThumbnailsBtn(document.querySelectorAll('span[style]._history-item')); break; } default: removeDownloadBar(); break; } } let firstRun = true; function observerCallback(records) { const addedNodes = []; records.forEach((record) => { if (!record.addedNodes.length) return; record.addedNodes.forEach((node) => { if (node.nodeType === Node.ELEMENT_NODE && node.tagName !== 'BUTTON' && node.tagName !== 'IMG') { addedNodes.push(node); } }); }); if (!addedNodes.length) return; if (firstRun) { createThumbnailsBtn(document.querySelectorAll('a')); firstRun = false; } else { fixPixivPreviewer(addedNodes); const thunmnails = addedNodes.reduce((prev, current) => { return prev.concat(current instanceof HTMLAnchorElement ? [current] : Array.from(current.querySelectorAll('a'))); }, []); createThumbnailsBtn(thunmnails); } pageActions(); } 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$7); GM_addStyle(css); } } if (env.isPixiv()) { new Pixiv(); } else { new Rule34(); } })(Dexie, dayjs, JSZip, GIF, workerChunk);