[{"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"}]