[ { "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>|]*\\/>/gi;\n return htmlString.replace(imgTagRegex, '');\n}\n\nlet svgs = [\n \" $displayText\",\n\n \"$displayText\",\n\n \"$displayText\",\n\n\n\n \"$displayText\",\n\n\n\n \"$displayText\",\n\n\n\n\n `\n \n $displayText\n`\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// 段评气泡注入(正文

支持任意属性)\nfunction paraForAndroid(content, sources) {\n let { cache } = this;\n const createSvg = this.createSvg.bind(this);\n return content.replace(/(]*>)([\\s\\S]*?)(?:)?(<\\/p>)/g,\n function (match, open, text, url, count, close) {\n if (url && count) {\n cache.putMemory(url, 0);\n return `

${text}

`;\n }\n return `

${text}

`;\n });\n}\n\n//源阅(iOS)段评\nfunction paraForiOS(content, sources) {\n return content.replace(/(]*>)([\\s\\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 `
${text}
`;\n }\n return `

${text}

`;\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 = `\n\n\n\n\n番茄聚合 - 书源设置\n\n\n\n
\n
\n
\n
🍅 番茄聚合 · 书源设置
\n
数据服务由 fanqie-go 提供
\n
\n
\n

设置完成后请点击右上角的√应用设置

\n
\n
\n
\n
搜索模式
\n
切换后搜索对应类型,也可以在搜索时使用关键词:t:书名 搜听书,d:书名 搜短剧,m:书名 搜漫画
\n
\n
\n \n
\n
\n
\n
\n
显示图片
\n
关闭后,文章内的图片将会清除
\n
\n \n
\n
\n
\n
完整简介
\n
关闭后,书籍详情页会精简简介内容
\n
\n \n
\n
\n ${(data && data.搜索模式) || '小说'}\n ${(data && data.显示图片) || 'true'}\n ${(data && data.完整简介) || 'true'}\n
\n
\n
\n\n\n`;\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 = `\n\n\n\n\n番茄聚合 - 段评设置\n\n\n\n
\n
\n
\n
🗨 番茄聚合 · 段评设置
\n
点击正文段尾气泡查看段落评论
\n
\n
\n

设置完成后请点击右上角的√应用设置

\n
\n
\n
\n
段评
\n
阅读正文时在段落末尾显示评论气泡
\n
\n \n
\n
\n
\n
气泡增大
\n
开启后,气泡显示会加大一些
\n
\n \n
\n
\n
\n
边框颜色
\n
设置段评边框颜色
\n
\n
\n \n \n
\n
\n
\n
\n
字体颜色
\n
设置段评字体颜色
\n
\n
\n \n \n
\n
\n
\n
\n
填充颜色
\n
设置段评背景填充颜色
\n
\n
\n \n \n \n
\n
\n
\n
实时预览(点击选择样式)
\n
\n
\n
\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
\n
\n
\n\n\n`;\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": "\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", "exploreUrl": "\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", "ruleSearch": { "bookList": "\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", "name": "$.book_name", "author": "$.author", "kind": "{{$.creation_status=='0'?'完结':'连载'}},{{$.category}},{{$.score}}分", "wordCount": "$.word_number##^0+$", "intro": "$.abstract", "coverUrl": "$.thumb_url", "bookUrl": "\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", "checkKeyWord": "斗罗大陆" }, "ruleExplore": { "bookList": "$.data.book_info[*]", "name": "$.book_name", "author": "$.author", "kind": "{{$.category}},{{$.score}}分", "wordCount": "$.word_number##^0+$", "intro": "$.abstract", "coverUrl": "$.thumb_url", "bookUrl": "\nvar m = String(baseUrl).match(/fqtab=([ntmd])/);\n`/detail?book_id=${result.book_id}&fqtab=${m ? m[1] : 'n'}`;\n" }, "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": "\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 = \"‎\\n‎\";\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‎${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", "tocUrl": "\nvar m = String(baseUrl).match(/fqtab=([ntmd])/);\n`/catalog?book_id=${result.book_id}&fqtab=${m ? m[1] : 'n'}`;\n", "canReName": "1" }, "ruleToc": { "chapterList": "\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$.data.item_data_list[*]", "chapterName": "$.title", "chapterUrl": "\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" }, "ruleContent": { "content": "\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('');\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('", "title": "$.data.title" } } ]