A generalized multi-site reading framework. Provides an enhanced reading experience with infinite scroll, full-screen reader mode, and smart image loading for E-Hentai, 18comic, 4KHD, etc.
// ==UserScript==
// @name Hentai Reader
// @name:zh-CN 绅士阅读器
// @namespace http://tampermonkey.net/
// @version 3.0.0
// @author Viki
// @description A generalized multi-site reading framework. Provides an enhanced reading experience with infinite scroll, full-screen reader mode, and smart image loading for E-Hentai, 18comic, 4KHD, etc.
// @description:zh-CN 多站点通用阅读框架,为 E-Hentai、18comic、4KHD 等提供无限滚动、全屏阅读模式、智能预取与硬件加速解码
// @license MIT
// @icon data:image/png;base64,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
// @homepageURL https://github.com/Leovikii/Hentai-Reader
// @match https://e-hentai.org/g/*
// @match https://exhentai.org/g/*
// @match https://e-hentai.org/s/*
// @match https://exhentai.org/s/*
// @match *://*.4khd.com/*
// @match *://*.xxtt.ink/*
// @match *://*.uuss.uk/*
// @match *://*.ssuu.uk/*
// @match *://*.18comic.vip/*
// @match *://*.18comic.ink/*
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant unsafeWindow
// ==/UserScript==
(function () {
'use strict';
const d=new Set;const importCSS = async e=>{d.has(e)||(d.add(e),(t=>{typeof GM_addStyle=="function"?GM_addStyle(t):(document.head||document.documentElement).appendChild(document.createElement("style")).append(t);})(e));};
importCSS(" *,:before,:after{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / .5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: }::backdrop{--un-rotate:0;--un-rotate-x:0;--un-rotate-y:0;--un-rotate-z:0;--un-scale-x:1;--un-scale-y:1;--un-scale-z:1;--un-skew-x:0;--un-skew-y:0;--un-translate-x:0;--un-translate-y:0;--un-translate-z:0;--un-pan-x: ;--un-pan-y: ;--un-pinch-zoom: ;--un-scroll-snap-strictness:proximity;--un-ordinal: ;--un-slashed-zero: ;--un-numeric-figure: ;--un-numeric-spacing: ;--un-numeric-fraction: ;--un-border-spacing-x:0;--un-border-spacing-y:0;--un-ring-offset-shadow:0 0 rgb(0 0 0 / 0);--un-ring-shadow:0 0 rgb(0 0 0 / 0);--un-shadow-inset: ;--un-shadow:0 0 rgb(0 0 0 / 0);--un-ring-inset: ;--un-ring-offset-width:0px;--un-ring-offset-color:#fff;--un-ring-width:0px;--un-ring-color:rgb(147 197 253 / .5);--un-blur: ;--un-brightness: ;--un-contrast: ;--un-drop-shadow: ;--un-grayscale: ;--un-hue-rotate: ;--un-invert: ;--un-saturate: ;--un-sepia: ;--un-backdrop-blur: ;--un-backdrop-brightness: ;--un-backdrop-contrast: ;--un-backdrop-grayscale: ;--un-backdrop-hue-rotate: ;--un-backdrop-invert: ;--un-backdrop-opacity: ;--un-backdrop-saturate: ;--un-backdrop-sepia: } ");
const stylesCss = ':root{--accent: #F596AA;--accent-hover: #F7ABBE}html.scroll-mode{background-color:#111!important;color:#ccc!important;overflow-x:hidden}html.scroll-mode body{background-color:#111!important;color:#ccc!important;margin:0;overflow:visible!important}html.scroll-mode #gdt{display:flex;flex-direction:column;align-items:center;width:100%;max-width:1200px;margin:auto;padding-bottom:100px}.page-batch{width:100%;display:flex;flex-direction:column;align-items:center;margin-bottom:60px}.r-img{display:block;width:auto;max-width:100%;margin-bottom:20px;background:transparent;box-shadow:0 0 20px #00000080;position:relative}.r-ph{color:#555;margin-bottom:50px;text-align:center;min-height:400px;display:flex;align-items:center;justify-content:center;font-family:sans-serif;font-size:18px;border:1px dashed #333;width:100%;flex-direction:column;gap:10px}.r-ph.loading{color:#888;border-color:#555}.r-ph.error{color:#d44;border-color:#d44}.retry-btn{padding:8px 16px;background:#333;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:14px;margin-top:10px}.retry-btn:hover{background:#555}.sp-virtualized{content-visibility:hidden}.float-control{position:fixed;right:30px;bottom:30px;z-index:2147483647;display:flex;flex-direction:row;align-items:center;transition:opacity .3s,transform .3s;-webkit-user-select:none;user-select:none;opacity:.4}.float-control:hover{opacity:1}.float-control.hidden{opacity:0;pointer-events:none}.side-btn.top-btn{position:absolute;bottom:calc(100% + 10px);left:50%;transform:translate(-50%);opacity:.3;pointer-events:auto}.float-control:hover .side-btn.top-btn{opacity:.8}.side-btn.top-btn:hover{opacity:1!important;background:#555;transform:translate(-50%) scale(1.1)}.side-btn{width:36px;height:36px;background:#333;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .3s;opacity:0;pointer-events:none}.float-control:hover .side-btn{opacity:1;pointer-events:auto}.side-btn:hover{background:#555;transform:scale(1.1)}.side-btn svg{width:18px;height:18px;fill:#fff}.side-btn.active{background:var(--accent)}.side-btn.active:hover{background:var(--accent-hover)}.auto-play-btn.hidden{display:none}.circle-control{position:relative;width:50px;height:50px;background:#1a1a1a;border:2px solid #555;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:all .3s;box-shadow:0 4px 12px #00000080;margin:0 6px}.circle-control:hover{border-color:#888;box-shadow:0 6px 16px #000000b3;transform:scale(1.05)}.circle-control svg{width:24px;height:24px;fill:#fff}.settings-btn{cursor:pointer}.settings-panel{position:absolute;bottom:calc(100% + 10px);right:0;background:#1a1a1a;border:1px solid #555;border-radius:8px;padding:12px;min-width:180px;opacity:0;pointer-events:none;transition:all .3s;box-shadow:0 4px 12px #00000080;transform:translateY(5px)}.settings-panel.show{opacity:1;pointer-events:auto;transform:translateY(0)}.settings-item{display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;font-size:13px;color:#ccc}.settings-item:last-child{margin-bottom:0}.settings-label{margin-right:10px;white-space:nowrap}.toggle-switch{width:40px;height:20px;background:#333;border-radius:10px;position:relative;cursor:pointer;transition:background .3s}.toggle-switch.on{background:var(--accent)}.toggle-slider{width:16px;height:16px;background:#fff;border-radius:50%;position:absolute;top:2px;left:2px;transition:left .3s}.toggle-switch.on .toggle-slider{left:22px}.interval-input{width:60px;background:#333;border:1px solid #555;border-radius:4px;color:#fff;padding:4px 8px;font-size:12px;text-align:center}.interval-input:focus{outline:none;border-color:#888}.single-page-overlay{position:fixed;top:0;right:0;bottom:0;left:0;width:100vw;height:100vh;background-color:#000!important;z-index:2147483646;display:none;align-items:center;justify-content:center}.single-page-overlay.active{display:flex}.sp-image-container{width:100%;height:100%;display:flex;align-items:center;justify-content:center;position:relative}.sp-current-image{width:100%;height:100%;object-fit:contain;-webkit-user-select:none;user-select:none;transition:opacity .1s}.sp-current-image:not([src]),.sp-current-image[src=""]{opacity:0}.sp-close-btn{position:absolute;top:20px;right:20px;width:40px;height:40px;background:#333c;border-radius:50%;display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:24px;color:#fff;transition:all .3s;z-index:10}.sp-close-btn:hover{background:#555555e6;transform:scale(1.1)}.sp-sidebar-track{position:absolute;right:12px;top:10%;width:12px;height:80%;background:#2828284d;border-radius:6px;z-index:10;pointer-events:none;opacity:0;transition:opacity .3s,background .3s}.sp-sidebar-track.active{opacity:1;pointer-events:auto}.sp-sidebar-track:hover{background:#32323280}.sp-sidebar-thumb{position:absolute;left:0;width:100%;min-height:60px;background:#fff6;border-radius:6px;transition:background .3s;cursor:grab;-webkit-user-select:none;user-select:none}.sp-sidebar-thumb:hover{background:#fff9}.sp-sidebar-thumb:active{cursor:grabbing;background:#ffffffb3}.sp-sidebar-label{position:absolute;right:calc(100% + 16px);top:50%;transform:translateY(-50%);background:#1a1a1af2;padding:8px 14px;border-radius:8px;color:#fff;font-family:monospace;font-size:14px;white-space:nowrap;opacity:0;pointer-events:none;transition:opacity .3s;box-shadow:0 2px 8px #0000004d}.sp-sidebar-track:hover .sp-sidebar-label{opacity:1}.sp-thumb-panel{position:absolute;right:12px;top:10%;max-height:80%;height:fit-content;width:116px;background:#141414f2;border-radius:8px;border:1px solid rgba(255,255,255,.1);box-shadow:-4px 4px 16px #00000080;overflow:hidden;pointer-events:none;display:flex;flex-direction:column;z-index:11;opacity:0;transform:translate(20px);transition:opacity .3s,transform .3s cubic-bezier(.2,.8,.2,1);-webkit-user-select:none;user-select:none}.sp-thumb-panel.active{opacity:1;transform:translate(0);pointer-events:auto}.sp-thumb-viewport{flex:1;min-height:0;width:100%;overflow:hidden;position:relative}.sp-thumb-content{position:relative;width:100%}.sp-thumb-item{position:absolute;left:8px;width:100px;height:72px;border-radius:4px;overflow:hidden;cursor:pointer;border:2px solid transparent;transition:border-color .15s;box-sizing:border-box}.sp-thumb-item:hover{border-color:#fff6}.sp-thumb-item.sp-thumb-active{border-color:var(--accent)}.sp-thumb-img{width:100%;height:100%;object-fit:cover;pointer-events:none}.sp-thumb-label{position:absolute;bottom:0;right:0;padding:1px 5px;background:#00000080;color:#fff9;font-family:monospace;font-size:11px;border-radius:4px 0 0;pointer-events:none}.sp-thumb-ph{width:100%;height:100%;background:#222;display:flex;align-items:center;justify-content:center;color:#555;font-family:monospace;font-size:13px;-webkit-user-select:none;user-select:none}.sp-thumb-counter{padding:8px 0;text-align:center;color:#999;font-family:monospace;font-size:13px;border-top:1px solid rgba(255,255,255,.08);-webkit-user-select:none;user-select:none}.sp-hud-container{position:absolute;top:40px;left:50%;transform:translate(-50%);z-index:100;opacity:0;pointer-events:none;transition:opacity .3s ease,transform .3s ease}.sp-hud-container.show{opacity:1;pointer-events:auto;transform:translate(-50%)}.sp-hud-box{display:flex;align-items:center;gap:10px;background:#141414b3;border:1px solid rgba(255,255,255,.1);padding:10px 20px;border-radius:30px;box-shadow:0 4px 16px #00000080;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);transition:all .2s}.sp-hud-box.hud-error{background:#b41e1eb3;border-color:#ff64644d}.sp-hud-box.hud-error:hover{background:#dc2828cc}.sp-hud-text{font-size:14px;color:#f3f4f6;font-family:sans-serif;font-weight:500;letter-spacing:.5px;white-space:nowrap;-webkit-user-select:none;user-select:none}.sp-hud-page{font-size:13px;color:#ffffff80;font-family:monospace;letter-spacing:1px;margin-left:6px;padding-left:10px;border-left:1px solid rgba(255,255,255,.2);-webkit-user-select:none;user-select:none}';
importCSS(stylesCss);
var _GM_getValue = (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)();
var _GM_registerMenuCommand = (() => typeof GM_registerMenuCommand != "undefined" ? GM_registerMenuCommand : void 0)();
var _GM_setValue = (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)();
const CFG = {
nextPage: "3000px 0px",
maxRetries: 3,
retryDelay: 1e3,
maxConcurrent: 3,
requestSpacing: 100,
imageLoadTimeout: 8e3
};
function loadSettings(adapterName) {
const prefix = adapterName ? `${adapterName}_` : "";
const is4KHD = adapterName === "4KHD";
const globalScrollMode = _GM_getValue("scrollMode", true);
return {
scrollMode: _GM_getValue(`${prefix}scrollMode`, is4KHD ? true : globalScrollMode),
showControl: _GM_getValue("showControl", true),
autoEnterSinglePage: _GM_getValue("autoEnterSinglePage", false),
autoPlayInterval: _GM_getValue("autoPlayInterval", 3e3)
};
}
class Store {
constructor() {
this.listeners = new Map();
this.activeAdapter = null;
this.currPage = 1;
this.totalPage = 1;
this.perPage = 20;
this.imageOffset = 0;
this.nextUrl = null;
this.prevUrl = null;
this.isFetching = false;
this.nextPagePrefetched = false;
this.currentImageIndex = 0;
this.allImages = [];
this.autoPlayTimer = null;
this.autoPlay = false;
this._settings = loadSettings();
}
get settings() {
return this._settings;
}
reloadSettings() {
var _a;
this._settings = loadSettings((_a = this.activeAdapter) == null ? void 0 : _a.name);
}
updateSetting(key, value) {
this._settings[key] = value;
if (key === "scrollMode" && this.activeAdapter) {
_GM_setValue(`${this.activeAdapter.name}_scrollMode`, value);
} else {
_GM_setValue(key, value);
}
this.emit("settingsChanged");
}
on(event, listener) {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event).add(listener);
}
emit(event) {
var _a;
(_a = this.listeners.get(event)) == null ? void 0 : _a.forEach((fn) => fn());
}
}
const store = new Store();
const q = (selector, root = document) => root.querySelector(selector);
const qa = (selector, root = document) => root.querySelectorAll(selector);
function isImageReady(img) {
if (img.dataset.realSrc) return true;
return !!(img && img.src && !img.src.includes("data:") && img.complete && img.naturalWidth > 0);
}
let toastContainer = null;
function showToast(msg, duration = 3e3) {
if (!toastContainer) {
toastContainer = document.createElement("div");
toastContainer.style.position = "fixed";
toastContainer.style.top = "20px";
toastContainer.style.left = "50%";
toastContainer.style.transform = "translateX(-50%)";
toastContainer.style.zIndex = "2147483647";
toastContainer.style.pointerEvents = "none";
toastContainer.style.display = "flex";
toastContainer.style.flexDirection = "column";
toastContainer.style.gap = "8px";
document.body.appendChild(toastContainer);
}
const toast = document.createElement("div");
toast.style.background = "rgba(20, 20, 20, 0.9)";
toast.style.color = "#fff";
toast.style.padding = "8px 16px";
toast.style.borderRadius = "20px";
toast.style.fontSize = "14px";
toast.style.boxShadow = "0 4px 12px rgba(0,0,0,0.5)";
toast.style.border = "1px solid rgba(255,255,255,0.1)";
toast.style.transition = "opacity 0.3s";
toast.textContent = msg;
toastContainer.appendChild(toast);
setTimeout(() => {
toast.style.opacity = "0";
setTimeout(() => {
toast.remove();
}, 300);
}, duration);
}
class RequestQueue {
constructor() {
this.queue = [];
this.running = 0;
this.pausedUntil = 0;
}
enqueue(task, priorityFn) {
return new Promise((resolve, reject) => {
this.queue.push({ execute: task, resolve, reject, priorityFn });
this.run();
});
}
pauseGlobally(durationMs) {
const until = Date.now() + durationMs;
if (until > this.pausedUntil) {
this.pausedUntil = until;
}
}
run() {
if (Date.now() < this.pausedUntil) {
setTimeout(() => this.run(), this.pausedUntil - Date.now() + 10);
return;
}
if (this.queue.length > 1) {
this.queue.sort((a, b) => {
const pa = a.priorityFn ? a.priorityFn() : 0;
const pb = b.priorityFn ? b.priorityFn() : 0;
return pb - pa;
});
}
while (this.running < CFG.maxConcurrent && this.queue.length > 0) {
const task = this.queue.shift();
this.running++;
const next = () => {
this.running--;
setTimeout(() => this.run(), CFG.requestSpacing);
};
task.execute().then((value) => {
task.resolve(value);
next();
}).catch((reason) => {
task.reject(reason);
next();
});
}
}
}
const requestQueue = new RequestQueue();
const parser$1 = new DOMParser();
function getNextUrl$1(doc) {
const ptt = q(".ptt", doc);
if (!ptt) return null;
const nextBtn = Array.from(qa("td a", ptt)).find((a) => (a.textContent ?? "").includes(">"));
return nextBtn ? nextBtn.href : null;
}
function getPrevUrl$1(doc) {
const ptt = q(".ptt", doc);
if (!ptt) return null;
const prevBtn = Array.from(qa("td a", ptt)).find((a) => (a.textContent ?? "").includes("<"));
return prevBtn ? prevBtn.href : null;
}
function extractLinks(doc) {
return Array.from(qa("#gdt a", doc)).map((a) => {
const url = a.href;
let thumb;
const divWithBg = a.closest('div[style*="background"]');
if (divWithBg) {
const style = divWithBg.getAttribute("style") || "";
const match = style.match(/url\(['"]?([^)'"]+)['"]?\)/);
if (match) thumb = match[1];
} else {
const img = a.querySelector("img");
if (img && img.src && !img.src.endsWith("x.gif")) {
thumb = img.src;
}
}
return { url, thumb };
});
}
const EHentaiAdapter = {
name: "E-Hentai/ExHentai",
match(url) {
return /https?:\/\/(e-|ex)hentai\.org\/(g|s)\//.test(url);
},
async init(doc) {
const initLinks = extractLinks(doc);
let totalPage = 1;
const gpc = q(".gpc", doc);
if (gpc) {
const txt = gpc.textContent ?? "";
const m = txt.match(/of\s+([\d,]+)\s+images/);
if (m && m[1]) {
const totalImgs = parseInt(m[1].replace(/,/g, ""));
const perPage = initLinks.length || 20;
totalPage = Math.ceil(totalImgs / perPage);
}
} else {
const allLinks = Array.from(qa(".ptt td a", doc));
const lastA = allLinks.pop();
if (lastA) {
const t = parseInt(lastA.textContent ?? "");
if (!isNaN(t)) totalPage = t;
}
}
if (gpc) {
const txt = gpc.textContent ?? "";
const m = txt.match(/Showing\s+([\d,]+)\s*-\s*([\d,]+)\s+of\s+([\d,]+)/);
if (m) {
store.imageOffset = parseInt(m[1].replace(/,/g, "")) - 1;
}
}
return {
links: initLinks,
nextUrl: getNextUrl$1(doc),
prevUrl: getPrevUrl$1(doc),
totalPage
};
},
async resolveImage(url, nlToken) {
const fetchUrl = nlToken ? `${url}${url.includes("?") ? "&" : "?"}nl=${nlToken}` : url;
let retries = 0;
while (retries <= CFG.maxRetries) {
try {
const response = await fetch(fetchUrl);
if (response.status === 429 || response.status === 503) {
requestQueue.pauseGlobally(5e3);
throw { rateLimited: true };
}
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const html = await response.text();
const doc = parser$1.parseFromString(html, "text/html");
const imgEl = q("#img", doc);
const imgSrc = imgEl == null ? void 0 : imgEl.src;
if (!imgSrc) throw new Error("Image not found");
const onerror = imgEl.getAttribute("onerror") || "";
const m = onerror.match(/nl\(['"]([^'"]+)['"]\)/);
const nextNlToken = m ? m[1] : null;
return { src: imgSrc, nl: nextNlToken ?? void 0 };
} catch (err) {
if (retries < CFG.maxRetries) {
const isRateLimited = err && typeof err === "object" && "rateLimited" in err;
const delay = isRateLimited ? 5e3 : CFG.retryDelay * Math.pow(2, retries);
await new Promise((resolve) => setTimeout(resolve, delay));
retries++;
} else {
return null;
}
}
}
return null;
},
async fetchPage(url) {
const response = await fetch(url);
if (!response.ok) throw new Error("Failed to fetch page");
const html = await response.text();
const doc = parser$1.parseFromString(html, "text/html");
const links = extractLinks(doc);
return {
links,
nextUrl: getNextUrl$1(doc),
prevUrl: getPrevUrl$1(doc)
};
},
getContainer() {
return document.querySelector("#gdt") || document.querySelector(".gm");
},
hideOriginalElements() {
const HIDDEN_SELECTORS = [
".c1",
".c2",
".c3",
".c4",
".c5",
".c6",
".c7",
".c8",
".ptt",
".ptb",
".gdtl",
".gdtm",
"#gdo",
"#cdiv",
"table.itg"
];
document.querySelectorAll(HIDDEN_SELECTORS.join(",")).forEach((el) => {
el.style.display = "none";
});
}
};
const parser = new DOMParser();
function extract4KHDImages(doc) {
const images = Array.from(qa("figure.wp-block-image img, #basicExample img, .entry-content p img", doc));
return images.map((img) => {
let src = img.src;
let thumb = src;
src = src.replace(/i\d\.wp\.com\//, "");
src = src.replace("pic.4khd.com", "img.4khd.com");
src = src.replace(/\?.+$/, "");
src = src.replace(/\/w\d+-rw\//, "/w2500-h2500-rw/");
return { url: src, thumb };
}).filter((link) => link.url && !link.url.includes("avatar"));
}
function get4KHDNextUrl(doc) {
const pageBox = doc.querySelector(".page-link-box, .pagination, .nav-links, .nav-previous");
if (pageBox) {
const current = pageBox.querySelector(".current, .active") || Array.from(pageBox.querySelectorAll("span")).find((s) => !s.querySelector("a"));
if (current) {
const currentPageNum = parseInt(current.textContent || "1", 10);
if (!isNaN(currentPageNum)) {
const nextBtn = Array.from(pageBox.querySelectorAll("a")).find((a) => parseInt(a.textContent || "0", 10) === currentPageNum + 1);
if (nextBtn && nextBtn.href && nextBtn.href !== window.location.href) {
return nextBtn.href;
}
}
}
const nextBtnFallback = pageBox.querySelector("a.next") || doc.querySelector("a.next.page-numbers");
if (nextBtnFallback && nextBtnFallback.href) {
const url = nextBtnFallback.href;
if (url !== window.location.href) return url;
}
}
return null;
}
function get4KHDPrevUrl(doc) {
const pageBox = doc.querySelector(".page-link-box, .pagination, .nav-links, .nav-previous");
if (pageBox) {
const current = pageBox.querySelector(".current, .active") || Array.from(pageBox.querySelectorAll("span")).find((s) => !s.querySelector("a"));
if (current) {
const currentPageNum = parseInt(current.textContent || "1", 10);
if (!isNaN(currentPageNum) && currentPageNum > 1) {
const prevBtn = Array.from(pageBox.querySelectorAll("a")).find((a) => parseInt(a.textContent || "0", 10) === currentPageNum - 1);
if (prevBtn && prevBtn.href && prevBtn.href !== window.location.href) {
return prevBtn.href;
}
}
}
const prevBtnFallback = pageBox.querySelector("a.prev") || doc.querySelector("a.prev.page-numbers");
if (prevBtnFallback && prevBtnFallback.href) {
const url = prevBtnFallback.href;
if (url !== window.location.href) return url;
}
}
return null;
}
const FourKHDAdapter = {
name: "4KHD",
match(url) {
return url.includes("4khd.com") || url.includes("xxtt.ink") || url.includes("uuss.uk") || url.includes("ssuu.uk");
},
async init(doc) {
const initLinks = extract4KHDImages(doc);
const totalPage = 1;
const pageBox = doc.querySelector(".page-link-box, .pagination, .nav-links, .nav-previous");
let currentPageNum = 1;
if (pageBox) {
const current = pageBox.querySelector(".current, .active") || Array.from(pageBox.querySelectorAll("span")).find((s) => !s.querySelector("a"));
if (current) {
currentPageNum = parseInt(current.textContent || "1", 10);
if (isNaN(currentPageNum)) currentPageNum = 1;
}
}
if (currentPageNum > 1) {
const perPage = initLinks.length > 0 ? initLinks.length : 20;
store.imageOffset = (currentPageNum - 1) * perPage;
}
return {
links: initLinks,
nextUrl: get4KHDNextUrl(doc),
prevUrl: get4KHDPrevUrl(doc),
totalPage
};
},
async resolveImage(url) {
return { src: url };
},
async fetchPage(url) {
const response = await fetch(url);
if (!response.ok) throw new Error("Failed to fetch page");
const html = await response.text();
const doc = parser.parseFromString(html, "text/html");
const links = extract4KHDImages(doc);
return {
links,
nextUrl: get4KHDNextUrl(doc),
prevUrl: get4KHDPrevUrl(doc)
};
},
getContainer() {
const entryContent = document.querySelector(".entry-content, .wp-block-post-content");
if (entryContent) return entryContent;
const basicExample = document.querySelector("#basicExample");
if (basicExample && basicExample.parentElement) return basicExample.parentElement;
return document.querySelector(".post-content");
},
hideOriginalElements() {
const HIDDEN_SELECTORS = [
".centbtd",
".popup",
".wp-container-13",
".popup-iframe",
"#basicExample",
".wp-block-image",
".page-link-box"
];
document.querySelectorAll(HIDDEN_SELECTORS.join(",")).forEach((el) => {
el.style.display = "none";
});
}
};
class Mutex {
constructor(maxConcurrent) {
this.maxConcurrent = maxConcurrent;
this.queue = [];
this.activeCount = 0;
}
async lock() {
if (this.activeCount < this.maxConcurrent) {
this.activeCount++;
return;
}
return new Promise((resolve) => this.queue.push(resolve));
}
unlock() {
if (this.queue.length > 0) {
const next = this.queue.shift();
next == null ? void 0 : next();
} else {
this.activeCount--;
}
}
}
const decodeMutex = new Mutex(3);
const Comic18Adapter = {
name: "18comic",
match: (url) => {
return url.includes("18comic.vip") || url.includes("18comic.ink");
},
init: async (doc) => {
const aidMatch = doc.documentElement.innerHTML.match(/aid\s*=\s*['"]?(\d+)['"]?/);
const scrambleMatch = doc.documentElement.innerHTML.match(/scramble_id\s*=\s*['"]?(\d+)['"]?/);
const aid = aidMatch ? aidMatch[1] : unsafeWindow.aid ? String(unsafeWindow.aid) : "";
const scrambleId = scrambleMatch ? scrambleMatch[1] : unsafeWindow.scramble_id ? String(unsafeWindow.scramble_id) : "";
const links = [];
const imgs = doc.querySelectorAll(".scramble-page img[id], .owl-item .center img[id]");
imgs.forEach((img) => {
const url = img.getAttribute("data-original") || img.getAttribute("data-src") || img.src;
if (url) {
const urlObj = new URL(url, window.location.href);
if (aid) urlObj.searchParams.set("18aid", aid);
if (scrambleId) urlObj.searchParams.set("18scid", scrambleId);
const viewerUrl = urlObj.toString();
links.push({ url: viewerUrl });
try {
img.getBoundingClientRect = () => ({
top: 999999,
left: 0,
right: 0,
bottom: 999999,
width: 0,
height: 0,
x: 0,
y: 0,
toJSON: () => {
}
});
} catch (e) {
}
}
});
return {
links,
nextUrl: getNextUrl(doc),
prevUrl: getPrevUrl(doc)
};
},
fetchPage: async (url) => {
const res = await fetch(url);
const html = await res.text();
const doc = new DOMParser().parseFromString(html, "text/html");
const aidMatch = html.match(/aid\s*=\s*['"]?(\d+)['"]?/);
const scrambleMatch = html.match(/scramble_id\s*=\s*['"]?(\d+)['"]?/);
const aid = aidMatch ? aidMatch[1] : "";
const scrambleId = scrambleMatch ? scrambleMatch[1] : "";
const links = [];
const imgs = doc.querySelectorAll(".scramble-page img[id], .owl-item .center img[id]");
imgs.forEach((img) => {
const imgUrl = img.getAttribute("data-original") || img.getAttribute("data-src") || img.src;
if (imgUrl) {
const urlObj = new URL(imgUrl, window.location.href);
if (aid) urlObj.searchParams.set("18aid", aid);
if (scrambleId) urlObj.searchParams.set("18scid", scrambleId);
links.push({ url: urlObj.toString() });
}
});
return {
links,
nextUrl: getNextUrl(doc),
prevUrl: getPrevUrl(doc)
};
},
resolveImage: async (urlStr) => {
try {
const urlObj = new URL(urlStr);
const aid = urlObj.searchParams.get("18aid");
const scrambleId = urlObj.searchParams.get("18scid");
urlObj.searchParams.delete("18aid");
urlObj.searchParams.delete("18scid");
const realUrl = urlObj.toString();
if (realUrl.includes(".gif") || !aid || !scrambleId || Number(aid) < Number(scrambleId)) {
return { src: realUrl };
}
const res = await fetch(realUrl);
if (!res.ok) throw new Error("Failed to fetch image");
const blob = await res.blob();
const fileName = urlObj.pathname.split("/").pop() || "";
const id = fileName.split(".")[0];
if (!id) return { src: realUrl };
await decodeMutex.lock();
try {
const bitmap = await createImageBitmap(blob);
const imgWidth = bitmap.width;
const imgHeight = bitmap.height;
if (!unsafeWindow.get_num) {
bitmap.close();
return { src: realUrl };
}
const num = unsafeWindow.get_num(btoa(aid), btoa(id));
if (!num || num <= 1) {
bitmap.close();
return { src: realUrl };
}
const canvas = new OffscreenCanvas(imgWidth, imgHeight);
const ctx = canvas.getContext("2d");
if (!ctx) {
bitmap.close();
return { src: realUrl };
}
ctx.fillStyle = "#ffffff";
ctx.fillRect(0, 0, imgWidth, imgHeight);
const cropHeight = Number(imgHeight % num);
const sHeight = Math.floor(imgHeight / num);
let sy = imgHeight - cropHeight - sHeight;
let dy = cropHeight + sHeight;
ctx.drawImage(bitmap, 0, sy, imgWidth, cropHeight + sHeight, 0, 0, imgWidth, cropHeight + sHeight);
for (let i = 1; i < num; ++i) {
sy -= sHeight;
ctx.drawImage(bitmap, 0, sy, imgWidth, sHeight, 0, dy, imgWidth, sHeight);
dy += sHeight;
}
bitmap.close();
const finalBlob = await canvas.convertToBlob({ type: "image/jpeg", quality: 0.85 });
const finalUrl = URL.createObjectURL(finalBlob);
return { src: finalUrl };
} finally {
decodeMutex.unlock();
}
} catch (err) {
const cleanUrl = new URL(urlStr);
cleanUrl.searchParams.delete("18aid");
cleanUrl.searchParams.delete("18scid");
return { src: cleanUrl.toString() };
}
},
getContainer: () => {
return document.querySelector(".scramble-page") || document.body;
},
hideOriginalElements: () => {
}
};
function getNextUrl(doc) {
var _a;
const activeLi = doc.querySelector(".pagination li.active");
if (activeLi) {
const nextA = (_a = activeLi.nextElementSibling) == null ? void 0 : _a.querySelector("a:not(.prevnext)");
if (nextA) return nextA.href;
}
const nextChapter = Array.from(doc.querySelectorAll('.menu-bolock-ul a[href^="/photo/"]')).find((a) => {
var _a2;
return (_a2 = a.textContent) == null ? void 0 : _a2.includes("下一");
});
if (nextChapter) return nextChapter.href;
return null;
}
function getPrevUrl(doc) {
var _a;
const activeLi = doc.querySelector(".pagination li.active");
if (activeLi) {
const prevA = (_a = activeLi.previousElementSibling) == null ? void 0 : _a.querySelector("a:not(.prevnext)");
if (prevA) return prevA.href;
}
const prevChapter = Array.from(doc.querySelectorAll('.menu-bolock-ul a[href^="/photo/"]')).find((a) => {
var _a2;
return (_a2 = a.textContent) == null ? void 0 : _a2.includes("上一");
});
if (prevChapter) return prevChapter.href;
return null;
}
const adapters = [EHentaiAdapter, FourKHDAdapter, Comic18Adapter];
const SiteManager = {
register(adapter) {
adapters.push(adapter);
},
getAdapter(url, doc = document) {
return adapters.find((a) => a.match(url, doc)) || null;
}
};
function setErrorState(placeholder, pIndex, index) {
placeholder.className = "r-ph sp-placeholder error";
placeholder.innerHTML = `
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; transform: translateY(-20px);">
<div style="display: flex; align-items: center; gap: 10px; background: rgba(200, 40, 40, 0.8); border: 1px solid rgba(255, 255, 255, 0.2); padding: 10px 20px; border-radius: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); backdrop-filter: blur(8px); margin-bottom: 16px;">
<svg style="color: #fff; width: 20px; height: 20px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round">
<circle cx="12" cy="12" r="10"></circle>
<line x1="12" y1="8" x2="12" y2="12"></line>
<line x1="12" y1="16" x2="12.01" y2="16"></line>
</svg>
<div style="font-size: 15px; color: #fff; font-weight: 500; letter-spacing: 0.5px;">Load Failed</div>
</div>
<div style="font-size: 14px; color: rgba(255, 255, 255, 0.5); font-family: monospace; letter-spacing: 1px;">P${pIndex}-${index + 1}</div>
</div>
`;
}
let virtualizationObserver = null;
let lazyLoadObserver = null;
function initVirtualization() {
if (virtualizationObserver) return;
virtualizationObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
const img = entry.target;
if (entry.isIntersecting) {
img.classList.remove("sp-virtualized");
} else {
img.classList.add("sp-virtualized");
}
});
}, { rootMargin: "3000px" });
}
function loadPlaceholderImage(placeholder) {
const url = placeholder.dataset.url;
const pIndex = parseInt(placeholder.dataset.pIndex);
const index = parseInt(placeholder.dataset.index);
const thumb = placeholder.dataset.thumb;
const adapter = store.activeAdapter;
if (!adapter) return;
adapter.resolveImage(url).then((res) => {
var _a;
if (res) {
let showError = function() {
if (placeholder.parentNode) {
setErrorState(placeholder, pIndex, index);
if (img.parentNode) {
img.parentNode.replaceChild(placeholder, img);
}
virtualizationObserver == null ? void 0 : virtualizationObserver.unobserve(img);
}
};
const img = document.createElement("img");
img.className = "r-img";
img.dataset.viewerUrl = url;
img.dataset.realSrc = res.src;
if (thumb) img.dataset.thumbSrc = thumb;
if (res.nl) img.dataset.nl = res.nl;
let currentNlToken = res.nl;
let autoRetries = 0;
const MAX_AUTO_RETRIES = 3;
img.onerror = () => {
if (currentNlToken && autoRetries < MAX_AUTO_RETRIES) {
autoRetries++;
showToast(`P${pIndex}-${index + 1}: Auto requesting new node... (${autoRetries}/${MAX_AUTO_RETRIES})`, 3e3);
if (placeholder.parentNode) {
placeholder.className = "r-ph sp-placeholder loading";
placeholder.innerHTML = `
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; transform: translateY(-20px);">
<div style="display: flex; align-items: center; gap: 10px; background: rgba(20, 20, 20, 0.8); border: 1px solid rgba(255, 255, 255, 0.1); padding: 10px 20px; border-radius: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); backdrop-filter: blur(8px); margin-bottom: 16px;">
<style>@keyframes sp-spin { 100% { transform: rotate(360deg); } }</style>
<svg style="color: #F596AA; width: 20px; height: 20px; animation: sp-spin 1s linear infinite;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round">
<path d="M21 12a9 9 0 1 1-6.219-8.56"></path>
</svg>
<div style="font-size: 15px; color: #f3f4f6; font-weight: 500; letter-spacing: 0.5px;">Auto Retrying...</div>
</div>
<div style="font-size: 14px; color: rgba(255, 255, 255, 0.5); font-family: monospace; letter-spacing: 1px;">P${pIndex}-${index + 1}</div>
</div>
`;
if (img.parentNode) {
img.parentNode.replaceChild(placeholder, img);
}
}
adapter.resolveImage(url, currentNlToken).then((newRes) => {
if (newRes) {
img.src = newRes.src;
img.dataset.realSrc = newRes.src;
currentNlToken = newRes.nl;
if (placeholder.parentNode) {
placeholder.parentNode.replaceChild(img, placeholder);
}
} else {
showError();
}
}).catch(showError);
} else {
showError();
}
};
img.onload = () => {
if (!img.dataset.locked && img.naturalWidth > 0) {
img.style.aspectRatio = `${img.naturalWidth} / ${img.naturalHeight}`;
img.style.width = "100%";
img.style.maxWidth = `${img.naturalWidth}px`;
img.style.height = "auto";
img.dataset.locked = "true";
}
};
img.src = res.src;
(_a = placeholder.parentNode) == null ? void 0 : _a.replaceChild(img, placeholder);
virtualizationObserver == null ? void 0 : virtualizationObserver.observe(img);
} else {
setErrorState(placeholder, pIndex, index);
}
}).catch(() => {
setErrorState(placeholder, pIndex, index);
});
}
function initLazyLoad() {
if (lazyLoadObserver) return;
lazyLoadObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const placeholder = entry.target;
if (placeholder.dataset.lazyLoaded) return;
placeholder.dataset.lazyLoaded = "true";
lazyLoadObserver == null ? void 0 : lazyLoadObserver.unobserve(placeholder);
loadPlaceholderImage(placeholder);
}
});
}, { rootMargin: "4000px 0px 4000px 0px" });
}
function processBatch(links, pIndex, container, prepend = false, pageUrl) {
const batchDiv = document.createElement("div");
batchDiv.className = "page-batch";
if (pageUrl) {
batchDiv.dataset.pageUrl = pageUrl;
}
const fragment = document.createDocumentFragment();
initVirtualization();
initLazyLoad();
let targetContainer = container;
if (!targetContainer) {
targetContainer = document.querySelector("#gdt-hidden") || document.querySelector(".scroll-mode #gdt, .scroll-mode .gm, .scroll-mode .entry-content, .scroll-mode .wp-block-post-content, .scroll-mode .post-content") || document.body;
}
links.forEach((link, index) => {
const url = link.url;
const placeholder = document.createElement("div");
placeholder.className = "r-ph sp-placeholder loading";
placeholder.dataset.url = url;
placeholder.dataset.pIndex = String(pIndex);
placeholder.dataset.index = String(index);
if (link.thumb) placeholder.dataset.thumb = link.thumb;
placeholder.innerHTML = `
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; transform: translateY(-20px);">
<div style="display: flex; align-items: center; gap: 10px; background: rgba(20, 20, 20, 0.8); border: 1px solid rgba(255, 255, 255, 0.1); padding: 10px 20px; border-radius: 30px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); backdrop-filter: blur(8px); margin-bottom: 16px;">
<style>@keyframes sp-spin { 100% { transform: rotate(360deg); } }</style>
<svg style="color: #F596AA; width: 20px; height: 20px; animation: sp-spin 1s linear infinite;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round">
<path d="M21 12a9 9 0 1 1-6.219-8.56"></path>
</svg>
<div style="font-size: 15px; color: #f3f4f6; font-weight: 500; letter-spacing: 0.5px;">Loading...</div>
</div>
<div style="font-size: 14px; color: rgba(255, 255, 255, 0.5); font-family: monospace; letter-spacing: 1px;">P${pIndex}-${index + 1}</div>
</div>
`;
fragment.appendChild(placeholder);
if (store.settings.scrollMode) {
lazyLoadObserver == null ? void 0 : lazyLoadObserver.observe(placeholder);
} else {
placeholder.dataset.lazyLoaded = "true";
loadPlaceholderImage(placeholder);
}
});
batchDiv.appendChild(fragment);
if (prepend && targetContainer.firstChild) {
targetContainer.insertBefore(batchDiv, targetContainer.firstChild);
} else {
targetContainer.appendChild(batchDiv);
}
}
function setupAutoScroll() {
const scrollSent = document.createElement("div");
document.body.appendChild(scrollSent);
const pageObs = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && store.nextUrl && !store.isFetching) {
store.isFetching = true;
store.activeAdapter.fetchPage(store.nextUrl).then(({ links, nextUrl: nUrl }) => {
store.currPage++;
processBatch(links, store.currPage, document.querySelector(".scroll-mode #gdt, .scroll-mode .gm, .scroll-mode .entry-content, .scroll-mode .wp-block-post-content, .scroll-mode .post-content") || document.body);
store.nextUrl = nUrl;
store.isFetching = false;
store.nextPagePrefetched = false;
if (!store.nextUrl) pageObs.disconnect();
}).catch(() => {
store.isFetching = false;
});
}
}, { rootMargin: CFG.nextPage });
pageObs.observe(scrollSent);
}
const ITEM_HEIGHT = 80;
const VISIBLE_COUNT = 12;
const BUFFER = 3;
function createSidebar(onIndexChange, onScrollToBottom, onScrollToTop) {
const progressTrack = document.createElement("div");
progressTrack.className = "sp-sidebar-track";
const progressThumb = document.createElement("div");
progressThumb.className = "sp-sidebar-thumb";
const progressLabel = document.createElement("div");
progressLabel.className = "sp-sidebar-label";
progressTrack.appendChild(progressThumb);
progressTrack.appendChild(progressLabel);
const thumbPanel = document.createElement("div");
thumbPanel.className = "sp-thumb-panel";
const viewport = document.createElement("div");
viewport.className = "sp-thumb-viewport";
const content = document.createElement("div");
content.className = "sp-thumb-content";
const counter = document.createElement("div");
counter.className = "sp-thumb-counter";
viewport.appendChild(content);
thumbPanel.appendChild(viewport);
thumbPanel.appendChild(counter);
let scrollOffset = 0;
let lastCenteredIndex = -1;
let clickedFromPanel = false;
const itemPool = [];
const activeItems = new Map();
let cachedTrackHeight = 0;
let progressWakeTimer = null;
let isPanelActive = false;
function wakeUpProgressBar() {
if (isPanelActive) return;
progressTrack.classList.add("active");
if (progressWakeTimer) clearTimeout(progressWakeTimer);
progressWakeTimer = setTimeout(() => {
progressTrack.classList.remove("active");
}, 1500);
}
function closePanel() {
if (!isPanelActive) return;
isPanelActive = false;
thumbPanel.classList.remove("active");
}
function openPanel() {
if (isPanelActive) return;
isPanelActive = true;
thumbPanel.classList.add("active");
progressTrack.classList.remove("active");
if (progressWakeTimer) clearTimeout(progressWakeTimer);
}
document.addEventListener("mousemove", (e) => {
if (!document.querySelector(".single-page-overlay.active")) return;
if (isDragging) return;
if (e.clientX < 0 || e.clientY < 0 || e.clientY >= window.innerHeight - 1) {
closePanel();
return;
}
const dx = window.innerWidth - e.clientX;
if (dx <= 140) {
openPanel();
} else {
closePanel();
}
});
document.addEventListener("mouseout", (e) => {
if (!e.relatedTarget) closePanel();
});
document.documentElement.addEventListener("mouseleave", closePanel);
function refreshTrackHeight() {
cachedTrackHeight = progressTrack.offsetHeight;
}
window.addEventListener("resize", refreshTrackHeight, { passive: true });
function clamp(val, min, max) {
return Math.max(min, Math.min(max, val));
}
function vpHeight() {
return viewport.offsetHeight || Math.min(VISIBLE_COUNT * ITEM_HEIGHT, store.allImages.length * ITEM_HEIGHT);
}
function maxOffset() {
return Math.max(0, store.allImages.length * ITEM_HEIGHT - vpHeight());
}
function acquireItem() {
return itemPool.pop() || (() => {
const el = document.createElement("div");
el.className = "sp-thumb-item";
return el;
})();
}
function releaseItem(el) {
el.remove();
itemPool.push(el);
}
function renderItemContent(el, index) {
el.dataset.index = String(index);
el.classList.toggle("sp-thumb-active", index === store.currentImageIndex);
const img = store.allImages[index];
if (img && img.src) {
let thumbCanvas = el.querySelector("canvas.sp-thumb-img");
if (!thumbCanvas) {
el.innerHTML = "";
thumbCanvas = document.createElement("canvas");
thumbCanvas.className = "sp-thumb-img";
el.appendChild(thumbCanvas);
const label2 = document.createElement("span");
label2.className = "sp-thumb-label";
el.appendChild(label2);
}
const realSrc = img.dataset.thumbSrc || img.dataset.realSrc || img.src;
if (thumbCanvas.dataset.src !== realSrc) {
thumbCanvas.dataset.src = realSrc;
const tempImg = new Image();
tempImg.onload = () => {
if (thumbCanvas.dataset.src === realSrc) {
thumbCanvas.width = tempImg.naturalWidth;
thumbCanvas.height = tempImg.naturalHeight;
const ctx = thumbCanvas.getContext("2d");
if (ctx) {
ctx.drawImage(tempImg, 0, 0);
}
}
};
tempImg.src = realSrc;
}
const label = el.querySelector(".sp-thumb-label");
if (label) label.textContent = String(store.imageOffset + index + 1);
} else {
if (!el.querySelector(".sp-thumb-ph")) {
el.innerHTML = "";
const ph = document.createElement("div");
ph.className = "sp-thumb-ph";
ph.textContent = String(store.imageOffset + index + 1);
el.appendChild(ph);
}
}
}
function renderVisibleItems() {
const total = store.allImages.length;
if (total === 0) return;
const vp = vpHeight();
content.style.height = `${total * ITEM_HEIGHT}px`;
scrollOffset = clamp(scrollOffset, 0, maxOffset());
const startIdx = Math.max(0, Math.floor(scrollOffset / ITEM_HEIGHT) - BUFFER);
const endIdx = Math.min(total - 1, Math.ceil((scrollOffset + vp) / ITEM_HEIGHT) + BUFFER);
for (const [idx, el] of activeItems) {
if (idx < startIdx || idx > endIdx) {
releaseItem(el);
activeItems.delete(idx);
}
}
for (let i = startIdx; i <= endIdx; i++) {
let el = activeItems.get(i);
if (!el) {
el = acquireItem();
activeItems.set(i, el);
content.appendChild(el);
}
el.style.transform = `translateY(${i * ITEM_HEIGHT}px)`;
renderItemContent(el, i);
}
content.style.transform = `translateY(${-scrollOffset}px)`;
}
function centerOnCurrent() {
const vp = vpHeight();
const target = store.currentImageIndex * ITEM_HEIGHT - vp / 2 + ITEM_HEIGHT / 2;
scrollOffset = clamp(target, 0, maxOffset());
}
function ensureVisible() {
const vp = vpHeight();
const itemTop = store.currentImageIndex * ITEM_HEIGHT;
const itemBottom = itemTop + ITEM_HEIGHT;
if (itemTop < scrollOffset) {
scrollOffset = itemTop;
} else if (itemBottom > scrollOffset + vp) {
scrollOffset = itemBottom - vp;
}
scrollOffset = clamp(scrollOffset, 0, maxOffset());
}
function update() {
if (store.allImages.length === 0) return;
if (store.currentImageIndex !== lastCenteredIndex) {
if (clickedFromPanel) {
ensureVisible();
clickedFromPanel = false;
} else {
centerOnCurrent();
}
lastCenteredIndex = store.currentImageIndex;
}
renderVisibleItems();
const displayLabel = `${store.imageOffset + store.currentImageIndex + 1} / ${store.imageOffset + store.allImages.length}`;
counter.textContent = displayLabel;
if (!cachedTrackHeight) refreshTrackHeight();
const trackHeight = cachedTrackHeight;
let thumbHeight;
if (store.allImages.length <= 10) {
thumbHeight = 60;
} else if (store.allImages.length <= 50) {
thumbHeight = Math.max(60, trackHeight * (10 / store.allImages.length));
} else {
thumbHeight = Math.max(60, trackHeight * (5 / store.allImages.length));
}
const scrollProgress = store.currentImageIndex / Math.max(1, store.allImages.length - 1);
const maxThumbTop = trackHeight - thumbHeight;
const thumbTop = scrollProgress * maxThumbTop;
progressThumb.style.height = `${thumbHeight}px`;
progressThumb.style.top = `${thumbTop}px`;
progressLabel.textContent = displayLabel;
}
viewport.addEventListener("wheel", (e) => {
e.preventDefault();
e.stopPropagation();
scrollOffset = clamp(scrollOffset + e.deltaY, 0, maxOffset());
renderVisibleItems();
if (onScrollToBottom && scrollOffset >= maxOffset() - ITEM_HEIGHT) {
onScrollToBottom();
}
if (onScrollToTop && scrollOffset <= ITEM_HEIGHT) {
onScrollToTop();
}
}, { passive: false });
content.addEventListener("click", (e) => {
const item = e.target.closest(".sp-thumb-item");
if (item == null ? void 0 : item.dataset.index) {
const index = parseInt(item.dataset.index);
if (!isNaN(index) && index >= 0 && index < store.allImages.length) {
clickedFromPanel = true;
onIndexChange(index);
}
}
});
progressTrack.onclick = (e) => {
if (e.target === progressThumb) return;
const rect = progressTrack.getBoundingClientRect();
const clickY = e.clientY - rect.top;
const scrollProgress = Math.min(1, Math.max(0, clickY / rect.height));
const targetIndex = Math.round(scrollProgress * (store.allImages.length - 1));
if (targetIndex >= 0 && targetIndex < store.allImages.length) {
onIndexChange(targetIndex);
}
};
let isDragging = false;
let dragStartY = 0;
let thumbStartTop = 0;
progressThumb.onmousedown = (e) => {
e.preventDefault();
e.stopPropagation();
isDragging = true;
dragStartY = e.clientY;
thumbStartTop = progressThumb.offsetTop;
document.body.style.userSelect = "none";
};
document.addEventListener("mousemove", (e) => {
if (!isDragging) return;
const deltaY = e.clientY - dragStartY;
const newTop = thumbStartTop + deltaY;
const trackHeight = cachedTrackHeight;
const thumbHeight = progressThumb.offsetHeight;
const maxTop = trackHeight - thumbHeight;
const clampedTop = Math.max(0, Math.min(maxTop, newTop));
const scrollProgress = maxTop > 0 ? clampedTop / maxTop : 0;
const targetIndex = Math.round(scrollProgress * (store.allImages.length - 1));
if (targetIndex >= 0 && targetIndex < store.allImages.length && targetIndex !== store.currentImageIndex) {
onIndexChange(targetIndex);
}
});
document.addEventListener("mouseup", () => {
if (isDragging) {
isDragging = false;
document.body.style.userSelect = "";
wakeUpProgressBar();
}
});
progressThumb.onclick = (e) => e.stopPropagation();
return { update, getElements: () => [progressTrack, thumbPanel], wakeUpProgressBar };
}
function setupNavigation(deps) {
function hasLoadingPlaceholders() {
return document.querySelectorAll(".r-ph").length > 0;
}
function syncAllImages() {
const freshImages = Array.from(qa(".r-img"));
if (freshImages.length !== store.allImages.length) {
store.allImages = freshImages;
}
}
function nextImage() {
if (store.currentImageIndex >= store.allImages.length - 3) {
syncAllImages();
}
if (store.currentImageIndex < store.allImages.length - 1) {
store.currentImageIndex++;
deps.updateImage();
deps.checkAndLoadNextPage();
} else if (hasLoadingPlaceholders()) {
deps.updateImage();
deps.checkAndLoadNextPage();
} else {
deps.checkAndLoadNextPage();
if (store.autoPlay) {
deps.stopAutoPlayAtEnd();
}
}
}
function previousImage() {
if (store.currentImageIndex > 0) {
store.currentImageIndex--;
deps.updateImage();
if (store.autoPlay) {
deps.resetAutoPlay();
}
}
if (store.currentImageIndex <= 3) {
deps.checkAndLoadPrevPage();
}
}
let accumulatedDelta = 0;
let isScrolling = false;
let lastFlipTime = 0;
let wheelTimeout;
const processWheelScroll = () => {
if (!isScrolling) return;
const threshold = 70;
const now = Date.now();
if (Math.abs(accumulatedDelta) >= threshold && now - lastFlipTime >= 40) {
if (accumulatedDelta > 0) {
nextImage();
} else {
previousImage();
}
accumulatedDelta = accumulatedDelta > 0 ? accumulatedDelta - threshold : accumulatedDelta + threshold;
lastFlipTime = now;
}
if (isScrolling) {
requestAnimationFrame(processWheelScroll);
}
};
deps.overlay.addEventListener("wheel", (e) => {
e.preventDefault();
let normalizedDelta = e.deltaY;
if (e.deltaMode === 1) {
normalizedDelta *= 33;
} else if (e.deltaMode === 2) {
normalizedDelta *= 800;
}
accumulatedDelta += normalizedDelta;
if (!isScrolling) {
isScrolling = true;
processWheelScroll();
}
clearTimeout(wheelTimeout);
wheelTimeout = setTimeout(() => {
isScrolling = false;
accumulatedDelta = 0;
}, 150);
}, { passive: false });
document.addEventListener("keydown", (e) => {
if (!deps.overlay.classList.contains("active")) return;
if (e.key === "Escape") {
deps.closeSinglePageMode();
} else if (e.key === "ArrowDown" || e.key === "ArrowRight") {
nextImage();
} else if (e.key === "ArrowUp" || e.key === "ArrowLeft") {
previousImage();
}
});
return { nextImage, previousImage };
}
function createAutoPlay(nextImageFn) {
function start() {
if (store.autoPlayTimer) clearInterval(store.autoPlayTimer);
if (store.autoPlay) {
store.autoPlayTimer = setInterval(nextImageFn, store.settings.autoPlayInterval);
}
}
function stop() {
if (store.autoPlayTimer) {
clearInterval(store.autoPlayTimer);
store.autoPlayTimer = null;
}
}
function reset() {
if (store.autoPlay) {
stop();
start();
}
}
function stopAtEnd() {
store.autoPlay = false;
store.emit("settingsChanged");
stop();
}
return { start, stop, reset, stopAtEnd };
}
function createStatusHUD() {
const container = document.createElement("div");
container.className = "sp-hud-container";
function show(config) {
const isError = config.status === "error";
const color = isError ? "#ef4444" : "#F596AA";
const iconSvg = isError ? `<svg style="color: ${color}; width: 18px; height: 18px;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>` : `<style>@keyframes sp-spin { 100% { transform: rotate(360deg); } }</style><svg style="color: ${color}; width: 18px; height: 18px; animation: sp-spin 1s linear infinite;" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M21 12a9 9 0 1 1-6.219-8.56"></path></svg>`;
container.innerHTML = `
<div class="sp-hud-box ${isError ? "hud-error" : ""}">
${iconSvg}
<div class="sp-hud-text">${config.text}</div>
${config.pageText ? `<div class="sp-hud-page">${config.pageText}</div>` : ""}
</div>
`;
const box = container.querySelector(".sp-hud-box");
if (config.onClick) {
box.style.cursor = "pointer";
box.onclick = config.onClick;
}
container.classList.add("show");
}
function hide() {
container.classList.remove("show");
}
return { show, hide, getElement: () => container };
}
const isZH = navigator.language.toLowerCase().includes("zh");
const i18n = {
readerMode: isZH ? "阅读器模式" : "Reader Mode",
autoPlay: isZH ? "自动翻页" : "Auto Play",
settings: isZH ? "设置" : "Settings",
backToTop: isZH ? "回到顶部" : "Back to Top",
scrollMode: isZH ? "卷轴模式" : "Scroll Mode",
autoEnter: isZH ? "自动进入阅读器" : "Auto Enter Reader",
showControl: isZH ? "显示悬浮控件" : "Show Float Control",
playSpeed: isZH ? "自动翻页速度" : "Auto Play Speed",
toggle: isZH ? "切换: " : "Toggle: ",
enabled: isZH ? "已开启" : "Enabled",
disabled: isZH ? "已关闭" : "Disabled",
waitingForNetwork: isZH ? "等待网络请求..." : "Waiting for network...",
downloading: isZH ? "下载中..." : "Downloading...",
loadFailed: isZH ? "加载失败" : "Load Failed",
waitImagesToLoad: isZH ? "请等待图片加载" : "Please wait for images to load"
};
function createSinglePageOverlay(deps) {
const overlay = document.createElement("div");
overlay.className = "single-page-overlay";
const closeBtn = document.createElement("div");
closeBtn.className = "sp-close-btn";
closeBtn.innerHTML = "✕";
const imageContainer = document.createElement("div");
imageContainer.className = "sp-image-container";
const currentImage = document.createElement("img");
currentImage.className = "sp-current-image";
imageContainer.appendChild(currentImage);
currentImage.addEventListener("error", () => {
if (!overlay.classList.contains("active")) return;
if (!currentImage.src || currentImage.src === location.href) return;
showError();
});
let loadPollTimer = null;
let loadTimeoutTimer = null;
let loadObserver = null;
function clearLoadPoll() {
if (loadPollTimer) {
clearInterval(loadPollTimer);
loadPollTimer = null;
}
if (loadTimeoutTimer) {
clearTimeout(loadTimeoutTimer);
loadTimeoutTimer = null;
}
if (loadObserver) {
loadObserver.disconnect();
loadObserver = null;
}
}
const statusHUD = createStatusHUD();
function showPlaceholder(statusText = "Loading...") {
statusHUD.show({
status: "loading",
text: statusText,
pageText: `${store.imageOffset + store.currentImageIndex + 1} / ${store.imageOffset + store.allImages.length}`
});
}
function removePlaceholder() {
statusHUD.hide();
}
function showError() {
clearLoadPoll();
currentImage.style.display = "none";
statusHUD.show({
status: "error",
text: i18n.loadFailed,
pageText: `${store.imageOffset + store.currentImageIndex + 1} / ${store.imageOffset + store.allImages.length}`
});
}
function removeErrorUI() {
statusHUD.hide();
}
function syncImages() {
const freshImages = Array.from(qa(".r-img, .r-ph"));
if (freshImages.length !== store.allImages.length || freshImages.some((img, i) => img !== store.allImages[i])) {
store.allImages = freshImages;
sidebar.update();
}
}
function applyOverlaySrc(imgSrc, currentImg) {
if (imgSrc && currentImage.dataset.assignedSrc !== imgSrc) {
currentImage.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
setTimeout(() => {
currentImage.src = imgSrc;
currentImage.dataset.assignedSrc = imgSrc;
if (!isImageReady(currentImg)) {
showPlaceholder(i18n.downloading);
} else {
removePlaceholder();
}
}, 0);
} else if (!imgSrc && currentImage.dataset.assignedSrc) {
currentImage.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
delete currentImage.dataset.assignedSrc;
}
}
function updateImage() {
clearLoadPoll();
removeErrorUI();
const idx = store.currentImageIndex;
syncImages();
const img = store.allImages[idx];
if (!img) {
showPlaceholder(i18n.waitingForNetwork);
sidebar.update();
startLoadPoll(idx);
return;
}
if (img.classList.contains("r-ph") && !img.dataset.lazyLoaded) {
img.dataset.lazyLoaded = "true";
loadPlaceholderImage(img);
}
const nextImg = store.allImages[idx + 1];
if (nextImg && nextImg.classList.contains("r-ph") && !nextImg.dataset.lazyLoaded) {
nextImg.dataset.lazyLoaded = "true";
loadPlaceholderImage(nextImg);
}
const imgSrc = img.dataset.realSrc || img.src;
applyOverlaySrc(imgSrc, img);
currentImage.style.display = "block";
if (!imgSrc) {
showPlaceholder(i18n.waitingForNetwork);
}
sidebar.update();
if (!isImageReady(img)) {
startLoadPoll(idx);
}
}
function startLoadPoll(idx) {
if (store.autoPlay) autoPlay.stop();
let imageErrored = false;
let lastKnownImg = store.allImages[idx];
function onImageReady() {
if (store.currentImageIndex !== idx) return;
const img = store.allImages[idx];
if (img && isImageReady(img)) {
clearLoadPoll();
removePlaceholder();
sidebar.update();
if (store.autoPlay) autoPlay.start();
}
}
function onImageError() {
imageErrored = true;
if (store.autoPlay) {
tryAutoSkip();
} else {
if (store.currentImageIndex === idx) {
showError();
}
}
}
function tryAutoSkip() {
if (store.currentImageIndex !== idx) return;
const nextIdx = idx + 1;
if (nextIdx < store.allImages.length) {
const nextImg = store.allImages[nextIdx];
if (nextImg && isImageReady(nextImg)) {
clearLoadPoll();
store.currentImageIndex = nextIdx;
updateImage();
checkAndLoadNextPage();
if (store.autoPlay) autoPlay.start();
return;
}
}
if (nextIdx < store.allImages.length || imageErrored) {
clearLoadPoll();
store.currentImageIndex = nextIdx < store.allImages.length ? nextIdx : idx;
updateImage();
if (nextIdx < store.allImages.length) {
checkAndLoadNextPage();
}
}
}
if (lastKnownImg && lastKnownImg.tagName === "IMG") {
lastKnownImg.addEventListener("load", onImageReady, { once: true });
lastKnownImg.addEventListener("error", onImageError, { once: true });
}
const mainBox = document.querySelector(store.settings.scrollMode ? "#gdt" : "#gdt-hidden");
if (mainBox) {
loadObserver = new MutationObserver(() => {
if (store.currentImageIndex !== idx) {
clearLoadPoll();
return;
}
syncImages();
const currentImg = store.allImages[idx];
if (currentImg) {
if (currentImg.classList.contains("error")) {
showError();
return;
} else if (currentImg.classList.contains("loading")) {
showPlaceholder(i18n.downloading);
return;
}
const imgSrc = currentImg.dataset.realSrc || currentImg.src;
applyOverlaySrc(imgSrc, currentImg);
if (currentImg !== lastKnownImg) {
lastKnownImg = currentImg;
if (currentImg.tagName === "IMG") {
currentImg.addEventListener("load", onImageReady, { once: true });
currentImg.addEventListener("error", onImageError, { once: true });
if (isImageReady(currentImg)) onImageReady();
}
}
}
});
loadObserver.observe(mainBox, { childList: true, subtree: true });
}
loadPollTimer = setInterval(() => {
if (store.currentImageIndex !== idx) {
clearLoadPoll();
return;
}
const currentImg = store.allImages[idx];
if (currentImg) {
if (currentImg.classList.contains("error")) {
showError();
return;
} else if (currentImg.classList.contains("loading")) {
showPlaceholder(i18n.downloading);
return;
}
const imgSrc = currentImg.dataset.realSrc || currentImg.src;
applyOverlaySrc(imgSrc, currentImg);
if (currentImg !== lastKnownImg) {
lastKnownImg = currentImg;
if (currentImg.tagName === "IMG") {
currentImg.addEventListener("load", onImageReady, { once: true });
currentImg.addEventListener("error", onImageError, { once: true });
}
}
}
onImageReady();
}, 500);
if (store.autoPlay) {
loadTimeoutTimer = setTimeout(() => {
if (store.currentImageIndex !== idx) return;
const img = store.allImages[idx];
if (img && isImageReady(img)) return;
tryAutoSkip();
}, CFG.imageLoadTimeout);
}
}
const autoPlay = createAutoPlay(() => nav.nextImage());
const sidebar = createSidebar((index) => {
store.currentImageIndex = index;
updateImage();
autoPlay.reset();
}, () => loadNextPage(), () => loadPrevPage());
const nav = setupNavigation({
overlay,
updateImage,
checkAndLoadNextPage: () => checkAndLoadNextPage(),
checkAndLoadPrevPage: () => loadPrevPage(),
resetAutoPlay: () => autoPlay.reset(),
stopAutoPlayAtEnd: () => autoPlay.stopAtEnd(),
closeSinglePageMode: () => close()
});
overlay.addEventListener("wheel", () => {
sidebar.wakeUpProgressBar();
}, { passive: true });
overlay.appendChild(closeBtn);
sidebar.getElements().forEach((el) => overlay.appendChild(el));
overlay.appendChild(statusHUD.getElement());
overlay.appendChild(imageContainer);
document.body.appendChild(overlay);
closeBtn.onclick = () => close();
function open() {
var _a, _b;
store.allImages = Array.from(qa(".r-img, .r-ph"));
if (store.allImages.length === 0) {
alert(i18n.waitImagesToLoad);
return;
}
let startIndex = 0;
if (store.settings.scrollMode) {
let minDistance = Infinity;
store.allImages.forEach((img, index) => {
const rect = img.getBoundingClientRect();
const viewportCenter = window.innerHeight / 2;
if (rect.top <= viewportCenter && rect.bottom >= viewportCenter) {
startIndex = index;
minDistance = -1;
} else if (minDistance !== -1) {
const distanceToCenter = rect.bottom < viewportCenter ? viewportCenter - rect.bottom : rect.top - viewportCenter;
if (distanceToCenter < minDistance) {
minDistance = distanceToCenter;
startIndex = index;
}
}
});
} else {
const adapter = store.activeAdapter;
let nativeImages = [];
if (adapter == null ? void 0 : adapter.getNativeImages) {
nativeImages = adapter.getNativeImages();
} else {
const container = adapter == null ? void 0 : adapter.getContainer();
if (container) {
nativeImages = Array.from(container.querySelectorAll("img")).filter((img) => img.clientWidth > 50 || img.clientHeight > 50);
}
}
if (nativeImages.length > 0) {
let minDistance = Infinity;
let bestNativeImg = null;
nativeImages.forEach((img) => {
const rect = img.getBoundingClientRect();
if (rect.width === 0 || rect.height === 0) return;
const viewportCenter = window.innerHeight / 2;
if (rect.top <= viewportCenter && rect.bottom >= viewportCenter) {
bestNativeImg = img;
minDistance = -1;
} else if (minDistance !== -1) {
const distanceToCenter = rect.bottom < viewportCenter ? viewportCenter - rect.bottom : rect.top - viewportCenter;
if (distanceToCenter < minDistance) {
minDistance = distanceToCenter;
bestNativeImg = img;
}
}
});
if (bestNativeImg) {
const currentSrc = ((_a = bestNativeImg.dataset) == null ? void 0 : _a.viewerUrl) || ((_b = bestNativeImg.dataset) == null ? void 0 : _b.src) || bestNativeImg.src;
const foundIdx = store.allImages.findIndex((i) => {
var _a2, _b2, _c;
const iSrc = ((_a2 = i.dataset) == null ? void 0 : _a2.viewerUrl) || ((_b2 = i.dataset) == null ? void 0 : _b2.realSrc) || ((_c = i.dataset) == null ? void 0 : _c.src) || i.src;
return iSrc === currentSrc;
});
if (foundIdx !== -1) {
startIndex = foundIdx;
}
}
} else {
startIndex = 0;
}
}
store.currentImageIndex = startIndex;
overlay.classList.add("active");
document.body.style.overflow = "hidden";
updateImage();
store.emit("readerModeChanged");
if (store.autoPlay) {
autoPlay.start();
}
}
function close() {
var _a, _b;
clearLoadPoll();
removeErrorUI();
autoPlay.stop();
store.autoPlay = false;
overlay.classList.remove("active");
document.body.style.overflow = "";
store.emit("readerModeChanged");
if (store.settings.scrollMode) {
const currentImages = Array.from(qa(".r-img, .r-ph"));
if (store.currentImageIndex >= 0 && store.currentImageIndex < currentImages.length) {
const targetImg = currentImages[store.currentImageIndex];
if (targetImg) {
setTimeout(() => {
targetImg.scrollIntoView({ behavior: "smooth", block: "center" });
}, 100);
}
}
} else {
const adapter = store.activeAdapter;
let nativeImages = [];
if (adapter == null ? void 0 : adapter.getNativeImages) {
nativeImages = adapter.getNativeImages();
} else {
const container = adapter == null ? void 0 : adapter.getContainer();
if (container) {
nativeImages = Array.from(container.querySelectorAll("img")).filter((img) => img.clientWidth > 50 || img.clientHeight > 50);
}
}
const targetImgFallback = store.allImages[store.currentImageIndex];
const batchDiv = targetImgFallback == null ? void 0 : targetImgFallback.closest(".page-batch");
if (batchDiv && batchDiv.dataset.pageUrl) {
const targetUrl = new URL(batchDiv.dataset.pageUrl, window.location.href);
const currentUrl = new URL(window.location.href);
if (targetUrl.pathname !== currentUrl.pathname || targetUrl.search !== currentUrl.search) {
window.location.href = targetUrl.toString();
return;
}
}
if (store.currentImageIndex >= 0 && nativeImages.length > 0) {
const currentSrc = ((_a = targetImgFallback == null ? void 0 : targetImgFallback.dataset) == null ? void 0 : _a.viewerUrl) || ((_b = targetImgFallback == null ? void 0 : targetImgFallback.dataset) == null ? void 0 : _b.realSrc) || (targetImgFallback == null ? void 0 : targetImgFallback.src);
if (currentSrc) {
const targetNativeImg = nativeImages.find((img) => {
var _a2, _b2;
const nativeSrc = ((_a2 = img.dataset) == null ? void 0 : _a2.viewerUrl) || ((_b2 = img.dataset) == null ? void 0 : _b2.src) || img.src;
return nativeSrc === currentSrc;
});
if (targetNativeImg) {
setTimeout(() => {
targetNativeImg.scrollIntoView({ behavior: "smooth", block: "center" });
}, 100);
return;
}
}
}
if ((adapter == null ? void 0 : adapter.name) === "E-Hentai" || (adapter == null ? void 0 : adapter.name) === "ExHentai") {
const globalIndex = store.imageOffset + store.currentImageIndex;
if (globalIndex >= 0) {
const targetPage = Math.floor(globalIndex / store.perPage);
const url = new URL(window.location.href);
const currentPage = parseInt(url.searchParams.get("p") || "0");
if (targetPage !== currentPage) {
url.searchParams.set("p", String(targetPage));
window.location.href = url.toString();
}
}
}
}
}
store.on("settingsChanged", () => {
if (!overlay.classList.contains("active")) return;
if (store.autoPlay) {
const img = store.allImages[store.currentImageIndex];
if (img && isImageReady(img)) {
autoPlay.start();
}
} else {
autoPlay.stop();
}
});
function loadNextPage() {
if (!store.nextUrl || store.isFetching) return;
store.isFetching = true;
store.activeAdapter.fetchPage(store.nextUrl).then(({ links, nextUrl, prevUrl }) => {
deps.onLoadNextPage(links, nextUrl, prevUrl);
syncImages();
store.isFetching = false;
checkAndLoadNextPage();
}).catch((err) => {
console.error("[Single Page] Load failed", err);
store.isFetching = false;
});
}
function loadPrevPage() {
if (!store.prevUrl || store.isFetching) return;
store.isFetching = true;
store.activeAdapter.fetchPage(store.prevUrl).then(({ links, prevUrl }) => {
const prevCount = links.length;
deps.onLoadPrevPage(links, prevUrl ?? null);
store.currentImageIndex += prevCount;
store.imageOffset = Math.max(0, store.imageOffset - prevCount);
if (prevCount > 0 && store.currentImageIndex === prevCount) {
store.currentImageIndex--;
}
store.allImages = Array.from(qa(".r-img, .r-ph"));
sidebar.update();
updateImage();
store.isFetching = false;
}).catch((err) => {
console.error("[Single Page] Load prev failed", err);
store.isFetching = false;
});
}
function checkAndLoadNextPage() {
if (!store.nextUrl || store.isFetching) return;
const remainingImages = store.allImages.length - store.currentImageIndex;
if (remainingImages <= 10) {
loadNextPage();
}
}
function jumpTo(index) {
if (!overlay.classList.contains("active")) return;
store.currentImageIndex = Math.max(0, Math.min(index, store.allImages.length - 1));
updateImage();
autoPlay.reset();
}
return {
open,
close,
isActive: () => overlay.classList.contains("active"),
getOverlayElement: () => overlay,
jumpTo
};
}
function initSinglePageMode() {
const spm = createSinglePageOverlay({
onLoadNextPage: (links, nextUrl) => {
store.currPage++;
processBatch(links, store.currPage, void 0, false, store.nextUrl || void 0);
store.nextUrl = nextUrl;
},
onLoadPrevPage: (links, prevUrl) => {
processBatch(links, store.currPage - 1, void 0, true, store.prevUrl || void 0);
store.prevUrl = prevUrl;
}
});
return spm;
}
const svgSettings = `<svg viewBox="0 0 24 24"><path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.7-1.62-0.94L14.4,2.81c-0.04-0.24-0.24-0.41-0.48-0.41 h-3.84c-0.24,0-0.43,0.17-0.47,0.41L9.25,5.35C8.66,5.59,8.12,5.92,7.63,6.29L5.24,5.33c-0.22-0.08-0.47,0-0.59,0.22L2.74,8.87 C2.62,9.08,2.66,9.34,2.86,9.48l2.03,1.58C4.84,11.36,4.8,11.69,4.8,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.7,1.62,0.94l0.36,2.54 c0.05,0.24,0.24,0.41,0.48,0.41h3.84c0.24,0,0.44-0.17,0.47-0.41l0.36-2.54c0.59-0.24,1.13-0.56,1.62-0.94l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.12-0.22,0.07-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/></svg>`;
const svgReader = `<svg viewBox="0 0 24 24"><path d="M21 5c-1.11-.35-2.33-.5-3.5-.5-1.95 0-4.05.4-5.5 1.5-1.45-1.1-3.55-1.5-5.5-1.5S2.45 4.9 1 6v14.65c0 .25.25.5.5.5.1 0 .15-.05.25-.05C3.1 20.45 5.05 20 6.5 20c1.95 0 4.05.4 5.5 1.5 1.35-.85 3.8-1.5 5.5-1.5 1.65 0 3.35.3 4.75 1.05.1.05.15.05.25.05.25 0 .5-.25.5-.5V6c-.6-.45-1.25-.75-2-1zm0 13.5c-1.1-.35-2.3-.5-3.5-.5-1.7 0-4.15.65-5.5 1.5V8c1.35-.85 3.8-1.5 5.5-1.5 1.2 0 2.4.15 3.5.5v11.5z"/></svg>`;
const svgPlay = `<svg viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>`;
const svgPause = `<svg viewBox="0 0 24 24"><path d="M6 4h4v16H6V4zm8 0h4v16h-4V4z"/></svg>`;
const svgTop = `<svg viewBox="0 0 24 24"><path d="M4 4h16v2H4V4zm4 8l1.41 1.41L11 11.83V22h2V11.83l1.59 1.58L16 12l-4-4-4 4z"/></svg>`;
const SETTINGS = [
{ label: i18n.scrollMode, key: "scrollMode" },
{ label: i18n.showControl, key: "showControl" },
{ label: i18n.autoEnter, key: "autoEnterSinglePage" }
];
function createSettingsPanel() {
const settingsBtn = document.createElement("div");
settingsBtn.className = "settings-btn";
const settingsPanel = document.createElement("div");
settingsPanel.className = "settings-panel";
SETTINGS.forEach(({ label, key }) => {
if (key === "scrollMode" && store.activeAdapter && ["18comic", "4KHD"].includes(store.activeAdapter.name)) {
return;
}
const item = document.createElement("div");
item.className = "settings-item";
const labelEl = document.createElement("span");
labelEl.className = "settings-label";
labelEl.textContent = label;
const toggle = document.createElement("div");
toggle.className = `toggle-switch${store.settings[key] ? " on" : ""}`;
const slider = document.createElement("div");
slider.className = "toggle-slider";
toggle.appendChild(slider);
toggle.onclick = () => {
const newValue = !store.settings[key];
store.updateSetting(key, newValue);
toggle.classList.toggle("on", newValue);
if (key === "scrollMode") {
window.location.reload();
}
};
item.appendChild(labelEl);
item.appendChild(toggle);
settingsPanel.appendChild(item);
});
const intervalItem = document.createElement("div");
intervalItem.className = "settings-item";
const intervalLabel = document.createElement("span");
intervalLabel.className = "settings-label";
intervalLabel.textContent = i18n.playSpeed;
const intervalRight = document.createElement("div");
intervalRight.style.cssText = "display:flex;align-items:center;gap:4px;";
const intervalInput = document.createElement("input");
intervalInput.type = "number";
intervalInput.className = "interval-input";
intervalInput.min = "1";
intervalInput.max = "60";
intervalInput.step = "0.5";
intervalInput.value = String(store.settings.autoPlayInterval / 1e3);
intervalInput.onclick = (e) => e.stopPropagation();
intervalInput.onchange = (e) => {
const value = parseFloat(e.target.value);
if (!isNaN(value) && value >= 1 && value <= 60) {
store.updateSetting("autoPlayInterval", value * 1e3);
}
};
const intervalUnit = document.createElement("span");
intervalUnit.textContent = "s";
intervalUnit.style.cssText = "font-size:12px;color:#888;";
intervalRight.appendChild(intervalInput);
intervalRight.appendChild(intervalUnit);
intervalItem.appendChild(intervalLabel);
intervalItem.appendChild(intervalRight);
settingsPanel.appendChild(intervalItem);
settingsBtn.onclick = (e) => {
e.stopPropagation();
settingsPanel.classList.toggle("show");
};
document.addEventListener("click", (e) => {
if (!settingsPanel.contains(e.target) && !settingsBtn.contains(e.target)) {
settingsPanel.classList.remove("show");
}
});
return {
getButtonElement: () => settingsBtn,
getPanelElement: () => settingsPanel
};
}
function createFloatControl(spmHandle) {
const floatControl = document.createElement("div");
floatControl.className = `float-control${store.settings.showControl ? "" : " hidden"}`;
const autoPlayBtn = document.createElement("div");
autoPlayBtn.className = `side-btn auto-play-btn hidden${store.autoPlay ? " active" : ""}`;
autoPlayBtn.innerHTML = store.autoPlay ? svgPause : svgPlay;
autoPlayBtn.title = i18n.autoPlay;
autoPlayBtn.onclick = (e) => {
e.stopPropagation();
const newValue = !store.autoPlay;
store.autoPlay = newValue;
store.emit("settingsChanged");
autoPlayBtn.innerHTML = newValue ? svgPause : svgPlay;
autoPlayBtn.classList.toggle("active", newValue);
};
const circleControl = document.createElement("div");
circleControl.className = "circle-control";
circleControl.innerHTML = svgReader;
circleControl.title = i18n.readerMode;
circleControl.onclick = (e) => {
var _a;
if (e.target !== circleControl && !((_a = circleControl.querySelector("svg")) == null ? void 0 : _a.contains(e.target))) return;
if (spmHandle.isActive()) {
spmHandle.close();
} else {
spmHandle.open();
}
};
store.on("readerModeChanged", () => {
if (spmHandle.isActive()) {
autoPlayBtn.classList.remove("hidden");
autoPlayBtn.innerHTML = store.autoPlay ? svgPause : svgPlay;
autoPlayBtn.classList.toggle("active", store.autoPlay);
} else {
autoPlayBtn.classList.add("hidden");
}
});
const settings = createSettingsPanel();
const settingsBtn = settings.getButtonElement();
settingsBtn.className = "side-btn";
settingsBtn.innerHTML = svgSettings;
settingsBtn.title = i18n.settings;
const topBtn = document.createElement("div");
topBtn.className = "side-btn top-btn";
topBtn.innerHTML = svgTop;
topBtn.title = i18n.backToTop;
topBtn.onclick = (e) => {
e.stopPropagation();
if (spmHandle.isActive()) {
spmHandle.jumpTo(0);
} else {
window.scrollTo({ top: 0, behavior: "smooth" });
document.documentElement.scrollTo({ top: 0, behavior: "smooth" });
document.body.scrollTo({ top: 0, behavior: "smooth" });
}
};
circleControl.appendChild(topBtn);
floatControl.appendChild(autoPlayBtn);
floatControl.appendChild(circleControl);
floatControl.appendChild(settingsBtn);
floatControl.appendChild(settings.getPanelElement());
document.body.appendChild(floatControl);
}
function registerMenuCommands() {
if (!store.activeAdapter || !["18comic", "4KHD"].includes(store.activeAdapter.name)) {
_GM_registerMenuCommand(`${i18n.toggle}${i18n.scrollMode}`, () => {
store.updateSetting("scrollMode", !store.settings.scrollMode);
alert(`${i18n.scrollMode} ${store.settings.scrollMode ? i18n.enabled : i18n.disabled}`);
location.reload();
});
}
_GM_registerMenuCommand(`${i18n.toggle}${i18n.showControl}`, () => {
store.updateSetting("showControl", !store.settings.showControl);
alert(`${i18n.showControl} ${store.settings.showControl ? i18n.enabled : i18n.disabled}`);
location.reload();
});
_GM_registerMenuCommand(`${i18n.toggle}${i18n.autoEnter}`, () => {
store.updateSetting("autoEnterSinglePage", !store.settings.autoEnterSinglePage);
alert(`${i18n.autoEnter} ${store.settings.autoEnterSinglePage ? i18n.enabled : i18n.disabled}`);
location.reload();
});
}
function initMemoryManager() {
const mainBox = document.querySelector(store.settings.scrollMode ? "#gdt" : "#gdt-hidden");
if (mainBox) {
const domObs = new MutationObserver(() => {
const images = Array.from(qa(".r-img, .r-ph"));
let changed = images.length !== store.allImages.length;
if (!changed) {
for (let i = 0; i < images.length; i++) {
if (images[i] !== store.allImages[i]) {
changed = true;
break;
}
}
}
if (changed) {
store.allImages = images;
}
});
domObs.observe(mainBox, { childList: true, subtree: true });
}
setInterval(() => {
if (store.allImages.length < 40) return;
if (document.querySelector(".single-page-overlay.active")) return;
const viewportCenter = window.innerHeight / 2;
let minDistance = Infinity;
let closestIndex = store.currentImageIndex;
store.allImages.forEach((el, i) => {
const rect = el.getBoundingClientRect();
if (rect.height > 0) {
const center = rect.top + rect.height / 2;
const dist = Math.abs(center - viewportCenter);
if (dist < minDistance) {
minDistance = dist;
closestIndex = i;
}
}
});
store.currentImageIndex = closestIndex;
const curr = store.currentImageIndex;
const buffer = 30;
store.allImages.forEach((img, i) => {
const distance = Math.abs(i - curr);
if (distance > buffer) {
if (img.tagName === "IMG" && img.hasAttribute("src") && img.complete) {
img.dataset.recycledSrc = img.getAttribute("src") || "";
img.removeAttribute("src");
}
} else {
if (img.tagName === "IMG" && !img.hasAttribute("src") && img.dataset.recycledSrc) {
img.setAttribute("src", img.dataset.recycledSrc);
delete img.dataset.recycledSrc;
}
}
});
}, 2e3);
}
(async function main() {
var _a;
const adapter = SiteManager.getAdapter(window.location.href);
if (!adapter) {
return;
}
store.activeAdapter = adapter;
store.reloadSettings();
if (adapter.name === "18comic" || adapter.name === "4KHD") {
store.settings.scrollMode = true;
}
const initData = await adapter.init(document);
if (!initData.links || initData.links.length === 0) return;
store.totalPage = initData.totalPage ?? 1;
store.nextUrl = initData.nextUrl;
store.prevUrl = initData.prevUrl;
store.perPage = initData.links.length;
let container = adapter.getContainer();
if (store.settings.scrollMode) {
document.documentElement.classList.add("scroll-mode");
(_a = adapter.hideOriginalElements) == null ? void 0 : _a.call(adapter);
if (!container) {
container = document.createElement("div");
container.id = "gdt";
document.body.appendChild(container);
}
container.innerHTML = "";
processBatch(initData.links, store.currPage, container, false, window.location.href);
setupAutoScroll();
} else {
const hiddenBox = document.createElement("div");
hiddenBox.id = "gdt-hidden";
hiddenBox.style.display = "none";
document.body.appendChild(hiddenBox);
processBatch(initData.links, store.currPage, hiddenBox, false, window.location.href);
}
let spmHandle;
createFloatControl({
open: () => spmHandle.open(),
close: () => spmHandle.close(),
isActive: () => spmHandle.isActive(),
getOverlayElement: () => spmHandle.getOverlayElement(),
jumpTo: (index) => spmHandle.jumpTo(index)
});
spmHandle = initSinglePageMode();
registerMenuCommands();
if (store.settings.autoEnterSinglePage) {
setTimeout(() => spmHandle.open(), 1e3);
}
initMemoryManager();
})();
})();