MYCOMIC漫画
https://mycomic.com/cn?qysg-source=2026.10.02.6
lonbeaubrunpjl81 (13720)10/03 08:42
该用户很懒,什么介绍也没有写!
{
"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"
}