Files
fanqie-go/bookSource.json
T
chenxin 59de8cd0b9 ✨ feat(bookSource): 聚合书源适配阅读 app 并完善评论体验
- 聚合书源取代早期书源版本:发现页筛选宫格/分类宫格、x:t:m:d: 前缀搜索、
  fqtab 类型随书切换、段评气泡(内置六种样式可配);showCmt 能力检测,魔改版
  原地弹窗(点遮罩关闭)、官方阅读降级 startBrowser;jsLib 不引用规则变量,
  searchUrl 修正编码与作用域
- 评论页:注入绝对 API_BASE,修复弹窗 WebView 内切换排序/懒加载失败;
  externalBaseURL 识别 X-Forwarded-Proto,气泡 ident 不再硬编码 http;
  顶栏固定(拖拽把手)+评论区独立滚动
- 部署:证书方案从 certbot 迁移到 acme.sh(CERT_DIR 可配置)
- 忽略 .codegraph 本地索引目录
2026-10-10 12:58:35 +08:00

63 lines
55 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
[
{
"bookSourceName": "🔅番茄聚合",
"bookSourceGroup": "📖番茄小说",
"bookSourceType": 0,
"bookSourceUrl": "https://fq.20010117.xyz",
"bookSourceComment": "番茄聚合书源(fanqie-go 后端)\n\n基础使用教程:\n1、搜索:\n 1) 基础搜索:直接搜,默认搜索当前模式内容\n 2) 快捷搜索:使用特殊关键词(x,t,m,d)搜索\n x:书名 如(x:十日终焉) 搜小说(可省略)\n t:书名 如(t:十日终焉) 搜听书\n m:书名 如(m:十日终焉) 搜漫画\n d:书名 如(d:十日终焉) 搜短剧\n 冒号支持中文英文,也可在书源设置中切换默认搜索模式\n2、发现页:\n 顶部筛选选择 类型/频道/字数/更新/排序 后,点击分类宫格浏览,下拉翻页\n3、段评:\n 阅读正文时点击段尾气泡即可查看段落评论,样式可在段评设置中调整\n4、听书/漫画/短剧:\n 听书自动用播放器播放;漫画走服务端解密代理;短剧打开章节自动唤起播放\n\n更换服务器地址:只需修改源URL(bookSourceUrl),全部规则使用相对地址自动跟随",
"bookSourceEnabled": true,
"enabledExplore": true,
"enabledCookieJar": true,
"customOrder": 0,
"weight": 0,
"respondTime": 180000,
"lastUpdateTime": 1791607295124,
"bookUrlPattern": "https?:\\/\\/(?:[a-zA-Z0-9-]+\\.)?[a-zA-Z0-9-]+(?::\\d+)?\\/detail\\?book_id=\\d+.*",
"jsLib": "// 当前书源版本号\nlet localVersion = '26.10.10';\n\n// 初始配置\nconst defaultConfig = {\n 发现页类型: \"小说\",\n 频道: \"男频\",\n 搜索模式: \"小说\"\n};\n\n// 获取源变量\nfunction getVariable(k) {\n if (k == undefined) k = \"\";\n const { source } = this;\n let parsed = {};\n try {\n parsed = JSON.parse(source.getVariable());\n } catch {}\n if (k == \"\") {\n return parsed;\n }\n let value = parsed[k];\n if (value == undefined) {\n value = defaultConfig[k];\n }\n return value != undefined ? value : \"\";\n}\n\n// 设置源变量\nfunction setVariable(k, v, t) {\n if (t == undefined) t = true;\n const { source, java } = this;\n const vs = this.getVariable();\n vs[k] = v;\n source.setVariable(JSON.stringify(vs, null, 4));\n if (k != '搜索关键词' && t) {\n java.toast(`\\n设置 ${k} 为 ${v}`)\n }\n}\n\n// 还原\nfunction deleteVariable() {\n const { source, java } = this;\n source.setVariable('');\n java.longToast('\\n所有设置已还原到初始化导入状态');\n}\n\n// 从 URL 提取内容类型标记(n小说/t听书/m漫画/d短剧)。\n// 注意:jsLib 函数作用域看不到规则变量 baseUrl,必须由调用方显式传入\nfunction fqTab(url, def) {\n if (def == undefined) def = 'n';\n let m = String(url).match(/fqtab=([ntmd])/);\n return m ? m[1] : def;\n}\n\n// 判断当前环境\nfunction checkEnv() {\n const {\n java,\n source\n } = this;\n\n try {\n java.qread();\n return \"轻阅读\";\n } catch (e) {}\n\n try {\n if (typeof java.reLoginView == 'function') {\n return \"改版\";\n }\n new Packages.io.legato.kazusa.utils.TimeoutCancellationException('');\n return \"改版\";\n } catch (e) {}\n try {\n java.deviceID();\n return \"苹果\";\n } catch (e) {}\n\n if (typeof source.loginUi == 'function') {\n return \"安卓\";\n }\n\n return \"改版\";\n}\n\n// 备用设置方案(登录页按钮)\nfunction ste(v) {\n const { java } = this;\n let moreSettings = this.getVariable(\"更多设置\") || {};\n let paraSettings = this.getVariable(\"段评设置\") || {};\n if (v == \"段评\") {\n if (paraSettings[\"段评开关\"] != \"false\") {\n paraSettings[\"段评开关\"] = \"false\";\n java.longToast(\"\\n❌段评已关闭\");\n } else {\n paraSettings[\"段评开关\"] = \"true\";\n java.longToast(\"\\n✅段评已开启\");\n }\n }\n if (v == \"小说\" || v == \"听书\" || v == \"漫画\" || v == \"短剧\") {\n moreSettings[\"搜索模式\"] = v;\n java.longToast(`\\n已设置搜索模式为 ${v}`);\n }\n if (v == \"图片\") {\n if (moreSettings[\"显示图片\"] != \"false\") {\n moreSettings[\"显示图片\"] = \"false\";\n java.longToast(\"\\n❌已关闭图片显示\");\n } else {\n moreSettings[\"显示图片\"] = \"true\";\n java.longToast(\"\\n✅已开启图片显示\");\n }\n }\n if (v == \"简介\") {\n if (moreSettings[\"完整简介\"] != \"false\") {\n moreSettings[\"完整简介\"] = \"false\";\n java.longToast(\"\\n❌已关闭完整简介\");\n } else {\n moreSettings[\"完整简介\"] = \"true\";\n java.longToast(\"\\n✅已开启完整简介\");\n }\n }\n this.setVariable(\"更多设置\", moreSettings, false);\n this.setVariable(\"段评设置\", paraSettings, false);\n}\n\n// 按钮切换时改变源变量并刷新发现\nfunction show(m, t) {\n const { java, source } = this;\n let data = {};\n try {\n data = JSON.parse(source.getVariable())\n } catch {}\n data[t] = m;\n if (t == \"发现页类型\") {\n data[\"频道\"] = '男频';\n }\n source.setVariable(JSON.stringify(data, null, 2));\n java.refreshExplore();\n}\n\nfunction createFilter(title, chars, defaultVal, paramKey, size, viewName) {\n if (viewName == undefined) viewName = \"'请选择类型'\";\n return {\n title: title,\n type: \"select\",\n chars: chars,\n default: defaultVal,\n \"viewName\": viewName,\n action: `show(infoMap['${title}'],'${paramKey}')`,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: size\n }\n };\n}\n\nfunction createText(title, action, defaultValue, size, viewName) {\n return {\n title: title,\n type: 'text',\n default: defaultValue || '',\n action: action,\n viewName: viewName || title,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: size\n }\n };\n}\n\nfunction createButton(title, action, size) {\n return {\n title: title,\n type: 'button',\n action: action,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: size\n }\n };\n}\n\nfunction exploreSearch() {\n let {java, source} = this;\n var key = this.getVariable('搜索关键词');\n var tab = this.getVariable('发现页类型') || '小说';\n if (!key) {\n try { java.toast('请输入搜索关键词'); } catch (e) {}\n return;\n }\n var tabMap = {\n '小说': 'x:',\n '漫画': 'm:',\n '听书': 't:',\n '短剧': 'd:',\n '': ''\n };\n if (['t:', 'x:', 'd:', 'm:', 't:', 'x:', 'd:', 'm:'].some(p => key.startsWith(p))) {\n tab = '';\n }\n key = `${tabMap[tab]}${key}`;\n try {\n java.searchBook(key, source.bookSourceName + \"::\" + source.getKey());\n } catch (e1) {\n try { java.searchBook(key); } catch (e2) {\n try { java.open('search', key); } catch (e3) { java.toast('搜索失败,请手动搜索'); }\n }\n }\n java.refreshExplore();\n}\n\n//去除图片\nfunction removeAllImgTags(htmlString) {\n const imgTagRegex = /<img\\b[^>]*>|<\\/img>|<img\\b[^>]*\\/>/gi;\n return htmlString.replace(imgTagRegex, '');\n}\n\nlet svgs = [\n \"<svg t='1760002253572' class='icon' viewBox='-150 0 1224 1224' version='1.1' xmlns='http://www.w3.org/2000/svg' p-id='1490' width='800' height='800'><g transform='rotate(90 512 512) scale(1 1.2)'><path d='M224 149.333333h576c40.533333 0 74.666667 34.133333 74.666667 74.666667v460.8c0 40.533333-34.133333 74.666667-74.666667 74.666667h-108.8c-40.533333 0-78.933333 17.066667-104.533334 46.933333l-49.066666 55.466667c-4.266667 6.4-14.933333 10.666667-23.466667 10.666667-10.666667 0-19.2-4.266667-25.6-10.666667l-46.933333-55.466667c-25.6-29.866667-64-46.933333-104.533334-46.933333h-110.933333c-40.533333 0-74.666667-34.133333-74.666667-74.666667V224c0-40.533333 34.133333-74.666667 74.666667-74.666667z' fill='$tccolor' p-id='1529'></path><path d='M512 938.666667c-27.733333 0-55.466667-12.8-72.533333-34.133334l-46.933334-55.466666c-14.933333-17.066667-34.133333-25.6-57.6-25.6h-110.933333c-76.8 0-138.666667-61.866667-138.666667-138.666667V224C85.333333 147.2 147.2 85.333333 224 85.333333h573.866667c76.8 0 138.666667 61.866667 138.666666 138.666667v460.8c0 76.8-61.866667 138.666667-138.666666 138.666667h-108.8c-21.333333 0-42.666667 8.533333-55.466667 25.6l-49.066667 55.466666c-17.066667 21.333333-44.8 34.133333-72.533333 34.133334zM224 149.333333C183.466667 149.333333 149.333333 183.466667 149.333333 224v460.8c0 40.533333 34.133333 74.666667 74.666667 74.666667h110.933333c40.533333 0 78.933333 17.066667 104.533334 49.066666l46.933333 55.466667c6.4 6.4 14.933333 10.666667 25.6 10.666667 8.533333 0 19.2-4.266667 23.466667-10.666667l49.066666-55.466667c25.6-29.866667 64-46.933333 104.533334-46.933333h108.8c40.533333 0 74.666667-34.133333 74.666666-74.666667V224C874.666667 183.466667 840.533333 149.333333 800 149.333333h-576z' fill='$color' p-id='1530'></path> </g><text x='460' y='650' font-family='Arial, sans-serif' text-anchor='middle' font-size='400' fill='$fontcolor'>$displayText</text></svg>\",\n\n \"<svg xmlns='http://www.w3.org/2000/svg' width='100' height='90' viewBox='0 0 25 17' fill='none' style='color: $color; opacity: 1;'><path stroke='currentColor' stroke-width='1.00' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='4.00' style='stroke: $color; stroke-opacity: 0.5; stroke-width: 1px; stroke-linejoin: round; fill: $tccolor; fill-opacity: 0.5;' d='M 7.5 0.5 L 16.5 0.5 A 7 7 0 0 1 23.5 7.5 L 23.5 7.5 A 7 7 0 0 1 16.5 14.5 L 8.5 14.5 L 5.192 16.5 L 5.5 14.5 A 7 7 30 0 1 0.5 7.5 L 0.5 7.5 A 7 7 0 0 1 7.5 0.5 Z'/><text fill='$fontcolor' font-family='Arial, sans-serif' font-size='10' font-weight='500' text-anchor='middle' dy='0.35em' x='12.00' y='7.50'>$displayText</text></svg>\",\n\n \"<svg xmlns='http://www.w3.org/2000/svg' width='100' height='90' viewBox='0 0 25 17' fill='none' style='color: $color; opacity: 1;'><path d='M24 14.5v-12a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v7.528a2 2 0 0 1-.211.894l-2.065 4.13a1 1 0 0 0 .894 1.448H22a2 2 0 0 0 2-2z' stroke='currentColor' style='stroke: $color; stroke-opacity: 0.5; stroke-width: 1px; stroke-linejoin: round; fill: $tccolor; fill-opacity: 0.5;'></path><text x='13.5' y='12' text-anchor='middle' alignment-baseline='auto' font-size='10' fill='currentColor' font-family='Arial, sans-serif' font-weight='500' style='fill: $fontcolor; opacity: 1;'>$displayText</text></svg>\",\n\n\n\n \"<svg xmlns='http://www.w3.org/2000/svg' width='100' height='90' viewBox='6 10 88 76' style='color: $color; opacity: 1;'><path d='M12,12 L88,12 Q92,12 92,16 L92,68 Q92,72 88,72 L28,72 L12,84 L12,72 Q8,72 8,68 L8,16 Q8,12 12,12 Z' stroke='currentColor' stroke-width='4' stroke-linejoin='round' style='stroke: $color; stroke-opacity: 0.5; stroke-width: 2.5px; stroke-linejoin: round; fill: $tccolor; fill-opacity: 0.5;'></path><text x='50' y='60' font-family='Arial, sans-serif' text-anchor='middle' font-size='45' fill='currentColor' font-weight='500' alignment-baseline='auto' style='fill: $fontcolor; opacity: 1;'>$displayText</text></svg>\",\n\n\n\n \"<svg xmlns='http://www.w3.org/2000/svg' width='100' height='90' viewBox='0 0 25 17' fill='none' style='color: $color; opacity: 1;'><rect x='0.5' y='0.5' width='24' height='16' rx='2.5' ry='2.5' stroke='currentColor' style='stroke: $color; stroke-opacity: 0.5; stroke-width: 1px; stroke-linejoin: round; fill: $tccolor; fill-opacity: 0.5;'></rect><text x='12.5' y='12' text-anchor='middle' alignment-baseline='auto' font-size='10' fill='currentColor' font-family='Arial, sans-serif' font-weight='500' style='fill: $fontcolor; opacity: 1;'>$displayText</text></svg>\",\n\n\n\n\n `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 216 200'>\n <path fill='#802622' d='M110 0h14l1 1h8v1h4v1h4v1h4v1h3v1h2l1 1h2l1 1h1l1 1h2v1h2v1h2v1h2v1h1v1h2v1h2v1h1v1h1l1 1h1v1h1v1h2v1h1v1h1l1 1v1h2v1h1v1h1v1l1 1h1v1h1v1h1v1h1v2h1v1h1v1h1v1h1v1h1v2h1v1h1v1l1 1v1h1v2h1v1h1v2h1v1l1 1v1l1 1v1h1v2h1v2h1v2h1v3h1v2h1v3a5 5 0 0 0 1 2h1v1h-1v4h1v4h1v5h1v8h1v18h-1v7h-1v5h-1v2h-1l-1 2v3h-1v2l-1 1v3h-1v2h-1v2h-1v2h-1v2h-1v2h-1v2h-1v1l-1 1v1h-1v2h-1v1l-1 1v1h-1v1h-1v1h-1v2h-1l-1 1v1h-1v1h-1v1h-1v1h-1v1h-1v1h-1v1l-1 1h-1v1h-1l-1 1h-1 1v-1h1v-1h1v-1h1a1 1 0 0 1 0-1 2 2 0 0 1 1-1h1v-2h1v-1h1v-1h1v-1h1v-1l1-1v-1h1v-1h1v-1l1-1h1v-2h1v-1l1-1v-1h1v-1l1-1v-1h1v-2h1v-1l1-1v-2h1v-2h1v-2h1v-2h1v-3h1v-5h1v-4l1-1v-6h1v-11h1v-2h-1v-1h1v-1h-1 1v-2h-1v-7h-1v24h-1v3h-1v1h-1v4h-1v4h-1v3h-1v2h-1v3l-1 1v1h-1v2l-1 1-1 2v1h-1v1h-1v2h-1v2h-1v1h-1v2h-1v1h-1v1h-1v1h-1v2h-1v1h-2v2h-1l-1 1v1h-2v1h-1v1h-1v1h-1v1h-1l-1 1-1 1h-2v1h-1v-1h1v-1h2v-1h1v-1h1v-1h1v-1h1v-1h1v-1l1-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1l1-1v-1h1v-1h1v-2l1-1v-1h1v-1h1v-2l1-1v-1h1v-2l1-1v-1h1l-1-1h1v-2h1v-2l1-1v-3h1v-3l1-1v-3h1v-4l1-1v-10h1V98h-1v-9h-1v-4l-1-1v-4l-1-1v-4h-1v-3h-1a6 6 0 0 0 0-3h-1v-1l-1-1v-2h-1v-2h-1v-1h-1v-2h-1v-1h-1v-1l-1-1v-1h-2v-1 2a2 2 0 0 1-1-1v-2h-1v-1h-1v-2h-1v-1l-1-1-1-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1l-1-1h-1v-1l-1-1h-1v-1h-1v-1h-1v-1h-1v-1h-1l-1-1v-1h-2v-1h-1v-1h-1v-1h-2v-1a1 1 0 0 1-1 0v-1h-2v-1h-1l-1-1h-1v-1h-2v-1h-2v-1h-2v-1h-1v-1h-4v-1h-1v-1h-2l-1-1h-2v-1h-5v-1a1 1 0 0 1-1-1v1h-1v-1 1h-2l-1-1h-3v-1h-23 1-2v1h-1v-1h-2v1h-4v1h-2v2l1-1h4v-1h4v-1h23v1h6v1h4v1h4v1h3v1h3v1h2l1 1h2l1 1 1 1h1v1h1l2 1h2v1h1v1-1 1h2v1h1v1h2v1h1v1h1v1h1v1h1v1h1l1 1h1v1h1v2h1l1 1 1 1v1h1v1h1v1h1v2l1 1v1h1v1h1v1h1v1h1v2l1 1v1h1v2h1v1l1 1v1h1v2l1 1v2h1v2h1v1l1 2v3h1v4l1 1v5h1v9l1 1v6h-1v8h-1v1-16h-1v-2h1v-2h-1v-5h-1v-4h-1v-3l-1-1v-3a2 2 0 0 1-1-1v-2l-1-1v-1h-1v-3h-1v-1h-1v-2l-1-1v3h1v3h1v4h1v3l1 1v3h1v5h1v25h-1v6h-1v4l-1 1v2h-1v2h-1v3h-1v2h-1v2l-1 1v1h-1v2h-1v2h-1v2h-1v1h-1v2l-1 1-1 1v1h-1v1h-1v2h-1l-1 1v1h-1v1h-1v1h-1v1h-1v1l-1 1-1 1h-1v1h-1v1h-1v1h-1l-1 1h-1v1h-1v-1h1v-1h1v-1l1-1h1v-1l2-1v-1h1v-1l1-1v-1h1v-1h1v-1h1v-2h1v-1l1-1v-1h1v-1l1-1v-2h1v-2h1v-1l1-1v-1h1v-3h1v-2h1v-1h1v-2l1-1v-2h1v-3h1v-5h1v-5h1v5-21h-1v-6h-1v-5h-1v-3h-1v-3h-1v-2h-1v-3h-1v-1l-1-1v-2h-1v-1l-1-3h-1v-1h-1a1 1 0 0 1 0-1v-1h-1v-2h-1v-1h-1v-1l-1-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1l-1-1v-1h-2v-1h-1v-1h-1v-1h-1l-1-1h-1v-1h-1v-1h-1l-1-1h-1v-1h-2v-1h-2v-1h-1v-1h-2a2 2 0 0 0-1-1h-1v-1h-2l-1-1h-2v-1h-3v-1h-2l-2-1h-2v-1h-5v-1h-2 3-22v1h-5v1h-4v1h-2l-2 1h-2v1h-2l-1 1h-2v1h-1l-1 1h-1v1h-2v1h-1l-1 1h-1v1h-1l-1 1h-1v1h-1v1h-1l-1 1-1 1-1 1h-1v1h-1v1h-1v1h-1v1h-1v1h-1v2h-1v1h-1v1h-1v1h-1v2h-1v1h-1v1h-1v2h-1v1l-1 1v1h-1v2h-1v2h-1v2h-1v3h-1v3h-1v3l-1 1v3l-1 1v6h-1v12h1v5l1 3v3h1v4h1v3h1v3h1v3h1v2l1 1v1h1v2l1 2v1h1v1l1 1v1h1v1h1v2h1v1h1v2h1v1h1v1h1v1h1v2h1v1h1l1 1h1v1h1v1l1 1h1v1h2v1h1v1h1v1h2v1h2v1h1v1h1v1h2v1h7v1h1v2h1v2h-4l-3-1v1h1a3 3 0 0 1 2 1h1v1-1 1h1v-1h1v2h5v1h1v1h1v1h2v1h-2l1 1v1h-1 1v3h-1v2h-2 1v1h1v1h-1v2h-1 1v3h-1l-1 1h-1v1h-3v1h-2v-1h-1l-1-1h-1v-1 1h-5v1h-4v-1l-1-1h-1v1h-2v-1h-1v-1h-1v2h2v1h2v1h2l1 1h2v1h1v1h2v1h1v1h5v1h1v1h1v1l-1 1h-4v-1h-2v-1h-3v-1h-5v-1h-1v-1h-1v1h-1a1 1 0 0 1 0-1l1-1h1-1v-1h-2a2 2 0 0 0-1-1h-1v-1h-2v-1h-2v-1h-3v-1h-1v-1h-2v-1h-1v-1h-1v-1h-2v-1h-2v-1h-1v-1h-1v-1h-1v-1h-2v-1a2 2 0 0 1-1-1h-1v-1h-1v-1h-1v-1h-1v-1h-1l-1-1v-1h-1v-1h-1v-1h-1a1 1 0 0 1 0-1h-1v-1l-1-1-1-1v-1h-1v-1h-1v-1h-1v-1h-1v-2h-1v-1h-1v-1l-1-1v-1h-1v-2h-1v-1h-1v-2h-1v-1l-1-1v-1h-1v-2h-1v-1h-2v-2a1 1 0 0 1-1 0v-2h-1v-1h-1l-1 1v1h-1v-1h-1l-1-2h1v-5h-1v-4a2 2 0 0 1-1-1v-3H9v-3a2 2 0 0 1-1-1v-2H7v-3H6v-7H5v-1h1v-1h1v-5l-1-2v-7l1-2v-6h1v-1H7h2v1H8v8H7v9h1V91h1v-7h1v-4h1v-1a3 3 0 0 1 0-2h-1v-1h1l1-1-1-1h1v-2h1v-3h1v-6h1v-2h1v-1 1-1h1-1v-1h1v-1h1v-1h-1v-1h1v-1h1-1v-1h1v-2h1v-2h1v-1h-1v2h-1v1h-1v2h-1v-1l1-1v-2h1v-1h1v-1l1-1v-2h1v-1h1v-3h1v-1h2v-1h1v-1h1v-2h1v-1h1v-1h1v-2h1l1-1v-1h1v-1h1v-2h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1l1-1h1v-1h1v-1h1v-1h1v-1h1l1-1h1v-1h1l1-1 1-1v1-1h1V8h2V7h1l1-1h1V5h2V4h1l1-1h2V2h2V1h2V0h1v1h-1l-1 1h-1v1h-2v1h-3v1h-1v1h-2v1h-1v1h-2v1h-2v1h-1v1h-1l-1 1-1 1h-1l-1 1h-1v1h-1v1h-1v1h-1v1h-1v1h-1l-1 1h-1v2h-1l-1 1v1h-2v1h-1v2h-1v1h-1v1h-1v1h-1v1l-1 1v1h-1v1h-1v1l-1 1v1h-1v2h-1v1h-1v2h-1v1h1v1h-1v1l-1 1h-1v1h-1v2l-1 1v2h-1v2h-1v2h-1v2h-1v3h-1v3h-1v3h-1v2l-1 1v3h-1v4H9v6H8v6H7v11-5h1v-3l1-2v-3h1v-4h1v-5l1-1v-3h1v-4h1v-3h1v-3h1v-3l1-2h1v-3h1v-1l1-1v-2h1v-1h1v-2h1v-1h1v-1l1-1v-1l1-1v-1h1v-1h1v-1h1v-1h1v-2h1v-1h1v-1h1v-1h1v-1h1v-2h1v-1h1l1-1h1v-1l1-1 1-1h1v-1h1v-1h1v-1h1v-1h1v-1h2v-1h1v-1h1v-1h2v-1h1l1-1v-1h2v-1h1l1-1h1v-1h2v-1h2v-1h2v-1h1l2-1h1V8h2V7h3V6h3V4h6V3h4V2h4l1-1h7V0h8z'/>\n <text x=\"115\" y=\"130\" font-family=\"Arial, sans-serif\" text-anchor=\"middle\" font-size=\"80\" font-weight=\"700\" fill='#802632'>$displayText</text>\n</svg>`\n\n];\n\n// 创建svg\nfunction createSvg(number, encodedUrl, sources) {\n var displayText = number > 99 ? \"99+\" : number.toString();\n let data = this.getVariable('段评设置');\n let svgstyle = data['段评样式'] || \"0\";\n let color = data['段评边框颜色'] || \"#A7A7A7\";\n let tccolor = data['段评填充颜色'] || \"transparent\";\n let fontcolor = data['段评字体颜色'] || \"#A7A7A7\";\n let zdy = svgs[0];\n try {\n zdy = data['自定义段评样式'][svgstyle] || svgs[0];\n } catch {}\n const styleMap = {\n \"0\": svgs[0],\n \"1\": svgs[1],\n \"2\": svgs[2],\n \"3\": svgs[3],\n \"4\": svgs[4],\n \"5\": svgs[5],\n };\n let svg = styleMap[svgstyle] || zdy;\n svg = svg.replace('$color', color)\n .replace('$tccolor', tccolor)\n .replace('$fontcolor', fontcolor)\n .replace('$displayText', displayText);\n var jc = 'js';\n let islyc = this.checkEnv()\n if (islyc == \"改版\") {\n jc = 'click'\n }\n let style = data['气泡增大'] == \"true\" ? 'TEXT' : 'text';\n var encodedSvg = this.java.base64Encode(svg);\n return `data:image/svg+xml;base64,${encodedSvg},{\"${jc}\":\"showCmt('${encodedUrl}', '${sources}' ,'段评')\",\"style\":\"${style}\"}`;\n}\n\n\n// 段评气泡注入(正文 <p> 支持任意属性)\nfunction paraForAndroid(content, sources) {\n let { cache } = this;\n const createSvg = this.createSvg.bind(this);\n return content.replace(/(<p[^>]*>)([\\s\\S]*?)(?:<comment ident=\"([^\"]*)\" count=\"([^\"]*)\"\\s*\\/>)?(<\\/p>)/g,\n function (match, open, text, url, count, close) {\n if (url && count) {\n cache.putMemory(url, 0);\n return `<p>${text}<img src=\"${createSvg(count, url, sources)}\"></p>`;\n }\n return `<p>${text}</p>`;\n });\n}\n\n//源阅(iOS)段评\nfunction paraForiOS(content, sources) {\n return content.replace(/(<p[^>]*>)([\\s\\S]*?)(?:<comment ident=\"([^\"]*)\" count=\"([^\"]*)\"\\s*\\/>)?(<\\/p>)/g,\n function (match, open, text, url, count, close) {\n if (url && count) {\n const numCount = Number(count);\n const displayCount = !isNaN(numCount) && numCount > 99 ? '99' : count;\n return `<div rs-native>${text}<comment count=\"${displayCount}\" onPress=\"java.showReadingBrowser('${url.replace(/&/g, '&amp;')}','${sources}段评')\"></div>`;\n }\n return `<p>${text}</p>`;\n });\n}\n\n// 显示段评:优先魔改版(阅读R/Sigma等)的原地底部弹窗(点击遮罩关闭、可拖拽下滑);\n// 官方版无此 API,自动降级为 startBrowser 打开自包含评论页\nfunction showCmt(url, sources, ident) {\n let { java } = this;\n if (ident == null) ident = '段评';\n var title = (sources || '番茄') + ident;\n var hasPopup = false;\n try { if (typeof java.showBrowser == 'function') hasPopup = true; } catch (e) {}\n if (hasPopup) {\n try {\n let html = '';\n // 正文注入的 ident 可能是 http://(反代 301 到 https),ajax 对 301 处理不稳,强制 https\n try { html = String(java.ajax(url.replace(/^http:\\/\\//, 'https://'))); } catch (e) {}\n java.showBrowser(url, html, 'window.java=java;', JSON.stringify({\n expandedCornersRadius: 20,\n dismissOnTouchOutside: true,\n isDraggable: true,\n shouldDimBackground: true,\n backgroundDimAmount: 0.5,\n hardwareAccelerated: true,\n isNestedScrollingEnabled: true,\n setFitToContents: true,\n isHideable: true,\n heightPercentage: 0.8\n }));\n return;\n } catch (e) {}\n }\n try {\n java.startBrowser(url, title);\n return;\n } catch (e) {}\n try { java.startBrowserDp(url, title); } catch (e2) { java.toast('当前版本不支持查看评论'); }\n}\n\n// ============ 书源设置页 ============\nfunction getHtmlSettings() {\n const { java } = this;\n let data = this.getVariable('更多设置');\n let 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,maximum-scale=1,user-scalable=no\">\n<title>番茄聚合 - 书源设置</title>\n<style>\n* { margin:0; padding:0; box-sizing:border-box; -webkit-tap-highlight-color:transparent; }\n:root {\n --color-primary:#2D9D78; --color-primary-light:#3DB893;\n --bg-card:rgba(255,255,255,.98); --bg-elevated:#fafbfc;\n --text-primary:#1a1d23; --text-secondary:#6b7280;\n --border-color:rgba(0,0,0,.08);\n}\nhtml,body { width:100%; min-height:100vh; font-family:-apple-system,BlinkMacSystemFont,sans-serif; font-size:16px; color:var(--text-primary); background:linear-gradient(135deg,#eef2ff,#f5f7ff); }\n.container { padding:16px; max-width:600px; margin:0 auto; }\n.main-card { background:var(--bg-card); border-radius:20px; box-shadow:0 4px 24px rgba(0,0,0,.1); border:1px solid var(--border-color); overflow:hidden; }\n.header { padding:20px 16px; background:linear-gradient(135deg,var(--color-primary),var(--color-primary-light)); text-align:center; }\n.header-title { font-size:18px; font-weight:700; color:#fff; margin-bottom:4px; }\n.header-desc { font-size:12px; color:rgba(255,255,255,.8); }\n.tip-alert { background:linear-gradient(135deg,rgba(255,193,7,.12),rgba(255,152,0,.08)); border:1px solid rgba(255,193,7,.25); border-radius:16px; padding:14px 16px; margin:16px; font-size:13px; color:var(--text-secondary); line-height:1.6; }\n.tip-alert .highlight-text { color:#ea580c; font-weight:700; }\n.setting-item { display:flex; align-items:center; justify-content:space-between; padding:16px; border-bottom:1px solid var(--border-color); }\n.setting-item:last-child { border-bottom:none; }\n.setting-title { font-size:15px; font-weight:700; margin-bottom:4px; }\n.setting-desc { font-size:12px; color:var(--text-secondary); line-height:1.4; }\n.switch { position:relative; display:inline-block; width:48px; height:28px; flex-shrink:0; }\n.switch input { opacity:0; width:0; height:0; }\n.slider { position:absolute; cursor:pointer; top:0; left:0; right:0; bottom:0; background-color:#ccc; transition:.3s; border-radius:28px; }\n.slider:before { position:absolute; content:\"\"; height:22px; width:22px; left:3px; bottom:3px; background-color:#fff; transition:.3s; border-radius:50%; box-shadow:0 2px 4px rgba(0,0,0,.2); }\ninput:checked + .slider { background:linear-gradient(135deg,var(--color-primary),var(--color-primary-light)); }\ninput:checked + .slider:before { transform:translateX(20px); }\n.select-wrapper select { appearance:none; -webkit-appearance:none; padding:8px 32px 8px 12px; font-size:13px; font-weight:600; color:var(--text-primary); background:var(--bg-elevated); border:1px solid var(--border-color); border-radius:8px; cursor:pointer; min-width:110px; }\n</style>\n</head>\n<body>\n<div class=\"container\">\n <div class=\"main-card\">\n <div class=\"header\">\n <div class=\"header-title\">🍅 番茄聚合 · 书源设置</div>\n <div class=\"header-desc\">数据服务由 fanqie-go 提供</div>\n </div>\n <div class=\"tip-alert\">\n <p>设置完成后请点击右上角的<span class=\"highlight-text\">√</span>应用设置</p>\n </div>\n <div class=\"setting-item\">\n <div>\n <div class=\"setting-title\">搜索模式</div>\n <div class=\"setting-desc\">切换后搜索对应类型,也可以在搜索时使用关键词:t:书名 搜听书,d:书名 搜短剧,m:书名 搜漫画</div>\n </div>\n <div class=\"select-wrapper\">\n <select id=\"searchMode\">\n <option value=\"小说\">小说</option>\n <option value=\"听书\">听书</option>\n <option value=\"短剧\">短剧</option>\n <option value=\"漫画\">漫画</option>\n </select>\n </div>\n </div>\n <div class=\"setting-item\">\n <div>\n <div class=\"setting-title\">显示图片</div>\n <div class=\"setting-desc\">关闭后,文章内的图片将会清除</div>\n </div>\n <label class=\"switch\"><input type=\"checkbox\" id=\"showImageSwitch\" checked><span class=\"slider\"></span></label>\n </div>\n <div class=\"setting-item\">\n <div>\n <div class=\"setting-title\">完整简介</div>\n <div class=\"setting-desc\">关闭后,书籍详情页会精简简介内容</div>\n </div>\n <label class=\"switch\"><input type=\"checkbox\" id=\"fullDescSwitch\" checked><span class=\"slider\"></span></label>\n </div>\n <div id=\"hiddenSettings\" style=\"display:none\">\n <span id=\"searchModeValue\">${(data && data.搜索模式) || '小说'}</span>\n <span id=\"showImageSwitchValue\">${(data && data.显示图片) || 'true'}</span>\n <span id=\"fullDescSwitchValue\">${(data && data.完整简介) || 'true'}</span>\n </div>\n </div>\n</div>\n<script>\nlet settings = {\n '搜索模式': '${(data && data.搜索模式) || '小说'}',\n '显示图片': '${(data && data.显示图片) || 'true'}',\n '完整简介': '${(data && data.完整简介) || 'true'}'\n};\ndocument.getElementById('searchMode').addEventListener('change', e => { settings['搜索模式'] = e.target.value; update(); });\ndocument.getElementById('showImageSwitch').addEventListener('change', e => { settings['显示图片'] = e.target.checked ? 'true' : 'false'; update(); });\ndocument.getElementById('fullDescSwitch').addEventListener('change', e => { settings['完整简介'] = e.target.checked ? 'true' : 'false'; update(); });\ndocument.getElementById('searchMode').value = settings['搜索模式'];\ndocument.getElementById('showImageSwitch').checked = settings['显示图片'] != 'false';\ndocument.getElementById('fullDescSwitch').checked = settings['完整简介'] != 'false';\nfunction update() {\n document.getElementById('searchModeValue').textContent = settings['搜索模式'];\n document.getElementById('showImageSwitchValue').textContent = settings['显示图片'];\n document.getElementById('fullDescSwitchValue').textContent = settings['完整简介'];\n}\n</script>\n</body>\n</html>`;\n let body;\n try {\n body = java.startBrowserAwait(`data:text/html;base64,${java.base64Encode(html)}`, '番茄聚合书源设置', false).body();\n } catch (e) {\n java.longToast(\"\\n请点击页面右上角√才能设置成功哦~\");\n return;\n }\n let settings = {};\n try {\n const getSpanText = (spanId) => {\n const regex = new RegExp('id=\"' + spanId + '\"\\s*>\\s*([^<]*?)\\s*<\\/span>');\n const match = body.match(regex);\n return match ? match[1].trim() : \"\";\n };\n settings = {\n '搜索模式': getSpanText('searchModeValue') || '小说',\n '显示图片': getSpanText('showImageSwitchValue') || 'true',\n '完整简介': getSpanText('fullDescSwitchValue') || 'true',\n };\n const settingsText = Object.keys(settings).map(key => `${key}: ${settings[key]}`).join('\\n');\n java.longToast(\"\\n\" + settingsText);\n } catch (e) {}\n this.setVariable(\"更多设置\", settings, false);\n}\n\n// ============ 段评设置页 ============\nfunction getSvgSettings() {\n const { java } = this;\n const islyc = this.checkEnv();\n if (islyc == \"苹果\") {\n java.longToast(\"\\n当前软件不支持设置段评样式\");\n return;\n }\n let data = this.getVariable('段评设置');\n let 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,maximum-scale=1,user-scalable=no\">\n<title>番茄聚合 - 段评设置</title>\n<style>\n* { margin:0; padding:0; box-sizing:border-box; -webkit-tap-highlight-color:transparent; }\n:root {\n --color-primary:#2D9D78; --color-primary-light:#3DB893;\n --bg-card:rgba(255,255,255,.98); --bg-elevated:#fafbfc;\n --text-primary:#1a1d23; --text-secondary:#6b7280;\n --border-color:rgba(0,0,0,.08);\n}\nhtml,body { width:100%; min-height:100vh; font-family:-apple-system,BlinkMacSystemFont,sans-serif; font-size:16px; color:var(--text-primary); background:linear-gradient(135deg,#eef2ff,#f5f7ff); }\n.container { padding:16px; max-width:600px; margin:0 auto; }\n.main-card { background:var(--bg-card); border-radius:20px; box-shadow:0 4px 24px rgba(0,0,0,.1); border:1px solid var(--border-color); overflow:hidden; }\n.header { padding:20px 16px; background:linear-gradient(135deg,var(--color-primary),var(--color-primary-light)); text-align:center; }\n.header-title { font-size:18px; font-weight:700; color:#fff; margin-bottom:4px; }\n.header-desc { font-size:12px; color:rgba(255,255,255,.8); }\n.tip-alert { background:linear-gradient(135deg,rgba(255,193,7,.12),rgba(255,152,0,.08)); border:1px solid rgba(255,193,7,.25); border-radius:16px; padding:14px 16px; margin:16px; font-size:13px; color:var(--text-secondary); line-height:1.6; }\n.tip-alert .highlight-text { color:#ea580c; font-weight:700; }\n.setting-item { display:flex; align-items:center; justify-content:space-between; padding:16px; border-bottom:1px solid var(--border-color); }\n.setting-title { font-size:15px; font-weight:700; margin-bottom:4px; }\n.setting-desc { font-size:12px; color:var(--text-secondary); line-height:1.4; }\n.switch { position:relative; display:inline-block; width:48px; height:28px; flex-shrink:0; }\n.switch input { opacity:0; width:0; height:0; }\n.slider { position:absolute; cursor:pointer; top:0; left:0; right:0; bottom:0; background-color:#ccc; transition:.3s; border-radius:28px; }\n.slider:before { position:absolute; content:\"\"; height:22px; width:22px; left:3px; bottom:3px; background-color:#fff; transition:.3s; border-radius:50%; box-shadow:0 2px 4px rgba(0,0,0,.2); }\ninput:checked + .slider { background:linear-gradient(135deg,var(--color-primary),var(--color-primary-light)); }\ninput:checked + .slider:before { transform:translateX(20px); }\n.select-wrapper select { appearance:none; -webkit-appearance:none; padding:8px 32px 8px 12px; font-size:13px; font-weight:600; color:var(--text-primary); background:var(--bg-elevated); border:1px solid var(--border-color); border-radius:8px; cursor:pointer; min-width:130px; }\n.color-picker-wrapper { display:flex; align-items:center; gap:8px; flex-shrink:0; }\n.color-input { width:40px; height:40px; border:none; border-radius:8px; cursor:pointer; background:transparent; padding:2px; }\n.color-text-input { width:100px; padding:8px 10px; font-size:12px; font-weight:600; color:var(--text-primary); background:var(--bg-elevated); border:1px solid var(--border-color); border-radius:6px; }\n.transparent-btn { padding:8px 12px; font-size:11px; font-weight:600; color:var(--color-primary); background:rgba(45,157,120,.1); border:1px solid var(--color-primary); border-radius:6px; cursor:pointer; }\n.preview-section { padding:20px; background:var(--bg-elevated); border-top:1px solid var(--border-color); }\n.preview-title { font-size:14px; font-weight:700; margin-bottom:16px; }\n.preview-container { display:flex; flex-wrap:wrap; gap:16px; justify-content:center; align-items:center; min-height:120px; }\n.preview-item { display:flex; flex-direction:column; align-items:center; gap:8px; padding:12px; background:var(--bg-card); border-radius:12px; border:2px solid transparent; cursor:pointer; }\n.preview-item.selected { border-color:var(--color-primary); background:rgba(45,157,120,.05); }\n.preview-item svg { width:60px; height:54px; }\n.preview-label { font-size:11px; color:var(--text-secondary); }\n.preview-item.selected .preview-label { color:var(--color-primary); font-weight:600; }\n</style>\n</head>\n<body>\n<div class=\"container\">\n <div class=\"main-card\">\n <div class=\"header\">\n <div class=\"header-title\">🗨 番茄聚合 · 段评设置</div>\n <div class=\"header-desc\">点击正文段尾气泡查看段落评论</div>\n </div>\n <div class=\"tip-alert\">\n <p>设置完成后请点击右上角的<span class=\"highlight-text\">√</span>应用设置</p>\n </div>\n <div class=\"setting-item\">\n <div>\n <div class=\"setting-title\">段评</div>\n <div class=\"setting-desc\">阅读正文时在段落末尾显示评论气泡</div>\n </div>\n <label class=\"switch\"><input type=\"checkbox\" id=\"duanpingSwitch\" checked><span class=\"slider\"></span></label>\n </div>\n <div class=\"setting-item\">\n <div>\n <div class=\"setting-title\">气泡增大</div>\n <div class=\"setting-desc\">开启后,气泡显示会加大一些</div>\n </div>\n <label class=\"switch\"><input type=\"checkbox\" id=\"bubbleLargeSwitch\"><span class=\"slider\"></span></label>\n </div>\n <div class=\"setting-item\">\n <div>\n <div class=\"setting-title\">边框颜色</div>\n <div class=\"setting-desc\">设置段评边框颜色</div>\n </div>\n <div class=\"color-picker-wrapper\">\n <input type=\"color\" id=\"borderColor\" value=\"#A7A7A7\" class=\"color-input\">\n <input type=\"text\" id=\"borderColorText\" value=\"#A7A7A7\" class=\"color-text-input\">\n </div>\n </div>\n <div class=\"setting-item\">\n <div>\n <div class=\"setting-title\">字体颜色</div>\n <div class=\"setting-desc\">设置段评字体颜色</div>\n </div>\n <div class=\"color-picker-wrapper\">\n <input type=\"color\" id=\"fontColor\" value=\"#A7A7A7\" class=\"color-input\">\n <input type=\"text\" id=\"fontColorText\" value=\"#A7A7A7\" class=\"color-text-input\">\n </div>\n </div>\n <div class=\"setting-item\">\n <div>\n <div class=\"setting-title\">填充颜色</div>\n <div class=\"setting-desc\">设置段评背景填充颜色</div>\n </div>\n <div class=\"color-picker-wrapper\">\n <input type=\"color\" id=\"fillColor\" value=\"#FFFFFF\" class=\"color-input\">\n <input type=\"text\" id=\"fillColorText\" value=\"transparent\" class=\"color-text-input\">\n <button id=\"fillColorTransparent\" class=\"transparent-btn\">透明</button>\n </div>\n </div>\n <div class=\"preview-section\">\n <div class=\"preview-title\">实时预览(点击选择样式)</div>\n <div id=\"previewContainer\" class=\"preview-container\"></div>\n </div>\n <div id=\"hiddenSettings\" style=\"display:none\">\n <span id=\"duanpingSwitchValue\">${(data && data.段评开关) || 'true'}</span>\n <span id=\"bubbleLargeSwitchValue\">${(data && data.气泡增大) || 'false'}</span>\n <span id=\"duanpingStyleValue\">${(data && data.段评样式) || '0'}</span>\n <span id=\"borderColorValue\">${(data && data.段评边框颜色) || '#A7A7A7'}</span>\n <span id=\"fontColorValue\">${(data && data.段评字体颜色) || '#A7A7A7'}</span>\n <span id=\"fillColorValue\">${(data && data.段评填充颜色) || 'transparent'}</span>\n </div>\n </div>\n</div>\n<script>\nconst SVG_TEMPLATES = ${JSON.stringify(svgs)};\nconst STYLE_NAMES = ['起点样式', '番茄样式', '精致样式', '对话样式', '方框样式', '圆圈样式'];\nlet settings = {\n '段评开关': '${(data && data.段评开关) || 'true'}',\n '气泡增大': '${(data && data.气泡增大) || 'false'}',\n '段评样式': '${(data && data.段评样式) || '0'}',\n '段评边框颜色': '${(data && data.段评边框颜色) || '#A7A7A7'}',\n '段评字体颜色': '${(data && data.段评字体颜色) || '#A7A7A7'}',\n '段评填充颜色': '${(data && data.段评填充颜色) || 'transparent'}'\n};\nfunction update() {\n document.getElementById('duanpingSwitchValue').textContent = settings['段评开关'];\n document.getElementById('bubbleLargeSwitchValue').textContent = settings['气泡增大'];\n document.getElementById('duanpingStyleValue').textContent = settings['段评样式'];\n document.getElementById('borderColorValue').textContent = settings['段评边框颜色'];\n document.getElementById('fontColorValue').textContent = settings['段评字体颜色'];\n document.getElementById('fillColorValue').textContent = settings['段评填充颜色'];\n}\nfunction renderPreview() {\n const container = document.getElementById('previewContainer');\n container.innerHTML = '';\n SVG_TEMPLATES.forEach((tpl, index) => {\n const content = tpl.split('$color').join(settings['段评边框颜色'])\n .split('$fontcolor').join(settings['段评字体颜色'])\n .split('$tccolor').join(settings['段评填充颜色'])\n .split('$displayText').join('99+');\n const item = document.createElement('div');\n item.className = 'preview-item ' + (settings['段评样式'] === String(index) ? 'selected' : '');\n item.innerHTML = content + '<div class=\"preview-label\">' + STYLE_NAMES[index] + '</div>';\n item.addEventListener('click', () => {\n settings['段评样式'] = String(index);\n update(); renderPreview();\n });\n container.appendChild(item);\n });\n}\ndocument.getElementById('duanpingSwitch').addEventListener('change', e => { settings['段评开关'] = e.target.checked ? 'true' : 'false'; update(); });\ndocument.getElementById('bubbleLargeSwitch').addEventListener('change', e => { settings['气泡增大'] = e.target.checked ? 'true' : 'false'; update(); });\ndocument.getElementById('borderColor').addEventListener('input', e => { settings['段评边框颜色'] = e.target.value; document.getElementById('borderColorText').value = e.target.value; update(); renderPreview(); });\ndocument.getElementById('borderColorText').addEventListener('input', e => { if (/^#[0-9A-Fa-f]{6}$/.test(e.target.value)) { settings['段评边框颜色'] = e.target.value; document.getElementById('borderColor').value = e.target.value; update(); renderPreview(); } });\ndocument.getElementById('fontColor').addEventListener('input', e => { settings['段评字体颜色'] = e.target.value; document.getElementById('fontColorText').value = e.target.value; update(); renderPreview(); });\ndocument.getElementById('fontColorText').addEventListener('input', e => { if (/^#[0-9A-Fa-f]{6}$/.test(e.target.value)) { settings['段评字体颜色'] = e.target.value; document.getElementById('fontColor').value = e.target.value; update(); renderPreview(); } });\ndocument.getElementById('fillColor').addEventListener('input', e => { settings['段评填充颜色'] = e.target.value; document.getElementById('fillColorText').value = e.target.value; update(); renderPreview(); });\ndocument.getElementById('fillColorText').addEventListener('input', e => { if (e.target.value.toLowerCase() == 'transparent' || /^#[0-9A-Fa-f]{6}$/.test(e.target.value)) { settings['段评填充颜色'] = e.target.value; update(); renderPreview(); } });\ndocument.getElementById('fillColorTransparent').addEventListener('click', () => { settings['段评填充颜色'] = 'transparent'; document.getElementById('fillColorText').value = 'transparent'; document.getElementById('fillColor').value = '#FFFFFF'; update(); renderPreview(); });\ndocument.getElementById('duanpingSwitch').checked = settings['段评开关'] != 'false';\ndocument.getElementById('bubbleLargeSwitch').checked = settings['气泡增大'] == 'true';\nrenderPreview();\n</script>\n</body>\n</html>`;\n let body;\n try {\n body = java.startBrowserAwait(`data:text/html;base64,${java.base64Encode(html)}`, '番茄聚合段评设置', false).body();\n } catch (e) {\n if (islyc == \"安卓\") {\n java.longToast(\"\\n当前软件版本过低,请更新软件,或者使用底部备用设置方案~\");\n } else {\n java.longToast(\"\\n请点击页面右上角√才能设置成功哦~\");\n }\n return;\n }\n let settings = {};\n try {\n const getSpanText = (spanId) => {\n const regex = new RegExp('id=\"' + spanId + '\"\\s*>\\s*([^<]*?)\\s*<\\/span>');\n const match = body.match(regex);\n return match ? match[1].trim() : \"\";\n };\n settings = {\n '段评开关': getSpanText('duanpingSwitchValue') || 'true',\n '气泡增大': getSpanText('bubbleLargeSwitchValue') || 'false',\n '段评样式': getSpanText('duanpingStyleValue') || '0',\n '段评边框颜色': getSpanText('borderColorValue') || '#A7A7A7',\n '段评字体颜色': getSpanText('fontColorValue') || '#A7A7A7',\n '段评填充颜色': getSpanText('fillColorValue') || 'transparent',\n };\n const settingsText = Object.keys(settings).map(key => `${key}: ${settings[key]}`).join('\\n');\n java.longToast(\"\\n\" + settingsText);\n } catch (e) {\n settings = {\n '段评开关': 'true',\n '气泡增大': 'false',\n '段评样式': '0',\n '段评边框颜色': '#A7A7A7',\n '段评字体颜色': '#A7A7A7',\n '段评填充颜色': 'transparent'\n };\n }\n this.setVariable(\"段评设置\", settings, false);\n}\n",
"loginUi": "[\n {\n \"name\": \"⚙️ 书源设置\",\n \"type\": \"button\",\n \"action\": \"getHtmlSettings()\",\n \"style\": { \"layout_flexGrow\": 1, \"layout_flexBasisPercent\": 0.4 }\n }, {\n \"name\": \"🗨 段评设置\",\n \"type\": \"button\",\n \"action\": \"getSvgSettings()\",\n \"style\": { \"layout_flexGrow\": 1, \"layout_flexBasisPercent\": 0.4 }\n }, {\n \"name\": \"===下方为备用设置方案===\",\n \"type\": \"button\",\n \"action\": \"\",\n \"style\": { \"layout_flexGrow\": 1, \"layout_flexBasisPercent\": 1 }\n }, {\n \"name\": \"📖搜索小说\",\n \"type\": \"button\",\n \"action\": \"ste('小说')\",\n \"style\": { \"layout_flexGrow\": 1, \"layout_flexBasisPercent\": 0.4 }\n }, {\n \"name\": \"🎧搜索听书\",\n \"type\": \"button\",\n \"action\": \"ste('听书')\",\n \"style\": { \"layout_flexGrow\": 1, \"layout_flexBasisPercent\": 0.4 }\n }, {\n \"name\": \"🎥搜索短剧\",\n \"type\": \"button\",\n \"action\": \"ste('短剧')\",\n \"style\": { \"layout_flexGrow\": 1, \"layout_flexBasisPercent\": 0.4 }\n }, {\n \"name\": \"🎨搜索漫画\",\n \"type\": \"button\",\n \"action\": \"ste('漫画')\",\n \"style\": { \"layout_flexGrow\": 1, \"layout_flexBasisPercent\": 0.4 }\n }, {\n \"name\": \"段评开关\",\n \"type\": \"button\",\n \"action\": \"ste('段评')\",\n \"style\": { \"layout_flexGrow\": 1, \"layout_flexBasisPercent\": 0.25 }\n }, {\n \"name\": \"图片开关\",\n \"type\": \"button\",\n \"action\": \"ste('图片')\",\n \"style\": { \"layout_flexGrow\": 1, \"layout_flexBasisPercent\": 0.25 }\n }, {\n \"name\": \"简介开关\",\n \"type\": \"button\",\n \"action\": \"ste('简介')\",\n \"style\": { \"layout_flexGrow\": 1, \"layout_flexBasisPercent\": 0.25 }\n }, {\n \"name\": \"⛔‼️ 清空还原设置\",\n \"type\": \"button\",\n \"action\": \"deleteVariable()\",\n \"style\": { \"layout_flexGrow\": 1, \"layout_flexBasisPercent\": 1 }\n }\n]",
"searchUrl": "<js>\nlet moreSettings = getVariable('更多设置');\nlet tab = moreSettings && moreSettings['搜索模式'] || '小说';\nvar k = String(key);\nlet m = k.match(/^[xtmdXTMD][::]\\s*/);\nif (m) {\n tab = { x: '小说', t: '听书', m: '漫画', d: '短剧' }[m[0][0].toLowerCase()];\n k = k.slice(m[0].length);\n}\nlet tabType = { 小说: 3, 听书: 2, 漫画: 8, 短剧: 11 }[tab] || 3;\n`/search?query=${encodeURIComponent(k)}&offset=${(page - 1) * 10}&tab_type=${tabType}`;\n</js>",
"exploreUrl": "<js>\nvar base = String(source.getKey()).replace(/\\/$/, '');\nvar tab = getVariable('发现页类型') || '小说';\nvar channel = getVariable('频道') || '男频';\nvar wordNumMap = { '不限': '0', '30万字以内': '4', '100万字以内': '1', '30万-100万': '5', '100万-300万': '2', '300万字以上': '3', '100万字以上': '9' };\nvar updateMap = { '不限': '2', '完结': '0', '连载中': '1', '今日更新': '3' };\nvar sortMap = { '综合': '0', '热门': '24', '最新': '76', '评分': '12' };\nvar wordNum = getVariable('字数') || '不限';\nvar update = getVariable('更新') || '不限';\nvar sort = getVariable('排序') || '综合';\nvar q = `&word_number=${wordNumMap[wordNum] || '0'}&book_status=${updateMap[update] || '2'}&sort_by=${sortMap[sort] || '0'}`;\nvar tabCode = { 小说: 'n', 听书: 't', 漫画: 'm', 短剧: 'd' }[tab] || 'n';\n\nvar list = [];\nlist.push(createFilter('类型', ['小说', '听书', '短剧', '漫画'], tab, '发现页类型', 0.4));\nlist.push(createFilter('频道', ['男频', '女频'], channel, '频道', 0.4));\nlist.push(createText('搜索关键词', \"setVariable('搜索关键词',(infoMap['搜索关键词'] || (infoMap.get && infoMap.get('搜索关键词'))))\", getVariable('搜索关键词') || '', 0.5, \"'搜索书名或作者'\"));\nlist.push(createButton('🔍搜索', 'exploreSearch()', 0.3));\nlist.push({ title: '⚙️书源设置', type: 'button', action: 'getHtmlSettings()', style: { layout_flexGrow: 1, layout_flexBasisPercent: 0.25 } });\nlist.push({ title: '🗨段评设置', type: 'button', action: 'getSvgSettings()', style: { layout_flexGrow: 1, layout_flexBasisPercent: 0.25 } });\nlist.push(createFilter('字数', Object.keys(wordNumMap), wordNum, '字数', 0.4));\nlist.push(createFilter('更新', Object.keys(updateMap), update, '更新', 0.4));\nlist.push(createFilter('排序', Object.keys(sortMap), sort, '排序', 0.1));\n\nif (tab == '短剧') {\n list.push({ title: '短剧暂无分类,请使用上方搜索 🔍', url: '', style: { layout_flexGrow: 1, layout_flexBasisPercent: 1 } });\n} else {\n var ctab = tab == '小说' ? (channel == '女频' ? 0 : 1) : (tab == '听书' ? 3 : 5);\n try {\n var res = JSON.parse(java.ajax(base + '/category-front?tab=' + ctab));\n var cells = ((res.data || {}).category_tab_data || {}).cell_data || [];\n cells.forEach(function (cell) {\n (cell.atom_data || []).forEach(function (a) {\n var c = a.category_data;\n if (c && c.category_id != null) {\n list.push({\n title: c.name,\n url: base + '/category-landing?category_id=' + c.category_id + '&offset={{(page-1)*20}}' + q + '&fqtab=' + tabCode,\n style: { layout_flexGrow: 1, layout_flexBasisPercent: 0.25 }\n });\n }\n });\n });\n } catch (e) {\n java.toast('发现页分类获取失败');\n }\n}\nJSON.stringify(list);\n</js>",
"ruleSearch": {
"bookList": "<js>\nvar d = JSON.parse(result);\nvar isVideo = /tab_type=11/.test(baseUrl);\nvar arr = [];\n(d.data || []).forEach(function (b) {\n var list = isVideo ? (b.video_data || []) : (b.book_data || []);\n list.forEach(function (x) {\n if (isVideo) {\n arr.push({\n book_id: x.series_id,\n book_name: x.title,\n author: '',\n abstract: x.rec_text || '',\n thumb_url: x.horiz_cover || x.cover,\n category: '短剧',\n creation_status: '1',\n score: x.score,\n word_number: String(x.episode_cnt || '')\n });\n } else {\n arr.push(x);\n }\n });\n});\narr;\n</js>",
"name": "$.book_name",
"author": "$.author",
"kind": "{{$.creation_status=='0'?'完结':'连载'}},{{$.category}},{{$.score}}分",
"wordCount": "$.word_number##^0+$",
"intro": "$.abstract",
"coverUrl": "$.thumb_url",
"bookUrl": "<js>\nvar t = /tab_type=11/.test(baseUrl) ? 'd' : /tab_type=2/.test(baseUrl) ? 't' : /tab_type=8/.test(baseUrl) ? 'm' : 'n';\n`/detail?book_id=${result.book_id}&fqtab=${t}`;\n</js>",
"checkKeyWord": "斗罗大陆"
},
"ruleExplore": {
"bookList": "$.data.book_info[*]",
"name": "$.book_name",
"author": "$.author",
"kind": "{{$.category}},{{$.score}}分",
"wordCount": "$.word_number##^0+$",
"intro": "$.abstract",
"coverUrl": "$.thumb_url",
"bookUrl": "<js>\nvar m = String(baseUrl).match(/fqtab=([ntmd])/);\n`/detail?book_id=${result.book_id}&fqtab=${m ? m[1] : 'n'}`;\n</js>"
},
"ruleBookInfo": {
"init": "$.data",
"name": "$.book_name",
"author": "$.author",
"kind": "{{$.creation_status=='0'?'完结':'连载'}},{{$.category}}",
"wordCount": "$.word_number##^0+$",
"lastChapter": "$.last_chapter_title",
"coverUrl": "$.thumb_url",
"intro": "<js>\nconst { book_id, last_chapter_title, last_chapter_update_time, word_number, score, abstract, read_count, category } = result;\nlet m0 = String(baseUrl).match(/fqtab=([ntmd])/);\nconst tab = { n: '小说', t: '听书', m: '漫画', d: '短剧' }[(m0 ? m0[1] : 'n')];\nlet moreSettings = getVariable('更多设置');\nconst lightDivider = \"❇️───────❇️───────❇️\";\nconst heavyDivider = \"&lrm;\\n&lrm;\";\nconst isValid = (v) => String(v).length > 1;\nlet ctitle = book.durChapterTitle || '未开始';\nconst two = (n) => ('0' + n).slice(-2);\nconst fmtTime = (t) => {\n let n = parseInt(t);\n if (!n || n <= 0) return '';\n let d = new Date(n * 1000);\n return `${d.getFullYear()}-${two(d.getMonth() + 1)}-${two(d.getDate())}`;\n};\nconst formatAbstract = (text) => String(text).split(\"\\n\").map(line => ` ${line}`).join(\"\\n\");\nconst buildBasicInfo = (items) => {\n let info = \"\";\n for (let [value, prefix, icon] of items) {\n if (isValid(value)) info += ` ${icon} ${prefix} ${value}\\n`;\n }\n return info;\n};\nlet info = ` 🏷 数据来源:番茄小说(fanqie-go)\\n 🔄 当前模式:${tab}\\n 📖 阅读至:${ctitle}\\n`;\nconst fullBasicInfo = buildBasicInfo([\n [category, \"书籍分类:\", \"🌈\"],\n [last_chapter_title, \"最新章节:\", \"📚\"],\n [fmtTime(last_chapter_update_time), \"更新时间:\", \"⏳\"],\n [word_number, \"书籍字数:\", \"📝\"],\n [score, \"书籍评分:\", \"⭐\"],\n [read_count, \"今阅读人数:\", \"📃\"]\n]);\nif (fullBasicInfo) info += `${lightDivider}\\n${fullBasicInfo}`;\nif (isValid(abstract)) info += `${heavyDivider}\\n 📖 书籍简介:\\n${formatAbstract(abstract)}\\n`;\ninfo += `${lightDivider}\\n${heavyDivider}\\n 数据更新于 ${new Date().toLocaleString()}\\n`;\nconst abstractMode = moreSettings && moreSettings['完整简介'] || 'true';\nif (abstractMode == \"false\") {\n const simplifiedBasicInfo = buildBasicInfo([\n [last_chapter_title, \"最新章节:\", \"📚\"],\n [fmtTime(last_chapter_update_time), \"更新时间:\", \"⏳\"],\n [word_number, \"书籍字数:\", \"📝\"],\n [score, \"书籍评分:\", \"⭐\"]\n ]);\n info = `\\n&lrm;${isValid(abstract) ? `📖 书籍简介:\\n${formatAbstract(abstract)}\\n${heavyDivider}` : ''}🏷 数据来源:番茄小说\\n${simplifiedBasicInfo}`;\n} else {\n info = info.split(\"\\n\").map(line => line.replace(/^ {4}/, \"\")).join(\"\\n\");\n}\ninfo;\n</js>",
"tocUrl": "<js>\nvar m = String(baseUrl).match(/fqtab=([ntmd])/);\n`/catalog?book_id=${result.book_id}&fqtab=${m ? m[1] : 'n'}`;\n</js>",
"canReName": "1"
},
"ruleToc": {
"chapterList": "<js>\n// 类型随书切换(照光遇 typeMap:安卓用 default,苹果端用其类型体系)\nconst typeMap = {\n \"小说\": { \"苹果\": 0, \"default\": 8 },\n \"听书\": { \"苹果\": 1, \"default\": 32 },\n \"漫画\": { \"苹果\": 2, \"default\": 64 },\n \"短剧\": { \"苹果\": 3, \"default\": 8 }\n};\nconst device_type = checkEnv();\nvar tabName = { n: '小说', t: '听书', m: '漫画', d: '短剧' }[fqTab(baseUrl, 'n')];\nvar currentType = typeMap[tabName] || typeMap[\"小说\"];\nif (device_type == '苹果') {\n book.type = currentType[device_type];\n} else {\n book.type = currentType.default;\n}\ntry {\n book.imageStyle = checkEnv() == '改版' ? 'FULL' : 'TEXT';\n} catch (e) {}\nresult;\n</js>$.data.item_data_list[*]",
"chapterName": "$.title",
"chapterUrl": "<js>\nvar t = fqTab(baseUrl, 'n');\nvar id = result.item_id;\nvar u = {\n n: `/content?item_id=${id}&text_mode=html&review=keep&image_mode=keep&fqtab=n`,\n t: `/audio?item_ids=${id}&fqtab=t`,\n m: `/manga?item_ids=${id}&output=proxy&fqtab=m`,\n d: `/video?item_ids=${id}&fqtab=d`\n}[t];\nu;\n</js>"
},
"ruleContent": {
"content": "<js>\nvar t = fqTab(baseUrl, 'n');\nlet moreSettings = getVariable('更多设置');\nlet dpSettings = getVariable('段评设置');\nlet islyc = checkEnv();\n\nif (t == 't') {\n // 听书:直接返回音频地址\n JSON.parse(result).data[0].main_url;\n} else if (t == 'm') {\n // 漫画:拼装图片\n var d = JSON.parse(result);\n var org = String(baseUrl).match(/^https?:\\/\\/[^\\/]+/)[0];\n var imgs = (d.data || {}).images || [];\n var out = [];\n for (var i = 0; i < imgs.length; i++) {\n out.push('<img src=\"' + org + imgs[i].data + '\">');\n }\n out.join('\\n');\n} else if (t == 'd') {\n // 短剧:选最高清晰度,当前章节自动唤起播放\n var d = JSON.parse(result).data || {};\n var vl = d.video_list || {};\n var list = [];\n for (var k in vl) { list.push(vl[k]); }\n list.sort(function (a, b) { return (b.vheight || 0) - (a.vheight || 0); });\n var org = String(baseUrl).match(/^https?:\\/\\/[^\\/]+/)[0];\n var url = list.length ? String(list[0].main_url) : '';\n if (url && url.indexOf('http') != 0) { url = org + url; }\n if (url && book.durChapterIndex == chapter.index) {\n java.startBrowser(url, chapter.title);\n }\n '▶️ 本集已唤起播放(菜单-刷新可重新打开):\\n' + url;\n} else {\n // 小说:正文 + 段评气泡\n var d = JSON.parse(result);\n var content = String((d.data || {}).content || '');\n let show_img = moreSettings && moreSettings['显示图片'] || 'true';\n if (show_img == 'false') {\n content = removeAllImgTags(content);\n }\n let para = dpSettings && dpSettings['段评开关'] || 'true';\n if (para == 'true' && content.indexOf('<comment') != -1) {\n content = islyc == '苹果' ? paraForiOS(content, '番茄') : paraForAndroid(content, '番茄');\n }\n content;\n}\n</js>",
"title": "$.data.title"
}
}
]