修复copymanga图片错误

处理图片资源加载失败时自动重新加载

目前为 2023-05-03 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name 修复copymanga图片错误
  3. // @namespace https://github.com/IronKinoko/userscripts/tree/master/packages/copymanga
  4. // @version 1.4.11
  5. // @license MIT
  6. // @description 处理图片资源加载失败时自动重新加载
  7. // @author IronKinoko
  8. // @match https://www.copymanga.org/*
  9. // @match https://www.copymanga.tv/*
  10. // @match https://www.copymanga.site/*
  11. // @icon https://www.google.com/s2/favicons?domain=www.copymanga.org
  12. // @grant none
  13. // @noframes
  14. // @require https://unpkg.com/jquery@3.6.1/dist/jquery.min.js
  15. // ==/UserScript==
  16. (function () {
  17. 'use strict';
  18.  
  19. function addErrorListener(img) {
  20. if (img.dataset.errorFix === "true")
  21. return;
  22. img.dataset.errorFix = "true";
  23. img.onerror = () => {
  24. const url = new URL(img.src);
  25. let v = parseInt(url.searchParams.get("v")) || 0;
  26. if (v > 5)
  27. return img.onerror = null;
  28. url.searchParams.set("v", ++v + "");
  29. img.src = url.toString();
  30. img.alt = "\u56FE\u7247\u52A0\u8F7D\u51FA\u9519";
  31. };
  32. }
  33. function h5URLToPC(href) {
  34. const url = new URL(href);
  35. const re = new RegExp("\\/h5\\/comicContent\\/(?<comicId>.*?)\\/(?<chapterId>.*)");
  36. const match = url.pathname.match(re);
  37. if (match) {
  38. const { comicId, chapterId } = match.groups;
  39. return `https://userscripts-proxy.vercel.app/api/copymanga/comic/${comicId}/chapter/${chapterId}`;
  40. }
  41. return null;
  42. }
  43. async function getFullImages() {
  44. const url = h5URLToPC(window.location.href);
  45. if (!url)
  46. throw new Error("\u8BF7\u5728\u79FB\u52A8\u7AEF\u8FD0\u884C");
  47. try {
  48. const data = await fetch(url).then((r) => r.json());
  49. if (!data.ok)
  50. throw new Error(data.message);
  51. return data.manga;
  52. } catch (error) {
  53. console.error(error);
  54. alert(`\u63A5\u53E3\u8C03\u7528\u5931\u8D25 ${error.message}`);
  55. return [];
  56. }
  57. }
  58.  
  59. /**
  60. * Checks if `value` is the
  61. * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
  62. * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
  63. *
  64. * @static
  65. * @memberOf _
  66. * @since 0.1.0
  67. * @category Lang
  68. * @param {*} value The value to check.
  69. * @returns {boolean} Returns `true` if `value` is an object, else `false`.
  70. * @example
  71. *
  72. * _.isObject({});
  73. * // => true
  74. *
  75. * _.isObject([1, 2, 3]);
  76. * // => true
  77. *
  78. * _.isObject(_.noop);
  79. * // => true
  80. *
  81. * _.isObject(null);
  82. * // => false
  83. */
  84. function isObject(value) {
  85. var type = typeof value;
  86. return value != null && (type == 'object' || type == 'function');
  87. }
  88.  
  89. /** Detect free variable `global` from Node.js. */
  90. var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
  91.  
  92. /** Detect free variable `self`. */
  93. var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
  94.  
  95. /** Used as a reference to the global object. */
  96. var root = freeGlobal || freeSelf || Function('return this')();
  97.  
  98. /**
  99. * Gets the timestamp of the number of milliseconds that have elapsed since
  100. * the Unix epoch (1 January 1970 00:00:00 UTC).
  101. *
  102. * @static
  103. * @memberOf _
  104. * @since 2.4.0
  105. * @category Date
  106. * @returns {number} Returns the timestamp.
  107. * @example
  108. *
  109. * _.defer(function(stamp) {
  110. * console.log(_.now() - stamp);
  111. * }, _.now());
  112. * // => Logs the number of milliseconds it took for the deferred invocation.
  113. */
  114. var now = function() {
  115. return root.Date.now();
  116. };
  117.  
  118. /** Used to match a single whitespace character. */
  119. var reWhitespace = /\s/;
  120.  
  121. /**
  122. * Used by `_.trim` and `_.trimEnd` to get the index of the last non-whitespace
  123. * character of `string`.
  124. *
  125. * @private
  126. * @param {string} string The string to inspect.
  127. * @returns {number} Returns the index of the last non-whitespace character.
  128. */
  129. function trimmedEndIndex(string) {
  130. var index = string.length;
  131.  
  132. while (index-- && reWhitespace.test(string.charAt(index))) {}
  133. return index;
  134. }
  135.  
  136. /** Used to match leading whitespace. */
  137. var reTrimStart = /^\s+/;
  138.  
  139. /**
  140. * The base implementation of `_.trim`.
  141. *
  142. * @private
  143. * @param {string} string The string to trim.
  144. * @returns {string} Returns the trimmed string.
  145. */
  146. function baseTrim(string) {
  147. return string
  148. ? string.slice(0, trimmedEndIndex(string) + 1).replace(reTrimStart, '')
  149. : string;
  150. }
  151.  
  152. /** Built-in value references. */
  153. var Symbol = root.Symbol;
  154.  
  155. /** Used for built-in method references. */
  156. var objectProto$1 = Object.prototype;
  157.  
  158. /** Used to check objects for own properties. */
  159. var hasOwnProperty = objectProto$1.hasOwnProperty;
  160.  
  161. /**
  162. * Used to resolve the
  163. * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
  164. * of values.
  165. */
  166. var nativeObjectToString$1 = objectProto$1.toString;
  167.  
  168. /** Built-in value references. */
  169. var symToStringTag$1 = Symbol ? Symbol.toStringTag : undefined;
  170.  
  171. /**
  172. * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
  173. *
  174. * @private
  175. * @param {*} value The value to query.
  176. * @returns {string} Returns the raw `toStringTag`.
  177. */
  178. function getRawTag(value) {
  179. var isOwn = hasOwnProperty.call(value, symToStringTag$1),
  180. tag = value[symToStringTag$1];
  181.  
  182. try {
  183. value[symToStringTag$1] = undefined;
  184. var unmasked = true;
  185. } catch (e) {}
  186.  
  187. var result = nativeObjectToString$1.call(value);
  188. if (unmasked) {
  189. if (isOwn) {
  190. value[symToStringTag$1] = tag;
  191. } else {
  192. delete value[symToStringTag$1];
  193. }
  194. }
  195. return result;
  196. }
  197.  
  198. /** Used for built-in method references. */
  199. var objectProto = Object.prototype;
  200.  
  201. /**
  202. * Used to resolve the
  203. * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
  204. * of values.
  205. */
  206. var nativeObjectToString = objectProto.toString;
  207.  
  208. /**
  209. * Converts `value` to a string using `Object.prototype.toString`.
  210. *
  211. * @private
  212. * @param {*} value The value to convert.
  213. * @returns {string} Returns the converted string.
  214. */
  215. function objectToString(value) {
  216. return nativeObjectToString.call(value);
  217. }
  218.  
  219. /** `Object#toString` result references. */
  220. var nullTag = '[object Null]',
  221. undefinedTag = '[object Undefined]';
  222.  
  223. /** Built-in value references. */
  224. var symToStringTag = Symbol ? Symbol.toStringTag : undefined;
  225.  
  226. /**
  227. * The base implementation of `getTag` without fallbacks for buggy environments.
  228. *
  229. * @private
  230. * @param {*} value The value to query.
  231. * @returns {string} Returns the `toStringTag`.
  232. */
  233. function baseGetTag(value) {
  234. if (value == null) {
  235. return value === undefined ? undefinedTag : nullTag;
  236. }
  237. return (symToStringTag && symToStringTag in Object(value))
  238. ? getRawTag(value)
  239. : objectToString(value);
  240. }
  241.  
  242. /**
  243. * Checks if `value` is object-like. A value is object-like if it's not `null`
  244. * and has a `typeof` result of "object".
  245. *
  246. * @static
  247. * @memberOf _
  248. * @since 4.0.0
  249. * @category Lang
  250. * @param {*} value The value to check.
  251. * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
  252. * @example
  253. *
  254. * _.isObjectLike({});
  255. * // => true
  256. *
  257. * _.isObjectLike([1, 2, 3]);
  258. * // => true
  259. *
  260. * _.isObjectLike(_.noop);
  261. * // => false
  262. *
  263. * _.isObjectLike(null);
  264. * // => false
  265. */
  266. function isObjectLike(value) {
  267. return value != null && typeof value == 'object';
  268. }
  269.  
  270. /** `Object#toString` result references. */
  271. var symbolTag = '[object Symbol]';
  272.  
  273. /**
  274. * Checks if `value` is classified as a `Symbol` primitive or object.
  275. *
  276. * @static
  277. * @memberOf _
  278. * @since 4.0.0
  279. * @category Lang
  280. * @param {*} value The value to check.
  281. * @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
  282. * @example
  283. *
  284. * _.isSymbol(Symbol.iterator);
  285. * // => true
  286. *
  287. * _.isSymbol('abc');
  288. * // => false
  289. */
  290. function isSymbol(value) {
  291. return typeof value == 'symbol' ||
  292. (isObjectLike(value) && baseGetTag(value) == symbolTag);
  293. }
  294.  
  295. /** Used as references for various `Number` constants. */
  296. var NAN = 0 / 0;
  297.  
  298. /** Used to detect bad signed hexadecimal string values. */
  299. var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
  300.  
  301. /** Used to detect binary string values. */
  302. var reIsBinary = /^0b[01]+$/i;
  303.  
  304. /** Used to detect octal string values. */
  305. var reIsOctal = /^0o[0-7]+$/i;
  306.  
  307. /** Built-in method references without a dependency on `root`. */
  308. var freeParseInt = parseInt;
  309.  
  310. /**
  311. * Converts `value` to a number.
  312. *
  313. * @static
  314. * @memberOf _
  315. * @since 4.0.0
  316. * @category Lang
  317. * @param {*} value The value to process.
  318. * @returns {number} Returns the number.
  319. * @example
  320. *
  321. * _.toNumber(3.2);
  322. * // => 3.2
  323. *
  324. * _.toNumber(Number.MIN_VALUE);
  325. * // => 5e-324
  326. *
  327. * _.toNumber(Infinity);
  328. * // => Infinity
  329. *
  330. * _.toNumber('3.2');
  331. * // => 3.2
  332. */
  333. function toNumber(value) {
  334. if (typeof value == 'number') {
  335. return value;
  336. }
  337. if (isSymbol(value)) {
  338. return NAN;
  339. }
  340. if (isObject(value)) {
  341. var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
  342. value = isObject(other) ? (other + '') : other;
  343. }
  344. if (typeof value != 'string') {
  345. return value === 0 ? value : +value;
  346. }
  347. value = baseTrim(value);
  348. var isBinary = reIsBinary.test(value);
  349. return (isBinary || reIsOctal.test(value))
  350. ? freeParseInt(value.slice(2), isBinary ? 2 : 8)
  351. : (reIsBadHex.test(value) ? NAN : +value);
  352. }
  353.  
  354. /** Error message constants. */
  355. var FUNC_ERROR_TEXT$1 = 'Expected a function';
  356.  
  357. /* Built-in method references for those with the same name as other `lodash` methods. */
  358. var nativeMax = Math.max,
  359. nativeMin = Math.min;
  360.  
  361. /**
  362. * Creates a debounced function that delays invoking `func` until after `wait`
  363. * milliseconds have elapsed since the last time the debounced function was
  364. * invoked. The debounced function comes with a `cancel` method to cancel
  365. * delayed `func` invocations and a `flush` method to immediately invoke them.
  366. * Provide `options` to indicate whether `func` should be invoked on the
  367. * leading and/or trailing edge of the `wait` timeout. The `func` is invoked
  368. * with the last arguments provided to the debounced function. Subsequent
  369. * calls to the debounced function return the result of the last `func`
  370. * invocation.
  371. *
  372. * **Note:** If `leading` and `trailing` options are `true`, `func` is
  373. * invoked on the trailing edge of the timeout only if the debounced function
  374. * is invoked more than once during the `wait` timeout.
  375. *
  376. * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
  377. * until to the next tick, similar to `setTimeout` with a timeout of `0`.
  378. *
  379. * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
  380. * for details over the differences between `_.debounce` and `_.throttle`.
  381. *
  382. * @static
  383. * @memberOf _
  384. * @since 0.1.0
  385. * @category Function
  386. * @param {Function} func The function to debounce.
  387. * @param {number} [wait=0] The number of milliseconds to delay.
  388. * @param {Object} [options={}] The options object.
  389. * @param {boolean} [options.leading=false]
  390. * Specify invoking on the leading edge of the timeout.
  391. * @param {number} [options.maxWait]
  392. * The maximum time `func` is allowed to be delayed before it's invoked.
  393. * @param {boolean} [options.trailing=true]
  394. * Specify invoking on the trailing edge of the timeout.
  395. * @returns {Function} Returns the new debounced function.
  396. * @example
  397. *
  398. * // Avoid costly calculations while the window size is in flux.
  399. * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
  400. *
  401. * // Invoke `sendMail` when clicked, debouncing subsequent calls.
  402. * jQuery(element).on('click', _.debounce(sendMail, 300, {
  403. * 'leading': true,
  404. * 'trailing': false
  405. * }));
  406. *
  407. * // Ensure `batchLog` is invoked once after 1 second of debounced calls.
  408. * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
  409. * var source = new EventSource('/stream');
  410. * jQuery(source).on('message', debounced);
  411. *
  412. * // Cancel the trailing debounced invocation.
  413. * jQuery(window).on('popstate', debounced.cancel);
  414. */
  415. function debounce(func, wait, options) {
  416. var lastArgs,
  417. lastThis,
  418. maxWait,
  419. result,
  420. timerId,
  421. lastCallTime,
  422. lastInvokeTime = 0,
  423. leading = false,
  424. maxing = false,
  425. trailing = true;
  426.  
  427. if (typeof func != 'function') {
  428. throw new TypeError(FUNC_ERROR_TEXT$1);
  429. }
  430. wait = toNumber(wait) || 0;
  431. if (isObject(options)) {
  432. leading = !!options.leading;
  433. maxing = 'maxWait' in options;
  434. maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
  435. trailing = 'trailing' in options ? !!options.trailing : trailing;
  436. }
  437.  
  438. function invokeFunc(time) {
  439. var args = lastArgs,
  440. thisArg = lastThis;
  441.  
  442. lastArgs = lastThis = undefined;
  443. lastInvokeTime = time;
  444. result = func.apply(thisArg, args);
  445. return result;
  446. }
  447.  
  448. function leadingEdge(time) {
  449. // Reset any `maxWait` timer.
  450. lastInvokeTime = time;
  451. // Start the timer for the trailing edge.
  452. timerId = setTimeout(timerExpired, wait);
  453. // Invoke the leading edge.
  454. return leading ? invokeFunc(time) : result;
  455. }
  456.  
  457. function remainingWait(time) {
  458. var timeSinceLastCall = time - lastCallTime,
  459. timeSinceLastInvoke = time - lastInvokeTime,
  460. timeWaiting = wait - timeSinceLastCall;
  461.  
  462. return maxing
  463. ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke)
  464. : timeWaiting;
  465. }
  466.  
  467. function shouldInvoke(time) {
  468. var timeSinceLastCall = time - lastCallTime,
  469. timeSinceLastInvoke = time - lastInvokeTime;
  470.  
  471. // Either this is the first call, activity has stopped and we're at the
  472. // trailing edge, the system time has gone backwards and we're treating
  473. // it as the trailing edge, or we've hit the `maxWait` limit.
  474. return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
  475. (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
  476. }
  477.  
  478. function timerExpired() {
  479. var time = now();
  480. if (shouldInvoke(time)) {
  481. return trailingEdge(time);
  482. }
  483. // Restart the timer.
  484. timerId = setTimeout(timerExpired, remainingWait(time));
  485. }
  486.  
  487. function trailingEdge(time) {
  488. timerId = undefined;
  489.  
  490. // Only invoke if we have `lastArgs` which means `func` has been
  491. // debounced at least once.
  492. if (trailing && lastArgs) {
  493. return invokeFunc(time);
  494. }
  495. lastArgs = lastThis = undefined;
  496. return result;
  497. }
  498.  
  499. function cancel() {
  500. if (timerId !== undefined) {
  501. clearTimeout(timerId);
  502. }
  503. lastInvokeTime = 0;
  504. lastArgs = lastCallTime = lastThis = timerId = undefined;
  505. }
  506.  
  507. function flush() {
  508. return timerId === undefined ? result : trailingEdge(now());
  509. }
  510.  
  511. function debounced() {
  512. var time = now(),
  513. isInvoking = shouldInvoke(time);
  514.  
  515. lastArgs = arguments;
  516. lastThis = this;
  517. lastCallTime = time;
  518.  
  519. if (isInvoking) {
  520. if (timerId === undefined) {
  521. return leadingEdge(lastCallTime);
  522. }
  523. if (maxing) {
  524. // Handle invocations in a tight loop.
  525. clearTimeout(timerId);
  526. timerId = setTimeout(timerExpired, wait);
  527. return invokeFunc(lastCallTime);
  528. }
  529. }
  530. if (timerId === undefined) {
  531. timerId = setTimeout(timerExpired, wait);
  532. }
  533. return result;
  534. }
  535. debounced.cancel = cancel;
  536. debounced.flush = flush;
  537. return debounced;
  538. }
  539.  
  540. /** Error message constants. */
  541. var FUNC_ERROR_TEXT = 'Expected a function';
  542.  
  543. /**
  544. * Creates a throttled function that only invokes `func` at most once per
  545. * every `wait` milliseconds. The throttled function comes with a `cancel`
  546. * method to cancel delayed `func` invocations and a `flush` method to
  547. * immediately invoke them. Provide `options` to indicate whether `func`
  548. * should be invoked on the leading and/or trailing edge of the `wait`
  549. * timeout. The `func` is invoked with the last arguments provided to the
  550. * throttled function. Subsequent calls to the throttled function return the
  551. * result of the last `func` invocation.
  552. *
  553. * **Note:** If `leading` and `trailing` options are `true`, `func` is
  554. * invoked on the trailing edge of the timeout only if the throttled function
  555. * is invoked more than once during the `wait` timeout.
  556. *
  557. * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
  558. * until to the next tick, similar to `setTimeout` with a timeout of `0`.
  559. *
  560. * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
  561. * for details over the differences between `_.throttle` and `_.debounce`.
  562. *
  563. * @static
  564. * @memberOf _
  565. * @since 0.1.0
  566. * @category Function
  567. * @param {Function} func The function to throttle.
  568. * @param {number} [wait=0] The number of milliseconds to throttle invocations to.
  569. * @param {Object} [options={}] The options object.
  570. * @param {boolean} [options.leading=true]
  571. * Specify invoking on the leading edge of the timeout.
  572. * @param {boolean} [options.trailing=true]
  573. * Specify invoking on the trailing edge of the timeout.
  574. * @returns {Function} Returns the new throttled function.
  575. * @example
  576. *
  577. * // Avoid excessively updating the position while scrolling.
  578. * jQuery(window).on('scroll', _.throttle(updatePosition, 100));
  579. *
  580. * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
  581. * var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
  582. * jQuery(element).on('click', throttled);
  583. *
  584. * // Cancel the trailing throttled invocation.
  585. * jQuery(window).on('popstate', throttled.cancel);
  586. */
  587. function throttle(func, wait, options) {
  588. var leading = true,
  589. trailing = true;
  590.  
  591. if (typeof func != 'function') {
  592. throw new TypeError(FUNC_ERROR_TEXT);
  593. }
  594. if (isObject(options)) {
  595. leading = 'leading' in options ? !!options.leading : leading;
  596. trailing = 'trailing' in options ? !!options.trailing : trailing;
  597. }
  598. return debounce(func, wait, {
  599. 'leading': leading,
  600. 'maxWait': wait,
  601. 'trailing': trailing
  602. });
  603. }
  604.  
  605. function setup() {
  606. customElements.define(
  607. "img-lazy",
  608. class ImgLazy extends HTMLElement {
  609. constructor() {
  610. super();
  611. const shadow = this.attachShadow({ mode: "open" });
  612. this.img = document.createElement("img");
  613. this.img.classList.add("loading");
  614. this.img.onload = () => {
  615. this.img.classList.remove("loading");
  616. };
  617. this.img.onerror = () => {
  618. const url = new URL(this.img.src);
  619. let v = parseInt(url.searchParams.get("v")) || 0;
  620. v++;
  621. url.searchParams.set("v", v + "");
  622. this.img.src = url.toString();
  623. this.img.alt = `\u56FE\u7247\u52A0\u8F7D\u51FA\u9519 [${v}]`;
  624. };
  625. this.ob = new IntersectionObserver(
  626. ([e]) => {
  627. if (!e.isIntersecting)
  628. return;
  629. const src = this.getAttribute("src");
  630. if (!src)
  631. return;
  632. this.img.src = src;
  633. this.ob.unobserve(this);
  634. },
  635. { rootMargin: "2000px 0px" }
  636. );
  637. const style = document.createElement("style");
  638. style.innerHTML = `
  639. img {
  640. display: block;
  641. width: 100%;
  642. }
  643. .loading { min-height: 500px }
  644. `;
  645. shadow.appendChild(style);
  646. shadow.appendChild(this.img);
  647. }
  648. connectedCallback() {
  649. this.ob.observe(this);
  650. }
  651. disconnectedCallback() {
  652. this.ob.disconnect();
  653. }
  654. static get observedAttributes() {
  655. return ["src"];
  656. }
  657. attributeChangedCallback(attrName, oldValue, newValue) {
  658. if (attrName === "src") {
  659. this.ob.unobserve(this);
  660. this.ob.observe(this);
  661. }
  662. }
  663. }
  664. );
  665. }
  666.  
  667. function sleep(time) {
  668. if (!time) {
  669. return new Promise((resolve) => {
  670. requestAnimationFrame(resolve);
  671. });
  672. }
  673. return new Promise((resolve) => {
  674. setTimeout(resolve, time);
  675. });
  676. }
  677.  
  678. async function wait(selector) {
  679. let bool = selector();
  680. while (!bool) {
  681. await sleep();
  682. bool = selector();
  683. }
  684. }
  685.  
  686. function normalizeKeyEvent(e) {
  687. const SPECIAL_KEY_EN = "`-=[]\\;',./~!@#$%^&*()_+{}|:\"<>?".split("");
  688. const SPECIAL_KEY_ZH = "\xB7-=\u3010\u3011\u3001\uFF1B\u2018\uFF0C\u3002/\uFF5E\uFF01@#\xA5%\u2026&*\uFF08\uFF09\u2014+\u300C\u300D\uFF5C\uFF1A\u201C\u300A\u300B\uFF1F".split("");
  689. let key = e.key;
  690. if (e.code === "Space") {
  691. key = "Space";
  692. }
  693. if (/^[a-z]$/.test(key)) {
  694. key = key.toUpperCase();
  695. } else if (SPECIAL_KEY_ZH.includes(key)) {
  696. key = SPECIAL_KEY_EN[SPECIAL_KEY_ZH.indexOf(key)];
  697. }
  698. let keyArr = [];
  699. e.ctrlKey && keyArr.push("ctrl");
  700. e.metaKey && keyArr.push("meta");
  701. e.shiftKey && !SPECIAL_KEY_EN.includes(key) && keyArr.push("shift");
  702. e.altKey && keyArr.push("alt");
  703. if (!/Control|Meta|Shift|Alt/i.test(key))
  704. keyArr.push(key);
  705. keyArr = [...new Set(keyArr)];
  706. return keyArr.join("+");
  707. }
  708. function keybind(keys, keydown, keyup) {
  709. const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
  710. keys = keys.filter((key) => !key.includes(isMac ? "ctrl" : "meta"));
  711. function createProcess(callback) {
  712. return function(e) {
  713. var _a;
  714. if (((_a = document.activeElement) == null ? void 0 : _a.tagName) === "INPUT")
  715. return;
  716. const normalizedKey = normalizeKeyEvent(e).toLowerCase();
  717. for (const key of keys) {
  718. if (key.toLowerCase() === normalizedKey)
  719. callback(e, key);
  720. }
  721. };
  722. }
  723. window.addEventListener("keydown", createProcess(keydown));
  724. if (keyup)
  725. window.addEventListener("keyup", createProcess(keyup));
  726. }
  727.  
  728. async function waitDOM(selector) {
  729. await wait(() => !!document.querySelector(selector));
  730. return document.querySelector(selector);
  731. }
  732.  
  733. function matcher(source, regexp) {
  734. if (typeof regexp === "string")
  735. return source.includes(regexp);
  736. return !!source.match(regexp);
  737. }
  738. function router(config) {
  739. const opts = {
  740. domain: "",
  741. routes: []
  742. };
  743. if ("routes" in config) {
  744. opts.domain = config.domain;
  745. opts.routes = config.routes;
  746. } else {
  747. opts.routes = Array.isArray(config) ? config : [config];
  748. }
  749. if (opts.domain) {
  750. const domains = Array.isArray(opts.domain) ? opts.domain : [opts.domain];
  751. const match = domains.some(
  752. (domain) => matcher(window.location.origin, domain)
  753. );
  754. if (!match)
  755. return;
  756. }
  757. const pathSource = window.location.pathname + window.location.search + window.location.hash;
  758. if (typeof opts.routes === "function") {
  759. opts.routes();
  760. return;
  761. }
  762. const routes = Array.isArray(opts.routes) ? opts.routes : [opts.routes];
  763. routes.forEach((route) => {
  764. let match = true;
  765. if (route.path) {
  766. match = matcher(pathSource, route.path);
  767. }
  768. if (route.pathname) {
  769. match = matcher(window.location.pathname, route.pathname);
  770. }
  771. if (route.search) {
  772. match = matcher(window.location.search, route.search);
  773. }
  774. if (route.hash) {
  775. match = matcher(window.location.hash, route.hash);
  776. }
  777. if (match)
  778. route.run();
  779. });
  780. }
  781.  
  782. async function openControl() {
  783. const li = await waitDOM("li.comicContentPopupImageItem");
  784. li.dispatchEvent(fakeClickEvent());
  785. await sleep(0);
  786. li.dispatchEvent(fakeClickEvent());
  787. }
  788. function fakeClickEvent() {
  789. const { width, height } = document.body.getBoundingClientRect();
  790. return new MouseEvent("click", { clientX: width / 2, clientY: height / 2 });
  791. }
  792. async function currentPage() {
  793. try {
  794. if (!/h5\/comicContent\/.*/.test(location.href))
  795. return;
  796. const scrollHeight = document.scrollingElement.scrollTop;
  797. const list = document.querySelectorAll("li.comicContentPopupImageItem");
  798. let height = 0;
  799. for (let i = 0; i < list.length; i++) {
  800. const item = list[i];
  801. height += item.getBoundingClientRect().height;
  802. if (height > scrollHeight) {
  803. const dom = document.querySelector(".comicContentPopup .comicFixed");
  804. dom.textContent = `${i + 1}/${list.length}`;
  805. break;
  806. }
  807. }
  808. } catch (e) {
  809. }
  810. }
  811. let trackId = { current: 0 };
  812. async function runH5main() {
  813. try {
  814. if (!/h5\/comicContent\/.*/.test(location.href))
  815. return;
  816. let runTrackId = ++trackId.current;
  817. const ulDom = await waitDOM(".comicContentPopupImageList");
  818. if (runTrackId !== trackId.current)
  819. return;
  820. const uuid = getComicId();
  821. const domUUID = ulDom.dataset.uuid;
  822. if (domUUID !== uuid) {
  823. ulDom.dataset.uuid = uuid;
  824. }
  825. await openControl();
  826. await injectImageData();
  827. const main = ulDom.parentElement;
  828. main.style.position = "unset";
  829. main.style.overflowY = "unset";
  830. createNextPartDom();
  831. } catch (error) {
  832. throw error;
  833. }
  834. }
  835. async function createNextPartDom() {
  836. let nextPartDom = document.querySelector(
  837. "#comicContentMain .next-part-btn"
  838. );
  839. let nextButton = document.querySelector(
  840. ".comicControlBottomTop > div:nth-child(3) > span"
  841. );
  842. if (!nextPartDom) {
  843. if (!nextButton) {
  844. await openControl();
  845. nextButton = document.querySelector(
  846. ".comicControlBottomTop > div:nth-child(3) > span"
  847. );
  848. }
  849. nextPartDom = document.createElement("div");
  850. nextPartDom.className = "next-part-btn";
  851. nextPartDom.textContent = "\u4E0B\u4E00\u8BDD";
  852. nextPartDom.onclick = async (e) => {
  853. e.stopPropagation();
  854. nextButton && nextButton.click();
  855. document.scrollingElement.scrollTop = 0;
  856. };
  857. document.getElementById("comicContentMain").appendChild(nextPartDom);
  858. }
  859. nextPartDom.style.display = nextButton.parentElement.classList.contains(
  860. "noneUuid"
  861. ) ? "none" : "block";
  862. let fixedNextBtn = document.querySelector(
  863. ".next-part-btn-fixed"
  864. );
  865. if (!fixedNextBtn) {
  866. fixedNextBtn = document.createElement("div");
  867. fixedNextBtn.className = "next-part-btn-fixed";
  868. fixedNextBtn.textContent = "\u4E0B\u4E00\u8BDD";
  869. document.body.appendChild(fixedNextBtn);
  870. let prevY = 0;
  871. let storeY = 0;
  872. window.addEventListener(
  873. "scroll",
  874. throttle(() => {
  875. if (!/h5\/comicContent\/.*/.test(location.href)) {
  876. fixedNextBtn == null ? void 0 : fixedNextBtn.classList.add("hide");
  877. return;
  878. }
  879. const dom = document.scrollingElement;
  880. const currentY = dom.scrollTop;
  881. let diffY = currentY - storeY;
  882. if (currentY < 50 || currentY + dom.clientHeight > dom.scrollHeight - 800 || diffY < -30) {
  883. fixedNextBtn == null ? void 0 : fixedNextBtn.classList.remove("hide");
  884. } else {
  885. fixedNextBtn == null ? void 0 : fixedNextBtn.classList.add("hide");
  886. }
  887. if (currentY > prevY) {
  888. storeY = currentY;
  889. }
  890. prevY = currentY;
  891. }, 100)
  892. );
  893. }
  894. fixedNextBtn.onclick = nextPartDom.onclick;
  895. fixedNextBtn.style.display = nextPartDom.style.display;
  896. }
  897. function getComicId() {
  898. const [, uuid] = location.href.match(/h5\/comicContent\/.*\/(.*)/);
  899. return uuid;
  900. }
  901. async function addH5HistoryListener() {
  902. history.pushState = _historyWrap("pushState");
  903. history.replaceState = _historyWrap("replaceState");
  904. window.addEventListener("pushState", runH5main);
  905. window.addEventListener("replaceState", runH5main);
  906. window.addEventListener("popstate", runH5main);
  907. window.addEventListener("scroll", throttle(currentPage, 100));
  908. runH5main();
  909. }
  910. const _historyWrap = function(type) {
  911. const orig = history[type];
  912. const e = new Event(type);
  913. return function() {
  914. const rv = orig.apply(this, arguments);
  915. window.dispatchEvent(e);
  916. return rv;
  917. };
  918. };
  919. async function injectImageData() {
  920. const data = await getFullImages();
  921. let html = "";
  922. data.forEach(({ url }, idx) => {
  923. html += `
  924. <li class="comicContentPopupImageItem" data-k data-idx="${idx}">
  925. <img-lazy src="${url}" />
  926. </li>
  927. `;
  928. });
  929. await waitDOM(".comicContentPopupImageList .comicContentPopupImageItem");
  930. $(".comicContentPopupImageItem").attr("class", "k-open-control-item").hide();
  931. $("[data-k]").remove();
  932. $(".comicContentPopupImageList").prepend(html);
  933. $(".comicContentPopupImageItem").on("click", (e) => {
  934. const { innerWidth, innerHeight } = window;
  935. const x = e.clientX;
  936. const y = e.clientY;
  937. if (innerWidth / 3 < x && x < innerWidth / 3 * 2 && innerHeight / 3 < y && y < innerHeight / 3 * 2) {
  938. const li = $(".k-open-control-item").get(0);
  939. li == null ? void 0 : li.dispatchEvent(fakeClickEvent());
  940. }
  941. });
  942. currentPage();
  943. }
  944. function h5() {
  945. addH5HistoryListener();
  946. }
  947.  
  948. function replaceHeader() {
  949. const header = document.querySelector(
  950. ".container.header-log .row"
  951. );
  952. if (header) {
  953. header.style.flexWrap = "nowrap";
  954. $(header).find("div:nth-child(6)").replaceWith(
  955. `<div class="col-1">
  956. <div class="log-txt">
  957. <a href="/web/person/shujia">\u6211\u7684\u4E66\u67B6</a>
  958. <div class="log-unboder"></div>
  959. </div>
  960. </div>`
  961. );
  962. $(header).find("div:nth-child(7)").replaceWith(
  963. `<div class="col-1">
  964. <div class="log-txt">
  965. <a href="/web/person/liulan">\u6211\u7684\u6D4F\u89C8</a>
  966. <div class="log-unboder"></div>
  967. </div>
  968. </div>`
  969. );
  970. header.querySelector("div:nth-child(8)").className = "col";
  971. header.querySelector(
  972. "div.col > div > div"
  973. ).style.justifyContent = "flex-end";
  974. }
  975. }
  976. async function injectFixImg() {
  977. const listDOM = await waitDOM("ul.comicContent-list");
  978. async function injectEvent2() {
  979. const imgs = document.querySelectorAll("ul li img");
  980. imgs.forEach(addErrorListener);
  981. }
  982. const ob = new MutationObserver(injectEvent2);
  983. ob.observe(listDOM, { childList: true, subtree: true });
  984. injectEvent2();
  985. }
  986. async function injectFastLoadImg() {
  987. const $list = await waitDOM(".comicContent-list");
  988. function fastLoad() {
  989. const $imgs = $list.querySelectorAll("li img");
  990. $imgs.forEach(($img) => {
  991. if ($img.dataset.fastLoad === "true")
  992. return;
  993. $img.dataset.fastLoad = "true";
  994. $img.src = $img.dataset.src;
  995. });
  996. }
  997. const ob = new MutationObserver(fastLoad);
  998. ob.observe($list, { childList: true, subtree: true });
  999. }
  1000. async function removeMouseupEvent() {
  1001. await wait(() => !!document.body.onmouseup);
  1002. document.body.onmouseup = null;
  1003. }
  1004. async function injectEvent() {
  1005. keybind(["z", "x"], (e, key) => {
  1006. var _a, _b;
  1007. switch (key) {
  1008. case "z": {
  1009. (_a = document.querySelector(`[class='comicContent-prev'] a`)) == null ? void 0 : _a.click();
  1010. break;
  1011. }
  1012. case "x": {
  1013. (_b = document.querySelector(`[class='comicContent-next'] a`)) == null ? void 0 : _b.click();
  1014. break;
  1015. }
  1016. }
  1017. });
  1018. }
  1019. function pc() {
  1020. if (/comic\/.*\/chapter/.test(location.href)) {
  1021. injectFixImg();
  1022. injectFastLoadImg();
  1023. removeMouseupEvent();
  1024. injectEvent();
  1025. }
  1026. replaceHeader();
  1027. }
  1028.  
  1029. 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}}
  1030.  
  1031. var css = ".k-copymanga .next-part-btn {\n height: 150px;\n line-height: 50px;\n text-align: center;\n font-size: 16px;\n}\n.k-copymanga .next-part-btn-fixed {\n position: fixed;\n right: 0;\n top: 25vh;\n font-size: 16px;\n background: white;\n padding: 8px;\n writing-mode: vertical-lr;\n box-shadow: rgba(0, 0, 0, 0.2) -1px 1px 10px 0px;\n transition: all 0.2s ease;\n transform: translateX(0);\n border-radius: 4px 0 0 4px;\n opacity: 1;\n}\n.k-copymanga .next-part-btn-fixed.hide {\n opacity: 0;\n pointer-events: none;\n transform: translateX(100%);\n}\n.k-copymanga .comicContentPopup .comicContentPopupImageList .comicContentPopupImageItem img {\n display: block;\n float: none;\n}\n.k-copymanga .comicContentPopup .comicContentPopupImageList > li[style] [role=alert],\n.k-copymanga .comicContentPopup .comicContentPopupImageList > li[style] [role=alert] + button {\n display: none;\n}";
  1032. n(css,{});
  1033.  
  1034. setup();
  1035. document.body.classList.add("k-copymanga");
  1036. router([
  1037. { pathname: /^\/h5/, run: h5 },
  1038. { pathname: /^(?!\/h5)/, run: pc }
  1039. ]);
  1040.  
  1041. })();

QingJ © 2025

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