// ==UserScript== // @name Pixiv Previewer L // @namespace https://github.com/LolipopJ/PixivPreviewer // @version 1.3.4-2025/10/31 // @description Original project: https://github.com/Ocrosoft/PixivPreviewer. // @author Ocrosoft, LolipopJ // @license GPL-3.0 // @supportURL https://github.com/LolipopJ/PixivPreviewer // @match *://www.pixiv.net/* // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM.xmlHttpRequest // @icon https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&size=32&url=https://www.pixiv.net // @icon64 https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&size=64&url=https://www.pixiv.net // @require https://update.greasyfork.icu/scripts/515994/1478507/gh_2215_make_GM_xhr_more_parallel_again.js // @require http://code.jquery.com/jquery-3.7.1.min.js // @run-at document-end // @downloadURL https://update.greasyfork.icu/scripts/533844/Pixiv%20Previewer%20L.user.js // @updateURL https://update.greasyfork.icu/scripts/533844/Pixiv%20Previewer%20L.meta.js // ==/UserScript== // src/constants/index.ts var g_version = "1.3.4"; var g_defaultSettings = { enablePreview: true, enableAnimePreview: true, previewDelay: 300, pageCount: 2, favFilter: 500, orderType: 0 /* BY_BOOKMARK_COUNT */, aiFilter: false, aiAssistedFilter: false, hideFavorite: true, hideByTag: false, hideByTagList: "", linkBlank: true, version: g_version }; var g_loadingImage = "https://pp-1252089172.cos.ap-chengdu.myqcloud.com/loading.gif"; var PREVIEW_WRAPPER_BORDER_WIDTH = 2; var PREVIEW_WRAPPER_BORDER_RADIUS = 8; var PREVIEW_WRAPPER_DISTANCE_TO_MOUSE = 20; var PREVIEW_PRELOAD_NUM = 5; var TOOLBAR_ID = "pp-toolbar"; var SORT_BUTTON_ID = "pp-sort"; var SORT_EVENT_NAME = "PIXIV_PREVIEWER_RUN_SORT"; var SORT_NEXT_PAGE_BUTTON_ID = "pp-sort-next-page"; var SORT_NEXT_PAGE_EVENT_NAME = "PIXIV_PREVIEWER_JUMP_TO_NEXT_PAGE"; var HIDE_FAVORITES_BUTTON_ID = "pp-hide-favorites"; var AI_ASSISTED_TAGS = [ "ai\u30A4\u30E9\u30B9\u30C8", "ai-generated", "ai-assisted", "ai-shoujo", "ai\u751F\u6210", "ai\u8F14\u52A9", "ai\u8F85\u52A9", "ai\u52A0\u7B46", "ai\u52A0\u7B14" ]; // src/utils/logger.ts var ILog = class { prefix = "%c Pixiv Preview"; v(...values) { console.log( this.prefix + " [VERBOSE] ", "color:#333 ;background-color: #fff", ...values ); } i(...infos) { console.info( this.prefix + " [INFO] ", "color:#333 ;background-color: #fff;", ...infos ); } w(...warnings) { console.warn( this.prefix + " [WARNING] ", "color:#111 ;background-color:#ffa500;", ...warnings ); } e(...errors) { console.error( this.prefix + " [ERROR] ", "color:#111 ;background-color:#ff0000;", ...errors ); } d(...data) { console.log( this.prefix + " [DATA] ", "color:#333 ;background-color: #fff;", ...data ); } }; var iLog = new ILog(); function DoLog(level = 3 /* Info */, ...msgOrElement) { switch (level) { case 1 /* Error */: iLog.e(...msgOrElement); break; case 2 /* Warning */: iLog.w(...msgOrElement); break; case 3 /* Info */: iLog.i(...msgOrElement); break; case 4 /* Elements */: case 0 /* None */: default: iLog.v(...msgOrElement); } } // src/databases/index.ts var INDEX_DB_NAME = "PIXIV_PREVIEWER_L"; var INDEX_DB_VERSION = 1; var ILLUSTRATION_DETAILS_CACHE_TABLE_KEY = "illustrationDetailsCache"; var ILLUSTRATION_DETAILS_CACHE_TIME = 1e3 * 60 * 60 * 6; var NEW_ILLUSTRATION_NOT_CACHE_TIME = 1e3 * 60 * 60 * 1; var request = indexedDB.open(INDEX_DB_NAME, INDEX_DB_VERSION); var db; request.onupgradeneeded = (event) => { const db2 = event.target.result; db2.createObjectStore(ILLUSTRATION_DETAILS_CACHE_TABLE_KEY, { keyPath: "id" }); }; request.onsuccess = (event) => { db = event.target.result; console.log("Open IndexedDB successfully:", db); deleteExpiredIllustrationDetails(); }; request.onerror = (event) => { iLog.e(`An error occurred while requesting IndexedDB`, event); }; var cacheIllustrationDetails = (illustrations, now = /* @__PURE__ */ new Date()) => { return new Promise(() => { const cachedIllustrationDetailsObjectStore = db.transaction(ILLUSTRATION_DETAILS_CACHE_TABLE_KEY, "readwrite").objectStore(ILLUSTRATION_DETAILS_CACHE_TABLE_KEY); illustrations.forEach((illustration) => { const uploadTimestamp = illustration.uploadTimestamp * 1e3; if (now.getTime() - uploadTimestamp > NEW_ILLUSTRATION_NOT_CACHE_TIME) { const illustrationDetails = { ...illustration, cacheDate: now }; const addCachedIllustrationDetailsRequest = cachedIllustrationDetailsObjectStore.put(illustrationDetails); addCachedIllustrationDetailsRequest.onerror = (event) => { iLog.e(`An error occurred while caching illustration details`, event); }; } }); }); }; var getCachedIllustrationDetails = (id, now = /* @__PURE__ */ new Date()) => { return new Promise((resolve) => { const cachedIllustrationDetailsObjectStore = db.transaction(ILLUSTRATION_DETAILS_CACHE_TABLE_KEY, "readwrite").objectStore(ILLUSTRATION_DETAILS_CACHE_TABLE_KEY); const getCachedIllustrationDetailsRequest = cachedIllustrationDetailsObjectStore.get(id); getCachedIllustrationDetailsRequest.onsuccess = (event) => { const illustrationDetails = event.target.result; if (illustrationDetails) { const { cacheDate } = illustrationDetails; if (now.getTime() - cacheDate.getTime() <= ILLUSTRATION_DETAILS_CACHE_TIME) { resolve(illustrationDetails); } else { cachedIllustrationDetailsObjectStore.delete(id).onerror = (event2) => { iLog.e( `An error occurred while deleting outdated illustration details`, event2 ); }; } } resolve(null); }; getCachedIllustrationDetailsRequest.onerror = (event) => { iLog.e( `An error occurred while getting cached illustration details`, event ); resolve(null); }; }); }; var deleteCachedIllustrationDetails = (ids) => { return new Promise((resolve) => { const cachedIllustrationDetailsObjectStore = db.transaction(ILLUSTRATION_DETAILS_CACHE_TABLE_KEY, "readwrite").objectStore(ILLUSTRATION_DETAILS_CACHE_TABLE_KEY); for (const id of ids) { const deleteCachedIllustrationDetailsRequest = cachedIllustrationDetailsObjectStore.delete(id); deleteCachedIllustrationDetailsRequest.onsuccess = () => { resolve(); }; deleteCachedIllustrationDetailsRequest.onerror = (event) => { iLog.w( `An error occurred while deleting cached details of illustration ${id}`, event ); resolve(); }; } }); }; function deleteExpiredIllustrationDetails() { return new Promise((resolve) => { const now = (/* @__PURE__ */ new Date()).getTime(); const cachedIllustrationDetailsObjectStore = db.transaction(ILLUSTRATION_DETAILS_CACHE_TABLE_KEY, "readwrite").objectStore(ILLUSTRATION_DETAILS_CACHE_TABLE_KEY); const getAllRequest = cachedIllustrationDetailsObjectStore.getAll(); getAllRequest.onsuccess = (event) => { const allEntries = event.target.result; allEntries.forEach((entry) => { if (now - entry.cacheDate.getTime() > ILLUSTRATION_DETAILS_CACHE_TIME) { cachedIllustrationDetailsObjectStore.delete(entry.id); } }); resolve(); }; }); } // src/features/hide-favorites.ts var isHidden = false; var hideFavorites = () => { const svgs = $("svg"); const favoriteSvgs = svgs.filter(function() { return $(this).css("color") === "rgb(255, 64, 96)"; }); favoriteSvgs.each(function() { const listItem = $(this).closest("li"); listItem.hide(); listItem.attr("data-pp-fav-hidden", "true"); }); isHidden = true; }; // src/icons/download.svg var download_default = '\n \n'; // src/icons/loading.svg var loading_default = '\n \n'; // src/icons/page.svg var page_default = '\n \n'; // src/utils/utils.ts var pause = (ms) => { return new Promise((resolve) => setTimeout(resolve, ms)); }; var convertObjectKeysFromSnakeToCamel = (obj) => { function snakeToCamel(snake) { return snake.replace(/_([a-z])/g, (result) => result[1].toUpperCase()); } const newResponse = {}; for (const key in obj) { newResponse[snakeToCamel(key)] = obj[key]; } return newResponse; }; // src/services/request.ts var xmlHttpRequest = window.GM.xmlHttpRequest; var request2 = (options) => { const { headers, ...restOptions } = options; return xmlHttpRequest({ responseType: "json", ...restOptions, headers: { referer: "https://www.pixiv.net/", ...headers } }); }; var requestWithRetry = async (options) => { const { retryDelay = 1e4, maxRetryTimes = Infinity, onRetry, ...restOptions } = options; let response; let retryTimes = 0; while (retryTimes < maxRetryTimes) { response = await request2(restOptions); if (response.status === 200) { const responseData = response.response; if (!responseData.error) { return response; } } retryTimes += 1; onRetry?.(response, retryTimes); await pause(retryDelay); } throw new Error( `Request for ${restOptions.url} failed: ${response.responseText}` ); }; var request_default = request2; // src/services/download.ts var downloadFile = (url, filename, options = {}) => { const { onload, onerror, ...restOptions } = options; request_default({ ...restOptions, url, method: "GET", responseType: "blob", onload: (resp) => { onload?.(resp); const blob = new Blob([resp.response], { // @ts-expect-error: specified in request options type: resp.responseType }); const blobUrl = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = blobUrl; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(blobUrl); }, onerror: (resp) => { onerror?.(resp); iLog.e(`Download ${filename} from ${url} failed: ${resp.responseText}`); } }); }; // src/services/illustration.ts var getIllustrationDetailsWithCache = async (id, retry = false) => { let illustDetails = await getCachedIllustrationDetails(id); if (illustDetails) { iLog.d(`Use cached details of illustration ${id}`, illustDetails); } else { const requestUrl = `/touch/ajax/illust/details?illust_id=${id}`; const getIllustDetailsRes = retry ? await requestWithRetry({ url: requestUrl, onRetry: (response, retryTimes) => { iLog.w( `Get illustration details via api \`${requestUrl}\` failed:`, response, `${retryTimes} times retrying...` ); } }) : await request_default({ url: requestUrl }); if (getIllustDetailsRes.status === 200) { illustDetails = convertObjectKeysFromSnakeToCamel( getIllustDetailsRes.response.body.illust_details ); cacheIllustrationDetails([illustDetails]); } else { illustDetails = null; } } return illustDetails; }; var getUserIllustrations = async (userId) => { const response = await request_default({ url: `/ajax/user/${userId}/profile/all?sensitiveFilterMode=userSetting&lang=zh` }); const responseData = response.response.body; const illusts = Object.keys(responseData.illusts).reverse(); const manga = Object.keys(responseData.manga).reverse(); const artworks = [...illusts, ...manga].sort((a, b) => Number(b) - Number(a)); return { illusts, manga, artworks }; }; var getUserIllustrationsWithCache = async (userId, { onRequesting } = {}) => { let userIllustrations = { illusts: [], manga: [], artworks: [] }; const userIllustrationsCacheKey = `PIXIV_PREVIEWER_CACHED_ARTWORKS_OF_USER_${userId}`; try { const userIllustrationsCacheString = sessionStorage.getItem( userIllustrationsCacheKey ); if (!userIllustrationsCacheString) throw new Error("Illustrations cache not existed."); userIllustrations = JSON.parse(userIllustrationsCacheString); } catch (error) { iLog.i( `Get illustrations of current user from session storage failed, re-getting...`, error ); onRequesting?.(); userIllustrations = await getUserIllustrations(userId); sessionStorage.setItem( userIllustrationsCacheKey, JSON.stringify(userIllustrations) ); } return userIllustrations; }; // src/services/preview.ts var downloadIllust = ({ url, filename, options = {} }) => { downloadFile(url, filename, { ...options, onerror: () => { window.open(url, "__blank"); } }); }; var getIllustPagesRequestUrl = (id) => { return `/ajax/illust/${id}/pages`; }; var getUgoiraMetadataRequestUrl = (id) => { return `/ajax/illust/${id}/ugoira_meta`; }; // src/utils/debounce.ts function debounce(func, delay = 100) { let timeout = null; return function(...args) { if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { func(...args); }, delay); }; } var debounce_default = debounce; // src/utils/event.ts var stopEventPropagation = (event) => { event.stopPropagation(); }; // src/utils/illustration.ts var checkIsR18 = (tags) => { const R18_TAGS = ["r-18", "r18"]; for (const tag of tags) { if (R18_TAGS.includes(tag.toLowerCase())) { return true; } } return false; }; var checkIsUgoira = (illustType) => { return illustType === 2 /* UGOIRA */; }; var checkIsAiGenerated = (aiType) => { return aiType === 2 /* AI */; }; var checkIsAiAssisted = (tags) => { for (const tag of tags) { if (AI_ASSISTED_TAGS.includes(tag.toLowerCase())) { return true; } } return false; }; // src/utils/mouse-monitor.ts var MouseMonitor = class { /** 鼠标相对网页的位置 */ mousePos = [0, 0]; /** 鼠标相对视窗的绝对位置 */ mouseAbsPos = [0, 0]; constructor() { document.addEventListener("mousemove", (mouseMoveEvent) => { this.mousePos = [mouseMoveEvent.pageX, mouseMoveEvent.pageY]; this.mouseAbsPos = [mouseMoveEvent.clientX, mouseMoveEvent.clientY]; }); } }; var mouseMonitor = new MouseMonitor(); var mouse_monitor_default = mouseMonitor; // src/utils/ugoira-player.ts function ZipImagePlayer(options) { this.op = options; this._URL = window.URL || window.webkitURL || window.MozURL || window.MSURL; this._Blob = window.Blob || window.WebKitBlob || window.MozBlob || window.MSBlob; this._BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder; this._Uint8Array = window.Uint8Array || window.WebKitUint8Array || window.MozUint8Array || window.MSUint8Array; this._DataView = window.DataView || window.WebKitDataView || window.MozDataView || window.MSDataView; this._ArrayBuffer = window.ArrayBuffer || window.WebKitArrayBuffer || window.MozArrayBuffer || window.MSArrayBuffer; this._maxLoadAhead = 0; if (!this._URL) { this._debugLog("No URL support! Will use slower data: URLs."); this._maxLoadAhead = 10; } if (!this._Blob) { this._error("No Blob support"); } if (!this._Uint8Array) { this._error("No Uint8Array support"); } if (!this._DataView) { this._error("No DataView support"); } if (!this._ArrayBuffer) { this._error("No ArrayBuffer support"); } this._isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor") > 0; this._loadingState = 0; this._dead = false; this._context = options.canvas.getContext("2d"); this._files = {}; this._frameCount = this.op.metadata.frames.length; this._debugLog("Frame count: " + this._frameCount); this._frame = 0; this._loadFrame = 0; this._frameImages = []; this._paused = false; this._loadTimer = null; this._startLoad(); if (this.op.autoStart) { this.play(); } else { this._paused = true; } } ZipImagePlayer.prototype = { _trailerBytes: 3e4, _failed: false, _mkerr: function(msg) { const _this = this; return function() { _this._error(msg); }; }, _error: function(msg) { this._failed = true; throw Error("ZipImagePlayer error: " + msg); }, _debugLog: function(msg) { if (this.op.debug) { console.log(msg); } }, _load: function(offset, length, callback) { const _this = this; const xhr = new XMLHttpRequest(); xhr.addEventListener( "load", function() { if (_this._dead) { return; } _this._debugLog( "Load: " + offset + " " + length + " status=" + xhr.status ); if (xhr.status == 200) { _this._debugLog("Range disabled or unsupported, complete load"); offset = 0; length = xhr.response.byteLength; _this._len = length; _this._buf = xhr.response; _this._bytes = new _this._Uint8Array(_this._buf); } else { if (xhr.status != 206) { _this._error("Unexpected HTTP status " + xhr.status); } if (xhr.response.byteLength != length) { _this._error( "Unexpected length " + xhr.response.byteLength + " (expected " + length + ")" ); } _this._bytes.set(new _this._Uint8Array(xhr.response), offset); } if (callback) { callback.apply(_this, [offset, length]); } }, false ); xhr.addEventListener("error", this._mkerr("Fetch failed"), false); xhr.open("GET", this.op.source); xhr.responseType = "arraybuffer"; if (offset != null && length != null) { const end = offset + length; xhr.setRequestHeader("Range", "bytes=" + offset + "-" + (end - 1)); if (this._isSafari) { xhr.setRequestHeader("Cache-control", "no-cache"); xhr.setRequestHeader("If-None-Match", Math.random().toString()); } } xhr.send(); }, _startLoad: function() { const _this = this; if (!this.op.source) { this._loadNextFrame(); return; } $.ajax({ url: this.op.source, type: "HEAD" }).done(function(data, status, xhr) { if (_this._dead) { return; } _this._pHead = 0; _this._pNextHead = 0; _this._pFetch = 0; const len = parseInt(String(xhr.getResponseHeader("Content-Length"))); if (!len) { _this._debugLog("HEAD request failed: invalid file length."); _this._debugLog("Falling back to full file mode."); _this._load(null, null, function(off2, len2) { _this._pTail = 0; _this._pHead = len2; _this._findCentralDirectory(); }); return; } _this._debugLog("Len: " + len); _this._len = len; _this._buf = new _this._ArrayBuffer(len); _this._bytes = new _this._Uint8Array(_this._buf); let off = len - _this._trailerBytes; if (off < 0) { off = 0; } _this._pTail = len; _this._load(off, len - off, function(off2) { _this._pTail = off2; _this._findCentralDirectory(); }); }).fail(this._mkerr("Length fetch failed")); }, _findCentralDirectory: function() { const dv = new this._DataView(this._buf, this._len - 22, 22); if (dv.getUint32(0, true) != 101010256) { this._error("End of Central Directory signature not found"); } const cd_count = dv.getUint16(10, true); const cd_size = dv.getUint32(12, true); const cd_off = dv.getUint32(16, true); if (cd_off < this._pTail) { this._load(cd_off, this._pTail - cd_off, function() { this._pTail = cd_off; this._readCentralDirectory(cd_off, cd_size, cd_count); }); } else { this._readCentralDirectory(cd_off, cd_size, cd_count); } }, _readCentralDirectory: function(offset, size, count) { const dv = new this._DataView(this._buf, offset, size); let p = 0; for (let i = 0; i < count; i++) { if (dv.getUint32(p, true) != 33639248) { this._error("Invalid Central Directory signature"); } const compMethod = dv.getUint16(p + 10, true); const uncompSize = dv.getUint32(p + 24, true); const nameLen = dv.getUint16(p + 28, true); const extraLen = dv.getUint16(p + 30, true); const cmtLen = dv.getUint16(p + 32, true); const off = dv.getUint32(p + 42, true); if (compMethod != 0) { this._error("Unsupported compression method"); } p += 46; const nameView = new this._Uint8Array(this._buf, offset + p, nameLen); let name = ""; for (let j = 0; j < nameLen; j++) { name += String.fromCharCode(nameView[j]); } p += nameLen + extraLen + cmtLen; this._files[name] = { off, len: uncompSize }; } if (this._pHead >= this._pTail) { this._pHead = this._len; $(this).triggerHandler("loadProgress", [this._pHead / this._len]); this._loadNextFrame(); } else { this._loadNextChunk(); this._loadNextChunk(); } }, _loadNextChunk: function() { if (this._pFetch >= this._pTail) { return; } const off = this._pFetch; let len = this.op.chunkSize; if (this._pFetch + len > this._pTail) { len = this._pTail - this._pFetch; } this._pFetch += len; this._load(off, len, function() { if (off == this._pHead) { if (this._pNextHead) { this._pHead = this._pNextHead; this._pNextHead = 0; } else { this._pHead = off + len; } if (this._pHead >= this._pTail) { this._pHead = this._len; } $(this).triggerHandler("loadProgress", [this._pHead / this._len]); if (!this._loadTimer) { this._loadNextFrame(); } } else { this._pNextHead = off + len; } this._loadNextChunk(); }); }, _fileDataStart: function(offset) { const dv = new DataView(this._buf, offset, 30); const nameLen = dv.getUint16(26, true); const extraLen = dv.getUint16(28, true); return offset + 30 + nameLen + extraLen; }, _isFileAvailable: function(name) { const info = this._files[name]; if (!info) { this._error("File " + name + " not found in ZIP"); } if (this._pHead < info.off + 30) { return false; } return this._pHead >= this._fileDataStart(info.off) + info.len; }, _loadNextFrame: function() { if (this._dead) { return; } const frame = this._loadFrame; if (frame >= this._frameCount) { return; } const meta = this.op.metadata.frames[frame]; if (!this.op.source) { this._loadFrame += 1; this._loadImage(frame, meta.file, false); return; } if (!this._isFileAvailable(meta.file)) { return; } this._loadFrame += 1; const off = this._fileDataStart(this._files[meta.file].off); const end = off + this._files[meta.file].len; let url; const mime_type = this.op.metadata.mime_type || "image/png"; if (this._URL) { let slice; if (!this._buf.slice) { slice = new this._ArrayBuffer(this._files[meta.file].len); const view = new this._Uint8Array(slice); view.set(this._bytes.subarray(off, end)); } else { slice = this._buf.slice(off, end); } let blob; try { blob = new this._Blob([slice], { type: mime_type }); } catch (err) { this._debugLog( "Blob constructor failed. Trying BlobBuilder... (" + err.message + ")" ); const bb = new this._BlobBuilder(); bb.append(slice); blob = bb.getBlob(); } url = this._URL.createObjectURL(blob); this._loadImage(frame, url, true); } else { url = "data:" + mime_type + ";base64," + base64ArrayBuffer(this._buf, off, end - off); this._loadImage(frame, url, false); } }, _loadImage: function(frame, url, isBlob) { const _this = this; const image = new Image(); const meta = this.op.metadata.frames[frame]; image.addEventListener("load", function() { _this._debugLog("Loaded " + meta.file + " to frame " + frame); if (isBlob) { _this._URL.revokeObjectURL(url); } if (_this._dead) { return; } _this._frameImages[frame] = image; $(_this).triggerHandler("frameLoaded", frame); if (_this._loadingState == 0) { _this._displayFrame.apply(_this); } if (frame >= _this._frameCount - 1) { _this._setLoadingState(2); _this._buf = null; _this._bytes = null; } else { if (!_this._maxLoadAhead || frame - _this._frame < _this._maxLoadAhead) { _this._loadNextFrame(); } else if (!_this._loadTimer) { _this._loadTimer = setTimeout(function() { _this._loadTimer = null; _this._loadNextFrame(); }, 200); } } }); image.src = url; }, _setLoadingState: function(state) { if (this._loadingState != state) { this._loadingState = state; $(this).triggerHandler("loadingStateChanged", [state]); } }, _displayFrame: function() { if (this._dead) { return; } const _this = this; const meta = this.op.metadata.frames[this._frame]; this._debugLog("Displaying frame: " + this._frame + " " + meta.file); const image = this._frameImages[this._frame]; if (!image) { this._debugLog("Image not available!"); this._setLoadingState(0); return; } if (this._loadingState != 2) { this._setLoadingState(1); } if (this.op.autosize) { if (this._context.canvas.width != image.width || this._context.canvas.height != image.height) { this._context.canvas.width = image.width; this._context.canvas.height = image.height; } } this._context.clearRect(0, 0, this.op.canvas.width, this.op.canvas.height); this._context.drawImage(image, 0, 0); $(this).triggerHandler("frame", this._frame); if (!this._paused) { this._timer = setTimeout(function() { _this._timer = null; _this._nextFrame.apply(_this); }, meta.delay); } }, _nextFrame: function() { if (this._frame >= this._frameCount - 1) { if (this.op.loop) { this._frame = 0; } else { this.pause(); return; } } else { this._frame += 1; } this._displayFrame(); }, play: function() { if (this._dead) { return; } if (this._paused) { $(this).triggerHandler("play", [this._frame]); this._paused = false; this._displayFrame(); } }, pause: function() { if (this._dead) { return; } if (!this._paused) { if (this._timer) { clearTimeout(this._timer); } this._paused = true; $(this).triggerHandler("pause", [this._frame]); } }, rewind: function() { if (this._dead) { return; } this._frame = 0; if (this._timer) { clearTimeout(this._timer); } this._displayFrame(); }, stop: function() { this._debugLog("Stopped!"); this._dead = true; if (this._timer) { clearTimeout(this._timer); } if (this._loadTimer) { clearTimeout(this._loadTimer); } this._frameImages = null; this._buf = null; this._bytes = null; $(this).triggerHandler("stop"); }, getCurrentFrame: function() { return this._frame; }, getLoadedFrames: function() { return this._frameImages.length; }, getFrameCount: function() { return this._frameCount; }, hasError: function() { return this._failed; } }; function base64ArrayBuffer(arrayBuffer, off, byteLength) { let base64 = ""; const encodings = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; const bytes = new Uint8Array(arrayBuffer); const byteRemainder = byteLength % 3; const mainLength = off + byteLength - byteRemainder; let a, b, c, d; let chunk; for (let i = off; i < mainLength; i = i + 3) { chunk = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2]; a = (chunk & 16515072) >> 18; b = (chunk & 258048) >> 12; c = (chunk & 4032) >> 6; d = chunk & 63; base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d]; } if (byteRemainder == 1) { chunk = bytes[mainLength]; a = (chunk & 252) >> 2; b = (chunk & 3) << 4; base64 += encodings[a] + encodings[b] + "=="; } else if (byteRemainder == 2) { chunk = bytes[mainLength] << 8 | bytes[mainLength + 1]; a = (chunk & 64512) >> 10; b = (chunk & 1008) >> 4; c = (chunk & 15) << 2; base64 += encodings[a] + encodings[b] + encodings[c] + "="; } return base64; } var ugoira_player_default = ZipImagePlayer; // src/features/preview.ts var isInitialized = false; var loadIllustPreview = (options) => { if (isInitialized) return; const { previewDelay, enableAnimePreview, linkBlank } = options; const mouseHoverDebounceWait = previewDelay / 5; const mouseHoverPreviewWait = previewDelay - mouseHoverDebounceWait; const getIllustMetadata = (target) => { let imgLink = target; while (!imgLink.is("A")) { imgLink = imgLink.parent(); if (!imgLink.length) { return null; } } const illustHref = imgLink.attr("href"); const illustHrefMatch = illustHref?.match(/\/artworks\/(\d+)(#(\d+))?/); if (!illustHrefMatch) { return null; } const illustId = illustHrefMatch[1]; const previewPage = Number(illustHrefMatch[3] ?? 1); const ugoiraSvg = imgLink.children("div:first").find("svg:first"); const illustType = ugoiraSvg.length || imgLink.hasClass("ugoku-illust") ? 2 /* UGOIRA */ : ( // 合并漫画类型作品 IllustType.MANGA 为 IllustType.ILLUST 统一处理 0 /* ILLUST */ ); return { /** 作品 ID */ illustId, /** 作品页码 */ previewPage, /** 作品类型 */ illustType, /** 作品链接 DOM */ illustLinkDom: imgLink }; }; const previewIllust = (() => { const previewedIllust = new PreviewedIllust(); let currentHoveredIllustId = ""; let getIllustPagesRequest = $.ajax(); const getIllustPagesCache = {}; const getUgoiraMetadataCache = {}; return ({ target, illustId, previewPage = 1, illustType }) => { getIllustPagesRequest.abort(); currentHoveredIllustId = illustId; if (illustType === 2 /* UGOIRA */ && !enableAnimePreview) { iLog.i("\u52A8\u56FE\u9884\u89C8\u5DF2\u7981\u7528\uFF0C\u8DF3\u8FC7"); return; } if ([0 /* ILLUST */, 1 /* MANGA */].includes(illustType)) { if (getIllustPagesCache[illustId]) { previewedIllust.setImage({ illustId, illustElement: target, previewPage, ...getIllustPagesCache[illustId] }); return; } getIllustPagesRequest = $.ajax(getIllustPagesRequestUrl(illustId), { method: "GET", success: (data) => { if (data.error) { iLog.e( `An error occurred while requesting preview urls of illust ${illustId}: ${data.message}` ); return; } const urls = data.body.map((item) => item.urls); const regularUrls = urls.map((url) => url.regular); const originalUrls = urls.map((url) => url.original); getIllustPagesCache[illustId] = { regularUrls, originalUrls }; if (currentHoveredIllustId !== illustId) return; previewedIllust.setImage({ illustId, illustElement: target, previewPage, regularUrls, originalUrls }); }, error: (err) => { iLog.e( `An error occurred while requesting preview urls of illust ${illustId}: ${err}` ); } }); } else if (illustType === 2 /* UGOIRA */) { if (getUgoiraMetadataCache[illustId]) { previewedIllust.setUgoira({ illustId, illustElement: target, ...getUgoiraMetadataCache[illustId] }); return; } getIllustPagesRequest = $.ajax(getUgoiraMetadataRequestUrl(illustId), { method: "GET", success: (data) => { if (data.error) { iLog.e( `An error occurred while requesting metadata of ugoira ${illustId}: ${data.message}` ); return; } getUgoiraMetadataCache[illustId] = data.body; if (currentHoveredIllustId !== illustId) return; const { src, originalSrc, mime_type, frames } = data.body; previewedIllust.setUgoira({ illustId, illustElement: target, src, originalSrc, mime_type, frames }); }, error: (err) => { iLog.e( `An error occurred while requesting metadata of ugoira ${illustId}: ${err.responseText}` ); } }); } else { iLog.e("Unknown illust type."); return; } }; })(); const onMouseOverIllust = (target) => { const { illustId, previewPage, illustType, illustLinkDom } = getIllustMetadata(target) || {}; if (illustId === void 0 || illustType === void 0) { return; } if (linkBlank) { illustLinkDom.attr({ target: "_blank", rel: "external" }); illustLinkDom.off("click", stopEventPropagation); illustLinkDom.on("click", stopEventPropagation); } const previewIllustTimeout = setTimeout(() => { previewIllust({ target, illustId, previewPage, illustType }); }, mouseHoverPreviewWait); const onMouseMove = (mouseMoveEvent) => { if (mouseMoveEvent.ctrlKey || mouseMoveEvent.metaKey) { clearTimeout(previewIllustTimeout); target.off("mousemove", onMouseMove); } }; target.on("mousemove", onMouseMove); const onMouseOut = () => { clearTimeout(previewIllustTimeout); target.off("mouseout", onMouseOut); }; target.on("mouseout", onMouseOut); }; const onMouseMoveDocument = (() => { const debouncedOnMouseOverIllust = debounce_default( onMouseOverIllust, mouseHoverDebounceWait ); let prevTarget; return (mouseMoveEvent) => { if (mouseMoveEvent.ctrlKey || mouseMoveEvent.metaKey) { return; } const currentTarget = $( mouseMoveEvent.target ); if (currentTarget.is(prevTarget)) { return; } prevTarget = currentTarget; debouncedOnMouseOverIllust(currentTarget); }; })(); $(document).on("mousemove", onMouseMoveDocument); (function inactiveUnexpectedDoms() { const styleRules = $("