Viewer for XiuRen22

秀人相册查看器:列表页快速预览,详情页用 Viewer.js 浏览全部图片

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey, Greasemonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey किंवा Violentmonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला Tampermonkey यासारखे एक्स्टेंशन इंस्टॉल करावे लागेल..

ही स्क्रिप्ट इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्क्रिप्ट व्यवस्थापक एक्स्टेंशन इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्क्रिप्ट व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला Stylus सारखे एक्स्टेंशन इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

ही स्टाईल इंस्टॉल करण्यासाठी तुम्हाला एक युझर स्टाईल व्यवस्थापक इंस्टॉल करावे लागेल.

(माझ्याकडे आधीच युझर स्टाईल व्यवस्थापक आहे, मला इंस्टॉल करू द्या!)

// ==UserScript==
// @name         Viewer for XiuRen22
// @namespace    http://tampermonkey.net/XiuRen22
// @version      1.0.0
// @description  秀人相册查看器:列表页快速预览,详情页用 Viewer.js 浏览全部图片
// @author       You
// @include      *://*.xx05.my/*
// @include      *://*xiuren*
// @include      *://*xr*
// @include      *://*.*/*XiuRen*
// @icon         https://www.google.com/s2/favicons?domain=xiurenb.net
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_getResourceText
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_listValues
// @grant        GM_deleteValue
// @require      https://cdnjs.cloudflare.com/ajax/libs/viewerjs/1.11.8/viewer.min.js
// @resource     viewerCSS   https://cdnjs.cloudflare.com/ajax/libs/viewerjs/1.11.8/viewer.min.css
// ==/UserScript==

