e-hentai-infinite-scroll

Exhentai infinite scroll scripts.

Version au 19/12/2022. Voir la dernière version.

  1. // ==UserScript==
  2. // @name e-hentai-infinite-scroll
  3. // @namespace https://github.com/IronKinoko/userscripts/tree/master/packages/e-hentai-infinite-scroll
  4. // @version 1.2.0
  5. // @description Exhentai infinite scroll scripts.
  6. // @author IronKinoko
  7. // @match https://e-hentai.org/*
  8. // @match https://exhentai.org/*
  9. // @grant none
  10. // @require https://unpkg.com/jquery@3.6.1/dist/jquery.min.js
  11. // ==/UserScript==
  12. (function () {
  13. 'use strict';
  14.  
  15. var e=[],t=[];function n(n,r){if(n&&"undefined"!=typeof document){var a,s=!0===r.prepend?"prepend":"append",d=!0===r.singleTag,i="string"==typeof r.container?document.querySelector(r.container):document.getElementsByTagName("head")[0];if(d){var u=e.indexOf(i);-1===u&&(u=e.push(i)-1,t[u]={}),a=t[u]&&t[u][s]?t[u][s]:t[u][s]=c();}else a=c();65279===n.charCodeAt(0)&&(n=n.substring(1)),a.styleSheet?a.styleSheet.cssText+=n:a.appendChild(document.createTextNode(n));}function c(){var e=document.createElement("style");if(e.setAttribute("type","text/css"),r.attributes)for(var t=Object.keys(r.attributes),n=0;n<t.length;n++)e.setAttribute(t[n],r.attributes[t[n]]);var a="prepend"===s?"afterbegin":"beforeend";return i.insertAdjacentElement(a,e),e}}
  16.  
  17. var css = ".e-hentai-infinite-scroll #gdt::after {\n content: \"\";\n display: block;\n clear: both;\n}\n.e-hentai-infinite-scroll .g-scroll-body {\n overflow: auto;\n max-height: 80vh;\n}\n.e-hentai-infinite-scroll .g-scroll-body::-webkit-scrollbar {\n width: 0;\n}\n@supports (overflow: overlay) {\n .e-hentai-infinite-scroll .g-scroll-body {\n overflow: overlay;\n }\n .e-hentai-infinite-scroll .g-scroll-body::-webkit-scrollbar {\n width: 8px;\n }\n .e-hentai-infinite-scroll .g-scroll-body::-webkit-scrollbar-thumb {\n background-color: rgba(255, 255, 255, 0.15);\n border-radius: 2px;\n }\n}\n.e-hentai-infinite-scroll .g-scroll-page-index {\n clear: both;\n display: flex;\n justify-content: center;\n align-items: center;\n margin-bottom: 10px;\n}\n.e-hentai-infinite-scroll .g-scroll-page-index::before, .e-hentai-infinite-scroll .g-scroll-page-index::after {\n display: block;\n content: \"\";\n width: 40px;\n height: 1px;\n background: #ddd;\n margin: 0 10px;\n}\n.e-hentai-infinite-scroll.s .auto-load-img {\n width: 100% !important;\n max-width: 100% !important;\n margin: 0 !important;\n padding: 10px;\n display: block;\n box-sizing: border-box;\n}\n.e-hentai-infinite-scroll.s .auto-load-img-empty {\n min-height: 1000px;\n width: 100px !important;\n margin: 0 auto !important;\n}\n.e-hentai-infinite-scroll.s #i3 a {\n pointer-events: none;\n}\n.e-hentai-infinite-scroll.s .ehis-bottom-info-wrapper {\n position: sticky;\n bottom: 0;\n overflow: hidden;\n}\n.e-hentai-infinite-scroll.s .ehis-bottom-info-wrapper .ehis-bottom-info-container {\n transition: all 0.15s ease-in;\n transform: translateY(20px);\n opacity: 0;\n}\n.e-hentai-infinite-scroll.s .ehis-bottom-info-wrapper .ehis-bottom-info-container::after {\n content: \"\";\n display: table;\n}\n.e-hentai-infinite-scroll.s .ehis-bottom-info-wrapper:hover .ehis-bottom-info-container {\n transform: translateY(0);\n opacity: 1;\n}\n.e-hentai-infinite-scroll.s .ehis-bottom-info-wrapper.static .ehis-bottom-info-container {\n opacity: 1;\n transform: none;\n}";
  18. n(css,{});
  19.  
  20. /**
  21. * Checks if `value` is the
  22. * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
  23. * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
  24. *
  25. * @static
  26. * @memberOf _
  27. * @since 0.1.0
  28. * @category Lang
  29. * @param {*} value The value to check.
  30. * @returns {boolean} Returns `true` if `value` is an object, else `false`.
  31. * @example
  32. *
  33. * _.isObject({});
  34. * // => true
  35. *
  36. * _.isObject([1, 2, 3]);
  37. * // => true
  38. *
  39. * _.isObject(_.noop);
  40. * // => true
  41. *
  42. * _.isObject(null);
  43. * // => false
  44. */
  45. function isObject(value) {
  46. var type = typeof value;
  47. return value != null && (type == 'object' || type == 'function');
  48. }
  49.  
  50. /** Detect free variable `global` from Node.js. */
  51. var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
  52.  
  53. /** Detect free variable `self`. */
  54. var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
  55.  
  56. /** Used as a reference to the global object. */
  57. var root = freeGlobal || freeSelf || Function('return this')();
  58.  
  59. /**
  60. * Gets the timestamp of the number of milliseconds that have elapsed since
  61. * the Unix epoch (1 January 1970 00:00:00 UTC).
  62. *
  63. * @static
  64. * @memberOf _
  65. * @since 2.4.0
  66. * @category Date
  67. * @returns {number} Returns the timestamp.
  68. * @example
  69. *
  70. * _.defer(function(stamp) {
  71. * console.log(_.now() - stamp);
  72. * }, _.now());
  73. * // => Logs the number of milliseconds it took for the deferred invocation.
  74. */
  75. var now = function() {
  76. return root.Date.now();
  77. };
  78.  
  79. /** Used to match a single whitespace character. */
  80. var reWhitespace = /\s/;
  81.  
  82. /**
  83. * Used by `_.trim` and `_.trimEnd` to get the index of the last non-whitespace
  84. * character of `string`.
  85. *
  86. * @private
  87. * @param {string} string The string to inspect.
  88. * @returns {number} Returns the index of the last non-whitespace character.
  89. */
  90. function trimmedEndIndex(string) {
  91. var index = string.length;
  92.  
  93. while (index-- && reWhitespace.test(string.charAt(index))) {}
  94. return index;
  95. }
  96.  
  97. /** Used to match leading whitespace. */
  98. var reTrimStart = /^\s+/;
  99.  
  100. /**
  101. * The base implementation of `_.trim`.
  102. *
  103. * @private
  104. * @param {string} string The string to trim.
  105. * @returns {string} Returns the trimmed string.
  106. */
  107. function baseTrim(string) {
  108. return string
  109. ? string.slice(0, trimmedEndIndex(string) + 1).replace(reTrimStart, '')
  110. : string;
  111. }
  112.  
  113. /** Built-in value references. */
  114. var Symbol = root.Symbol;
  115.  
  116. /** Used for built-in method references. */
  117. var objectProto$1 = Object.prototype;
  118.  
  119. /** Used to check objects for own properties. */
  120. var hasOwnProperty = objectProto$1.hasOwnProperty;
  121.  
  122. /**
  123. * Used to resolve the
  124. * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
  125. * of values.
  126. */
  127. var nativeObjectToString$1 = objectProto$1.toString;
  128.  
  129. /** Built-in value references. */
  130. var symToStringTag$1 = Symbol ? Symbol.toStringTag : undefined;
  131.  
  132. /**
  133. * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
  134. *
  135. * @private
  136. * @param {*} value The value to query.
  137. * @returns {string} Returns the raw `toStringTag`.
  138. */
  139. function getRawTag(value) {
  140. var isOwn = hasOwnProperty.call(value, symToStringTag$1),
  141. tag = value[symToStringTag$1];
  142.  
  143. try {
  144. value[symToStringTag$1] = undefined;
  145. var unmasked = true;
  146. } catch (e) {}
  147.  
  148. var result = nativeObjectToString$1.call(value);
  149. if (unmasked) {
  150. if (isOwn) {
  151. value[symToStringTag$1] = tag;
  152. } else {
  153. delete value[symToStringTag$1];
  154. }
  155. }
  156. return result;
  157. }
  158.  
  159. /** Used for built-in method references. */
  160. var objectProto = Object.prototype;
  161.  
  162. /**
  163. * Used to resolve the
  164. * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
  165. * of values.
  166. */
  167. var nativeObjectToString = objectProto.toString;
  168.  
  169. /**
  170. * Converts `value` to a string using `Object.prototype.toString`.
  171. *
  172. * @private
  173. * @param {*} value The value to convert.
  174. * @returns {string} Returns the converted string.
  175. */
  176. function objectToString(value) {
  177. return nativeObjectToString.call(value);
  178. }
  179.  
  180. /** `Object#toString` result references. */
  181. var nullTag = '[object Null]',
  182. undefinedTag = '[object Undefined]';
  183.  
  184. /** Built-in value references. */
  185. var symToStringTag = Symbol ? Symbol.toStringTag : undefined;
  186.  
  187. /**
  188. * The base implementation of `getTag` without fallbacks for buggy environments.
  189. *
  190. * @private
  191. * @param {*} value The value to query.
  192. * @returns {string} Returns the `toStringTag`.
  193. */
  194. function baseGetTag(value) {
  195. if (value == null) {
  196. return value === undefined ? undefinedTag : nullTag;
  197. }
  198. return (symToStringTag && symToStringTag in Object(value))
  199. ? getRawTag(value)
  200. : objectToString(value);
  201. }
  202.  
  203. /**
  204. * Checks if `value` is object-like. A value is object-like if it's not `null`
  205. * and has a `typeof` result of "object".
  206. *
  207. * @static
  208. * @memberOf _
  209. * @since 4.0.0
  210. * @category Lang
  211. * @param {*} value The value to check.
  212. * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
  213. * @example
  214. *
  215. * _.isObjectLike({});
  216. * // => true
  217. *
  218. * _.isObjectLike([1, 2, 3]);
  219. * // => true
  220. *
  221. * _.isObjectLike(_.noop);
  222. * // => false
  223. *
  224. * _.isObjectLike(null);
  225. * // => false
  226. */
  227. function isObjectLike(value) {
  228. return value != null && typeof value == 'object';
  229. }
  230.  
  231. /** `Object#toString` result references. */
  232. var symbolTag = '[object Symbol]';
  233.  
  234. /**
  235. * Checks if `value` is classified as a `Symbol` primitive or object.
  236. *
  237. * @static
  238. * @memberOf _
  239. * @since 4.0.0
  240. * @category Lang
  241. * @param {*} value The value to check.
  242. * @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
  243. * @example
  244. *
  245. * _.isSymbol(Symbol.iterator);
  246. * // => true
  247. *
  248. * _.isSymbol('abc');
  249. * // => false
  250. */
  251. function isSymbol(value) {
  252. return typeof value == 'symbol' ||
  253. (isObjectLike(value) && baseGetTag(value) == symbolTag);
  254. }
  255.  
  256. /** Used as references for various `Number` constants. */
  257. var NAN = 0 / 0;
  258.  
  259. /** Used to detect bad signed hexadecimal string values. */
  260. var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
  261.  
  262. /** Used to detect binary string values. */
  263. var reIsBinary = /^0b[01]+$/i;
  264.  
  265. /** Used to detect octal string values. */
  266. var reIsOctal = /^0o[0-7]+$/i;
  267.  
  268. /** Built-in method references without a dependency on `root`. */
  269. var freeParseInt = parseInt;
  270.  
  271. /**
  272. * Converts `value` to a number.
  273. *
  274. * @static
  275. * @memberOf _
  276. * @since 4.0.0
  277. * @category Lang
  278. * @param {*} value The value to process.
  279. * @returns {number} Returns the number.
  280. * @example
  281. *
  282. * _.toNumber(3.2);
  283. * // => 3.2
  284. *
  285. * _.toNumber(Number.MIN_VALUE);
  286. * // => 5e-324
  287. *
  288. * _.toNumber(Infinity);
  289. * // => Infinity
  290. *
  291. * _.toNumber('3.2');
  292. * // => 3.2
  293. */
  294. function toNumber(value) {
  295. if (typeof value == 'number') {
  296. return value;
  297. }
  298. if (isSymbol(value)) {
  299. return NAN;
  300. }
  301. if (isObject(value)) {
  302. var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
  303. value = isObject(other) ? (other + '') : other;
  304. }
  305. if (typeof value != 'string') {
  306. return value === 0 ? value : +value;
  307. }
  308. value = baseTrim(value);
  309. var isBinary = reIsBinary.test(value);
  310. return (isBinary || reIsOctal.test(value))
  311. ? freeParseInt(value.slice(2), isBinary ? 2 : 8)
  312. : (reIsBadHex.test(value) ? NAN : +value);
  313. }
  314.  
  315. /** Error message constants. */
  316. var FUNC_ERROR_TEXT = 'Expected a function';
  317.  
  318. /* Built-in method references for those with the same name as other `lodash` methods. */
  319. var nativeMax = Math.max,
  320. nativeMin = Math.min;
  321.  
  322. /**
  323. * Creates a debounced function that delays invoking `func` until after `wait`
  324. * milliseconds have elapsed since the last time the debounced function was
  325. * invoked. The debounced function comes with a `cancel` method to cancel
  326. * delayed `func` invocations and a `flush` method to immediately invoke them.
  327. * Provide `options` to indicate whether `func` should be invoked on the
  328. * leading and/or trailing edge of the `wait` timeout. The `func` is invoked
  329. * with the last arguments provided to the debounced function. Subsequent
  330. * calls to the debounced function return the result of the last `func`
  331. * invocation.
  332. *
  333. * **Note:** If `leading` and `trailing` options are `true`, `func` is
  334. * invoked on the trailing edge of the timeout only if the debounced function
  335. * is invoked more than once during the `wait` timeout.
  336. *
  337. * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
  338. * until to the next tick, similar to `setTimeout` with a timeout of `0`.
  339. *
  340. * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
  341. * for details over the differences between `_.debounce` and `_.throttle`.
  342. *
  343. * @static
  344. * @memberOf _
  345. * @since 0.1.0
  346. * @category Function
  347. * @param {Function} func The function to debounce.
  348. * @param {number} [wait=0] The number of milliseconds to delay.
  349. * @param {Object} [options={}] The options object.
  350. * @param {boolean} [options.leading=false]
  351. * Specify invoking on the leading edge of the timeout.
  352. * @param {number} [options.maxWait]
  353. * The maximum time `func` is allowed to be delayed before it's invoked.
  354. * @param {boolean} [options.trailing=true]
  355. * Specify invoking on the trailing edge of the timeout.
  356. * @returns {Function} Returns the new debounced function.
  357. * @example
  358. *
  359. * // Avoid costly calculations while the window size is in flux.
  360. * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
  361. *
  362. * // Invoke `sendMail` when clicked, debouncing subsequent calls.
  363. * jQuery(element).on('click', _.debounce(sendMail, 300, {
  364. * 'leading': true,
  365. * 'trailing': false
  366. * }));
  367. *
  368. * // Ensure `batchLog` is invoked once after 1 second of debounced calls.
  369. * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
  370. * var source = new EventSource('/stream');
  371. * jQuery(source).on('message', debounced);
  372. *
  373. * // Cancel the trailing debounced invocation.
  374. * jQuery(window).on('popstate', debounced.cancel);
  375. */
  376. function debounce(func, wait, options) {
  377. var lastArgs,
  378. lastThis,
  379. maxWait,
  380. result,
  381. timerId,
  382. lastCallTime,
  383. lastInvokeTime = 0,
  384. leading = false,
  385. maxing = false,
  386. trailing = true;
  387.  
  388. if (typeof func != 'function') {
  389. throw new TypeError(FUNC_ERROR_TEXT);
  390. }
  391. wait = toNumber(wait) || 0;
  392. if (isObject(options)) {
  393. leading = !!options.leading;
  394. maxing = 'maxWait' in options;
  395. maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
  396. trailing = 'trailing' in options ? !!options.trailing : trailing;
  397. }
  398.  
  399. function invokeFunc(time) {
  400. var args = lastArgs,
  401. thisArg = lastThis;
  402.  
  403. lastArgs = lastThis = undefined;
  404. lastInvokeTime = time;
  405. result = func.apply(thisArg, args);
  406. return result;
  407. }
  408.  
  409. function leadingEdge(time) {
  410. // Reset any `maxWait` timer.
  411. lastInvokeTime = time;
  412. // Start the timer for the trailing edge.
  413. timerId = setTimeout(timerExpired, wait);
  414. // Invoke the leading edge.
  415. return leading ? invokeFunc(time) : result;
  416. }
  417.  
  418. function remainingWait(time) {
  419. var timeSinceLastCall = time - lastCallTime,
  420. timeSinceLastInvoke = time - lastInvokeTime,
  421. timeWaiting = wait - timeSinceLastCall;
  422.  
  423. return maxing
  424. ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke)
  425. : timeWaiting;
  426. }
  427.  
  428. function shouldInvoke(time) {
  429. var timeSinceLastCall = time - lastCallTime,
  430. timeSinceLastInvoke = time - lastInvokeTime;
  431.  
  432. // Either this is the first call, activity has stopped and we're at the
  433. // trailing edge, the system time has gone backwards and we're treating
  434. // it as the trailing edge, or we've hit the `maxWait` limit.
  435. return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
  436. (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
  437. }
  438.  
  439. function timerExpired() {
  440. var time = now();
  441. if (shouldInvoke(time)) {
  442. return trailingEdge(time);
  443. }
  444. // Restart the timer.
  445. timerId = setTimeout(timerExpired, remainingWait(time));
  446. }
  447.  
  448. function trailingEdge(time) {
  449. timerId = undefined;
  450.  
  451. // Only invoke if we have `lastArgs` which means `func` has been
  452. // debounced at least once.
  453. if (trailing && lastArgs) {
  454. return invokeFunc(time);
  455. }
  456. lastArgs = lastThis = undefined;
  457. return result;
  458. }
  459.  
  460. function cancel() {
  461. if (timerId !== undefined) {
  462. clearTimeout(timerId);
  463. }
  464. lastInvokeTime = 0;
  465. lastArgs = lastCallTime = lastThis = timerId = undefined;
  466. }
  467.  
  468. function flush() {
  469. return timerId === undefined ? result : trailingEdge(now());
  470. }
  471.  
  472. function debounced() {
  473. var time = now(),
  474. isInvoking = shouldInvoke(time);
  475.  
  476. lastArgs = arguments;
  477. lastThis = this;
  478. lastCallTime = time;
  479.  
  480. if (isInvoking) {
  481. if (timerId === undefined) {
  482. return leadingEdge(lastCallTime);
  483. }
  484. if (maxing) {
  485. // Handle invocations in a tight loop.
  486. clearTimeout(timerId);
  487. timerId = setTimeout(timerExpired, wait);
  488. return invokeFunc(lastCallTime);
  489. }
  490. }
  491. if (timerId === undefined) {
  492. timerId = setTimeout(timerExpired, wait);
  493. }
  494. return result;
  495. }
  496. debounced.cancel = cancel;
  497. debounced.flush = flush;
  498. return debounced;
  499. }
  500.  
  501. function createStorage(storage) {
  502. function getItem(key, defaultValue) {
  503. try {
  504. const value = storage.getItem(key);
  505. if (value)
  506. return JSON.parse(value);
  507. return defaultValue;
  508. } catch (error) {
  509. return defaultValue;
  510. }
  511. }
  512. return {
  513. getItem,
  514. setItem(key, value) {
  515. storage.setItem(key, JSON.stringify(value));
  516. },
  517. removeItem: storage.removeItem.bind(storage),
  518. clear: storage.clear.bind(storage)
  519. };
  520. }
  521. const session = createStorage(window.sessionStorage);
  522. createStorage(window.localStorage);
  523.  
  524. function matcher(source, regexp) {
  525. if (typeof regexp === "string")
  526. return source.includes(regexp);
  527. return !!source.match(regexp);
  528. }
  529. function router(config) {
  530. const opts = {
  531. routes: []
  532. };
  533. if ("routes" in config) {
  534. opts.domain = config.domain;
  535. opts.routes = config.routes;
  536. } else {
  537. opts.routes = Array.isArray(config) ? config : [config];
  538. }
  539. if (opts.domain) {
  540. const match = matcher(window.location.origin, opts.domain);
  541. if (!match)
  542. return;
  543. }
  544. const pathSource = window.location.pathname + window.location.search + window.location.hash;
  545. if (typeof opts.routes === "function") {
  546. opts.routes();
  547. return;
  548. }
  549. const routes = Array.isArray(opts.routes) ? opts.routes : [opts.routes];
  550. routes.forEach((route) => {
  551. let match = true;
  552. if (route.path) {
  553. match = matcher(pathSource, route.path);
  554. }
  555. if (route.pathname) {
  556. match = matcher(window.location.pathname, route.pathname);
  557. }
  558. if (route.search) {
  559. match = matcher(window.location.search, route.search);
  560. }
  561. if (route.hash) {
  562. match = matcher(window.location.hash, route.hash);
  563. }
  564. if (match)
  565. route.run();
  566. });
  567. }
  568.  
  569. var __defProp = Object.defineProperty;
  570. var __defProps = Object.defineProperties;
  571. var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
  572. var __getOwnPropSymbols = Object.getOwnPropertySymbols;
  573. var __hasOwnProp = Object.prototype.hasOwnProperty;
  574. var __propIsEnum = Object.prototype.propertyIsEnumerable;
  575. var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
  576. var __spreadValues = (a, b) => {
  577. for (var prop in b || (b = {}))
  578. if (__hasOwnProp.call(b, prop))
  579. __defNormalProp(a, prop, b[prop]);
  580. if (__getOwnPropSymbols)
  581. for (var prop of __getOwnPropSymbols(b)) {
  582. if (__propIsEnum.call(b, prop))
  583. __defNormalProp(a, prop, b[prop]);
  584. }
  585. return a;
  586. };
  587. var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
  588. const store = {};
  589. function parseI3(i3) {
  590. return i3.match(new RegExp(`'(?<key>.*)'.*src="(?<src>.*?")(.*nl\\('(?<nl>.*)'\\))?`)).groups;
  591. }
  592. let isLoadEnd = false;
  593. function setupInfiniteScroll() {
  594. function api_call(page2, nextImgKey2) {
  595. return new Promise((resolve, reject) => {
  596. const xhr = new XMLHttpRequest();
  597. xhr.open("POST", window.api_url);
  598. xhr.setRequestHeader("Content-Type", "application/json");
  599. xhr.withCredentials = true;
  600. xhr.onreadystatechange = () => {
  601. if (xhr.readyState === xhr.DONE) {
  602. resolve(JSON.parse(xhr.responseText));
  603. }
  604. };
  605. xhr.send(
  606. JSON.stringify({
  607. method: "showpage",
  608. gid: window.gid,
  609. page: page2,
  610. imgkey: nextImgKey2,
  611. showkey: window.showkey
  612. })
  613. );
  614. });
  615. }
  616. const maxPageSize = parseInt(
  617. document.querySelector("#i2 > div.sn > div > span:nth-child(2)").textContent
  618. );
  619. let nextImgKey = document.querySelector("#i3 a[onclick]").onclick.toString().match(new RegExp("'(?<key>.*)'")).groups.key;
  620. let page = window.startpage + 1;
  621. let isLoading = false;
  622. async function loadImgInfo() {
  623. if (maxPageSize < page) {
  624. isLoadEnd = true;
  625. return;
  626. }
  627. if (isLoading)
  628. return;
  629. isLoading = true;
  630. const res = await api_call(page, nextImgKey);
  631. isLoading = false;
  632. const groups = parseI3(res.i3);
  633. const info = {
  634. key: res.k,
  635. nl: groups.nl,
  636. src: groups.src.slice(0, -1),
  637. source: res.s[0] === "/" ? res.s : "/" + res.s
  638. };
  639. store[res.k] = { info, res };
  640. renderImg(page, info);
  641. nextImgKey = groups.key;
  642. page++;
  643. }
  644. function renderImg(page2, info) {
  645. const { key, source } = info;
  646. const img = document.createElement("img");
  647. img.src = "data:image/svg+xml,%3Csvg class='loading-icon' viewBox='0 0 1024 1024' version='1.1' xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cpath d='M512 0a512 512 0 0 1 512 512h-64a448 448 0 0 0-448-448V0z' fill='%23999'%3E%3C/path%3E%3Cstyle%3E%0A.loading-icon %7B animation: rotate 1s infinite linear; %7D%0A@keyframes rotate %7B from %7B transform: rotate(0); %7D to %7B transform: rotate(360deg); %7D %7D%0A%3C/style%3E%3C/svg%3E";
  648. img.dataset.imgKey = key;
  649. img.dataset.page = page2 + "";
  650. img.dataset.source = source;
  651. img.classList.add("auto-load-img", "auto-load-img-empty");
  652. img.alt = source;
  653. loadImg(img, info);
  654. document.getElementById("i3").append(img);
  655. }
  656. function loadImg(imgDOM, info) {
  657. const { source, src, nl } = info;
  658. const img = new Image();
  659. img.onload = () => {
  660. imgDOM.src = src;
  661. imgDOM.classList.remove("auto-load-img-empty");
  662. };
  663. img.onerror = () => {
  664. imgDOM.alt = `\u56FE\u7247\u52A0\u8F7D\u51FA\u9519 ${source}?nl=${nl}`;
  665. retry(imgDOM, info);
  666. };
  667. img.src = src;
  668. }
  669. function retry(img, info) {
  670. const iframe = document.createElement("iframe");
  671. iframe.style.cssText = "position:fixed;width:0;height:0;opacity:0;left:0;top:0;";
  672. const url = new URL(info.source, location.origin);
  673. url.searchParams.set("nl", info.nl);
  674. iframe.src = url.toString();
  675. document.body.append(iframe);
  676. iframe.contentWindow.addEventListener("DOMContentLoaded", () => {
  677. const src = iframe.contentWindow.document.querySelector("#i3 a img").getAttribute("src");
  678. loadImg(img, __spreadProps(__spreadValues({}, info), { src }));
  679. iframe.remove();
  680. });
  681. }
  682. function resetDefaultImgDOM() {
  683. const groups = parseI3(document.querySelector("#i3").innerHTML);
  684. store[window.startkey] = {
  685. info: {
  686. key: window.startkey,
  687. nl: groups.nl,
  688. src: groups.src,
  689. source: location.pathname
  690. },
  691. res: {
  692. i: document.querySelector("#i4 > div").outerHTML,
  693. i3: document.querySelector("#i3").innerHTML,
  694. n: document.querySelector("#i4 > .sn").outerHTML,
  695. i5: document.querySelector("#i5").innerHTML,
  696. i6: document.querySelector("#i6").innerHTML,
  697. i7: document.querySelector("#i7").innerHTML,
  698. k: window.startkey,
  699. s: location.pathname
  700. }
  701. };
  702. const $img = document.querySelector("#i3 a img");
  703. $img.removeAttribute("style");
  704. $img.classList.add("auto-load-img");
  705. $img.dataset.imgKey = window.startkey;
  706. $img.dataset.source = location.pathname;
  707. document.getElementById("i3").append($img);
  708. document.querySelector("#i3 a").remove();
  709. removeSnAnchor();
  710. }
  711. document.body.classList.add("e-hentai-infinite-scroll", "s");
  712. resetDefaultImgDOM();
  713. loadImgInfo();
  714. document.addEventListener("scroll", () => {
  715. const dom = document.scrollingElement;
  716. if (dom.scrollHeight <= dom.scrollTop + dom.clientHeight + 2e3) {
  717. loadImgInfo();
  718. }
  719. updateCurrentInfo();
  720. });
  721. }
  722. function removeSnAnchor() {
  723. document.querySelectorAll(".sn a[onclick]").forEach((a) => {
  724. a.removeAttribute("onclick");
  725. });
  726. }
  727. function getCurrentActiveImg() {
  728. const imgs = document.querySelectorAll("#i3 img");
  729. for (const img of imgs) {
  730. const { top, bottom } = img.getBoundingClientRect();
  731. const base = 200;
  732. if (top < base && bottom > base) {
  733. return img;
  734. }
  735. }
  736. return null;
  737. }
  738. function updateCurrentPathname($img) {
  739. const source = $img.dataset.source;
  740. history.replaceState(null, "", source);
  741. }
  742. function updateBottomInfo($img) {
  743. const key = $img.dataset.imgKey;
  744. const { res } = store[key];
  745. document.querySelector("#i2").innerHTML = res.n + res.i;
  746. document.querySelector("#i4").innerHTML = res.i + res.n;
  747. document.querySelector("#i5").innerHTML = res.i5;
  748. document.querySelector("#i6").innerHTML = res.i6;
  749. document.querySelector("#i7").innerHTML = res.i7;
  750. removeSnAnchor();
  751. }
  752. const updateCurrentInfo = debounce(function() {
  753. const $img = getCurrentActiveImg();
  754. if (!$img)
  755. return;
  756. const source = $img.dataset.source;
  757. if (location.pathname === source)
  758. return;
  759. updateCurrentPathname($img);
  760. updateBottomInfo($img);
  761. }, 30);
  762. function setupBottomInfo() {
  763. const $root = document.querySelector("#i1");
  764. const $wrapper = document.createElement("div");
  765. $wrapper.className = "ehis-bottom-info-wrapper";
  766. $root.insertBefore($wrapper, document.querySelector("#i4"));
  767. const $container = document.createElement("div");
  768. $container.className = "ehis-bottom-info-container";
  769. $container.style.background = getComputedStyle($root).background;
  770. $wrapper.append($container);
  771. $container.append(...document.querySelectorAll("#i4,#i5,#i6,#i7"));
  772. const calcSticky = () => {
  773. const dom = document.scrollingElement;
  774. if (isLoadEnd) {
  775. if (dom.scrollHeight <= dom.scrollTop + dom.clientHeight + 200) {
  776. $wrapper.classList.add("static");
  777. } else {
  778. $wrapper.classList.remove("static");
  779. }
  780. }
  781. };
  782. calcSticky();
  783. document.addEventListener("scroll", () => {
  784. calcSticky();
  785. });
  786. }
  787. function setup$1() {
  788. setupInfiniteScroll();
  789. setupBottomInfo();
  790. }
  791.  
  792. const $ = (selector) => document.querySelector(selector);
  793. const $$ = (selector) => document.querySelectorAll(selector);
  794. function getPageInfo() {
  795. const rows = +$("#gdo2 .ths").textContent.replace(" rows", "");
  796. const mode = $("#gdo4 .ths").textContent.toLowerCase();
  797. const pageSize = (mode === "normal" ? 10 : 5) * rows;
  798. const total = +$(".gtb p.gpc").textContent.match(
  799. new RegExp("of\\s(?<total>[0-9,]+)\\simages")
  800. ).groups.total;
  801. const url = new URL(window.location.href);
  802. let currentPage = 0;
  803. if (url.searchParams.has("p")) {
  804. currentPage = +url.searchParams.get("p");
  805. }
  806. const pageCount = +$(".gtb .ptb td:nth-last-child(2)").textContent;
  807. const unloadPageCount = pageCount - 1 - currentPage;
  808. let unloadPageLinks = Array(unloadPageCount).fill(0).map((_, i) => {
  809. url.searchParams.set("p", 1 + currentPage + i + "");
  810. return url.toString();
  811. });
  812. return {
  813. rows,
  814. mode,
  815. url,
  816. total,
  817. currentPage,
  818. pageSize,
  819. pageCount,
  820. unloadPageLinks,
  821. childrenClass: mode === "normal" ? "#gdt .gdtm" : "#gdt .gdtl"
  822. };
  823. }
  824. async function fetchNextDom(url, info) {
  825. const storageKey = url + info.mode;
  826. let html = session.getItem(storageKey) || await fetch(url).then((r) => r.text());
  827. const doc = new DOMParser().parseFromString(html, "text/html");
  828. if (doc.querySelector("#gdt")) {
  829. info.currentPage++;
  830. session.setItem(storageKey, html);
  831. const items = doc.querySelectorAll(info.childrenClass);
  832. items.forEach((node) => {
  833. node.setAttribute("data-page", info.currentPage + "");
  834. });
  835. return items;
  836. } else {
  837. return null;
  838. }
  839. }
  840. let isLoading = false;
  841. async function loadNextPage(info, mode) {
  842. if (isLoading)
  843. return;
  844. let url = info.unloadPageLinks.shift();
  845. if (url) {
  846. isLoading = true;
  847. const items = await fetchNextDom(url, info);
  848. isLoading = false;
  849. if (items) {
  850. if (mode === "large") {
  851. createPageIndex(info.currentPage);
  852. }
  853. $("#gdt").append(...items);
  854. $$("#gdt .c").forEach((node) => node.remove());
  855. }
  856. }
  857. }
  858. function createPageIndex(currentPage) {
  859. const dom = document.createElement("div");
  860. dom.innerText = currentPage + 1 + "";
  861. dom.className = "g-scroll-page-index";
  862. $("#gdt").append(dom);
  863. }
  864. function tinyGallery() {
  865. const info = getPageInfo();
  866. const handleScroll = () => {
  867. const dom = document.scrollingElement;
  868. if ($("#cdiv").getBoundingClientRect().y <= dom.scrollTop + dom.clientHeight + 2e3) {
  869. loadNextPage(info, "tiny");
  870. }
  871. };
  872. document.addEventListener("scroll", handleScroll);
  873. }
  874. function largeGallery() {
  875. const info = getPageInfo();
  876. $("#gdt").classList.add("g-scroll-body");
  877. $$(info.childrenClass).forEach((node) => {
  878. node.setAttribute("data-page", info.currentPage + "");
  879. });
  880. const replaceCurrentURL = debounce(function() {
  881. const imgs = document.querySelectorAll(info.childrenClass);
  882. const rect = $("#gdt").getBoundingClientRect();
  883. const base = rect.top + rect.height / 2;
  884. for (const img of imgs) {
  885. const { top, bottom } = img.getBoundingClientRect();
  886. if (top < base && bottom > base) {
  887. const page = img.dataset.page;
  888. const url = new URL(window.location.href);
  889. if (+page === 0) {
  890. url.searchParams.delete("p");
  891. } else {
  892. url.searchParams.set("p", page);
  893. }
  894. if (window.location.href !== url.toString()) {
  895. history.replaceState(null, "", url);
  896. const activeElement = (node, idx) => {
  897. node.className = "";
  898. if (idx === +page + 1) {
  899. node.className = "ptds";
  900. }
  901. };
  902. $$(".gtb .ptt td").forEach(activeElement);
  903. $$(".gtb .ptb td").forEach(activeElement);
  904. }
  905. return;
  906. }
  907. }
  908. }, 30);
  909. const handleScroll = () => {
  910. const dom = $("#gdt");
  911. if (dom.scrollHeight - 2e3 < dom.scrollTop + dom.clientHeight) {
  912. loadNextPage(info, "large");
  913. }
  914. replaceCurrentURL();
  915. };
  916. handleScroll();
  917. $("#gdt").addEventListener("scroll", handleScroll);
  918. }
  919. function addWatchTag(tag) {
  920. return fetch("/mytags", {
  921. method: "POST",
  922. body: new URLSearchParams({
  923. usertag_action: "add",
  924. tagname_new: tag,
  925. tagwatch_new: "on",
  926. tagcolor_new: "",
  927. tagweight_new: "10",
  928. usertag_target: "0"
  929. })
  930. });
  931. }
  932. async function injectWatchTag() {
  933. const node = document.querySelector("#tagmenu_act");
  934. const inject = () => {
  935. const img = document.createElement("img");
  936. const a = document.createElement("a");
  937. const br = document.createElement("br");
  938. node.append(br, img, a);
  939. img.outerHTML = '<img src="https://ehgt.org/g/mr.gif" class="mr" alt=">"> ';
  940. a.href = "#";
  941. a.textContent = "Watch";
  942. a.addEventListener("click", (e) => {
  943. e.preventDefault();
  944. if (window.selected_tag) {
  945. addWatchTag(window.selected_tag).then(() => {
  946. alert("success");
  947. }).catch((error) => {
  948. console.error(error);
  949. alert(error.message);
  950. });
  951. }
  952. });
  953. };
  954. const ob = new MutationObserver(() => {
  955. if (node.style.display !== "none") {
  956. inject();
  957. }
  958. });
  959. ob.observe(node, { attributes: true });
  960. }
  961. async function setup() {
  962. injectWatchTag();
  963. const info = getPageInfo();
  964. $("body").classList.add("e-hentai-infinite-scroll");
  965. if (!info.unloadPageLinks.length)
  966. return;
  967. if (info.unloadPageLinks.length > 2) {
  968. largeGallery();
  969. } else {
  970. tinyGallery();
  971. }
  972. }
  973.  
  974. router([
  975. { pathname: /\/g\/.*\/.*/, run: setup },
  976. { pathname: /\/s\/.*\/.*/, run: setup$1 }
  977. ]);
  978.  
  979. })();