Pixiv Previewer L

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

  1. // ==UserScript==
  2. // @name Pixiv Previewer L
  3. // @namespace https://github.com/LolipopJ/PixivPreviewer
  4. // @version 1.2.2-2025/6/12
  5. // @description Original project: https://github.com/Ocrosoft/PixivPreviewer.
  6. // @author Ocrosoft, LolipopJ
  7. // @license GPL-3.0
  8. // @supportURL https://github.com/LolipopJ/PixivPreviewer
  9. // @match *://www.pixiv.net/*
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @grant GM_registerMenuCommand
  13. // @grant GM_unregisterMenuCommand
  14. // @grant GM.xmlHttpRequest
  15. // @icon https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&size=32&url=https://www.pixiv.net
  16. // @icon64 https://t0.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&size=64&url=https://www.pixiv.net
  17. // @require https://update.gf.qytechs.cn/scripts/515994/1478507/gh_2215_make_GM_xhr_more_parallel_again.js
  18. // @require http://code.jquery.com/jquery-3.7.1.min.js
  19. // @run-at document-end
  20. // ==/UserScript==
  21.  
  22. // src/constants/index.ts
  23. var g_version = "1.2.2";
  24. var g_defaultSettings = {
  25. enablePreview: true,
  26. enableAnimePreview: true,
  27. previewDelay: 300,
  28. pageCount: 2,
  29. favFilter: 500,
  30. orderType: 0 /* BY_BOOKMARK_COUNT */,
  31. aiFilter: false,
  32. aiAssistedFilter: false,
  33. hideFavorite: true,
  34. hideByTag: false,
  35. hideByTagList: "",
  36. linkBlank: true,
  37. version: g_version
  38. };
  39. var g_loadingImage = "https://pp-1252089172.cos.ap-chengdu.myqcloud.com/loading.gif";
  40. var PREVIEW_WRAPPER_BORDER_WIDTH = 2;
  41. var PREVIEW_WRAPPER_BORDER_RADIUS = 8;
  42. var PREVIEW_WRAPPER_DISTANCE_TO_MOUSE = 20;
  43. var PREVIEW_PRELOAD_NUM = 5;
  44. var TOOLBAR_ID = "pp-toolbar";
  45. var SORT_BUTTON_ID = "pp-sort";
  46. var SORT_EVENT_NAME = "PIXIV_PREVIEWER_RUN_SORT";
  47. var SORT_NEXT_PAGE_BUTTON_ID = "pp-sort-next-page";
  48. var SORT_NEXT_PAGE_EVENT_NAME = "PIXIV_PREVIEWER_JUMP_TO_NEXT_PAGE";
  49. var AI_ASSISTED_TAGS = [
  50. "ai\u30A4\u30E9\u30B9\u30C8",
  51. "ai-generated",
  52. "ai-assisted",
  53. "ai-shoujo",
  54. "ai\u751F\u6210",
  55. "ai\u8F14\u52A9",
  56. "ai\u8F85\u52A9",
  57. "ai\u52A0\u7B46",
  58. "ai\u52A0\u7B14"
  59. ];
  60.  
  61. // src/utils/logger.ts
  62. var ILog = class {
  63. prefix = "%c Pixiv Preview";
  64. v(...values) {
  65. console.log(
  66. this.prefix + " [VERBOSE] ",
  67. "color:#333 ;background-color: #fff",
  68. ...values
  69. );
  70. }
  71. i(...infos) {
  72. console.info(
  73. this.prefix + " [INFO] ",
  74. "color:#333 ;background-color: #fff;",
  75. ...infos
  76. );
  77. }
  78. w(...warnings) {
  79. console.warn(
  80. this.prefix + " [WARNING] ",
  81. "color:#111 ;background-color:#ffa500;",
  82. ...warnings
  83. );
  84. }
  85. e(...errors) {
  86. console.error(
  87. this.prefix + " [ERROR] ",
  88. "color:#111 ;background-color:#ff0000;",
  89. ...errors
  90. );
  91. }
  92. d(...data) {
  93. console.log(
  94. this.prefix + " [DATA] ",
  95. "color:#333 ;background-color: #fff;",
  96. ...data
  97. );
  98. }
  99. };
  100. var iLog = new ILog();
  101. function DoLog(level = 3 /* Info */, ...msgOrElement) {
  102. switch (level) {
  103. case 1 /* Error */:
  104. iLog.e(...msgOrElement);
  105. break;
  106. case 2 /* Warning */:
  107. iLog.w(...msgOrElement);
  108. break;
  109. case 3 /* Info */:
  110. iLog.i(...msgOrElement);
  111. break;
  112. case 4 /* Elements */:
  113. case 0 /* None */:
  114. default:
  115. iLog.v(...msgOrElement);
  116. }
  117. }
  118.  
  119. // src/databases/index.ts
  120. var INDEX_DB_NAME = "PIXIV_PREVIEWER_L";
  121. var INDEX_DB_VERSION = 1;
  122. var ILLUSTRATION_DETAILS_CACHE_TABLE_KEY = "illustrationDetailsCache";
  123. var ILLUSTRATION_DETAILS_CACHE_TIME = 1e3 * 60 * 60 * 6;
  124. var NEW_ILLUSTRATION_NOT_CACHE_TIME = 1e3 * 60 * 60 * 1;
  125. var request = indexedDB.open(INDEX_DB_NAME, INDEX_DB_VERSION);
  126. var db;
  127. request.onupgradeneeded = (event) => {
  128. const db2 = event.target.result;
  129. db2.createObjectStore(ILLUSTRATION_DETAILS_CACHE_TABLE_KEY, {
  130. keyPath: "id"
  131. });
  132. };
  133. request.onsuccess = (event) => {
  134. db = event.target.result;
  135. console.log("Open IndexedDB successfully:", db);
  136. deleteExpiredIllustrationDetails();
  137. };
  138. request.onerror = (event) => {
  139. iLog.e(`An error occurred while requesting IndexedDB`, event);
  140. };
  141. var cacheIllustrationDetails = (illustrations, now = /* @__PURE__ */ new Date()) => {
  142. return new Promise(() => {
  143. const cachedIllustrationDetailsObjectStore = db.transaction(ILLUSTRATION_DETAILS_CACHE_TABLE_KEY, "readwrite").objectStore(ILLUSTRATION_DETAILS_CACHE_TABLE_KEY);
  144. illustrations.forEach((illustration) => {
  145. const uploadTimestamp = illustration.uploadTimestamp * 1e3;
  146. if (now.getTime() - uploadTimestamp > NEW_ILLUSTRATION_NOT_CACHE_TIME) {
  147. const illustrationDetails = {
  148. ...illustration,
  149. cacheDate: now
  150. };
  151. const addCachedIllustrationDetailsRequest = cachedIllustrationDetailsObjectStore.put(illustrationDetails);
  152. addCachedIllustrationDetailsRequest.onerror = (event) => {
  153. iLog.e(`An error occurred while caching illustration details`, event);
  154. };
  155. }
  156. });
  157. });
  158. };
  159. var getCachedIllustrationDetails = (id, now = /* @__PURE__ */ new Date()) => {
  160. return new Promise((resolve) => {
  161. const cachedIllustrationDetailsObjectStore = db.transaction(ILLUSTRATION_DETAILS_CACHE_TABLE_KEY, "readwrite").objectStore(ILLUSTRATION_DETAILS_CACHE_TABLE_KEY);
  162. const getCachedIllustrationDetailsRequest = cachedIllustrationDetailsObjectStore.get(id);
  163. getCachedIllustrationDetailsRequest.onsuccess = (event) => {
  164. const illustrationDetails = event.target.result;
  165. if (illustrationDetails) {
  166. const { cacheDate } = illustrationDetails;
  167. if (now.getTime() - cacheDate.getTime() <= ILLUSTRATION_DETAILS_CACHE_TIME) {
  168. resolve(illustrationDetails);
  169. } else {
  170. cachedIllustrationDetailsObjectStore.delete(id).onerror = (event2) => {
  171. iLog.e(
  172. `An error occurred while deleting outdated illustration details`,
  173. event2
  174. );
  175. };
  176. }
  177. }
  178. resolve(null);
  179. };
  180. getCachedIllustrationDetailsRequest.onerror = (event) => {
  181. iLog.e(
  182. `An error occurred while getting cached illustration details`,
  183. event
  184. );
  185. resolve(null);
  186. };
  187. });
  188. };
  189. var deleteCachedIllustrationDetails = (ids) => {
  190. return new Promise((resolve) => {
  191. const cachedIllustrationDetailsObjectStore = db.transaction(ILLUSTRATION_DETAILS_CACHE_TABLE_KEY, "readwrite").objectStore(ILLUSTRATION_DETAILS_CACHE_TABLE_KEY);
  192. for (const id of ids) {
  193. const deleteCachedIllustrationDetailsRequest = cachedIllustrationDetailsObjectStore.delete(id);
  194. deleteCachedIllustrationDetailsRequest.onsuccess = () => {
  195. resolve();
  196. };
  197. deleteCachedIllustrationDetailsRequest.onerror = (event) => {
  198. iLog.w(
  199. `An error occurred while deleting cached details of illustration ${id}`,
  200. event
  201. );
  202. resolve();
  203. };
  204. }
  205. });
  206. };
  207. function deleteExpiredIllustrationDetails() {
  208. return new Promise((resolve) => {
  209. const now = (/* @__PURE__ */ new Date()).getTime();
  210. const cachedIllustrationDetailsObjectStore = db.transaction(ILLUSTRATION_DETAILS_CACHE_TABLE_KEY, "readwrite").objectStore(ILLUSTRATION_DETAILS_CACHE_TABLE_KEY);
  211. const getAllRequest = cachedIllustrationDetailsObjectStore.getAll();
  212. getAllRequest.onsuccess = (event) => {
  213. const allEntries = event.target.result;
  214. allEntries.forEach((entry) => {
  215. if (now - entry.cacheDate.getTime() > ILLUSTRATION_DETAILS_CACHE_TIME) {
  216. cachedIllustrationDetailsObjectStore.delete(entry.id);
  217. }
  218. });
  219. resolve();
  220. };
  221. });
  222. }
  223.  
  224. // src/icons/download.svg
  225. 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>';
  226.  
  227. // src/icons/loading.svg
  228. 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>';
  229.  
  230. // src/icons/page.svg
  231. 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>';
  232.  
  233. // src/utils/utils.ts
  234. var pause = (ms) => {
  235. return new Promise((resolve) => setTimeout(resolve, ms));
  236. };
  237. var convertObjectKeysFromSnakeToCamel = (obj) => {
  238. function snakeToCamel(snake) {
  239. return snake.replace(/_([a-z])/g, (result) => result[1].toUpperCase());
  240. }
  241. const newResponse = {};
  242. for (const key in obj) {
  243. newResponse[snakeToCamel(key)] = obj[key];
  244. }
  245. return newResponse;
  246. };
  247.  
  248. // src/services/request.ts
  249. var xmlHttpRequest = window.GM.xmlHttpRequest;
  250. var request2 = (options) => {
  251. const { headers, ...restOptions } = options;
  252. return xmlHttpRequest({
  253. responseType: "json",
  254. ...restOptions,
  255. headers: {
  256. referer: "https://www.pixiv.net/",
  257. ...headers
  258. }
  259. });
  260. };
  261. var requestWithRetry = async (options) => {
  262. const {
  263. retryDelay = 1e4,
  264. maxRetryTimes = Infinity,
  265. onRetry,
  266. ...restOptions
  267. } = options;
  268. let response;
  269. let retryTimes = 0;
  270. while (retryTimes < maxRetryTimes) {
  271. response = await request2(restOptions);
  272. if (response.status === 200) {
  273. const responseData = response.response;
  274. if (!responseData.error) {
  275. return response;
  276. }
  277. }
  278. retryTimes += 1;
  279. onRetry?.(response, retryTimes);
  280. await pause(retryDelay);
  281. }
  282. throw new Error(
  283. `Request for ${restOptions.url} failed: ${response.responseText}`
  284. );
  285. };
  286. var request_default = request2;
  287.  
  288. // src/services/download.ts
  289. var downloadFile = (url, filename, options = {}) => {
  290. const { onload, onerror, ...restOptions } = options;
  291. request_default({
  292. ...restOptions,
  293. url,
  294. method: "GET",
  295. responseType: "blob",
  296. onload: (resp) => {
  297. onload?.(resp);
  298. const blob = new Blob([resp.response], {
  299. // @ts-expect-error: specified in request options
  300. type: resp.responseType
  301. });
  302. const blobUrl = URL.createObjectURL(blob);
  303. const a = document.createElement("a");
  304. a.href = blobUrl;
  305. a.download = filename;
  306. document.body.appendChild(a);
  307. a.click();
  308. document.body.removeChild(a);
  309. URL.revokeObjectURL(blobUrl);
  310. },
  311. onerror: (resp) => {
  312. onerror?.(resp);
  313. iLog.e(`Download ${filename} from ${url} failed: ${resp.responseText}`);
  314. }
  315. });
  316. };
  317.  
  318. // src/services/illustration.ts
  319. var getIllustrationDetailsWithCache = async (id, retry = false) => {
  320. let illustDetails = await getCachedIllustrationDetails(id);
  321. if (illustDetails) {
  322. iLog.d(`Use cached details of illustration ${id}`, illustDetails);
  323. } else {
  324. const requestUrl = `/touch/ajax/illust/details?illust_id=${id}`;
  325. const getIllustDetailsRes = retry ? await requestWithRetry({
  326. url: requestUrl,
  327. onRetry: (response, retryTimes) => {
  328. iLog.w(
  329. `Get illustration details via api \`${requestUrl}\` failed:`,
  330. response,
  331. `${retryTimes} times retrying...`
  332. );
  333. }
  334. }) : await request_default({ url: requestUrl });
  335. if (getIllustDetailsRes.status === 200) {
  336. illustDetails = convertObjectKeysFromSnakeToCamel(
  337. getIllustDetailsRes.response.body.illust_details
  338. );
  339. cacheIllustrationDetails([illustDetails]);
  340. } else {
  341. illustDetails = null;
  342. }
  343. }
  344. return illustDetails;
  345. };
  346. var getUserIllustrations = async (userId) => {
  347. const response = await request_default({
  348. url: `/ajax/user/${userId}/profile/all?sensitiveFilterMode=userSetting&lang=zh`
  349. });
  350. const responseData = response.response.body;
  351. const illusts = Object.keys(responseData.illusts).reverse();
  352. const manga = Object.keys(responseData.manga).reverse();
  353. const artworks = [...illusts, ...manga].sort((a, b) => Number(b) - Number(a));
  354. return {
  355. illusts,
  356. manga,
  357. artworks
  358. };
  359. };
  360. var getUserIllustrationsWithCache = async (userId, { onRequesting } = {}) => {
  361. let userIllustrations = {
  362. illusts: [],
  363. manga: [],
  364. artworks: []
  365. };
  366. const userIllustrationsCacheKey = `PIXIV_PREVIEWER_CACHED_ARTWORKS_OF_USER_${userId}`;
  367. try {
  368. const userIllustrationsCacheString = sessionStorage.getItem(
  369. userIllustrationsCacheKey
  370. );
  371. if (!userIllustrationsCacheString)
  372. throw new Error("Illustrations cache not existed.");
  373. userIllustrations = JSON.parse(userIllustrationsCacheString);
  374. } catch (error) {
  375. iLog.i(
  376. `Get illustrations of current user from session storage failed, re-getting...`,
  377. error
  378. );
  379. onRequesting?.();
  380. userIllustrations = await getUserIllustrations(userId);
  381. sessionStorage.setItem(
  382. userIllustrationsCacheKey,
  383. JSON.stringify(userIllustrations)
  384. );
  385. }
  386. return userIllustrations;
  387. };
  388.  
  389. // src/services/preview.ts
  390. var downloadIllust = ({
  391. url,
  392. filename,
  393. options = {}
  394. }) => {
  395. downloadFile(url, filename, {
  396. ...options,
  397. onerror: () => {
  398. window.open(url, "__blank");
  399. }
  400. });
  401. };
  402. var getIllustPagesRequestUrl = (id) => {
  403. return `/ajax/illust/${id}/pages`;
  404. };
  405. var getUgoiraMetadataRequestUrl = (id) => {
  406. return `/ajax/illust/${id}/ugoira_meta`;
  407. };
  408.  
  409. // src/utils/debounce.ts
  410. function debounce(func, delay = 100) {
  411. let timeout = null;
  412. return function(...args) {
  413. if (timeout) {
  414. clearTimeout(timeout);
  415. }
  416. timeout = setTimeout(() => {
  417. func(...args);
  418. }, delay);
  419. };
  420. }
  421. var debounce_default = debounce;
  422.  
  423. // src/utils/event.ts
  424. var stopEventPropagation = (event) => {
  425. event.stopPropagation();
  426. };
  427.  
  428. // src/utils/illustration.ts
  429. var checkIsR18 = (tags) => {
  430. const R18_TAGS = ["r-18", "r18"];
  431. for (const tag of tags) {
  432. if (R18_TAGS.includes(tag.toLowerCase())) {
  433. return true;
  434. }
  435. }
  436. return false;
  437. };
  438. var checkIsUgoira = (illustType) => {
  439. return illustType === 2 /* UGOIRA */;
  440. };
  441. var checkIsAiGenerated = (aiType) => {
  442. return aiType === 2 /* AI */;
  443. };
  444. var checkIsAiAssisted = (tags) => {
  445. for (const tag of tags) {
  446. if (AI_ASSISTED_TAGS.includes(tag.toLowerCase())) {
  447. return true;
  448. }
  449. }
  450. return false;
  451. };
  452.  
  453. // src/utils/mouse-monitor.ts
  454. var MouseMonitor = class {
  455. /** 鼠标相对网页的位置 */
  456. mousePos = [0, 0];
  457. /** 鼠标相对视窗的绝对位置 */
  458. mouseAbsPos = [0, 0];
  459. constructor() {
  460. document.addEventListener("mousemove", (mouseMoveEvent) => {
  461. this.mousePos = [mouseMoveEvent.pageX, mouseMoveEvent.pageY];
  462. this.mouseAbsPos = [mouseMoveEvent.clientX, mouseMoveEvent.clientY];
  463. });
  464. }
  465. };
  466. var mouseMonitor = new MouseMonitor();
  467. var mouse_monitor_default = mouseMonitor;
  468.  
  469. // src/utils/ugoira-player.ts
  470. function ZipImagePlayer(options) {
  471. this.op = options;
  472. this._URL = window.URL || window.webkitURL || window.MozURL || window.MSURL;
  473. this._Blob = window.Blob || window.WebKitBlob || window.MozBlob || window.MSBlob;
  474. this._BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder;
  475. this._Uint8Array = window.Uint8Array || window.WebKitUint8Array || window.MozUint8Array || window.MSUint8Array;
  476. this._DataView = window.DataView || window.WebKitDataView || window.MozDataView || window.MSDataView;
  477. this._ArrayBuffer = window.ArrayBuffer || window.WebKitArrayBuffer || window.MozArrayBuffer || window.MSArrayBuffer;
  478. this._maxLoadAhead = 0;
  479. if (!this._URL) {
  480. this._debugLog("No URL support! Will use slower data: URLs.");
  481. this._maxLoadAhead = 10;
  482. }
  483. if (!this._Blob) {
  484. this._error("No Blob support");
  485. }
  486. if (!this._Uint8Array) {
  487. this._error("No Uint8Array support");
  488. }
  489. if (!this._DataView) {
  490. this._error("No DataView support");
  491. }
  492. if (!this._ArrayBuffer) {
  493. this._error("No ArrayBuffer support");
  494. }
  495. this._isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf("Constructor") > 0;
  496. this._loadingState = 0;
  497. this._dead = false;
  498. this._context = options.canvas.getContext("2d");
  499. this._files = {};
  500. this._frameCount = this.op.metadata.frames.length;
  501. this._debugLog("Frame count: " + this._frameCount);
  502. this._frame = 0;
  503. this._loadFrame = 0;
  504. this._frameImages = [];
  505. this._paused = false;
  506. this._loadTimer = null;
  507. this._startLoad();
  508. if (this.op.autoStart) {
  509. this.play();
  510. } else {
  511. this._paused = true;
  512. }
  513. }
  514. ZipImagePlayer.prototype = {
  515. _trailerBytes: 3e4,
  516. _failed: false,
  517. _mkerr: function(msg) {
  518. const _this = this;
  519. return function() {
  520. _this._error(msg);
  521. };
  522. },
  523. _error: function(msg) {
  524. this._failed = true;
  525. throw Error("ZipImagePlayer error: " + msg);
  526. },
  527. _debugLog: function(msg) {
  528. if (this.op.debug) {
  529. console.log(msg);
  530. }
  531. },
  532. _load: function(offset, length, callback) {
  533. const _this = this;
  534. const xhr = new XMLHttpRequest();
  535. xhr.addEventListener(
  536. "load",
  537. function() {
  538. if (_this._dead) {
  539. return;
  540. }
  541. _this._debugLog(
  542. "Load: " + offset + " " + length + " status=" + xhr.status
  543. );
  544. if (xhr.status == 200) {
  545. _this._debugLog("Range disabled or unsupported, complete load");
  546. offset = 0;
  547. length = xhr.response.byteLength;
  548. _this._len = length;
  549. _this._buf = xhr.response;
  550. _this._bytes = new _this._Uint8Array(_this._buf);
  551. } else {
  552. if (xhr.status != 206) {
  553. _this._error("Unexpected HTTP status " + xhr.status);
  554. }
  555. if (xhr.response.byteLength != length) {
  556. _this._error(
  557. "Unexpected length " + xhr.response.byteLength + " (expected " + length + ")"
  558. );
  559. }
  560. _this._bytes.set(new _this._Uint8Array(xhr.response), offset);
  561. }
  562. if (callback) {
  563. callback.apply(_this, [offset, length]);
  564. }
  565. },
  566. false
  567. );
  568. xhr.addEventListener("error", this._mkerr("Fetch failed"), false);
  569. xhr.open("GET", this.op.source);
  570. xhr.responseType = "arraybuffer";
  571. if (offset != null && length != null) {
  572. const end = offset + length;
  573. xhr.setRequestHeader("Range", "bytes=" + offset + "-" + (end - 1));
  574. if (this._isSafari) {
  575. xhr.setRequestHeader("Cache-control", "no-cache");
  576. xhr.setRequestHeader("If-None-Match", Math.random().toString());
  577. }
  578. }
  579. xhr.send();
  580. },
  581. _startLoad: function() {
  582. const _this = this;
  583. if (!this.op.source) {
  584. this._loadNextFrame();
  585. return;
  586. }
  587. $.ajax({
  588. url: this.op.source,
  589. type: "HEAD"
  590. }).done(function(data, status, xhr) {
  591. if (_this._dead) {
  592. return;
  593. }
  594. _this._pHead = 0;
  595. _this._pNextHead = 0;
  596. _this._pFetch = 0;
  597. const len = parseInt(String(xhr.getResponseHeader("Content-Length")));
  598. if (!len) {
  599. _this._debugLog("HEAD request failed: invalid file length.");
  600. _this._debugLog("Falling back to full file mode.");
  601. _this._load(null, null, function(off2, len2) {
  602. _this._pTail = 0;
  603. _this._pHead = len2;
  604. _this._findCentralDirectory();
  605. });
  606. return;
  607. }
  608. _this._debugLog("Len: " + len);
  609. _this._len = len;
  610. _this._buf = new _this._ArrayBuffer(len);
  611. _this._bytes = new _this._Uint8Array(_this._buf);
  612. let off = len - _this._trailerBytes;
  613. if (off < 0) {
  614. off = 0;
  615. }
  616. _this._pTail = len;
  617. _this._load(off, len - off, function(off2) {
  618. _this._pTail = off2;
  619. _this._findCentralDirectory();
  620. });
  621. }).fail(this._mkerr("Length fetch failed"));
  622. },
  623. _findCentralDirectory: function() {
  624. const dv = new this._DataView(this._buf, this._len - 22, 22);
  625. if (dv.getUint32(0, true) != 101010256) {
  626. this._error("End of Central Directory signature not found");
  627. }
  628. const cd_count = dv.getUint16(10, true);
  629. const cd_size = dv.getUint32(12, true);
  630. const cd_off = dv.getUint32(16, true);
  631. if (cd_off < this._pTail) {
  632. this._load(cd_off, this._pTail - cd_off, function() {
  633. this._pTail = cd_off;
  634. this._readCentralDirectory(cd_off, cd_size, cd_count);
  635. });
  636. } else {
  637. this._readCentralDirectory(cd_off, cd_size, cd_count);
  638. }
  639. },
  640. _readCentralDirectory: function(offset, size, count) {
  641. const dv = new this._DataView(this._buf, offset, size);
  642. let p = 0;
  643. for (let i = 0; i < count; i++) {
  644. if (dv.getUint32(p, true) != 33639248) {
  645. this._error("Invalid Central Directory signature");
  646. }
  647. const compMethod = dv.getUint16(p + 10, true);
  648. const uncompSize = dv.getUint32(p + 24, true);
  649. const nameLen = dv.getUint16(p + 28, true);
  650. const extraLen = dv.getUint16(p + 30, true);
  651. const cmtLen = dv.getUint16(p + 32, true);
  652. const off = dv.getUint32(p + 42, true);
  653. if (compMethod != 0) {
  654. this._error("Unsupported compression method");
  655. }
  656. p += 46;
  657. const nameView = new this._Uint8Array(this._buf, offset + p, nameLen);
  658. let name = "";
  659. for (let j = 0; j < nameLen; j++) {
  660. name += String.fromCharCode(nameView[j]);
  661. }
  662. p += nameLen + extraLen + cmtLen;
  663. this._files[name] = { off, len: uncompSize };
  664. }
  665. if (this._pHead >= this._pTail) {
  666. this._pHead = this._len;
  667. $(this).triggerHandler("loadProgress", [this._pHead / this._len]);
  668. this._loadNextFrame();
  669. } else {
  670. this._loadNextChunk();
  671. this._loadNextChunk();
  672. }
  673. },
  674. _loadNextChunk: function() {
  675. if (this._pFetch >= this._pTail) {
  676. return;
  677. }
  678. const off = this._pFetch;
  679. let len = this.op.chunkSize;
  680. if (this._pFetch + len > this._pTail) {
  681. len = this._pTail - this._pFetch;
  682. }
  683. this._pFetch += len;
  684. this._load(off, len, function() {
  685. if (off == this._pHead) {
  686. if (this._pNextHead) {
  687. this._pHead = this._pNextHead;
  688. this._pNextHead = 0;
  689. } else {
  690. this._pHead = off + len;
  691. }
  692. if (this._pHead >= this._pTail) {
  693. this._pHead = this._len;
  694. }
  695. $(this).triggerHandler("loadProgress", [this._pHead / this._len]);
  696. if (!this._loadTimer) {
  697. this._loadNextFrame();
  698. }
  699. } else {
  700. this._pNextHead = off + len;
  701. }
  702. this._loadNextChunk();
  703. });
  704. },
  705. _fileDataStart: function(offset) {
  706. const dv = new DataView(this._buf, offset, 30);
  707. const nameLen = dv.getUint16(26, true);
  708. const extraLen = dv.getUint16(28, true);
  709. return offset + 30 + nameLen + extraLen;
  710. },
  711. _isFileAvailable: function(name) {
  712. const info = this._files[name];
  713. if (!info) {
  714. this._error("File " + name + " not found in ZIP");
  715. }
  716. if (this._pHead < info.off + 30) {
  717. return false;
  718. }
  719. return this._pHead >= this._fileDataStart(info.off) + info.len;
  720. },
  721. _loadNextFrame: function() {
  722. if (this._dead) {
  723. return;
  724. }
  725. const frame = this._loadFrame;
  726. if (frame >= this._frameCount) {
  727. return;
  728. }
  729. const meta = this.op.metadata.frames[frame];
  730. if (!this.op.source) {
  731. this._loadFrame += 1;
  732. this._loadImage(frame, meta.file, false);
  733. return;
  734. }
  735. if (!this._isFileAvailable(meta.file)) {
  736. return;
  737. }
  738. this._loadFrame += 1;
  739. const off = this._fileDataStart(this._files[meta.file].off);
  740. const end = off + this._files[meta.file].len;
  741. let url;
  742. const mime_type = this.op.metadata.mime_type || "image/png";
  743. if (this._URL) {
  744. let slice;
  745. if (!this._buf.slice) {
  746. slice = new this._ArrayBuffer(this._files[meta.file].len);
  747. const view = new this._Uint8Array(slice);
  748. view.set(this._bytes.subarray(off, end));
  749. } else {
  750. slice = this._buf.slice(off, end);
  751. }
  752. let blob;
  753. try {
  754. blob = new this._Blob([slice], { type: mime_type });
  755. } catch (err) {
  756. this._debugLog(
  757. "Blob constructor failed. Trying BlobBuilder... (" + err.message + ")"
  758. );
  759. const bb = new this._BlobBuilder();
  760. bb.append(slice);
  761. blob = bb.getBlob();
  762. }
  763. url = this._URL.createObjectURL(blob);
  764. this._loadImage(frame, url, true);
  765. } else {
  766. url = "data:" + mime_type + ";base64," + base64ArrayBuffer(this._buf, off, end - off);
  767. this._loadImage(frame, url, false);
  768. }
  769. },
  770. _loadImage: function(frame, url, isBlob) {
  771. const _this = this;
  772. const image = new Image();
  773. const meta = this.op.metadata.frames[frame];
  774. image.addEventListener("load", function() {
  775. _this._debugLog("Loaded " + meta.file + " to frame " + frame);
  776. if (isBlob) {
  777. _this._URL.revokeObjectURL(url);
  778. }
  779. if (_this._dead) {
  780. return;
  781. }
  782. _this._frameImages[frame] = image;
  783. $(_this).triggerHandler("frameLoaded", frame);
  784. if (_this._loadingState == 0) {
  785. _this._displayFrame.apply(_this);
  786. }
  787. if (frame >= _this._frameCount - 1) {
  788. _this._setLoadingState(2);
  789. _this._buf = null;
  790. _this._bytes = null;
  791. } else {
  792. if (!_this._maxLoadAhead || frame - _this._frame < _this._maxLoadAhead) {
  793. _this._loadNextFrame();
  794. } else if (!_this._loadTimer) {
  795. _this._loadTimer = setTimeout(function() {
  796. _this._loadTimer = null;
  797. _this._loadNextFrame();
  798. }, 200);
  799. }
  800. }
  801. });
  802. image.src = url;
  803. },
  804. _setLoadingState: function(state) {
  805. if (this._loadingState != state) {
  806. this._loadingState = state;
  807. $(this).triggerHandler("loadingStateChanged", [state]);
  808. }
  809. },
  810. _displayFrame: function() {
  811. if (this._dead) {
  812. return;
  813. }
  814. const _this = this;
  815. const meta = this.op.metadata.frames[this._frame];
  816. this._debugLog("Displaying frame: " + this._frame + " " + meta.file);
  817. const image = this._frameImages[this._frame];
  818. if (!image) {
  819. this._debugLog("Image not available!");
  820. this._setLoadingState(0);
  821. return;
  822. }
  823. if (this._loadingState != 2) {
  824. this._setLoadingState(1);
  825. }
  826. if (this.op.autosize) {
  827. if (this._context.canvas.width != image.width || this._context.canvas.height != image.height) {
  828. this._context.canvas.width = image.width;
  829. this._context.canvas.height = image.height;
  830. }
  831. }
  832. this._context.clearRect(0, 0, this.op.canvas.width, this.op.canvas.height);
  833. this._context.drawImage(image, 0, 0);
  834. $(this).triggerHandler("frame", this._frame);
  835. if (!this._paused) {
  836. this._timer = setTimeout(function() {
  837. _this._timer = null;
  838. _this._nextFrame.apply(_this);
  839. }, meta.delay);
  840. }
  841. },
  842. _nextFrame: function() {
  843. if (this._frame >= this._frameCount - 1) {
  844. if (this.op.loop) {
  845. this._frame = 0;
  846. } else {
  847. this.pause();
  848. return;
  849. }
  850. } else {
  851. this._frame += 1;
  852. }
  853. this._displayFrame();
  854. },
  855. play: function() {
  856. if (this._dead) {
  857. return;
  858. }
  859. if (this._paused) {
  860. $(this).triggerHandler("play", [this._frame]);
  861. this._paused = false;
  862. this._displayFrame();
  863. }
  864. },
  865. pause: function() {
  866. if (this._dead) {
  867. return;
  868. }
  869. if (!this._paused) {
  870. if (this._timer) {
  871. clearTimeout(this._timer);
  872. }
  873. this._paused = true;
  874. $(this).triggerHandler("pause", [this._frame]);
  875. }
  876. },
  877. rewind: function() {
  878. if (this._dead) {
  879. return;
  880. }
  881. this._frame = 0;
  882. if (this._timer) {
  883. clearTimeout(this._timer);
  884. }
  885. this._displayFrame();
  886. },
  887. stop: function() {
  888. this._debugLog("Stopped!");
  889. this._dead = true;
  890. if (this._timer) {
  891. clearTimeout(this._timer);
  892. }
  893. if (this._loadTimer) {
  894. clearTimeout(this._loadTimer);
  895. }
  896. this._frameImages = null;
  897. this._buf = null;
  898. this._bytes = null;
  899. $(this).triggerHandler("stop");
  900. },
  901. getCurrentFrame: function() {
  902. return this._frame;
  903. },
  904. getLoadedFrames: function() {
  905. return this._frameImages.length;
  906. },
  907. getFrameCount: function() {
  908. return this._frameCount;
  909. },
  910. hasError: function() {
  911. return this._failed;
  912. }
  913. };
  914. function base64ArrayBuffer(arrayBuffer, off, byteLength) {
  915. let base64 = "";
  916. const encodings = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  917. const bytes = new Uint8Array(arrayBuffer);
  918. const byteRemainder = byteLength % 3;
  919. const mainLength = off + byteLength - byteRemainder;
  920. let a, b, c, d;
  921. let chunk;
  922. for (let i = off; i < mainLength; i = i + 3) {
  923. chunk = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
  924. a = (chunk & 16515072) >> 18;
  925. b = (chunk & 258048) >> 12;
  926. c = (chunk & 4032) >> 6;
  927. d = chunk & 63;
  928. base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d];
  929. }
  930. if (byteRemainder == 1) {
  931. chunk = bytes[mainLength];
  932. a = (chunk & 252) >> 2;
  933. b = (chunk & 3) << 4;
  934. base64 += encodings[a] + encodings[b] + "==";
  935. } else if (byteRemainder == 2) {
  936. chunk = bytes[mainLength] << 8 | bytes[mainLength + 1];
  937. a = (chunk & 64512) >> 10;
  938. b = (chunk & 1008) >> 4;
  939. c = (chunk & 15) << 2;
  940. base64 += encodings[a] + encodings[b] + encodings[c] + "=";
  941. }
  942. return base64;
  943. }
  944. var ugoira_player_default = ZipImagePlayer;
  945.  
  946. // src/features/preview.ts
  947. var isInitialized = false;
  948. var loadIllustPreview = (options) => {
  949. if (isInitialized) return;
  950. const { previewDelay, enableAnimePreview, linkBlank } = options;
  951. const mouseHoverDebounceWait = previewDelay / 5;
  952. const mouseHoverPreviewWait = previewDelay - mouseHoverDebounceWait;
  953. const getIllustMetadata = (target) => {
  954. let imgLink = target;
  955. while (!imgLink.is("A")) {
  956. imgLink = imgLink.parent();
  957. if (!imgLink.length) {
  958. return null;
  959. }
  960. }
  961. const illustHref = imgLink.attr("href");
  962. const illustHrefMatch = illustHref?.match(/\/artworks\/(\d+)(#(\d+))?/);
  963. if (!illustHrefMatch) {
  964. return null;
  965. }
  966. const illustId = illustHrefMatch[1];
  967. const previewPage = Number(illustHrefMatch[3] ?? 1);
  968. const ugoiraSvg = imgLink.children("div:first").find("svg:first");
  969. const illustType = ugoiraSvg.length || imgLink.hasClass("ugoku-illust") ? 2 /* UGOIRA */ : (
  970. // 合并漫画类型作品 IllustType.MANGA 为 IllustType.ILLUST 统一处理
  971. 0 /* ILLUST */
  972. );
  973. return {
  974. /** 作品 ID */
  975. illustId,
  976. /** 作品页码 */
  977. previewPage,
  978. /** 作品类型 */
  979. illustType,
  980. /** 作品链接 DOM */
  981. illustLinkDom: imgLink
  982. };
  983. };
  984. const previewIllust = (() => {
  985. const previewedIllust = new PreviewedIllust();
  986. let currentHoveredIllustId = "";
  987. let getIllustPagesRequest = $.ajax();
  988. const getIllustPagesCache = {};
  989. const getUgoiraMetadataCache = {};
  990. return ({
  991. target,
  992. illustId,
  993. previewPage = 1,
  994. illustType
  995. }) => {
  996. getIllustPagesRequest.abort();
  997. currentHoveredIllustId = illustId;
  998. if (illustType === 2 /* UGOIRA */ && !enableAnimePreview) {
  999. iLog.i("\u52A8\u56FE\u9884\u89C8\u5DF2\u7981\u7528\uFF0C\u8DF3\u8FC7");
  1000. return;
  1001. }
  1002. if ([0 /* ILLUST */, 1 /* MANGA */].includes(illustType)) {
  1003. if (getIllustPagesCache[illustId]) {
  1004. previewedIllust.setImage({
  1005. illustId,
  1006. illustElement: target,
  1007. previewPage,
  1008. ...getIllustPagesCache[illustId]
  1009. });
  1010. return;
  1011. }
  1012. getIllustPagesRequest = $.ajax(getIllustPagesRequestUrl(illustId), {
  1013. method: "GET",
  1014. success: (data) => {
  1015. if (data.error) {
  1016. iLog.e(
  1017. `An error occurred while requesting preview urls of illust ${illustId}: ${data.message}`
  1018. );
  1019. return;
  1020. }
  1021. const urls = data.body.map((item) => item.urls);
  1022. const regularUrls = urls.map((url) => url.regular);
  1023. const originalUrls = urls.map((url) => url.original);
  1024. getIllustPagesCache[illustId] = {
  1025. regularUrls,
  1026. originalUrls
  1027. };
  1028. if (currentHoveredIllustId !== illustId) return;
  1029. previewedIllust.setImage({
  1030. illustId,
  1031. illustElement: target,
  1032. previewPage,
  1033. regularUrls,
  1034. originalUrls
  1035. });
  1036. },
  1037. error: (err) => {
  1038. iLog.e(
  1039. `An error occurred while requesting preview urls of illust ${illustId}: ${err}`
  1040. );
  1041. }
  1042. });
  1043. } else if (illustType === 2 /* UGOIRA */) {
  1044. if (getUgoiraMetadataCache[illustId]) {
  1045. previewedIllust.setUgoira({
  1046. illustId,
  1047. illustElement: target,
  1048. ...getUgoiraMetadataCache[illustId]
  1049. });
  1050. return;
  1051. }
  1052. getIllustPagesRequest = $.ajax(getUgoiraMetadataRequestUrl(illustId), {
  1053. method: "GET",
  1054. success: (data) => {
  1055. if (data.error) {
  1056. iLog.e(
  1057. `An error occurred while requesting metadata of ugoira ${illustId}: ${data.message}`
  1058. );
  1059. return;
  1060. }
  1061. getUgoiraMetadataCache[illustId] = data.body;
  1062. if (currentHoveredIllustId !== illustId) return;
  1063. const { src, originalSrc, mime_type, frames } = data.body;
  1064. previewedIllust.setUgoira({
  1065. illustId,
  1066. illustElement: target,
  1067. src,
  1068. originalSrc,
  1069. mime_type,
  1070. frames
  1071. });
  1072. },
  1073. error: (err) => {
  1074. iLog.e(
  1075. `An error occurred while requesting metadata of ugoira ${illustId}: ${err.responseText}`
  1076. );
  1077. }
  1078. });
  1079. } else {
  1080. iLog.e("Unknown illust type.");
  1081. return;
  1082. }
  1083. };
  1084. })();
  1085. const onMouseOverIllust = (target) => {
  1086. const { illustId, previewPage, illustType, illustLinkDom } = getIllustMetadata(target) || {};
  1087. if (illustId === void 0 || illustType === void 0) {
  1088. return;
  1089. }
  1090. const pathname = location.pathname;
  1091. if (illustId === /^\/artworks\/(\d+)$/.exec(pathname)?.[1]) {
  1092. return;
  1093. }
  1094. if (linkBlank) {
  1095. illustLinkDom.attr({ target: "_blank", rel: "external" });
  1096. illustLinkDom.off("click", stopEventPropagation);
  1097. illustLinkDom.on("click", stopEventPropagation);
  1098. }
  1099. const previewIllustTimeout = setTimeout(() => {
  1100. previewIllust({ target, illustId, previewPage, illustType });
  1101. }, mouseHoverPreviewWait);
  1102. const onMouseMove = (mouseMoveEvent) => {
  1103. if (mouseMoveEvent.ctrlKey || mouseMoveEvent.metaKey) {
  1104. clearTimeout(previewIllustTimeout);
  1105. target.off("mousemove", onMouseMove);
  1106. }
  1107. };
  1108. target.on("mousemove", onMouseMove);
  1109. const onMouseOut = () => {
  1110. clearTimeout(previewIllustTimeout);
  1111. target.off("mouseout", onMouseOut);
  1112. };
  1113. target.on("mouseout", onMouseOut);
  1114. };
  1115. const onMouseMoveDocument = (() => {
  1116. const debouncedOnMouseOverIllust = debounce_default(
  1117. onMouseOverIllust,
  1118. mouseHoverDebounceWait
  1119. );
  1120. let prevTarget;
  1121. return (mouseMoveEvent) => {
  1122. if (mouseMoveEvent.ctrlKey || mouseMoveEvent.metaKey) {
  1123. return;
  1124. }
  1125. const currentTarget = $(
  1126. mouseMoveEvent.target
  1127. );
  1128. if (currentTarget.is(prevTarget)) {
  1129. return;
  1130. }
  1131. prevTarget = currentTarget;
  1132. debouncedOnMouseOverIllust(currentTarget);
  1133. };
  1134. })();
  1135. $(document).on("mousemove", onMouseMoveDocument);
  1136. (function inactiveUnexpectedDoms() {
  1137. const styleRules = $("<style>").prop("type", "text/css");
  1138. styleRules.append(`
  1139. @keyframes pp-spin {
  1140. 0% { transform: rotate(0deg); }
  1141. 100% { transform: rotate(360deg); }
  1142. }`);
  1143. styleRules.append(`
  1144. ._layout-thumbnail img + div {
  1145. pointer-events: none;
  1146. }`);
  1147. styleRules.appendTo("head");
  1148. })();
  1149. isInitialized = true;
  1150. };
  1151. var PreviewedIllust = class {
  1152. /** 当前正在预览的作品的 ID */
  1153. illustId = "";
  1154. /** 当前正在预览的作品的详细信息 */
  1155. illustDetails = null;
  1156. /** 当前正在预览的作品 DOM 元素 */
  1157. illustElement = $();
  1158. /** 当前预览的作品是否加载完毕 */
  1159. illustLoaded = false;
  1160. /** 图片的链接 */
  1161. regularUrls = [];
  1162. /** 图片的原图链接 */
  1163. originalUrls = [];
  1164. /** 当前预览图片的页数 */
  1165. currentPage = 1;
  1166. /** 当前预览图片的总页数 */
  1167. pageCount = 1;
  1168. /** 预览图片或动图容器 DOM */
  1169. previewWrapperElement = $();
  1170. /** 预览容器顶部栏 DOM */
  1171. previewWrapperHeader = $();
  1172. /** 当前预览的是第几张图片标记 DOM */
  1173. pageCountElement = $();
  1174. pageCountText = $();
  1175. /** 下载原图按钮 DOM */
  1176. downloadOriginalElement = $();
  1177. /** 预览图片或动图加载状态 DOM */
  1178. previewLoadingElement = $();
  1179. /** 当前预览的图片或动图 DOM */
  1180. previewImageElement = $();
  1181. /** 预加载图片的列表 */
  1182. #images = [];
  1183. /** 保存的鼠标位置 */
  1184. #prevMousePos = [0, 0];
  1185. /** 当前预览图片的实际尺寸 */
  1186. #currentIllustSize = [0, 0];
  1187. /** 当前预览的动图播放器 */
  1188. // @ts-expect-error: ignore type defines
  1189. #currentUgoiraPlayer;
  1190. constructor() {
  1191. this.reset();
  1192. }
  1193. /** 初始化预览组件 */
  1194. reset() {
  1195. this.illustId = "";
  1196. this.illustDetails = null;
  1197. this.illustElement = $();
  1198. this.illustLoaded = false;
  1199. this.regularUrls = [];
  1200. this.originalUrls = [];
  1201. this.currentPage = 1;
  1202. this.pageCount = 1;
  1203. this.previewWrapperElement?.remove();
  1204. this.previewWrapperElement = $(document.createElement("div")).attr({ id: "pp-wrapper" }).css({
  1205. position: "fixed",
  1206. "z-index": "999999",
  1207. border: `${PREVIEW_WRAPPER_BORDER_WIDTH}px solid rgb(0, 150, 250)`,
  1208. "border-radius": `${PREVIEW_WRAPPER_BORDER_RADIUS}px`,
  1209. background: "rgba(31, 31, 31, 0.8)",
  1210. "backdrop-filter": "blur(4px)",
  1211. "text-align": "center"
  1212. }).hide().appendTo($("body"));
  1213. this.previewWrapperHeader = $(document.createElement("div")).attr({
  1214. id: "pp-wrapper__header"
  1215. }).css({
  1216. position: "absolute",
  1217. top: "0px",
  1218. left: "0px",
  1219. right: "0px",
  1220. padding: "5px",
  1221. display: "flex",
  1222. gap: "5px",
  1223. "align-items": "center",
  1224. "justify-content": "flex-end"
  1225. }).hide().appendTo(this.previewWrapperElement);
  1226. this.pageCountText = $(document.createElement("span")).attr({ id: "pp-page-count__text" }).text("1/1");
  1227. this.pageCountElement = $(document.createElement("div")).attr({ id: "pp-page-count" }).css({
  1228. height: "20px",
  1229. "border-radius": "12px",
  1230. color: "white",
  1231. background: "rgba(0, 0, 0, 0.32)",
  1232. "font-size": "12px",
  1233. "line-height": "1",
  1234. "font-weight": "bold",
  1235. padding: "3px 6px",
  1236. cursor: "pointer",
  1237. display: "flex",
  1238. "align-items": "center",
  1239. gap: "4px"
  1240. }).append(page_default).append(this.pageCountText).hide().prependTo(this.previewWrapperHeader);
  1241. this.downloadOriginalElement = $(document.createElement("a")).attr({ id: "pp-download-original" }).css({
  1242. height: "20px",
  1243. "border-radius": "12px",
  1244. color: "white",
  1245. background: "rgba(0, 0, 0, 0.32)",
  1246. "font-size": "12px",
  1247. "line-height": "1",
  1248. "font-weight": "bold",
  1249. padding: "3px 6px",
  1250. cursor: "pointer",
  1251. display: "flex",
  1252. "align-items": "center",
  1253. gap: "4px"
  1254. }).append(`${download_default}<span>\u539F\u56FE</span>`).prependTo(this.previewWrapperHeader);
  1255. this.previewLoadingElement = $(loading_default).attr({ id: "pp-loading" }).css({ padding: "12px", animation: "pp-spin 1s linear infinite" }).appendTo(this.previewWrapperElement);
  1256. this.previewImageElement = $(new Image()).attr({ id: "pp-image" }).css({
  1257. "border-radius": `${PREVIEW_WRAPPER_BORDER_RADIUS}px`
  1258. }).hide().appendTo(this.previewWrapperElement);
  1259. this.#images.forEach((image) => {
  1260. if (image) image.src = "";
  1261. });
  1262. this.#images = [];
  1263. this.#prevMousePos = [0, 0];
  1264. this.#currentIllustSize = [0, 0];
  1265. this.#currentUgoiraPlayer?.stop();
  1266. this.unbindPreviewImageEvents();
  1267. this.unbindUgoiraPreviewEvents();
  1268. }
  1269. //#region 预览图片功能
  1270. /** 初始化预览容器,默认显示第一张图片 */
  1271. setImage({
  1272. illustId,
  1273. illustElement,
  1274. previewPage = 1,
  1275. regularUrls,
  1276. originalUrls
  1277. }) {
  1278. this.reset();
  1279. this.initPreviewWrapper();
  1280. this.illustId = illustId;
  1281. this.illustElement = illustElement;
  1282. this.regularUrls = regularUrls;
  1283. this.originalUrls = originalUrls;
  1284. this.currentPage = previewPage;
  1285. this.pageCount = regularUrls.length;
  1286. this.preloadImages();
  1287. this.bindPreviewImageEvents();
  1288. this.updatePreviewImage();
  1289. this.showIllustrationDetails();
  1290. }
  1291. bindPreviewImageEvents() {
  1292. this.previewImageElement.on("load", this.onImageLoad);
  1293. this.previewImageElement.on("click", this.onPreviewImageMouseClick);
  1294. this.downloadOriginalElement.on("click", this.onDownloadImage);
  1295. $(document).on("wheel", this.onPreviewImageMouseWheel);
  1296. $(document).on("keydown", this.onPreviewImageKeyDown);
  1297. $(document).on("mousemove", this.onMouseMove);
  1298. window.addEventListener("wheel", this.preventPageZoom, { passive: false });
  1299. }
  1300. unbindPreviewImageEvents() {
  1301. this.previewImageElement.off();
  1302. this.downloadOriginalElement.off();
  1303. $(document).off("wheel", this.onPreviewImageMouseWheel);
  1304. $(document).off("keydown", this.onPreviewImageKeyDown);
  1305. $(document).off("mousemove", this.onMouseMove);
  1306. window.removeEventListener("wheel", this.preventPageZoom);
  1307. }
  1308. /** 显示 pageIndex 指向的图片 */
  1309. updatePreviewImage(page = this.currentPage) {
  1310. const currentImageUrl = this.regularUrls[page - 1];
  1311. this.previewImageElement.attr("src", currentImageUrl);
  1312. this.pageCountText.text(`${page}/${this.pageCount}`);
  1313. }
  1314. onImageLoad = () => {
  1315. this.illustLoaded = true;
  1316. this.previewLoadingElement.hide();
  1317. this.previewImageElement.show();
  1318. this.previewWrapperHeader.show();
  1319. if (this.pageCount > 1) {
  1320. this.pageCountElement.show();
  1321. }
  1322. this.previewImageElement.css({
  1323. width: "",
  1324. height: ""
  1325. });
  1326. this.#currentIllustSize = [
  1327. this.previewImageElement.width() ?? 0,
  1328. this.previewImageElement.height() ?? 0
  1329. ];
  1330. this.adjustPreviewWrapper({
  1331. baseOnMousePos: false
  1332. });
  1333. };
  1334. nextPage() {
  1335. if (this.currentPage < this.pageCount) {
  1336. this.currentPage += 1;
  1337. } else {
  1338. this.currentPage = 1;
  1339. }
  1340. this.updatePreviewImage();
  1341. this.preloadImages();
  1342. }
  1343. prevPage() {
  1344. if (this.currentPage > 1) {
  1345. this.currentPage -= 1;
  1346. } else {
  1347. this.currentPage = this.pageCount;
  1348. }
  1349. this.updatePreviewImage();
  1350. }
  1351. preloadImages(from = this.currentPage - 1, to = this.currentPage - 1 + PREVIEW_PRELOAD_NUM) {
  1352. if (!this.#images.length) {
  1353. this.#images = new Array(this.regularUrls.length);
  1354. }
  1355. for (let i = from; i < to && i < this.regularUrls.length; i += 1) {
  1356. const preloadImage = new Image();
  1357. preloadImage.src = this.regularUrls[i];
  1358. this.#images[i] = preloadImage;
  1359. }
  1360. }
  1361. onPreviewImageMouseClick = () => {
  1362. this.nextPage();
  1363. };
  1364. onPreviewImageMouseWheel = (mouseWheelEvent) => {
  1365. if (mouseWheelEvent.ctrlKey || mouseWheelEvent.metaKey) {
  1366. mouseWheelEvent.preventDefault();
  1367. if (mouseWheelEvent.originalEvent.deltaY > 0) {
  1368. this.nextPage();
  1369. } else {
  1370. this.prevPage();
  1371. }
  1372. }
  1373. };
  1374. onPreviewImageKeyDown = (keyDownEvent) => {
  1375. if (keyDownEvent.ctrlKey || keyDownEvent.metaKey) {
  1376. keyDownEvent.preventDefault();
  1377. switch (keyDownEvent.key) {
  1378. case "ArrowUp":
  1379. case "ArrowRight":
  1380. this.nextPage();
  1381. break;
  1382. case "ArrowDown":
  1383. case "ArrowLeft":
  1384. this.prevPage();
  1385. break;
  1386. }
  1387. }
  1388. };
  1389. onDownloadImage = (onClickEvent) => {
  1390. onClickEvent.preventDefault();
  1391. const currentImageOriginalUrl = this.originalUrls[this.currentPage - 1];
  1392. const currentImageFilename = currentImageOriginalUrl.split("/").pop() || "illust.jpg";
  1393. downloadIllust({
  1394. url: currentImageOriginalUrl,
  1395. filename: currentImageFilename
  1396. });
  1397. };
  1398. //#endregion
  1399. //#region 预览动图功能
  1400. setUgoira({
  1401. illustId,
  1402. illustElement,
  1403. src,
  1404. // originalSrc,
  1405. mime_type,
  1406. frames
  1407. }) {
  1408. this.reset();
  1409. this.initPreviewWrapper();
  1410. this.illustId = illustId;
  1411. this.illustElement = illustElement;
  1412. illustElement.siblings("svg").css({ "pointer-events": "none" });
  1413. this.#currentUgoiraPlayer = this.createUgoiraPlayer({
  1414. source: src,
  1415. metadata: {
  1416. mime_type,
  1417. frames
  1418. }
  1419. });
  1420. this.bindUgoiraPreviewEvents();
  1421. this.showIllustrationDetails();
  1422. }
  1423. createUgoiraPlayer(options) {
  1424. const canvas = document.createElement("canvas");
  1425. const p = new ugoira_player_default({
  1426. canvas,
  1427. chunkSize: 3e5,
  1428. loop: true,
  1429. autoStart: true,
  1430. debug: false,
  1431. ...options
  1432. });
  1433. p.canvas = canvas;
  1434. return p;
  1435. }
  1436. bindUgoiraPreviewEvents() {
  1437. $(this.#currentUgoiraPlayer).on("frameLoaded", this.onUgoiraFrameLoaded);
  1438. $(document).on("mousemove", this.onMouseMove);
  1439. }
  1440. unbindUgoiraPreviewEvents() {
  1441. $(this.#currentUgoiraPlayer).off();
  1442. $(document).off("mousemove", this.onMouseMove);
  1443. }
  1444. onUgoiraFrameLoaded = (ev, frame) => {
  1445. if (frame !== 0) {
  1446. return;
  1447. }
  1448. this.illustLoaded = true;
  1449. this.previewLoadingElement.hide();
  1450. const canvas = $(this.#currentUgoiraPlayer.canvas);
  1451. this.previewImageElement.after(canvas);
  1452. this.previewImageElement.remove();
  1453. this.previewImageElement = canvas;
  1454. const ugoiraOriginWidth = ev.currentTarget._frameImages[0].width;
  1455. const ugoiraOriginHeight = ev.currentTarget._frameImages[0].height;
  1456. this.#currentIllustSize = [ugoiraOriginWidth, ugoiraOriginHeight];
  1457. this.previewImageElement.attr({
  1458. width: ugoiraOriginWidth,
  1459. height: ugoiraOriginHeight
  1460. });
  1461. this.adjustPreviewWrapper({
  1462. baseOnMousePos: false
  1463. });
  1464. };
  1465. //#endregion
  1466. async showIllustrationDetails() {
  1467. const illustrationDetails = await getIllustrationDetailsWithCache(
  1468. this.illustId
  1469. );
  1470. if (illustrationDetails && illustrationDetails.id === this.illustId) {
  1471. const { aiType, bookmarkId, bookmarkUserTotal, tags } = illustrationDetails;
  1472. const isR18 = checkIsR18(tags);
  1473. const isAi = checkIsAiGenerated(aiType);
  1474. const isAiAssisted = checkIsAiAssisted(tags);
  1475. const illustrationDetailsElements = [];
  1476. const defaultElementCss = {
  1477. height: "20px",
  1478. "border-radius": "12px",
  1479. color: "rgb(245, 245, 245)",
  1480. background: "rgba(0, 0, 0, 0.32)",
  1481. "font-size": "12px",
  1482. "line-height": "1",
  1483. "font-weight": "bold",
  1484. padding: "3px 6px",
  1485. display: "flex",
  1486. "align-items": "center",
  1487. gap: "4px"
  1488. };
  1489. if (isR18) {
  1490. illustrationDetailsElements.push(
  1491. $(document.createElement("div")).css({
  1492. ...defaultElementCss,
  1493. background: "rgb(255, 64, 96)"
  1494. }).text("R-18")
  1495. );
  1496. }
  1497. if (isAi) {
  1498. illustrationDetailsElements.push(
  1499. $(document.createElement("div")).css({
  1500. ...defaultElementCss,
  1501. background: "rgb(29, 78, 216)"
  1502. }).text("AI \u751F\u6210")
  1503. );
  1504. } else if (isAiAssisted) {
  1505. illustrationDetailsElements.push(
  1506. $(document.createElement("div")).css({
  1507. ...defaultElementCss,
  1508. background: "rgb(109, 40, 217)"
  1509. }).text("AI \u8F85\u52A9")
  1510. );
  1511. }
  1512. illustrationDetailsElements.push(
  1513. $(document.createElement("div")).css({
  1514. ...defaultElementCss,
  1515. 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)",
  1516. "margin-right": "auto"
  1517. }).text(`${bookmarkId ? "\u{1F496}" : "\u2764"} ${bookmarkUserTotal}`)
  1518. );
  1519. this.previewWrapperHeader.prepend(illustrationDetailsElements);
  1520. }
  1521. }
  1522. /** 初始化显示预览容器 */
  1523. initPreviewWrapper() {
  1524. this.previewWrapperElement.show();
  1525. this.previewLoadingElement.show();
  1526. this.adjustPreviewWrapper({
  1527. baseOnMousePos: true
  1528. });
  1529. }
  1530. /** 阻止页面缩放事件 */
  1531. preventPageZoom = (mouseWheelEvent) => {
  1532. if (mouseWheelEvent.ctrlKey || mouseWheelEvent.metaKey) {
  1533. mouseWheelEvent.preventDefault();
  1534. }
  1535. };
  1536. /**
  1537. * 根据鼠标移动调整预览容器位置与显隐
  1538. * @param mouseMoveEvent
  1539. */
  1540. onMouseMove = (mouseMoveEvent) => {
  1541. if (mouseMoveEvent.ctrlKey || mouseMoveEvent.metaKey) {
  1542. return;
  1543. }
  1544. const currentElement = $(mouseMoveEvent.target);
  1545. if (currentElement.is(this.illustElement)) {
  1546. this.adjustPreviewWrapper({
  1547. baseOnMousePos: true
  1548. });
  1549. } else {
  1550. this.reset();
  1551. }
  1552. };
  1553. /**
  1554. * 调整预览容器的位置与大小
  1555. * @param `baseOnMousePos` 是否根据当前鼠标所在位置调整
  1556. * @param `illustSize` 作品的实际大小
  1557. */
  1558. adjustPreviewWrapper({
  1559. baseOnMousePos = true
  1560. } = {}) {
  1561. const [mousePosX, mousePosY] = baseOnMousePos ? mouse_monitor_default.mouseAbsPos : this.#prevMousePos;
  1562. this.#prevMousePos = [mousePosX, mousePosY];
  1563. const [illustWidth, illustHeight] = this.#currentIllustSize;
  1564. const screenWidth = document.documentElement.clientWidth;
  1565. const screenHeight = document.documentElement.clientHeight;
  1566. const isShowLeft = mousePosX > screenWidth / 2;
  1567. const isShowTop = mousePosY > screenHeight / 2;
  1568. const illustRatio = illustWidth / illustHeight;
  1569. const screenRestWidth = isShowLeft ? mousePosX - PREVIEW_WRAPPER_DISTANCE_TO_MOUSE : screenWidth - mousePosX - PREVIEW_WRAPPER_DISTANCE_TO_MOUSE;
  1570. const screenRestRatio = screenRestWidth / screenHeight;
  1571. const isFitToFullHeight = screenRestRatio > illustRatio;
  1572. let fitToScreenScale = 1;
  1573. if (this.illustLoaded) {
  1574. if (isFitToFullHeight) {
  1575. fitToScreenScale = Number((screenHeight / illustHeight).toFixed(3));
  1576. } else {
  1577. fitToScreenScale = Number((screenRestWidth / illustWidth).toFixed(3));
  1578. }
  1579. }
  1580. const previewImageFitWidth = Math.floor(illustWidth * fitToScreenScale);
  1581. const previewImageFitHeight = Math.floor(illustHeight * fitToScreenScale);
  1582. const previewWrapperElementPos = {
  1583. left: "",
  1584. right: "",
  1585. top: "",
  1586. bottom: ""
  1587. };
  1588. if (isShowLeft) {
  1589. previewWrapperElementPos.right = `${screenWidth - mousePosX + PREVIEW_WRAPPER_DISTANCE_TO_MOUSE}px`;
  1590. } else {
  1591. previewWrapperElementPos.left = `${mousePosX + PREVIEW_WRAPPER_DISTANCE_TO_MOUSE}px`;
  1592. }
  1593. if (this.illustLoaded) {
  1594. if (isFitToFullHeight) {
  1595. previewWrapperElementPos.top = "0px";
  1596. } else {
  1597. const screenRestHeight = isShowTop ? mousePosY : screenHeight - mousePosY;
  1598. if (previewImageFitHeight > screenRestHeight) {
  1599. if (isShowTop) {
  1600. previewWrapperElementPos.top = "0px";
  1601. } else {
  1602. previewWrapperElementPos.bottom = "0px";
  1603. }
  1604. } else {
  1605. if (isShowTop) {
  1606. previewWrapperElementPos.bottom = `${screenHeight - mousePosY}px`;
  1607. } else {
  1608. previewWrapperElementPos.top = `${mousePosY}px`;
  1609. }
  1610. }
  1611. }
  1612. } else {
  1613. if (isShowTop) {
  1614. previewWrapperElementPos.bottom = `${screenHeight - mousePosY}px`;
  1615. } else {
  1616. previewWrapperElementPos.top = `${mousePosY}px`;
  1617. }
  1618. }
  1619. this.previewWrapperElement.css(previewWrapperElementPos);
  1620. this.previewImageElement.css({
  1621. width: `${previewImageFitWidth}px`,
  1622. height: `${previewImageFitHeight}px`
  1623. });
  1624. }
  1625. };
  1626.  
  1627. // src/i18n/index.ts
  1628. var Texts = {
  1629. install_title: "\u6B22\u8FCE\u4F7F\u7528 Pixiv Previewer (LolipopJ Edition) v",
  1630. upgrade_body: `<div>
  1631. <p style="line-height: 1.6;">
  1632. \u672C\u811A\u672C\u57FA\u4E8E
  1633. <a
  1634. style="color: skyblue"
  1635. href="https://gf.qytechs.cn/zh-CN/scripts/30766-pixiv-previewer"
  1636. target="_blank"
  1637. >Pixiv Previewer</a
  1638. >
  1639. \u4E8C\u6B21\u5F00\u53D1\uFF0C\u65E8\u5728\u6EE1\u8DB3\u5F00\u53D1\u8005\u81EA\u5DF1\u9700\u8981\u7684\u80FD\u529B\u3002\u5982\u679C\u60A8\u6709\u4E0D\u9519\u7684\u60F3\u6CD5\u6216\u5EFA\u8BAE\uFF0C\u8BF7\u524D\u5F80\u539F\u811A\u672C\u7684
  1640. <a
  1641. style="color: skyblue"
  1642. href="https://gf.qytechs.cn/zh-CN/scripts/30766-pixiv-previewer/feedback"
  1643. target="_blank"
  1644. >Greasy Fork镜像 \u53CD\u9988\u9875\u9762</a
  1645. >\u6216\u5F00\u542F\u4E00\u4E2A\u65B0\u7684
  1646. <a
  1647. style="color: skyblue"
  1648. href="https://github.com/Ocrosoft/PixivPreviewer/issues"
  1649. target="_blank"
  1650. >Github \u8BAE\u9898</a
  1651. >\uFF01
  1652. </p>
  1653. </div>
  1654. `,
  1655. setting_language: "\u8BED\u8A00",
  1656. setting_preview: "\u9884\u89C8",
  1657. setting_animePreview: "\u52A8\u56FE\u9884\u89C8",
  1658. setting_sort: "\u641C\u7D22\u9875\u81EA\u52A8\u6392\u5E8F",
  1659. setting_anime: "\u52A8\u56FE\u4E0B\u8F7D\uFF08\u52A8\u56FE\u9884\u89C8\u53CA\u8BE6\u60C5\u9875\u751F\u6548\uFF09",
  1660. setting_origin: "\u9884\u89C8\u65F6\u4F18\u5148\u663E\u793A\u539F\u56FE\uFF08\u6162\uFF09",
  1661. setting_previewDelay: "\u5EF6\u8FDF\u663E\u793A\u9884\u89C8\u56FE\uFF08\u6BEB\u79D2\uFF09",
  1662. setting_previewByKey: "\u4F7F\u7528\u6309\u952E\u63A7\u5236\u9884\u89C8\u56FE\u5C55\u793A\uFF08Ctrl\uFF09",
  1663. setting_previewByKeyHelp: "\u5F00\u542F\u540E\u9F20\u6807\u79FB\u52A8\u5230\u56FE\u7247\u4E0A\u4E0D\u518D\u5C55\u793A\u9884\u89C8\u56FE\uFF0C\u6309\u4E0BCtrl\u952E\u624D\u5C55\u793A\uFF0C\u540C\u65F6\u201C\u5EF6\u8FDF\u663E\u793A\u9884\u89C8\u201D\u8BBE\u7F6E\u9879\u4E0D\u751F\u6548\u3002",
  1664. setting_maxPage: "\u6BCF\u6B21\u6392\u5E8F\u65F6\u7EDF\u8BA1\u7684\u6700\u5927\u9875\u6570",
  1665. setting_hideWork: "\u9690\u85CF\u6536\u85CF\u6570\u5C11\u4E8E\u8BBE\u5B9A\u503C\u7684\u4F5C\u54C1",
  1666. setting_sortOrderByBookmark: "\u6309\u7167\u6536\u85CF\u6570\u6392\u5E8F\u4F5C\u54C1",
  1667. setting_hideAiWork: "\u6392\u5E8F\u65F6\u9690\u85CF AI \u751F\u6210\u4F5C\u54C1",
  1668. setting_hideAiAssistedWork: "\u6392\u5E8F\u65F6\u9690\u85CF AI \u8F85\u52A9\u4F5C\u54C1",
  1669. setting_hideFav: "\u6392\u5E8F\u65F6\u9690\u85CF\u5DF2\u6536\u85CF\u7684\u4F5C\u54C1",
  1670. setting_hideFollowed: "\u6392\u5E8F\u65F6\u9690\u85CF\u5DF2\u5173\u6CE8\u753B\u5E08\u4F5C\u54C1",
  1671. setting_hideByTag: "\u6392\u5E8F\u65F6\u9690\u85CF\u6307\u5B9A\u6807\u7B7E\u7684\u4F5C\u54C1",
  1672. setting_hideByTagPlaceholder: "\u8F93\u5165\u6807\u7B7E\u540D\uFF0C\u591A\u4E2A\u6807\u7B7E\u7528','\u5206\u9694",
  1673. setting_clearFollowingCache: "\u6E05\u9664\u7F13\u5B58",
  1674. setting_clearFollowingCacheHelp: "\u5173\u6CE8\u753B\u5E08\u4FE1\u606F\u4F1A\u5728\u672C\u5730\u4FDD\u5B58\u4E00\u5929\uFF0C\u5982\u679C\u5E0C\u671B\u7ACB\u5373\u66F4\u65B0\uFF0C\u8BF7\u70B9\u51FB\u6E05\u9664\u7F13\u5B58",
  1675. setting_followingCacheCleared: "\u5DF2\u6E05\u9664\u7F13\u5B58\uFF0C\u8BF7\u5237\u65B0\u9875\u9762\u3002",
  1676. setting_blank: "\u4F7F\u7528\u65B0\u6807\u7B7E\u9875\u6253\u5F00\u4F5C\u54C1\u8BE6\u60C5\u9875",
  1677. setting_turnPage: "\u4F7F\u7528\u952E\u76D8\u2190\u2192\u8FDB\u884C\u7FFB\u9875\uFF08\u6392\u5E8F\u540E\u7684\u641C\u7D22\u9875\uFF09",
  1678. setting_save: "\u4FDD\u5B58\u8BBE\u7F6E",
  1679. setting_reset: "\u91CD\u7F6E\u811A\u672C",
  1680. setting_resetHint: "\u8FD9\u4F1A\u5220\u9664\u6240\u6709\u8BBE\u7F6E\uFF0C\u76F8\u5F53\u4E8E\u91CD\u65B0\u5B89\u88C5\u811A\u672C\uFF0C\u786E\u5B9A\u8981\u91CD\u7F6E\u5417\uFF1F",
  1681. setting_novelSort: "\u5C0F\u8BF4\u6392\u5E8F",
  1682. setting_novelMaxPage: "\u5C0F\u8BF4\u6392\u5E8F\u65F6\u7EDF\u8BA1\u7684\u6700\u5927\u9875\u6570",
  1683. setting_novelHideWork: "\u9690\u85CF\u6536\u85CF\u6570\u5C11\u4E8E\u8BBE\u5B9A\u503C\u7684\u4F5C\u54C1",
  1684. setting_novelHideFav: "\u6392\u5E8F\u65F6\u9690\u85CF\u5DF2\u6536\u85CF\u7684\u4F5C\u54C1",
  1685. sort_noWork: "\u6CA1\u6709\u53EF\u4EE5\u663E\u793A\u7684\u4F5C\u54C1\uFF08\u9690\u85CF\u4E86 %1 \u4E2A\u4F5C\u54C1\uFF09",
  1686. sort_getWorks: "\u6B63\u5728\u83B7\u53D6\u7B2C %1/%2 \u9875\u4F5C\u54C1",
  1687. sort_getBookmarkCount: "\u83B7\u53D6\u6536\u85CF\u6570\uFF1A%1/%2",
  1688. sort_getPublicFollowing: "\u83B7\u53D6\u516C\u5F00\u5173\u6CE8\u753B\u5E08",
  1689. sort_getPrivateFollowing: "\u83B7\u53D6\u79C1\u6709\u5173\u6CE8\u753B\u5E08",
  1690. sort_filtering: "\u8FC7\u6EE4%1\u6536\u85CF\u91CF\u4F4E\u4E8E%2\u7684\u4F5C\u54C1",
  1691. sort_filteringHideFavorite: "\u5DF2\u6536\u85CF\u548C",
  1692. sort_fullSizeThumb: "\u5168\u5C3A\u5BF8\u7F29\u7565\u56FE\uFF08\u641C\u7D22\u9875\u3001\u7528\u6237\u9875\uFF09",
  1693. label_sort: "\u6392\u5E8F",
  1694. label_sorting: "\u6392\u5E8F\u4E2D",
  1695. label_nextPage: "\u4E0B\u4E00\u9875"
  1696. };
  1697. var i18n_default = Texts;
  1698.  
  1699. // src/icons/heart.svg
  1700. 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>';
  1701.  
  1702. // src/icons/heart-filled.svg
  1703. 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>';
  1704.  
  1705. // src/icons/play.svg
  1706. 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>';
  1707.  
  1708. // src/utils/promise.ts
  1709. var execLimitConcurrentPromises = async (promises, limit = 48) => {
  1710. const results = [];
  1711. let index = 0;
  1712. const executeNext = async () => {
  1713. if (index >= promises.length) return Promise.resolve();
  1714. const currentIndex = index++;
  1715. const result = await promises[currentIndex]();
  1716. results[currentIndex] = result;
  1717. return await executeNext();
  1718. };
  1719. const initialPromises = Array.from(
  1720. { length: Math.min(limit, promises.length) },
  1721. () => executeNext()
  1722. );
  1723. await Promise.all(initialPromises);
  1724. return results;
  1725. };
  1726.  
  1727. // src/features/sort.ts
  1728. var TAG_PAGE_ILLUSTRATION_LIST_SELECTOR = "ul.sc-98699d11-1.hHLaTl";
  1729. var BOOKMARK_USER_PAGE_ILLUSTRATION_LIST_SELECTOR = "ul.sc-bf8cea3f-1.bCxfvI";
  1730. var USER_TYPE_ARTWORKS_PER_PAGE = 48;
  1731. var isInitialized2 = false;
  1732. var loadIllustSort = (options) => {
  1733. if (isInitialized2) return;
  1734. const {
  1735. pageCount: optionPageCount,
  1736. favFilter: optionFavFilter,
  1737. orderType = 0 /* BY_BOOKMARK_COUNT */,
  1738. hideFavorite = false,
  1739. hideByTag = false,
  1740. hideByTagList: hideByTagListString,
  1741. aiFilter = false,
  1742. aiAssistedFilter = false
  1743. // csrfToken,
  1744. } = options;
  1745. let pageCount = Number(optionPageCount), favFilter = Number(optionFavFilter);
  1746. if (pageCount <= 0) {
  1747. pageCount = g_defaultSettings.pageCount;
  1748. }
  1749. if (favFilter < 0) {
  1750. favFilter = g_defaultSettings.favFilter;
  1751. }
  1752. const hideByTagList = hideByTagListString.split(",").map((tag) => tag.trim().toLowerCase()).filter((tag) => !!tag);
  1753. if (aiAssistedFilter) {
  1754. hideByTagList.push(...AI_ASSISTED_TAGS);
  1755. }
  1756. class IllustSorter {
  1757. type;
  1758. illustrations;
  1759. sorting = false;
  1760. nextSortPage;
  1761. listElement = $();
  1762. progressElement = $();
  1763. progressText = $();
  1764. sortButtonElement = $(`#${SORT_BUTTON_ID}`);
  1765. reset({ type }) {
  1766. try {
  1767. this.type = type;
  1768. this.illustrations = [];
  1769. this.sorting = false;
  1770. this.nextSortPage = void 0;
  1771. this.listElement = getIllustrationsListDom(type);
  1772. this.progressElement?.remove();
  1773. this.progressElement = $(document.createElement("div")).attr({
  1774. id: "pp-sort-progress"
  1775. }).css({
  1776. width: "100%",
  1777. display: "flex",
  1778. "flex-direction": "column",
  1779. "align-items": "center",
  1780. "justify-content": "center",
  1781. gap: "6px"
  1782. }).append(
  1783. $(new Image(96, 96)).attr({
  1784. id: "sort-progress__loading",
  1785. src: g_loadingImage
  1786. }).css({
  1787. "border-radius": "50%"
  1788. })
  1789. ).prependTo(this.listElement).hide();
  1790. this.progressText = $(document.createElement("div")).attr({
  1791. id: "pp-sort-progress__text"
  1792. }).css({
  1793. "text-align": "center",
  1794. "font-size": "16px",
  1795. "font-weight": "bold",
  1796. color: "initial"
  1797. }).appendTo(this.progressElement);
  1798. this.sortButtonElement.text(i18n_default.label_sort);
  1799. } catch (error) {
  1800. iLog.e(`An error occurred while resetting sorter:`, error);
  1801. throw new Error(error);
  1802. }
  1803. }
  1804. async sort({
  1805. type,
  1806. api,
  1807. searchParams
  1808. }) {
  1809. this.sorting = true;
  1810. iLog.i("Start to sort illustrations.");
  1811. this.sortButtonElement.text(i18n_default.label_sorting);
  1812. try {
  1813. let illustrations = [];
  1814. const startPage = Number(searchParams.get("p") ?? 1);
  1815. this.nextSortPage = startPage + pageCount;
  1816. for (let page = startPage; page < startPage + pageCount; page += 1) {
  1817. searchParams.set("p", String(page));
  1818. if ([
  1819. 5 /* USER_ARTWORK */,
  1820. 6 /* USER_ILLUST */,
  1821. 7 /* USER_MANGA */
  1822. ].includes(type)) {
  1823. searchParams.set("is_first_page", page > 1 ? "0" : "1");
  1824. searchParams.delete("ids[]");
  1825. const userId = searchParams.get("user_id");
  1826. const userIllustrations = await getUserIllustrationsWithCache(
  1827. userId,
  1828. {
  1829. onRequesting: () => this.setProgress(`Getting illustrations of current user...`)
  1830. }
  1831. );
  1832. const fromIndex = (page - 1) * USER_TYPE_ARTWORKS_PER_PAGE;
  1833. const toIndex = page * USER_TYPE_ARTWORKS_PER_PAGE;
  1834. switch (type) {
  1835. case 5 /* USER_ARTWORK */:
  1836. userIllustrations.artworks.slice(fromIndex, toIndex).forEach((id) => searchParams.append("ids[]", id));
  1837. break;
  1838. case 6 /* USER_ILLUST */:
  1839. userIllustrations.illusts.slice(fromIndex, toIndex).forEach((id) => searchParams.append("ids[]", id));
  1840. break;
  1841. case 7 /* USER_MANGA */:
  1842. userIllustrations.manga.slice(fromIndex, toIndex).forEach((id) => searchParams.append("ids[]", id));
  1843. break;
  1844. }
  1845. } else if ([8 /* USER_BOOKMARK */].includes(type)) {
  1846. searchParams.set(
  1847. "offset",
  1848. String((page - 1) * USER_TYPE_ARTWORKS_PER_PAGE)
  1849. );
  1850. }
  1851. this.setProgress(`Getting illustration list of page ${page} ...`);
  1852. const requestUrl = `${api}?${searchParams}`;
  1853. const getIllustRes = await requestWithRetry({
  1854. url: requestUrl,
  1855. onRetry: (response, retryTimes) => {
  1856. iLog.w(
  1857. `Get illustration list through \`${requestUrl}\` failed:`,
  1858. response,
  1859. `${retryTimes} times retrying...`
  1860. );
  1861. this.setProgress(
  1862. `Retry to get illustration list of page ${page} (${retryTimes} times)...`
  1863. );
  1864. }
  1865. });
  1866. const extractedIllustrations = getIllustrationsFromResponse(
  1867. type,
  1868. getIllustRes.response
  1869. );
  1870. illustrations = illustrations.concat(extractedIllustrations);
  1871. }
  1872. const getDetailedIllustrationPromises = [];
  1873. for (let i = 0; i < illustrations.length; i += 1) {
  1874. const illustration = illustrations[i];
  1875. const illustrationId = illustration.id;
  1876. const illustrationAuthorId = illustration.userId;
  1877. if (String(illustrationAuthorId) === "0") {
  1878. continue;
  1879. }
  1880. getDetailedIllustrationPromises.push(async () => {
  1881. this.setProgress(
  1882. `Getting details of ${i + 1}/${illustrations.length} illustration...`
  1883. );
  1884. const illustrationDetails = await getIllustrationDetailsWithCache(
  1885. illustrationId,
  1886. true
  1887. );
  1888. return {
  1889. ...illustration,
  1890. bookmarkUserTotal: illustrationDetails?.bookmarkUserTotal ?? -1
  1891. };
  1892. });
  1893. }
  1894. const detailedIllustrations = await execLimitConcurrentPromises(
  1895. getDetailedIllustrationPromises
  1896. );
  1897. iLog.d("Queried detailed illustrations:", detailedIllustrations);
  1898. this.setProgress("Filtering illustrations...");
  1899. const filteredIllustrations = detailedIllustrations.filter(
  1900. (illustration) => {
  1901. if (hideFavorite && illustration.bookmarkData) {
  1902. return false;
  1903. }
  1904. if (aiFilter && illustration.aiType === 2 /* AI */) {
  1905. return false;
  1906. }
  1907. if ((hideByTag || aiAssistedFilter) && hideByTagList.length) {
  1908. for (const tag of illustration.tags) {
  1909. if (hideByTagList.includes(tag.toLowerCase())) {
  1910. return false;
  1911. }
  1912. }
  1913. }
  1914. return illustration.bookmarkUserTotal >= favFilter;
  1915. }
  1916. );
  1917. this.setProgress("Sorting filtered illustrations...");
  1918. const sortedIllustrations = orderType === 0 /* BY_BOOKMARK_COUNT */ ? filteredIllustrations.sort(
  1919. (a, b) => b.bookmarkUserTotal - a.bookmarkUserTotal
  1920. ) : filteredIllustrations;
  1921. iLog.d("Filtered and sorted illustrations:", sortedIllustrations);
  1922. iLog.i("Sort illustrations successfully.");
  1923. this.illustrations = sortedIllustrations;
  1924. this.showIllustrations();
  1925. } catch (error) {
  1926. iLog.e("Sort illustrations failed:", error);
  1927. }
  1928. this.hideProgress();
  1929. this.sorting = false;
  1930. this.sortButtonElement.text(i18n_default.label_sort);
  1931. }
  1932. setProgress(text) {
  1933. this.progressText.text(text);
  1934. this.progressElement.show();
  1935. }
  1936. hideProgress() {
  1937. this.progressText.text("");
  1938. this.progressElement.hide();
  1939. }
  1940. showIllustrations() {
  1941. const fragment = document.createDocumentFragment();
  1942. for (const {
  1943. aiType,
  1944. alt,
  1945. bookmarkData,
  1946. bookmarkUserTotal,
  1947. id,
  1948. illustType,
  1949. pageCount: pageCount2,
  1950. profileImageUrl,
  1951. tags,
  1952. title,
  1953. url,
  1954. userId,
  1955. userName
  1956. } of this.illustrations) {
  1957. const isR18 = checkIsR18(tags);
  1958. const isUgoira = checkIsUgoira(illustType);
  1959. const isAi = checkIsAiGenerated(aiType);
  1960. const isAiAssisted = checkIsAiAssisted(tags);
  1961. const listItem = document.createElement("li");
  1962. const container = document.createElement("div");
  1963. container.style = "width: 184px;";
  1964. const illustrationAnchor = document.createElement("a");
  1965. illustrationAnchor.setAttribute("data-gtm-value", id);
  1966. illustrationAnchor.setAttribute("data-gtm-user-id", userId);
  1967. illustrationAnchor.href = `/artworks/${id}`;
  1968. illustrationAnchor.target = "_blank";
  1969. illustrationAnchor.rel = "external";
  1970. illustrationAnchor.style = "display: block; position: relative; width: 184px;";
  1971. const illustrationImageWrapper = document.createElement("div");
  1972. illustrationImageWrapper.style = "position: relative; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;";
  1973. const illustrationImage = document.createElement("img");
  1974. illustrationImage.src = url;
  1975. illustrationImage.alt = alt;
  1976. illustrationImage.style = "object-fit: cover; object-position: center center; width: 100%; height: 100%; border-radius: 4px; background-color: rgb(31, 31, 31);";
  1977. const ugoriaSvg = document.createElement("div");
  1978. ugoriaSvg.style = "position: absolute;";
  1979. ugoriaSvg.innerHTML = play_default;
  1980. const illustrationMeta = document.createElement("div");
  1981. 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;";
  1982. illustrationMeta.innerHTML = `
  1983. ${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>' : ""}
  1984. ${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 \u751F\u6210</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 \u8F85\u52A9</div>' : ""}
  1985. ${pageCount2 > 1 ? `
  1986. <div style="margin-left: auto;">
  1987. <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;">
  1988. ${page_default}
  1989. <span>${pageCount2}</span>
  1990. </div>
  1991. </div>` : ""}
  1992. `;
  1993. const illustrationToolbar = document.createElement("div");
  1994. 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;";
  1995. illustrationToolbar.innerHTML = `
  1996. <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;">\u2764 ${bookmarkUserTotal}</div>
  1997. <div style="margin-left: auto; display: none;">${bookmarkData ? heart_filled_default : heart_default}</div>
  1998. `;
  1999. const illustrationTitle = document.createElement("div");
  2000. illustrationTitle.innerHTML = title;
  2001. 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;";
  2002. const illustrationAuthor = document.createElement("a");
  2003. illustrationAuthor.setAttribute("data-gtm-value", userId);
  2004. illustrationAuthor.href = `/users/${userId}`;
  2005. illustrationAuthor.target = "_blank";
  2006. illustrationAuthor.rel = "external";
  2007. illustrationAuthor.style = "display: flex; align-items: center; margin-top: 4px;";
  2008. illustrationAuthor.innerHTML = `
  2009. <img src="${profileImageUrl}" alt="${userName}" style="object-fit: cover; object-position: center top; width: 24px; height: 24px; border-radius: 50%; margin-right: 4px;">
  2010. <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>
  2011. `;
  2012. illustrationImageWrapper.appendChild(illustrationImage);
  2013. if (isUgoira) illustrationImageWrapper.appendChild(ugoriaSvg);
  2014. illustrationAnchor.appendChild(illustrationImageWrapper);
  2015. illustrationAnchor.appendChild(illustrationMeta);
  2016. illustrationAnchor.appendChild(illustrationToolbar);
  2017. illustrationAnchor.appendChild(illustrationTitle);
  2018. container.appendChild(illustrationAnchor);
  2019. container.appendChild(illustrationAuthor);
  2020. listItem.appendChild(container);
  2021. fragment.appendChild(listItem);
  2022. }
  2023. if ([
  2024. 3 /* BOOKMARK_NEW */,
  2025. 4 /* BOOKMARK_NEW_R18 */,
  2026. 5 /* USER_ARTWORK */,
  2027. 6 /* USER_ILLUST */,
  2028. 7 /* USER_MANGA */,
  2029. 8 /* USER_BOOKMARK */
  2030. ].includes(this.type)) {
  2031. this.listElement.css({
  2032. gap: "24px"
  2033. });
  2034. }
  2035. this.listElement.find("li").remove();
  2036. this.listElement.append(fragment);
  2037. }
  2038. }
  2039. const illustSorter = new IllustSorter();
  2040. window.addEventListener(SORT_EVENT_NAME, () => {
  2041. if (illustSorter.sorting) {
  2042. iLog.w("Current is in sorting progress.");
  2043. return;
  2044. }
  2045. const url = new URL(location.href);
  2046. const { pathname, searchParams } = url;
  2047. const {
  2048. type,
  2049. api,
  2050. searchParams: defaultSearchParams
  2051. } = getSortOptionsFromPathname(pathname);
  2052. if (type === void 0) {
  2053. iLog.w("Current page doesn't support sorting illustrations.");
  2054. return;
  2055. }
  2056. const mergedSearchParams = new URLSearchParams(defaultSearchParams);
  2057. searchParams.forEach((value, key) => {
  2058. mergedSearchParams.set(key, value);
  2059. });
  2060. illustSorter.reset({
  2061. type
  2062. });
  2063. illustSorter.sort({
  2064. type,
  2065. api,
  2066. searchParams: mergedSearchParams
  2067. });
  2068. });
  2069. window.addEventListener(SORT_NEXT_PAGE_EVENT_NAME, () => {
  2070. const url = new URL(location.href);
  2071. const { origin, pathname, searchParams } = url;
  2072. const currentPage = Number(searchParams.get("p") ?? 1);
  2073. let nextPage = currentPage + 1;
  2074. if (illustSorter.listElement?.length && illustSorter.nextSortPage) {
  2075. iLog.i(
  2076. "Illustrations in current page are sorted, jump to next available page..."
  2077. );
  2078. nextPage = illustSorter.nextSortPage;
  2079. }
  2080. searchParams.set("p", String(nextPage));
  2081. location.href = `${origin}${pathname}?${searchParams}`;
  2082. });
  2083. isInitialized2 = true;
  2084. };
  2085. function getIllustrationsListDom(type) {
  2086. let dom;
  2087. if ([
  2088. 0 /* TAG_ARTWORK */,
  2089. 1 /* TAG_ILLUST */,
  2090. 2 /* TAG_MANGA */
  2091. ].includes(type)) {
  2092. dom = $(TAG_PAGE_ILLUSTRATION_LIST_SELECTOR);
  2093. if (!dom.length) {
  2094. dom = $("section").find("ul").last();
  2095. }
  2096. } else if ([
  2097. 3 /* BOOKMARK_NEW */,
  2098. 4 /* BOOKMARK_NEW_R18 */,
  2099. 8 /* USER_BOOKMARK */
  2100. ].includes(type)) {
  2101. dom = $(BOOKMARK_USER_PAGE_ILLUSTRATION_LIST_SELECTOR);
  2102. if (!dom.length) {
  2103. dom = $("section").find("ul").last();
  2104. }
  2105. } else if ([
  2106. 5 /* USER_ARTWORK */,
  2107. 6 /* USER_ILLUST */,
  2108. 7 /* USER_MANGA */
  2109. ].includes(type)) {
  2110. dom = $(BOOKMARK_USER_PAGE_ILLUSTRATION_LIST_SELECTOR);
  2111. if (!dom.length) {
  2112. dom = $(".__top_side_menu_body").find("ul").last();
  2113. }
  2114. }
  2115. if (dom.length) {
  2116. return dom;
  2117. } else {
  2118. throw new Error(
  2119. `Illustrations list DOM not found in current page: ${location.href}. Please create a new issue here: ${"https://github.com/LolipopJ/PixivPreviewer/issues"}`
  2120. );
  2121. }
  2122. }
  2123. function getSortOptionsFromPathname(pathname) {
  2124. let type;
  2125. let api;
  2126. let defaultSearchParams;
  2127. let match;
  2128. if (match = pathname.match(/\/tags\/(.+)\/(artworks|illustrations|manga)$/)) {
  2129. const tagName = match[1];
  2130. const filterType = match[2];
  2131. switch (filterType) {
  2132. case "artworks":
  2133. type = 0 /* TAG_ARTWORK */;
  2134. api = `/ajax/search/artworks/${tagName}`;
  2135. defaultSearchParams = `word=${tagName}&order=date_d&mode=all&p=1&csw=0&s_mode=s_tag_full&type=all&lang=zh`;
  2136. break;
  2137. case "illustrations":
  2138. type = 1 /* TAG_ILLUST */;
  2139. api = `/ajax/search/illustrations/${tagName}`;
  2140. defaultSearchParams = `word=${tagName}&order=date_d&mode=all&p=1&csw=0&s_mode=s_tag_full&type=illust_and_ugoira&lang=zh`;
  2141. break;
  2142. case "manga":
  2143. type = 2 /* TAG_MANGA */;
  2144. api = `/ajax/search/manga/${tagName}`;
  2145. defaultSearchParams = `word=${tagName}&order=date_d&mode=all&p=1&csw=0&s_mode=s_tag_full&type=manga&lang=zh`;
  2146. break;
  2147. }
  2148. } else if (match = pathname.match(/\/bookmark_new_illust(_r18)?\.php$/)) {
  2149. const isR18 = !!match[1];
  2150. api = "/ajax/follow_latest/illust";
  2151. if (isR18) {
  2152. type = 3 /* BOOKMARK_NEW */;
  2153. defaultSearchParams = "mode=r18&lang=zh";
  2154. } else {
  2155. type = 4 /* BOOKMARK_NEW_R18 */;
  2156. defaultSearchParams = "mode=all&lang=zh";
  2157. }
  2158. } else if (match = pathname.match(/\/users\/(\d+)\/bookmarks\/artworks$/)) {
  2159. const userId = match[1];
  2160. type = 8 /* USER_BOOKMARK */;
  2161. api = `/ajax/user/${userId}/illusts/bookmarks`;
  2162. defaultSearchParams = `tag=&offset=0&limit=${USER_TYPE_ARTWORKS_PER_PAGE}&rest=show&lang=zh`;
  2163. } else if (match = pathname.match(/\/users\/(\d+)\/(artworks|illustrations|manga)$/)) {
  2164. const userId = match[1];
  2165. const filterType = match[2];
  2166. api = `/ajax/user/${userId}/profile/illusts`;
  2167. switch (filterType) {
  2168. case "artworks":
  2169. type = 5 /* USER_ARTWORK */;
  2170. defaultSearchParams = `work_category=illustManga&is_first_page=1&sensitiveFilterMode=userSetting&user_id=${userId}&lang=zh`;
  2171. break;
  2172. case "illustrations":
  2173. type = 6 /* USER_ILLUST */;
  2174. defaultSearchParams = `work_category=illust&is_first_page=1&sensitiveFilterMode=userSetting&user_id=${userId}&lang=zh`;
  2175. break;
  2176. case "manga":
  2177. type = 7 /* USER_MANGA */;
  2178. defaultSearchParams = `work_category=manga&is_first_page=1&sensitiveFilterMode=userSetting&user_id=${userId}&lang=zh`;
  2179. break;
  2180. }
  2181. }
  2182. return {
  2183. type,
  2184. api,
  2185. searchParams: new URLSearchParams(defaultSearchParams)
  2186. };
  2187. }
  2188. function getIllustrationsFromResponse(type, response) {
  2189. if (type === 0 /* TAG_ARTWORK */) {
  2190. return response.body.illustManga.data ?? [];
  2191. } else if (type === 1 /* TAG_ILLUST */) {
  2192. return response.body.illust.data ?? [];
  2193. } else if (type === 2 /* TAG_MANGA */) {
  2194. return response.body.manga.data ?? [];
  2195. } else if ([3 /* BOOKMARK_NEW */, 4 /* BOOKMARK_NEW_R18 */].includes(
  2196. type
  2197. )) {
  2198. return response.body.thumbnails.illust ?? [];
  2199. } else if ([
  2200. 5 /* USER_ARTWORK */,
  2201. 6 /* USER_ILLUST */,
  2202. 7 /* USER_MANGA */,
  2203. 8 /* USER_BOOKMARK */
  2204. ].includes(type)) {
  2205. return Object.values(
  2206. response.body.works
  2207. );
  2208. }
  2209. return [];
  2210. }
  2211.  
  2212. // src/utils/setting.ts
  2213. var SETTINGS_KEY = "PIXIV_PREVIEWER_L_SETTINGS";
  2214. var toggleSettingBooleanValue = (key) => {
  2215. const settings = getSettings();
  2216. const currentValue = Boolean(settings[key] ?? g_defaultSettings[key]);
  2217. const newValue = !currentValue;
  2218. GM_setValue(SETTINGS_KEY, { ...settings, [key]: newValue });
  2219. };
  2220. var setSettingStringValue = (key, label, {
  2221. parseValue = (v) => v,
  2222. onSet
  2223. }) => {
  2224. const settings = getSettings();
  2225. const currentValue = settings[key] ?? g_defaultSettings[key];
  2226. const newValue = prompt(label, String(currentValue));
  2227. if (newValue !== null) {
  2228. const savedValue = parseValue(newValue);
  2229. GM_setValue(SETTINGS_KEY, { ...settings, [key]: savedValue });
  2230. onSet?.(savedValue);
  2231. }
  2232. };
  2233. var setSettingValue = (key, value) => {
  2234. const settings = getSettings();
  2235. const newValue = value ?? g_defaultSettings[key];
  2236. GM_setValue(SETTINGS_KEY, { ...settings, [key]: newValue });
  2237. };
  2238. var getSettings = () => {
  2239. return GM_getValue(SETTINGS_KEY) ?? g_defaultSettings;
  2240. };
  2241. var resetSettings = () => {
  2242. GM_setValue(SETTINGS_KEY, g_defaultSettings);
  2243. };
  2244.  
  2245. // src/index.ts
  2246. var g_csrfToken = "";
  2247. var g_pageType;
  2248. var g_settings;
  2249. var Pages = {
  2250. [0 /* Search */]: {
  2251. PageTypeString: "SearchPage",
  2252. CheckUrl: function(url) {
  2253. return /^https?:\/\/www.pixiv.net(\/en)?\/tags\/.+\/(artworks|illustrations|manga)/.test(
  2254. url
  2255. );
  2256. },
  2257. GetToolBar: function() {
  2258. return findToolbarCommon();
  2259. }
  2260. },
  2261. [1 /* BookMarkNew */]: {
  2262. PageTypeString: "BookMarkNewPage",
  2263. CheckUrl: function(url) {
  2264. return /^https:\/\/www.pixiv.net(\/en)?\/bookmark_new_illust(_r18)?.php.*/.test(
  2265. url
  2266. );
  2267. },
  2268. GetToolBar: function() {
  2269. return findToolbarCommon();
  2270. }
  2271. },
  2272. [2 /* Discovery */]: {
  2273. PageTypeString: "DiscoveryPage",
  2274. CheckUrl: function(url) {
  2275. return /^https?:\/\/www.pixiv.net(\/en)?\/discovery.*/.test(url);
  2276. },
  2277. GetToolBar: function() {
  2278. return findToolbarCommon();
  2279. }
  2280. },
  2281. [3 /* Member */]: {
  2282. PageTypeString: "MemberPage/MemberIllustPage/MemberBookMark",
  2283. CheckUrl: function(url) {
  2284. return /^https?:\/\/www.pixiv.net(\/en)?\/users\/\d+/.test(url);
  2285. },
  2286. GetToolBar: function() {
  2287. return findToolbarCommon();
  2288. }
  2289. },
  2290. [4 /* Home */]: {
  2291. PageTypeString: "HomePage",
  2292. CheckUrl: function(url) {
  2293. 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);
  2294. },
  2295. GetToolBar: function() {
  2296. return findToolbarCommon();
  2297. }
  2298. },
  2299. [5 /* Ranking */]: {
  2300. PageTypeString: "RankingPage",
  2301. CheckUrl: function(url) {
  2302. return /^https?:\/\/www.pixiv.net(\/en)?\/ranking.php.*/.test(url);
  2303. },
  2304. GetToolBar: function() {
  2305. return findToolbarOld();
  2306. }
  2307. },
  2308. [6 /* NewIllust */]: {
  2309. PageTypeString: "NewIllustPage",
  2310. CheckUrl: function(url) {
  2311. return /^https?:\/\/www.pixiv.net(\/en)?\/new_illust.php.*/.test(url);
  2312. },
  2313. GetToolBar: function() {
  2314. return findToolbarCommon();
  2315. }
  2316. },
  2317. [7 /* R18 */]: {
  2318. PageTypeString: "R18Page",
  2319. CheckUrl: function(url) {
  2320. return /^https?:\/\/www.pixiv.net(\/en)?\/cate_r18.php.*/.test(url);
  2321. },
  2322. GetToolBar: function() {
  2323. return findToolbarCommon();
  2324. }
  2325. },
  2326. [8 /* BookMark */]: {
  2327. PageTypeString: "BookMarkPage",
  2328. CheckUrl: function(url) {
  2329. return /^https:\/\/www.pixiv.net(\/en)?\/bookmark.php\/?$/.test(url);
  2330. },
  2331. GetToolBar: function() {
  2332. return findToolbarOld();
  2333. }
  2334. },
  2335. [9 /* Stacc */]: {
  2336. PageTypeString: "StaccPage",
  2337. CheckUrl: function(url) {
  2338. return /^https:\/\/www.pixiv.net(\/en)?\/stacc.*/.test(url);
  2339. },
  2340. GetToolBar: function() {
  2341. return findToolbarOld();
  2342. }
  2343. },
  2344. [10 /* Artwork */]: {
  2345. PageTypeString: "ArtworkPage",
  2346. CheckUrl: function(url) {
  2347. return /^https:\/\/www.pixiv.net(\/en)?\/artworks\/.*/.test(url);
  2348. },
  2349. GetToolBar: function() {
  2350. return findToolbarCommon();
  2351. }
  2352. },
  2353. [11 /* NovelSearch */]: {
  2354. PageTypeString: "NovelSearchPage",
  2355. CheckUrl: function(url) {
  2356. return /^https:\/\/www.pixiv.net(\/en)?\/tags\/.*\/novels/.test(url);
  2357. },
  2358. GetToolBar: function() {
  2359. return findToolbarCommon();
  2360. }
  2361. },
  2362. [12 /* SearchTop */]: {
  2363. PageTypeString: "SearchTopPage",
  2364. CheckUrl: function(url) {
  2365. return /^https?:\/\/www.pixiv.net(\/en)?\/tags\/[^/*]/.test(url);
  2366. },
  2367. GetToolBar: function() {
  2368. return findToolbarCommon();
  2369. }
  2370. }
  2371. };
  2372. function findToolbarCommon() {
  2373. const toolbar = $(`#${TOOLBAR_ID}`);
  2374. if (toolbar.length > 0) {
  2375. return toolbar.get(0);
  2376. }
  2377. $("body").append(
  2378. `<div id="${TOOLBAR_ID}" style="position: fixed; right: 28px; bottom: 96px;"></div>`
  2379. );
  2380. return $(`#${TOOLBAR_ID}`).get(0);
  2381. }
  2382. function findToolbarOld() {
  2383. return $("._toolmenu").get(0);
  2384. }
  2385. function showSearchLinksForDeletedArtworks() {
  2386. const searchEngines = [
  2387. { name: "Google", url: "https://www.google.com/search?q=" },
  2388. { name: "Bing", url: "https://www.bing.com/search?q=" },
  2389. { name: "Baidu", url: "https://www.baidu.com/s?wd=" }
  2390. ];
  2391. const spans = document.querySelectorAll("span[to]");
  2392. spans.forEach((span) => {
  2393. const artworkPath = span.getAttribute("to");
  2394. if (span.textContent.trim() === "-----" && artworkPath.startsWith("/artworks/")) {
  2395. const keyword = `pixiv "${artworkPath.slice(10)}"`;
  2396. const container = document.createElement("span");
  2397. container.className = span.className;
  2398. searchEngines.forEach((engine, i) => {
  2399. const link = document.createElement("a");
  2400. link.href = engine.url + encodeURIComponent(keyword);
  2401. link.textContent = engine.name;
  2402. link.target = "_blank";
  2403. container.appendChild(link);
  2404. if (i < searchEngines.length - 1) {
  2405. container.appendChild(document.createTextNode(" | "));
  2406. }
  2407. });
  2408. span.parentNode.replaceChild(container, span);
  2409. }
  2410. });
  2411. }
  2412. var menuIds = [];
  2413. var registerSettingsMenu = () => {
  2414. const settings = getSettings();
  2415. for (const menuId of menuIds) {
  2416. GM_unregisterMenuCommand(menuId);
  2417. }
  2418. menuIds = [];
  2419. menuIds.push(
  2420. GM_registerMenuCommand(
  2421. `\u{1F5BC}\uFE0F \u63D2\u753B\u4F5C\u54C1\u9884\u89C8 ${settings.enablePreview ? "\u2705" : "\u274C"}`,
  2422. () => {
  2423. toggleSettingBooleanValue("enablePreview");
  2424. registerSettingsMenu();
  2425. }
  2426. ),
  2427. GM_registerMenuCommand(
  2428. `\u{1F3A6} \u52A8\u56FE\u4F5C\u54C1\u9884\u89C8 ${settings.enableAnimePreview ? "\u2705" : "\u274C"}`,
  2429. () => {
  2430. toggleSettingBooleanValue("enableAnimePreview");
  2431. registerSettingsMenu();
  2432. }
  2433. ),
  2434. GM_registerMenuCommand(
  2435. `\u{1F557} \u5EF6\u8FDF ${settings.previewDelay} \u6BEB\u79D2\u663E\u793A\u9884\u89C8\u56FE`,
  2436. () => {
  2437. setSettingStringValue("previewDelay", "\u5EF6\u8FDF\u663E\u793A\u9884\u89C8\u56FE\u65F6\u95F4\uFF08\u6BEB\u79D2\uFF09", {
  2438. parseValue: (newValue) => Number(newValue) || g_defaultSettings.previewDelay,
  2439. onSet: () => registerSettingsMenu()
  2440. });
  2441. }
  2442. ),
  2443. GM_registerMenuCommand(`\u{1F4DA}\uFE0F \u6BCF\u6B21\u6392\u5E8F ${settings.pageCount} \u9875`, () => {
  2444. setSettingStringValue("pageCount", "\u6BCF\u6B21\u6392\u5E8F\u7684\u9875\u6570", {
  2445. parseValue: (newValue) => Number(newValue) || g_defaultSettings.pageCount,
  2446. onSet: () => registerSettingsMenu()
  2447. });
  2448. }),
  2449. GM_registerMenuCommand(
  2450. `\u{1F468}\u200D\u{1F469}\u200D\u{1F467} \u6392\u5E8F\u9690\u85CF\u6536\u85CF\u6570\u5C11\u4E8E ${settings.favFilter} \u7684\u4F5C\u54C1`,
  2451. () => {
  2452. setSettingStringValue("favFilter", "\u6392\u5E8F\u9690\u85CF\u5C11\u4E8E\u8BBE\u5B9A\u6536\u85CF\u6570\u7684\u4F5C\u54C1", {
  2453. parseValue: (newValue) => Number(newValue) || g_defaultSettings.favFilter,
  2454. onSet: () => registerSettingsMenu()
  2455. });
  2456. }
  2457. ),
  2458. GM_registerMenuCommand(
  2459. `\u{1F3A8} \u6309\u7167 ${settings.orderType === 0 /* BY_BOOKMARK_COUNT */ ? "\u4F5C\u54C1\u6536\u85CF\u6570" : "\u4F5C\u54C1\u53D1\u5E03\u65F6\u95F4"} \u6392\u5E8F\u4F5C\u54C1`,
  2460. () => {
  2461. setSettingValue(
  2462. "orderType",
  2463. settings.orderType === 0 /* BY_BOOKMARK_COUNT */ ? 1 /* BY_DATE */ : 0 /* BY_BOOKMARK_COUNT */
  2464. );
  2465. registerSettingsMenu();
  2466. }
  2467. ),
  2468. GM_registerMenuCommand(
  2469. `\u{1F916} \u6392\u5E8F\u8FC7\u6EE4 AI \u751F\u6210\u4F5C\u54C1 ${settings.aiFilter ? "\u2705" : "\u274C"}`,
  2470. () => {
  2471. toggleSettingBooleanValue("aiFilter");
  2472. registerSettingsMenu();
  2473. }
  2474. ),
  2475. GM_registerMenuCommand(
  2476. `\u{1F9BE} \u6392\u5E8F\u8FC7\u6EE4 AI \u8F85\u52A9\uFF08\u52A0\u7B14\uFF09\u4F5C\u54C1 ${settings.aiAssistedFilter ? "\u2705" : "\u274C"}`,
  2477. () => {
  2478. toggleSettingBooleanValue("aiAssistedFilter");
  2479. registerSettingsMenu();
  2480. }
  2481. ),
  2482. GM_registerMenuCommand(
  2483. `\u2764\uFE0F \u6392\u5E8F\u8FC7\u6EE4\u5DF2\u6536\u85CF\u4F5C\u54C1 ${settings.hideFavorite ? "\u2705" : "\u274C"}`,
  2484. () => {
  2485. toggleSettingBooleanValue("hideFavorite");
  2486. registerSettingsMenu();
  2487. }
  2488. ),
  2489. GM_registerMenuCommand(
  2490. `\u{1F516} \u6392\u5E8F\u8FC7\u6EE4\u5305\u542B\u6307\u5B9A\u6807\u7B7E\u7684\u4F5C\u54C1 ${settings.hideByTag ? "\u2705" : "\u274C"}`,
  2491. () => {
  2492. toggleSettingBooleanValue("hideByTag");
  2493. registerSettingsMenu();
  2494. }
  2495. ),
  2496. GM_registerMenuCommand(
  2497. `\u{1F516} \u6392\u5E8F\u8FC7\u6EE4\u7684\u6807\u7B7E\uFF1A${settings.hideByTagList}`,
  2498. () => {
  2499. setSettingStringValue(
  2500. "hideByTagList",
  2501. "\u8FC7\u6EE4\u7684\u6807\u7B7E\u5217\u8868\uFF0C\u4F7F\u7528`,`\u5206\u9694\u4E0D\u540C\u6807\u7B7E",
  2502. {
  2503. onSet: () => registerSettingsMenu()
  2504. }
  2505. );
  2506. }
  2507. ),
  2508. GM_registerMenuCommand(
  2509. `\u{1F4D1} \u5728\u65B0\u6807\u7B7E\u9875\u6253\u5F00\u4F5C\u54C1 ${settings.linkBlank ? "\u2705" : "\u274C"}`,
  2510. () => {
  2511. toggleSettingBooleanValue("linkBlank");
  2512. registerSettingsMenu();
  2513. }
  2514. ),
  2515. GM_registerMenuCommand(`\u{1F501} \u91CD\u7F6E\u8BBE\u7F6E`, () => {
  2516. if (confirm("\u60A8\u786E\u5B9A\u8981\u91CD\u7F6E\u6240\u6709\u8BBE\u7F6E\u5230\u811A\u672C\u7684\u9ED8\u8BA4\u503C\u5417\uFF1F")) {
  2517. resetSettings();
  2518. location.reload();
  2519. }
  2520. })
  2521. );
  2522. return settings;
  2523. };
  2524. var ShowUpgradeMessage = () => {
  2525. $("#pp-bg").remove();
  2526. const bg = $('<div id="pp-bg"></div>').css({
  2527. position: "fixed",
  2528. "z-index": 9999,
  2529. "background-color": "rgba(0, 0, 0, 0.8)",
  2530. inset: "0px"
  2531. });
  2532. $("body").append(bg);
  2533. 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;">' + i18n_default.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;">' + i18n_default.upgrade_body + "</div>";
  2534. $("#pps-close").on("click", () => {
  2535. setSettingValue("version", g_version);
  2536. $("#pp-bg").remove();
  2537. });
  2538. };
  2539. var initializePixivPreviewer = () => {
  2540. try {
  2541. g_settings = registerSettingsMenu();
  2542. iLog.i(
  2543. "Start to initialize Pixiv Previewer with global settings:",
  2544. g_settings
  2545. );
  2546. if (g_settings.version !== g_version) {
  2547. ShowUpgradeMessage();
  2548. }
  2549. if (g_settings.enablePreview) {
  2550. loadIllustPreview(g_settings);
  2551. }
  2552. $.get(location.href, function(data) {
  2553. const matched = data.match(/token\\":\\"([a-z0-9]{32})/);
  2554. if (matched.length > 0) {
  2555. g_csrfToken = matched[1];
  2556. DoLog(3 /* Info */, "Got g_csrfToken: " + g_csrfToken);
  2557. loadIllustSort({ ...g_settings, csrfToken: g_csrfToken });
  2558. } else {
  2559. DoLog(
  2560. 1 /* Error */,
  2561. "Can not get g_csrfToken, sort function is disabled."
  2562. );
  2563. }
  2564. });
  2565. for (let i = 0; i < Object.keys(Pages).length; i++) {
  2566. if (Pages[i].CheckUrl(location.href)) {
  2567. g_pageType = i;
  2568. break;
  2569. }
  2570. }
  2571. if (g_pageType !== void 0) {
  2572. DoLog(
  2573. 3 /* Info */,
  2574. "Current page is " + Pages[g_pageType].PageTypeString
  2575. );
  2576. } else {
  2577. DoLog(3 /* Info */, "Unsupported page.");
  2578. return;
  2579. }
  2580. if (g_pageType === 3 /* Member */) {
  2581. showSearchLinksForDeletedArtworks();
  2582. } else if (g_pageType === 10 /* Artwork */) {
  2583. const artworkId = window.location.pathname.match(/\/artworks\/(\d+)/)?.[1];
  2584. if (artworkId) {
  2585. setTimeout(() => {
  2586. deleteCachedIllustrationDetails([artworkId]);
  2587. });
  2588. }
  2589. }
  2590. const toolBar = Pages[g_pageType].GetToolBar();
  2591. if (toolBar) {
  2592. DoLog(4 /* Elements */, toolBar);
  2593. } else {
  2594. DoLog(2 /* Warning */, "Get toolbar failed.");
  2595. return;
  2596. }
  2597. if (!$(`#${SORT_BUTTON_ID}`).length) {
  2598. const newListItem = document.createElement("div");
  2599. newListItem.title = "Sort artworks";
  2600. newListItem.innerHTML = "";
  2601. const newButton = document.createElement("button");
  2602. newButton.id = SORT_BUTTON_ID;
  2603. 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;";
  2604. newButton.innerHTML = i18n_default.label_sort;
  2605. newListItem.appendChild(newButton);
  2606. toolBar.appendChild(newListItem);
  2607. $(newButton).on("click", () => {
  2608. const sortEvent = new Event(SORT_EVENT_NAME);
  2609. window.dispatchEvent(sortEvent);
  2610. });
  2611. }
  2612. if (!$(`#${SORT_NEXT_PAGE_BUTTON_ID}`).length) {
  2613. const newListItem = document.createElement("div");
  2614. newListItem.title = "Jump to next page";
  2615. newListItem.innerHTML = "";
  2616. const newButton = document.createElement("button");
  2617. newButton.id = SORT_NEXT_PAGE_BUTTON_ID;
  2618. 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;";
  2619. newButton.innerHTML = i18n_default.label_nextPage;
  2620. newListItem.appendChild(newButton);
  2621. toolBar.appendChild(newListItem);
  2622. $(newButton).on("click", () => {
  2623. const sortEvent = new Event(SORT_NEXT_PAGE_EVENT_NAME);
  2624. window.dispatchEvent(sortEvent);
  2625. });
  2626. }
  2627. } catch (e) {
  2628. DoLog(1 /* Error */, "An error occurred while initializing:", e);
  2629. }
  2630. };
  2631. window.addEventListener("DOMContentLoaded", () => {
  2632. setTimeout(initializePixivPreviewer, 1e3);
  2633. });

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址