Greasy Fork

Greasy Fork is available in English.

Pixiv Previewer L

Original project: https://github.com/Ocrosoft/PixivPreviewer.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name                Pixiv Previewer L
// @namespace           https://github.com/LolipopJ/PixivPreviewer
// @version             1.4.3-20260407
// @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             https://code.jquery.com/jquery-4.0.0.min.js
// @require             https://code.jquery.com/jquery-migrate-4.0.2.min.js
// @run-at              document-end
// ==/UserScript==

//#region src/enums/index.ts
let LogLevel = /* @__PURE__ */ function(LogLevel) {
	LogLevel[LogLevel["None"] = 0] = "None";
	LogLevel[LogLevel["Error"] = 1] = "Error";
	LogLevel[LogLevel["Warning"] = 2] = "Warning";
	LogLevel[LogLevel["Info"] = 3] = "Info";
	LogLevel[LogLevel["Elements"] = 4] = "Elements";
	return LogLevel;
}({});
let IllustType = /* @__PURE__ */ function(IllustType) {
	/** 插画 */
	IllustType[IllustType["ILLUST"] = 0] = "ILLUST";
	/** 漫画 */
	IllustType[IllustType["MANGA"] = 1] = "MANGA";
	/** 动图 */
	IllustType[IllustType["UGOIRA"] = 2] = "UGOIRA";
	return IllustType;
}({});
let AiType = /* @__PURE__ */ function(AiType) {
	/** 非 AI 生成 */
	AiType[AiType["NONE_AI"] = 1] = "NONE_AI";
	/** AI 生成 */
	AiType[AiType["AI"] = 2] = "AI";
	return AiType;
}({});
let PageType = /* @__PURE__ */ function(PageType) {
	PageType[PageType["Search"] = 0] = "Search";
	PageType[PageType["BookMarkNew"] = 1] = "BookMarkNew";
	PageType[PageType["Discovery"] = 2] = "Discovery";
	PageType[PageType["Member"] = 3] = "Member";
	PageType[PageType["Home"] = 4] = "Home";
	PageType[PageType["Ranking"] = 5] = "Ranking";
	PageType[PageType["NewIllust"] = 6] = "NewIllust";
	PageType[PageType["R18"] = 7] = "R18";
	PageType[PageType["Stacc"] = 8] = "Stacc";
	PageType[PageType["Artwork"] = 9] = "Artwork";
	PageType[PageType["NovelSearch"] = 10] = "NovelSearch";
	PageType[PageType["SearchTop"] = 11] = "SearchTop";
	return PageType;
}({});
/** 插画(漫画)作品排序类型 */
let IllustSortType = /* @__PURE__ */ function(IllustSortType) {
	/** @link https://www.pixiv.net/tags/%E5%A4%A9%E7%AB%A5%E3%82%A2%E3%83%AA%E3%82%B9/artworks */
	IllustSortType[IllustSortType["TAG_ARTWORK"] = 0] = "TAG_ARTWORK";
	/** @link https://www.pixiv.net/tags/%E5%A4%A9%E7%AB%A5%E3%82%A2%E3%83%AA%E3%82%B9/illustrations */
	IllustSortType[IllustSortType["TAG_ILLUST"] = 1] = "TAG_ILLUST";
	/** @link https://www.pixiv.net/tags/%E5%A4%A9%E7%AB%A5%E3%82%A2%E3%83%AA%E3%82%B9/manga */
	IllustSortType[IllustSortType["TAG_MANGA"] = 2] = "TAG_MANGA";
	/** @link https://www.pixiv.net/search?q=%E5%A4%A9%E7%AB%A5%E3%82%A2%E3%83%AA%E3%82%B9&s_mode=tag&type=illust_ugoira */
	IllustSortType[IllustSortType["SEARCH_ILLUST"] = 3] = "SEARCH_ILLUST";
	/** @link https://www.pixiv.net/search?q=%E5%A4%A9%E7%AB%A5%E3%82%A2%E3%83%AA%E3%82%B9&s_mode=tag&type=manga */
	IllustSortType[IllustSortType["SEARCH_MANGA"] = 4] = "SEARCH_MANGA";
	/** @link https://www.pixiv.net/bookmark_new_illust.php */
	IllustSortType[IllustSortType["BOOKMARK_NEW"] = 5] = "BOOKMARK_NEW";
	/** @link https://www.pixiv.net/bookmark_new_illust_r18.php */
	IllustSortType[IllustSortType["BOOKMARK_NEW_R18"] = 6] = "BOOKMARK_NEW_R18";
	/** @link https://www.pixiv.net/users/333556/artworks */
	IllustSortType[IllustSortType["USER_ARTWORK"] = 7] = "USER_ARTWORK";
	/** @link https://www.pixiv.net/users/333556/illustrations */
	IllustSortType[IllustSortType["USER_ILLUST"] = 8] = "USER_ILLUST";
	/** @link https://www.pixiv.net/users/49906039/manga */
	IllustSortType[IllustSortType["USER_MANGA"] = 9] = "USER_MANGA";
	/** @link https://www.pixiv.net/users/17435436/bookmarks/artworks */
	IllustSortType[IllustSortType["USER_BOOKMARK"] = 10] = "USER_BOOKMARK";
	return IllustSortType;
}({});
/** 作品排序顺序 */
let IllustSortOrder = /* @__PURE__ */ function(IllustSortOrder) {
	/** 按收藏数 */
	IllustSortOrder[IllustSortOrder["BY_BOOKMARK_COUNT"] = 0] = "BY_BOOKMARK_COUNT";
	/** 按发布日期 */
	IllustSortOrder[IllustSortOrder["BY_DATE"] = 1] = "BY_DATE";
	return IllustSortOrder;
}({});

//#endregion
//#region src/constants/index.ts
/** 版本号,发生改变时将会弹窗 */
const g_version = "1.4.3";
/** 默认设置 */
const g_defaultSettings = {
	enablePreview: true,
	enableAnimePreview: true,
	previewDelay: 300,
	pageCount: 2,
	favFilter: 500,
	orderType: IllustSortOrder.BY_BOOKMARK_COUNT,
	aiFilter: false,
	aiAssistedFilter: false,
	hideFavorite: true,
	hideByTag: false,
	hideByTagList: "",
	linkBlank: true,
	version: g_version
};
/** 加载中占位图片 */
const g_loadingImage = "https://pp-1252089172.cos.ap-chengdu.myqcloud.com/loading.gif";
/** 工具栏 ID */
const TOOLBAR_ID = "pp-toolbar";
/** 排序按钮 ID */
const SORT_BUTTON_ID = "pp-sort";
/** 排序事件名称 */
const SORT_EVENT_NAME = "PIXIV_PREVIEWER_RUN_SORT";
/** 下一页按钮 ID */
const SORT_NEXT_PAGE_BUTTON_ID = "pp-sort-next-page";
/** 下一页事件名称 */
const SORT_NEXT_PAGE_EVENT_NAME = "PIXIV_PREVIEWER_JUMP_TO_NEXT_PAGE";
/** 隐藏已收藏作品按钮 */
const HIDE_FAVORITES_BUTTON_ID = "pp-hide-favorites";
/** AI 辅助标签列表,全小写 */
const AI_ASSISTED_TAGS = [
	"aiイラスト",
	"ai-generated",
	"ai-assisted",
	"ai-shoujo",
	"ai生成",
	"ai輔助",
	"ai辅助",
	"ai加筆",
	"ai加笔"
];

//#endregion
//#region 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);
	}
};
const iLog = new ILog();
function DoLog(level = LogLevel.Info, ...msgOrElement) {
	switch (level) {
		case LogLevel.Error:
			iLog.e(...msgOrElement);
			break;
		case LogLevel.Warning:
			iLog.w(...msgOrElement);
			break;
		case LogLevel.Info:
			iLog.i(...msgOrElement);
			break;
		case LogLevel.Elements:
		case LogLevel.None:
		default: iLog.v(...msgOrElement);
	}
}

//#endregion
//#region src/databases/index.ts
const INDEX_DB_NAME = "PIXIV_PREVIEWER_L";
const INDEX_DB_VERSION = 1;
const ILLUSTRATION_DETAILS_CACHE_TABLE_KEY = "illustrationDetailsCache";
/** 缓存过期时间 */
const ILLUSTRATION_DETAILS_CACHE_TIME = 1e3 * 60 * 60 * 6;
/** 新作品发布初期不添加缓存 */
const NEW_ILLUSTRATION_NOT_CACHE_TIME = 1e3 * 60 * 60 * 1;
const request$1 = indexedDB.open(INDEX_DB_NAME, INDEX_DB_VERSION);
let db;
request$1.onupgradeneeded = (event) => {
	event.target.result.createObjectStore(ILLUSTRATION_DETAILS_CACHE_TABLE_KEY, { keyPath: "id" });
};
request$1.onsuccess = (event) => {
	db = event.target.result;
	console.log("Open IndexedDB successfully:", db);
	deleteExpiredIllustrationDetails();
};
request$1.onerror = (event) => {
	iLog.e(`An error occurred while requesting IndexedDB`, event);
};
const 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);
				};
			}
		});
	});
};
const 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 = (event) => {
					iLog.e(`An error occurred while deleting outdated illustration details`, event);
				};
			}
			resolve(null);
		};
		getCachedIllustrationDetailsRequest.onerror = (event) => {
			iLog.e(`An error occurred while getting cached illustration details`, event);
			resolve(null);
		};
	});
};
const 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) => {
			event.target.result.forEach((entry) => {
				if (now - entry.cacheDate.getTime() > ILLUSTRATION_DETAILS_CACHE_TIME) cachedIllustrationDetailsObjectStore.delete(entry.id);
			});
			resolve();
		};
	});
}

//#endregion
//#region src/features/hide-favorites.ts
let isHidden = false;
const hideFavorites = () => {
	$("svg").filter(function() {
		return $(this).css("color") === "rgb(255, 64, 96)";
	}).each(function() {
		const listItem = $(this).closest("li, div.col-span-2");
		listItem.hide();
		listItem.attr("data-pp-fav-hidden", "true");
	});
	isHidden = true;
};

//#endregion
//#region src/icons/download.svg
var download_default = "<svg t=\"1742281193586\" class=\"icon\" viewBox=\"0 0 1024 1024\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\"\n  p-id=\"24408\" width=\"10\" height=\"10\">\n  <path\n    d=\"M1024 896v128H0v-320h128v192h768v-192h128v192zM576 554.688L810.688 320 896 405.312l-384 384-384-384L213.312 320 448 554.688V0h128v554.688z\"\n    fill=\"#ffffff\" p-id=\"24409\"></path>\n</svg>";