(() => {
  // src/shared/constants.js
  var API_BASE_URL = "http://de.ik5.cc/06/";
  var DEBUG_INFO = true;
  var DEBUG_VERBOSE = true;
  var CACHE_TTL = 7 * 24 * 60 * 60 * 1e3;
  var VIEWER_OPTIONS = {
    fullscreen: false,
    interval: 1200,
    loop: false,
    transition: false
  };

  // src/shared/debug.js
  function debugInfo(...data) {
    if (DEBUG_INFO) console.log("[INFO]", ...data);
  }
  function debugVerbose(...data) {
    if (DEBUG_VERBOSE) console.log("[VERBOSE]", ...data);
  }

  // src/shared/img.js
  function fixImgSrc(src) {
    if (!src || src.indexOf("/uploadfile/") === -1) return src;
    const parts = src.split("/uploadfile/");
    return parts.length > 1 ? API_BASE_URL + "Uploadfile/" + parts[1] : src;
  }
  function fixImgNode(img) {
    const raw = img.getAttribute("src");
    const fixed = fixImgSrc(raw);
    if (raw === fixed) return;
    img.setAttribute("src", fixed);
    const origin = img.getAttribute("data-original");
    if (origin) img.setAttribute("data-original", fixImgSrc(origin));
  }
  function fixPageImages(root) {
    for (const img of root.getElementsByTagName("img")) fixImgNode(img);
  }
  function imageParagraphNode(rootDoc) {
    for (const img of rootDoc.getElementsByTagName("img")) {
      const src = img.getAttribute("src");
      if (!src) continue;
      if (src.indexOf("/uploadfile/2") !== -1 || src.indexOf("Uploadfile/2") !== -1) {
        return img.parentNode;
      }
    }
    return null;
  }

  // src/shared/cache.js
  function createCache(prefix, ttl) {
    const memCache = {};
    function get(url) {
      const key = prefix + url;
      if (memCache[key]) return memCache[key];
      let entry = null;
      try {
        const raw = GM_getValue(key, null);
        if (Array.isArray(raw)) {
          entry = { urls: raw, pageCount: 1, time: Date.now() };
          try {
            GM_setValue(key, entry);
          } catch (e) {
          }
        } else if (raw && Array.isArray(raw.urls)) {
          entry = raw;
        }
      } catch (e) {
        return null;
      }
      if (!entry || Date.now() - entry.time > ttl) {
        delete memCache[key];
        try {
          GM_deleteValue(key);
        } catch (e) {
        }
        return null;
      }
      memCache[key] = entry;
      return entry;
    }
    function set(url, urls, pageCount) {
      const key = prefix + url;
      const entry = { urls, pageCount: pageCount || 1, time: Date.now() };
      memCache[key] = entry;
      try {
        GM_setValue(key, entry);
      } catch (e) {
        console.warn("\u6301\u4E45\u7F13\u5B58\u5199\u5165\u5931\u8D25\uFF0C\u6E05\u7406\u8FC7\u671F\u7F13\u5B58\u540E\u91CD\u8BD5", e);
        purge();
        try {
          GM_setValue(key, entry);
        } catch (e2) {
        }
      }
    }
    function purge() {
      const now = Date.now();
      try {
        GM_listValues().forEach((key) => {
          if (key.indexOf(prefix) !== 0) return;
          const raw = GM_getValue(key, null);
          if (!raw || Array.isArray(raw)) return;
          if (!raw.time || now - raw.time > ttl) {
            GM_deleteValue(key);
          }
        });
      } catch (e) {
      }
    }
    return { get, set, purge };
  }

  // src/shared/page.js
  function fetchPageHtml(url) {
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        url,
        method: "GET",
        onload: (xhr) => resolve(xhr.response),
        onerror: reject
      });
    });
  }
  function parseDetailPage(html) {
    const doc = new DOMParser().parseFromString(html, "text/html");
    const node = imageParagraphNode(doc);
    const urls = node ? Array.from(node.getElementsByTagName("img")).map((img) => img.getAttribute("src")).filter(Boolean).map(fixImgSrc) : [];
    const current = doc.getElementsByClassName("current")[0];
    const indicator = current ? current.parentNode : null;
    let pageCount = 1;
    if (indicator) {
      indicator.querySelectorAll("a").forEach((a) => {
        const m = a.textContent.trim().match(/^(\d+)$/);
        if (m) pageCount = Math.max(pageCount, parseInt(m[1], 10));
        const h = (a.getAttribute("href") || "").match(/_(\d+)\.html$/);
        if (h) pageCount = Math.max(pageCount, parseInt(h[1], 10) + 1);
      });
    }
    return { urls, pageCount };
  }
  function parseImageUrlsFromHtml(html) {
    return parseDetailPage(html).urls;
  }
  function createImageLoader(cache2) {
    async function fetchPageImageUrls(url) {
      const cached = cache2.get(url);
      if (cached) {
        debugVerbose(`\u7F13\u5B58\u547D\u4E2D: ${url} (${cached.urls.length} \u5F20)`);
        return cached.urls;
      }
      debugInfo("\u91C7\u96C6\u56FE\u7247\u81EA", url);
      const html = await fetchPageHtml(url);
      const urls = parseImageUrlsFromHtml(html);
      debugVerbose(`\u89E3\u6790\u5230 ${urls.length} \u5F20\u56FE\u7247: ${url}`);
      if (urls.length > 0) cache2.set(url, urls, 1);
      return urls;
    }
    async function loadSubPageUrls(baseUrl, pageCount) {
      if (pageCount <= 1) {
        const all = [];
        let i = 1;
        while (true) {
          const urls = await fetchPageImageUrls(baseUrl + "_" + i + ".html");
          if (urls.length === 0) break;
          all.push(...urls);
          i++;
        }
        return all;
      }
      const tasks = [];
      for (let i = 1; i < pageCount; i++) {
        tasks.push(fetchPageImageUrls(baseUrl + "_" + i + ".html"));
      }
      const results = await Promise.allSettled(tasks);
      return results.filter((r) => r.status === "fulfilled").map((r) => r.value).flat();
    }
    return { fetchPageImageUrls, loadSubPageUrls };
  }

  // src/shared/preview.js
  async function openGalleryPreview(btn, galleryUrl, { cache: cache2, loader: loader2 }) {
    const originalText = btn.innerText;
    btn.disabled = true;
    btn.innerText = "...";
    debugInfo("\u5F00\u59CB\u5FEB\u901F\u9884\u89C8:", galleryUrl);
    try {
      let firstUrls;
      let pageCount;
      const cached = cache2.get(galleryUrl);
      if (cached && cached.urls.length > 0) {
        firstUrls = cached.urls;
        pageCount = cached.pageCount;
        debugVerbose(`\u7F13\u5B58\u547D\u4E2D: ${galleryUrl} (${firstUrls.length} \u5F20)`);
      } else {
        const html = await fetchPageHtml(galleryUrl);
        const data = parseDetailPage(html);
        firstUrls = data.urls;
        pageCount = data.pageCount;
        if (firstUrls.length === 0) {
          alert("\u672A\u627E\u5230\u56FE\u7247");
          return;
        }
        cache2.set(galleryUrl, firstUrls, pageCount);
      }
      const ul = document.createElement("ul");
      ul.style.display = "none";
      document.body.appendChild(ul);
      const addImgs = (urls) => {
        for (const url of urls) {
          const img = document.createElement("img");
          img.src = url;
          img.style.maxHeight = "1080px";
          ul.appendChild(img);
        }
      };
      addImgs(firstUrls);
      let closed = false;
      let playerObserver = null;
      const viewer = new Viewer(ul, {
        ...VIEWER_OPTIONS,
        title: false,
        hidden() {
          closed = true;
          if (playerObserver) playerObserver.disconnect();
          viewer.destroy();
          ul.remove();
          btn.innerText = originalText;
        }
      });
      viewer.show();
      if (viewer.player) {
        playerObserver = new MutationObserver(() => {
          if (!viewer.played) return;
          const imgs = viewer.player.querySelectorAll("img");
          const last = imgs[imgs.length - 1];
          if (last && last.classList.contains("viewer-in")) {
            setTimeout(() => {
              if (viewer.played) viewer.stop();
            }, Math.max(50, viewer.options.interval - 100));
          }
        });
        playerObserver.observe(viewer.player, {
          attributes: true,
          subtree: true,
          attributeFilter: ["class"]
        });
      }
      const baseUrl = galleryUrl.replace(".html", "");
      const extraUrls = await loader2.loadSubPageUrls(baseUrl, pageCount);
      if (!closed && extraUrls.length > 0) {
        addImgs(extraUrls);
        viewer.update();
      }
      debugInfo("\u5FEB\u901F\u9884\u89C8\u5171\u52A0\u8F7D", ul.children.length, "\u5F20\u56FE\u7247");
    } catch (e) {
      console.error("Quick Preview Error:", e);
      btn.innerText = "Err";
      setTimeout(() => {
        btn.innerText = originalText;
      }, 2e3);
    } finally {
      btn.disabled = false;
    }
  }

  // src/shared/random.js
  function createRandomPlayButton({ getUrls, cache: cache2, loader: loader2 }) {
    const btn = document.createElement("button");
    btn.className = "xr-random-play";
    btn.title = "\u968F\u673A\u64AD\u653E\u5F53\u524D\u7011\u5E03\u6D41\u4E2D\u7684\u4E00\u4E2A\u76F8\u518C";
    btn.textContent = "\u968F\u673A\u64AD\u653E";
    btn.addEventListener("click", () => {
      const urls = getUrls();
      if (urls.length === 0) {
        alert("\u6682\u65E0\u53EF\u64AD\u653E\u7684\u76F8\u518C");
        return;
      }
      const url = urls[Math.floor(Math.random() * urls.length)];
      debugInfo("\u968F\u673A\u64AD\u653E:", url);
      openGalleryPreview(btn, url, { cache: cache2, loader: loader2 });
    });
    document.body.appendChild(btn);
    return btn;
  }

  // src/shared/styles.js
  function addSharedStyles() {
    GM_addStyle(GM_getResourceText("viewerCSS"));
    GM_addStyle(`
    .xiuren-preview-btn {
      position: absolute;
      top: 5px;
      right: 5px;
      z-index: 1000;
      padding: 4px 8px;
      font-size: 12px;
      opacity: 0.9;
      display: inline-block;
      background-color: #e91e63;
      color: #fff;
      border-radius: 4px;
      cursor: pointer;
      font-weight: bold;
      border: none;
      transition: background-color 0.3s;
      line-height: normal;
    }
    .xiuren-preview-btn:hover {
      background-color: #c2185b;
    }
    .xiuren-preview-btn:disabled {
      background-color: #ccc;
      cursor: not-allowed;
    }
    /* \u7F29\u7565\u56FE\u5DE6\u4E0B\u89D2\u65E5\u671F\u5FBD\u6807 */
    .xr-s-date {
      position: absolute;
      left: 5px;
      bottom: 5px;
      z-index: 1;
      padding: 2px 6px;
      font-size: 11px;
      line-height: 1.2;
      color: #fff;
      background: rgba(0, 0, 0, 0.55);
      border-radius: 3px;
      pointer-events: none;
    }
    /* \u53F3\u4E0B\u89D2\u60AC\u6D6E\u7684\u968F\u673A\u64AD\u653E\u6309\u94AE */
    .xr-random-play {
      position: fixed;
      right: 20px;
      bottom: 20px;
      z-index: 9999;
      padding: 10px 16px;
      font-size: 14px;
      font-weight: bold;
      color: #fff;
      background: #e91e63;
      border: none;
      border-radius: 20px;
      cursor: pointer;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
    }
    .xr-random-play:hover {
      background: #c2185b;
    }
    .xr-random-play:disabled {
      background: #ccc;
      cursor: not-allowed;
    }
  `);
  }

  // src/shared/infinite.js
  function setupInfiniteScroll({ loadPage }) {
    let loading = false;
    let stopped = false;
    async function maybeLoad() {
      if (loading || stopped) return;
      if (window.innerHeight + window.scrollY < document.documentElement.scrollHeight - 600) {
        return;
      }
      loading = true;
      let more = false;
      try {
        more = await loadPage();
      } catch (e) {
        more = false;
      }
      loading = false;
      if (!more) {
        stopped = true;
        return;
      }
      maybeLoad();
    }
    let ticking = false;
    const onScroll = () => {
      if (ticking) return;
      ticking = true;
      requestAnimationFrame(() => {
        ticking = false;
        maybeLoad();
      });
    };
    window.addEventListener("scroll", onScroll);
    window.addEventListener("resize", onScroll);
    maybeLoad();
  }

  // src/shared/util.js
  function extractPostId(url) {
    const m = url.match(/(\d+)\.html/);
    return m ? m[1] : null;
  }

  // src/main.js
  var CACHE_PREFIX = "xr_cache_";
  var LIST_ITEM_SELECTOR = ".update_area_lists .i_list";
  var LIST_CONTAINER_SELECTOR = "ul.update_area_lists";
  var cache = createCache(CACHE_PREFIX, CACHE_TTL);
  var loader = createImageLoader(cache);
  addSharedStyles();
  GM_addStyle(`
  /* \u5217\u8868\u9875\u5361\u7247\u6837\u5F0F\u7EDF\u4E00\u4E3A\u641C\u7D22\u9875\u98CE\u683C\uFF1A\u54CD\u5E94\u5F0F\u7EA6 5 \u5217\u5706\u89D2\u5361\u7247 */
  ul.update_area_lists {
    display: grid !important;
    grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
    gap: 12px;
    padding: 12px;
    margin: 0;
    list-style: none;
  }
  ul.update_area_lists > .i_list {
    position: relative;
    overflow: hidden;
    border-radius: 6px;
    background: #fff;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.12);
    margin: 0 !important;
    width: auto !important;
  }
  ul.update_area_lists .i_list > a {
    display: block;
    position: relative;
  }
  ul.update_area_lists .i_list img {
    width: 100%;
    aspect-ratio: 3 / 4;
    object-fit: cover;
    display: block !important;
  }
  ul.update_area_lists .case_info {
    padding: 0;
  }
  ul.update_area_lists .case_info .meta-title {
    padding: 6px 8px;
    font-size: 12px;
    line-height: 17px;
    color: #333;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  ul.update_area_lists .case_info .meta-post {
    display: none;
  }
  /* \u6A21\u7279\u540D\u505A\u6210\u5C0F\u5FBD\u6807\uFF0C\u653E\u5728\u7F29\u7565\u56FE\u53F3\u4E0B\u89D2\uFF08\u4E0E\u5DE6\u4E0B\u89D2\u65E5\u671F\u5FBD\u6807\u5BF9\u79F0\uFF09 */
  ul.update_area_lists .postlist-imagenum {
    position: absolute;
    right: 5px;
    bottom: 5px;
    left: auto;
    top: auto;
    z-index: 1;
    width: auto !important;
    max-width: calc(100% - 10px);
    padding: 2px 6px;
    background: rgba(0, 0, 0, 0.55);
    border-radius: 3px;
  }
  .postlist-imagenum span {
    color: #fff !important;
    cursor: pointer;
  }
  .postlist-imagenum span:hover {
    text-decoration: underline;
  }
`);
  function scanListItems() {
    document.querySelectorAll(LIST_ITEM_SELECTOR).forEach((item) => {
      if (item.dataset.xiurenPreviewDone) return;
      item.dataset.xiurenPreviewDone = "true";
      const link = item.querySelector('a[href*=".html"]');
      if (!link) return;
      const href = link.getAttribute("href");
      if (!extractPostId(href)) return;
      item.style.position = "relative";
      const meta = item.querySelector(".case_info .meta-post");
      const dateMatch = meta ? meta.textContent.match(/(\d{4})\.(\d{1,2})\.(\d{1,2})/) : null;
      if (dateMatch) {
        const badge = document.createElement("span");
        badge.className = "xr-s-date";
        badge.textContent = `${dateMatch[1].slice(-2)}.${parseInt(dateMatch[2], 10)}.${parseInt(dateMatch[3], 10)}`;
        link.appendChild(badge);
      }
      const btn = document.createElement("button");
      btn.className = "xiuren-preview-btn";
      btn.innerText = "\u25B6";
      btn.title = "Quick Preview";
      btn.addEventListener("click", (e) => {
        e.preventDefault();
        e.stopPropagation();
        openGalleryPreview(btn, href, { cache, loader });
      });
      item.appendChild(btn);
      link.addEventListener("click", (e) => {
        e.preventDefault();
        e.stopPropagation();
        openGalleryPreview(btn, href, { cache, loader });
      });
      const modelSpan = item.querySelector(".postlist-imagenum span");
      if (modelSpan) {
        modelSpan.addEventListener("click", (e) => {
          e.preventDefault();
          e.stopPropagation();
          const name = modelSpan.textContent.trim();
          if (!name) return;
          window.open(
            location.origin + "/plus/search/index.asp?keyword=" + encodeURIComponent(name) + "&searchtype=titlekeywords",
            "_blank"
          );
        });
      }
    });
  }
  var infiniteScroll = {
    current: 1,
    // 已加载到的页码(首页为 1)
    total: 1,
    // 总页数
    finished: false
  };
  function detectPagination() {
    const m = location.pathname.match(/index(\d+)\.html$/);
    infiniteScroll.current = m ? parseInt(m[1], 10) : 1;
    let max = 1;
    document.querySelectorAll('.page a[href*="index"]').forEach((a) => {
      const n = a.getAttribute("href").match(/index(\d+)\.html/);
      if (n) max = Math.max(max, parseInt(n[1], 10));
    });
    infiniteScroll.total = max;
    infiniteScroll.finished = infiniteScroll.current >= max;
  }
  function nextPageUrl() {
    const base = location.pathname.replace(/index\d+\.html$/, "").replace(/\/+$/, "") + "/";
    return location.origin + base + `index${infiniteScroll.current + 1}.html`;
  }
  function parseListItems(html) {
    const doc = new DOMParser().parseFromString(html, "text/html");
    return doc.querySelectorAll(LIST_ITEM_SELECTOR);
  }
  function appendListPage(items) {
    const container = document.querySelector(LIST_CONTAINER_SELECTOR);
    if (!container || items.length === 0) return false;
    items.forEach((li) => {
      const node = document.importNode(li, true);
      fixPageImages(node);
      container.appendChild(node);
    });
    return true;
  }
  function loadNextListPage() {
    return new Promise((resolve) => {
      if (infiniteScroll.finished) {
        resolve(false);
        return;
      }
      const url = nextPageUrl();
      debugInfo("\u7011\u5E03\u6D41\u52A0\u8F7D\u7B2C", infiniteScroll.current + 1, "\u9875:", url);
      GM_xmlhttpRequest({
        url,
        method: "GET",
        onload: (xhr) => {
          const items = parseListItems(xhr.response);
          let more = false;
          if (appendListPage(items)) {
            infiniteScroll.current++;
            debugInfo("\u5DF2\u8FFD\u52A0\u7B2C", infiniteScroll.current, "\u9875\uFF0C\u5171", items.length, "\u4E2A\u76F8\u518C");
            scanListItems();
            more = true;
          } else {
            infiniteScroll.finished = true;
            debugInfo("\u7011\u5E03\u6D41\u7ED3\u675F\uFF1A\u8BE5\u9875\u65E0\u66F4\u591A\u76F8\u518C");
          }
          if (infiniteScroll.current >= infiniteScroll.total) {
            infiniteScroll.finished = true;
            debugInfo("\u7011\u5E03\u6D41\u7ED3\u675F\uFF1A\u5DF2\u52A0\u8F7D\u5168\u90E8\u9875\u9762");
          }
          resolve(more);
        },
        onerror: () => {
          console.error("XiuRen: \u7011\u5E03\u6D41\u52A0\u8F7D\u5931\u8D25", url);
          infiniteScroll.finished = true;
          resolve(false);
        }
      });
    });
  }
  function initListPage() {
    scanListItems();
    new MutationObserver(scanListItems).observe(document.body, {
      childList: true,
      subtree: true
    });
    createRandomPlayButton({
      getUrls() {
        const urls = [];
        document.querySelectorAll(LIST_ITEM_SELECTOR).forEach((item) => {
          const link = item.querySelector('a[href*=".html"]');
          const href = link ? link.getAttribute("href") : null;
          if (href && extractPostId(href)) urls.push(href);
        });
        return urls;
      },
      cache,
      loader
    });
    detectPagination();
    if (infiniteScroll.finished) return;
    setupInfiniteScroll({ loadPage: loadNextListPage });
  }
  function getPageImages(i, htmlDoc) {
    const node = imageParagraphNode(htmlDoc);
    if (!node) return [];
    const template = document.createElement("div");
    template.innerHTML = `<div class='child${i}'>${node.outerHTML}</div>`;
    return Array.from(template.firstChild.getElementsByTagName("img"));
  }
  function getPageNumbers() {
    const current = document.getElementsByClassName("current")[0];
    const indicator = current ? current.parentNode : null;
    if (!indicator) return [];
    const nums = [];
    indicator.querySelectorAll("a").forEach((a) => {
      const m = a.textContent.trim().match(/^(\d+)$/);
      if (m) nums.push(parseInt(m[1], 10));
    });
    return nums;
  }
  function buildPageUrls(href, pageNumbers) {
    const urls = [href];
    for (const num of pageNumbers) {
      const page = parseInt(num, 10);
      if (!page || page - 1 === 0) continue;
      urls.push(href.slice(0, href.lastIndexOf(".")) + `_${page - 1}.html`);
    }
    return urls;
  }
  function initGalleryPage(href) {
    const paragraph = imageParagraphNode(document);
    if (!paragraph) return;
    const container = paragraph.parentNode;
    const viewerUl = document.createElement("ul");
    viewerUl.id = "imagesViewer";
    container.appendChild(viewerUl);
    const gallery = new Viewer(viewerUl, VIEWER_OPTIONS);
    paragraph.remove();
    const pageUrls = buildPageUrls(href, getPageNumbers());
    const pagesMap = {};
    function updateImgSet(page, imgSet) {
      pagesMap[page] = imgSet;
      debugVerbose(
        `update_img_set: \u52A0\u8F7D\u9875\u9762 ${page + 1} / ${pageUrls.length}, \u5171\u8BA1 ${imgSet.length} \u5F20\u56FE\u7247`
      );
      if (Object.keys(pagesMap).length === pageUrls.length) {
        debugInfo("\u6240\u6709\u9875\u9762\u7684\u4FE1\u606F\u5DF2\u7ECF\u52A0\u8F7D\u5230 pages_map \u4E2D");
        let total = 0;
        for (let i = 0; i < pageUrls.length; i++) {
          const imgs = pagesMap[i];
          if (!imgs) continue;
          let pageCount = 0;
          for (const img of imgs) {
            img.style.maxHeight = "1080px";
            fixImgNode(img);
            viewerUl.appendChild(img);
            total++;
            pageCount++;
          }
          debugInfo(i, "\u9875", pageCount, "\u5F20");
        }
        debugInfo("\u5171\u8BA1", total, "\u5F20");
      }
      gallery.update();
    }
    pageUrls.forEach((url, i) => {
      debugInfo("\u91C7\u96C6\u56FE\u7247\u81EA", url);
      GM_xmlhttpRequest({
        url,
        method: "GET",
        onload: (xhr) => {
          const doc = new DOMParser().parseFromString(xhr.response, "text/html");
          updateImgSet(i, getPageImages(i, doc));
        }
      });
    });
  }
  function init() {
    cache.purge();
    fixPageImages(document);
    const href = window.location.href;
    debugInfo("handle: " + href);
    if (href.indexOf("_") !== -1) return;
    if (document.querySelector(".update_area_lists")) {
      debugInfo("\u68C0\u6D4B\u5230\u5217\u8868\u9875\uFF0C\u542F\u7528\u9884\u89C8\u6309\u94AE");
      initListPage();
      return;
    }
    initGalleryPage(href);
  }
  init();
})();