🔞WNACG 绅士漫画
https://www.wnacg.com/?qysg-source=2026.10.03.4
lonbeaubrunpjl81 (13720)10/03 08:44
需要科学上网环境
{
"bookSourceUrl": "https:\/\/www.wnacg.com\/?qysg-source=2026.10.03.4",
"bookSourceName": "🔞WNACG 绅士漫画",
"enabledExplore": true,
"enabled": true,
"bookSourceGroup": "WNACG原生漫画",
"author": "",
"help": true,
"html": "<!DOCTYPE html>\n<html lang=\"zh-CN\"><head>\n<meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>WNACG · 轻悦时光原生漫画源<\/title>\n<\/head><body>\n<p>WNACG 轻悦时光原生漫画源,版本 2026.10.03.4。<\/p>\n<p>请完整导入原生书源 JSON。每个图集为一本书,目录按每 20 页生成阅读分段。<\/p>\n<p>遇到验证请在同一应用内网页完成,看到正常页面后返回。<\/p>\n<script>\nvar isCookieJar = true;\n(function () {\n var loader = {version: \"2026.10.03.4\", implementation: null, error: \"\", completed: false};\n window.__wnacgLoader = loader;\n window.wnacgSourceVersion = loader.version;\n window.isCookieJar = true;\n window.addEventListener(\"error\", function (event) {\n if (!loader.completed) loader.error = String(event.message || \"脚本加载错误\") +\n \"(行 \" + (event.lineno || \"未知\") + \")\";\n });\n function reportFailedLoad() {\n if (loader.completed || loader.implementation) return;\n var bridge = window.flutter_inappwebview;\n if (!bridge || typeof bridge.callHandler !== \"function\") return;\n loader.completed = true;\n try { bridge.callHandler(\"log\", \"WNACG \" + loader.version + \":主脚本加载失败,\" +\n (loader.error || \"未执行完整 HTML\")); } catch (_) {}\n try { bridge.callHandler(\"CookieJar\", true); } catch (_) {}\n }\n window.addEventListener(\"flutterInAppWebViewPlatformReady\", function () {\n setTimeout(reportFailedLoad, 1000);\n });\n setTimeout(reportFailedLoad, 1500);\n})();\n\nfunction wnacgDiagnosticLog(message) {\n var bridge = window.flutter_inappwebview;\n if (!bridge || typeof bridge.callHandler !== \"function\") return;\n try {\n var result = bridge.callHandler(\"log\", message);\n if (result && typeof result.catch === \"function\") result.catch(function () {});\n } catch (_) {}\n}\n\n\/\/ 与官方模板一致:入口用顶层函数声明,同时挂到 window,兼容包装执行。\nasync function wnacgInvoke(name, args) {\n var loader = window.__wnacgLoader;\n var detail = name === \"search\" ? \",关键字 \" + String(args[0] || \"\") + \",页码 \" + String(args[1]) :\n name === \"find\" ? \",页码 \" + String(args[1]) + \",地址 \" + String(args[0] || \"\") : \"\";\n wnacgDiagnosticLog(\"WNACG \" + loader.version + \":入口 \" + name + detail);\n try {\n if (!loader.implementation || typeof loader.implementation[name] !== \"function\") {\n throw new Error(\"主脚本未完整执行:\" + (loader.error || \"请重新导入完整文件\"));\n }\n var result = await loader.implementation[name].apply(null, args);\n wnacgDiagnosticLog(\"WNACG:\" + name + \" 完成,返回 \" + String(result == null ? \"\" : result).length + \" 字符\");\n return result;\n } catch (error) {\n var message = \"WNACG \" + loader.version + \":\" + name + \" 失败:\" + String(error && error.message || error);\n wnacgDiagnosticLog(message);\n try {\n var bridge = window.flutter_inappwebview;\n if (bridge && typeof bridge.callHandler === \"function\") {\n var notice = bridge.callHandler(\"showToast\", message);\n if (notice && typeof notice.catch === \"function\") notice.catch(function () {});\n }\n } catch (_) {}\n throw error;\n }\n}\nasync function search(key, page) { return await wnacgInvoke(\"search\", [key, page]); }\nasync function info(bookurl) { return await wnacgInvoke(\"info\", [bookurl]); }\nasync function chapter(tocUrl) { return await wnacgInvoke(\"chapter\", [tocUrl]); }\nasync function content(url) { return await wnacgInvoke(\"content\", [url]); }\nasync function getfinds() { return await wnacgInvoke(\"getfinds\", []); }\nasync function find(url, page) { return await wnacgInvoke(\"find\", [url, page]); }\nasync function getloginurl() { return await wnacgInvoke(\"getloginurl\", []); }\nasync function login() { return await wnacgInvoke(\"login\", []); }\nasync function pay(bookurl, url) { return await wnacgInvoke(\"pay\", [bookurl, url]); }\nasync function gethelp() { return await wnacgInvoke(\"gethelp\", []); }\nasync function verifyWNACG() { return await wnacgInvoke(\"verifyWNACG\", []); }\nasync function cycleWNACGSpeed() { return await wnacgInvoke(\"cycleWNACGSpeed\", []); }\nasync function clearWNACGImageCache() { return await wnacgInvoke(\"clearWNACGImageCache\", []); }\nwindow.search = search; window.info = info; window.chapter = chapter; window.content = content;\nwindow.getfinds = getfinds; window.find = find; window.getloginurl = getloginurl; window.login = login;\nwindow.pay = pay; window.gethelp = gethelp; window.verifyWNACG = verifyWNACG;\nwindow.cycleWNACGSpeed = cycleWNACGSpeed; window.clearWNACGImageCache = clearWNACGImageCache;\n\n\/\/ WNACG_MAIN_IMPLEMENTATION_BEGIN\n(function () {\n\"use strict\";\nvar baseurl = \"https:\/\/www.wnacg.com\";\nvar sourceVersion = \"2026.10.03.4\";\nvar pagesPerChapter = 20;\nvar wnacgUA = \"\";\nvar nativeReady = null;\nvar backgroundSettingsStarted = false;\nvar requestQueue = Promise.resolve();\nvar detailCache = new Map();\nvar pendingPages = new Map();\nvar manifestCache = new Map();\nvar pendingManifests = new Map();\nvar pendingContents = new Map();\nvar searchPages = new Map();\nvar verificationPausedUntil = 0;\nvar imageConcurrency = 4;\nvar imageSpeedKey = \"WNACG.imageConcurrency.v1\";\nvar activeImageRequests = 0;\nvar imageWaiters = [];\nvar imageCache = new Map();\nvar pendingImages = new Map();\nvar imageCacheCharacters = 0;\nvar imageCacheLimit = 12 * 1024 * 1024;\nvar imageCacheEntries = 128;\nvar imageCacheTTL = 10 * 60 * 1000;\nvar manifestTTL = 2 * 60 * 1000;\nvar imageCacheGeneration = 0;\nvar preferredImageScheme = \"https\";\nwindow.isCookieJar = true;\n\nfunction nativeCall(name) {\n var bridge = window.flutter_inappwebview;\n if (!bridge || typeof bridge.callHandler !== \"function\") {\n return Promise.reject(new Error(\"请在轻悦时光的原生书源中运行本文件。\"));\n }\n var args = Array.prototype.slice.call(arguments, 1);\n try { return Promise.resolve(bridge.callHandler.apply(bridge, [name].concat(args))); }\n catch (error) { return Promise.reject(error); }\n}\n\nasync function optionalCall() {\n var args = arguments;\n var timer;\n try {\n return await Promise.race([\n nativeCall.apply(null, args),\n new Promise(function (resolve) { timer = setTimeout(function () { resolve(null); }, 1500); })\n ]);\n }\n catch (_) { return null; }\n finally { clearTimeout(timer); }\n}\n\nfunction initializeWNACG() {\n if (nativeReady) return nativeReady;\n nativeReady = (async function () {\n wnacgUA = wnacgUA || navigator.userAgent;\n var names = [\"search\", \"info\", \"chapter\", \"content\", \"getfinds\", \"find\", \"getloginurl\", \"login\", \"gethelp\"];\n var missing = names.filter(function (name) { return typeof window[name] !== \"function\"; });\n if (missing.length) throw new Error(\"WNACG:书源入口未注册:\" + missing.join(\", \"));\n \/\/ 准备通知先发送,UA、缓存和日志不得阻塞应用加载书源。\n await nativeCall(\"CookieJar\", true);\n wnacgDiagnosticLog(\"WNACG \" + sourceVersion + \":准备完成,入口已注册\");\n if (!backgroundSettingsStarted) {\n backgroundSettingsStarted = true;\n optionalCall(\"getWebViewUA\").then(function (ua) {\n if (typeof ua === \"string\" && ua.trim()) wnacgUA = ua.trim();\n });\n optionalCall(\"cache.get\", imageSpeedKey).then(function (saved) {\n var speed = Number(saved);\n if ([2, 4, 6].indexOf(speed) >= 0) imageConcurrency = speed;\n });\n }\n })();\n var ready = nativeReady;\n ready.catch(function () { if (nativeReady === ready) nativeReady = null; });\n return ready;\n}\n\nfunction cleanText(value) {\n return String(value == null ? \"\" : value).replace(\/\\s+\/g, \" \").trim();\n}\n\nfunction parseDoc(html) {\n return new DOMParser().parseFromString(String(html || \"\"), \"text\/html\");\n}\n\nfunction absoluteUrl(value, from) {\n if (!value) return \"\";\n try {\n \/\/ 网站部分封面使用 \/\/\/\/host\/path,浏览器 URL 解析前规范成 \/\/host\/path。\n var text = String(value).trim().replace(\/^\\\/{3,}\/, \"\/\/\");\n var u = new URL(text, from || baseurl + \"\/\");\n return \/^https?:$\/.test(u.protocol) ? u.href : \"\";\n } catch (_) { return \"\"; }\n}\n\nfunction sourceUrl(value, keepHash) {\n var absolute = absoluteUrl(value);\n if (!absolute) throw new Error(\"WNACG:链接无效。\");\n var u = new URL(absolute);\n if (!\/^(?:www\\.)?wnacg\\.com$\/i.test(u.hostname) || u.port || u.username || u.password) {\n throw new Error(\"WNACG:书籍或章节链接必须属于 wnacg.com。\");\n }\n if (!\/^(?:\\\/|\\\/albums(?:-[a-z0-9%_.~\\-]+)?\\.html|\\\/photos-(?:index|slide|item)(?:-[a-z0-9\\-]+)\\.html|\\\/search\\\/(?:index\\.php)?|\\\/users-login\\.html)$\/i.test(u.pathname)) {\n throw new Error(\"WNACG:不支持此页面链接。\");\n }\n u.protocol = \"https:\";\n u.hostname = \"www.wnacg.com\";\n if (!keepHash) u.hash = \"\";\n return u.href;\n}\n\nfunction albumId(value) {\n var u = new URL(sourceUrl(value));\n var m = \/^\\\/photos-(?:index|slide|item)-(?:page-\\d+-)?aid-(\\d+)\\.html$\/.exec(u.pathname);\n if (!m || !Number(m[1])) throw new Error(\"WNACG:缺少有效的图集编号,请重新打开书籍。\");\n return m[1];\n}\n\nfunction bookUrl(value) {\n return baseurl + \"\/photos-index-aid-\" + albumId(value) + \".html\";\n}\n\nfunction pageHeaders() {\n return {\"User-Agent\": wnacgUA || navigator.userAgent, \"Referer\": baseurl + \"\/\",\n \"Accept-Language\": \"zh-CN,zh;q=0.9,zh-TW;q=0.8,en;q=0.5\"};\n}\n\nfunction responseObject(value) {\n for (var i = 0; i < 3 && typeof value === \"string\"; i++) {\n try { value = JSON.parse(value); } catch (_) { return null; }\n }\n return value && typeof value === \"object\" ? value : null;\n}\n\nfunction pageResult(value) {\n var parsed = value;\n for (var i = 0; i < 3 && typeof parsed === \"string\" && \/^\\s*[{\"]\/.test(parsed); i++) {\n try { parsed = JSON.parse(parsed); } catch (_) { break; }\n }\n return {\n html: typeof parsed === \"string\" ? parsed.trim() : cleanHtml(parsed && (parsed.html || parsed.data || parsed.body)),\n url: parsed && typeof parsed === \"object\" ? absoluteUrl(parsed.url || parsed.location) : \"\"\n };\n}\n\nfunction cleanHtml(value) {\n return typeof value === \"string\" ? value.trim() : \"\";\n}\n\nfunction pageProblem(html) {\n if (!html) return \"网页没有返回内容\";\n var doc = parseDoc(html), title = cleanText(doc.title);\n var body = cleanText(doc.body && doc.body.textContent);\n if (\/too many requests|rate limit|请求过于频繁|訪問過於頻繁\/i.test(title) || \/^(?:429\\b|Too Many Requests\\b)\/i.test(body)) return \"网站限流,请稍后重试\";\n if (\/attention required.*cloudflare|access denied|forbidden|^403\\b\/i.test(title) || \/sorry,? you have been blocked|you are unable to access|error\\s*1020\/i.test(body)) return \"Cloudflare 拒绝当前网络或浏览器的访问\";\n if (\/just a moment|checking your browser|security verification|请稍候|請稍候\/i.test(title) ||\n doc.querySelector(\"#challenge-form, #cf-challenge-running, #cf-error-details\") || \/(?:window\\.)?_cf_chl_opt\\s*=\/.test(html)) return \"需要完成 Cloudflare 人机验证\";\n if (\/^401\\b|unauthorized|authentication required\/i.test(title)) return \"网站要求登录\";\n if (\/^404\\b|not found|页面不存在|頁面不存在\/i.test(title)) return \"网页不存在或已经下架\";\n if (\/^50[0234]\\b|bad gateway|service unavailable|gateway time.?out\/i.test(title)) return \"网站暂时不可用,请稍后重试\";\n return \"\";\n}\n\nfunction albumLink(node, from) {\n if (!node) return \"\";\n var href = node.getAttribute(\"href\") || \"\";\n \/\/ 兼容模板内没有首斜杠的作品路由;仍严格校验域名与作品路径。\n if (\/^photos-index-(?:page-\\d+-)?aid-\\d+\\.html(?:[?#]|$)\/.test(href)) href = \"\/\" + href;\n var absolute = absoluteUrl(href, from || baseurl + \"\/\");\n if (!absolute) return \"\";\n var u = new URL(absolute);\n if (!\/^(?:www\\.)?wnacg\\.com$\/i.test(u.hostname) || u.port || u.username || u.password ||\n !\/^\\\/photos-index-(?:page-\\d+-)?aid-\\d+\\.html$\/.test(u.pathname)) return \"\";\n try { return bookUrl(u.href); } catch (_) { return \"\"; }\n}\n\nfunction listCards(doc, from) {\n \/\/ 网站按 UA 返回两套模板:桌面 gallary_item,手机 col_2\/ImgA。\n return Array.from(doc.querySelectorAll(\"li.gallary_item, .gallary_wrap .gallary_item, ul.col_2 > li, #classify_container > li\")).filter(function (card) {\n if (card.closest(\"header, nav, footer, aside, .related, .recommend, .advertisement, .ad-banner\")) return false;\n return Array.from(card.querySelectorAll(\"a[href]\")).some(function (a) { return !!albumLink(a, from); });\n });\n}\n\nfunction noSearchResults(doc) {\n \/\/ 仅识别明确的无结果句式。裸「無\/无」会错误匹配筛选项、标签和脚本。\n if (Array.from(doc.querySelectorAll(\"a[href]\")).some(function (a) { return !!albumLink(a); })) return false;\n var nodes = Array.from(doc.querySelectorAll(\".empty, .no-results, .search-empty, #no-results, #classify_container, .gallary_wrap\"));\n return nodes.some(function (node) {\n var clone = node.cloneNode(true);\n Array.from(clone.querySelectorAll(\"script, style, noscript, template\")).forEach(function (el) { el.remove(); });\n var text = cleanText(clone.textContent);\n return \/(?:沒有|没有|未)找到(?:符合|相關|相关|結果|结果|任何|漫畫|漫画|作品)|(?:沒有|没有|無|无)(?:搜尋|搜索|搜尋結果|搜索结果|匹配結果|匹配结果)|(?:查無|查无)(?:結果|结果|資料|资料)|\\bno (?:search )?results\\b\/i.test(text);\n });\n}\n\nfunction listIdentity(value) {\n var u = new URL(sourceUrl(value));\n u.hash = \"\";\n u.searchParams.delete(\"qysg-source\");\n u.searchParams.delete(\"p\");\n u.searchParams.delete(\"page\");\n u.searchParams.delete(\"c\");\n if (u.searchParams.get(\"m\") === \"\") u.searchParams.delete(\"m\");\n u.pathname = u.pathname.replace(\/-page-\\d+(?=-|\\.html)\/, \"\");\n if (u.pathname === \"\/albums.html\") u.pathname = \"\/albums-index.html\";\n if (\/^\\\/search\\\/\/.test(u.pathname)) u.pathname = \"\/search\/\";\n u.searchParams.sort();\n return u.href;\n}\n\nfunction numberedPage(value) {\n var n = Number(value == null || value === \"\" ? 1 : value);\n \/\/ 兼容客户端以 0 表示首次调用,按用户提供的 MYCOMIC 源处理为第一页。\n if (n === 0) { wnacgDiagnosticLog(\"WNACG:客户端页码 0 按第一页读取\"); n = 1; }\n if (!Number.isInteger(n) || n < 1) throw new Error(\"WNACG:页码必须为正整数。\");\n return n;\n}\n\nfunction currentPage(doc) {\n var node = doc.querySelector(\".paginator .thispage, span.thispage, [aria-current='page']\");\n var text = cleanText(node && node.textContent);\n return \/^\\d+$\/.test(text) ? Number(text) : null;\n}\n\nfunction pageNumber(value) {\n var u = new URL(sourceUrl(value));\n var m = \/-page-(\\d+)(?:-|\\.html)\/.exec(u.pathname);\n return numberedPage(m ? m[1] : u.searchParams.get(\"p\") || u.searchParams.get(\"page\") || 1);\n}\n\nfunction validPage(html, url, kind, observedUrl) {\n if (pageProblem(html)) return false;\n var doc = parseDoc(html), expected = new URL(url);\n if (observedUrl) {\n try {\n var actual = new URL(sourceUrl(observedUrl));\n if (kind === \"list\") {\n if (listIdentity(actual.href) !== listIdentity(expected.href)) return false;\n \/\/ 页码另由 DOM 检查:超出末页有可能重定向到最后一页。\n } else if (actual.pathname !== expected.pathname) return false;\n } catch (_) { return false; }\n }\n if (kind === \"detail\") {\n var reader = doc.querySelector(\"#reader-btn[href], a[href*='photos-slide-aid-'], a[href*='photos-list-aid-']\");\n var name = doc.querySelector(\"#comicName, .userwrap > h2, .bodywrap h2, h2\");\n if (!reader || !cleanText(name && name.textContent)) return false;\n try {\n var href = absoluteUrl(reader.getAttribute(\"href\"), url).replace(\/\\\/photos-list-aid-\/, \"\/photos-slide-aid-\");\n return albumId(href) === albumId(url);\n } catch (_) { return false; }\n }\n if (kind === \"list\") {\n var query = doc.querySelector('input[name=\"q\"]');\n if (\/^\\\/search\\\/\/.test(expected.pathname) && (!query || (query.getAttribute(\"value\") || \"\") !== (expected.searchParams.get(\"q\") || \"\"))) return false;\n if (parseBooks(html, url).length) return true;\n return !!query && noSearchResults(doc);\n }\n return false;\n}\n\nasync function failWNACG(message) {\n await optionalCall(\"showToast\", message);\n await optionalCall(\"log\", message);\n throw new Error(message);\n}\n\nasync function webPage(url) {\n \/\/ 不传 JS 时官方接口直接返回 HTML,兼容不接受表达式返回值的客户端。\n return pageResult(await nativeCall(\"webview\", url, \"\", \"\", \"\", JSON.stringify(pageHeaders()), \"\", \"\"));\n}\n\nasync function httpPage(url) {\n var response = responseObject(await nativeCall(\"http\", \"get\", url, \"\", JSON.stringify(pageHeaders()), true, \"\"));\n assertHttp(response);\n var html = typeof response.data === \"string\" && \/<\\s*(?:!doctype|html|head|body)\/i.test(response.data) ? response.data : utf8Response(response);\n return {html: html, url: \"\"};\n}\n\nfunction logPage(result, via, url, kind) {\n var doc = parseDoc(result.html);\n var problem = pageProblem(result.html);\n wnacgDiagnosticLog(\"WNACG:\" + via + \" \" + kind + \",HTML \" + result.html.length +\n \" 字符,候选 \" + listCards(doc, url).length + \",作品链接 \" +\n Array.from(doc.querySelectorAll(\"a[href]\")).filter(function (a) { return !!albumLink(a, url); }).length +\n \",模板 \" + (doc.querySelector(\"ul.col_2, #classify_container\") ? \"手机\" : doc.querySelector(\".gallary_wrap\") ? \"桌面\" : \"未知\") +\n \",页码 \" + (currentPage(doc) || \"无\") +\n (problem ? \",\" + problem : \"\") + \",地址 \" + url);\n}\n\nasync function fetchPage(url, kind) {\n await initializeWNACG();\n var initial = {html: \"\", url: \"\"}, firstError = \"\";\n try { initial = await webPage(url); logPage(initial, \"WebView\", url, kind); }\n catch (e) { firstError = cleanText(e.message); wnacgDiagnosticLog(\"WNACG:WebView 失败:\" + firstError); }\n if (validPage(initial.html, url, kind, initial.url)) return initial.html;\n var problem = pageProblem(initial.html);\n if (\/限流|不存在|暂时不可用\/.test(problem)) return failWNACG(\"WNACG:\" + problem);\n \/\/ WebView 未取得完整正文时,补试原生 HTTP;同样检查验证页面。\n try {\n var direct = await httpPage(url);\n logPage(direct, \"HTTP\", url, kind);\n if (validPage(direct.html, url, kind, direct.url)) return direct.html;\n var directProblem = pageProblem(direct.html);\n if (\/限流|不存在|暂时不可用\/.test(directProblem)) return failWNACG(\"WNACG:\" + directProblem);\n if (!problem || problem === \"网页没有返回内容\") problem = directProblem || problem;\n } catch (e) { firstError = firstError || cleanText(e.message); }\n if (Date.now() < verificationPausedUntil) return failWNACG(\"WNACG:上次验证未完成,请先点击发现页的“网页验证 \/ 登录”再重试。\");\n await optionalCall(\"showToast\", \"请在应用内网页完成验证或登录,看到正常页面后返回。\");\n var manual = {html: \"\", url: \"\"};\n try { manual = pageResult(await nativeCall(\"startBrowser\", url, \"WNACG:完成验证后返回\", JSON.stringify(pageHeaders()))); }\n catch (e) { firstError = firstError || cleanText(e.message); }\n if (validPage(manual.html, url, kind, manual.url)) { verificationPausedUntil = 0; return manual.html; }\n if (manual.html && !pageProblem(manual.html)) {\n try { initial = await webPage(url); } catch (_) {}\n if (validPage(initial.html, url, kind, initial.url)) { verificationPausedUntil = 0; return initial.html; }\n }\n verificationPausedUntil = Date.now() + 30000;\n return failWNACG(\"WNACG:\" + (pageProblem(manual.html) || problem || firstError || \"页面与目标不符或网站结构已变化\") + \"。可从发现页重新验证。\");\n}\n\nfunction loadPage(value, kind) {\n var url = sourceUrl(value), key = kind + \":\" + url;\n var cached = kind === \"detail\" && detailCache.get(url);\n if (cached && Date.now() - cached.time < 30000) return Promise.resolve(cached.html);\n if (pendingPages.has(key)) return pendingPages.get(key);\n var task = requestQueue.catch(function () {}).then(function () { return fetchPage(url, kind); });\n requestQueue = task.catch(function () {});\n pendingPages.set(key, task);\n return task.then(function (html) {\n pendingPages.delete(key);\n if (kind === \"detail\") {\n if (detailCache.size >= 8) detailCache.delete(detailCache.keys().next().value);\n detailCache.set(url, {html: html, time: Date.now()});\n }\n return html;\n }, function (err) { pendingPages.delete(key); throw err; });\n}\n\nfunction imageSrc(img) {\n if (!img) return \"\";\n return img.getAttribute(\"data-original\") || img.getAttribute(\"data-src\") || img.getAttribute(\"src\") || \"\";\n}\n\nfunction mediaSource(value, referer) {\n var url = absoluteUrl(value, referer);\n if (!url) return \"\";\n url = url.replace(\/^http:\/, \"https:\");\n return url.replace(\/\"\/g, \"%22\") + \",\" + JSON.stringify({headers: {\n \"User-Agent\": wnacgUA || navigator.userAgent, \"Referer\": referer || baseurl + \"\/\"\n }});\n}\n\nfunction parseBooks(html, from) {\n var doc = parseDoc(html), books = [], seen = new Set();\n listCards(doc, from).forEach(function (card) {\n function isBookLink(node) {\n return !!albumLink(node, from);\n }\n var a = Array.from(card.querySelectorAll(\".title a[href]\")).find(isBookLink) ||\n Array.from(card.querySelectorAll(\"a[href]\")).find(isBookLink);\n var url = albumLink(a, from);\n if (!url) return;\n if (seen.has(url)) return;\n var img = card.querySelector(\".pic_box img, img\");\n var name = cleanText(card.querySelector(\".txtA\") && card.querySelector(\".txtA\").textContent) ||\n cleanText(a.getAttribute(\"title\")) || cleanText(a.querySelector(\"span\") && a.querySelector(\"span\").textContent) ||\n cleanText(a.textContent) || cleanText(img && img.getAttribute(\"alt\"));\n if (!name) return;\n seen.add(url);\n var infoNode = card.querySelector(\".info_col, span.info\");\n var summary = cleanText(infoNode && infoNode.textContent);\n var count = \/(\\d+)\\s*(?:P\\b|頁|页|張|张)\/i.exec(summary);\n books.push({bookUrl: url, name: name, author: \"\", kind: \"漫画 \/ 图集\",\n coverUrl: mediaSource(imageSrc(img), baseurl + \"\/\"), intro: summary,\n tocUrl: url, wordCount: count ? count[1] + \" 页\" : \"\", type: 2,\n latestChapterTitle: count ? \"共 \" + count[1] + \" 页\" : \"图集\"});\n });\n return books;\n}\n\nfunction rememberSearchLinks(doc, from, key) {\n var state = searchPages.get(key) || {time: Date.now(), links: new Map()};\n Array.from(doc.querySelectorAll(\".paginator a[href]\")).forEach(function (a) {\n try {\n var url = sourceUrl(absoluteUrl(a.getAttribute(\"href\"), from));\n if (listIdentity(url) === key) state.links.set(pageNumber(url), url);\n } catch (_) {}\n });\n state.time = Date.now();\n searchPages.delete(key);\n searchPages.set(key, state);\n while (searchPages.size > 8) searchPages.delete(searchPages.keys().next().value);\n}\n\nasync function search(key, page) {\n var p = numberedPage(page), u = new URL(baseurl + \"\/search\/\");\n u.searchParams.set(\"q\", String(key || \"\"));\n u.searchParams.set(\"f\", \"_all\"); u.searchParams.set(\"s\", \"create_time_DESC\"); u.searchParams.set(\"syn\", \"yes\");\n var identity = listIdentity(u.href), state = searchPages.get(identity);\n if (state && Date.now() - state.time > 5 * 60 * 1000) { searchPages.delete(identity); state = null; }\n \/\/ 顺序加载优先沿用网站游标;直接跳页仍使用网站支持的 p 参数。\n var target = state && state.links.get(p);\n if (!target) { u.searchParams.set(\"p\", p); target = u.href; }\n var html = await loadPage(target, \"list\"), doc = parseDoc(html), actual = currentPage(doc);\n if (actual !== null && actual !== p || p > 1 && actual === null) return \"[]\";\n rememberSearchLinks(doc, target, identity);\n var books = parseBooks(html, target);\n wnacgDiagnosticLog(\"WNACG:search 解析 \" + books.length + \" 本,候选 \" + listCards(doc).length);\n if (!books.length && listCards(doc).length) return failWNACG(\"WNACG:有候选卡片但解析出 0 本,页面结构不兼容,请发送 WNACG 日志。\");\n return JSON.stringify(books);\n}\n\nfunction detailMetadata(html, url) {\n var doc = parseDoc(html), header = doc.querySelector(\"#comicName, .userwrap > h2, .bodywrap h2, h2\");\n var connection = doc.querySelector(\".uwconn\");\n var labels = connection ? Array.from(connection.querySelectorAll(\"label\")) : Array.from(doc.querySelectorAll(\"label\"));\n var count = 0, category = \"\", description = \"\";\n labels.forEach(function (label) {\n var text = cleanText(label.textContent), m = \/(?:頁數|页数)\\s*[::]\\s*(\\d+)\\s*(?:P|頁|页)?\/i.exec(text);\n if (m) count = Number(m[1]);\n if (\/^分[類类]\\s*[::]\/.test(text)) category = text.replace(\/^分[類类]\\s*[::]\\s*\/, \"\");\n if (\/^(?:簡介|简介|說明|说明)\\s*[::]\/.test(text)) description = text.replace(\/^[^::]+[::]\\s*\/, \"\");\n });\n if (!count) {\n var mobileCount = cleanText(doc.querySelector(\".Introduct_Sub .date\") && doc.querySelector(\".Introduct_Sub .date\").textContent);\n var countMatch = \/(\\d+)\\s*(?:P\\b|頁|页)\/i.exec(mobileCount);\n if (countMatch) count = Number(countMatch[1]);\n }\n if (!category) {\n var categoryNode = doc.querySelector(\".Introduct_Sub a[href*='\/albums-index-cate-']\");\n category = cleanText(categoryNode && categoryNode.textContent);\n }\n var descriptionNode = doc.querySelector(\".dv-desc-text, .uwconn > p\");\n if (!description && descriptionNode) description = cleanText(descriptionNode.textContent);\n var tags = Array.from(doc.querySelectorAll(\".uwconn a.tagshow, .dv-tags .dv-tag\")).map(function (a) { return cleanText(a.textContent); }).filter(Boolean);\n var name = cleanText(header && header.textContent);\n if (!name) throw new Error(\"WNACG:作品标题未取得,网页结构可能已变化。\");\n if (!Number.isInteger(count) || count < 0 || count > 10000) throw new Error(\"WNACG:图集页数无效。\");\n return {bookUrl: url, name: name, author: \"\", kind: category || \"漫画 \/ 图集\",\n coverUrl: mediaSource(imageSrc(doc.querySelector(\"#Cover img, .uwthumb img\")), baseurl + \"\/\"),\n intro: [description, count ? \"共 \" + count + \" 页;目录每 \" + pagesPerChapter + \" 页分一段。\" : \"目录根据图片清单生成。\",\n tags.length ? \"标签:\" + tags.join(\" \/ \") : \"\"].filter(Boolean).join(\"\\n\"),\n tocUrl: url, wordCount: count ? count + \" 页\" : \"\", type: 2,\n latestChapterTitle: count ? \"共 \" + count + \" 页\" : \"图集\", pageCount: count};\n}\n\nasync function info(value) {\n var url = bookUrl(value), result = detailMetadata(await loadPage(url, \"detail\"), url);\n delete result.pageCount;\n return JSON.stringify(result);\n}\n\nfunction decodeBase64(body) {\n if (typeof body !== \"string\" || !body.trim()) throw new Error(\"原生 HTTP 缺少二进制 body(base64)\");\n var value = body.replace(\/\\s+\/g, \"\").replace(\/-\/g, \"+\").replace(\/_\/g, \"\/\");\n if (!\/^[A-Za-z0-9+\/]+={0,2}$\/.test(value) || value.length % 4 === 1) throw new Error(\"原生 HTTP body 不是有效 base64\");\n while (value.length % 4) value += \"=\";\n return value;\n}\n\nfunction assertHttp(response) {\n if (!response) throw new Error(\"原生 HTTP 未返回响应\");\n var status = Number(response.statusCode);\n if (!(status >= 200 && status < 300)) {\n var error = new Error(\"请求返回 HTTP \" + (status || \"未知状态\"));\n error.statusCode = status;\n throw error;\n }\n}\n\nfunction utf8Response(response) {\n assertHttp(response);\n var binary = atob(decodeBase64(response.body));\n if (typeof TextDecoder === \"function\") {\n var bytes = new Uint8Array(binary.length);\n for (var i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);\n return new TextDecoder(\"utf-8\", {fatal: true}).decode(bytes);\n }\n var encoded = \"\";\n for (var j = 0; j < binary.length; j++) encoded += \"%\" + (\"0\" + binary.charCodeAt(j).toString(16)).slice(-2);\n return decodeURIComponent(encoded);\n}\n\n\/\/ 只解析公开数据中的字符串数组,不执行网站脚本;允许尾逗号。\nfunction manifestSources(text, from) {\n var match = \/[\"']page_url[\"']\\s*:\\s*\\[\/.exec(String(text));\n if (!match) throw new Error(\"图片数据中缺少 page_url,网站结构可能已变化\");\n var pos = match.index + match[0].length, values = [], ended = false;\n function whitespace() { while (\/\\s\/.test(text.charAt(pos)) && pos < text.length) pos++; }\n while (pos < text.length) {\n whitespace();\n if (text.charAt(pos) === \"]\") { ended = true; pos++; break; }\n if (text.charAt(pos) !== '\"') throw new Error(\"图片地址数组含有无法解析的数据\");\n var start = pos++, escaped = false, closed = false;\n for (; pos < text.length; pos++) {\n var c = text.charAt(pos);\n if (escaped) escaped = false;\n else if (c === \"\\\\\") escaped = true;\n else if (c === '\"') { pos++; closed = true; break; }\n }\n if (!closed) throw new Error(\"图片地址数组被截断\");\n var value = JSON.parse(text.slice(start, pos));\n var src = absoluteUrl(value, from);\n if (!src) throw new Error(\"图片清单包含无效地址\");\n src = src.replace(\/^http:\/, \"https:\");\n values.push(src);\n if (values.length > 10000) throw new Error(\"图集图片数量超出支持范围\");\n whitespace();\n if (text.charAt(pos) === \",\") pos++;\n else if (text.charAt(pos) !== \"]\") throw new Error(\"图片地址数组格式已变化\");\n }\n if (!ended || !values.length) throw new Error(\"图片清单为空或不完整\");\n return values;\n}\n\nasync function requestManifest(id) {\n var url = baseurl + \"\/photos-item-aid-\" + id + \".html\";\n var headers = pageHeaders();\n headers.Referer = baseurl + \"\/photos-slide-aid-\" + id + \".html\";\n headers.Accept = \"application\/javascript,text\/javascript,text\/plain,*\/*;q=0.8\";\n var text = utf8Response(responseObject(await nativeCall(\"http\", \"get\", url, \"\", JSON.stringify(headers), true, \"\")));\n if (\/^\\s*<\/.test(text)) throw new Error(pageProblem(text) || \"图片接口返回网页,未取得图片清单\");\n return manifestSources(text, url);\n}\n\nfunction loadManifest(id, generation) {\n if (generation == null) generation = imageCacheGeneration;\n var saved = manifestCache.get(id);\n if (generation === imageCacheGeneration && saved && Date.now() - saved.time < manifestTTL) return Promise.resolve(saved.sources);\n var pendingKey = generation + \":\" + id;\n if (pendingManifests.has(pendingKey)) return pendingManifests.get(pendingKey);\n var task = requestQueue.catch(function () {}).then(async function () {\n await initializeWNACG();\n var firstError;\n try { return await requestManifest(id); } catch (e) { firstError = e; }\n if (firstError.statusCode === 429 || firstError.statusCode === 404 || firstError.statusCode >= 500) {\n return failWNACG(\"WNACG:图片清单读取失败:\" + cleanText(firstError.message));\n }\n \/\/ 只通过同一应用的网页完成常规验证,不执行数据脚本或绕过验证码。\n if (Date.now() < verificationPausedUntil) return failWNACG(\"WNACG:图片清单读取失败,请先完成网页验证。\" + cleanText(firstError.message));\n var url = baseurl + \"\/photos-index-aid-\" + id + \".html\";\n await optionalCall(\"showToast\", \"请在应用内网页完成验证,看到正常图集页面后返回。\");\n var manual = pageResult(await nativeCall(\"startBrowser\", url, \"WNACG:完成验证后返回\", JSON.stringify(pageHeaders())));\n if (!validPage(manual.html, url, \"detail\", manual.url)) {\n verificationPausedUntil = Date.now() + 30000;\n return failWNACG(\"WNACG:验证未完成,未取得图片清单。\" + (pageProblem(manual.html) || cleanText(firstError.message)));\n }\n verificationPausedUntil = 0;\n try { return await requestManifest(id); }\n catch (e) { return failWNACG(\"WNACG:图片清单读取失败:\" + cleanText(e.message) + \"。请稍后刷新目录。\"); }\n });\n requestQueue = task.catch(function () {});\n pendingManifests.set(pendingKey, task);\n return task.then(function (sources) {\n pendingManifests.delete(pendingKey);\n if (generation === imageCacheGeneration) {\n manifestCache.delete(id);\n manifestCache.set(id, {sources: sources, time: Date.now()});\n while (manifestCache.size > 12) manifestCache.delete(manifestCache.keys().next().value);\n }\n return sources;\n }, function (err) { pendingManifests.delete(pendingKey); throw err; });\n}\n\nasync function chapter(value) {\n var url = bookUrl(value), id = albumId(url);\n var metadata = detailMetadata(await loadPage(url, \"detail\"), url);\n var total = metadata.pageCount;\n \/\/ 元数据缺失时从完整公开图片数组取得总数;不把缩略图分页当成完整正文。\n if (!total) total = (await loadManifest(id)).length;\n var result = [];\n for (var start = 0; start < total; start += pagesPerChapter) {\n var count = Math.min(pagesPerChapter, total - start);\n result.push({name: \"第 \" + (start + 1) + \"—\" + (start + count) + \" 页 \/ 共 \" + total + \" 页\",\n chapterId: baseurl + \"\/photos-slide-aid-\" + id + \".html#qysg-start=\" + start + \"&qysg-count=\" + count + \"&qysg-total=\" + total,\n index: result.length, isPay: false, isVip: false, isVolume: false, tag: \"图集分页\"});\n }\n if (!result.length) return failWNACG(\"WNACG:没有取得图集页数,请刷新目录。\");\n return JSON.stringify(result);\n}\n\nfunction imageDataUri(response) {\n assertHttp(response);\n var body = decodeBase64(response.body), signature;\n try { signature = atob(body.slice(0, Math.min(64, body.length))); }\n catch (_) { throw new Error(\"图片 base64 解码失败\"); }\n var mime = \"\";\n if (signature.charCodeAt(0) === 255 && signature.charCodeAt(1) === 216 && signature.charCodeAt(2) === 255) mime = \"image\/jpeg\";\n else if (signature.slice(0, 8) === \"\\x89PNG\\r\\n\\x1a\\n\") mime = \"image\/png\";\n else if (\/^GIF8[79]a\/.test(signature)) mime = \"image\/gif\";\n else if (signature.slice(0, 4) === \"RIFF\" && signature.slice(8, 12) === \"WEBP\") mime = \"image\/webp\";\n else if (signature.slice(4, 8) === \"ftyp\" && \/avif|avis\/.test(signature.slice(8, 32))) mime = \"image\/avif\";\n if (!mime) throw new Error(\"返回内容不是受支持的图片,可能为验证或错误页面\");\n return \"data:\" + mime + \";base64,\" + body;\n}\n\nfunction startWaitingImages() {\n while (imageWaiters.length && activeImageRequests < imageConcurrency) {\n activeImageRequests++;\n imageWaiters.shift()();\n }\n}\n\nasync function withImageSlot(run) {\n await new Promise(function (resolve) { imageWaiters.push(resolve); startWaitingImages(); });\n try { return await run(); }\n finally { activeImageRequests--; startWaitingImages(); }\n}\n\nfunction removeCachedImage(key) {\n var entry = imageCache.get(key);\n if (entry) imageCacheCharacters -= entry.data.length;\n imageCache.delete(key);\n}\n\nfunction cachedImage(key) {\n var entry = imageCache.get(key);\n if (!entry) return \"\";\n if (Date.now() - entry.time >= imageCacheTTL) { removeCachedImage(key); return \"\"; }\n imageCache.delete(key); imageCache.set(key, entry);\n return entry.data;\n}\n\nfunction rememberImage(key, data, generation) {\n if (generation !== imageCacheGeneration || data.length > imageCacheLimit) return;\n removeCachedImage(key);\n imageCache.set(key, {data: data, time: Date.now()}); imageCacheCharacters += data.length;\n while (imageCacheCharacters > imageCacheLimit || imageCache.size > imageCacheEntries) removeCachedImage(imageCache.keys().next().value);\n}\n\nasync function requestImage(src, referer, pageIndex) {\n var headers = {\"User-Agent\": wnacgUA || navigator.userAgent, \"Referer\": referer,\n \"Accept\": \"image\/avif,image\/webp,image\/jpeg,image\/png,image\/gif;q=0.9,*\/*;q=0.5\"};\n var schemes = preferredImageScheme === \"https2\" ? [\"https2\", \"https\"] : [\"https\", \"https2\"], lastError;\n for (var attempt = 0; attempt < schemes.length; attempt++) {\n try {\n var response = responseObject(await nativeCall(\"http\", \"get\", src.replace(\/^https:\/, schemes[attempt] + \":\"), \"\", JSON.stringify(headers), true, \"\"));\n var result = imageDataUri(response); preferredImageScheme = schemes[attempt]; return result;\n } catch (e) {\n lastError = e;\n if (e.statusCode === 429 && imageConcurrency > 2) {\n imageConcurrency = 2;\n optionalCall(\"log\", \"WNACG:图片请求限流,本次运行自动降为 2 并发。\");\n }\n if (e.statusCode === 429 || e.statusCode === 404 || e.statusCode >= 500) break;\n }\n }\n throw new Error(\"WNACG:第 \" + (pageIndex + 1) + \" 页图片加载失败:\" + cleanText(lastError && lastError.message) + \"。请刷新本段后重试。\");\n}\n\nfunction downloadImage(src, referer, pageIndex, generation) {\n var key = referer + \"\\n\" + src;\n var saved = generation === imageCacheGeneration ? cachedImage(key) : \"\";\n if (saved) return Promise.resolve(saved);\n var pendingKey = generation + \":\" + key;\n if (pendingImages.has(pendingKey)) return pendingImages.get(pendingKey);\n var task = withImageSlot(function () { return requestImage(src, referer, pageIndex); }).then(function (data) {\n rememberImage(key, data, generation); pendingImages.delete(pendingKey); return data;\n }, function (err) { pendingImages.delete(pendingKey); throw err; });\n pendingImages.set(pendingKey, task);\n return task;\n}\n\nasync function inlineImages(sources, referer, start, generation) {\n var lines = new Array(sources.length);\n for (var cachedIndex = 0; cachedIndex < sources.length; cachedIndex++) {\n var saved = generation === imageCacheGeneration ? cachedImage(referer + \"\\n\" + sources[cachedIndex]) : \"\";\n if (saved) lines[cachedIndex] = '<img src=\"' + saved + '\" \/>';\n }\n var next = 0, stopped = false, failure = null;\n async function worker() {\n while (!stopped && next < sources.length) {\n var index = next++;\n if (lines[index]) continue;\n try { lines[index] = '<img src=\"' + await downloadImage(sources[index], referer, start + index, generation) + '\" \/>'; }\n catch (e) { stopped = true; failure = failure || e; }\n }\n }\n var workers = [];\n for (var i = 0; i < Math.min(imageConcurrency, sources.length); i++) workers.push(worker());\n await Promise.all(workers);\n if (failure) return failWNACG(failure.message);\n return lines.join(\"\\r\\n\");\n}\n\nasync function content(value) {\n var url = sourceUrl(value, true), u = new URL(url), id = albumId(url);\n var generation = imageCacheGeneration, pendingKey = generation + \":\" + url;\n if (pendingContents.has(pendingKey)) return pendingContents.get(pendingKey);\n var range = new URLSearchParams(u.hash.replace(\/^#\/, \"\"));\n var hasRange = range.has(\"qysg-start\");\n var start = hasRange ? Number(range.get(\"qysg-start\")) : 0;\n var count = hasRange ? Number(range.get(\"qysg-count\")) : pagesPerChapter;\n var expected = hasRange ? Number(range.get(\"qysg-total\")) : 0;\n if (!Number.isInteger(start) || start < 0 || !Number.isInteger(count) || count < 1 || count > pagesPerChapter ||\n hasRange && (!Number.isInteger(expected) || expected < 1 || expected > 10000 || start + count > expected)) {\n return failWNACG(\"WNACG:阅读分段参数无效,请重新刷新目录。\");\n }\n var referer = baseurl + \"\/photos-slide-aid-\" + id + \".html\";\n var task = (async function () {\n await initializeWNACG();\n var started = Date.now(), sources = await loadManifest(id, generation);\n if (expected && expected !== sources.length) {\n if (generation === imageCacheGeneration) {\n manifestCache.delete(id);\n detailCache.delete(baseurl + \"\/photos-index-aid-\" + id + \".html\");\n }\n sources = await loadManifest(id, generation);\n if (expected !== sources.length) return failWNACG(\"WNACG:图集页数已变化或图片清单不完整(目录 \" + expected + \" 页,清单 \" + sources.length + \" 页),请刷新目录。\");\n }\n \/\/ 旧链接没有分段参数时,仅支持本来就不超过一段的图集,避免静默少页。\n if (!hasRange && sources.length > pagesPerChapter) return failWNACG(\"WNACG:此链接未包含分页信息,请刷新目录后从分段章节进入。\");\n var selected = sources.slice(start, Math.min(start + count, sources.length));\n if (!selected.length || hasRange && selected.length !== count) return failWNACG(\"WNACG:图片清单缺页,请刷新目录。\");\n var hits = selected.filter(function (src) { return !!cachedImage(referer + \"\\n\" + src); }).length;\n await optionalCall(\"log\", \"WNACG \" + sourceVersion + \":读取第 \" + (start + 1) + \"—\" + (start + selected.length) +\n \" 页,缓存 \" + hits + \" 页,\" + imageConcurrency + \" 并发\");\n try {\n var result = await inlineImages(selected, referer, start, generation);\n await optionalCall(\"log\", \"WNACG:本段图片已完成,耗时 \" + ((Date.now() - started) \/ 1000).toFixed(1) + \" 秒\");\n return result;\n } catch (error) { if (generation === imageCacheGeneration) manifestCache.delete(id); throw error; }\n })();\n pendingContents.set(pendingKey, task);\n try { return await task; } finally { pendingContents.delete(pendingKey); }\n}\n\nasync function getfinds() {\n \/\/ 发现分类为本地静态数据;显示按钮不依赖网络、UA 或缓存读取。\n var finds = [\n {title: \"WNACG\", url: \"\", js: \"\", type: 0, width: 3},\n {title: \"最新图集\", url: baseurl + \"\/albums-index-page-{{page}}.html\", js: \"\", type: 0, width: 0},\n {title: \"收藏排行\", url: baseurl + \"\/albums-favorite_ranking.html\", js: \"\", type: 0, width: 0},\n {title: \"网页验证 \/ 登录\", url: baseurl + \"\/?qysg-action=verify\", js: \"\", type: 0, width: 3},\n {title: \"图片速度:\" + imageConcurrency + \" 并发(点击切换)\", url: baseurl + \"\/?qysg-action=speed\", js: \"\", type: 0, width: 3},\n {title: \"清除图片缓存\", url: baseurl + \"\/?qysg-action=clear-images\", js: \"\", type: 0, width: 3},\n {title: \"分类\", url: \"\", js: \"\", type: 0, width: 3}\n ];\n [[\"同人志 \/ CG\", \"5\"], [\"单行本\", \"6\"], [\"杂志 \/ 短篇\", \"7\"], [\"韩漫\", \"19\"], [\"3D \/ 漫画\", \"3\"]].forEach(function (item) {\n finds.push({title: item[0], url: baseurl + \"\/albums-index-page-{{page}}-cate-\" + item[1] + \".html\", js: \"\", type: 0, width: 0});\n });\n wnacgDiagnosticLog(\"WNACG:getfinds 返回 \" + finds.length + \" 个分类 \/ 操作\");\n return JSON.stringify(finds);\n}\n\nasync function find(value, page) {\n var p = numberedPage(page);\n if (!value) return \"[]\";\n var u = new URL(sourceUrl(String(value).replace(\/\\{\\{page\\}\\}\/g, p)));\n if (u.pathname === \"\/\" && u.searchParams.has(\"qysg-action\")) {\n if (p > 1) return \"[]\";\n var action = u.searchParams.get(\"qysg-action\");\n await initializeWNACG();\n if (action === \"verify\") await verifyWNACG();\n else if (action === \"speed\") await cycleWNACGSpeed();\n else if (action === \"clear-images\") await clearWNACGImageCache();\n else throw new Error(\"WNACG:未知发现操作,请刷新发现页。\");\n return \"[]\";\n }\n \/\/ 收藏排行本身不分页,客户端自动下一页直接结束。\n if (u.pathname === \"\/albums-favorite_ranking.html\" && p > 1) return \"[]\";\n if (\/^\\\/albums-index(?:-|\\.html)\/.test(u.pathname)) {\n var path = u.pathname.replace(\/-page-\\d+(?=-|\\.html)\/, \"\");\n u.pathname = path.replace(\/^\\\/albums-index\/, \"\/albums-index-page-\" + p);\n } else if (u.pathname === \"\/albums.html\" || u.pathname === \"\/\") u.pathname = \"\/albums-index-page-\" + p + \".html\";\n var html = await loadPage(u.href, \"list\"), doc = parseDoc(html), actual = currentPage(doc);\n if (actual !== null && actual !== p || p > 1 && actual === null) return \"[]\";\n var books = parseBooks(html, u.href);\n wnacgDiagnosticLog(\"WNACG:find 解析 \" + books.length + \" 本,候选 \" + listCards(doc).length);\n if (!books.length && listCards(doc).length) return failWNACG(\"WNACG:发现列表有候选卡片但解析出 0 本,请发送 WNACG 日志。\");\n return JSON.stringify(books);\n}\n\nasync function cycleWNACGSpeed() {\n await initializeWNACG();\n imageConcurrency = imageConcurrency === 4 ? 6 : imageConcurrency === 6 ? 2 : 4;\n await optionalCall(\"cache.set\", imageSpeedKey, String(imageConcurrency));\n startWaitingImages();\n await optionalCall(\"showToast\", \"WNACG:已切换为 \" + imageConcurrency + \" 并发,刷新发现页显示新档位。\");\n return \"\";\n}\n\nasync function clearWNACGImageCache() {\n imageCacheGeneration++; imageCache.clear(); imageCacheCharacters = 0; manifestCache.clear();\n await optionalCall(\"log\", \"WNACG:图片与图片地址缓存已清除\");\n await optionalCall(\"showToast\", \"WNACG:已清除本次运行的图片缓存,请刷新阅读分段。\");\n return \"\";\n}\n\nasync function verifyWNACG() {\n await initializeWNACG();\n verificationPausedUntil = 0; detailCache.clear(); manifestCache.clear(); searchPages.clear();\n var task = requestQueue.catch(function () {}).then(async function () {\n var url = baseurl + \"\/albums-index-page-1.html\";\n var manual = pageResult(await nativeCall(\"startBrowser\", url, \"WNACG:完成验证后返回\", JSON.stringify(pageHeaders())));\n if (!validPage(manual.html, url, \"list\", manual.url)) return failWNACG(\"WNACG:验证尚未完成,请看到正常图集列表后再返回。\");\n await optionalCall(\"showToast\", \"WNACG 验证完成,请重新搜索或刷新目录。\");\n return \"\";\n });\n requestQueue = task.catch(function () {});\n return task;\n}\n\nasync function getloginurl() {\n verificationPausedUntil = 0; detailCache.clear(); manifestCache.clear(); searchPages.clear();\n return baseurl + \"\/users-login.html\";\n}\n\nasync function login() {\n verificationPausedUntil = 0; detailCache.clear(); manifestCache.clear(); searchPages.clear();\n return \"\";\n}\n\nasync function pay() {\n return failWNACG(\"WNACG:本源读取公开图集,需要权限时请在应用内网页登录。\");\n}\n\nasync function gethelp() {\n return \"WNACG 轻悦时光原生漫画源 \" + sourceVersion + \"\\n\" +\n \"请完整导入 JSON 文件到原生书源,不能作为阅读 App 的规则书源导入。\\n\" +\n \"搜索、详情和发现列表通过应用 WebView 读取;图片清单从网站公开接口解析,不执行其中的 JavaScript。\\n\" +\n \"每个图集对应一本书,目录每 20 页分一段,名称中的页码是图片顺序,不是网站原有章节。\\n\" +\n \"正文带阅读页 Referer 下载后返回内嵌 base64,默认 4 并发,可在发现页切换 6/2/4。\\n\" +\n \"首次打开需要等当前分段图片下载完成;长图或大图仍可能耗时较长。图片缓存仅在本次书源运行中有效,10 分钟过期,容量有上限;图片地址缓存 2 分钟。\\n\" +\n \"遇到验证请在应用内网页完成,看到正常页面后返回。外部浏览器验证不保证共享到应用;无法自动破解验证码或绕过网站限制。\\n\" +\n \"操作按钮执行后结果页没有书籍;返回并刷新发现页可查看并发档位。\\n\" +\n \"图片数与目录不一致会报错,请刷新目录;更新源后清除旧正文缓存,以免使用旧链接。\\n\" +\n \"尚未在你的设备上进行轻悦时光实机验证,运行情况取决于应用版本、网络、网站和图片服务器。\";\n}\n\nwindow.__wnacgLoader.implementation = {\n search: search, info: info, chapter: chapter, content: content,\n getfinds: getfinds, find: find, getloginurl: getloginurl, login: login,\n pay: pay, gethelp: gethelp, verifyWNACG: verifyWNACG,\n cycleWNACGSpeed: cycleWNACGSpeed, clearWNACGImageCache: clearWNACGImageCache\n};\nwindow.__wnacgLoader.completed = true;\nwindow.wnacgSourceVersion = sourceVersion;\nwindow.addEventListener(\"flutterInAppWebViewPlatformReady\", initializeWNACG);\nif (window.flutter_inappwebview) initializeWNACG();\n})();\n\n\/\/ WNACG_MAIN_IMPLEMENTATION_END\n<\/script>\n<\/body><\/html>\n",
"login": true,
"customButton": false,
"eventListener": false,
"lastUpdateTime": "1790988269093"
}