//#endregion
//#region src/icons/loading.svg
var loading_default = "<svg t=\"1742282291278\" class=\"icon\" viewBox=\"0 0 1024 1024\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\"\n  p-id=\"38665\" width=\"48\" height=\"48\">\n  <path\n    d=\"M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 0 0-94.3-139.9 437.71 437.71 0 0 0-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3 0.1 19.9-16 36-35.9 36z\"\n    p-id=\"38666\" fill=\"#1296db\"></path>\n</svg>";

//#endregion
//#region src/icons/page.svg
var page_default = "<svg viewBox=\"0 0 10 10\" width=\"10\" height=\"10\">\n  <path\n    d=\"M 8 3 C 8.55228 3 9 3.44772 9 4 L 9 9 C 9 9.55228 8.55228 10 8 10 L 3 10 C 2.44772 10 2 9.55228 2 9 L 6 9 C 7.10457 9 8 8.10457 8 7 L 8 3 Z M 1 1 L 6 1 C 6.55228 1 7 1.44772 7 2 L 7 7 C 7 7.55228 6.55228 8 6 8 L 1 8 C 0.447715 8 0 7.55228 0 7 L 0 2 C 0 1.44772 0.447715 1 1 1 Z\"\n    fill=\"#ffffff\"></path>\n</svg>";

//#endregion
//#region src/utils/utils.ts
const pause = (ms) => {
	return new Promise((resolve) => setTimeout(resolve, ms));
};
const 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;
};

//#endregion
//#region src/services/request.ts
const xmlHttpRequest = window.GM.xmlHttpRequest;
const request = (options) => {
	const { headers, ...restOptions } = options;
	return xmlHttpRequest({
		responseType: "json",
		...restOptions,
		headers: {
			referer: "https://www.pixiv.net/",
			...headers
		}
	});
};
const requestWithRetry = async (options) => {
	const { retryDelay = 1e4, maxRetryTimes = Infinity, onRetry, ...restOptions } = options;
	let response;
	let retryTimes = 0;
	while (retryTimes < maxRetryTimes) {
		response = await request(restOptions);
		if (response.status === 200) {
			if (!response.response.error) return response;
		}
		retryTimes += 1;
		onRetry?.(response, retryTimes);
		await pause(retryDelay);
	}
	throw new Error(`Request for ${restOptions.url} failed: ${response.responseText}`);
};

//#endregion
//#region src/services/download.ts
const downloadFile = (url, filename, options = {}) => {
	const { onload, onerror, ...restOptions } = options;
	request({
		...restOptions,
		url,
		method: "GET",
		responseType: "blob",
		onload: function(resp) {
			onload?.call(this, resp);
			const blob = new Blob([resp.response], { 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: function(resp) {
			onerror?.call(this, resp);
			iLog.e(`Download ${filename} from ${url} failed: ${resp.responseText}`);
		}
	});
};

//#endregion
//#region src/services/illustration.ts
/** 从 IndexedDB 或接口获取指定作品的详细信息 */
const 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({ url: requestUrl });
		if (getIllustDetailsRes.status === 200) {
			illustDetails = convertObjectKeysFromSnakeToCamel(getIllustDetailsRes.response.body.illust_details);
			cacheIllustrationDetails([illustDetails]);
		} else illustDetails = null;
	}
	return illustDetails;
};
/** 获取用户发布的所有作品,按照发布时间倒叙 */
const getUserIllustrations = async (userId) => {
	const responseData = (await request({ url: `/ajax/user/${userId}/profile/all?sensitiveFilterMode=userSetting&lang=zh` })).response.body;
	const illusts = Object.keys(responseData.illusts).reverse();
	const manga = Object.keys(responseData.manga).reverse();
	return {
		illusts,
		manga,
		artworks: [...illusts, ...manga].sort((a, b) => Number(b) - Number(a))
	};
};
/** 从 Session Storage 或接口获取指定用户的作品列表 */
const 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;
};

//#endregion
//#region src/services/preview.ts
/** 下载作品 */
const downloadIllust = ({ url, filename, options = {} }) => {
	downloadFile(url, filename, {
		...options,
		onerror: function(resp) {
			options.onerror?.call(this, resp);
			window.open(url, "__blank");
		}
	});
};
/** 获取图片分页信息和访问链接 */
const getIllustPagesRequestUrl = (id) => {
	return `/ajax/illust/${id}/pages`;
};
/** 获取动图下载链接的链接 */
const getUgoiraMetadataRequestUrl = (id) => {
	return `/ajax/illust/${id}/ugoira_meta`;
};

//#endregion
//#region src/utils/debounce.ts
function debounce(func, delay = 100) {
	let timeout = null;
	return function(...args) {
		if (timeout) clearTimeout(timeout);
		timeout = setTimeout(() => {
			func(...args);
		}, delay);
	};
}

//#endregion
//#region src/utils/event.ts
const stopEventPropagation = (event) => {
	event.stopPropagation();
};

//#endregion
//#region src/utils/illustration.ts
const checkIsR18 = (tags) => {
	const R18_TAGS = ["r-18", "r18"];
	for (const tag of tags) if (R18_TAGS.includes(tag.toLowerCase())) return true;
	return false;
};
const checkIsUgoira = (illustType) => {
	return illustType === IllustType.UGOIRA;
};
const checkIsAiGenerated = (aiType) => {
	return aiType === AiType.AI;
};
const checkIsAiAssisted = (tags) => {
	for (const tag of tags) if (AI_ASSISTED_TAGS.includes(tag.toLowerCase())) return true;
	return false;
};

//#endregion
//#region 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];
		});
	}
};
const mouseMonitor = new MouseMonitor();

//#endregion
//#region 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(off, len) {
					_this._pTail = 0;
					_this._pHead = len;
					_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(off) {
				_this._pTail = off;
				_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);
				new this._Uint8Array(slice).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;
}

