Sleazy Fork is available in English.

Modal Image in rule(rule34图片查看器 水果玉米版)

Add a modal image and thumbnails vertical-carrousel to rule34, add functions of drag img and resize img, rule34图片查看器, 修改版

  1. // ==UserScript==
  2. // @name Modal Image in rule(rule34图片查看器 水果玉米版)
  3. // @namespace http://tampermonkey.net/
  4. // @version 20250119
  5. // @description Add a modal image and thumbnails vertical-carrousel to rule34, add functions of drag img and resize img, rule34图片查看器, 修改版
  6. // @author falaz, hzx
  7. // @match https://rule34.xxx/index.php?page=po*
  8. // @icon https://www.google.com/s2/favicons?domain=rule34.xxx
  9. // @grant GM_download
  10. // @license MIT
  11. // ==/UserScript==
  12. /*jshint esversion: 6 */
  13. // @ts-check
  14. // original author is falaz, hzx just add functions original https://sleazyfork.org/en/scripts/390625-modal-image-in-rule\
  15.  
  16. /**
  17. * get file name from url
  18. * input: 'https://wimg.rule34.xxx//samples/2749/sample_ee25a33b3079953bab541bce5724694c.jpg?11795365'
  19. * output: 'sample_ee25a33b3079953bab541bce5724694c.jpg'
  20. * @param url
  21. * @returns {string}
  22. */
  23. function getFileNameFromURL(url) {
  24. // 检查是否包含问号并去掉?及其后面的内容
  25. // Check if the URL contains a question mark and remove '?' And what follows
  26. let cleanedUrl = url.includes('?') ? url.split('?')[0] : url;
  27.  
  28. // 获取最后一个斜杠后的内容
  29. // Gets the content after the last slash
  30. let fileName = cleanedUrl.substring(cleanedUrl.lastIndexOf('/') + 1);
  31.  
  32. return fileName;
  33. }
  34. class Falaz {
  35. /**
  36. * Like querySelector, but small
  37. * @param {String} selector
  38. * @param {Element|Document} element
  39. * @returns {HTMLElement}
  40. */
  41. q(selector, element = document) {
  42. return element.querySelector(selector);
  43. }
  44.  
  45. /**
  46. * Like querySelectorAll, but small
  47. * @param {String} selector
  48. * @param {Element|Document} element
  49. * @returns
  50. */
  51. qa(selector, element = document) {
  52. return element.querySelectorAll(selector);
  53. }
  54. }
  55.  
  56. class Modal {
  57. constructor(document, dp) {
  58. this.pointer = 0;
  59. this.dp = dp;
  60. this.infinityScroll = new InfinityScroll(document, dp);
  61. /**
  62. * @property {Media[]} medias
  63. */
  64. // media 构造参数传递页面的src, 就可以解析出图片的真实地址
  65. // getMedias
  66. this.medias = this.infinityScroll.getMedias(document, 0);
  67. this.thumbsGallery = new ThumbsGallery(this.medias);
  68. this.createModalNode(document, this.thumbsGallery.render());
  69. this.bodyParent = F.q('body');
  70. this.visible = false;
  71. }
  72.  
  73. createConfigBar() {
  74. return `<div id="menuModal" style="display: flex;">
  75. <div><input type="checkbox" id="night">Night Theme</div>
  76. <div><input type="checkbox" id="night">Only videos</div></div>`
  77. }
  78.  
  79. createModalNode(document, extraDiv = null) {
  80. const div = document.createElement("div");
  81. const css = document.createElement("style");
  82. div.innerHTML =
  83. `<div id="modal-container" style="display:none"><div id="modal"></div>${extraDiv ? extraDiv : ''}${this.createButtons()}</div>`;
  84. css.innerHTML =
  85. ".content {display: flex; flex-wrap: wrap; gap: 5px;}" +
  86. "span[data-nosnippet] {display: none;}" +
  87. "#modal-container{background: #000000a8;width: 100%;height: 100%;position: fixed;z-index: 10;}" +
  88. "#modal{height: 90%;width: 80%;background: transparent;padding: 0% 5%;margin: 2% 5% 2% 0;position: fixed}" +
  89. "#modal img{width: auto;border: none;vertical-align: middle;height: 100%;margin: 0 auto;display:block;}" +
  90. "#modal video{width: 100%;height:100%}" +
  91. "#modal-container #thumbGallery{float:right;overflow-y: scroll;height: 900px;} #modal-container #thumbGallery ul{list-style:none}" +
  92. "#modal-container .gallery-item{cursor: pointer;}" +
  93. '#navigationModal{margin-bottom: 0px;font-size: 20px;color: white;display: flex;justify-content: center;bottom: 0px;width: 100%; position:absolute}' +
  94. '#navigationModal svg:hover g g {fill: white;}' +
  95. '.fluid_video_wrapper video{cursor:default!important}' +
  96. '@media (max-width: 1024px) {#thumbGallery {display: none;} #modal{width:100%;padding:0%;overflow-x:scroll}}' +
  97. '#modal-container button {border-radius: 25px; border: none; background: #1abc9c; margin-left: 10px}';
  98. if (debug) {
  99. css.innerHTML += "img,video{filter:blur(30px)}";
  100. }
  101. document.body.prepend(div);
  102. document.head.prepend(css);
  103. this.modalContainer = F.q("#modal-container");
  104. this.modal = F.q("#modal");
  105.  
  106. this.infinityScroll.nextPage = this.infinityScroll.getNextPageHref(document);
  107. }
  108.  
  109. /**
  110. * [important] 创建按钮, 添加功能
  111. * [important] Create buttons & add functionality
  112. * @returns {string}
  113. */
  114. createButtons() {
  115. return `<div id="navigationModal"">
  116. <div onclick="document.modalObj.prevMedia()"><svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="52" height="52" viewBox="0 0 172 172" style=" fill:#000000;"><g fill="none" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><path d="M0,172v-172h172v172z" fill="none"></path><g fill="#1abc9c"><path d="M125.69231,19.84615h-79.38462c-14.54868,0 -26.46154,11.91286 -26.46154,26.46154v79.38462c0,14.54868 11.91286,26.46154 26.46154,26.46154h79.38462c14.54868,0 26.46154,-11.91286 26.46154,-26.46154v-79.38462c0,-14.54868 -11.91286,-26.46154 -26.46154,-26.46154zM112.46154,59.53846l-47.6256,26.46154l47.6256,26.46154v13.23077l-59.53846,-33.07692v-13.23077l59.53846,-33.07692z"></path></g></g></svg></div>
  117. <div onclick="document.modalObj.nextMedia()"><svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="52" height="52" viewBox="0 0 172 172" style=" fill:#000000;"><g fill="none" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><path d="M0,172v-172h172v172z" fill="none"></path><g fill="#1abc9c"><path d="M125.69231,19.84615h-79.38462c-14.54868,0 -26.46154,11.91286 -26.46154,26.46154v79.38462c0,14.54868 11.91286,26.46154 26.46154,26.46154h79.38462c14.54868,0 26.46154,-11.91286 26.46154,-26.46154v-79.38462c0,-14.54868 -11.91286,-26.46154 -26.46154,-26.46154zM119.07692,92.61538l-59.53846,33.07692v-13.23077l47.6256,-26.46154l-47.6256,-26.46154v-13.23077l59.53846,33.07692z"></path></g></g></svg></div>
  118. <div onclick="document.modalObj.close()"><svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="60" height="60" viewBox="0 0 172 172" style=" fill:#000000;"><g fill="none" fill-rule="nonzero" stroke="none" stroke-width="1" stroke-linecap="butt" stroke-linejoin="miter" stroke-miterlimit="10" stroke-dasharray="" stroke-dashoffset="0" font-family="none" font-weight="none" font-size="none" text-anchor="none" style="mix-blend-mode: normal"><path d="M0,172v-172h172v172z" fill="none"></path><g id="original-icon" fill="#1abc9c"><path d="M86,17.2c-37.9948,0 -68.8,30.8052 -68.8,68.8c0,37.9948 30.8052,68.8 68.8,68.8c37.9948,0 68.8,-30.8052 68.8,-68.8c0,-37.9948 -30.8052,-68.8 -68.8,-68.8zM112.9868,104.87987c2.24173,2.24173 2.24173,5.8652 0,8.10693c-1.118,1.118 -2.58573,1.67987 -4.05347,1.67987c-1.46773,0 -2.93547,-0.56187 -4.05347,-1.67987l-18.87987,-18.87987l-18.87987,18.87987c-1.118,1.118 -2.58573,1.67987 -4.05347,1.67987c-1.46773,0 -2.93547,-0.56187 -4.05347,-1.67987c-2.24173,-2.24173 -2.24173,-5.8652 0,-8.10693l18.87987,-18.87987l-18.87987,-18.87987c-2.24173,-2.24173 -2.24173,-5.8652 0,-8.10693c2.24173,-2.24173 5.8652,-2.24173 8.10693,0l18.87987,18.87987l18.87987,-18.87987c2.24173,-2.24173 5.8652,-2.24173 8.10693,0c2.24173,2.24173 2.24173,5.8652 0,8.10693l-18.87987,18.87987z"></path></g></g></svg></div>
  119. <button onclick="document.modalObj.resetImage()"> 还原<br/>(reset) </button>
  120. <button onclick="document.modalObj.download()"> 下载<br/>(download) </button>
  121. <button onclick="document.modalObj.favorite()"> 收藏<br/>(favorite) </button>
  122. </div>`
  123. }
  124.  
  125. /**
  126. * @param {Media} media
  127. */
  128. async render(media) {
  129. if (!this.modalContainer) {
  130. this.createModalNode(document, this.thumbsGallery.render());
  131. }
  132. if (media.src == "Retry") {
  133. await this.reloadMediaSrc(media);
  134. }
  135. if (media.type == "video") {
  136. this.modal.innerHTML = `<video src="${media.src}" ${debug ? '' : "autoplay"} controls loop id="modalVideo"></video>`;
  137. F.q('#modalVideo').volume = this.getCurrentVolume();
  138. F.q('#modalVideo').onvolumechange = e => this.saveCurrentVolume(e.target.volume);
  139. fluidPlayer(document.querySelector('#modal video'), {
  140. layoutControls: {
  141. autoPlay: true,
  142. allowDownload: true,
  143. loop: true,
  144. fillToContainer: true
  145. }
  146. })
  147. F.q('#modal video').loop = true
  148. } else {
  149. this.modal.innerHTML = `<img src="${media.src}"/>`;
  150.  
  151. /**
  152. * hzx 修改开始
  153. * hzx modification BEGIN
  154. *
  155. * 添加图片的拖拽和滚轮缩放功能
  156. * Add drag-and-drop and scroll zoom functions for images
  157. *
  158. * Button 的 onclick 事件处理函数的实现: 复位, 下载, 添加到收藏
  159. * Implementation of onclick event handler for Button: reset, download, add to favorites
  160. */
  161. this.innerImg = F.q("img", this.modal);
  162. let img = this.innerImg;
  163. let scale = 1;
  164. let originX = 0;
  165. let originY = 0;
  166. let isDragging = false;
  167. let startX, startY;
  168.  
  169. img.addEventListener('wheel', (event) => {
  170. event.preventDefault();
  171.  
  172. const rect = img.getBoundingClientRect();
  173. const offsetX = event.clientX - rect.left;
  174. const offsetY = event.clientY - rect.top;
  175.  
  176. const delta = Math.sign(event.deltaY) * -0.1;
  177. const newScale = Math.min(Math.max(0.5, scale + delta), 3);
  178.  
  179. originX = (originX - offsetX) * (newScale / scale) + offsetX;
  180. originY = (originY - offsetY) * (newScale / scale) + offsetY;
  181.  
  182. scale = newScale;
  183.  
  184. img.style.transform = `translate(${originX}px, ${originY}px) scale(${scale})`;
  185. });
  186.  
  187. img.addEventListener('mousedown', (event) => {
  188. event.preventDefault();
  189. isDragging = true;
  190. startX = event.clientX;
  191. startY = event.clientY;
  192. img.style.cursor = 'grabbing';
  193. });
  194.  
  195. window.addEventListener('mouseup', () => {
  196. event.preventDefault();
  197. isDragging = false;
  198. img.style.cursor = 'grab';
  199. });
  200.  
  201. window.addEventListener('mousemove', (event) => {
  202. event.preventDefault();
  203. if (!isDragging) return;
  204.  
  205. const dx = event.clientX - startX;
  206. const dy = event.clientY - startY;
  207.  
  208. originX += dx;
  209. originY += dy;
  210.  
  211. startX = event.clientX;
  212. startY = event.clientY;
  213.  
  214. img.style.transform = `translate(${originX}px, ${originY}px) scale(${scale})`;
  215. });
  216.  
  217. this.resetImage = function () {
  218. scale = 1;
  219. originX = 0;
  220. originY = 0;
  221. img.style.transform = `translate(0, 0) scale(${scale})`;
  222. }
  223.  
  224.  
  225. // hzx 修改结束
  226. // hzx modification END
  227. }
  228. this.modalContainer.style.display = "block";
  229. this.visible = true;
  230. this.toggleBodyScroll(false);
  231. modalObj.scrollIntoCurrentMedia();
  232. }
  233.  
  234. /**
  235. * hzx 修改开始
  236. * 收藏, 下载功能
  237. * hzx modification BEGIN
  238. */
  239. favorite() {
  240. function apiAddFav(id) {
  241. return fetch(`https://rule34.xxx/public/addfav.php?id=${id}`);
  242. }
  243.  
  244. async function addFav(id) {
  245. let resp = await apiAddFav(id);
  246. let content = await resp.text();
  247.  
  248.  
  249. debugger;
  250. if (content !== '') {
  251. showToast("[Success] 收藏成功");
  252. } else {
  253. showToast("[fail] 收藏失败");
  254. }
  255. }
  256.  
  257. /**
  258. * input: http://www.xxx.com?123333
  259. * output: 123333
  260. * @param url
  261. * @returns {*|null}
  262. */
  263. function getUrlId(url) {
  264. const parts = url.split('?');
  265. return parts.length > 1 ? parts[1] : null;
  266. }
  267.  
  268. addFav(getUrlId(this.medias[this.pointer].src));
  269. }
  270.  
  271. /**
  272. * 2025/01/19:
  273. * [bug fix] 修复无法下载视频的bug
  274. * [bug fix] Fixed a bug where videos could not be downloaded
  275. */
  276. download() {
  277. showToast("下载开始, 请等待\ndownload begin, plz wait")
  278. let src = this.medias[this.pointer].src;
  279. GM_download({url: src, name: getFileNameFromURL(src)});
  280. }
  281.  
  282. // hzx 修改结束
  283. // hzx modification END
  284.  
  285. toggleBodyScroll(visible) {
  286. this.bodyParent.style.overflow = visible ? 'inherit' : 'hidden';
  287. }
  288.  
  289. getCurrentVolume() {
  290. return localStorage.volume ? localStorage.volume : 0.0;
  291. }
  292.  
  293. /**
  294. * Attach to event video.onvolumechange
  295. * @param {number} value
  296. */
  297. saveCurrentVolume(value) {
  298. localStorage.volume = value;
  299. }
  300.  
  301. async getNextPage() {
  302. if (!this.infinityScroll.nextSended) {
  303. this.infinityScroll.nextSended = true;
  304. const docResponse = await this.infinityScroll.getNextPage();
  305. if (docResponse) {
  306. const medias = this.infinityScroll.getMedias(
  307. docResponse,
  308. this.medias.length
  309. );
  310. this.addMedias(medias);
  311. this.infinityScroll.nextSended = false;
  312. } else {
  313. c('The page requested is on the medias');
  314. }
  315. }
  316. }
  317.  
  318. close() {
  319. if (!this.modalContainer) {
  320. this.createModalNode(document);
  321. }
  322. try {
  323. this.modal.querySelector("video").pause();
  324. } catch (e) {
  325. }
  326. this.modalContainer.style.display = "none";
  327. this.toggleBodyScroll(true);
  328. this.visible = false;
  329. }
  330.  
  331. nextMedia() {
  332. if (this.pointer < this.medias.length - 1) {
  333. this.pointer++;
  334. this.render(this.medias[this.pointer]);
  335. } else {
  336. this.getNextPage();
  337. this.pointer++;
  338. this.render(this.medias[this.pointer]);
  339. }
  340. }
  341.  
  342. goToMedia(index) {
  343. if (index < this.medias.length - 1) {
  344. this.pointer = index;
  345. this.render(this.medias[index])
  346. }
  347. }
  348.  
  349. prevMedia() {
  350. if (this.pointer > 0) {
  351. this.pointer--;
  352. this.render(this.medias[this.pointer]);
  353. } else {
  354. console.error("Reach the start of the medias");
  355. }
  356. }
  357.  
  358. getCurrentMedia() {
  359. this.medias[this.pointer];
  360. }
  361.  
  362. getCurrentThumb() {
  363. return F.q(`span [data-index="${this.pointer}"]`).parentElement
  364. }
  365.  
  366. /**
  367. *
  368. * @param {Media[]} medias
  369. */
  370. addMedias(medias) {
  371. const mediasTemp = [...this.medias, ...medias];
  372. // @ts-ignore
  373. this.medias = mediasTemp;
  374. this.thumbsGallery.updateMedias(mediasTemp);
  375. }
  376.  
  377. async reloadMediaSrcFromMedias(index) {
  378. await this.medias[index].reloadSrc();
  379. }
  380.  
  381. async reloadMediaSrc(media) {
  382. await media.reloadSrc();
  383. }
  384.  
  385. scrollIntoCurrentMedia() {
  386. c('fired')
  387. this.getCurrentThumb().scrollIntoView();
  388. this.thumbsGallery.scrollToThumb(modalObj.pointer)
  389. }
  390. }
  391.  
  392. // 获取页面图片的真实地址, 并保存
  393. class Media {
  394. /**
  395. * @param {string} _page This is the page of the media. Not the real image src.
  396. * @param {string} _type
  397. * @param {string} _thumb
  398. */
  399. constructor(_page, _type, _thumb, dp) {
  400. this.page = _page;
  401. this.type = _type;
  402. this.thumb = _thumb;
  403. this.dp = dp;
  404. this.getSrc().then((src) => {
  405. this.src = src;
  406. });
  407. }
  408.  
  409. async getSrc() {
  410. const response = await fetch(this.page);
  411. if ([202, 200].includes(response.status)) {
  412. const body = await response.text();
  413. const dp = new DOMParser();
  414. const pageDocument = dp.parseFromString(body, "text/html");
  415. const video = F.q("video source", pageDocument);
  416. const image = F.q(".flexi img", pageDocument);
  417. if (video) {
  418. this.type = "video";
  419. // @ts-ignore
  420. return video.src;
  421. } else {
  422. this.type = "image";
  423. // @ts-ignore
  424. return image.src;
  425. }
  426. } else {
  427. return "Retry";
  428. }
  429. }
  430.  
  431. async reloadSrc() {
  432. this.src = await this.getSrc();
  433. }
  434. }
  435.  
  436. // 实现无线滚动
  437. class InfinityScroll {
  438. /**
  439. * @param {Document} document
  440. * @param {DOMParser} _dp
  441. */
  442. constructor(document, _dp) {
  443. this.nextSended = false;
  444. this.pagesParsed = [this.getPageNumber(document)];
  445. this.nextPage = this.getNextPageHref(document);
  446. this.dp = _dp;
  447. }
  448.  
  449. /**
  450. * @param {Document} doc
  451. */
  452. getPageNumber(doc) {
  453. // get current page number from one page
  454. return parseInt(doc.querySelector("#paginator div b").innerHTML);
  455. }
  456.  
  457. /**
  458. * @param {Document} doc
  459. */
  460. getNextPageHref(doc) {
  461. const nextNode = doc.querySelector('#paginator [alt="next"]');
  462. // @ts-ignore
  463. return nextNode ? nextNode.href : null;
  464. }
  465.  
  466. async getNextPage() {
  467. c("getNextPage");
  468. if (this.nextPage === null) {
  469. // @ts-ignore
  470. this.nextPage = F.q('#paginator [alt="next"]').href;
  471. }
  472. const response = await fetch(this.nextPage);
  473. if ([204, 202, 200, 201].includes(response.status)) {
  474. const body = await response.text();
  475. const dp = new DOMParser();
  476. const pageDocument = dp.parseFromString(body, "text/html");
  477. const pageNum = this.getPageNumber(pageDocument);
  478. this.nextPage = this.getNextPageHref(pageDocument);
  479. if (!this.pagesParsed.includes(pageNum)) { // remove the page allready parsed
  480. this.pagesParsed.push(pageNum);
  481. return pageDocument;
  482. } else {
  483. return false;
  484. }
  485. } else {
  486. await this.sleep(500);
  487. return await this.getNextPage();
  488. }
  489. }
  490.  
  491. sleep(ms) {
  492. return new Promise(resolve => setTimeout(resolve, ms));
  493. }
  494.  
  495. /**
  496. * 获取所有缩略图, 并设置onclick事件
  497. * Gets all the thumbnail elements and sets the onclick event
  498. * @param {Document} document
  499. * @param {number} pad The length of medias in the Modal Object
  500. * @returns {Media[]}
  501. */
  502. getMedias(document, pad) {
  503. // .thumb 作为类名通常是指 "thumbnail"(缩略图)的简称
  504. const thumbsNode = F.qa("#content .thumb", document);
  505. const medias = [];
  506. const nodes = [];
  507. //const pad = this.medias? this.medias.length : 0;
  508. for (let i = 0; i < thumbsNode.length; i++) {
  509. const node = thumbsNode[i];
  510. /**
  511. * @type {HTMLImageElement} img
  512. */
  513. // @ts-ignore
  514. const img = F.q("img", node);
  515. const title = img.title;
  516. const anchor = node.querySelector("a");
  517. const media = new Media(
  518. anchor.href,
  519. /animated|video/.test(title) ? "video" : "image",
  520. img.src,
  521. this.dp
  522. );
  523. anchor.dataset.index = (i + pad).toString();
  524. img.dataset.index = (i + pad).toString();
  525. //node.dataset.index = (i+pad).toString();
  526. medias.push(media);
  527. nodes.push(node);
  528. }
  529. this.addElementsToCurrentContentView(nodes); // async
  530. return medias;
  531. }
  532.  
  533. async addElementsToCurrentContentView(nodes) {
  534. for (const node of nodes) {
  535. this.clickFunction(node);
  536. F.q(".content").insertBefore(node, F.q("#paginator"));
  537. }
  538. }
  539.  
  540. clickFunction(element) {
  541. /**
  542. * hzx 修改开始
  543. * hzx modification BEGIN
  544. *
  545. * 2025/01/19:
  546. * [bug fix] 多音频, 关闭视频后, 仍有音频的问题
  547. * [bug fix] fix multiple audio problem
  548. */
  549. element.onclick = (e) => {
  550. e.preventDefault();
  551. const index = e.target.dataset.index
  552. ? e.target.dataset.index
  553. : modalObj.pointer;
  554. modalObj.pointer = index;
  555. modalObj.render(modalObj.medias[index]);
  556. };
  557. // hzx 修改结束
  558. // hzx modification END
  559. }
  560. }
  561.  
  562. class ThumbsGallery {
  563. /**
  564. * @param {Media[]} medias
  565. */
  566. constructor(medias) {
  567. this.medias = medias;
  568. }
  569.  
  570. /**
  571. * @param {number} index
  572. */
  573. scrollToThumb(index) {
  574. F.q(`.gallery-item[data-index="${index}"]`).scrollIntoView();
  575. }
  576.  
  577. /**
  578. * @param {Media[]} medias
  579. */
  580. updateMedias(medias) {
  581. this.medias = medias;
  582. F.q('#thumbGallery').innerHTML = `<ul>${this.buildItems()}</ul>`;
  583. }
  584.  
  585. render() {
  586. return `<div id="thumbGallery"><ul>${this.buildItems()}</ul></div>`
  587. }
  588.  
  589. buildItems() {
  590. let li = ''
  591. this.medias.forEach((e, i) => {
  592. li += `<li class="gallery-item" data-index="${i}" onclick="{document.modalObj.goToMedia(${i})}","_blank")}">
  593. <img src="${e.thumb}"\\>
  594. </li>`
  595. })
  596. return li;
  597. }
  598.  
  599. }
  600.  
  601. const dp = new DOMParser();
  602. const F = new Falaz();
  603. let modalObj;
  604. const loadingSVG = "https://samherbert.net/svg-loaders/svg-loaders/puff.svg";
  605. const debug = false;
  606. const c = (...e) => {
  607. if (debug) {
  608. console.log(e);
  609. }
  610. }
  611.  
  612. (function () {
  613. "use strict";
  614. modalObj = new Modal(document, dp);
  615. if (debug) {
  616. document.title = 'New Tab'
  617. }
  618. // @ts-ignore
  619. document.modalObj = modalObj;
  620. F.qa(".content .thumb").forEach((element) => {
  621. modalObj.infinityScroll.clickFunction(element);
  622. });
  623. document.addEventListener("keydown", (e) => {
  624. if (e.key == "ArrowRight") {
  625. modalObj.nextMedia();
  626. modalObj.scrollIntoCurrentMedia();
  627. } else if (e.key == "ArrowLeft") {
  628. modalObj.prevMedia();
  629. modalObj.scrollIntoCurrentMedia();
  630. } else if (e.key == "Escape") {
  631. modalObj.close();
  632. }
  633. });
  634. // @ts-ignore
  635. document.addEventListener("scroll", (e) => {
  636. if (!modalObj.visible) {
  637. const inner = window.innerHeight;
  638. if (
  639. window.scrollY + inner >
  640. document.documentElement.scrollHeight - inner * 2
  641. ) {
  642. modalObj.getNextPage();
  643. }
  644. } else {
  645. e.preventDefault();
  646. modalObj.scrollIntoCurrentMedia();
  647. }
  648. });
  649.  
  650. // queryAsync(".awesomplete input").then(inputEl => {
  651. // if (!inputEl.value.includes("-ai_generated")) {
  652. // setTimeout(() => {
  653. // inputEl.value = inputEl.value += " -ai_generated";
  654. // queryAsync("input[type=submit]").then(btnEl => btnEl.click());
  655. // }, 500);
  656. // }
  657. // });
  658.  
  659. })();
  660.  
  661. /**
  662. * 类似于querySelector, 但是异步, 并且不会报错, 不会导致脚本停止执行
  663. * like querySelector, but async, and never throw error to causes the script to stop executing
  664. * @param selector
  665. * 选择器, 和querySelector的参数一致
  666. * selector, same to the param of querySelector
  667. * @param timeout
  668. * timeout 为-1时 无限查询并等待
  669. * If timeout is -1, query indefinitely and wait
  670. * @param onError
  671. * 回调函数, 在查找不到时执行
  672. * callback function, it will be called when the query fails
  673. * @param isBlocking
  674. * 在查询失败时, 是否保持Promise为pending状态, 默认为false
  675. * whether to keep the Promise in pending state when a query fails. The default is false
  676. * @returns {Promise<unknown>}
  677. */
  678. function queryAsync(selector, timeout = 1000, onError = null, isBlocking = false) {
  679. let errEl = document.body.errEl;
  680.  
  681. if (errEl === undefined) {
  682. errEl = document.createElement('div');
  683. errEl.classList.add('no-found');
  684. errEl.remove = () => {};
  685.  
  686. document.body.errEl = errEl;
  687. }
  688.  
  689.  
  690. // 这里不用reject是担心脚本报错后, 终止运行
  691. // We don't use reject because it will throw error and case the script to stop executing
  692. return new Promise((resolve) => {
  693. const startTime = Date.now();
  694.  
  695. function check() {
  696. const ele = document.querySelector(selector);
  697. if (ele !== null) {
  698. resolve(ele); // 找到元素,结束 Promise
  699. return;
  700. }
  701. if (timeout !== -1 && Date.now() - startTime > timeout) {
  702. if (onError !== null) {
  703. onError();
  704. }
  705. if (!isBlocking) {
  706. resolve(errEl);
  707. }
  708. console.warn(`$Q_Async: Timeout: Cannot find element for selector "${selector}"`);
  709. return;
  710. }
  711.  
  712. setTimeout(check, 100);
  713. }
  714.  
  715. check();
  716. });
  717. }
  718.  
  719. function showToast(message) {
  720. let toast = document.body.toast;
  721.  
  722. if (toast === undefined) {
  723. toast = document.createElement('div');
  724. toast.style.position = 'fixed';
  725. toast.style.bottom = '55vh';
  726. toast.style.left = '50%';
  727. toast.style.transform = 'translateX(-50%)';
  728. toast.style.padding = '10px 20px';
  729. toast.style.backgroundColor = '#333';
  730. toast.style.color = '#fff';
  731. toast.style.borderRadius = '5px';
  732. toast.style.fontSize = '14px';
  733. toast.style.zIndex = '9999';
  734. toast.style.opacity = '0';
  735. toast.style.transition = 'all 0.5s ease';
  736. toast.style.visibility = 'hidden';
  737. document.body.appendChild(toast);
  738. document.body.toast = toast;
  739. }
  740. toast.innerText = message;
  741.  
  742. // 显示 toast
  743. toast.style.opacity = '1';
  744. toast.style.visibility = 'visible';
  745.  
  746. // 3秒后隐藏
  747. setTimeout(() => {
  748. toast.style.opacity = '0';
  749. toast.style.visibility = 'hidden';
  750. }, 3000);
  751. }
  752.  
  753.