[{"bookSourceUrl":"https:\/\/mycomic.com\/cn?qysg-source=2026.10.02.6","bookSourceName":"MYCOMIC漫画","enabledExplore":true,"enabled":true,"bookSourceGroup":"MYCOMIC原生漫画","author":"","help":true,"html":"<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n  <title>MYCOMIC · 轻悦时光漫画源 · 发现按钮修复版<\/title>\n  <script>\n  (function () {\n    \/\/ 此独立 ES5 脚本先注册入口。主脚本出错时也能返回加载诊断。\n    var loader = {version: \"2026.10.02.6\", implementation: null, error: \"\", completed: false};\n    window.__mycomicLoader = loader;\n    window.mycomicSourceVersion = loader.version;\n    var names = [\"search\", \"info\", \"chapter\", \"content\", \"getfinds\", \"find\", \"getloginurl\", \"login\", \"pay\", \"gethelp\", \"verifyMyComic\", \"cycleMyComicSpeed\", \"clearMyComicImageCache\"];\n    for (var i = 0; i < names.length; i++) {\n      (function (name) {\n        window[name] = function () {\n          var implementation = loader.implementation;\n          if (implementation && typeof implementation[name] === \"function\") {\n            return implementation[name].apply(null, arguments);\n          }\n          throw new Error(\"MYCOMIC \" + loader.version + \"：入口已注册，但主脚本没有完整执行。\" +\n            (loader.error || \"请从原生书源 JSON 重新导入完整文件。\"));\n        };\n      })(names[i]);\n    }\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\", \"MYCOMIC \" + loader.version + \"：主脚本加载失败，\" +\n        (loader.error || \"未执行完整 HTML，入口诊断已启用\")); } catch (_) {}\n      \/\/ 允许应用调用已注册的诊断入口，显示具体错误而不是 indefinitely 等待。\n      try { bridge.callHandler(\"CookieJar\", true); } catch (_) {}\n    }\n    window.addEventListener(\"flutterInAppWebViewPlatformReady\", function () {\n      setTimeout(reportFailedLoad, 1000);\n    });\n    setTimeout(reportFailedLoad, 1500);\n  })();\n  <\/script>\n<\/head>\n<body>\n  <p>MYCOMIC 轻悦时光原生漫画源，版本 2026.10.02.6（发现按钮修复版）。<\/p>\n  <p>请将本文件完整导入轻悦时光的原生书源，或完整粘贴到书源代码编辑器。<\/p>\n  <p>遇到 Cloudflare 验证时，在弹出的应用内网页完成验证，看到正常页面后返回。<\/p>\n<!--\n  接口依据：autobcb\/qysg README.md、default.html；漫画 type = 2。\n  网页使用应用 WebView；正文图片用原生 http 携带 Referer 获取二进制 base64。\n  正文输出 data:image 内嵌图片，避免阅读器二次下载丢失请求头或误解析 URL 后缀。\n  仅解析页面公开的 JSON-LD、Alpine chapters 数据与 img.page 图片地址。\n  CF、人机验证、网络和网页结构错误会明确报错，不以空目录\/空正文掩盖失败。\n-->\n<script>\n(function () {\nvar isCookieJar = true;\nvar baseurl = \"https:\/\/mycomic.com\";\nvar sourceVersion = \"2026.10.02.6\";\nvar mycomicUA = \"\";\nvar nativeReady = null;\nvar requestQueue = Promise.resolve();\nvar detailCache = new Map();\nvar pendingPages = new Map();\nvar verificationPausedUntil = 0;\nvar preferredImageScheme = \"https\";\nvar pendingContents = new Map();\nvar activeImageRequests = 0;\nvar imageWaiters = [];\nvar imageConcurrency = 4;\nvar imageSpeedKey = \"MYCOMIC.imageConcurrency.v1\";\nvar imageCache = new Map();\nvar imageCacheCharacters = 0;\nvar imageCacheLimit = 12 * 1024 * 1024;\nvar imageCacheEntries = 128;\nvar imageCacheTTL = 10 * 60 * 1000;\nvar imageCacheGeneration = 0;\nvar pendingImages = new Map();\nvar manifestCache = new Map();\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  return Promise.resolve(bridge.callHandler.apply(bridge, [name].concat(args)));\n}\n\nasync function optionalCall() {\n  try { return await nativeCall.apply(null, arguments); }\n  catch (_) { return null; }\n}\n\nfunction initializeMyComic() {\n  if (nativeReady) return nativeReady;\n  nativeReady = (async function () {\n    var ua = await optionalCall(\"getWebViewUA\");\n    mycomicUA = typeof ua === \"string\" && ua.trim() ? ua.trim() : navigator.userAgent;\n    var savedSpeed = Number(await optionalCall(\"cache.get\", imageSpeedKey));\n    if ([2, 4, 6].indexOf(savedSpeed) >= 0) imageConcurrency = savedSpeed;\n    var entryNames = [\"search\", \"info\", \"chapter\", \"content\", \"getfinds\", \"find\", \"getloginurl\", \"login\", \"gethelp\"];\n    var missing = entryNames.filter(function (name) { return typeof window[name] !== \"function\"; });\n    if (missing.length) throw new Error(\"MYCOMIC：书源入口未注册：\" + missing.join(\", \"));\n    await optionalCall(\"log\", \"MYCOMIC \" + sourceVersion + \"：书源入口已注册，search\/getfinds 可用\");\n    \/\/ CookieJar 也是原生的准备完成通知；在 UA 初始化后再通知。\n    await nativeCall(\"CookieJar\", true);\n  })();\n  \/\/ 普通浏览器预览不会启动桥接；应用会发送下面的事件。\n  nativeReady.catch(function () {});\n  return nativeReady;\n}\n\nfunction pageHeaders(url) {\n  return {\n    \"User-Agent\": mycomicUA || navigator.userAgent,\n    \"Referer\": baseurl + \"\/cn\",\n    \"Accept-Language\": \"zh-CN,zh;q=0.9,en;q=0.5\"\n  };\n}\n\nfunction absoluteUrl(value, from) {\n  if (!value) return \"\";\n  try {\n    var u = new URL(String(value).trim(), from || baseurl + \"\/cn\");\n    return \/^https?:$\/.test(u.protocol) ? u.href : \"\";\n  } catch (_) { return \"\"; }\n}\n\nfunction sourceUrl(value) {\n  var u = new URL(absoluteUrl(value));\n  if (u.origin !== baseurl || !\/^\\\/cn(?:\\\/|$)\/.test(u.pathname)) {\n    throw new Error(\"MYCOMIC：书籍或章节链接必须属于 mycomic.com\/cn。\");\n  }\n  u.hash = \"\";\n  return u.href;\n}\n\nfunction normalizeHtml(value) {\n  if (value && typeof value === \"object\") {\n    value = value.html || value.data || value.body || \"\";\n  }\n  if (typeof value !== \"string\") return \"\";\n  value = value.trim();\n  if (value.charAt(0) === '\"') {\n    try {\n      var decoded = JSON.parse(value);\n      if (typeof decoded === \"string\") value = decoded;\n    } catch (_) {}\n  }\n  return value;\n}\n\nfunction pageResult(value) {\n  var parsed = value;\n  for (var i = 0; i < 2 && typeof parsed === \"string\" && \/^[\\s]*[\\{\"]\/.test(parsed); i++) {\n    try { parsed = JSON.parse(parsed); } catch (_) { break; }\n  }\n  return {\n    html: normalizeHtml(parsed),\n    url: parsed && typeof parsed === \"object\" ? absoluteUrl(parsed.url || parsed.location) : \"\"\n  };\n}\n\nfunction parseDoc(html) {\n  return new DOMParser().parseFromString(html, \"text\/html\");\n}\n\nfunction cleanText(value) {\n  return String(value || \"\").replace(\/\\s+\/g, \" \").trim();\n}\n\nfunction pageProblem(html) {\n  if (!html) return \"网页没有返回内容\";\n  var doc = parseDoc(html);\n  var title = cleanText(doc.title);\n  var body = cleanText(doc.body && doc.body.textContent);\n  if (\/too many requests|rate limit|请求过于频繁|访问过于频繁\/i.test(title) ||\n      \/^(?:429\\b|Too Many Requests\\b)\/i.test(body)) return \"网站限流，请稍后重试\";\n  if (\/attention required.*cloudflare|access denied|forbidden|^403\\b\/i.test(title) ||\n      \/sorry,? you have been blocked|you are unable to access|error\\s*1020\/i.test(body)) {\n    return \"Cloudflare 拒绝了当前浏览器或网络的访问\";\n  }\n  if (\/just a moment|checking your browser|security verification|请稍候|请稍等\/i.test(title) ||\n      doc.querySelector(\"#challenge-form, #cf-challenge-running, #cf-error-details\") ||\n      \/(?: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)) {\n    return \"网站暂时不可用，请稍后重试\";\n  }\n  return \"\";\n}\n\nfunction comicJson(doc) {\n  var scripts = doc.querySelectorAll('script[type=\"application\/ld+json\"]');\n  for (var i = 0; i < scripts.length; i++) {\n    try {\n      var data = JSON.parse(scripts[i].textContent);\n      var queue = Array.isArray(data) ? data.slice() : [data];\n      while (queue.length) {\n        var item = queue.shift();\n        if (!item || typeof item !== \"object\") continue;\n        var types = Array.isArray(item[\"@type\"]) ? item[\"@type\"] : [item[\"@type\"]];\n        if (types.indexOf(\"ComicSeries\") >= 0) return item;\n        if (Array.isArray(item[\"@graph\"])) queue = queue.concat(item[\"@graph\"]);\n      }\n    } catch (_) {}\n  }\n  return null;\n}\n\nfunction listCards(doc) {\n  return Array.from(doc.querySelectorAll(\"div.aspect-w-3\")).filter(function (el) {\n    return Array.from(el.querySelectorAll(\"a[href]\")).some(function (a) {\n      return \/\\\/cn\\\/comics\\\/\\d+(?:[\/?#]|$)\/.test(a.getAttribute(\"href\") || \"\");\n    });\n  });\n}\n\nfunction samePath(actual, expected) {\n  return actual.origin === expected.origin &&\n    actual.pathname.replace(\/\\\/$\/, \"\") === expected.pathname.replace(\/\\\/$\/, \"\");\n}\n\nfunction sameQuery(actual, expected) {\n  var names = new Set(Array.from(actual.searchParams.keys()).concat(Array.from(expected.searchParams.keys())));\n  return Array.from(names).every(function (key) {\n    if (key === \"page\") return Number(actual.searchParams.get(key) || 1) === Number(expected.searchParams.get(key) || 1);\n    return (actual.searchParams.get(key) || \"\") === (expected.searchParams.get(key) || \"\");\n  });\n}\n\nfunction validPage(html, url, kind, observedUrl) {\n  if (pageProblem(html)) return false;\n  var doc = parseDoc(html);\n  var expected = new URL(url);\n  if (observedUrl) {\n    var actual = new URL(observedUrl);\n    if (!samePath(actual, expected) || kind === \"list\" && !sameQuery(actual, expected)) return false;\n  }\n  var canonical = doc.querySelector('link[rel=\"canonical\"], meta[property=\"og:url\"]');\n  if (canonical) {\n    var real = absoluteUrl(canonical.getAttribute(\"href\") || canonical.getAttribute(\"content\"));\n    if (real && !samePath(new URL(real), expected)) return false;\n  }\n  if (kind === \"detail\") {\n    var data = comicJson(doc);\n    if (!data) return false;\n    var idUrl = absoluteUrl(data.url || data[\"@id\"]);\n    return !idUrl || samePath(new URL(idUrl), expected);\n  }\n  if (kind === \"content\") {\n    var images = Array.from(doc.querySelectorAll(\"img.page\"));\n    if (!images.length) return false;\n    var id = \/\\\/chapters\\\/(\\d+)\/.exec(expected.pathname);\n    \/\/ 图片路径也带章节 id，避免前台导航后拿到另一章的内容。\n    return images.every(function (img) {\n      var src = img.getAttribute(\"data-src\") || img.getAttribute(\"src\") || \"\";\n      var imageId = \/\\\/chapters\\\/(\\d+)\\\/\/.exec(src);\n      return !id || !imageId || id[1] === imageId[1];\n    });\n  }\n  var inputs = Array.from(doc.querySelectorAll('input[name=\"q\"]'));\n  if (inputs.some(function (input) { return (input.getAttribute(\"value\") || \"\") !== (expected.searchParams.get(\"q\") || \"\"); })) return false;\n  var selected = doc.querySelectorAll(\"ui-disclosure[data-flux-navlist-group] a[data-current][href]\");\n  for (var i = 0; i < selected.length; i++) {\n    var selection = new URL(absoluteUrl(selected[i].getAttribute(\"href\"), url));\n    \/\/ 每个选项 href 保留其他筛选；仅省略 page，因此逐个核对。\n    var desired = new URL(url);\n    desired.searchParams.delete(\"page\");\n    selection.searchParams.delete(\"page\");\n    if (!sameQuery(selection, desired)) return false;\n  }\n  if (listCards(doc).length) return true;\n  \/\/ 真正无搜索结果时仍然保留站点的搜索表单\/搜索结果提示。\n  return !!doc.querySelector('input[name=\"q\"]') && \/mycomic\/i.test(doc.title) ||\n    \/(?:没有找到|未找到|查无|無搜尋結果|No results)\/i.test(cleanText(doc.body && doc.body.textContent));\n}\n\nasync function failMyComic(message) {\n  await optionalCall(\"showToast\", message);\n  await optionalCall(\"log\", message);\n  throw new Error(message);\n}\n\nasync function webPage(url) {\n  \/\/ 官方接口允许用 JS 返回值；读取实际 location 避免验证重定向造成串页。\n  var js = \"JSON.stringify({html:document.documentElement.outerHTML,url:window.location.href})\";\n  return pageResult(await nativeCall(\"webview\", url, js, \"\", \"\",\n    JSON.stringify(pageHeaders(url)), \"\", \"\"));\n}\n\nasync function fetchPage(url, kind) {\n  await initializeMyComic();\n  var html = \"\";\n  var observedUrl = \"\";\n  var firstError = \"\";\n  try { var initial = await webPage(url); html = initial.html; observedUrl = initial.url; }\n  catch (e) { firstError = cleanText(e.message); }\n  if (validPage(html, url, kind, observedUrl)) return html;\n  var problem = pageProblem(html);\n  \/\/ 429、404、服务故障不能靠重复弹出验证网页解决。\n  if (\/限流|不存在|暂时不可用\/.test(problem)) return failMyComic(\"MYCOMIC：\" + problem);\n  if (Date.now() < verificationPausedUntil) {\n    return failMyComic(\"MYCOMIC：上次验证未完成，请点击书源登录或发现页的“CF 验证”后重试。\");\n  }\n  await optionalCall(\"showToast\", \"请在应用内网页完成 CF 验证或登录，看到正常页面后返回。\");\n  var foreground = \"\";\n  var foregroundUrl = \"\";\n  try {\n    var manual = pageResult(await nativeCall(\"startBrowser\", url,\n      \"MYCOMIC：完成验证后返回\", JSON.stringify(pageHeaders(url))));\n    foreground = manual.html;\n    foregroundUrl = manual.url;\n  } catch (e) { firstError = firstError || cleanText(e.message); }\n  if (validPage(foreground, url, kind, foregroundUrl)) {\n    verificationPausedUntil = 0;\n    return foreground;\n  }\n  \/\/ 用户可能在前台浏览器中导航到了其他页面；只重载一次目标页。\n  if (foreground && !pageProblem(foreground)) {\n    try { var retry = await webPage(url); html = retry.html; observedUrl = retry.url; } catch (_) {}\n    if (validPage(html, url, kind, observedUrl)) return html;\n  }\n  verificationPausedUntil = Date.now() + 30000;\n  var why = pageProblem(foreground) || problem || firstError || \"返回页面与目标不符或网站结构已变化\";\n  if (!foreground) why = \"验证窗口已关闭，但没有取得正常页面；\" + why;\n  return failMyComic(\"MYCOMIC：\" + why + \"。可从书源登录或发现页重新验证。\");\n}\n\nfunction loadPage(value, kind) {\n  var url = sourceUrl(value);\n  var 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  \/\/ 应用的 WebView 和验证窗串行使用，防止并行搜索\/预读抢占页面。\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 numberedPage(value) {\n  var n = Number(value || 1);\n  if (!Number.isInteger(n) || n < 1) throw new Error(\"MYCOMIC：页码必须为正整数。\");\n  return n;\n}\n\nfunction mediaSource(url, referer) {\n  url = absoluteUrl(url, referer);\n  if (!url) return \"\";\n  \/\/ 封面是 JSON 字符串字段，没有 HTML 属性的引号冲突，须保留标准 JSON。\n  var options = JSON.stringify({headers: {\n    \"User-Agent\": mycomicUA || navigator.userAgent,\n    \"Referer\": referer || baseurl + \"\/\"\n  }});\n  return url.replace(\/\"\/g, \"%22\") + \",\" + options;\n}\n\nfunction imageResponse(value) {\n  for (var i = 0; i < 2 && 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 imageDataUri(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  \/\/ body 是官方接口规定的二进制 base64；不能使用文本字段 data 或 base64encode。\n  if (typeof response.body !== \"string\" || !response.body.trim()) {\n    throw new Error(\"原生 HTTP 缺少图片二进制 body（base64）\");\n  }\n  var body = response.body.replace(\/\\s+\/g, \"\").replace(\/-\/g, \"+\").replace(\/_\/g, \"\/\");\n  if (!\/^[A-Za-z0-9+\/]+={0,2}$\/.test(body) || body.length % 4 === 1) {\n    throw new Error(\"原生 HTTP 图片 body 不是有效 base64\");\n  }\n  while (body.length % 4) body += \"=\";\n  var signature;\n  try { signature = atob(body.slice(0, Math.min(64, body.length))); }\n  catch (_) { throw new Error(\"原生 HTTP 图片 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  if (!mime) throw new Error(\"返回内容不是 JPEG\/PNG\/GIF\/WebP 图片，可能为验证或错误页面\");\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) {\n    imageWaiters.push(resolve);\n    startWaitingImages();\n  });\n  try { return await run(); }\n  finally {\n    activeImageRequests--;\n    startWaitingImages();\n  }\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);\n  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()});\n  imageCacheCharacters += data.length;\n  while (imageCacheCharacters > imageCacheLimit || imageCache.size > imageCacheEntries) {\n    removeCachedImage(imageCache.keys().next().value);\n  }\n}\n\nfunction downloadImage(src, chapterUrl, pageIndex) {\n  var key = chapterUrl + \"\\n\" + src;\n  var saved = cachedImage(key);\n  if (saved) return Promise.resolve(saved);\n  if (pendingImages.has(key)) return pendingImages.get(key);\n  var generation = imageCacheGeneration;\n  var task = withImageSlot(function () { return requestImage(src, chapterUrl, pageIndex); }).then(function (data) {\n    rememberImage(key, data, generation);\n    pendingImages.delete(key);\n    return data;\n  }, function (error) {\n    pendingImages.delete(key);\n    throw error;\n  });\n  pendingImages.set(key, task);\n  return task;\n}\n\nasync function requestImage(src, chapterUrl, pageIndex) {\n  var headers = {\n    \"User-Agent\": mycomicUA || navigator.userAgent,\n    \"Referer\": chapterUrl,\n    \"Accept\": \"image\/webp,image\/jpeg,image\/png,image\/gif;q=0.9,*\/*;q=0.5\"\n  };\n  var schemes = preferredImageScheme === \"https2\" ? [\"https2\", \"https\"] : [\"https\", \"https2\"];\n  var lastError;\n  for (var attempt = 0; attempt < schemes.length; attempt++) {\n    var requestUrl = src.replace(\/^https:\/, schemes[attempt] + \":\");\n    try {\n      var response = imageResponse(await nativeCall(\"http\", \"get\", requestUrl, \"\",\n        JSON.stringify(headers), true, \"\"));\n      var result = imageDataUri(response);\n      preferredImageScheme = schemes[attempt];\n      return result;\n    } catch (e) {\n      lastError = e;\n      if (e.statusCode === 429 && imageConcurrency > 2) {\n        imageConcurrency = 2;\n        optionalCall(\"log\", \"MYCOMIC：图片请求被限流，本次运行自动降为 2 并发，请稍后重试。\");\n      }\n      if (e.statusCode === 429 || e.statusCode === 404 || e.statusCode >= 500 || !\/^https:\/.test(src)) break;\n    }\n  }\n  throw new Error(\"MYCOMIC：第 \" + (pageIndex + 1) + \" 张图片加载失败：\" +\n    cleanText(lastError && lastError.message) + \"。请刷新本章后重试。\");\n}\n\nasync function inlineImages(sources, chapterUrl) {\n  var lines = new Array(sources.length);\n  \/\/ 先保留本章现有缓存，避免下载缺失页时淘汰后续仍要读取的图片。\n  for (var cachedIndex = 0; cachedIndex < sources.length; cachedIndex++) {\n    var saved = cachedImage(chapterUrl + \"\\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 {\n        var data = await downloadImage(sources[index], chapterUrl, index);\n        lines[index] = '<img src=\"' + data + '\" \/>';\n      } catch (e) { stopped = true; failure = failure || e; }\n    }\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 failMyComic(failure.message);\n  return lines.join(\"\\r\\n\");\n}\n\nfunction parseBooks(html, from) {\n  var doc = parseDoc(html);\n  var books = [];\n  var seen = new Set();\n  listCards(doc).forEach(function (card) {\n    var a = Array.from(card.querySelectorAll(\"a[href]\")).find(function (el) {\n      return \/\\\/cn\\\/comics\\\/\\d+(?:[\/?#]|$)\/.test(el.getAttribute(\"href\") || \"\");\n    });\n    var url = absoluteUrl(a.getAttribute(\"href\"), from);\n    if (!url || seen.has(url)) return;\n    var img = card.querySelector(\"img\");\n    var name = cleanText(img && img.getAttribute(\"alt\") || a.getAttribute(\"title\"));\n    if (!name) return;\n    seen.add(url);\n    var badge = a.querySelector(\"div > div\");\n    books.push({\n      bookUrl: url, name: name, author: \"\", kind: \"漫画\",\n      coverUrl: mediaSource(img && (img.getAttribute(\"data-src\") || img.getAttribute(\"src\")), baseurl + \"\/\"),\n      intro: \"\", tocUrl: url, wordCount: \"\", type: 2,\n      latestChapterTitle: cleanText(badge && badge.textContent)\n    });\n  });\n  return books;\n}\n\nfunction currentPage(doc) {\n  var node = doc.querySelector('[aria-current=\"page\"]');\n  if (!node) return null;\n  var text = cleanText(node.textContent);\n  return \/^\\d+$\/.test(text) ? Number(text) : null;\n}\n\nasync function search(key, page) {\n  var p = numberedPage(page);\n  var u = new URL(baseurl + \"\/cn\/comics\");\n  u.searchParams.set(\"q\", String(key || \"\"));\n  u.searchParams.set(\"page\", p);\n  var html = await loadPage(u.href, \"list\");\n  var actual = currentPage(parseDoc(html));\n  if (actual !== null && actual !== p) return \"[]\";\n  return JSON.stringify(parseBooks(html, u.href));\n}\n\n\/\/ 从 x-data 提取 JSON 数组；不执行网站 JavaScript。\nfunction chapterGroups(doc) {\n  var groups = [];\n  Array.from(doc.querySelectorAll(\"div[x-data]\")).forEach(function (el) {\n    var init = el.getAttribute(\"x-data\") || \"\";\n    var match = \/\\bchapters\\s*:\\s*\\[\/.exec(init);\n    if (!match) return;\n    var start = match.index + match[0].length - 1;\n    var depth = 0, quoted = false, escaped = false, end = -1;\n    for (var k = start; k < init.length; k++) {\n      var c = init.charAt(k);\n      if (quoted) {\n        if (escaped) escaped = false;\n        else if (c === \"\\\\\") escaped = true;\n        else if (c === '\"') quoted = false;\n      } else if (c === '\"') quoted = true;\n      else if (c === \"[\") depth++;\n      else if (c === \"]\" && --depth === 0) { end = k + 1; break; }\n    }\n    if (end < 0) throw new Error(\"MYCOMIC：目录数据不完整，请重试。\");\n    var rows;\n    try { rows = JSON.parse(init.slice(start, end)); }\n    catch (_) { throw new Error(\"MYCOMIC：目录格式发生变化，不能解析章节数据。\"); }\n    var order = \/\\b(?:decending|descending)\\s*:\\s*(true|false)\\b\/.exec(init.slice(end));\n    var labelEl = el.querySelector(\"[data-flux-subheading] > div\");\n    groups.push({\n      rows: rows, descending: !order || order[1] === \"true\",\n      label: cleanText(labelEl && labelEl.textContent)\n    });\n  });\n  return groups;\n}\n\nfunction makeChapters(doc) {\n  var result = [], seen = new Set();\n  chapterGroups(doc).forEach(function (group) {\n    var rows = group.rows.slice();\n    if (group.descending) rows.reverse();\n    rows.forEach(function (row) {\n      if (!row || !\/^\\d+$\/.test(String(row.id || \"\"))) return;\n      var url = baseurl + \"\/cn\/chapters\/\" + row.id;\n      if (seen.has(url)) return;\n      seen.add(url);\n      result.push({\n        name: (group.label ? \"[\" + group.label + \"] \" : \"\") + cleanText(row.title || row.name),\n        chapterId: url, index: result.length,\n        isPay: false, isVip: false, isVolume: false, tag: group.label\n      });\n    });\n  });\n  return result;\n}\n\nfunction humanNames(value) {\n  return (Array.isArray(value) ? value : value ? [value] : []).map(function (item) {\n    return cleanText(typeof item === \"string\" ? item : item && item.name);\n  }).filter(Boolean).join(\"、\");\n}\n\nasync function info(bookurl) {\n  var url = sourceUrl(bookurl);\n  var html = await loadPage(url, \"detail\");\n  var doc = parseDoc(html), data = comicJson(doc);\n  var groups = chapterGroups(doc);\n  var latest = \"\";\n  for (var i = 0; i < groups.length; i++) {\n    var rows = groups[i].rows;\n    if (rows.length) {\n      latest = cleanText(rows[groups[i].descending ? 0 : rows.length - 1].title);\n      break;\n    }\n  }\n  var keywords = Array.isArray(data.keywords) ? data.keywords : data.keywords ? [data.keywords] : [];\n  var audience = humanNames(data.audience);\n  var image = data.image || data.thumbnailUrl;\n  if (Array.isArray(image)) image = image[0];\n  if (image && typeof image === \"object\") image = image.url || image.contentUrl;\n  return JSON.stringify({\n    bookUrl: url, name: cleanText(data.name), author: humanNames(data.author),\n    kind: keywords.concat(audience ? [audience] : []).map(cleanText).filter(Boolean).join(\" \/ \") || \"漫画\",\n    coverUrl: mediaSource(image, baseurl + \"\/\"),\n    intro: cleanText(data.description), tocUrl: url, wordCount: \"\", type: 2,\n    latestChapterTitle: latest\n  });\n}\n\nasync function chapter(tocUrl) {\n  var html = await loadPage(tocUrl, \"detail\");\n  var chapters;\n  try { chapters = makeChapters(parseDoc(html)); }\n  catch (e) { return failMyComic(e.message); }\n  if (!chapters.length) return failMyComic(\"MYCOMIC：没有取得完整目录，作品可能未发布章节或网页结构已变化。\");\n  return JSON.stringify(chapters);\n}\n\nasync function content(url) {\n  url = sourceUrl(url);\n  if (pendingContents.has(url)) return pendingContents.get(url);\n  var task = (async function () {\n    await initializeMyComic();\n    var started = Date.now();\n    var manifestGeneration = imageCacheGeneration;\n    var manifest = manifestCache.get(url);\n    var sources = manifest && Date.now() - manifest.time < imageCacheTTL ? manifest.sources : null;\n    if (sources) {\n      manifestCache.delete(url);\n      manifestCache.set(url, manifest);\n    } else {\n      manifestCache.delete(url);\n      var html = await loadPage(url, \"content\");\n      var images = parseDoc(html).querySelectorAll(\"img.page\");\n      sources = [];\n      var seen = new Set();\n      for (var i = 0; i < images.length; i++) {\n        var src = absoluteUrl(images[i].getAttribute(\"data-src\") || images[i].getAttribute(\"src\"), url);\n        if (!src || seen.has(src)) continue;\n        seen.add(src);\n        sources.push(src);\n      }\n      if (sources.length && manifestGeneration === imageCacheGeneration) {\n        if (manifestCache.size >= 12) manifestCache.delete(manifestCache.keys().next().value);\n        manifestCache.set(url, {sources: sources, time: Date.now()});\n      }\n    }\n    if (!sources.length) return failMyComic(\"MYCOMIC：未取得章节图片，请完成验证后重试。\");\n    var hits = sources.filter(function (src) { return !!cachedImage(url + \"\\n\" + src); }).length;\n    await optionalCall(\"log\", \"MYCOMIC \" + sourceVersion + \"：读取 \" + sources.length +\n      \" 张图片，缓存 \" + hits + \" 张，\" + imageConcurrency + \" 并发\");\n    var result;\n    try { result = await inlineImages(sources, url); }\n    catch (error) {\n      \/\/ 失败后手动刷新须重新取得图片地址；已成功图片仍可复用。\n      manifestCache.delete(url);\n      throw error;\n    }\n    await optionalCall(\"log\", \"MYCOMIC：已取得 \" + sources.length + \" 张内嵌图片，耗时 \" +\n      ((Date.now() - started) \/ 1000).toFixed(1) + \" 秒\");\n    return result;\n  })();\n  pendingContents.set(url, task);\n  try { return await task; }\n  finally { pendingContents.delete(url); }\n}\n\nasync function getfinds() {\n  await initializeMyComic();\n  var finds = [\n    {title: \"MYCOMIC 漫画\", url: \"\", js: \"\", type: 0, width: 3},\n    {title: \"最新上架\", url: baseurl + \"\/cn\/comics?page={{page}}\", js: \"\", type: 0, width: 0},\n    {title: \"最近更新\", url: baseurl + \"\/cn\/comics?sort=-update&page={{page}}\", js: \"\", type: 0, width: 0},\n    {title: \"最高人气\", url: baseurl + \"\/cn\/comics?sort=-views&page={{page}}\", js: \"\", type: 0, width: 0},\n    {title: \"CF 验证 \/ 网页登录\", url: baseurl + \"\/cn?qysg-action=verify\", js: \"\", type: 0, width: 3},\n    {title: \"图片速度：\" + imageConcurrency + \" 并发（点击切换）\", url: baseurl + \"\/cn?qysg-action=speed\", js: \"\", type: 0, width: 3},\n    {title: \"清除图片缓存\", url: baseurl + \"\/cn?qysg-action=clear-images\", js: \"\", type: 0, width: 3}\n  ];\n  var categories = [\n    [\"状态\", \"end\", [[\"连载\", \"0\"], [\"完结\", \"1\"]]],\n    [\"地区\", \"country\", [[\"日本\", \"japan\"], [\"港台\", \"hongkong\"], [\"欧美\", \"europe\"], [\"内地\", \"china\"], [\"韩国\", \"korea\"], [\"其他\", \"other\"]]],\n    [\"读者\", \"audience\", [[\"少女\", \"shaonv\"], [\"少年\", \"shaonian\"], [\"青年\", \"qingnian\"], [\"儿童\", \"ertong\"], [\"通用\", \"tongyong\"]]],\n    [\"题材\", \"tag\", [[\"魔幻\", \"mohuan\"], [\"魔法\", \"mofa\"], [\"热血\", \"rexue\"], [\"冒险\", \"maoxian\"], [\"悬疑\", \"xuanyi\"], [\"侦探\", \"zhentan\"], [\"爱情\", \"aiqing\"], [\"校园\", \"xiaoyuan\"], [\"搞笑\", \"gaoxiao\"], [\"科幻\", \"kehuan\"], [\"百合\", \"baihe\"], [\"机战\", \"jizhan\"], [\"格斗\", \"gedou\"], [\"恐怖\", \"kongbu\"], [\"历史\", \"lishi\"], [\"职场\", \"zhichang\"], [\"生活\", \"shenghuo\"], [\"治愈\", \"zhiyu\"], [\"竞技\", \"jingji\"], [\"体育\", \"tiyu\"], [\"美食\", \"meishi\"]]]\n  ];\n  categories.forEach(function (group) {\n    finds.push({title: group[0], url: \"\", js: \"\", type: 0, width: 3});\n    group[2].forEach(function (item) {\n      var u = new URL(baseurl + \"\/cn\/comics\");\n      u.searchParams.set(\"filter[\" + group[1] + \"]\", item[1]);\n      finds.push({title: item[0], url: u.href + \"&page={{page}}\", js: \"\", type: 0, width: 0});\n    });\n  });\n  return JSON.stringify(finds);\n}\n\nasync function find(url, page) {\n  var p = numberedPage(page);\n  var u = new URL(sourceUrl(String(url).replace(\/\\{\\{page\\}\\}\/g, p)));\n  \/\/ 用客户端已有的分类点击入口处理操作，避免空 URL \/ 顶层 await 的兼容问题。\n  \/\/ 操作地址只在本地分发，绝不交给网站；自动加载下一页时不重复执行操作。\n  if (u.pathname === \"\/cn\" && u.searchParams.has(\"qysg-action\")) {\n    if (p > 1) return \"[]\";\n    var action = u.searchParams.get(\"qysg-action\");\n    await initializeMyComic();\n    await optionalCall(\"log\", \"MYCOMIC \" + sourceVersion + \"：发现操作 \" + action);\n    if (action === \"verify\") await verifyMyComic();\n    else if (action === \"speed\") await cycleMyComicSpeed();\n    else if (action === \"clear-images\") await clearMyComicImageCache();\n    else throw new Error(\"MYCOMIC：未知发现操作，请刷新发现页。\");\n    return \"[]\";\n  }\n  u.searchParams.set(\"page\", p);\n  var html = await loadPage(u.href, \"list\");\n  var actual = currentPage(parseDoc(html));\n  if (actual !== null && actual !== p) return \"[]\";\n  return JSON.stringify(parseBooks(html, u.href));\n}\n\nasync function cycleMyComicSpeed() {\n  await initializeMyComic();\n  imageConcurrency = imageConcurrency === 4 ? 6 : imageConcurrency === 6 ? 2 : 4;\n  await optionalCall(\"cache.set\", imageSpeedKey, String(imageConcurrency));\n  startWaitingImages();\n  await optionalCall(\"log\", \"MYCOMIC：图片并发已切换为 \" + imageConcurrency);\n  await optionalCall(\"showToast\", \"MYCOMIC：已切换为 \" + imageConcurrency + \" 并发，刷新发现页显示新档位。\");\n  return \"\";\n}\n\nasync function clearMyComicImageCache() {\n  imageCacheGeneration++;\n  imageCache.clear();\n  imageCacheCharacters = 0;\n  manifestCache.clear();\n  await optionalCall(\"log\", \"MYCOMIC：图片缓存和章节图片地址缓存已清除\");\n  await optionalCall(\"showToast\", \"MYCOMIC：已清除本次运行的图片缓存，请刷新章节。\");\n  return \"\";\n}\n\nasync function verifyMyComic() {\n  await initializeMyComic();\n  verificationPausedUntil = 0;\n  detailCache.clear();\n  var task = requestQueue.catch(function () {}).then(async function () {\n    var url = baseurl + \"\/cn\/comics\";\n    var html = normalizeHtml(await nativeCall(\"startBrowser\", url,\n      \"MYCOMIC：完成验证后返回\", JSON.stringify(pageHeaders(url))));\n    if (!validPage(html, url, \"list\")) {\n      return failMyComic(\"MYCOMIC：验证尚未完成。请看到正常漫画列表后再返回。\");\n    }\n    await optionalCall(\"showToast\", \"MYCOMIC 验证完成，请重新搜索或刷新目录。\");\n    return \"\";\n  });\n  requestQueue = task.catch(function () {});\n  return task;\n}\n\nasync function getloginurl() {\n  verificationPausedUntil = 0;\n  detailCache.clear();\n  return baseurl + \"\/cn\";\n}\n\nasync function login() {\n  verificationPausedUntil = 0;\n  detailCache.clear();\n}\n\nasync function pay() {\n  return failMyComic(\"MYCOMIC：本源仅读取网站公开章节；需要权限的章节请在网页中登录。\");\n}\n\nasync function gethelp() {\n  return \"MYCOMIC 原生漫画源 \" + sourceVersion + \"\\n\" +\n    \"网页使用轻悦时光内置 WebView。首次使用或 CF 验证过期时，请打开书源登录，或发现页的 CF 验证。\" +\n    \"在应用内网页完成验证，确认出现正常漫画列表后返回，再重试。\\n\" +\n    \"目录按单话、单行本、番外等分组排列，每组从早到晚；正文包含所有懒加载图片。\\n\" +\n    \"图片先带章节 Referer 下载，再返回内嵌 base64；默认 4 并发，可在发现页切换 6／2／4 并发。\" +\n    \"同次运行的图片缓存有效 10 分钟，有容量上限；重开或失败重试可复用已下载图片。\" +\n    \"初次打开仍需等待本章图片下载，受网络和网站限流影响。\\n\" +\n    \"发现按钮通过分类入口执行；提示操作完成后返回，刷新发现页可更新并发档位。操作页没有书籍结果。\\n\" +\n    \"更新书源后请刷新正文或清除旧章节缓存，避免继续使用旧的图片链接。\\n\" +\n    \"如果网页显示 Sorry, you have been blocked，说明 CF 拒绝当前网络或浏览器，\" +\n    \"普通 Cookie 无法保证恢复，请稍后在同一应用内重试。外部浏览器的验证不保证共享到应用。\\n\" +\n    \"不支持自动解验证码，也不承诺绕过 Cloudflare。\";\n}\n\n\/\/ 部分客户端将脚本放在 eval\/包装函数内执行；函数声明不会自动出现在全局。\n\/\/ 显式注册应用要调用的入口，再发送 CookieJar 准备通知。\nwindow.__mycomicLoader.implementation = {\n  search: search, info: info, chapter: chapter, content: content,\n  getfinds: getfinds, find: find, getloginurl: getloginurl, login: login,\n  pay: pay, gethelp: gethelp, verifyMyComic: verifyMyComic,\n  cycleMyComicSpeed: cycleMyComicSpeed, clearMyComicImageCache: clearMyComicImageCache\n};\nwindow.__mycomicLoader.completed = true;\nwindow.mycomicSourceVersion = sourceVersion;\nwindow.addEventListener(\"flutterInAppWebViewPlatformReady\", initializeMyComic);\nif (window.flutter_inappwebview) initializeMyComic();\n})();\n<\/script>\n<\/body>\n<\/html>\n","login":true,"customButton":false,"eventListener":false,"lastUpdateTime":"1790988145227"}]