From 99826c6d25b6c60474c927888e882fed5aadb8a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=AD=86?= <2685475410@qq.com> Date: Sat, 10 Oct 2026 12:48:59 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(bookSource):=20=E8=81=9A?= =?UTF-8?q?=E5=90=88=E4=B9=A6=E6=BA=90=E6=94=AF=E6=8C=81=E5=BC=B9=E7=AA=97?= =?UTF-8?q?=E6=AE=B5=E8=AF=84=E5=B9=B6=E5=85=BC=E5=AE=B9=E5=AE=98=E6=96=B9?= =?UTF-8?q?=E9=98=85=E8=AF=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - showCmt 能力检测:魔改版(有 showBrowser)原地弹窗(点遮罩关闭/80%高度),官方版降级 startBrowser - 弹窗预载 ajax 强制 https,规避反代 301 拿到空内容 - jsLib 函数不再引用规则变量 baseUrl(作用域隔离),fqTab 显式传参 - bookList 返回数组对象;ruleBookInfo 以 init 提取 data;searchUrl 改用局部变量并 encodeURIComponent --- bookSource.json | 86 +++++++++++++++++++++++++------------------------ deploy.sh | 61 +++++++++++++++++++++++++++++++---- 2 files changed, 98 insertions(+), 49 deletions(-) diff --git a/bookSource.json b/bookSource.json index e60a7ed..2455572 100644 --- a/bookSource.json +++ b/bookSource.json @@ -1,61 +1,63 @@ [ { - "bookSourceComment": "fanqie-go 聚合书源:一个源覆盖 小说/听书/漫画/短剧。\n\n【设置入口】发现页顶部选择按钮:模式/音色/清晰度/段评/图片/频道,点选即生效;", - "bookSourceGroup": "🍅 番茄", - "bookSourceName": "🍅 番茄聚合", + "bookSourceName": "🔅番茄聚合", + "bookSourceGroup": "📖番茄小说", "bookSourceType": 0, "bookSourceUrl": "https://fq.20010117.xyz", - "customOrder": 0, - "enabled": true, - "enabledCookieJar": false, + "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, - "header": "{\"User-Agent\": \"Mozilla/5.0 (Linux; Android 13; 2210132C Build/TP1A.220624.014; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/116.0.5845.92 Mobile Safari/537.36\"}", - "jsLib": "// ============ 源变量配置(照光遇模式:defaultConfig 兜底,全 try-catch) ============\nconst defaultConfig = {\n 模式: \"小说\",\n 音色: \"0\",\n 清晰度: \"max\",\n 段评: \"开\",\n 图片: \"保留\"\n};\n\nconst fqVarsKey = 'fq_vars';\n\nfunction getVariable(k) {\n if (k == undefined) k = \"\";\n var parsed = {};\n try {\n var raw = String(cache.get(fqVarsKey) || '');\n if (raw) parsed = JSON.parse(raw);\n } catch (e) {}\n if (k == \"\") return parsed;\n var value = parsed[k];\n if (value == undefined) value = defaultConfig[k];\n return value != undefined ? value : \"\";\n}\n\nfunction setVariable(k, v, t) {\n if (t == undefined) t = true;\n var parsed = {};\n try {\n var raw = String(cache.get(fqVarsKey) || '');\n if (raw) parsed = JSON.parse(raw);\n } catch (e) {}\n parsed[k] = v;\n try { cache.put(fqVarsKey, JSON.stringify(parsed)); } catch (e) {}\n if (t) java.toast('\\n设置 ' + k + ' 为 ' + v);\n}\n\n// 发现页筛选按钮回调:存变量并刷新发现页\nfunction show(m, t) {\n setVariable(t, m, false);\n java.toast('\\n已设置' + t + ':' + m + '\\n已在书架的书切换模式后需刷新目录');\n try { java.refreshExplore(); } catch (e) {}\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\n// ============ 基础工具 ============\n// 服务地址:source 可用时取书源地址,否则用常量兜底(部分阅读版本正文环境不注入 source)\nvar FQ_BASE = 'https://fq.20010117.xyz';\n\nfunction BaseUrl() {\n try {\n if (typeof source != 'undefined' && source && source.getKey) {\n var u = String(source.getKey()).replace(/\\/+$/, '');\n if (u.indexOf('http') == 0) return u;\n }\n } catch (e) {}\n return FQ_BASE;\n}\n\nfunction request(url) {\n let urla = url;\n if (!url.includes('http')) urla = this.BaseUrl() + url;\n return java.ajax(urla);\n}\n\nfunction parseJsonSafely(str, defaultValue) {\n if (defaultValue == null) defaultValue = null;\n try {\n return JSON.parse(str);\n } catch (e) {\n return defaultValue;\n }\n}\n\nfunction isIOS() {\n try {\n java.deviceID();\n return true;\n } catch (e) {\n return false;\n }\n}\n\nfunction isMod() {\n try {\n if (typeof java.reLoginView == 'function') return true;\n new Packages.io.legato.kazusa.utils.TimeoutCancellationException('');\n return true;\n } catch (e) {\n return false;\n }\n}\n\n// 纯 JS base64(UTF-8),规避部分阅读版本 base64 编码 API 不可用\nfunction b64encode(str) {\n var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';\n var bytes = [];\n str = String(str);\n for (var i = 0; i < str.length; i++) {\n var c = str.charCodeAt(i);\n if (c < 0x80) {\n bytes.push(c);\n } else if (c < 0x800) {\n bytes.push(0xC0 | (c >> 6), 0x80 | (c & 63));\n } else {\n bytes.push(0xE0 | (c >> 12), 0x80 | ((c >> 6) & 63), 0x80 | (c & 63));\n }\n }\n var out = '';\n for (var j = 0; j < bytes.length; j += 3) {\n var b1 = bytes[j];\n var b2 = j + 1 < bytes.length ? bytes[j + 1] : undefined;\n var b3 = j + 2 < bytes.length ? bytes[j + 2] : undefined;\n out += chars[b1 >> 2];\n out += chars[((b1 & 3) << 4) | (b2 == undefined ? 0 : b2 >> 4)];\n out += b2 == undefined ? '=' : chars[((b2 & 15) << 2) | (b3 == undefined ? 0 : b3 >> 6)];\n out += b3 == undefined ? '=' : chars[b3 & 63];\n }\n return out;\n}\n\n// ============ 段评气泡(/content?review=keep 注入 标记,点击打开 /review_html 页面) ============\nfunction createSvg(number, url) {\n var t = number > 99 ? '99+' : String(number);\n var svg = `${t}`;\n var k = isMod() ? 'click' : 'js';\n return `data:image/svg+xml;base64,${b64encode(svg)},{\"${k}\":\"showCmt('${url}','段评')\",\"style\":\"text\"}`;\n}\n\nfunction showCmt(url, title) {\n var t = title || '段评';\n try {\n java.startBrowser(url, t);\n return;\n } catch (e) {}\n try {\n java.startBrowserDp(url, t);\n } catch (e2) {}\n}\n\nfunction paraForAndroid(content) {\n return content.replace(/]*>([\\s\\S]*?)(?:)?<\\/p>/g,\n function (m, text, url, count) {\n if (url && count) return `

${text}

`;\n return `

${text}

`;\n });\n}\n\nfunction paraForiOS(content) {\n return content.replace(/]*>([\\s\\S]*?)(?:)?<\\/p>/g,\n function (m, text, url, count) {\n if (url && count) {\n var u = url.replace(/&/g, '&');\n return `
${text} 99 ? '99' : count}\" onPress=\"java.showReadingBrowser('${u}','段评')\">
`;\n }\n return `
${text}
`;\n }).replace(/"/g, '\"');\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: {layout_flexGrow: 1, layout_flexBasisPercent: size}\n };\n}\n\nfunction exploreSearch() {\n var key = getVariable('搜索关键词');\n if (!key || !String(key).trim()) {\n java.toast('请输入搜索关键词');\n return;\n }\n var prefix = {听书: 't:', 漫画: 'm:', 短剧: 'd:'}[getVariable('模式')] || '';\n var word = prefix + String(key).trim();\n var done = false;\n try {\n var sk = '';\n try { sk = source.bookSourceName + '::' + source.getKey(); } catch (e) {}\n if (sk) { java.searchBook(word, sk); done = true; }\n } catch (e) {}\n if (!done) {\n try { java.searchBook(word); done = true; } catch (e) {}\n }\n if (!done) {\n try { java.open('search', word); done = true; } catch (e) {}\n }\n if (!done) java.toast('请到搜索页搜索:' + word);\n try { java.refreshExplore(); } catch (e) {}\n}", - "lastUpdateTime": 1791569653299, + "enabledCookieJar": true, + "customOrder": 0, + "weight": 0, "respondTime": 180000, - "exploreUrl": "\nvar groups = {\"🔥 男频精选\": [{\"title\": \"玄幻\", \"url\": \"/category-landing?category_id=7&offset={{(page-1)*20}}\"}, {\"title\": \"都市\", \"url\": \"/category-landing?category_id=1&offset={{(page-1)*20}}\"}, {\"title\": \"历史\", \"url\": \"/category-landing?category_id=12&offset={{(page-1)*20}}\"}, {\"title\": \"悬疑\", \"url\": \"/category-landing?category_id=10&offset={{(page-1)*20}}\"}, {\"title\": \"衍生\", \"url\": \"/category-landing?category_id=1079&offset={{(page-1)*20}}\"}, {\"title\": \"同人\", \"url\": \"/category-landing?category_id=538&offset={{(page-1)*20}}\"}, {\"title\": \"科幻末世\", \"url\": \"/category-landing?category_id=8&offset={{(page-1)*20}}\"}, {\"title\": \"奇幻仙侠\", \"url\": \"/category-landing?category_id=259&offset={{(page-1)*20}}\"}, {\"title\": \"种田\", \"url\": \"/category-landing?category_id=23&offset={{(page-1)*20}}\"}, {\"title\": \"游戏体育\", \"url\": \"/category-landing?category_id=746&offset={{(page-1)*20}}\"}, {\"title\": \"系统\", \"url\": \"/category-landing?category_id=19&offset={{(page-1)*20}}\"}, {\"title\": \"重生\", \"url\": \"/category-landing?category_id=36&offset={{(page-1)*20}}\"}, {\"title\": \"穿越\", \"url\": \"/category-landing?category_id=37&offset={{(page-1)*20}}\"}, {\"title\": \"多女主\", \"url\": \"/category-landing?category_id=91&offset={{(page-1)*20}}\"}, {\"title\": \"修仙\", \"url\": \"/category-landing?category_id=517&offset={{(page-1)*20}}\"}, {\"title\": \"二次元\", \"url\": \"/category-landing?category_id=39&offset={{(page-1)*20}}\"}, {\"title\": \"搞笑轻松\", \"url\": \"/category-landing?category_id=778&offset={{(page-1)*20}}\"}, {\"title\": \"单女主\", \"url\": \"/category-landing?category_id=389&offset={{(page-1)*20}}\"}, {\"title\": \"悬疑灵异\", \"url\": \"/category-landing?category_id=751&offset={{(page-1)*20}}\"}, {\"title\": \"末日求生\", \"url\": \"/category-landing?category_id=515&offset={{(page-1)*20}}\"}, {\"title\": \"娱乐圈\", \"url\": \"/category-landing?category_id=43&offset={{(page-1)*20}}\"}, {\"title\": \"末世\", \"url\": \"/category-landing?category_id=68&offset={{(page-1)*20}}\"}], \"🌸 女频精选\": [{\"title\": \"现代言情\", \"url\": \"/category-landing?category_id=3&offset={{(page-1)*20}}\"}, {\"title\": \"古代言情\", \"url\": \"/category-landing?category_id=5&offset={{(page-1)*20}}\"}, {\"title\": \"纯爱\", \"url\": \"/category-landing?category_id=1012&offset={{(page-1)*20}}\"}, {\"title\": \"甜宠\", \"url\": \"/category-landing?category_id=96&offset={{(page-1)*20}}\"}, {\"title\": \"总裁\", \"url\": \"/category-landing?category_id=29&offset={{(page-1)*20}}\"}, {\"title\": \"幻想言情\", \"url\": \"/category-landing?category_id=32&offset={{(page-1)*20}}\"}, {\"title\": \"双男主\", \"url\": \"/category-landing?category_id=275&offset={{(page-1)*20}}\"}, {\"title\": \"双女主\", \"url\": \"/category-landing?category_id=704&offset={{(page-1)*20}}\"}, {\"title\": \"年代\", \"url\": \"/category-landing?category_id=79&offset={{(page-1)*20}}\"}, {\"title\": \"快穿\", \"url\": \"/category-landing?category_id=24&offset={{(page-1)*20}}\"}, {\"title\": \"豪门总裁\", \"url\": \"/category-landing?category_id=748&offset={{(page-1)*20}}\"}, {\"title\": \"追妻火葬场\", \"url\": \"/category-landing?category_id=616&offset={{(page-1)*20}}\"}, {\"title\": \"无CP\", \"url\": \"/category-landing?category_id=392&offset={{(page-1)*20}}\"}, {\"title\": \"大女主\", \"url\": \"/category-landing?category_id=760&offset={{(page-1)*20}}\"}, {\"title\": \"女强\", \"url\": \"/category-landing?category_id=86&offset={{(page-1)*20}}\"}], \"🏷️ 男频标签\": [{\"title\": \"古代\", \"url\": \"/category-landing?category_id=758&offset={{(page-1)*20}}\"}, {\"title\": \"历史古代\", \"url\": \"/category-landing?category_id=273&offset={{(page-1)*20}}\"}, {\"title\": \"轻小说\", \"url\": \"/category-landing?category_id=858&offset={{(page-1)*20}}\"}, {\"title\": \"都市生活\", \"url\": \"/category-landing?category_id=2&offset={{(page-1)*20}}\"}, {\"title\": \"都市高武\", \"url\": \"/category-landing?category_id=1014&offset={{(page-1)*20}}\"}, {\"title\": \"游戏动漫\", \"url\": \"/category-landing?category_id=57&offset={{(page-1)*20}}\"}, {\"title\": \"抗战谍战\", \"url\": \"/category-landing?category_id=504&offset={{(page-1)*20}}\"}, {\"title\": \"体育\", \"url\": \"/category-landing?category_id=15&offset={{(page-1)*20}}\"}, {\"title\": \"都市异能\", \"url\": \"/category-landing?category_id=516&offset={{(page-1)*20}}\"}, {\"title\": \"武侠\", \"url\": \"/category-landing?category_id=16&offset={{(page-1)*20}}\"}, {\"title\": \"东方仙侠\", \"url\": \"/category-landing?category_id=1140&offset={{(page-1)*20}}\"}, {\"title\": \"东方玄幻\", \"url\": \"/category-landing?category_id=511&offset={{(page-1)*20}}\"}, {\"title\": \"男频衍生\", \"url\": \"/category-landing?category_id=1016&offset={{(page-1)*20}}\"}, {\"title\": \"西方奇幻\", \"url\": \"/category-landing?category_id=1141&offset={{(page-1)*20}}\"}, {\"title\": \"异世大陆\", \"url\": \"/category-landing?category_id=512&offset={{(page-1)*20}}\"}, {\"title\": \"都市种田\", \"url\": \"/category-landing?category_id=263&offset={{(page-1)*20}}\"}, {\"title\": \"影视小说\", \"url\": \"/category-landing?category_id=45&offset={{(page-1)*20}}\"}, {\"title\": \"仕途\", \"url\": \"/category-landing?category_id=558&offset={{(page-1)*20}}\"}, {\"title\": \"规则怪谈\", \"url\": \"/category-landing?category_id=851&offset={{(page-1)*20}}\"}, {\"title\": \"国运\", \"url\": \"/category-landing?category_id=496&offset={{(page-1)*20}}\"}, {\"title\": \"灵气复苏\", \"url\": \"/category-landing?category_id=514&offset={{(page-1)*20}}\"}, {\"title\": \"历史脑洞\", \"url\": \"/category-landing?category_id=272&offset={{(page-1)*20}}\"}, {\"title\": \"都市脑洞\", \"url\": \"/category-landing?category_id=262&offset={{(page-1)*20}}\"}, {\"title\": \"玄幻脑洞\", \"url\": \"/category-landing?category_id=257&offset={{(page-1)*20}}\"}, {\"title\": \"综漫\", \"url\": \"/category-landing?category_id=465&offset={{(page-1)*20}}\"}, {\"title\": \"综影视\", \"url\": \"/category-landing?category_id=1058&offset={{(page-1)*20}}\"}, {\"title\": \"架空\", \"url\": \"/category-landing?category_id=452&offset={{(page-1)*20}}\"}, {\"title\": \"异能\", \"url\": \"/category-landing?category_id=598&offset={{(page-1)*20}}\"}, {\"title\": \"克苏鲁\", \"url\": \"/category-landing?category_id=705&offset={{(page-1)*20}}\"}, {\"title\": \"高武世界\", \"url\": \"/category-landing?category_id=513&offset={{(page-1)*20}}\"}, {\"title\": \"悬疑脑洞\", \"url\": \"/category-landing?category_id=539&offset={{(page-1)*20}}\"}, {\"title\": \"宝可梦\", \"url\": \"/category-landing?category_id=859&offset={{(page-1)*20}}\"}, {\"title\": \"谍战\", \"url\": \"/category-landing?category_id=507&offset={{(page-1)*20}}\"}, {\"title\": \"第四天灾\", \"url\": \"/category-landing?category_id=855&offset={{(page-1)*20}}\"}, {\"title\": \"开局\", \"url\": \"/category-landing?category_id=453&offset={{(page-1)*20}}\"}, {\"title\": \"天灾\", \"url\": \"/category-landing?category_id=658&offset={{(page-1)*20}}\"}, {\"title\": \"宋朝\", \"url\": \"/category-landing?category_id=501&offset={{(page-1)*20}}\"}, {\"title\": \"第一人称\", \"url\": \"/category-landing?category_id=871&offset={{(page-1)*20}}\"}, {\"title\": \"武将\", \"url\": \"/category-landing?category_id=497&offset={{(page-1)*20}}\"}, {\"title\": \"赛博朋克\", \"url\": \"/category-landing?category_id=873&offset={{(page-1)*20}}\"}, {\"title\": \"断层\", \"url\": \"/category-landing?category_id=500&offset={{(page-1)*20}}\"}, {\"title\": \"清朝\", \"url\": \"/category-landing?category_id=503&offset={{(page-1)*20}}\"}, {\"title\": \"扮猪吃虎\", \"url\": \"/category-landing?category_id=93&offset={{(page-1)*20}}\"}, {\"title\": \"无女主\", \"url\": \"/category-landing?category_id=391&offset={{(page-1)*20}}\"}, {\"title\": \"特种兵\", \"url\": \"/category-landing?category_id=375&offset={{(page-1)*20}}\"}, {\"title\": \"校花\", \"url\": \"/category-landing?category_id=385&offset={{(page-1)*20}}\"}, {\"title\": \"大小姐\", \"url\": \"/category-landing?category_id=519&offset={{(page-1)*20}}\"}, {\"title\": \"神医\", \"url\": \"/category-landing?category_id=26&offset={{(page-1)*20}}\"}, {\"title\": \"腹黑\", \"url\": \"/category-landing?category_id=92&offset={{(page-1)*20}}\"}, {\"title\": \"反派\", \"url\": \"/category-landing?category_id=369&offset={{(page-1)*20}}\"}, {\"title\": \"大佬\", \"url\": \"/category-landing?category_id=520&offset={{(page-1)*20}}\"}, {\"title\": \"女帝\", \"url\": \"/category-landing?category_id=378&offset={{(page-1)*20}}\"}, {\"title\": \"天才\", \"url\": \"/category-landing?category_id=90&offset={{(page-1)*20}}\"}, {\"title\": \"学霸\", \"url\": \"/category-landing?category_id=82&offset={{(page-1)*20}}\"}, {\"title\": \"赘婿\", \"url\": \"/category-landing?category_id=25&offset={{(page-1)*20}}\"}, {\"title\": \"奶爸\", \"url\": \"/category-landing?category_id=42&offset={{(page-1)*20}}\"}, {\"title\": \"战神赘婿\", \"url\": \"/category-landing?category_id=27&offset={{(page-1)*20}}\"}, {\"title\": \"皇帝\", \"url\": \"/category-landing?category_id=498&offset={{(page-1)*20}}\"}, {\"title\": \"群像\", \"url\": \"/category-landing?category_id=841&offset={{(page-1)*20}}\"}, {\"title\": \"特工\", \"url\": \"/category-landing?category_id=518&offset={{(page-1)*20}}\"}, {\"title\": \"游戏主播\", \"url\": \"/category-landing?category_id=509&offset={{(page-1)*20}}\"}, {\"title\": \"全能\", \"url\": \"/category-landing?category_id=856&offset={{(page-1)*20}}\"}, {\"title\": \"贵族\", \"url\": \"/category-landing?category_id=502&offset={{(page-1)*20}}\"}, {\"title\": \"神探\", \"url\": \"/category-landing?category_id=506&offset={{(page-1)*20}}\"}, {\"title\": \"动漫衍生\", \"url\": \"/category-landing?category_id=718&offset={{(page-1)*20}}\"}, {\"title\": \"无脑爽\", \"url\": \"/category-landing?category_id=850&offset={{(page-1)*20}}\"}, {\"title\": \"网游\", \"url\": \"/category-landing?category_id=372&offset={{(page-1)*20}}\"}, {\"title\": \"风水秘术\", \"url\": \"/category-landing?category_id=1455&offset={{(page-1)*20}}\"}, {\"title\": \"灵异\", \"url\": \"/category-landing?category_id=100&offset={{(page-1)*20}}\"}, {\"title\": \"无限流\", \"url\": \"/category-landing?category_id=70&offset={{(page-1)*20}}\"}, {\"title\": \"仙侠\", \"url\": \"/category-landing?category_id=9&offset={{(page-1)*20}}\"}, {\"title\": \"四合院\", \"url\": \"/category-landing?category_id=495&offset={{(page-1)*20}}\"}, {\"title\": \"空间\", \"url\": \"/category-landing?category_id=44&offset={{(page-1)*20}}\"}, {\"title\": \"乡村\", \"url\": \"/category-landing?category_id=11&offset={{(page-1)*20}}\"}, {\"title\": \"诸天万界\", \"url\": \"/category-landing?category_id=71&offset={{(page-1)*20}}\"}, {\"title\": \"神豪\", \"url\": \"/category-landing?category_id=20&offset={{(page-1)*20}}\"}, {\"title\": \"三国\", \"url\": \"/category-landing?category_id=67&offset={{(page-1)*20}}\"}, {\"title\": \"穿书\", \"url\": \"/category-landing?category_id=382&offset={{(page-1)*20}}\"}, {\"title\": \"盗墓\", \"url\": \"/category-landing?category_id=81&offset={{(page-1)*20}}\"}, {\"title\": \"洪荒\", \"url\": \"/category-landing?category_id=66&offset={{(page-1)*20}}\"}, {\"title\": \"无敌\", \"url\": \"/category-landing?category_id=384&offset={{(page-1)*20}}\"}, {\"title\": \"都市修真\", \"url\": \"/category-landing?category_id=124&offset={{(page-1)*20}}\"}, {\"title\": \"星际\", \"url\": \"/category-landing?category_id=77&offset={{(page-1)*20}}\"}, {\"title\": \"民国\", \"url\": \"/category-landing?category_id=390&offset={{(page-1)*20}}\"}, {\"title\": \"捉鬼\", \"url\": \"/category-landing?category_id=867&offset={{(page-1)*20}}\"}, {\"title\": \"魂穿\", \"url\": \"/category-landing?category_id=852&offset={{(page-1)*20}}\"}, {\"title\": \"黑道\", \"url\": \"/category-landing?category_id=826&offset={{(page-1)*20}}\"}, {\"title\": \"破案\", \"url\": \"/category-landing?category_id=505&offset={{(page-1)*20}}\"}, {\"title\": \"1v1\", \"url\": \"/category-landing?category_id=834&offset={{(page-1)*20}}\"}, {\"title\": \"求生\", \"url\": \"/category-landing?category_id=379&offset={{(page-1)*20}}\"}, {\"title\": \"囤物资\", \"url\": \"/category-landing?category_id=494&offset={{(page-1)*20}}\"}, {\"title\": \"惊悚游戏\", \"url\": \"/category-landing?category_id=537&offset={{(page-1)*20}}\"}, {\"title\": \"美食\", \"url\": \"/category-landing?category_id=78&offset={{(page-1)*20}}\"}, {\"title\": \"火影\", \"url\": \"/category-landing?category_id=368&offset={{(page-1)*20}}\"}, {\"title\": \"直播\", \"url\": \"/category-landing?category_id=69&offset={{(page-1)*20}}\"}, {\"title\": \"奇幻\", \"url\": \"/category-landing?category_id=6&offset={{(page-1)*20}}\"}, {\"title\": \"发家致富\", \"url\": \"/category-landing?category_id=840&offset={{(page-1)*20}}\"}, {\"title\": \"职场\", \"url\": \"/category-landing?category_id=127&offset={{(page-1)*20}}\"}, {\"title\": \"宠物\", \"url\": \"/category-landing?category_id=74&offset={{(page-1)*20}}\"}, {\"title\": \"双重生\", \"url\": \"/category-landing?category_id=524&offset={{(page-1)*20}}\"}, {\"title\": \"山海经\", \"url\": \"/category-landing?category_id=875&offset={{(page-1)*20}}\"}, {\"title\": \"双系统\", \"url\": \"/category-landing?category_id=866&offset={{(page-1)*20}}\"}, {\"title\": \"海岛\", \"url\": \"/category-landing?category_id=40&offset={{(page-1)*20}}\"}, {\"title\": \"打脸\", \"url\": \"/category-landing?category_id=522&offset={{(page-1)*20}}\"}, {\"title\": \"电竞\", \"url\": \"/category-landing?category_id=508&offset={{(page-1)*20}}\"}, {\"title\": \"大唐\", \"url\": \"/category-landing?category_id=73&offset={{(page-1)*20}}\"}, {\"title\": \"推理\", \"url\": \"/category-landing?category_id=61&offset={{(page-1)*20}}\"}, {\"title\": \"红楼衍生\", \"url\": \"/category-landing?category_id=1036&offset={{(page-1)*20}}\"}, {\"title\": \"奥特同人\", \"url\": \"/category-landing?category_id=367&offset={{(page-1)*20}}\"}, {\"title\": \"家庭\", \"url\": \"/category-landing?category_id=125&offset={{(page-1)*20}}\"}, {\"title\": \"海贼王\", \"url\": \"/category-landing?category_id=833&offset={{(page-1)*20}}\"}, {\"title\": \"升级流\", \"url\": \"/category-landing?category_id=830&offset={{(page-1)*20}}\"}, {\"title\": \"基建\", \"url\": \"/category-landing?category_id=828&offset={{(page-1)*20}}\"}, {\"title\": \"传统玄幻\", \"url\": \"/category-landing?category_id=258&offset={{(page-1)*20}}\"}, {\"title\": \"明朝\", \"url\": \"/category-landing?category_id=126&offset={{(page-1)*20}}\"}, {\"title\": \"钓鱼\", \"url\": \"/category-landing?category_id=493&offset={{(page-1)*20}}\"}, {\"title\": \"鉴宝\", \"url\": \"/category-landing?category_id=17&offset={{(page-1)*20}}\"}, {\"title\": \"黑科技\", \"url\": \"/category-landing?category_id=854&offset={{(page-1)*20}}\"}, {\"title\": \"大秦\", \"url\": \"/category-landing?category_id=377&offset={{(page-1)*20}}\"}, {\"title\": \"神奇宝贝\", \"url\": \"/category-landing?category_id=371&offset={{(page-1)*20}}\"}, {\"title\": \"剑修\", \"url\": \"/category-landing?category_id=868&offset={{(page-1)*20}}\"}, {\"title\": \"漫威\", \"url\": \"/category-landing?category_id=374&offset={{(page-1)*20}}\"}, {\"title\": \"都市江湖\", \"url\": \"/category-landing?category_id=1460&offset={{(page-1)*20}}\"}, {\"title\": \"黑化\", \"url\": \"/category-landing?category_id=846&offset={{(page-1)*20}}\"}, {\"title\": \"九叔\", \"url\": \"/category-landing?category_id=383&offset={{(page-1)*20}}\"}, {\"title\": \"废土\", \"url\": \"/category-landing?category_id=869&offset={{(page-1)*20}}\"}, {\"title\": \"无后宫\", \"url\": \"/category-landing?category_id=838&offset={{(page-1)*20}}\"}, {\"title\": \"迪化\", \"url\": \"/category-landing?category_id=843&offset={{(page-1)*20}}\"}, {\"title\": \"高手下山\", \"url\": \"/category-landing?category_id=845&offset={{(page-1)*20}}\"}, {\"title\": \"灵魂互换\", \"url\": \"/category-landing?category_id=831&offset={{(page-1)*20}}\"}, {\"title\": \"西游衍生\", \"url\": \"/category-landing?category_id=373&offset={{(page-1)*20}}\"}, {\"title\": \"封神\", \"url\": \"/category-landing?category_id=731&offset={{(page-1)*20}}\"}, {\"title\": \"聊天群\", \"url\": \"/category-landing?category_id=381&offset={{(page-1)*20}}\"}, {\"title\": \"争霸\", \"url\": \"/category-landing?category_id=837&offset={{(page-1)*20}}\"}, {\"title\": \"卡牌\", \"url\": \"/category-landing?category_id=874&offset={{(page-1)*20}}\"}, {\"title\": \"科举\", \"url\": \"/category-landing?category_id=832&offset={{(page-1)*20}}\"}, {\"title\": \"海贼\", \"url\": \"/category-landing?category_id=370&offset={{(page-1)*20}}\"}, {\"title\": \"副本\", \"url\": \"/category-landing?category_id=864&offset={{(page-1)*20}}\"}, {\"title\": \"龙珠\", \"url\": \"/category-landing?category_id=376&offset={{(page-1)*20}}\"}, {\"title\": \"剑道\", \"url\": \"/category-landing?category_id=80&offset={{(page-1)*20}}\"}, {\"title\": \"甄嬛衍生\", \"url\": \"/category-landing?category_id=1035&offset={{(page-1)*20}}\"}, {\"title\": \"斩神\", \"url\": \"/category-landing?category_id=857&offset={{(page-1)*20}}\"}, {\"title\": \"斩神衍生\", \"url\": \"/category-landing?category_id=1062&offset={{(page-1)*20}}\"}, {\"title\": \"外卖\", \"url\": \"/category-landing?category_id=75&offset={{(page-1)*20}}\"}, {\"title\": \"公版衍生\", \"url\": \"/category-landing?category_id=1037&offset={{(page-1)*20}}\"}, {\"title\": \"如懿衍生\", \"url\": \"/category-landing?category_id=1034&offset={{(page-1)*20}}\"}, {\"title\": \"十日衍生\", \"url\": \"/category-landing?category_id=1060&offset={{(page-1)*20}}\"}], \"🏷️ 女频标签\": [{\"title\": \"虐恋情深\", \"url\": \"/category-landing?category_id=481&offset={{(page-1)*20}}\"}, {\"title\": \"玄幻言情\", \"url\": \"/category-landing?category_id=248&offset={{(page-1)*20}}\"}, {\"title\": \"异世穿越\", \"url\": \"/category-landing?category_id=464&offset={{(page-1)*20}}\"}, {\"title\": \"豪门世家\", \"url\": \"/category-landing?category_id=473&offset={{(page-1)*20}}\"}, {\"title\": \"女频衍生\", \"url\": \"/category-landing?category_id=1015&offset={{(page-1)*20}}\"}, {\"title\": \"宫斗宅斗\", \"url\": \"/category-landing?category_id=246&offset={{(page-1)*20}}\"}, {\"title\": \"现言甜宠\", \"url\": \"/category-landing?category_id=395&offset={{(page-1)*20}}\"}, {\"title\": \"青春甜宠\", \"url\": \"/category-landing?category_id=749&offset={{(page-1)*20}}\"}, {\"title\": \"现言脑洞\", \"url\": \"/category-landing?category_id=267&offset={{(page-1)*20}}\"}, {\"title\": \"女频悬疑\", \"url\": \"/category-landing?category_id=747&offset={{(page-1)*20}}\"}, {\"title\": \"古言权谋\", \"url\": \"/category-landing?category_id=1317&offset={{(page-1)*20}}\"}, {\"title\": \"独宠\", \"url\": \"/category-landing?category_id=460&offset={{(page-1)*20}}\"}, {\"title\": \"现代\", \"url\": \"/category-landing?category_id=757&offset={{(page-1)*20}}\"}, {\"title\": \"民国言情\", \"url\": \"/category-landing?category_id=1017&offset={{(page-1)*20}}\"}, {\"title\": \"悬疑恋爱\", \"url\": \"/category-landing?category_id=1169&offset={{(page-1)*20}}\"}, {\"title\": \"日久生情\", \"url\": \"/category-landing?category_id=474&offset={{(page-1)*20}}\"}, {\"title\": \"职场商战\", \"url\": \"/category-landing?category_id=485&offset={{(page-1)*20}}\"}, {\"title\": \"古言脑洞\", \"url\": \"/category-landing?category_id=253&offset={{(page-1)*20}}\"}, {\"title\": \"古风世情\", \"url\": \"/category-landing?category_id=1139&offset={{(page-1)*20}}\"}, {\"title\": \"萌宝\", \"url\": \"/category-landing?category_id=28&offset={{(page-1)*20}}\"}, {\"title\": \"病娇\", \"url\": \"/category-landing?category_id=380&offset={{(page-1)*20}}\"}, {\"title\": \"团宠\", \"url\": \"/category-landing?category_id=94&offset={{(page-1)*20}}\"}, {\"title\": \"双病娇\", \"url\": \"/category-landing?category_id=870&offset={{(page-1)*20}}\"}, {\"title\": \"明星\", \"url\": \"/category-landing?category_id=486&offset={{(page-1)*20}}\"}, {\"title\": \"宠妻\", \"url\": \"/category-landing?category_id=30&offset={{(page-1)*20}}\"}, {\"title\": \"白切黑\", \"url\": \"/category-landing?category_id=853&offset={{(page-1)*20}}\"}, {\"title\": \"忠犬\", \"url\": \"/category-landing?category_id=865&offset={{(page-1)*20}}\"}, {\"title\": \"公主\", \"url\": \"/category-landing?category_id=83&offset={{(page-1)*20}}\"}, {\"title\": \"嫡女\", \"url\": \"/category-landing?category_id=88&offset={{(page-1)*20}}\"}, {\"title\": \"替身\", \"url\": \"/category-landing?category_id=470&offset={{(page-1)*20}}\"}, {\"title\": \"医生\", \"url\": \"/category-landing?category_id=487&offset={{(page-1)*20}}\"}, {\"title\": \"王妃\", \"url\": \"/category-landing?category_id=85&offset={{(page-1)*20}}\"}, {\"title\": \"位尊权重\", \"url\": \"/category-landing?category_id=1456&offset={{(page-1)*20}}\"}, {\"title\": \"厨娘\", \"url\": \"/category-landing?category_id=490&offset={{(page-1)*20}}\"}, {\"title\": \"古灵精怪\", \"url\": \"/category-landing?category_id=459&offset={{(page-1)*20}}\"}, {\"title\": \"精灵\", \"url\": \"/category-landing?category_id=89&offset={{(page-1)*20}}\"}, {\"title\": \"双学霸\", \"url\": \"/category-landing?category_id=849&offset={{(page-1)*20}}\"}, {\"title\": \"将军\", \"url\": \"/category-landing?category_id=492&offset={{(page-1)*20}}\"}, {\"title\": \"作精\", \"url\": \"/category-landing?category_id=521&offset={{(page-1)*20}}\"}, {\"title\": \"皇叔\", \"url\": \"/category-landing?category_id=87&offset={{(page-1)*20}}\"}, {\"title\": \"冰山\", \"url\": \"/category-landing?category_id=468&offset={{(page-1)*20}}\"}, {\"title\": \"皇后\", \"url\": \"/category-landing?category_id=84&offset={{(page-1)*20}}\"}, {\"title\": \"毒医\", \"url\": \"/category-landing?category_id=491&offset={{(page-1)*20}}\"}, {\"title\": \"可盐可甜\", \"url\": \"/category-landing?category_id=454&offset={{(page-1)*20}}\"}, {\"title\": \"律师\", \"url\": \"/category-landing?category_id=488&offset={{(page-1)*20}}\"}, {\"title\": \"戏精\", \"url\": \"/category-landing?category_id=863&offset={{(page-1)*20}}\"}, {\"title\": \"双面\", \"url\": \"/category-landing?category_id=469&offset={{(page-1)*20}}\"}, {\"title\": \"天作之合\", \"url\": \"/category-landing?category_id=455&offset={{(page-1)*20}}\"}, {\"title\": \"虐文\", \"url\": \"/category-landing?category_id=95&offset={{(page-1)*20}}\"}, {\"title\": \"强强\", \"url\": \"/category-landing?category_id=478&offset={{(page-1)*20}}\"}, {\"title\": \"破镜重圆\", \"url\": \"/category-landing?category_id=475&offset={{(page-1)*20}}\"}, {\"title\": \"校园\", \"url\": \"/category-landing?category_id=4&offset={{(page-1)*20}}\"}, {\"title\": \"先婚后爱\", \"url\": \"/category-landing?category_id=265&offset={{(page-1)*20}}\"}, {\"title\": \"青梅竹马\", \"url\": \"/category-landing?category_id=387&offset={{(page-1)*20}}\"}, {\"title\": \"男二上位\", \"url\": \"/category-landing?category_id=1457&offset={{(page-1)*20}}\"}, {\"title\": \"兽世\", \"url\": \"/category-landing?category_id=72&offset={{(page-1)*20}}\"}, {\"title\": \"虐渣\", \"url\": \"/category-landing?category_id=457&offset={{(page-1)*20}}\"}, {\"title\": \"暗恋\", \"url\": \"/category-landing?category_id=482&offset={{(page-1)*20}}\"}, {\"title\": \"HE\", \"url\": \"/category-landing?category_id=484&offset={{(page-1)*20}}\"}, {\"title\": \"今穿古\", \"url\": \"/category-landing?category_id=463&offset={{(page-1)*20}}\"}, {\"title\": \"女扮男装\", \"url\": \"/category-landing?category_id=388&offset={{(page-1)*20}}\"}, {\"title\": \"真假千金\", \"url\": \"/category-landing?category_id=844&offset={{(page-1)*20}}\"}, {\"title\": \"年下\", \"url\": \"/category-landing?category_id=829&offset={{(page-1)*20}}\"}, {\"title\": \"追妻\", \"url\": \"/category-landing?category_id=842&offset={{(page-1)*20}}\"}, {\"title\": \"带球跑\", \"url\": \"/category-landing?category_id=479&offset={{(page-1)*20}}\"}, {\"title\": \"双洁\", \"url\": \"/category-landing?category_id=702&offset={{(page-1)*20}}\"}, {\"title\": \"一见钟情\", \"url\": \"/category-landing?category_id=477&offset={{(page-1)*20}}\"}, {\"title\": \"古色古香\", \"url\": \"/category-landing?category_id=877&offset={{(page-1)*20}}\"}, {\"title\": \"追夫\", \"url\": \"/category-landing?category_id=872&offset={{(page-1)*20}}\"}, {\"title\": \"养崽\", \"url\": \"/category-landing?category_id=847&offset={{(page-1)*20}}\"}, {\"title\": \"胎穿\", \"url\": \"/category-landing?category_id=876&offset={{(page-1)*20}}\"}, {\"title\": \"年龄差\", \"url\": \"/category-landing?category_id=848&offset={{(page-1)*20}}\"}, {\"title\": \"久别重逢\", \"url\": \"/category-landing?category_id=839&offset={{(page-1)*20}}\"}, {\"title\": \"古穿今\", \"url\": \"/category-landing?category_id=462&offset={{(page-1)*20}}\"}, {\"title\": \"养成\", \"url\": \"/category-landing?category_id=835&offset={{(page-1)*20}}\"}, {\"title\": \"前世今生\", \"url\": \"/category-landing?category_id=523&offset={{(page-1)*20}}\"}, {\"title\": \"宠夫\", \"url\": \"/category-landing?category_id=861&offset={{(page-1)*20}}\"}, {\"title\": \"职场婚恋\", \"url\": \"/category-landing?category_id=750&offset={{(page-1)*20}}\"}, {\"title\": \"相爱相杀\", \"url\": \"/category-landing?category_id=483&offset={{(page-1)*20}}\"}, {\"title\": \"逃荒\", \"url\": \"/category-landing?category_id=557&offset={{(page-1)*20}}\"}, {\"title\": \"攻略反派\", \"url\": \"/category-landing?category_id=1459&offset={{(page-1)*20}}\"}, {\"title\": \"清穿\", \"url\": \"/category-landing?category_id=76&offset={{(page-1)*20}}\"}, {\"title\": \"家长里短\", \"url\": \"/category-landing?category_id=862&offset={{(page-1)*20}}\"}, {\"title\": \"星光璀璨\", \"url\": \"/category-landing?category_id=745&offset={{(page-1)*20}}\"}, {\"title\": \"双向奔赴\", \"url\": \"/category-landing?category_id=476&offset={{(page-1)*20}}\"}, {\"title\": \"契约婚姻\", \"url\": \"/category-landing?category_id=471&offset={{(page-1)*20}}\"}, {\"title\": \"医术\", \"url\": \"/category-landing?category_id=247&offset={{(page-1)*20}}\"}, {\"title\": \"相互救赎\", \"url\": \"/category-landing?category_id=860&offset={{(page-1)*20}}\"}, {\"title\": \"婚恋\", \"url\": \"/category-landing?category_id=34&offset={{(page-1)*20}}\"}, {\"title\": \"群穿\", \"url\": \"/category-landing?category_id=461&offset={{(page-1)*20}}\"}, {\"title\": \"隐婚\", \"url\": \"/category-landing?category_id=467&offset={{(page-1)*20}}\"}, {\"title\": \"情有独钟\", \"url\": \"/category-landing?category_id=456&offset={{(page-1)*20}}\"}, {\"title\": \"代嫁代娶\", \"url\": \"/category-landing?category_id=1458&offset={{(page-1)*20}}\"}, {\"title\": \"闪婚\", \"url\": \"/category-landing?category_id=466&offset={{(page-1)*20}}\"}, {\"title\": \"互宠\", \"url\": \"/category-landing?category_id=836&offset={{(page-1)*20}}\"}, {\"title\": \"护短\", \"url\": \"/category-landing?category_id=458&offset={{(page-1)*20}}\"}]};\nvar tab = getVariable('模式');\nvar channel = getVariable('频道') || '男频';\nvar data = [];\n// ── 设置区 ──\ndata.push(createFilter('类型', ['小说', '听书', '漫画', '短剧'], tab, '模式', 0.5, \"'当前类型'\"));\ndata.push(createFilter('频道', ['男频', '女频'], channel, '频道', 0.5, \"'分类频道'\"));\ndata.push(createFilter('音色', ['0', '1', '2', '4', '5', '6'], getVariable('音色'), '音色', 0.5, \"'听书音色'\"));\ndata.push(createFilter('清晰度', ['max', '720p', '540p', '480p', '360p'], getVariable('清晰度'), '清晰度', 0.5, \"'视频清晰度'\"));\ndata.push(createFilter('段评', ['开', '关'], getVariable('段评'), '段评', 0.5, \"'段评'\"));\ndata.push(createFilter('图片', ['保留', '移除'], getVariable('图片'), '图片', 0.5, \"'插图'\"));\n// ── 发现页搜索(漫画/短剧等无分类的类型用这里搜)──\ndata.push(createText('搜索关键词', \"setVariable('搜索关键词', infoMap['搜索关键词'])\", getVariable('搜索关键词'), 0.75, \"'书名'\"));\ndata.push({title: '🔍搜索', type: 'button', action: 'exploreSearch()', style: {layout_flexGrow: 1, layout_flexBasisPercent: 0.25}});\n// ── 类型提示 ──\nif (tab != '小说') {\n var tips = {\n 听书: '🎧 当前类型:听书 | 下方分类为小说书城,点开的书将按听书模式播放(TTS)',\n 漫画: '🎨 当前类型:漫画 | 漫画无分类,请用上方搜索框(自动加 m: 前缀)',\n 短剧: '🎬 当前类型:短剧 | 短剧无分类,请用上方搜索框(自动加 d: 前缀)'\n };\n data.push({title: tips[tab] || '', style: {layout_flexGrow: 1, layout_flexBasisPercent: 1}});\n}\n// ── 当前频道分类:组标题整行,条目一行 3 个 ──\n['精选', '标签'].forEach(function (suffix) {\n var key = (suffix === '精选'\n ? (channel === '女频' ? '🌸 女频' : '🔥 男频') + suffix\n : '🏷️ ' + channel + suffix);\n var list = groups[key] || [];\n if (!list.length) return;\n data.push({title: key, style: {layout_flexGrow: 1, layout_flexBasisPercent: 1}});\n list.forEach(function (it) {\n it.style = {layout_flexGrow: 1, layout_flexBasisPercent: 0.32};\n data.push(it);\n });\n});\nJSON.stringify(data);\n", - "searchUrl": "\nvar tab = getVariable('模式');\nvar m = String(key).match(/^([xtmdXTMD])[:::]/);\nvar q = String(key);\nif (m) {\n tab = {x: '小说', t: '听书', m: '漫画', d: '短剧'}[m[1].toLowerCase()];\n q = q.slice(2);\n}\njava.put('fq_search_tab', tab);\nvar typeMap = {'小说': 3, '听书': 2, '漫画': 8, '短剧': 11};\n`/search?query=${encodeURIComponent(q)}&tab_type=${typeMap[tab] || 3}&offset=${(page - 1) * 10}`;\n", + "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": { - "author": "author", - "bookList": "\nvar tab = java.get('fq_search_tab') || getVariable('模式');\nvar d = parseJsonSafely(result, {});\nvar list = [];\n(d.data || []).forEach(function (b) {\n if (b.book_data) {\n b.book_data.forEach(function (x) {\n x.fq_tab = tab;\n list.push(x);\n });\n } else if (b.video_data) {\n // 短剧字段与书籍不同,归一化为 book_data 结构(book_id 用 series_id 走通 detail/catalog)\n b.video_data.forEach(function (v) {\n list.push({\n book_id: v.series_id || v.vid,\n book_name: v.title || v.raw_book_name,\n author: (v.video_detail && v.video_detail.actor) || '',\n thumb_url: v.cover || '',\n abstract: v.sub_title || '',\n category: v.sub_title || '',\n fq_tab: tab\n });\n });\n }\n});\nJSON.stringify({data: list});\n$.data", - "bookUrl": "\n'/detail?book_id=' + result.book_id + '&tab=' + (result.fq_tab || getVariable('模式'));\n", - "checkKeyWord": "十日终焉", - "coverUrl": "thumb_url", - "intro": "abstract", - "kind": "category", - "name": "book_name", - "wordCount": "word_number" + "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": { - "author": "author", "bookList": "$.data.book_info[*]", - "bookUrl": "\n'/detail?book_id=' + result.book_id + '&tab=' + (result.fq_tab || getVariable('模式'));\n", - "coverUrl": "thumb_url", - "intro": "abstract", - "kind": "category", - "name": "book_name", - "wordCount": "word_number" + "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": { - "author": "$.data.author", - "canReName": "1", - "coverUrl": "$.data.thumb_url", - "init": "\njava.put('fq_tab', (String(baseUrl).match(/[?&]tab=([^&]+)/) || [])[1] || getVariable('模式'));\nresult;\n", - "intro": "\nvar tab = java.get('fq_tab') || getVariable('模式');\nvar x = (parseJsonSafely(result, {}) || {}).data || {};\nfunction v(t) { return (t != undefined && t != null && String(t).length > 0 && String(t) != 'null') ? String(t) : ''; }\nfunction cats() {\n if (x.category) return x.category;\n if (Array.isArray(x.category_v2)) return x.category_v2.map(function (c) { return c.Name || c.name || ''; }).filter(String).join('/');\n return '';\n}\nvar statusMap = {'0': '连载中', '1': '连载中', '2': '已完结', 'creation_status_end': '已完结', 'creation_status_loading': '连载中'};\nvar st = statusMap[v(x.creation_status)] || v(x.creation_status);\nvar lines = ['📡 来源:番茄 | 🔄 模式:' + tab];\n[['🌈 分类', cats()],\n ['📚 最新章节', v(x.last_chapter_title)],\n ['📝 字数', v(x.word_number)],\n ['🚩 状态', st],\n ['⭐ 评分', v(x.score)],\n ['👀 阅读人数', v(x.read_count)]\n].forEach(function (p) { if (p[1]) lines.push(' ' + p[0] + ':' + p[1]); });\nif (v(x.abstract)) lines.push('❇️─────────\\n📖 简介:\\n' + String(x.abstract).split('\\n').map(function (l) { return ' ' + l; }).join('\\n'));\nlines.join('\\n');\n", - "kind": "$.data.category_v2[*].Name", - "lastChapter": "$.data.last_chapter_title", - "name": "$.data.book_name", - "tocUrl": "/catalog?book_id={{$.data.book_id}}", - "wordCount": "$.data.word_number" + "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": "\njava.put('fq_book_id', (String(baseUrl).match(/book_id=(\\d+)/) || [])[1] || '');\n// 书籍类型按模式动态设置:安卓 8文本/32音频/64图片,iOS 0/1/2\nvar tab = java.get('fq_tab') || getVariable('模式');\nvar tMap = {'小说': 8, '听书': 32, '漫画': 64, '短剧': 8};\nvar t = tMap[tab] || 8;\nif (isIOS()) t = {'小说': 0, '听书': 1, '漫画': 2, '短剧': 0}[tab] || 0;\ntry { book.type = t; } catch (e) {}\nresult;\n$.data.item_data_list[*]", + "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 tab = java.get('fq_tab') || getVariable('模式');\nvar id = result.item_id;\nvar url;\nif (tab == '听书') {\n url = '/audio?item_ids=' + id + '&tone_id=' + getVariable('音色') + '&output=url';\n} else if (tab == '短剧') {\n url = '/video?item_ids=' + id + '&output=proxy&quality=' + getVariable('清晰度');\n} else {\n var review = getVariable('段评') != '关' ? 'keep' : 'remove';\n var img = getVariable('图片') == '移除' ? 'remove' : 'keep';\n url = '/content?item_id=' + id + '&text_mode=' + (review == 'keep' ? 'html' : 'plain') + '&review=' + review + '&image_mode=' + img;\n}\nurl;\n" + "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 base = BaseUrl();\nvar u = String(baseUrl);\nvar item = (u.match(/item_ids?=(\\d+)/) || [])[1] || '';\nvar tab = java.get('fq_tab') || getVariable('模式');\nfunction absUrl(s) { return (s && String(s).indexOf('http') != 0) ? base + s : s; }\nfunction videoUrl() {\n var v = parseJsonSafely(request('/video?item_ids=' + item + '&output=proxy&quality=' + getVariable('清晰度')), null);\n var vl = v && v.data && v.data.video_list;\n if (vl) { var ks = Object.keys(vl); if (ks.length) return absUrl(vl[ks[0]].main_url); }\n return '';\n}\nvar out = '';\nif (u.indexOf('/video') > -1) {\n // 短剧:解密代理直链,当前章节自动打开播放\n var d = parseJsonSafely(result, {});\n var vl = d.data && d.data.video_list;\n var vu = '';\n if (vl) { var ks = Object.keys(vl); if (ks.length) vu = absUrl(vl[ks[0]].main_url); }\n if (vu && book.durChapterIndex == chapter.index) {\n try { java.startBrowser(vu, title || ''); } catch (e) {}\n }\n out = vu ? `▶️ 正在加载视频…\\n若未自动播放,请复制以下直链到播放器打开:\\n${vu}` : '❌ 视频获取失败,请刷新重试';\n} else if (u.indexOf('/audio') > -1) {\n // 听书:短剧视频章优先原声音轨,其余回退 TTS 音色\n out = videoUrl();\n if (!out) {\n var a = parseJsonSafely(result, {});\n if (a.data && a.data[0] && a.data[0].main_url) out = absUrl(a.data[0].main_url);\n }\n} else {\n // 小说/漫画:段评气泡 + 空正文回退图片列表\n var review = getVariable('段评') != '关';\n var c = '';\n var d = parseJsonSafely(result, {});\n c = (d.data && d.data.content) || '';\n if (c && review) {\n // 段评 ident 域名由服务端按请求 Host 拼接,强制替换为书源地址\n c = c.replace(/(`; }).join('\\n') : ((m && m.data && m.data.content) || '❌ 内容获取失败,请刷新重试');\n }\n}\nout;\n" - }, - "weight": 0 + "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" + } } ] \ No newline at end of file diff --git a/deploy.sh b/deploy.sh index f33e4a6..46772f4 100755 --- a/deploy.sh +++ b/deploy.sh @@ -7,6 +7,10 @@ # ./deploy.sh v1.2.0 # 编译 + 部署(指定版本) # ./deploy.sh v1.2.0 --init # 首次初始化(写 systemd/nginx 配置) # ./deploy.sh v1.2.0 --no-pull # 跳过 git pull +# ./deploy.sh v1.2.0 --no-autostart # 部署后不开机自启 +# ./deploy.sh v1.2.0 --no-restart # 只部署,不重启服务 +# ./deploy.sh --stop # 停止服务 +# ./deploy.sh --start # 启动服务 # ./deploy.sh --build-only # 只编译,不部署 # ./deploy.sh --rollback # 交互式回滚到指定版本 # ./deploy.sh --rollback v1.0.0 # 直接回滚到指定版本 @@ -39,6 +43,7 @@ CERT_EMAIL="${CERT_EMAIL:-admin@${DOMAIN}}" # 证书申请邮箱(过期提醒 BUILD_TARGET="${BUILD_TARGET:-./cmd/fanqie}" # go build 的包路径 ENV_FILE="${ENV_FILE:-${SRC_DIR}/.env}" # 应用环境变量(.env.example 为模板) KEEP_VERSIONS="${KEEP_VERSIONS:-5}" # 保留最近几个版本 +AUTO_START="${AUTO_START:-true}" # 服务是否开机自启(--no-autostart 覆盖) # ==================== 颜色与日志 ==================== C_GREEN='\033[1;32m'; C_YELLOW='\033[1;33m'; C_RED='\033[1;31m'; C_BLUE='\033[1;34m'; C_RESET='\033[0m' @@ -63,6 +68,10 @@ ROLLBACK_VERSION="" LIST=false FORCE=false SYNC_NGINX=false +AUTO_START="${AUTO_START:-true}" +NO_RESTART=false +STOP=false +START=false print_help() { cat </dev/null 2>&1; then - $SUDO systemctl restart "${APP_NAME}" +# 应用开机自启设置(AUTO_START 由 --no-autostart / .deploy.env 决定) +apply_autostart() { + if [ "$AUTO_START" = true ]; then $SUDO systemctl enable "${APP_NAME}" >/dev/null 2>&1 || true else + $SUDO systemctl disable "${APP_NAME}" >/dev/null 2>&1 || true + info "开机自启已禁用(--no-autostart)" + fi +} + +# 重启服务 + 验证 +restart_and_verify() { + # 注意:不要用 "list-unit-files | grep -q"——grep -q 提前退出会让 systemctl 收到 + # SIGPIPE,在 pipefail 下整条管道判假(unit 多的机器必踩),误报"服务不存在" + if ! systemctl cat "${APP_NAME}" >/dev/null 2>&1; then warn "服务 ${APP_NAME}.service 不存在,请先执行 $0 --init" return 0 fi + apply_autostart + + if [ "$NO_RESTART" = true ]; then + info "跳过服务重启(--no-restart),需要时执行: sudo systemctl restart ${APP_NAME}" + return 0 + fi + + log "重启 ${APP_NAME} 服务..." + $SUDO systemctl restart "${APP_NAME}" + if command -v nginx >/dev/null 2>&1; then $SUDO systemctl reload nginx 2>/dev/null || $SUDO nginx -s reload 2>/dev/null || true fi @@ -342,6 +375,20 @@ if [ "$SYNC_NGINX" = true ]; then exit 0 fi +# ==================== --stop / --start ==================== +if [ "$STOP" = true ] || [ "$START" = true ]; then + systemctl cat "${APP_NAME}" >/dev/null 2>&1 || { err "服务 ${APP_NAME}.service 不存在,请先执行 $0 --init"; exit 1; } + if [ "$STOP" = true ]; then + $SUDO systemctl stop "${APP_NAME}" + log "✅ 已停止 ${APP_NAME}(开机自启设置不变)" + else + $SUDO systemctl start "${APP_NAME}" + sleep 1 + $SUDO systemctl status "${APP_NAME}" --no-pager -l | head -n 15 || true + fi + exit 0 +fi + # ==================== --rollback ==================== if [ "$ROLLBACK" = true ]; then if [ ! -d "${BIN_DIR}" ]; then