// ==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 = '';
// src/icons/loading.svg
var loading_default = '';
// src/icons/page.svg
var page_default = '';
// 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 = $("