修复copymanga图片错误

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

目前為 2022-10-22 提交的版本,檢視 最新版本

// ==UserScript==
// @name         修复copymanga图片错误
// @namespace    https://github.com/IronKinoko/userscripts/tree/master/packages/copymanga
// @version      1.3.0
// @license      MIT
// @description  处理图片资源加载失败时自动重新加载
// @author       IronKinoko
// @match        https://www.copymanga.org/*
// @match        https://www.copymanga.site/*
// @icon         https://www.google.com/s2/favicons?domain=www.copymanga.org
// @grant        none
// @noframes
// ==/UserScript==
(function () {
  'use strict';

  function s2d(string) {
    return new DOMParser().parseFromString(string, "text/html").body.firstChild;
  }
  function addErrorListener(img) {
    if (img.dataset.errorFix === "true")
      return;
    img.dataset.errorFix = "true";
    img.onerror = () => {
      const url = new URL(img.src);
      let v = parseInt(url.searchParams.get("v")) || 0;
      if (v > 5)
        return img.onerror = null;
      url.searchParams.set("v", ++v + "");
      img.src = url.toString();
      img.alt = "\u56FE\u7247\u52A0\u8F7D\u51FA\u9519";
    };
  }

  /**
   * Checks if `value` is the
   * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
   * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
   *
   * @static
   * @memberOf _
   * @since 0.1.0
   * @category Lang
   * @param {*} value The value to check.
   * @returns {boolean} Returns `true` if `value` is an object, else `false`.
   * @example
   *
   * _.isObject({});
   * // => true
   *
   * _.isObject([1, 2, 3]);
   * // => true
   *
   * _.isObject(_.noop);
   * // => true
   *
   * _.isObject(null);
   * // => false
   */
  function isObject(value) {
    var type = typeof value;
    return value != null && (type == 'object' || type == 'function');
  }

  /** Detect free variable `global` from Node.js. */
  var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;

  /** Detect free variable `self`. */
  var freeSelf = typeof self == 'object' && self && self.Object === Object && self;

  /** Used as a reference to the global object. */
  var root = freeGlobal || freeSelf || Function('return this')();

  /**
   * Gets the timestamp of the number of milliseconds that have elapsed since
   * the Unix epoch (1 January 1970 00:00:00 UTC).
   *
   * @static
   * @memberOf _
   * @since 2.4.0
   * @category Date
   * @returns {number} Returns the timestamp.
   * @example
   *
   * _.defer(function(stamp) {
   *   console.log(_.now() - stamp);
   * }, _.now());
   * // => Logs the number of milliseconds it took for the deferred invocation.
   */
  var now = function() {
    return root.Date.now();
  };

  /** Used to match a single whitespace character. */
  var reWhitespace = /\s/;

  /**
   * Used by `_.trim` and `_.trimEnd` to get the index of the last non-whitespace
   * character of `string`.
   *
   * @private
   * @param {string} string The string to inspect.
   * @returns {number} Returns the index of the last non-whitespace character.
   */
  function trimmedEndIndex(string) {
    var index = string.length;

    while (index-- && reWhitespace.test(string.charAt(index))) {}
    return index;
  }

  /** Used to match leading whitespace. */
  var reTrimStart = /^\s+/;

  /**
   * The base implementation of `_.trim`.
   *
   * @private
   * @param {string} string The string to trim.
   * @returns {string} Returns the trimmed string.
   */
  function baseTrim(string) {
    return string
      ? string.slice(0, trimmedEndIndex(string) + 1).replace(reTrimStart, '')
      : string;
  }

  /** Built-in value references. */
  var Symbol = root.Symbol;

  /** Used for built-in method references. */
  var objectProto$1 = Object.prototype;

  /** Used to check objects for own properties. */
  var hasOwnProperty = objectProto$1.hasOwnProperty;

  /**
   * Used to resolve the
   * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
   * of values.
   */
  var nativeObjectToString$1 = objectProto$1.toString;

  /** Built-in value references. */
  var symToStringTag$1 = Symbol ? Symbol.toStringTag : undefined;

  /**
   * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
   *
   * @private
   * @param {*} value The value to query.
   * @returns {string} Returns the raw `toStringTag`.
   */
  function getRawTag(value) {
    var isOwn = hasOwnProperty.call(value, symToStringTag$1),
        tag = value[symToStringTag$1];

    try {
      value[symToStringTag$1] = undefined;
      var unmasked = true;
    } catch (e) {}

    var result = nativeObjectToString$1.call(value);
    if (unmasked) {
      if (isOwn) {
        value[symToStringTag$1] = tag;
      } else {
        delete value[symToStringTag$1];
      }
    }
    return result;
  }

  /** Used for built-in method references. */
  var objectProto = Object.prototype;

  /**
   * Used to resolve the
   * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
   * of values.
   */
  var nativeObjectToString = objectProto.toString;

  /**
   * Converts `value` to a string using `Object.prototype.toString`.
   *
   * @private
   * @param {*} value The value to convert.
   * @returns {string} Returns the converted string.
   */
  function objectToString(value) {
    return nativeObjectToString.call(value);
  }

  /** `Object#toString` result references. */
  var nullTag = '[object Null]',
      undefinedTag = '[object Undefined]';

  /** Built-in value references. */
  var symToStringTag = Symbol ? Symbol.toStringTag : undefined;

  /**
   * The base implementation of `getTag` without fallbacks for buggy environments.
   *
   * @private
   * @param {*} value The value to query.
   * @returns {string} Returns the `toStringTag`.
   */
  function baseGetTag(value) {
    if (value == null) {
      return value === undefined ? undefinedTag : nullTag;
    }
    return (symToStringTag && symToStringTag in Object(value))
      ? getRawTag(value)
      : objectToString(value);
  }

  /**
   * Checks if `value` is object-like. A value is object-like if it's not `null`
   * and has a `typeof` result of "object".
   *
   * @static
   * @memberOf _
   * @since 4.0.0
   * @category Lang
   * @param {*} value The value to check.
   * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
   * @example
   *
   * _.isObjectLike({});
   * // => true
   *
   * _.isObjectLike([1, 2, 3]);
   * // => true
   *
   * _.isObjectLike(_.noop);
   * // => false
   *
   * _.isObjectLike(null);
   * // => false
   */
  function isObjectLike(value) {
    return value != null && typeof value == 'object';
  }

  /** `Object#toString` result references. */
  var symbolTag = '[object Symbol]';

  /**
   * Checks if `value` is classified as a `Symbol` primitive or object.
   *
   * @static
   * @memberOf _
   * @since 4.0.0
   * @category Lang
   * @param {*} value The value to check.
   * @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
   * @example
   *
   * _.isSymbol(Symbol.iterator);
   * // => true
   *
   * _.isSymbol('abc');
   * // => false
   */
  function isSymbol(value) {
    return typeof value == 'symbol' ||
      (isObjectLike(value) && baseGetTag(value) == symbolTag);
  }

  /** Used as references for various `Number` constants. */
  var NAN = 0 / 0;

  /** Used to detect bad signed hexadecimal string values. */
  var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;

  /** Used to detect binary string values. */
  var reIsBinary = /^0b[01]+$/i;

  /** Used to detect octal string values. */
  var reIsOctal = /^0o[0-7]+$/i;

  /** Built-in method references without a dependency on `root`. */
  var freeParseInt = parseInt;

  /**
   * Converts `value` to a number.
   *
   * @static
   * @memberOf _
   * @since 4.0.0
   * @category Lang
   * @param {*} value The value to process.
   * @returns {number} Returns the number.
   * @example
   *
   * _.toNumber(3.2);
   * // => 3.2
   *
   * _.toNumber(Number.MIN_VALUE);
   * // => 5e-324
   *
   * _.toNumber(Infinity);
   * // => Infinity
   *
   * _.toNumber('3.2');
   * // => 3.2
   */
  function toNumber(value) {
    if (typeof value == 'number') {
      return value;
    }
    if (isSymbol(value)) {
      return NAN;
    }
    if (isObject(value)) {
      var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
      value = isObject(other) ? (other + '') : other;
    }
    if (typeof value != 'string') {
      return value === 0 ? value : +value;
    }
    value = baseTrim(value);
    var isBinary = reIsBinary.test(value);
    return (isBinary || reIsOctal.test(value))
      ? freeParseInt(value.slice(2), isBinary ? 2 : 8)
      : (reIsBadHex.test(value) ? NAN : +value);
  }

  /** Error message constants. */
  var FUNC_ERROR_TEXT$1 = 'Expected a function';

  /* Built-in method references for those with the same name as other `lodash` methods. */
  var nativeMax = Math.max,
      nativeMin = Math.min;

  /**
   * Creates a debounced function that delays invoking `func` until after `wait`
   * milliseconds have elapsed since the last time the debounced function was
   * invoked. The debounced function comes with a `cancel` method to cancel
   * delayed `func` invocations and a `flush` method to immediately invoke them.
   * Provide `options` to indicate whether `func` should be invoked on the
   * leading and/or trailing edge of the `wait` timeout. The `func` is invoked
   * with the last arguments provided to the debounced function. Subsequent
   * calls to the debounced function return the result of the last `func`
   * invocation.
   *
   * **Note:** If `leading` and `trailing` options are `true`, `func` is
   * invoked on the trailing edge of the timeout only if the debounced function
   * is invoked more than once during the `wait` timeout.
   *
   * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
   * until to the next tick, similar to `setTimeout` with a timeout of `0`.
   *
   * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
   * for details over the differences between `_.debounce` and `_.throttle`.
   *
   * @static
   * @memberOf _
   * @since 0.1.0
   * @category Function
   * @param {Function} func The function to debounce.
   * @param {number} [wait=0] The number of milliseconds to delay.
   * @param {Object} [options={}] The options object.
   * @param {boolean} [options.leading=false]
   *  Specify invoking on the leading edge of the timeout.
   * @param {number} [options.maxWait]
   *  The maximum time `func` is allowed to be delayed before it's invoked.
   * @param {boolean} [options.trailing=true]
   *  Specify invoking on the trailing edge of the timeout.
   * @returns {Function} Returns the new debounced function.
   * @example
   *
   * // Avoid costly calculations while the window size is in flux.
   * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
   *
   * // Invoke `sendMail` when clicked, debouncing subsequent calls.
   * jQuery(element).on('click', _.debounce(sendMail, 300, {
   *   'leading': true,
   *   'trailing': false
   * }));
   *
   * // Ensure `batchLog` is invoked once after 1 second of debounced calls.
   * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
   * var source = new EventSource('/stream');
   * jQuery(source).on('message', debounced);
   *
   * // Cancel the trailing debounced invocation.
   * jQuery(window).on('popstate', debounced.cancel);
   */
  function debounce(func, wait, options) {
    var lastArgs,
        lastThis,
        maxWait,
        result,
        timerId,
        lastCallTime,
        lastInvokeTime = 0,
        leading = false,
        maxing = false,
        trailing = true;

    if (typeof func != 'function') {
      throw new TypeError(FUNC_ERROR_TEXT$1);
    }
    wait = toNumber(wait) || 0;
    if (isObject(options)) {
      leading = !!options.leading;
      maxing = 'maxWait' in options;
      maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
      trailing = 'trailing' in options ? !!options.trailing : trailing;
    }

    function invokeFunc(time) {
      var args = lastArgs,
          thisArg = lastThis;

      lastArgs = lastThis = undefined;
      lastInvokeTime = time;
      result = func.apply(thisArg, args);
      return result;
    }

    function leadingEdge(time) {
      // Reset any `maxWait` timer.
      lastInvokeTime = time;
      // Start the timer for the trailing edge.
      timerId = setTimeout(timerExpired, wait);
      // Invoke the leading edge.
      return leading ? invokeFunc(time) : result;
    }

    function remainingWait(time) {
      var timeSinceLastCall = time - lastCallTime,
          timeSinceLastInvoke = time - lastInvokeTime,
          timeWaiting = wait - timeSinceLastCall;

      return maxing
        ? nativeMin(timeWaiting, maxWait - timeSinceLastInvoke)
        : timeWaiting;
    }

    function shouldInvoke(time) {
      var timeSinceLastCall = time - lastCallTime,
          timeSinceLastInvoke = time - lastInvokeTime;

      // Either this is the first call, activity has stopped and we're at the
      // trailing edge, the system time has gone backwards and we're treating
      // it as the trailing edge, or we've hit the `maxWait` limit.
      return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
        (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
    }

    function timerExpired() {
      var time = now();
      if (shouldInvoke(time)) {
        return trailingEdge(time);
      }
      // Restart the timer.
      timerId = setTimeout(timerExpired, remainingWait(time));
    }

    function trailingEdge(time) {
      timerId = undefined;

      // Only invoke if we have `lastArgs` which means `func` has been
      // debounced at least once.
      if (trailing && lastArgs) {
        return invokeFunc(time);
      }
      lastArgs = lastThis = undefined;
      return result;
    }

    function cancel() {
      if (timerId !== undefined) {
        clearTimeout(timerId);
      }
      lastInvokeTime = 0;
      lastArgs = lastCallTime = lastThis = timerId = undefined;
    }

    function flush() {
      return timerId === undefined ? result : trailingEdge(now());
    }

    function debounced() {
      var time = now(),
          isInvoking = shouldInvoke(time);

      lastArgs = arguments;
      lastThis = this;
      lastCallTime = time;

      if (isInvoking) {
        if (timerId === undefined) {
          return leadingEdge(lastCallTime);
        }
        if (maxing) {
          // Handle invocations in a tight loop.
          clearTimeout(timerId);
          timerId = setTimeout(timerExpired, wait);
          return invokeFunc(lastCallTime);
        }
      }
      if (timerId === undefined) {
        timerId = setTimeout(timerExpired, wait);
      }
      return result;
    }
    debounced.cancel = cancel;
    debounced.flush = flush;
    return debounced;
  }

  /** Error message constants. */
  var FUNC_ERROR_TEXT = 'Expected a function';

  /**
   * Creates a throttled function that only invokes `func` at most once per
   * every `wait` milliseconds. The throttled function comes with a `cancel`
   * method to cancel delayed `func` invocations and a `flush` method to
   * immediately invoke them. Provide `options` to indicate whether `func`
   * should be invoked on the leading and/or trailing edge of the `wait`
   * timeout. The `func` is invoked with the last arguments provided to the
   * throttled function. Subsequent calls to the throttled function return the
   * result of the last `func` invocation.
   *
   * **Note:** If `leading` and `trailing` options are `true`, `func` is
   * invoked on the trailing edge of the timeout only if the throttled function
   * is invoked more than once during the `wait` timeout.
   *
   * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
   * until to the next tick, similar to `setTimeout` with a timeout of `0`.
   *
   * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
   * for details over the differences between `_.throttle` and `_.debounce`.
   *
   * @static
   * @memberOf _
   * @since 0.1.0
   * @category Function
   * @param {Function} func The function to throttle.
   * @param {number} [wait=0] The number of milliseconds to throttle invocations to.
   * @param {Object} [options={}] The options object.
   * @param {boolean} [options.leading=true]
   *  Specify invoking on the leading edge of the timeout.
   * @param {boolean} [options.trailing=true]
   *  Specify invoking on the trailing edge of the timeout.
   * @returns {Function} Returns the new throttled function.
   * @example
   *
   * // Avoid excessively updating the position while scrolling.
   * jQuery(window).on('scroll', _.throttle(updatePosition, 100));
   *
   * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
   * var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
   * jQuery(element).on('click', throttled);
   *
   * // Cancel the trailing throttled invocation.
   * jQuery(window).on('popstate', throttled.cancel);
   */
  function throttle(func, wait, options) {
    var leading = true,
        trailing = true;

    if (typeof func != 'function') {
      throw new TypeError(FUNC_ERROR_TEXT);
    }
    if (isObject(options)) {
      leading = 'leading' in options ? !!options.leading : leading;
      trailing = 'trailing' in options ? !!options.trailing : trailing;
    }
    return debounce(func, wait, {
      'leading': leading,
      'maxWait': wait,
      'trailing': trailing
    });
  }

  function sleep(time) {
    return new Promise((resolve) => {
      setTimeout(resolve, time);
    });
  }

  async function wait(selector) {
    let bool = selector();
    while (!bool) {
      await sleep();
      bool = selector();
    }
  }
  async function waitDOM(selector) {
    await wait(() => !!document.querySelector(selector));
    return document.querySelector(selector);
  }

  function normalizeKeyEvent(e) {
    const SPECIAL_KEY_EN = "`-=[]\\;',./~!@#$%^&*()_+{}|:\"<>?".split("");
    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("");
    let key = e.key;
    if (e.code === "Space") {
      key = "Space";
    }
    if (/^[a-z]$/.test(key)) {
      key = key.toUpperCase();
    } else if (SPECIAL_KEY_ZH.includes(key)) {
      key = SPECIAL_KEY_EN[SPECIAL_KEY_ZH.indexOf(key)];
    }
    let keyArr = [];
    e.ctrlKey && keyArr.push("ctrl");
    e.metaKey && keyArr.push("meta");
    e.shiftKey && !SPECIAL_KEY_EN.includes(key) && keyArr.push("shift");
    e.altKey && keyArr.push("alt");
    if (!/Control|Meta|Shift|Alt/i.test(key))
      keyArr.push(key);
    keyArr = [...new Set(keyArr)];
    return keyArr.join("+");
  }
  function keybind(keys, keydown, keyup) {
    const isMac = /macintosh|mac os x/i.test(navigator.userAgent);
    keys = keys.filter((key) => !key.includes(isMac ? "ctrl" : "meta"));
    function createProcess(callback) {
      return function(e) {
        var _a;
        if (((_a = document.activeElement) == null ? void 0 : _a.tagName) === "INPUT")
          return;
        const normalizedKey = normalizeKeyEvent(e).toLowerCase();
        for (const key of keys) {
          if (key.toLowerCase() === normalizedKey)
            callback(e, key);
        }
      };
    }
    window.addEventListener("keydown", createProcess(keydown));
    if (keyup)
      window.addEventListener("keyup", createProcess(keyup));
  }

  async function openControl() {
    const li = await waitDOM("li.comicContentPopupImageItem");
    li.dispatchEvent(fakeClickEvent());
    await sleep(0);
    li.dispatchEvent(fakeClickEvent());
  }
  function fakeClickEvent() {
    const { width, height } = document.body.getBoundingClientRect();
    return new MouseEvent("click", { clientX: width / 2, clientY: height / 2 });
  }
  async function currentPage() {
    try {
      if (!/h5\/comicContent\/.*/.test(location.href))
        return;
      const scrollHeight = document.scrollingElement.scrollTop;
      const list = await waitHasComicContent();
      let height = 0;
      for (let i = 0; i < list.length; i++) {
        const item = list[i];
        height += item.getBoundingClientRect().height;
        if (height > scrollHeight) {
          const dom = document.querySelector(".comicContentPopup .comicFixed");
          dom.textContent = dom.textContent.replace(/(.*)\//, `${i + 1}/`);
          break;
        }
      }
    } catch (e) {
    }
  }
  let trackId = { current: 0 };
  async function runH5main() {
    try {
      if (!/h5\/comicContent\/.*/.test(location.href))
        return;
      let runTrackId = ++trackId.current;
      const ulDom = await waitDOM(".comicContentPopupImageList");
      if (runTrackId !== trackId.current)
        return;
      const uuid = getComicId();
      const domUUID = ulDom.dataset.uuid;
      if (domUUID !== uuid) {
        ulDom.dataset.uuid = uuid;
      }
      injectFixImg$1();
      injectFastLoadImg$1();
      const main = ulDom.parentElement;
      main.style.position = "unset";
      main.style.overflowY = "unset";
      createNextPartDom();
    } catch (error) {
      throw error;
    }
  }
  async function createNextPartDom() {
    let nextPartDom = document.querySelector("#comicContentMain .next-part-btn");
    let nextButton = document.querySelector(".comicControlBottomTop > div:nth-child(3) > span");
    if (!nextPartDom) {
      if (!nextButton) {
        await openControl();
        nextButton = document.querySelector(".comicControlBottomTop > div:nth-child(3) > span");
      }
      nextPartDom = document.createElement("div");
      nextPartDom.className = "next-part-btn";
      nextPartDom.textContent = "\u4E0B\u4E00\u8BDD";
      nextPartDom.onclick = async (e) => {
        e.stopPropagation();
        nextButton && nextButton.click();
        document.scrollingElement.scrollTop = 0;
      };
      document.getElementById("comicContentMain").appendChild(nextPartDom);
    }
    nextPartDom.style.display = nextButton.parentElement.classList.contains("noneUuid") ? "none" : "block";
    let fixedNextBtn = document.querySelector(".next-part-btn-fixed");
    if (!fixedNextBtn) {
      fixedNextBtn = document.createElement("div");
      fixedNextBtn.className = "next-part-btn-fixed";
      fixedNextBtn.textContent = "\u4E0B\u4E00\u8BDD";
      document.body.appendChild(fixedNextBtn);
      window.addEventListener("scroll", throttle(() => {
        if (!/h5\/comicContent\/.*/.test(location.href)) {
          fixedNextBtn == null ? void 0 : fixedNextBtn.classList.add("hide");
          return;
        }
        const dom = document.scrollingElement;
        if (dom.scrollTop < 50 || dom.scrollTop + dom.clientHeight > dom.scrollHeight - 800) {
          fixedNextBtn == null ? void 0 : fixedNextBtn.classList.remove("hide");
        } else {
          fixedNextBtn == null ? void 0 : fixedNextBtn.classList.add("hide");
        }
      }, 100));
    }
    fixedNextBtn.onclick = nextPartDom.onclick;
    fixedNextBtn.style.display = nextPartDom.style.display;
  }
  function getComicId() {
    const [, uuid] = location.href.match(/h5\/comicContent\/.*\/(.*)/);
    return uuid;
  }
  async function waitHasComicContent() {
    return document.querySelectorAll(".comicContentPopupImageItem");
  }
  async function addH5HistoryListener() {
    history.pushState = _historyWrap("pushState");
    history.replaceState = _historyWrap("replaceState");
    window.addEventListener("pushState", runH5main);
    window.addEventListener("replaceState", runH5main);
    window.addEventListener("popstate", runH5main);
    window.addEventListener("scroll", throttle(currentPage, 100));
    runH5main();
  }
  const _historyWrap = function(type) {
    const orig = history[type];
    const e = new Event(type);
    return function() {
      const rv = orig.apply(this, arguments);
      window.dispatchEvent(e);
      return rv;
    };
  };
  async function injectFixImg$1() {
    const listDOM = await waitDOM(".comicContentPopupImageList");
    async function injectEvent() {
      const imgs = document.querySelectorAll("ul li img");
      imgs.forEach(addErrorListener);
    }
    const ob = new MutationObserver(injectEvent);
    ob.observe(listDOM, { childList: true, subtree: true });
    injectEvent();
  }
  async function injectFastLoadImg$1() {
    const $list = await waitDOM(".comicContentPopupImageList");
    function fastLoad() {
      const $imgs = document.querySelectorAll("ul li img");
      $imgs.forEach(($img) => {
        if ($img.dataset.fastLoad === $img.dataset.src)
          return;
        $img.dataset.fastLoad = $img.dataset.src;
        $img.src = $img.dataset.src;
      });
    }
    const ob = new MutationObserver(fastLoad);
    ob.observe($list, {
      childList: true,
      subtree: true,
      attributes: true,
      attributeFilter: ["data-src"]
    });
  }
  function h5() {
    addH5HistoryListener();
  }

  function replaceHeader() {
    const header = document.querySelector(".container.header-log .row");
    if (header) {
      header.style.flexWrap = "nowrap";
      header.querySelector("div:nth-child(6)").replaceWith(s2d(`<div class="col-1">
          <div class="log-txt">
            <a href="/web/person/shujia">\u6211\u7684\u4E66\u67B6</a>
            <div class="log-unboder"></div>
          </div>
        </div>`));
      header.querySelector("div:nth-child(7)").replaceWith(s2d(`<div class="col-1">
          <div class="log-txt">
            <a href="/web/person/liulan">\u6211\u7684\u6D4F\u89C8</a>
            <div class="log-unboder"></div>
          </div>
        </div>`));
      header.querySelector("div:nth-child(8)").className = "col";
      header.querySelector("div.col > div > div").style.justifyContent = "flex-end";
    }
  }
  async function injectFixImg() {
    const listDOM = await waitDOM("ul.comicContent-list");
    async function injectEvent2() {
      const imgs = document.querySelectorAll("ul li img");
      imgs.forEach(addErrorListener);
    }
    const ob = new MutationObserver(injectEvent2);
    ob.observe(listDOM, { childList: true, subtree: true });
    injectEvent2();
  }
  async function injectFastLoadImg() {
    const $list = await waitDOM(".comicContent-list");
    function fastLoad() {
      const $imgs = $list.querySelectorAll("li img");
      $imgs.forEach(($img) => {
        if ($img.dataset.fastLoad === "true")
          return;
        $img.dataset.fastLoad = "true";
        $img.src = $img.dataset.src;
      });
    }
    const ob = new MutationObserver(fastLoad);
    ob.observe($list, { childList: true, subtree: true });
  }
  async function removeMouseupEvent() {
    await wait(() => !!document.body.onmouseup);
    document.body.onmouseup = null;
  }
  async function injectEvent() {
    keybind(["z", "x"], (e, key) => {
      var _a, _b;
      switch (key) {
        case "z": {
          (_a = document.querySelector(`[class='comicContent-prev'] a`)) == null ? void 0 : _a.click();
          break;
        }
        case "x": {
          (_b = document.querySelector(`[class='comicContent-next'] a`)) == null ? void 0 : _b.click();
          break;
        }
      }
    });
  }
  function pc() {
    if (/comic\/.*\/chapter/.test(location.href)) {
      injectFixImg();
      injectFastLoadImg();
      removeMouseupEvent();
      injectEvent();
    }
    replaceHeader();
  }

  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}}

  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(css,{});

  document.body.classList.add("k-copymanga");
  if (location.pathname.startsWith("/h5")) {
    h5();
  } else {
    pc();
  }

})();

QingJ © 2025

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