//#endregion
//#region src/features/preview.ts
let isInitialized$1 = false;
const loadIllustPreview = (options) => {
	if (isInitialized$1) return;
	const { previewDelay, enableAnimePreview, linkBlank } = options;
	const mouseHoverDebounceWait = previewDelay / 5;
	const mouseHoverPreviewWait = previewDelay - mouseHoverDebounceWait;
	/**
	* 获取作品的元数据信息
	* @param target 查找的 JQuery 对象
	* @returns 作品的元数据
	*/
	const getIllustMetadata = (target) => {
		const imgLink = target.closest("a");
		if (!imgLink.length) return null;
		const illustHrefMatch = imgLink.attr("href")?.match(/\/artworks\/(\d+)(#(\d+))?/);
		if (!illustHrefMatch) return null;
		return {
			illustId: illustHrefMatch[1],
			previewPage: Number(illustHrefMatch[3] ?? 1),
			illustType: imgLink.children("div:first").find("svg:first").length || imgLink.hasClass("ugoku-illust") ? IllustType.UGOIRA : IllustType.ILLUST,
			illustLinkDom: imgLink
		};
	};
	/**
	* 获取作品访问链接并在前端显示预览
	* @param target 作品的元数据
	*/
	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 === IllustType.UGOIRA && !enableAnimePreview) {
				iLog.i("动图预览已禁用,跳过");
				return;
			}
			if ([IllustType.ILLUST, IllustType.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 === IllustType.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(onMouseOverIllust, mouseHoverDebounceWait);
		let prevTarget = null;
		return (mouseMoveEvent) => {
			if (mouseMoveEvent.ctrlKey || mouseMoveEvent.metaKey) return;
			if (mouseMoveEvent.target === prevTarget) return;
			prevTarget = mouseMoveEvent.target;
			debouncedOnMouseOverIllust($(mouseMoveEvent.target));
		};
	})();
	$(document).on("mousemove", onMouseMoveDocument);
	(function inactiveUnexpectedDoms() {
		const styleRules = $("<style>").prop("type", "text/css");
		styleRules.append(`
@keyframes pp-spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}`);
		styleRules.append(`
._layout-thumbnail img + div {
  pointer-events: none;
}`);
		styleRules.appendTo("head");
	})();
	isInitialized$1 = true;
};
const DETAIL_BADGE_CSS = {
	height: "20px",
	"border-radius": "12px",
	color: "rgb(245, 245, 245)",
	background: "rgba(0, 0, 0, 0.32)",
	"font-size": "12px",
	"line-height": "1",
	"font-weight": "bold",
	padding: "3px 6px",
	display: "flex",
	"align-items": "center",
	gap: "4px"
};
var PreviewedIllust = class {
	/** 当前正在预览的作品的 ID */
	illustId = "";
	/** 当前正在预览的作品的详细信息 */
	illustDetails = null;
	/** 当前正在预览的作品 DOM 元素 */
	illustElement = $();
	/** 当前预览的作品是否加载完毕 */
	illustLoaded = false;
	/** 图片的链接 */
	regularUrls = [];
	/** 图片的原图链接 */
	originalUrls = [];
	/** 当前预览图片的页数 */
	currentPage = 1;
	/** 当前预览图片的总页数 */
	pageCount = 1;
	/** 预览图片或动图容器 DOM */
	previewWrapperElement = $();
	/** 预览容器顶部栏 DOM */
	previewWrapperHeader = $();
	/** 当前预览作品的元数据 */
	illustMeta = $();
	/** 当前预览的是第几张图片标记 DOM */
	pageCountElement = $();
	pageCountText = $();
	/** 下载原图按钮 DOM */
	downloadOriginalElement = $();
	/** 预览图片或动图加载状态 DOM */
	previewLoadingElement = $();
	/** 当前预览的图片或动图 DOM */
	previewImageElement = $();
	/** 预加载图片的列表 */
	#images = [];
	/** 下载按钮重置定时器 */
	#downloadResetTimeout = null;
	/** 保存的鼠标位置 */
	#prevMousePos = [0, 0];
	/** 当前预览图片的实际尺寸 */
	#currentIllustSize = [0, 0];
	/** 当前预览的动图播放器 */
	#currentUgoiraPlayer;
	constructor() {
		this.reset();
	}
	/** 初始化预览组件 */
	reset() {
		this.illustId = "";
		this.illustDetails = null;
		this.illustElement = $();
		this.illustLoaded = false;
		this.regularUrls = [];
		this.originalUrls = [];
		this.currentPage = 1;
		this.pageCount = 1;
		this.previewWrapperElement?.remove();
		this.previewWrapperElement = $(document.createElement("div")).attr({ id: "pp-wrapper" }).css({
			position: "fixed",
			"z-index": "999999",
			border: `${2}px solid rgb(0, 150, 250)`,
			"border-radius": `${8}px`,
			background: "rgba(31, 31, 31, 0.8)",
			"backdrop-filter": "blur(4px)",
			"text-align": "center",
			"pointer-events": "none"
		}).hide().appendTo($("body"));
		this.previewWrapperHeader = $(document.createElement("div")).css({
			position: "absolute",
			top: "0px",
			left: "0px",
			right: "0px",
			padding: "5px",
			display: "flex",
			gap: "5px",
			"align-items": "center"
		}).hide().appendTo(this.previewWrapperElement);
		this.illustMeta = $(document.createElement("div")).css({
			display: "flex",
			gap: "5px",
			"align-items": "center",
			"margin-right": "auto"
		}).appendTo(this.previewWrapperHeader);
		this.pageCountText = $(document.createElement("span")).text("1/1");
		this.pageCountElement = $(document.createElement("div")).css({
			height: "20px",
			"border-radius": "12px",
			color: "white",
			background: "rgba(0, 0, 0, 0.32)",
			"font-size": "12px",
			"line-height": "1",
			"font-weight": "bold",
			padding: "3px 6px",
			cursor: "pointer",
			display: "flex",
			"align-items": "center",
			gap: "4px"
		}).append(page_default).append(this.pageCountText).hide().appendTo(this.previewWrapperHeader);
		this.downloadOriginalElement = $(document.createElement("a")).css({
			height: "20px",
			"border-radius": "12px",
			color: "white",
			background: "rgba(0, 0, 0, 0.32)",
			"font-size": "12px",
			"line-height": "1",
			"font-weight": "bold",
			padding: "3px 6px",
			cursor: "pointer",
			display: "flex",
			"align-items": "center",
			gap: "4px"
		}).append(`${download_default}<span>原图</span>`).appendTo(this.previewWrapperHeader);
		this.previewLoadingElement = $(loading_default).css({
			padding: "12px",
			animation: "pp-spin 1s linear infinite"
		}).appendTo(this.previewWrapperElement);
		this.previewImageElement = $(new Image()).css({ "border-radius": `${8}px` }).hide().appendTo(this.previewWrapperElement);
		this.#images.forEach((image) => {
			if (image) image.src = "";
		});
		this.#images = [];
		this.#prevMousePos = [0, 0];
		this.#currentIllustSize = [0, 0];
		this.#currentUgoiraPlayer?.stop();
		if (this.#downloadResetTimeout !== null) {
			clearTimeout(this.#downloadResetTimeout);
			this.#downloadResetTimeout = null;
		}
		this.unbindPreviewImageEvents();
		this.unbindUgoiraPreviewEvents();
	}
	/** 初始化预览容器,默认显示第一张图片 */
	setImage({ illustId, illustElement, previewPage = 1, regularUrls, originalUrls }) {
		this.reset();
		this.initPreviewWrapper();
		this.illustId = illustId;
		this.illustElement = illustElement;
		this.regularUrls = regularUrls;
		this.originalUrls = originalUrls;
		this.currentPage = previewPage;
		this.pageCount = regularUrls.length;
		this.preloadImages();
		this.bindPreviewImageEvents();
		this.updatePreviewImage();
		this.showIllustrationDetails();
	}
	bindPreviewImageEvents() {
		this.previewImageElement.on("load", this.onImageLoad);
		this.previewImageElement.on("click", this.onPreviewImageMouseClick);
		this.downloadOriginalElement.on("click", this.onDownloadImage);
		$(document).on("keydown", this.onCtrlKeyDown);
		$(document).on("keyup", this.onCtrlKeyUp);
		$(document).on("wheel", this.onPreviewImageMouseWheel);
		$(document).on("keydown", this.onPreviewImageKeyDown);
		$(document).on("mousemove", this.onMouseMove);
		window.addEventListener("wheel", this.preventPageZoom, { passive: false });
	}
	unbindPreviewImageEvents() {
		this.previewImageElement.off();
		this.downloadOriginalElement.off();
		$(document).off("keydown", this.onCtrlKeyDown);
		$(document).off("keyup", this.onCtrlKeyUp);
		$(document).off("wheel", this.onPreviewImageMouseWheel);
		$(document).off("keydown", this.onPreviewImageKeyDown);
		$(document).off("mousemove", this.onMouseMove);
		window.removeEventListener("wheel", this.preventPageZoom);
	}
	/** 显示 pageIndex 指向的图片 */
	updatePreviewImage(page = this.currentPage) {
		const currentImageUrl = this.regularUrls[page - 1];
		this.previewImageElement.attr("src", currentImageUrl);
		this.pageCountText.text(`${page}/${this.pageCount}`);
	}
	onImageLoad = () => {
		this.illustLoaded = true;
		this.previewLoadingElement.hide();
		this.previewImageElement.show();
		this.previewWrapperHeader.show();
		if (this.pageCount > 1) this.pageCountElement.show();
		this.previewImageElement.css({
			width: "",
			height: ""
		});
		this.#currentIllustSize = [this.previewImageElement.width() ?? 0, this.previewImageElement.height() ?? 0];
		this.adjustPreviewWrapper({ baseOnMousePos: false });
	};
	nextPage() {
		if (this.currentPage < this.pageCount) this.currentPage += 1;
		else this.currentPage = 1;
		this.resetDownloadButton();
		this.updatePreviewImage();
		this.preloadImages();
	}
	prevPage() {
		if (this.currentPage > 1) this.currentPage -= 1;
		else this.currentPage = this.pageCount;
		this.resetDownloadButton();
		this.updatePreviewImage();
	}
	resetDownloadButton() {
		if (this.#downloadResetTimeout !== null) {
			clearTimeout(this.#downloadResetTimeout);
			this.#downloadResetTimeout = null;
		}
		this.downloadOriginalElement.find("span").text("原图");
		this.downloadOriginalElement.css({
			pointerEvents: "",
			backgroundImage: "",
			backgroundSize: "",
			backgroundRepeat: ""
		});
	}
	preloadImages(from = this.currentPage - 1, to = this.currentPage - 1 + 5) {
		if (!this.#images.length) this.#images = new Array(this.regularUrls.length);
		for (let i = from; i < to && i < this.regularUrls.length; i += 1) {
			const preloadImage = new Image();
			preloadImage.src = this.regularUrls[i];
			this.#images[i] = preloadImage;
		}
	}
	onPreviewImageMouseClick = () => {
		this.nextPage();
	};
	onPreviewImageMouseWheel = (mouseWheelEvent) => {
		if (mouseWheelEvent.ctrlKey || mouseWheelEvent.metaKey) {
			mouseWheelEvent.preventDefault();
			if (mouseWheelEvent.originalEvent.deltaY > 0) this.nextPage();
			else this.prevPage();
		}
	};
	onPreviewImageKeyDown = (keyDownEvent) => {
		if (keyDownEvent.ctrlKey || keyDownEvent.metaKey) {
			keyDownEvent.preventDefault();
			switch (keyDownEvent.key) {
				case "ArrowUp":
				case "ArrowRight":
					this.nextPage();
					break;
				case "ArrowDown":
				case "ArrowLeft":
					this.prevPage();
					break;
			}
		}
	};
	onDownloadImage = (onClickEvent) => {
		onClickEvent.preventDefault();
		const downloadPage = this.currentPage;
		const currentImageOriginalUrl = this.originalUrls[downloadPage - 1];
		const currentImageFilename = currentImageOriginalUrl.split("/").pop() || "illust.jpg";
		const textSpan = this.downloadOriginalElement.find("span");
		const originalText = textSpan.text();
		this.downloadOriginalElement.css({
			pointerEvents: "none",
			backgroundImage: "linear-gradient(to right, rgba(0,150,250,0.28), rgba(0,150,250,0.28))",
			backgroundSize: "0% 100%",
			backgroundRepeat: "no-repeat"
		});
		textSpan.text("下载中");
		downloadIllust({
			url: currentImageOriginalUrl,
			filename: currentImageFilename,
			options: {
				onprogress: (ev) => {
					if (this.currentPage !== downloadPage) return;
					try {
						const loaded = ev.loaded ?? 0;
						const total = ev.total ?? 0;
						let percent = 0;
						if (total && total > 0) percent = Math.min(100, Math.round(loaded / total * 100));
						this.downloadOriginalElement.css({ backgroundSize: `${percent}% 100%` });
					} catch (e) {
						console.error(`An error occurred in download progress callback: ${e}`);
					}
				},
				onload: () => {
					if (this.currentPage !== downloadPage) return;
					textSpan.text("已下载");
					this.downloadOriginalElement.css({
						backgroundImage: "",
						backgroundSize: "",
						pointerEvents: ""
					});
					this.#downloadResetTimeout = setTimeout(() => {
						textSpan.text(originalText);
						this.#downloadResetTimeout = null;
					}, 3e3);
				}
			}
		});
	};
	setUgoira({ illustId, illustElement, src, mime_type, frames }) {
		this.reset();
		this.initPreviewWrapper();
		this.illustId = illustId;
		this.illustElement = illustElement;
		illustElement.siblings("svg").css({ "pointer-events": "none" });
		this.#currentUgoiraPlayer = this.createUgoiraPlayer({
			source: src,
			metadata: {
				mime_type,
				frames
			}
		});
		this.bindUgoiraPreviewEvents();
		this.showIllustrationDetails();
	}
	createUgoiraPlayer(options) {
		const canvas = document.createElement("canvas");
		const p = new ZipImagePlayer({
			canvas,
			chunkSize: 3e5,
			loop: true,
			autoStart: true,
			debug: false,
			...options
		});
		p.canvas = canvas;
		return p;
	}
	bindUgoiraPreviewEvents() {
		$(this.#currentUgoiraPlayer).on("frameLoaded", this.onUgoiraFrameLoaded);
		$(document).on("mousemove", this.onMouseMove);
		$(document).on("keydown", this.onCtrlKeyDown);
		$(document).on("keyup", this.onCtrlKeyUp);
	}
	unbindUgoiraPreviewEvents() {
		$(this.#currentUgoiraPlayer).off();
		$(document).off("mousemove", this.onMouseMove);
		$(document).off("keydown", this.onCtrlKeyDown);
		$(document).off("keyup", this.onCtrlKeyUp);
	}
	onUgoiraFrameLoaded = (ev, frame) => {
		if (frame !== 0) return;
		this.illustLoaded = true;
		this.previewLoadingElement.hide();
		const canvas = $(this.#currentUgoiraPlayer.canvas);
		this.previewImageElement.after(canvas);
		this.previewImageElement.remove();
		this.previewImageElement = canvas;
		const ugoiraOriginWidth = ev.currentTarget._frameImages[0].width;
		const ugoiraOriginHeight = ev.currentTarget._frameImages[0].height;
		this.#currentIllustSize = [ugoiraOriginWidth, ugoiraOriginHeight];
		this.previewImageElement.attr({
			width: ugoiraOriginWidth,
			height: ugoiraOriginHeight
		});
		this.adjustPreviewWrapper({ baseOnMousePos: false });
	};
	async showIllustrationDetails() {
		const illustrationDetails = await getIllustrationDetailsWithCache(this.illustId);
		if (illustrationDetails && illustrationDetails.id === this.illustId) {
			this.illustMeta.empty();
			const { aiType, bookmarkId, bookmarkUserTotal, tags } = illustrationDetails;
			const isR18 = checkIsR18(tags);
			const isAi = checkIsAiGenerated(aiType);
			const isAiAssisted = checkIsAiAssisted(tags);
			const illustrationDetailsElements = [];
			if (isR18) illustrationDetailsElements.push($(document.createElement("div")).css({
				...DETAIL_BADGE_CSS,
				background: "rgb(255, 64, 96)"
			}).text("R-18"));
			if (isAi) illustrationDetailsElements.push($(document.createElement("div")).css({
				...DETAIL_BADGE_CSS,
				background: "rgb(29, 78, 216)"
			}).text("AI 生成"));
			else if (isAiAssisted) illustrationDetailsElements.push($(document.createElement("div")).css({
				...DETAIL_BADGE_CSS,
				background: "rgb(109, 40, 217)"
			}).text("AI 辅助"));
			illustrationDetailsElements.push($(document.createElement("div")).css({
				...DETAIL_BADGE_CSS,
				background: bookmarkUserTotal > 5e4 ? "rgb(159, 18, 57)" : bookmarkUserTotal > 1e4 ? "rgb(220, 38, 38)" : bookmarkUserTotal > 5e3 ? "rgb(29, 78, 216)" : bookmarkUserTotal > 1e3 ? "rgb(21, 128, 61)" : "rgb(71, 85, 105)"
			}).text(`${bookmarkId ? "❤️" : "❤"} ${bookmarkUserTotal}`));
			this.illustMeta.append(illustrationDetailsElements);
		}
	}
	/** 初始化显示预览容器 */
	initPreviewWrapper() {
		this.previewWrapperElement.show();
		this.previewLoadingElement.show();
		this.adjustPreviewWrapper({ baseOnMousePos: true });
	}
	/** 阻止页面缩放事件 */
	preventPageZoom = (mouseWheelEvent) => {
		if (mouseWheelEvent.ctrlKey || mouseWheelEvent.metaKey) mouseWheelEvent.preventDefault();
	};
	/**
	* 按下 Ctrl 或 Meta 键时,预览容器接收鼠标事件
	* @param keyDownEvent
	*/
	onCtrlKeyDown = (keyDownEvent) => {
		if (keyDownEvent.key === "Control" || keyDownEvent.key === "Meta") this.previewWrapperElement.css({ "pointer-events": "auto" });
	};
	/**
	* 松开 Ctrl 或 Meta 键时,鼠标事件穿透预览容器,避免鼠标快速移动时预览窗口闪烁
	* @param keyUpEvent
	*/
	onCtrlKeyUp = (keyUpEvent) => {
		if (keyUpEvent.key === "Control" || keyUpEvent.key === "Meta") this.previewWrapperElement.css({ "pointer-events": "none" });
	};
	/**
	* 根据鼠标移动调整预览容器位置与显隐
	* @param mouseMoveEvent
	*/
	onMouseMove = (mouseMoveEvent) => {
		if (mouseMoveEvent.ctrlKey || mouseMoveEvent.metaKey) return;
		if ($(mouseMoveEvent.target).is(this.illustElement)) this.adjustPreviewWrapper({ baseOnMousePos: true });
		else this.reset();
	};
	/**
	* 调整预览容器的位置与大小
	* @param `baseOnMousePos` 是否根据当前鼠标所在位置调整
	* @param `illustSize` 作品的实际大小
	*/
	adjustPreviewWrapper({ baseOnMousePos = true } = {}) {
		const [mousePosX, mousePosY] = baseOnMousePos ? mouseMonitor.mouseAbsPos : this.#prevMousePos;
		this.#prevMousePos = [mousePosX, mousePosY];
		const [illustWidth, illustHeight] = this.#currentIllustSize;
		const screenWidth = document.documentElement.clientWidth;
		const screenHeight = document.documentElement.clientHeight;
		const DIST = 20;
		if (!illustWidth || !illustHeight) {
			const defaultPos = {
				left: `${mousePosX + DIST}px`,
				top: `${mousePosY}px`
			};
			this.previewWrapperElement.css(defaultPos);
			this.previewImageElement.css({
				width: "",
				height: ""
			});
			return;
		}
		const candidates = [
			{
				side: "left",
				availW: Math.max(0, mousePosX - DIST),
				availH: screenHeight
			},
			{
				side: "right",
				availW: Math.max(0, screenWidth - mousePosX - DIST),
				availH: screenHeight
			},
			{
				side: "top",
				availW: screenWidth,
				availH: Math.max(0, mousePosY - DIST)
			},
			{
				side: "bottom",
				availW: screenWidth,
				availH: Math.max(0, screenHeight - mousePosY - DIST)
			}
		];
		let best = null;
		for (const c of candidates) {
			let scale = 1;
			if (this.illustLoaded) {
				const sx = c.availW / illustWidth;
				const sy = c.availH / illustHeight;
				scale = Number(Math.min(sx, sy).toFixed(3));
			}
			const fitW = Math.max(0, Math.floor(illustWidth * scale));
			const fitH = Math.max(0, Math.floor(illustHeight * scale));
			const area = fitW * fitH;
			if (!best || area > best.area) best = {
				side: c.side,
				fitW,
				fitH,
				area
			};
		}
		const previewImageFitWidth = best?.fitW ?? 0;
		const previewImageFitHeight = best?.fitH ?? 0;
		const clamp = (v, lo, hi) => Math.max(lo, Math.min(v, hi));
		const side = best?.side ?? "bottom";
		const isHorizontal = side === "left" || side === "right";
		const anchorX = isHorizontal ? side === "right" ? mousePosX + DIST : mousePosX - DIST - previewImageFitWidth : Math.floor(mousePosX - previewImageFitWidth / 2);
		const anchorY = isHorizontal ? Math.floor(mousePosY - previewImageFitHeight / 2) : side === "bottom" ? mousePosY + DIST : mousePosY - DIST - previewImageFitHeight;
		const left = clamp(anchorX, 0, Math.max(0, screenWidth - previewImageFitWidth));
		const top = clamp(anchorY, 0, Math.max(0, screenHeight - previewImageFitHeight));
		this.previewWrapperElement.css({
			left: `${left}px`,
			top: `${top}px`,
			right: "",
			bottom: ""
		});
		this.previewImageElement.css({
			width: `${previewImageFitWidth}px`,
			height: `${previewImageFitHeight}px`
		});
	}
};

//#endregion
//#region src/i18n/index.ts
const Texts = {
	install_title: "欢迎使用 Pixiv Previewer (LolipopJ Edition) v",
	upgrade_body: `<div>
  <p style="line-height: 1.6;">
    本脚本基于
    <a
      style="color: skyblue"
      href="http://greasyfork.icu/zh-CN/scripts/30766-pixiv-previewer"
      target="_blank"
      >Pixiv Previewer</a
    >
    二次开发,旨在满足开发者自己需要的能力。如果您有不错的想法或建议,请前往原脚本的
    <a
      style="color: skyblue"
      href="http://greasyfork.icu/zh-CN/scripts/30766-pixiv-previewer/feedback"
      target="_blank"
      >Greasy Fork 反馈页面</a
    >或开启一个新的
    <a
      style="color: skyblue"
      href="https://github.com/Ocrosoft/PixivPreviewer/issues"
      target="_blank"
      >Github 议题</a
    >!
  </p>
</div>
`,
	setting_language: "语言",
	setting_preview: "预览",
	setting_animePreview: "动图预览",
	setting_sort: "搜索页自动排序",
	setting_anime: "动图下载(动图预览及详情页生效)",
	setting_origin: "预览时优先显示原图(慢)",
	setting_previewDelay: "延迟显示预览图(毫秒)",
	setting_previewByKey: "使用按键控制预览图展示(Ctrl)",
	setting_previewByKeyHelp: "开启后鼠标移动到图片上不再展示预览图,按下Ctrl键才展示,同时“延迟显示预览”设置项不生效。",
	setting_maxPage: "每次排序时统计的最大页数",
	setting_hideWork: "隐藏收藏数少于设定值的作品",
	setting_sortOrderByBookmark: "按照收藏数排序作品",
	setting_hideAiWork: "排序时隐藏 AI 生成作品",
	setting_hideAiAssistedWork: "排序时隐藏 AI 辅助作品",
	setting_hideFav: "排序时隐藏已收藏的作品",
	setting_hideFollowed: "排序时隐藏已关注画师作品",
	setting_hideByTag: "排序时隐藏指定标签的作品",
	setting_hideByTagPlaceholder: "输入标签名,多个标签用','分隔",
	setting_clearFollowingCache: "清除缓存",
	setting_clearFollowingCacheHelp: "关注画师信息会在本地保存一天,如果希望立即更新,请点击清除缓存",
	setting_followingCacheCleared: "已清除缓存,请刷新页面。",
	setting_blank: "使用新标签页打开作品详情页",
	setting_turnPage: "使用键盘←→进行翻页(排序后的搜索页)",
	setting_save: "保存设置",
	setting_reset: "重置脚本",
	setting_resetHint: "这会删除所有设置,相当于重新安装脚本,确定要重置吗?",
	setting_novelSort: "小说排序",
	setting_novelMaxPage: "小说排序时统计的最大页数",
	setting_novelHideWork: "隐藏收藏数少于设定值的作品",
	setting_novelHideFav: "排序时隐藏已收藏的作品",
	sort_noWork: "没有可以显示的作品(隐藏了 %1 个作品)",
	sort_getWorks: "正在获取第 %1/%2 页作品",
	sort_getBookmarkCount: "获取收藏数:%1/%2",
	sort_getPublicFollowing: "获取公开关注画师",
	sort_getPrivateFollowing: "获取私有关注画师",
	sort_filtering: "过滤%1收藏量低于%2的作品",
	sort_filteringHideFavorite: "已收藏和",
	sort_fullSizeThumb: "全尺寸缩略图(搜索页、用户页)",
	label_sort: "排序",
	label_sorting: "排序中",
	label_nextPage: "下一页",
	label_hideFav: "过滤收藏"
};

//#endregion
//#region src/icons/heart.svg
var heart_default = "<svg viewBox=\"0 0 32 32\" width=\"32\" height=\"32\">\n  <path d=\"\nM21,5.5 C24.8659932,5.5 28,8.63400675 28,12.5 C28,18.2694439 24.2975093,23.1517313 17.2206059,27.1100183\nC16.4622493,27.5342993 15.5379984,27.5343235 14.779626,27.110148 C7.70250208,23.1517462 4,18.2694529 4,12.5\nC4,8.63400691 7.13400681,5.5 11,5.5 C12.829814,5.5 14.6210123,6.4144028 16,7.8282366\nC17.3789877,6.4144028 19.170186,5.5 21,5.5 Z\"></path>\n  <path d=\"M16,11.3317089 C15.0857201,9.28334665 13.0491506,7.5 11,7.5\nC8.23857625,7.5 6,9.73857647 6,12.5 C6,17.4386065 9.2519779,21.7268174 15.7559337,25.3646328\nC15.9076021,25.4494645 16.092439,25.4494644 16.2441073,25.3646326 C22.7480325,21.7268037 26,17.4385986 26,12.5\nC26,9.73857625 23.7614237,7.5 21,7.5 C18.9508494,7.5 16.9142799,9.28334665 16,11.3317089 Z\" style=\"fill: #fafafa;\">\n  </path>\n</svg>";

//#endregion
//#region src/icons/heart-filled.svg
var heart_filled_default = "<svg viewBox=\"0 0 32 32\" width=\"32\" height=\"32\">\n  <path d=\"\nM21,5.5 C24.8659932,5.5 28,8.63400675 28,12.5 C28,18.2694439 24.2975093,23.1517313 17.2206059,27.1100183\nC16.4622493,27.5342993 15.5379984,27.5343235 14.779626,27.110148 C7.70250208,23.1517462 4,18.2694529 4,12.5\nC4,8.63400691 7.13400681,5.5 11,5.5 C12.829814,5.5 14.6210123,6.4144028 16,7.8282366\nC17.3789877,6.4144028 19.170186,5.5 21,5.5 Z\"></path>\n  <path d=\"M16,11.3317089 C15.0857201,9.28334665 13.0491506,7.5 11,7.5\nC8.23857625,7.5 6,9.73857647 6,12.5 C6,17.4386065 9.2519779,21.7268174 15.7559337,25.3646328\nC15.9076021,25.4494645 16.092439,25.4494644 16.2441073,25.3646326 C22.7480325,21.7268037 26,17.4385986 26,12.5\nC26,9.73857625 23.7614237,7.5 21,7.5 C18.9508494,7.5 16.9142799,9.28334665 16,11.3317089 Z\" style=\"fill: #dc2626;\">\n  </path>\n</svg>";

//#endregion
//#region src/icons/play.svg
var play_default = "<svg viewBox=\"0 0 24 24\"\n  style=\"width: 48px; height: 48px; stroke: none; line-height: 0; font-size: 0px; vertical-align: middle;\">\n  <circle cx=\"12\" cy=\"12\" r=\"10\" style=\"fill: rgba(0, 0, 0, 0.32);\"></circle>\n  <path d=\"M9,8.74841664 L9,15.2515834 C9,15.8038681 9.44771525,16.2515834 10,16.2515834\nC10.1782928,16.2515834 10.3533435,16.2039156 10.5070201,16.1135176 L16.0347118,12.8619342\nC16.510745,12.5819147 16.6696454,11.969013 16.3896259,11.4929799\nC16.3034179,11.3464262 16.1812655,11.2242738 16.0347118,11.1380658 L10.5070201,7.88648243\nC10.030987,7.60646294 9.41808527,7.76536339 9.13806578,8.24139652\nC9.04766776,8.39507316 9,8.57012386 9,8.74841664 Z\" style=\"fill: rgb(245, 245, 245);\"></path>\n</svg>";

//#endregion
//#region src/utils/promise.ts
const execLimitConcurrentPromises = async (promises, limit = 48) => {
	const results = [];
	let index = 0;
	const executeNext = async () => {
		if (index >= promises.length) return Promise.resolve();
		const currentIndex = index++;
		results[currentIndex] = await promises[currentIndex]();
		return await executeNext();
	};
	const initialPromises = Array.from({ length: Math.min(limit, promises.length) }, () => executeNext());
	await Promise.all(initialPromises);
	return results;
};

//#endregion
//#region src/features/sort.ts
const BOOKMARK_USER_PAGE_ILLUSTRATION_LIST_SELECTOR = "ul.sc-e83d358-1.gIHHFW";
const USER_TYPE_ARTWORKS_PER_PAGE = 48;
let isInitialized = false;
const loadIllustSort = (options) => {
	if (isInitialized) return;
	const { pageCount: optionPageCount, favFilter: optionFavFilter, orderType = IllustSortOrder.BY_BOOKMARK_COUNT, hideFavorite = false, hideByTag = false, hideByTagList: hideByTagListString, aiFilter = false, aiAssistedFilter = false } = options;
	let pageCount = Number(optionPageCount), favFilter = Number(optionFavFilter);
	if (pageCount <= 0) pageCount = g_defaultSettings.pageCount;
	if (favFilter < 0) favFilter = g_defaultSettings.favFilter;
	const hideByTagList = hideByTagListString.split(",").map((tag) => tag.trim().toLowerCase()).filter((tag) => !!tag);
	if (aiAssistedFilter) hideByTagList.push(...AI_ASSISTED_TAGS);
	class IllustSorter {
		type;
		illustrations;
		sorting = false;
		nextSortPage;
		listElement = $();
		progressElement = $();
		progressText = $();
		sortButtonElement = $(`#${SORT_BUTTON_ID}`);
		reset({ type }) {
			try {
				this.type = type;
				this.illustrations = [];
				this.sorting = false;
				this.nextSortPage = void 0;
				this.listElement = getIllustrationsListDom(type);
				this.progressElement?.remove();
				this.progressElement = $(document.createElement("div")).attr({ id: "pp-sort-progress" }).css({
					width: "100%",
					display: "flex",
					"flex-direction": "column",
					"align-items": "center",
					"justify-content": "center",
					gap: "6px"
				}).append($(new Image(96, 96)).attr({
					id: "sort-progress__loading",
					src: g_loadingImage
				}).css({ "border-radius": "50%" })).prependTo(this.listElement).hide();
				this.progressText = $(document.createElement("div")).attr({ id: "pp-sort-progress__text" }).css({
					"text-align": "center",
					"font-size": "16px",
					"font-weight": "bold",
					color: "initial"
				}).appendTo(this.progressElement);
				this.sortButtonElement.text(Texts.label_sort);
			} catch (error) {
				iLog.e(`An error occurred while resetting sorter:`, error);
				throw new Error(String(error), { cause: error });
			}
		}
		async sort({ type, api, searchParams }) {
			this.sorting = true;
			iLog.i("Start to sort illustrations.");
			this.sortButtonElement.text(Texts.label_sorting);
			try {
				let illustrations = [];
				const startPage = Number(searchParams.get("p") ?? 1);
				this.nextSortPage = startPage + pageCount;
				for (let page = startPage; page < startPage + pageCount; page += 1) {
					searchParams.set("p", String(page));
					if ([
						IllustSortType.USER_ARTWORK,
						IllustSortType.USER_ILLUST,
						IllustSortType.USER_MANGA
					].includes(type)) {
						searchParams.set("is_first_page", page > 1 ? "0" : "1");
						searchParams.delete("ids[]");
						const userIllustrations = await getUserIllustrationsWithCache(searchParams.get("user_id"), { onRequesting: () => this.setProgress(`Getting illustrations of current user...`) });
						const fromIndex = (page - 1) * USER_TYPE_ARTWORKS_PER_PAGE;
						const toIndex = page * USER_TYPE_ARTWORKS_PER_PAGE;
						switch (type) {
							case IllustSortType.USER_ARTWORK:
								userIllustrations.artworks.slice(fromIndex, toIndex).forEach((id) => searchParams.append("ids[]", id));
								break;
							case IllustSortType.USER_ILLUST:
								userIllustrations.illusts.slice(fromIndex, toIndex).forEach((id) => searchParams.append("ids[]", id));
								break;
							case IllustSortType.USER_MANGA:
								userIllustrations.manga.slice(fromIndex, toIndex).forEach((id) => searchParams.append("ids[]", id));
								break;
						}
					} else if ([IllustSortType.USER_BOOKMARK].includes(type)) searchParams.set("offset", String((page - 1) * USER_TYPE_ARTWORKS_PER_PAGE));
					this.setProgress(`Getting illustration list of page ${page} ...`);
					const requestUrl = `${api}?${searchParams}`;
					const extractedIllustrations = getIllustrationsFromResponse(type, (await requestWithRetry({
						url: requestUrl,
						onRetry: (response, retryTimes) => {
							iLog.w(`Get illustration list through \`${requestUrl}\` failed:`, response, `${retryTimes} times retrying...`);
							this.setProgress(`Retry to get illustration list of page ${page} (${retryTimes} times)...`);
						}
					})).response);
					illustrations = illustrations.concat(extractedIllustrations);
				}
				const getDetailedIllustrationPromises = [];
				for (let i = 0; i < illustrations.length; i += 1) {
					const illustration = illustrations[i];
					const illustrationId = illustration.id;
					const illustrationAuthorId = illustration.userId;
					if (String(illustrationAuthorId) === "0") continue;
					getDetailedIllustrationPromises.push(async () => {
						this.setProgress(`Getting details of ${i + 1}/${illustrations.length} illustration...`);
						const illustrationDetails = await getIllustrationDetailsWithCache(illustrationId, true);
						return {
							...illustration,
							bookmarkUserTotal: illustrationDetails?.bookmarkUserTotal ?? -1
						};
					});
				}
				const detailedIllustrations = await execLimitConcurrentPromises(getDetailedIllustrationPromises);
				iLog.d("Queried detailed illustrations:", detailedIllustrations);
				this.setProgress("Filtering illustrations...");
				const filteredIllustrations = detailedIllustrations.filter((illustration) => {
					if (hideFavorite && illustration.bookmarkData) return false;
					if (aiFilter && illustration.aiType === AiType.AI) return false;
					if ((hideByTag || aiAssistedFilter) && hideByTagList.length) {
						for (const tag of illustration.tags) if (hideByTagList.includes(tag.toLowerCase())) return false;
					}
					return illustration.bookmarkUserTotal >= favFilter;
				});
				this.setProgress("Sorting filtered illustrations...");
				const sortedIllustrations = orderType === IllustSortOrder.BY_BOOKMARK_COUNT ? filteredIllustrations.sort((a, b) => b.bookmarkUserTotal - a.bookmarkUserTotal) : filteredIllustrations;
				iLog.d("Filtered and sorted illustrations:", sortedIllustrations);
				iLog.i("Sort illustrations successfully.");
				this.illustrations = sortedIllustrations;
				this.showIllustrations();
			} catch (error) {
				iLog.e("Sort illustrations failed:", error);
			}
			this.hideProgress();
			this.sorting = false;
			this.sortButtonElement.text(Texts.label_sort);
		}
		setProgress(text) {
			this.progressText.text(text);
			this.progressElement.show();
		}
		hideProgress() {
			this.progressText.text("");
			this.progressElement.hide();
		}
		showIllustrations() {
			const fragment = document.createDocumentFragment();
			for (const { aiType, alt, bookmarkData, bookmarkUserTotal, id, illustType, pageCount, profileImageUrl, tags, title, url, userId, userName } of this.illustrations) {
				const isR18 = checkIsR18(tags);
				const isUgoira = checkIsUgoira(illustType);
				const isAi = checkIsAiGenerated(aiType);
				const isAiAssisted = checkIsAiAssisted(tags);
				const listItem = document.createElement("li");
				listItem.className = "col-span-2";
				const container = document.createElement("div");
				container.style = "width: 184px;";
				const illustrationAnchor = document.createElement("a");
				illustrationAnchor.setAttribute("data-gtm-value", id);
				illustrationAnchor.setAttribute("data-gtm-user-id", userId);
				illustrationAnchor.href = `/artworks/${id}`;
				illustrationAnchor.target = "_blank";
				illustrationAnchor.rel = "external";
				illustrationAnchor.style = "display: block; position: relative; width: 184px;";
				const illustrationImageWrapper = document.createElement("div");
				illustrationImageWrapper.style = "position: relative; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;";
				const illustrationImage = document.createElement("img");
				illustrationImage.src = url;
				illustrationImage.alt = alt;
				illustrationImage.style = "object-fit: cover; object-position: center center; width: 100%; height: 100%; border-radius: 4px; background-color: rgb(31, 31, 31);";
				const ugoriaSvg = document.createElement("div");
				ugoriaSvg.style = "position: absolute;";
				ugoriaSvg.innerHTML = play_default;
				const illustrationMeta = document.createElement("div");
				illustrationMeta.style = "position: absolute; top: 0px; left: 0px; right: 0px; display: flex; align-items: flex-start; padding: 4px 4px 0; pointer-events: none; font-size: 10px;";
				illustrationMeta.innerHTML = `
          ${isR18 ? "<div style=\"padding: 0px 4px; border-radius: 4px; color: rgb(245, 245, 245); background: rgb(255, 64, 96); font-weight: bold; line-height: 16px; user-select: none;\">R-18</div>" : ""}
          ${isAi ? "<div style=\"padding: 0px 4px; border-radius: 4px; color: rgb(245, 245, 245); background: rgb(29, 78, 216); font-weight: bold; line-height: 16px; user-select: none;\">AI 生成</div>" : isAiAssisted ? "<div style=\"padding: 0px 4px; border-radius: 4px; color: rgb(245, 245, 245); background: rgb(109, 40, 217); font-weight: bold; line-height: 16px; user-select: none;\">AI 辅助</div>" : ""}
          ${pageCount > 1 ? `
                <div style="margin-left: auto;">
                  <div style="display: flex; justify-content: center; align-items: center; height: 20px; min-width: 20px; color: rgb(245, 245, 245); font-weight: bold; padding: 0px 6px; background: rgba(0, 0, 0, 0.32); border-radius: 10px; line-height: 10px;">
                    ${page_default}
                    <span>${pageCount}</span>
                  </div>
                </div>` : ""}
        `;
				const illustrationToolbar = document.createElement("div");
				illustrationToolbar.style = "position: absolute; top: 154px; left: 0px; right: 0px; display: flex; align-items: center; padding: 0 4px 4px; pointer-events: none; font-size: 12px;";
				illustrationToolbar.innerHTML = `
          <div style="padding: 0px 4px; border-radius: 4px; color: rgb(245, 245, 245); background: ${bookmarkUserTotal > 5e4 ? "rgb(159, 18, 57)" : bookmarkUserTotal > 1e4 ? "rgb(220, 38, 38)" : bookmarkUserTotal > 5e3 ? "rgb(29, 78, 216)" : bookmarkUserTotal > 1e3 ? "rgb(21, 128, 61)" : "rgb(71, 85, 105)"}; font-weight: bold; line-height: 16px; user-select: none;">❤ ${bookmarkUserTotal}</div>
          <div style="margin-left: auto; display: none;">${bookmarkData ? heart_filled_default : heart_default}</div>
        `;
				const illustrationTitle = document.createElement("div");
				illustrationTitle.innerHTML = title;
				illustrationTitle.style = "margin-top: 4px; max-width: 100%; overflow: hidden; text-decoration: none; text-overflow: ellipsis; white-space: nowrap; line-height: 22px; font-size: 14px; font-weight: bold; color: rgb(245, 245, 245); transition: color 0.2s;";
				const illustrationAuthor = document.createElement("a");
				illustrationAuthor.setAttribute("data-gtm-value", userId);
				illustrationAuthor.href = `/users/${userId}`;
				illustrationAuthor.target = "_blank";
				illustrationAuthor.rel = "external";
				illustrationAuthor.style = "display: flex; align-items: center; margin-top: 4px;";
				illustrationAuthor.innerHTML = `
          <img src="${profileImageUrl}" alt="${userName}" style="object-fit: cover; object-position: center top; width: 24px; height: 24px; border-radius: 50%; margin-right: 4px;">
          <span style="min-width: 0px; line-height: 22px; font-size: 14px; color: rgb(214, 214, 214); text-decoration: none; text-overflow: ellipsis; white-space: nowrap; overflow: hidden;">${userName}</span>
        `;
				illustrationImageWrapper.appendChild(illustrationImage);
				if (isUgoira) illustrationImageWrapper.appendChild(ugoriaSvg);
				illustrationAnchor.appendChild(illustrationImageWrapper);
				illustrationAnchor.appendChild(illustrationMeta);
				illustrationAnchor.appendChild(illustrationToolbar);
				illustrationAnchor.appendChild(illustrationTitle);
				container.appendChild(illustrationAnchor);
				container.appendChild(illustrationAuthor);
				listItem.appendChild(container);
				fragment.appendChild(listItem);
			}
			if ([
				IllustSortType.BOOKMARK_NEW,
				IllustSortType.BOOKMARK_NEW_R18,
				IllustSortType.USER_ARTWORK,
				IllustSortType.USER_ILLUST,
				IllustSortType.USER_MANGA,
				IllustSortType.USER_BOOKMARK
			].includes(this.type)) this.listElement.css({ gap: "24px" });
			this.listElement.children().remove();
			this.listElement.append(fragment);
		}
	}
	const illustSorter = new IllustSorter();
	window.addEventListener(SORT_EVENT_NAME, () => {
		if (illustSorter.sorting) {
			iLog.w("Current is in sorting progress.");
			return;
		}
		const url = new URL(location.href);
		const { type, api, searchParams: defaultSearchParams } = getSortOptionsFromUrl(url);
		if (type === void 0) {
			iLog.w("Current page doesn't support sorting illustrations.");
			return;
		}
		const mergedSearchParams = new URLSearchParams(defaultSearchParams);
		url.searchParams.forEach((value, key) => {
			mergedSearchParams.set(key, value);
		});
		illustSorter.reset({ type });
		illustSorter.sort({
			type,
			api,
			searchParams: mergedSearchParams
		});
	});
	window.addEventListener(SORT_NEXT_PAGE_EVENT_NAME, () => {
		const { origin, pathname, searchParams } = new URL(location.href);
		let nextPage = Number(searchParams.get("p") ?? 1) + 1;
		if (illustSorter.listElement?.length && illustSorter.nextSortPage) {
			iLog.i("Illustrations in current page are sorted, jump to next available page...");
			nextPage = illustSorter.nextSortPage;
		}
		searchParams.set("p", String(nextPage));
		location.href = `${origin}${pathname}?${searchParams}`;
	});
	isInitialized = true;
};
/** 获取作品节点 li 的父节点 ul */
function getIllustrationsListDom(type) {
	let dom;
	if ([
		IllustSortType.TAG_ARTWORK,
		IllustSortType.TAG_ILLUST,
		IllustSortType.TAG_MANGA,
		IllustSortType.SEARCH_ILLUST,
		IllustSortType.SEARCH_MANGA
	].includes(type)) {
		dom = $("div[data-ga4-label=\"works_content\"]").children("div").last();
		if (!dom.length) dom = $("section").find("ul").last();
	} else if ([
		IllustSortType.BOOKMARK_NEW,
		IllustSortType.BOOKMARK_NEW_R18,
		IllustSortType.USER_BOOKMARK
	].includes(type)) {
		dom = $(BOOKMARK_USER_PAGE_ILLUSTRATION_LIST_SELECTOR);
		if (!dom.length) dom = $("section").find("ul").last();
	} else if ([
		IllustSortType.USER_ARTWORK,
		IllustSortType.USER_ILLUST,
		IllustSortType.USER_MANGA
	].includes(type)) {
		dom = $(BOOKMARK_USER_PAGE_ILLUSTRATION_LIST_SELECTOR);
		if (!dom.length) dom = $(".__top_side_menu_body").find("ul").last();
	}
	if (dom.length) return dom;
	else throw new Error(`Illustrations list DOM not found in current page: ${location.href}. Please create a new issue here: https://github.com/LolipopJ/PixivPreviewer/issues`);
}
/** 根据当前路由获取接口参数 */
function getSortOptionsFromUrl(url) {
	const { pathname, searchParams } = url;
	let type;
	let api;
	let defaultSearchParams;
	let match;
	if (match = pathname.match(/\/tags\/(.+)\/(artworks|illustrations|manga)$/)) {
		const tagName = match[1];
		switch (match[2]) {
			case "artworks":
				type = IllustSortType.TAG_ARTWORK;
				api = `/ajax/search/artworks/${tagName}`;
				defaultSearchParams = `word=${tagName}&order=date_d&mode=all&p=1&csw=0&s_mode=s_tag_full&type=all&lang=zh`;
				break;
			case "illustrations":
				type = IllustSortType.TAG_ILLUST;
				api = `/ajax/search/illustrations/${tagName}`;
				defaultSearchParams = `word=${tagName}&order=date_d&mode=all&p=1&csw=0&s_mode=s_tag_full&type=illust_and_ugoira&lang=zh`;
				break;
			case "manga":
				type = IllustSortType.TAG_MANGA;
				api = `/ajax/search/manga/${tagName}`;
				defaultSearchParams = `word=${tagName}&order=date_d&mode=all&p=1&csw=0&s_mode=s_tag_full&type=manga&lang=zh`;
				break;
		}
	} else if (match = pathname.match(/\/search/)) {
		const tagName = searchParams.get("q");
		switch (searchParams.get("type")) {
			case "illust_ugoira":
				type = IllustSortType.SEARCH_ILLUST;
				api = `/ajax/search/illustrations/${tagName}`;
				defaultSearchParams = `word=${tagName}&order=date_d&mode=all&p=1&csw=0&s_mode=s_tag_full&type=illust_and_ugoira&lang=zh`;
				break;
			case "manga":
				type = IllustSortType.SEARCH_MANGA;
				api = `/ajax/search/manga/${tagName}`;
				defaultSearchParams = `word=${tagName}&order=date_d&mode=all&p=1&csw=0&s_mode=s_tag_full&type=manga&lang=zh`;
				break;
		}
	} else if (match = pathname.match(/\/bookmark_new_illust(_r18)?\.php$/)) {
		const isR18 = !!match[1];
		api = "/ajax/follow_latest/illust";
		if (isR18) {
			type = IllustSortType.BOOKMARK_NEW;
			defaultSearchParams = "mode=r18&lang=zh";
		} else {
			type = IllustSortType.BOOKMARK_NEW_R18;
			defaultSearchParams = "mode=all&lang=zh";
		}
	} else if (match = pathname.match(/\/users\/(\d+)\/bookmarks\/artworks$/)) {
		const userId = match[1];
		type = IllustSortType.USER_BOOKMARK;
		api = `/ajax/user/${userId}/illusts/bookmarks`;
		defaultSearchParams = `tag=&offset=0&limit=${USER_TYPE_ARTWORKS_PER_PAGE}&rest=show&lang=zh`;
	} else if (match = pathname.match(/\/users\/(\d+)\/(artworks|illustrations|manga)$/)) {
		const userId = match[1];
		const filterType = match[2];
		api = `/ajax/user/${userId}/profile/illusts`;
		switch (filterType) {
			case "artworks":
				type = IllustSortType.USER_ARTWORK;
				defaultSearchParams = `work_category=illustManga&is_first_page=1&sensitiveFilterMode=userSetting&user_id=${userId}&lang=zh`;
				break;
			case "illustrations":
				type = IllustSortType.USER_ILLUST;
				defaultSearchParams = `work_category=illust&is_first_page=1&sensitiveFilterMode=userSetting&user_id=${userId}&lang=zh`;
				break;
			case "manga":
				type = IllustSortType.USER_MANGA;
				defaultSearchParams = `work_category=manga&is_first_page=1&sensitiveFilterMode=userSetting&user_id=${userId}&lang=zh`;
				break;
		}
	}
	return {
		type,
		api,
		searchParams: new URLSearchParams(defaultSearchParams)
	};
}
/** 从响应值里提取作品数据列表 */
function getIllustrationsFromResponse(type, response) {
	if (type === IllustSortType.TAG_ARTWORK) return response.body.illustManga.data ?? [];
	else if (type === IllustSortType.TAG_ILLUST || type === IllustSortType.SEARCH_ILLUST) return response.body.illust.data ?? [];
	else if (type === IllustSortType.TAG_MANGA || type === IllustSortType.SEARCH_MANGA) return response.body.manga.data ?? [];
	else if ([IllustSortType.BOOKMARK_NEW, IllustSortType.BOOKMARK_NEW_R18].includes(type)) return response.body.thumbnails.illust ?? [];
	else if ([
		IllustSortType.USER_ARTWORK,
		IllustSortType.USER_ILLUST,
		IllustSortType.USER_MANGA,
		IllustSortType.USER_BOOKMARK
	].includes(type)) return Object.values(response.body.works);
	return [];
}

//#endregion
//#region src/utils/setting.ts
const SETTINGS_KEY = "PIXIV_PREVIEWER_L_SETTINGS";
const toggleSettingBooleanValue = (key) => {
	const settings = getSettings();
	const newValue = !Boolean(settings[key] ?? g_defaultSettings[key]);
	GM_setValue(SETTINGS_KEY, {
		...settings,
		[key]: newValue
	});
};
const setSettingStringValue = (key, label, { parseValue = (v) => v, onSet }) => {
	const settings = getSettings();
	const currentValue = settings[key] ?? g_defaultSettings[key];
	const newValue = prompt(label, String(currentValue));
	if (newValue !== null) {
		const savedValue = parseValue(newValue);
		GM_setValue(SETTINGS_KEY, {
			...settings,
			[key]: savedValue
		});
		onSet?.(savedValue);
	}
};
const setSettingValue = (key, value) => {
	const settings = getSettings();
	const newValue = value ?? g_defaultSettings[key];
	GM_setValue(SETTINGS_KEY, {
		...settings,
		[key]: newValue
	});
};
const getSettings = () => {
	return GM_getValue(SETTINGS_KEY) ?? g_defaultSettings;
};
const resetSettings = () => {
	GM_setValue(SETTINGS_KEY, g_defaultSettings);
};

//#endregion
//#region src/index.ts
let g_csrfToken = "";
let g_pageType;
let g_settings;
const Pages = {
	[PageType.Search]: {
		PageTypeString: "SearchPage",
		CheckUrl: function(url) {
			return /^https?:\/\/www.pixiv.net(\/en)?\/tags\/.+\/(artworks|illustrations|manga)/.test(url) || /^https?:\/\/www.pixiv.net(\/en)?\/search/.test(url);
		},
		GetToolBar: getToolbar
	},
	[PageType.BookMarkNew]: {
		PageTypeString: "BookMarkNewPage",
		CheckUrl: function(url) {
			return /^https:\/\/www.pixiv.net(\/en)?\/bookmark_new_illust(_r18)?.php.*/.test(url);
		},
		GetToolBar: getToolbar
	},
	[PageType.Discovery]: {
		PageTypeString: "DiscoveryPage",
		CheckUrl: function(url) {
			return /^https?:\/\/www.pixiv.net(\/en)?\/discovery.*/.test(url);
		},
		GetToolBar: getToolbar
	},
	[PageType.Member]: {
		PageTypeString: "MemberPage/MemberIllustPage/MemberBookMark",
		CheckUrl: function(url) {
			return /^https?:\/\/www.pixiv.net(\/en)?\/users\/\d+/.test(url);
		},
		GetToolBar: getToolbar
	},
	[PageType.Home]: {
		PageTypeString: "HomePage",
		CheckUrl: function(url) {
			return /https?:\/\/www.pixiv.net(\/en)?\/?$/.test(url) || /https?:\/\/www.pixiv.net(\/en)?\/illustration\/?$/.test(url) || /https?:\/\/www.pixiv.net(\/en)?\/manga\/?$/.test(url) || /https?:\/\/www.pixiv.net(\/en)?\/cate_r18\.php$/.test(url);
		},
		GetToolBar: getToolbar
	},
	[PageType.Ranking]: {
		PageTypeString: "RankingPage",
		CheckUrl: function(url) {
			return /^https?:\/\/www.pixiv.net(\/en)?\/ranking.php.*/.test(url);
		},
		GetToolBar: getToolbar
	},
	[PageType.NewIllust]: {
		PageTypeString: "NewIllustPage",
		CheckUrl: function(url) {
			return /^https?:\/\/www.pixiv.net(\/en)?\/new_illust.php.*/.test(url);
		},
		GetToolBar: getToolbar
	},
	[PageType.R18]: {
		PageTypeString: "R18Page",
		CheckUrl: function(url) {
			return /^https?:\/\/www.pixiv.net(\/en)?\/cate_r18.php.*/.test(url);
		},
		GetToolBar: getToolbar
	},
	[PageType.Stacc]: {
		PageTypeString: "StaccPage",
		CheckUrl: function(url) {
			return /^https:\/\/www.pixiv.net(\/en)?\/stacc.*/.test(url);
		},
		GetToolBar: function() {
			return getToolbarOld();
		}
	},
	[PageType.Artwork]: {
		PageTypeString: "ArtworkPage",
		CheckUrl: function(url) {
			return /^https:\/\/www.pixiv.net(\/en)?\/artworks\/.*/.test(url);
		},
		GetToolBar: getToolbar
	},
	[PageType.NovelSearch]: {
		PageTypeString: "NovelSearchPage",
		CheckUrl: function(url) {
			return /^https:\/\/www.pixiv.net(\/en)?\/tags\/.*\/novels/.test(url);
		},
		GetToolBar: getToolbar
	},
	[PageType.SearchTop]: {
		PageTypeString: "SearchTopPage",
		CheckUrl: function(url) {
			return /^https?:\/\/www.pixiv.net(\/en)?\/tags\/[^/*]/.test(url);
		},
		GetToolBar: getToolbar
	}
};
function getToolbar() {
	const toolbar = $(`#${TOOLBAR_ID}`);
	if (toolbar.length > 0) return toolbar.get(0);
	$("body").append(`<div id="${TOOLBAR_ID}" style="position: fixed; right: 28px; bottom: 96px;"></div>`);
	return $(`#${TOOLBAR_ID}`).get(0);
}
function getToolbarOld() {
	return $("._toolmenu").get(0);
}
function showSearchLinksForDeletedArtworks() {
	const searchEngines = [
		{
			name: "Google",
			url: "https://www.google.com/search?q="
		},
		{
			name: "Bing",
			url: "https://www.bing.com/search?q="
		},
		{
			name: "Baidu",
			url: "https://www.baidu.com/s?wd="
		}
	];
	document.querySelectorAll("span[to]").forEach((span) => {
		const artworkPath = span.getAttribute("to");
		if (span.textContent.trim() === "-----" && artworkPath.startsWith("/artworks/")) {
			const keyword = `pixiv "${artworkPath.slice(10)}"`;
			const container = document.createElement("span");
			container.className = span.className;
			searchEngines.forEach((engine, i) => {
				const link = document.createElement("a");
				link.href = engine.url + encodeURIComponent(keyword);
				link.textContent = engine.name;
				link.target = "_blank";
				container.appendChild(link);
				if (i < searchEngines.length - 1) container.appendChild(document.createTextNode(" | "));
			});
			span.parentNode.replaceChild(container, span);
		}
	});
}
let menuIds = [];
const registerSettingsMenu = () => {
	const settings = getSettings();
	for (const menuId of menuIds) GM_unregisterMenuCommand(menuId);
	menuIds = [];
	menuIds.push(GM_registerMenuCommand(`🖼️ 插画作品预览 ${settings.enablePreview ? "✅" : "❌"}`, () => {
		toggleSettingBooleanValue("enablePreview");
		registerSettingsMenu();
	}), GM_registerMenuCommand(`🎦 动图作品预览 ${settings.enableAnimePreview ? "✅" : "❌"}`, () => {
		toggleSettingBooleanValue("enableAnimePreview");
		registerSettingsMenu();
	}), GM_registerMenuCommand(`🕗 延迟 ${settings.previewDelay} 毫秒显示预览图`, () => {
		setSettingStringValue("previewDelay", "延迟显示预览图时间(毫秒)", {
			parseValue: (newValue) => Number(newValue) || g_defaultSettings.previewDelay,
			onSet: () => registerSettingsMenu()
		});
	}), GM_registerMenuCommand(`📚️ 每次排序 ${settings.pageCount} 页`, () => {
		setSettingStringValue("pageCount", "每次排序的页数", {
			parseValue: (newValue) => Number(newValue) || g_defaultSettings.pageCount,
			onSet: () => registerSettingsMenu()
		});
	}), GM_registerMenuCommand(`👨‍👩‍👧 排序隐藏收藏数少于 ${settings.favFilter} 的作品`, () => {
		setSettingStringValue("favFilter", "排序隐藏少于设定收藏数的作品", {
			parseValue: (newValue) => Number(newValue) || g_defaultSettings.favFilter,
			onSet: () => registerSettingsMenu()
		});
	}), GM_registerMenuCommand(`🎨 按照 ${settings.orderType === IllustSortOrder.BY_BOOKMARK_COUNT ? "作品收藏数" : "作品发布时间"} 排序作品`, () => {
		setSettingValue("orderType", settings.orderType === IllustSortOrder.BY_BOOKMARK_COUNT ? IllustSortOrder.BY_DATE : IllustSortOrder.BY_BOOKMARK_COUNT);
		registerSettingsMenu();
	}), GM_registerMenuCommand(`🤖 排序过滤 AI 生成作品 ${settings.aiFilter ? "✅" : "❌"}`, () => {
		toggleSettingBooleanValue("aiFilter");
		registerSettingsMenu();
	}), GM_registerMenuCommand(`🦾 排序过滤 AI 辅助(加笔)作品 ${settings.aiAssistedFilter ? "✅" : "❌"}`, () => {
		toggleSettingBooleanValue("aiAssistedFilter");
		registerSettingsMenu();
	}), GM_registerMenuCommand(`❤️ 排序过滤已收藏作品 ${settings.hideFavorite ? "✅" : "❌"}`, () => {
		toggleSettingBooleanValue("hideFavorite");
		registerSettingsMenu();
	}), GM_registerMenuCommand(`🔖 排序过滤包含指定标签的作品 ${settings.hideByTag ? "✅" : "❌"}`, () => {
		toggleSettingBooleanValue("hideByTag");
		registerSettingsMenu();
	}), GM_registerMenuCommand(`🔖 排序过滤的标签:${settings.hideByTagList}`, () => {
		setSettingStringValue("hideByTagList", "过滤的标签列表,使用`,`分隔不同标签", { onSet: () => registerSettingsMenu() });
	}), GM_registerMenuCommand(`📑 在新标签页打开作品 ${settings.linkBlank ? "✅" : "❌"}`, () => {
		toggleSettingBooleanValue("linkBlank");
		registerSettingsMenu();
	}), GM_registerMenuCommand(`🔁 重置设置`, () => {
		if (confirm("您确定要重置所有设置到脚本的默认值吗?")) {
			resetSettings();
			location.reload();
		}
	}));
	return settings;
};
const ShowUpgradeMessage = () => {
	$("#pp-bg").remove();
	const bg = $("<div id=\"pp-bg\"></div>").css({
		position: "fixed",
		"z-index": 9999,
		"background-color": "rgba(0, 0, 0, 0.8)",
		inset: "0px"
	});
	$("body").append(bg);
	bg.get(0).innerHTML = "<img id=\"pps-close\" src=\"https://pp-1252089172.cos.ap-chengdu.myqcloud.com/Close.png\"style=\"position: absolute; right: 35px; top: 20px; width: 32px; height: 32px; cursor: pointer;\"><div style=\"position: absolute; width: 40%; left: 30%; top: 25%; font-size: 25px; font-weight: bold; text-align: center; color: white;\">" + Texts.install_title + g_version + "</div><br><div style=\"position: absolute; left: 50%; top: 35%; font-size: 20px; color: white; transform: translate(-50%,0); height: 50%; overflow: auto;\">" + Texts.upgrade_body + "</div>";
	$("#pps-close").on("click", () => {
		setSettingValue("version", g_version);
		$("#pp-bg").remove();
	});
};
const initializePixivPreviewer = () => {
	try {
		g_settings = registerSettingsMenu();
		iLog.i("Start to initialize Pixiv Previewer with global settings:", g_settings);
		if (g_settings.version !== "1.4.3") ShowUpgradeMessage();
		if (g_settings.enablePreview) loadIllustPreview(g_settings);
		$.get(location.href, function(data) {
			const matched = data.match(/token\\":\\"([a-z0-9]{32})/);
			if (matched.length > 0) {
				g_csrfToken = matched[1];
				DoLog(LogLevel.Info, "Got g_csrfToken: " + g_csrfToken);
				loadIllustSort({
					...g_settings,
					csrfToken: g_csrfToken
				});
			} else DoLog(LogLevel.Error, "Can not get g_csrfToken, sort function is disabled.");
		});
		for (let i = 0; i < Object.keys(Pages).length; i++) if (Pages[i].CheckUrl(location.href)) {
			g_pageType = i;
			break;
		}
		if (g_pageType !== void 0) DoLog(LogLevel.Info, "Current page is " + Pages[g_pageType].PageTypeString);
		else {
			DoLog(LogLevel.Info, "Unsupported page.");
			return;
		}
		if (g_pageType === PageType.Member) showSearchLinksForDeletedArtworks();
		else if (g_pageType === PageType.Artwork) {
			const artworkId = window.location.pathname.match(/\/artworks\/(\d+)/)?.[1];
			if (artworkId) setTimeout(() => {
				deleteCachedIllustrationDetails([artworkId]);
			});
		}
		const toolBar = Pages[g_pageType].GetToolBar();
		if (toolBar) DoLog(LogLevel.Elements, toolBar);
		else {
			DoLog(LogLevel.Warning, "Get toolbar failed.");
			return;
		}
		if (!$(`#${"pp-sort"}`).length) {
			const newListItem = document.createElement("div");
			newListItem.title = "Sort artworks";
			newListItem.innerHTML = "";
			const newButton = document.createElement("button");
			newButton.id = SORT_BUTTON_ID;
			newButton.style.cssText = "box-sizing: border-box; background-color: rgba(0,0,0,0.32); color: #fff; margin-top: 5px; opacity: 0.8; cursor: pointer; border: none; padding: 0px; border-radius: 24px; width: 48px; height: 48px; font-size: 12px; font-weight: bold;";
			newButton.innerHTML = Texts.label_sort;
			newListItem.appendChild(newButton);
			toolBar.appendChild(newListItem);
			$(newButton).on("click", () => {
				const sortEvent = new Event(SORT_EVENT_NAME);
				window.dispatchEvent(sortEvent);
			});
		}
		if (!$(`#${"pp-sort-next-page"}`).length) {
			const newListItem = document.createElement("div");
			newListItem.title = "Jump to next page";
			newListItem.innerHTML = "";
			const newButton = document.createElement("button");
			newButton.id = SORT_NEXT_PAGE_BUTTON_ID;
			newButton.style.cssText = "box-sizing: border-box; background-color: rgba(0,0,0,0.32); color: #fff; margin-top: 5px; opacity: 0.8; cursor: pointer; border: none; padding: 0px; border-radius: 24px; width: 48px; height: 48px; font-size: 12px; font-weight: bold;";
			newButton.innerHTML = Texts.label_nextPage;
			newListItem.appendChild(newButton);
			toolBar.appendChild(newListItem);
			$(newButton).on("click", () => {
				const sortEvent = new Event(SORT_NEXT_PAGE_EVENT_NAME);
				window.dispatchEvent(sortEvent);
			});
		}
		if (!$(`#${"pp-hide-favorites"}`).length) {
			const newListItem = document.createElement("div");
			newListItem.title = "Hide favorite illustrations";
			newListItem.innerHTML = "";
			const newButton = document.createElement("button");
			newButton.id = HIDE_FAVORITES_BUTTON_ID;
			newButton.style.cssText = "box-sizing: border-box; background-color: rgba(0,0,0,0.32); color: #fff; margin-top: 5px; opacity: 0.8; cursor: pointer; border: none; padding: 0px; border-radius: 24px; width: 48px; height: 48px; font-size: 12px; font-weight: bold;";
			newButton.innerHTML = Texts.label_hideFav;
			newListItem.appendChild(newButton);
			toolBar.appendChild(newListItem);
			$(newButton).on("click", () => {
				hideFavorites();
			});
		}
	} catch (e) {
		DoLog(LogLevel.Error, "An error occurred while initializing:", e);
	}
};
window.addEventListener("DOMContentLoaded", () => {
	setTimeout(initializePixivPreviewer, 1e3);
});

//#endregion