e-hentai-infinite-scroll

Exhentai infinite scroll scripts.

  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.3.10
  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. /** Detect free variable `global` from Node.js. */
  16. var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
  17.  
  18. /** Detect free variable `self`. */
  19. var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
  20.  
  21. /** Used as a reference to the global object. */
  22. var root = freeGlobal || freeSelf || Function('return this')();
  23.  
  24. /** Built-in value references. */
  25. var Symbol = root.Symbol;
  26.  
  27. /** Used for built-in method references. */
  28. var objectProto$1 = Object.prototype;
  29.  
  30. /** Used to check objects for own properties. */
  31. var hasOwnProperty = objectProto$1.hasOwnProperty;
  32.  
  33. /**
  34. * Used to resolve the
  35. * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
  36. * of values.
  37. */
  38. var nativeObjectToString$1 = objectProto$1.toString;
  39.  
  40. /** Built-in value references. */
  41. var symToStringTag$1 = Symbol ? Symbol.toStringTag : undefined;
  42.  
  43. /**
  44. * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
  45. *
  46. * @private
  47. * @param {*} value The value to query.
  48. * @returns {string} Returns the raw `toStringTag`.
  49. */
  50. function getRawTag(value) {
  51. var isOwn = hasOwnProperty.call(value, symToStringTag$1),
  52. tag = value[symToStringTag$1];
  53.  
  54. try {
  55. value[symToStringTag$1] = undefined;
  56. var unmasked = true;
  57. } catch (e) {}
  58.  
  59. var result = nativeObjectToString$1.call(value);
  60. if (unmasked) {
  61. if (isOwn) {
  62. value[symToStringTag$1] = tag;
  63. } else {
  64. delete value[symToStringTag$1];
  65. }
  66. }
  67. return result;
  68. }
  69.  
  70. /** Used for built-in method references. */
  71. var objectProto = Object.prototype;
  72.  
  73. /**
  74. * Used to resolve the
  75. * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
  76. * of values.
  77. */
  78. var nativeObjectToString = objectProto.toString;
  79.  
  80. /**
  81. * Converts `value` to a string using `Object.prototype.toString`.
  82. *
  83. * @private
  84. * @param {*} value The value to convert.
  85. * @returns {string} Returns the converted string.
  86. */
  87. function objectToString(value) {
  88. return nativeObjectToString.call(value);
  89. }
  90.  
  91. /** `Object#toString` result references. */
  92. var nullTag = '[object Null]',
  93. undefinedTag = '[object Undefined]';
  94.  
  95. /** Built-in value references. */
  96. var symToStringTag = Symbol ? Symbol.toStringTag : undefined;
  97.  
  98. /**
  99. * The base implementation of `getTag` without fallbacks for buggy environments.
  100. *
  101. * @private
  102. * @param {*} value The value to query.
  103. * @returns {string} Returns the `toStringTag`.
  104. */
  105. function baseGetTag(value) {
  106. if (value == null) {
  107. return value === undefined ? undefinedTag : nullTag;
  108. }
  109. return (symToStringTag && symToStringTag in Object(value))
  110. ? getRawTag(value)
  111. : objectToString(value);
  112. }
  113.  
  114. /**
  115. * Checks if `value` is object-like. A value is object-like if it's not `null`
  116. * and has a `typeof` result of "object".
  117. *
  118. * @static
  119. * @memberOf _
  120. * @since 4.0.0
  121. * @category Lang
  122. * @param {*} value The value to check.
  123. * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
  124. * @example
  125. *
  126. * _.isObjectLike({});
  127. * // => true
  128. *
  129. * _.isObjectLike([1, 2, 3]);
  130. * // => true
  131. *
  132. * _.isObjectLike(_.noop);
  133. * // => false
  134. *
  135. * _.isObjectLike(null);
  136. * // => false
  137. */
  138. function isObjectLike(value) {
  139. return value != null && typeof value == 'object';
  140. }
  141.  
  142. /** `Object#toString` result references. */
  143. var symbolTag = '[object Symbol]';
  144.  
  145. /**
  146. * Checks if `value` is classified as a `Symbol` primitive or object.
  147. *
  148. * @static
  149. * @memberOf _
  150. * @since 4.0.0
  151. * @category Lang
  152. * @param {*} value The value to check.
  153. * @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
  154. * @example
  155. *
  156. * _.isSymbol(Symbol.iterator);
  157. * // => true
  158. *
  159. * _.isSymbol('abc');
  160. * // => false
  161. */
  162. function isSymbol(value) {
  163. return typeof value == 'symbol' ||
  164. (isObjectLike(value) && baseGetTag(value) == symbolTag);
  165. }
  166.  
  167. /** Used to match a single whitespace character. */
  168. var reWhitespace = /\s/;
  169.  
  170. /**
  171. * Used by `_.trim` and `_.trimEnd` to get the index of the last non-whitespace
  172. * character of `string`.
  173. *
  174. * @private
  175. * @param {string} string The string to inspect.
  176. * @returns {number} Returns the index of the last non-whitespace character.
  177. */
  178. function trimmedEndIndex(string) {
  179. var index = string.length;
  180.  
  181. while (index-- && reWhitespace.test(string.charAt(index))) {}
  182. return index;
  183. }
  184.  
  185. /** Used to match leading whitespace. */
  186. var reTrimStart = /^\s+/;
  187.  
  188. /**
  189. * The base implementation of `_.trim`.
  190. *
  191. * @private
  192. * @param {string} string The string to trim.
  193. * @returns {string} Returns the trimmed string.
  194. */
  195. function baseTrim(string) {
  196. return string
  197. ? string.slice(0, trimmedEndIndex(string) + 1).replace(reTrimStart, '')
  198. : string;
  199. }
  200.  
  201. /**
  202. * Checks if `value` is the
  203. * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
  204. * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
  205. *
  206. * @static
  207. * @memberOf _
  208. * @since 0.1.0
  209. * @category Lang
  210. * @param {*} value The value to check.
  211. * @returns {boolean} Returns `true` if `value` is an object, else `false`.
  212. * @example
  213. *
  214. * _.isObject({});
  215. * // => true
  216. *
  217. * _.isObject([1, 2, 3]);
  218. * // => true
  219. *
  220. * _.isObject(_.noop);
  221. * // => true
  222. *
  223. * _.isObject(null);
  224. * // => false
  225. */
  226. function isObject(value) {
  227. var type = typeof value;
  228. return value != null && (type == 'object' || type == 'function');
  229. }
  230.  
  231. /** Used as references for various `Number` constants. */
  232. var NAN = 0 / 0;
  233.  
  234. /** Used to detect bad signed hexadecimal string values. */
  235. var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
  236.  
  237. /** Used to detect binary string values. */
  238. var reIsBinary = /^0b[01]+$/i;
  239.  
  240. /** Used to detect octal string values. */
  241. var reIsOctal = /^0o[0-7]+$/i;
  242.  
  243. /** Built-in method references without a dependency on `root`. */
  244. var freeParseInt = parseInt;
  245.  
  246. /**
  247. * Converts `value` to a number.
  248. *
  249. * @static
  250. * @memberOf _
  251. * @since 4.0.0
  252. * @category Lang
  253. * @param {*} value The value to process.
  254. * @returns {number} Returns the number.
  255. * @example
  256. *
  257. * _.toNumber(3.2);
  258. * // => 3.2
  259. *
  260. * _.toNumber(Number.MIN_VALUE);
  261. * // => 5e-324
  262. *
  263. * _.toNumber(Infinity);
  264. * // => Infinity
  265. *
  266. * _.toNumber('3.2');
  267. * // => 3.2
  268. */
  269. function toNumber(value) {
  270. if (typeof value == 'number') {
  271. return value;
  272. }
  273. if (isSymbol(value)) {
  274. return NAN;
  275. }
  276. if (isObject(value)) {
  277. var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
  278. value = isObject(other) ? (other + '') : other;
  279. }
  280. if (typeof value != 'string') {
  281. return value === 0 ? value : +value;
  282. }
  283. value = baseTrim(value);
  284. var isBinary = reIsBinary.test(value);
  285. return (isBinary || reIsOctal.test(value))
  286. ? freeParseInt(value.slice(2), isBinary ? 2 : 8)
  287. : (reIsBadHex.test(value) ? NAN : +value);
  288. }
  289.  
  290. /**
  291. * Gets the timestamp of the number of milliseconds that have elapsed since
  292. * the Unix epoch (1 January 1970 00:00:00 UTC).
  293. *
  294. * @static
  295. * @memberOf _
  296. * @since 2.4.0
  297. * @category Date
  298. * @returns {number} Returns the timestamp.
  299. * @example
  300. *
  301. * _.defer(function(stamp) {
  302. * console.log(_.now() - stamp);
  303. * }, _.now());
  304. * // => Logs the number of milliseconds it took for the deferred invocation.
  305. */
  306. var now = function() {
  307. return root.Date.now();
  308. };
  309.  
  310. /** Error message constants. */
  311. var FUNC_ERROR_TEXT = 'Expected a function';
  312.  
  313. /* Built-in method references for those with the same name as other `lodash` methods. */
  314. var nativeMax = Math.max,
  315. nativeMin = Math.min;
  316.  
  317. /**
  318. * Creates a debounced function that delays invoking `func` until after `wait`
  319. * milliseconds have elapsed since the last time the debounced function was
  320. * invoked. The debounced function comes with a `cancel` method to cancel
  321. * delayed `func` invocations and a `flush` method to immediately invoke them.
  322. * Provide `options` to indicate whether `func` should be invoked on the
  323. * leading and/or trailing edge of the `wait` timeout. The `func` is invoked
  324. * with the last arguments provided to the debounced function. Subsequent
  325. * calls to the debounced function return the result of the last `func`
  326. * invocation.
  327. *
  328. * **Note:** If `leading` and `trailing` options are `true`, `func` is
  329. * invoked on the trailing edge of the timeout only if the debounced function
  330. * is invoked more than once during the `wait` timeout.
  331. *
  332. * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
  333. * until to the next tick, similar to `setTimeout` with a timeout of `0`.
  334. *
  335. * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
  336. * for details over the differences between `_.debounce` and `_.throttle`.
  337. *
  338. * @static
  339. * @memberOf _
  340. * @since 0.1.0
  341. * @category Function
  342. * @param {Function} func The function to debounce.
  343. * @param {number} [wait=0] The number of milliseconds to delay.
  344. * @param {Object} [options={}] The options object.
  345. * @param {boolean} [options.leading=false]
  346. * Specify invoking on the leading edge of the timeout.
  347. * @param {number} [options.maxWait]
  348. * The maximum time `func` is allowed to be delayed before it's invoked.
  349. * @param {boolean} [options.trailing=true]
  350. * Specify invoking on the trailing edge of the timeout.
  351. * @returns {Function} Returns the new debounced function.
  352. * @example
  353. *
  354. * // Avoid costly calculations while the window size is in flux.
  355. * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
  356. *
  357. * // Invoke `sendMail` when clicked, debouncing subsequent calls.
  358. * jQuery(element).on('click', _.debounce(sendMail, 300, {
  359. * 'leading': true,
  360. * 'trailing': false
  361. * }));
  362. *
  363. * // Ensure `batchLog` is invoked once after 1 second of debounced calls.
  364. * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
  365. * var source = new EventSource('/stream');
  366. * jQuery(source).on('message', debounced);
  367. *
  368. * // Cancel the trailing debounced invocation.
  369. * jQuery(window).on('popstate', debounced.cancel);
  370. */
  371. function debounce(func, wait, options) {
  372. var lastArgs,
  373. lastThis,
  374. maxWait,
  375. result,
  376. timerId,
  377. lastCallTime,
  378. lastInvokeTime = 0,
  379. leading = false,
  380. maxing = false,
  381. trailing = true;
  382.  
  383. if (typeof func != 'function') {
  384. throw new TypeError(FUNC_ERROR_TEXT);
  385. }
  386. wait = toNumber(wait) || 0;
  387. if (isObject(options)) {
  388. leading = !!options.leading;
  389. maxing = 'maxWait' in options;
  390. maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
  391. trailing = 'trailing' in options ? !!options.trailing : trailing;
  392. }
  393.  
  394. function invokeFunc(time) {
  395. var args = lastArgs,
  396. thisArg = lastThis;
  397.  
  398. lastArgs = lastThis = undefined;
  399. lastInvokeTime = time;
  400. result = func.apply(thisArg, args);
  401. return result;
  402. }
  403.  
  404. function leadingEdge(time) {
  405. // Reset any `maxWait` timer.
  406. lastInvokeTime = time;
  407. // Start the timer for the trailing edge.
  408. timerId = setTimeout(timerExpired, wait);
  409. // Invoke the leading edge.
  410. return leading ? invokeFunc(time) : result;
  411. }
  412.  
  413. function remainingWait(time) {
  414. var timeSinceLastCall = time - lastCallTime,
  415. timeSinceLastInvoke = time - lastInvokeTime,
  416. timeWaiting = wait - timeSinceLastCall;
  417.  
  418. return maxing
  419. ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke)
  420. : timeWaiting;
  421. }
  422.  
  423. function shouldInvoke(time) {
  424. var timeSinceLastCall = time - lastCallTime,
  425. timeSinceLastInvoke = time - lastInvokeTime;
  426.  
  427. // Either this is the first call, activity has stopped and we're at the
  428. // trailing edge, the system time has gone backwards and we're treating
  429. // it as the trailing edge, or we've hit the `maxWait` limit.
  430. return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
  431. (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
  432. }
  433.  
  434. function timerExpired() {
  435. var time = now();
  436. if (shouldInvoke(time)) {
  437. return trailingEdge(time);
  438. }
  439. // Restart the timer.
  440. timerId = setTimeout(timerExpired, remainingWait(time));
  441. }
  442.  
  443. function trailingEdge(time) {
  444. timerId = undefined;
  445.  
  446. // Only invoke if we have `lastArgs` which means `func` has been
  447. // debounced at least once.
  448. if (trailing && lastArgs) {
  449. return invokeFunc(time);
  450. }
  451. lastArgs = lastThis = undefined;
  452. return result;
  453. }
  454.  
  455. function cancel() {
  456. if (timerId !== undefined) {
  457. clearTimeout(timerId);
  458. }
  459. lastInvokeTime = 0;
  460. lastArgs = lastCallTime = lastThis = timerId = undefined;
  461. }
  462.  
  463. function flush() {
  464. return timerId === undefined ? result : trailingEdge(now());
  465. }
  466.  
  467. function debounced() {
  468. var time = now(),
  469. isInvoking = shouldInvoke(time);
  470.  
  471. lastArgs = arguments;
  472. lastThis = this;
  473. lastCallTime = time;
  474.  
  475. if (isInvoking) {
  476. if (timerId === undefined) {
  477. return leadingEdge(lastCallTime);
  478. }
  479. if (maxing) {
  480. // Handle invocations in a tight loop.
  481. clearTimeout(timerId);
  482. timerId = setTimeout(timerExpired, wait);
  483. return invokeFunc(lastCallTime);
  484. }
  485. }
  486. if (timerId === undefined) {
  487. timerId = setTimeout(timerExpired, wait);
  488. }
  489. return result;
  490. }
  491. debounced.cancel = cancel;
  492. debounced.flush = flush;
  493. return debounced;
  494. }
  495.  
  496. var __defProp = Object.defineProperty;
  497. var __defProps = Object.defineProperties;
  498. var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
  499. var __getOwnPropSymbols = Object.getOwnPropertySymbols;
  500. var __hasOwnProp = Object.prototype.hasOwnProperty;
  501. var __propIsEnum = Object.prototype.propertyIsEnumerable;
  502. var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
  503. var __spreadValues = (a, b) => {
  504. for (var prop in b || (b = {}))
  505. if (__hasOwnProp.call(b, prop))
  506. __defNormalProp(a, prop, b[prop]);
  507. if (__getOwnPropSymbols)
  508. for (var prop of __getOwnPropSymbols(b)) {
  509. if (__propIsEnum.call(b, prop))
  510. __defNormalProp(a, prop, b[prop]);
  511. }
  512. return a;
  513. };
  514. var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
  515. function set(arg1, arg2) {
  516. let options = {
  517. name: "",
  518. value: "",
  519. maxAge: 24 * 60 * 60,
  520. path: "/"
  521. };
  522. if (typeof arg1 === "object") {
  523. Object.assign(options, arg1);
  524. } else {
  525. options.name = arg1;
  526. options.value = arg2;
  527. }
  528. options.value = encodeURIComponent(options.value);
  529. document.cookie = [
  530. `${options.name}=${options.value}`,
  531. `max-age=${options.maxAge}`,
  532. !!options.domain && `domain=${options.domain}`,
  533. !!options.path && `path=${options.path}`,
  534. !!options.sameSite && `sameSite=${options.sameSite}`,
  535. !!options.secure && `secure`
  536. ].filter(Boolean).join(";");
  537. }
  538. function get(name) {
  539. let reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)");
  540. let arr = document.cookie.match(reg);
  541. if (arr) {
  542. return decodeURIComponent(arr[2]);
  543. } else {
  544. return null;
  545. }
  546. }
  547. function remove(arg1) {
  548. if (typeof arg1 === "string") {
  549. set({ name: arg1, value: "", maxAge: 0 });
  550. } else {
  551. set(__spreadProps(__spreadValues({}, arg1), { maxAge: 0 }));
  552. }
  553. }
  554. const Cookie = {
  555. get,
  556. set,
  557. remove
  558. };
  559.  
  560. var toggleSelection = function () {
  561. var selection = document.getSelection();
  562. if (!selection.rangeCount) {
  563. return function () {};
  564. }
  565. var active = document.activeElement;
  566.  
  567. var ranges = [];
  568. for (var i = 0; i < selection.rangeCount; i++) {
  569. ranges.push(selection.getRangeAt(i));
  570. }
  571.  
  572. switch (active.tagName.toUpperCase()) { // .toUpperCase handles XHTML
  573. case 'INPUT':
  574. case 'TEXTAREA':
  575. active.blur();
  576. break;
  577.  
  578. default:
  579. active = null;
  580. break;
  581. }
  582.  
  583. selection.removeAllRanges();
  584. return function () {
  585. selection.type === 'Caret' &&
  586. selection.removeAllRanges();
  587.  
  588. if (!selection.rangeCount) {
  589. ranges.forEach(function(range) {
  590. selection.addRange(range);
  591. });
  592. }
  593.  
  594. active &&
  595. active.focus();
  596. };
  597. };
  598.  
  599. var deselectCurrent = toggleSelection;
  600.  
  601. var clipboardToIE11Formatting = {
  602. "text/plain": "Text",
  603. "text/html": "Url",
  604. "default": "Text"
  605. };
  606.  
  607. var defaultMessage = "Copy to clipboard: #{key}, Enter";
  608.  
  609. function format(message) {
  610. var copyKey = (/mac os x/i.test(navigator.userAgent) ? "⌘" : "Ctrl") + "+C";
  611. return message.replace(/#{\s*key\s*}/g, copyKey);
  612. }
  613.  
  614. function copy(text, options) {
  615. var debug,
  616. message,
  617. reselectPrevious,
  618. range,
  619. selection,
  620. mark,
  621. success = false;
  622. if (!options) {
  623. options = {};
  624. }
  625. debug = options.debug || false;
  626. try {
  627. reselectPrevious = deselectCurrent();
  628.  
  629. range = document.createRange();
  630. selection = document.getSelection();
  631.  
  632. mark = document.createElement("span");
  633. mark.textContent = text;
  634. // reset user styles for span element
  635. mark.style.all = "unset";
  636. // prevents scrolling to the end of the page
  637. mark.style.position = "fixed";
  638. mark.style.top = 0;
  639. mark.style.clip = "rect(0, 0, 0, 0)";
  640. // used to preserve spaces and line breaks
  641. mark.style.whiteSpace = "pre";
  642. // do not inherit user-select (it may be `none`)
  643. mark.style.webkitUserSelect = "text";
  644. mark.style.MozUserSelect = "text";
  645. mark.style.msUserSelect = "text";
  646. mark.style.userSelect = "text";
  647. mark.addEventListener("copy", function(e) {
  648. e.stopPropagation();
  649. if (options.format) {
  650. e.preventDefault();
  651. if (typeof e.clipboardData === "undefined") { // IE 11
  652. debug && console.warn("unable to use e.clipboardData");
  653. debug && console.warn("trying IE specific stuff");
  654. window.clipboardData.clearData();
  655. var format = clipboardToIE11Formatting[options.format] || clipboardToIE11Formatting["default"];
  656. window.clipboardData.setData(format, text);
  657. } else { // all other browsers
  658. e.clipboardData.clearData();
  659. e.clipboardData.setData(options.format, text);
  660. }
  661. }
  662. if (options.onCopy) {
  663. e.preventDefault();
  664. options.onCopy(e.clipboardData);
  665. }
  666. });
  667.  
  668. document.body.appendChild(mark);
  669.  
  670. range.selectNodeContents(mark);
  671. selection.addRange(range);
  672.  
  673. var successful = document.execCommand("copy");
  674. if (!successful) {
  675. throw new Error("copy command was unsuccessful");
  676. }
  677. success = true;
  678. } catch (err) {
  679. debug && console.error("unable to copy using execCommand: ", err);
  680. debug && console.warn("trying IE specific stuff");
  681. try {
  682. window.clipboardData.setData(options.format || "text", text);
  683. options.onCopy && options.onCopy(window.clipboardData);
  684. success = true;
  685. } catch (err) {
  686. debug && console.error("unable to copy using clipboardData: ", err);
  687. debug && console.error("falling back to prompt");
  688. message = format("message" in options ? options.message : defaultMessage);
  689. window.prompt(message, text);
  690. }
  691. } finally {
  692. if (selection) {
  693. if (typeof selection.removeRange == "function") {
  694. selection.removeRange(range);
  695. } else {
  696. selection.removeAllRanges();
  697. }
  698. }
  699.  
  700. if (mark) {
  701. document.body.removeChild(mark);
  702. }
  703. reselectPrevious();
  704. }
  705.  
  706. return success;
  707. }
  708.  
  709. var copyToClipboard = copy;
  710.  
  711. var copy$1 = copyToClipboard;
  712.  
  713. function createStorage(storage) {
  714. function getItem(key, defaultValue) {
  715. try {
  716. const value = storage.getItem(key);
  717. if (value)
  718. return JSON.parse(value);
  719. return defaultValue;
  720. } catch (error) {
  721. return defaultValue;
  722. }
  723. }
  724. return {
  725. getItem,
  726. setItem(key, value) {
  727. storage.setItem(key, JSON.stringify(value));
  728. },
  729. removeItem: storage.removeItem.bind(storage),
  730. clear: storage.clear.bind(storage)
  731. };
  732. }
  733. const session = createStorage(window.sessionStorage);
  734. createStorage(window.localStorage);
  735.  
  736. function matcher(source, regexp) {
  737. if (typeof regexp === "string")
  738. return source.includes(regexp);
  739. return !!source.match(regexp);
  740. }
  741. function router(config) {
  742. const opts = {
  743. domain: "",
  744. routes: []
  745. };
  746. if ("routes" in config) {
  747. opts.domain = config.domain;
  748. opts.routes = config.routes;
  749. } else {
  750. opts.routes = Array.isArray(config) ? config : [config];
  751. }
  752. if (opts.domain) {
  753. const domains = Array.isArray(opts.domain) ? opts.domain : [opts.domain];
  754. const match = domains.some(
  755. (domain) => matcher(window.location.origin, domain)
  756. );
  757. if (!match)
  758. return;
  759. }
  760. const pathSource = window.location.pathname + window.location.search + window.location.hash;
  761. if (typeof opts.routes === "function") {
  762. opts.routes();
  763. return;
  764. }
  765. const routes = Array.isArray(opts.routes) ? opts.routes : [opts.routes];
  766. routes.forEach((route) => {
  767. let match = true;
  768. if (route.path) {
  769. match = matcher(pathSource, route.path);
  770. }
  771. if (route.pathname) {
  772. match = matcher(window.location.pathname, route.pathname);
  773. }
  774. if (route.search) {
  775. match = matcher(window.location.search, route.search);
  776. }
  777. if (route.hash) {
  778. match = matcher(window.location.hash, route.hash);
  779. }
  780. if (match)
  781. route.run();
  782. });
  783. }
  784.  
  785. 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}}
  786.  
  787. var css = ".e-hentai-infinite-scroll.g #gd2 > * {\n cursor: pointer;\n}\n.e-hentai-infinite-scroll.g #gd2 > *:active {\n color: #2af;\n text-decoration: underline;\n}\n.e-hentai-infinite-scroll.g #gdt::after {\n content: \"\";\n display: block;\n clear: both;\n}\n.e-hentai-infinite-scroll.g .g-scroll-body {\n display: grid;\n overflow: hidden auto;\n max-height: 80vh;\n}\n.e-hentai-infinite-scroll.g .g-scroll-body::-webkit-scrollbar {\n width: 8px;\n}\n.e-hentai-infinite-scroll.g .g-scroll-body::-webkit-scrollbar-thumb {\n background-color: rgba(255, 255, 255, 0.15);\n border-radius: 2px;\n}\n.e-hentai-infinite-scroll.g .g-scroll-body.large {\n grid-template-columns: repeat(5, 1fr);\n}\n@media screen and (max-width: 1230px) {\n .e-hentai-infinite-scroll.g .g-scroll-body.large {\n grid-template-columns: repeat(4, 1fr);\n }\n}\n@media screen and (max-width: 990px) {\n .e-hentai-infinite-scroll.g .g-scroll-body.large {\n grid-template-columns: repeat(3, 1fr);\n }\n}\n.e-hentai-infinite-scroll.g .g-scroll-body.normal {\n grid-template-columns: repeat(10, 1fr);\n}\n@media screen and (max-width: 1230px) {\n .e-hentai-infinite-scroll.g .g-scroll-body.normal {\n grid-template-columns: repeat(8, 1fr);\n }\n}\n@media screen and (max-width: 990px) {\n .e-hentai-infinite-scroll.g .g-scroll-body.normal {\n grid-template-columns: repeat(6, 1fr);\n }\n}\n.e-hentai-infinite-scroll.g .g-scroll-page-index {\n clear: both;\n grid-column: 1/-1;\n display: flex;\n justify-content: center;\n align-items: center;\n}\n.e-hentai-infinite-scroll.g .g-scroll-page-index::before, .e-hentai-infinite-scroll.g .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}";
  788. n(css,{});
  789.  
  790. const $$1 = (selector) => document.querySelector(selector);
  791. const $$ = (selector) => document.querySelectorAll(selector);
  792. function getPageInfo() {
  793. const mode = $$1("#gdt").className.includes("gt200") ? "large" : "normal";
  794. const pageSize = mode === "normal" ? 40 : 20;
  795. const total = +$$1(".gtb p.gpc").textContent.match(
  796. new RegExp("of\\s(?<total>[0-9,]+)\\simages")
  797. ).groups.total;
  798. const url = new URL(window.location.href);
  799. let currentPage = 0;
  800. if (url.searchParams.has("p")) {
  801. currentPage = +url.searchParams.get("p");
  802. }
  803. const pageCount = +$$1(".gtb .ptb td:nth-last-child(2)").textContent;
  804. const unloadPageCount = pageCount - 1 - currentPage;
  805. let unloadPageLinks = Array(unloadPageCount).fill(0).map((_, i) => {
  806. url.searchParams.set("p", 1 + currentPage + i + "");
  807. return url.toString();
  808. });
  809. return {
  810. mode,
  811. url,
  812. total,
  813. currentPage,
  814. pageSize,
  815. pageCount,
  816. unloadPageLinks,
  817. childrenClass: "#gdt > a"
  818. };
  819. }
  820. async function fetchNextDom(url, info) {
  821. const storageKey = url + info.mode;
  822. let html = session.getItem(storageKey) || await fetch(url).then((r) => r.text());
  823. const doc = new DOMParser().parseFromString(html, "text/html");
  824. if (doc.querySelector("#gdt")) {
  825. info.currentPage++;
  826. session.setItem(storageKey, html);
  827. const items = doc.querySelectorAll(info.childrenClass);
  828. items.forEach((node) => {
  829. node.setAttribute("data-page", info.currentPage + "");
  830. });
  831. return items;
  832. } else {
  833. return null;
  834. }
  835. }
  836. let isLoading = false;
  837. async function loadNextPage(info, mode) {
  838. if (isLoading)
  839. return;
  840. let url = info.unloadPageLinks.shift();
  841. if (url) {
  842. isLoading = true;
  843. const items = await fetchNextDom(url, info);
  844. isLoading = false;
  845. if (items) {
  846. createPageIndex(info.currentPage);
  847. $$1("#gdt").append(...items);
  848. $$("#gdt .c").forEach((node) => node.remove());
  849. }
  850. }
  851. }
  852. function createPageIndex(currentPage) {
  853. const dom = document.createElement("div");
  854. dom.innerText = currentPage + 1 + "";
  855. dom.className = "g-scroll-page-index";
  856. $$1("#gdt").append(dom);
  857. }
  858. function tinyGallery() {
  859. const info = getPageInfo();
  860. const handleScroll = () => {
  861. const dom = document.scrollingElement;
  862. if ($$1("#cdiv").getBoundingClientRect().y <= dom.scrollTop + dom.clientHeight + 2e3) {
  863. loadNextPage(info);
  864. }
  865. };
  866. document.addEventListener("scroll", handleScroll);
  867. }
  868. function largeGallery() {
  869. const info = getPageInfo();
  870. $$1("#gdt").classList.add("g-scroll-body", info.mode);
  871. $$(info.childrenClass).forEach((node) => {
  872. node.setAttribute("data-page", info.currentPage + "");
  873. });
  874. const replaceCurrentURL = debounce(function() {
  875. const imgs = document.querySelectorAll(info.childrenClass);
  876. const rect = $$1("#gdt").getBoundingClientRect();
  877. const base = rect.top + rect.height / 2;
  878. for (const img of imgs) {
  879. const { top, bottom } = img.getBoundingClientRect();
  880. if (top < base && bottom > base) {
  881. const page = img.dataset.page;
  882. const url = new URL(window.location.href);
  883. if (+page === 0) {
  884. url.searchParams.delete("p");
  885. } else {
  886. url.searchParams.set("p", page);
  887. }
  888. if (window.location.href !== url.toString()) {
  889. history.replaceState(null, "", url);
  890. const activeElement = (node, idx) => {
  891. node.className = "";
  892. if (idx === +page + 1) {
  893. node.className = "ptds";
  894. }
  895. };
  896. $$(".gtb .ptt td").forEach(activeElement);
  897. $$(".gtb .ptb td").forEach(activeElement);
  898. }
  899. return;
  900. }
  901. }
  902. }, 30);
  903. const handleScroll = () => {
  904. const dom = $$1("#gdt");
  905. if (dom.scrollHeight - 2e3 < dom.scrollTop + dom.clientHeight) {
  906. loadNextPage(info);
  907. }
  908. replaceCurrentURL();
  909. };
  910. handleScroll();
  911. $$1("#gdt").addEventListener("scroll", handleScroll);
  912. }
  913. function addWatchTag(tag) {
  914. return fetch("/mytags", {
  915. method: "POST",
  916. body: new URLSearchParams({
  917. usertag_action: "add",
  918. tagname_new: tag,
  919. tagwatch_new: "on",
  920. tagcolor_new: "",
  921. tagweight_new: "10",
  922. usertag_target: "0"
  923. })
  924. });
  925. }
  926. async function injectWatchTag() {
  927. const node = document.querySelector("#tagmenu_act");
  928. const inject = () => {
  929. const img = document.createElement("img");
  930. const a = document.createElement("a");
  931. const br = document.createElement("br");
  932. node.append(br, img, a);
  933. img.outerHTML = '<img src="https://ehgt.org/g/mr.gif" class="mr" alt=">"> ';
  934. a.href = "#";
  935. a.textContent = "Watch";
  936. a.addEventListener("click", (e) => {
  937. e.preventDefault();
  938. if (window.selected_tagname) {
  939. addWatchTag(window.selected_tagname).then(() => {
  940. alert("success");
  941. }).catch((error) => {
  942. console.error(error);
  943. alert(error.message);
  944. });
  945. }
  946. });
  947. };
  948. const ob = new MutationObserver(() => {
  949. if (node.style.display !== "none") {
  950. inject();
  951. }
  952. });
  953. ob.observe(node, { attributes: true });
  954. }
  955. function addTitleCopyEvent() {
  956. $$("#gd2>*").forEach(function(node) {
  957. node.addEventListener("click", function() {
  958. if (this.textContent)
  959. copy$1(this.textContent);
  960. });
  961. });
  962. }
  963. async function setup$1() {
  964. injectWatchTag();
  965. addTitleCopyEvent();
  966. const info = getPageInfo();
  967. $$1("body").classList.add("e-hentai-infinite-scroll", "g");
  968. if (!info.unloadPageLinks.length)
  969. return;
  970. if (info.unloadPageLinks.length > 2) {
  971. largeGallery();
  972. } else {
  973. tinyGallery();
  974. }
  975. }
  976.  
  977. function checkCookie() {
  978. const igneous = Cookie.get("igneous");
  979. if (!igneous || igneous === "mystery") {
  980. $("<button>refresh</button>").on("click", refresh).appendTo("body");
  981. $("<button>login</button>").on("click", login).appendTo("body");
  982. }
  983. if (igneous === "mystery") {
  984. $(
  985. "<h2>[Cookie] igneous error! Change system proxy and reload page</h2>"
  986. ).appendTo("body");
  987. }
  988. }
  989. function refresh() {
  990. Cookie.remove({ name: "yay", domain: ".exhentai.org" });
  991. Cookie.remove({ name: "igneous", domain: ".exhentai.org" });
  992. Cookie.remove({ name: "ipb_pass_hash", domain: ".exhentai.org" });
  993. Cookie.remove({ name: "ipb_member_id", domain: ".exhentai.org" });
  994. window.location.reload();
  995. }
  996. function login() {
  997. window.location.href = "https://forums.e-hentai.org/index.php?act=Login&CODE=00";
  998. }
  999.  
  1000. const store = {};
  1001. function parseI3(i3) {
  1002. return i3.match(new RegExp(`'(?<key>.*)'.*src="(?<src>.*?")(.*nl\\('(?<nl>.*)'\\))?`)).groups;
  1003. }
  1004. function setupInfiniteScroll() {
  1005. function api_call(page2, nextImgKey2) {
  1006. return new Promise((resolve, reject) => {
  1007. const xhr = new XMLHttpRequest();
  1008. xhr.open("POST", window.api_url);
  1009. xhr.setRequestHeader("Content-Type", "application/json");
  1010. xhr.withCredentials = true;
  1011. xhr.addEventListener("loadend", () => {
  1012. if (200 <= xhr.status && xhr.status <= 300)
  1013. resolve(JSON.parse(xhr.response));
  1014. else
  1015. reject(xhr.response);
  1016. });
  1017. xhr.send(
  1018. JSON.stringify({
  1019. method: "showpage",
  1020. gid: window.gid,
  1021. page: page2,
  1022. imgkey: nextImgKey2,
  1023. showkey: window.showkey
  1024. })
  1025. );
  1026. });
  1027. }
  1028. const maxPageSize = parseInt(
  1029. document.querySelector("#i2 > div.sn > div > span:nth-child(2)").textContent
  1030. );
  1031. let nextImgKey = document.querySelector("#i3 a[onclick]").onclick.toString().match(new RegExp("'(?<key>.*)'")).groups.key;
  1032. let page = window.startpage + 1;
  1033. let isLoading = false;
  1034. async function loadImgInfo() {
  1035. try {
  1036. if (maxPageSize < page) {
  1037. return;
  1038. }
  1039. if (isLoading)
  1040. return;
  1041. isLoading = true;
  1042. const res = await api_call(page, nextImgKey);
  1043. isLoading = false;
  1044. const groups = parseI3(res.i3);
  1045. const info = {
  1046. key: res.k,
  1047. nl: groups.nl,
  1048. src: groups.src.slice(0, -1),
  1049. source: res.s[0] === "/" ? res.s : "/" + res.s
  1050. };
  1051. store[res.k] = { info, res };
  1052. renderImg(page, info);
  1053. nextImgKey = groups.key;
  1054. page++;
  1055. } catch (error) {
  1056. isLoading = false;
  1057. console.error(error);
  1058. await loadImgInfo();
  1059. }
  1060. }
  1061. function renderImg(page2, info) {
  1062. const { key, source, src } = info;
  1063. const img = document.createElement("img");
  1064. img.setAttribute("src", src);
  1065. img.dataset.imgKey = key;
  1066. img.dataset.page = page2 + "";
  1067. img.dataset.source = source;
  1068. img.classList.add("auto-load-img");
  1069. document.getElementById("i3").append(img);
  1070. }
  1071. function detectShouldLoadNextPage() {
  1072. const dom = document.scrollingElement;
  1073. if (dom.scrollHeight <= dom.scrollTop + dom.clientHeight + 2e3) {
  1074. loadImgInfo();
  1075. }
  1076. }
  1077. function resetDefaultImgDOM() {
  1078. const groups = parseI3(document.querySelector("#i3").innerHTML);
  1079. store[window.startkey] = {
  1080. info: {
  1081. key: window.startkey,
  1082. nl: groups.nl,
  1083. src: groups.src,
  1084. source: location.pathname
  1085. },
  1086. res: {
  1087. i: document.querySelector("#i4 > div").outerHTML,
  1088. i3: document.querySelector("#i3").innerHTML,
  1089. n: document.querySelector("#i4 > .sn").outerHTML,
  1090. i5: document.querySelector("#i5").innerHTML,
  1091. i6: document.querySelector("#i6").innerHTML,
  1092. k: window.startkey,
  1093. s: location.pathname
  1094. }
  1095. };
  1096. const $img = document.querySelector("#i3 a img");
  1097. $img.removeAttribute("style");
  1098. $img.classList.add("auto-load-img");
  1099. $img.dataset.imgKey = window.startkey;
  1100. $img.dataset.source = location.pathname;
  1101. document.getElementById("i3").append($img);
  1102. document.querySelector("#i3 a").remove();
  1103. removeSnAnchor();
  1104. }
  1105. document.body.classList.add("e-hentai-infinite-scroll", "s");
  1106. resetDefaultImgDOM();
  1107. detectShouldLoadNextPage();
  1108. document.addEventListener("scroll", () => {
  1109. detectShouldLoadNextPage();
  1110. updateCurrentInfo();
  1111. });
  1112. const ob = new MutationObserver(() => {
  1113. detectShouldLoadNextPage();
  1114. });
  1115. ob.observe(document.querySelector("#i3"), {
  1116. childList: true,
  1117. subtree: true,
  1118. attributes: true
  1119. });
  1120. }
  1121. function removeSnAnchor() {
  1122. document.querySelectorAll(".sn a[onclick]").forEach((a) => {
  1123. a.removeAttribute("onclick");
  1124. });
  1125. }
  1126. function getCurrentActiveImg() {
  1127. const imgs = document.querySelectorAll("#i3 img,#i3 img");
  1128. for (const img of imgs) {
  1129. const { top, bottom } = img.getBoundingClientRect();
  1130. const base = 200;
  1131. if (top < base && bottom > base) {
  1132. return img;
  1133. }
  1134. }
  1135. return null;
  1136. }
  1137. function updateCurrentPathname($img) {
  1138. const source = $img.dataset.source;
  1139. history.replaceState(null, "", source);
  1140. }
  1141. function updateBottomInfo($img) {
  1142. const key = $img.dataset.imgKey;
  1143. const { res } = store[key];
  1144. document.querySelector("#i2").innerHTML = res.n + res.i;
  1145. document.querySelector("#i4").innerHTML = res.i + res.n;
  1146. document.querySelector("#i5").innerHTML = res.i5;
  1147. document.querySelector("#i6").innerHTML = res.i6;
  1148. removeSnAnchor();
  1149. }
  1150. const updateCurrentInfo = debounce(function() {
  1151. const $img = getCurrentActiveImg();
  1152. if (!$img)
  1153. return;
  1154. const source = $img.dataset.source;
  1155. if (location.pathname === source)
  1156. return;
  1157. updateCurrentPathname($img);
  1158. updateBottomInfo($img);
  1159. }, 30);
  1160. function setup() {
  1161. setupInfiniteScroll();
  1162. }
  1163.  
  1164. router({
  1165. domain: "exhentai.org",
  1166. routes: [{ run: checkCookie }]
  1167. });
  1168. router([
  1169. { pathname: /^\/g\//, run: setup$1 },
  1170. { pathname: /^\/s\//, run: setup }
  1171. ]);
  1172.  
  1173. })();