📖 淘小说API

https://tybook.taoyuewenhua.net

laizhiyu (6509)1天前

//淘小说APP接口API源
二维码导入
{
    "bookSourceComment": "\/\/淘小说APP接口API源",
    "bookSourceGroup": "APP接口",
    "bookSourceName": "📖 淘小说API",
    "bookSourceType": 0,
    "bookSourceUrl": "https:\/\/tybook.taoyuewenhua.net",
    "bookUrlPattern": "https?:\/\/(?:tybook|scdn|app)\\.taoyuewenhua\\.net\/(?:tf\/book|app\/chapters)\\?.*|https?:\/\/app\\.taoyuewenhua\\.net\/tybook\/.*|ttbook:\/\/m\\.taoyuewenhua\\.com\/(?:push|game|homepage)\/.*",
    "concurrentRate": "1\/1500",
    "customButton": false,
    "customOrder": 2,
    "enabled": true,
    "enabledCookieJar": true,
    "enabledExplore": true,
    "eventListener": false,
    "exploreUrl": "@js:\n(function(){\n  var CFG={\"男生\":{\"ctype\":\"1\",\"freeType\":\"1\",\"modules\":[[\"推荐榜\",-1000006],[\"好看到停不下来\",61]],\"ranks\":[[\"推荐榜\",100],[\"完本榜\",110],[\"人气榜\",10],[\"热搜榜\",80],[\"收藏榜\",20],[\"阅读榜\",30]],\"cats\":[[\"都市\",105000],[\"玄幻\",101000],[\"历史\",106000],[\"军事\",107000],[\"奇闻怪谈\",108000],[\"武侠\",109000],[\"科幻\",110000],[\"游戏\",111000],[\"仙侠\",102000],[\"悬疑\",112000],[\"轻小说\",113000],[\"都市修真\",114000]],\"tags\":[\"末世\",\"系统\",\"穿越\",\"重生\",\"无敌\",\"签到\",\"爽文\",\"打脸\",\"赘婿\",\"战神\",\"神医\",\"奶爸\",\"脑洞\",\"网游\",\"同人\",\"无限流\",\"种田\",\"都市\"],\"tagFrom\":\"5\"},\"女生\":{\"ctype\":\"2\",\"freeType\":\"2\",\"modules\":[[\"推荐榜\",-1000005],[\"恋爱神作\",69]],\"ranks\":[[\"推荐榜\",100],[\"完本榜\",110],[\"人气榜\",10],[\"热搜榜\",80],[\"收藏榜\",20],[\"阅读榜\",30]],\"cats\":[[\"现代言情\",204000],[\"古代言情\",203000],[\"幻想言情\",205000],[\"浪漫青春\",206000],[\"悬疑推理\",208000],[\"短篇\",210000],[\"豪门总裁\",207000],[\"穿越时空\",209000],[\"宫闱宅斗\",211000]],\"tags\":[\"重生\",\"穿越\",\"年代\",\"娱乐圈\",\"系统\",\"爽文\",\"种田\"],\"tagFrom\":\"5\"},\"完结\":{\"ctype\":\"1\",\"freeType\":\"1\",\"modules\":[[\"精品完结\",32],[\"都市爽文\",41],[\"玄幻奇文\",42],[\"最新完结\",-1000016]],\"ranks\":[[\"完本榜\",110],[\"推荐榜\",100],[\"人气榜\",10],[\"收藏榜\",20],[\"阅读榜\",30]],\"cats\":[[\"完结·都市\",105000],[\"完结·玄幻\",101000],[\"完结·历史\",106000],[\"完结·仙侠\",102000],[\"完结·科幻\",110000],[\"完结·游戏\",111000]],\"tags\":[\"爽文\",\"系统\",\"重生\",\"无敌\",\"种田\",\"年代\",\"脑洞\",\"无限流\",\"末世\",\"穿越\"],\"tagFrom\":\"5\"},\"新书\":{\"ctype\":\"1\",\"freeType\":\"1\",\"modules\":[[\"精品新书\",37],[\"都市爽文\",48],[\"玄幻奇文\",49],[\"最新上架\",-1000004]],\"ranks\":[[\"推荐榜\",100],[\"人气榜\",10],[\"热搜榜\",80],[\"新书榜\",100]],\"cats\":[[\"新书·都市\",105000],[\"新书·玄幻\",101000],[\"新书·仙侠\",102000],[\"新书·科幻\",110000],[\"新书·历史\",106000],[\"新书·游戏\",111000]],\"tags\":[\"系统\",\"穿越\",\"重生\",\"脑洞\",\"签到\",\"都市\",\"玄幻\",\"爽文\",\"无敌\",\"末世\"],\"tagFrom\":\"5\"},\"阅文好书\":{\"ctype\":\"1\",\"freeType\":\"1\",\"modules\":[[\"热门精品\",26],[\"主编强推\",27],[\"名家神作\",29],[\"完结好书\",30],[\"新书上架\",28],[\"精品推荐\",31]],\"ranks\":[[\"推荐榜\",100],[\"完本榜\",110],[\"人气榜\",10],[\"阅读榜\",30]],\"cats\":[[\"名家·都市\",105000],[\"名家·玄幻\",101000],[\"名家·仙侠\",102000],[\"名家·历史\",106000],[\"名家·科幻\",110000],[\"名家·悬疑\",112000]],\"tags\":[\"热血\",\"玄幻\",\"仙侠\",\"都市\",\"系统\",\"重生\",\"穿越\",\"爽文\",\"种田\",\"脑洞\"],\"tagFrom\":\"5\"}};\n  var NAMES=[\"男生\",\"女生\",\"完结\",\"新书\",\"阅文好书\"];\n  \/\/ 选中频道:infoMap 优先(发现页 select 真机注入),java.get 兜底(无 infoMap 版本)\n  var ch=\"\",hasIm=false;\n  try{ if(typeof infoMap!==\"undefined\" && infoMap[\"频道\"]){ch=String(infoMap[\"频道\"]);hasIm=true;} }catch(e){}\n  if(!hasIm){ try{ var v=String(java.get(\"txChannel\")||\"\"); if(v) ch=v; }catch(e){} }\n  if(!CFG[ch]) ch=\"男生\";\n  var c=CFG[ch], out=[];\n  function head(t){\n    out.push({title:t,url:\"\",type:\"button\",action:\"java.toast('\"+t+\"')\",\n      style:{layout_flexBasisPercent:1,layout_wrapBefore:true,layout_justifySelf:\"flex_start\"}});\n  }\n  function row(t,path,extra){\n    out.push({title:t,\n      url:\"@js:\\nTX_explore(java, source, \"+JSON.stringify(path)+\", \"+JSON.stringify(extra)+\", page);\",\n      type:\"url\",style:{layout_flexGrow:1,layout_flexBasisPercent:0.25}});\n  }\n  \/\/ ① 频道 select(5 选 1):infoMap 优先 → result 兜底 → 拿不到则保持原频道,\n  \/\/    再 java.refreshExplore() 清两级缓存并重绘(青橙 v7.1 成熟范式,LT\/E 均验证)\n  out.push({title:\"频道\",type:\"select\",chars:NAMES,default:\"男生\",\n    action:\"(function(){var v='';try{if(typeof infoMap!=='undefined'&&infoMap['频道'])v=String(infoMap['频道']);}catch(e){}if(!v){try{if(typeof result!=='undefined'&&result&&result.get)v=String(result.get('频道')||'');}catch(e2){}}if(v){java.put('txChannel',v);java.toast('✅ 已切换到频道:'+v);}else{java.toast('⚠ 未取到频道选项,保持原频道');}try{java.refreshExplore();}catch(e3){try{java.reUiView(true);}catch(e4){}}})()\",\n    style:{layout_flexBasisPercent:1,layout_wrapBefore:true}});\n  \/\/ ② 当前频道状态行(viewName 动态文本,返回纯文本不带外层引号)\n  out.push({title:\"当前频道\",type:\"button\",action:\"\",\n    viewName:\"'当前频道:'+(java.get('txChannel')||'男生')\",\n    style:{layout_flexBasisPercent:1,layout_wrapBefore:true}});\n  \/\/ ③ 频道真实模块(channel_books,带 page\/pageSize 由 Legado 递增,不写死页码上限)\n  head(\"▌ \"+ch+\" · 频道精选\");\n  for(var i=0;i<c.modules.length;i++){\n    row(c.modules[i][0],\"\/book_mall\/channel_books\",\n        {mcid:String(c.modules[i][1]),seed:\"0\",category:\"\",ext:\"\"});\n  }\n  \/\/ ④ 频道榜单(book_rank,brtype 来自 \/public\/mioptions 的 bookRankTabs)\n  head(\"▌ \"+ch+\" · 排行榜\");\n  for(var j=0;j<c.ranks.length;j++){\n    row(c.ranks[j][0],\"\/book_mall\/book_rank\",\n        {brtype:String(c.ranks[j][1]),ctype:c.ctype,status:\"0\"});\n  }\n  \/\/ ⑤ 频道分类(search_by_category,categoryId 来自旧源 + 真机抓包)\n  head(\"▌ \"+ch+\" · 分类\");\n  for(var k=0;k<c.cats.length;k++){\n    row(c.cats[k][0],\"\/ty\/search_by_category\",\n        {categoryId:String(c.cats[k][1]),freeType:c.freeType,from:\"13\",order:\"0\",status:\"0\"});\n  }\n  \/\/ ⑥ 频道热门标签(tags,from 用抓包实锤的 5)\n  head(\"▌ \"+ch+\" · 热门标签\");\n  for(var m=0;m<c.tags.length;m++){\n    row(c.tags[m],\"\/ty\/search_by_category\",\n        {freeType:c.freeType,from:c.tagFrom,order:\"0\",status:\"0\",tags:c.tags[m]});\n  }\n  \/\/ ⑦ 全频道通用:搜索发现(search_recommend 返回 $.data.books)\n  head(\"▌ 全站 · 搜索发现\");\n  row(\"猜你喜欢\",\"\/authopt\/search_recommend\",{ctype:c.ctype});\n  return JSON.stringify(out);\n})()",
    "header": "{\"User-Agent\": \"okhttp\/5.1.0\"}",
    "jsLib": "\/\/ ===== 淘小说 11.2.8 公共签名库 =====\n\/\/ sign = MD5( 全部请求参数按 key 字典序排列、值用 UTF-8 明文拼接 + \"&key=mibook_123456\" ).toUpperCase()\n\/\/ 实锤: 112 条请求离线碰撞 112\/112 命中;App 内 java.md5Encode 复算一致。\n\/\/ 注意:sign 的是「解码后的参数值」;URL 里才 encodeURIComponent。\n\/\/ 注意:key 也要参与排序(当业务参数恰好含 key 时不可重复追加)。\nfunction TX_signBase(p) {\n  var ks = Object.keys(p).sort();\n  var s = '';\n  for (var i = 0; i < ks.length; i++) {\n    if (i) s += '&';\n    s += ks[i] + '=' + String(p[ks[i]]);\n  }\n  return s;\n}\nfunction TX_query(p) {\n  var ks = Object.keys(p).sort();\n  var q = '';\n  for (var i = 0; i < ks.length; i++) {\n    if (i) q += '&';\n    q += ks[i] + '=' + encodeURIComponent(String(p[ks[i]]));\n  }\n  return q;\n}\n\/\/ jv = java 绑定(规则 @js 环境),显式传入以兼容 jsLib 共享 scope\nfunction TX_url(jv, p, host, path) {\n  var sign = String(jv.md5Encode(TX_signBase(p) + '&key=mibook_123456')).toUpperCase();\n  return host + path + '?' + TX_query(p) + '&sign=' + sign;\n}\n\n\/\/ 11.2.8 真机抓包的固定设备\/游客参数。\n\/\/ 对照实验实锤:book_rank\/tf\/book\/tf\/chapter_content 只校验 sign,token↔uid 可任意;\n\/\/ 唯独 \/authopt\/ty\/search_books 额外校验 token↔uid 配对(伪造 → errcode 205 Invalid token)。\n\/\/ 故此处使用真机抓包的那组配对凭证,绝不自造。\nfunction TX_dev() {\n  return {\n    android_id: '76546fb5b7fa81c2',\n    appid: 'mibook',\n    brand: 'Xiaomi',\n    channel: 'XiaoMi',\n    device_id: '38825f4366714ddba026677ee69a004c',\n    model: 'MI 8',\n    optype: '0',\n    ostype: '0',\n    osversion: '10',\n    package_name: 'com.martian.ttbook',\n    token: 'a8976c97-ab2f-4bac-b4a6-389475e77719',\n    uid: '85861337',\n    version_code: '801',\n    version_name: '11.2.8'\n  };\n}\n\/\/ 服务端不校验 t 时效(2022 年旧时间戳 \/ 未来时间戳 \/ 当前时间戳 三组均 errcode:0),\n\/\/ 但仍每次用当前时间,避免任何潜在的日志侧异常。\nfunction TX_params(extra) {\n  var p = TX_dev();\n  if (extra) for (var k in extra) p[k] = extra[k];\n  p.t = String(Date.now());\n  return p;\n}\n\n\/\/ 面板切换的线路:sv = source 绑定,由规则 @js 显式传入。\n\/\/ ★jsLib 共享 scope 不注入任何规则环境绑定(java\/book\/source\/result\/baseUrl 全无,源码级实锤),\n\/\/   所以本文件里所有函数一律「显式传参」,绝不裸用 source\/java。\nfunction TX_host(sv) {\n  var d = '';\n  try { d = String(sv.get('txApiHost') || ''); } catch (e) { d = ''; }\n  if (!d || d.indexOf('http') !== 0) d = 'https:\/\/tybook.taoyuewenhua.net';\n  return d.replace(\/\\\/+$\/, '');\n}\nfunction TX_cdn(sv) {\n  var d = '';\n  try { d = String(sv.get('txCdnHost') || ''); } catch (e) { d = ''; }\n  if (!d || d.indexOf('http') !== 0) d = 'https:\/\/scdn.taoyuewenhua.net';\n  return d.replace(\/\\\/+$\/, '');\n}\n\n\/\/ ===== 各规则入口(jv=java、sv=source,全部由规则 @js 显式传入) =====\n\/\/ 搜索:\/authopt\/ty\/search_books,Legado 的 page 从 1 开始,接口 page 从 0 开始\nfunction TX_search(jv, sv, key, page) {\n  var p = TX_params({\n    ctype: '1',\n    keywords: String(key),\n    page: String((Number(page) || 1) - 1),\n    pageSize: '10',\n    searchType: '1',\n    sourceName: 'yw'\n  });\n  return TX_url(jv, p, TX_host(sv), '\/authopt\/ty\/search_books');\n}\n\/\/ 搜索\/发现列表里的 bookUrl:\/tf\/book?bid=xxx\nfunction TX_bookUrl(jv, sv, bid) {\n  var p = TX_params({ bid: String(bid) });\n  return TX_url(jv, p, TX_host(sv), '\/tf\/book');\n}\n\/\/ 详情 → 目录:\/tf\/chapter_list 302 跳到免签名 CDN,Legado 网络栈自动跟随\n\/\/ 极简参数报 errcode 202 Empty app id ⇒ 必须带 TX_dev() 全套参数(实锤)\nfunction TX_tocUrl(jv, sv, bid) {\n  var p = TX_params({ bid: String(bid), retry: '0' });\n  return TX_url(jv, p, TX_host(sv), '\/tf\/chapter_list');\n}\n\/\/ 目录章节 → 正文:\/tf\/chapter_content?bid=&cid=&autoBuy=true\nfunction TX_contentUrl(jv, sv, bid, cid) {\n  var p = TX_params({ bid: String(bid), cid: String(cid), autoBuy: 'true' });\n  return TX_url(jv, p, TX_host(sv), '\/tf\/chapter_content');\n}\n\/\/ 发现页:book_rank 榜单 \/ search_by_category 分类 \/ channel_books 书城频道 \/ search_recommend\n\/\/ 站点无「下一页」字段,终止判据用「本页返回空 \/ 与上一页内容重复」由 Legado 自己处理,\n\/\/ 不写死页码上限(用户明确要求:站点以后加书要能自动继续)。\nfunction TX_explore(jv, sv, path, extra, page) {\n  var e = {};\n  if (extra) for (var k in extra) e[k] = extra[k];\n  e.page = String((Number(page) || 1) - 1);\n  e.pageSize = '10';\n  var p = TX_params(e);\n  return TX_url(jv, p, TX_host(sv), path);\n}\n\n\/\/ 供用户粘贴 App 分享链接时反解 bid(bookUrlPattern 命中后由详情页规则使用)\nfunction TX_bidFrom(u) {\n  var m = \/[?&]bid=([^&#]+)\/.exec(String(u));\n  return m ? decodeURIComponent(m[1]) : '';\n}",
    "lastUpdateTime": "1794548504444",
    "loginUi": "[{\"name\": \"发布页\", \"type\": \"text\", \"viewName\": \"'发布页地址(官方落地页)'\", \"style\": {\"layout_flexBasisPercent\": 0.48, \"layout_flexGrow\": 1}}, {\"name\": \"应用发布页\", \"type\": \"button\", \"action\": \"TX_pubFrom(result)\", \"viewName\": \"'💾 应用发布页地址'\", \"style\": {\"layout_flexBasisPercent\": 0.48, \"layout_flexGrow\": 1}}, {\"name\": \"获取路线\", \"type\": \"button\", \"action\": \"TX_fetchPub()\", \"viewName\": \"'🧭 一键从发布页获取网址路线'\", \"style\": {\"layout_flexBasisPercent\": 1, \"layout_wrapBefore\": true}}, {\"name\": \"测试延时\", \"type\": \"button\", \"action\": \"TX_testAll()\", \"viewName\": \"'⏱ 检测路线延迟(ms写入路线按钮)'\", \"style\": {\"layout_flexBasisPercent\": 0.48, \"layout_flexGrow\": 1}}, {\"name\": \"检查登录状态\", \"type\": \"button\", \"action\": \"TX_checkLogin()\", \"viewName\": \"'🔍 检查登录状态\/游客凭证'\", \"style\": {\"layout_flexBasisPercent\": 0.48, \"layout_flexGrow\": 1}}, {\"name\": \"显示线路\", \"type\": \"button\", \"action\": \"TX_toast(TX_found().join(' \/ '))\", \"viewName\": \"'📋 显示当前全部路线'\", \"style\": {\"layout_flexBasisPercent\": 0.48, \"layout_flexGrow\": 1}}, {\"name\": \"打开网站\", \"type\": \"button\", \"action\": \"TX_web()\", \"viewName\": \"'🌐 打开官方落地页'\", \"style\": {\"layout_flexBasisPercent\": 0.48, \"layout_flexGrow\": 1}}, {\"name\": \"当前线路\", \"type\": \"button\", \"action\": \"\", \"viewName\": \"TX_curLineText()\", \"style\": {\"layout_flexBasisPercent\": 1, \"layout_wrapBefore\": true}}, {\"name\": \"路线标题\", \"type\": \"button\", \"action\": \"\", \"viewName\": \"'━━ 点击切换路线(延时见按钮名)━━'\", \"style\": {\"layout_flexBasisPercent\": 1, \"layout_wrapBefore\": true}}, {\"name\": \"路线1\", \"type\": \"button\", \"action\": \"TX_switch(1)\", \"viewName\": \"TX_lineBtn(1)\", \"style\": {\"layout_flexBasisPercent\": 0.48, \"layout_flexGrow\": 1}}, {\"name\": \"路线2\", \"type\": \"button\", \"action\": \"TX_switch(2)\", \"viewName\": \"TX_lineBtn(2)\", \"style\": {\"layout_flexBasisPercent\": 0.48, \"layout_flexGrow\": 1}}, {\"name\": \"路线3\", \"type\": \"button\", \"action\": \"TX_switch(3)\", \"viewName\": \"TX_lineBtn(3)\", \"style\": {\"layout_flexBasisPercent\": 0.48, \"layout_flexGrow\": 1}}, {\"name\": \"路线4\", \"type\": \"button\", \"action\": \"TX_switch(4)\", \"viewName\": \"TX_lineBtn(4)\", \"style\": {\"layout_flexBasisPercent\": 0.48, \"layout_flexGrow\": 1}}, {\"name\": \"路线5\", \"type\": \"button\", \"action\": \"TX_switch(5)\", \"viewName\": \"TX_lineBtn(5)\", \"style\": {\"layout_flexBasisPercent\": 0.48, \"layout_flexGrow\": 1}}, {\"name\": \"路线6\", \"type\": \"button\", \"action\": \"TX_switch(6)\", \"viewName\": \"TX_lineBtn(6)\", \"style\": {\"layout_flexBasisPercent\": 0.48, \"layout_flexGrow\": 1}}]",
    "loginUrl": "\/\/ ========== 淘小说 11.2.8 运维面板 ==========\n\/\/ 全部函数挂在 loginUrl;SourceLoginDialog 用 \"$loginJS\\n$action\" 一起 eval。\n\/\/ 绑定差异(源码级实锤):loginUi<js> 无 source\/java;按钮 action 有 java(SourceLoginJsExtensions)\n\/\/ 且 source 仍绑定;viewName 表达式用 source 可用但不保证 java ⇒ 视图函数只读 source。\n\/\/ 兼容 LT(gedoor\/LegadoTeam) 与 E(Luoyacheng\/legado-E):V1 静态 loginUi 是 E 版唯一可用形态。\n\nvar TX_MAX = 6;\nvar TX_PUB_DEFAULT = 'https:\/\/m.taoyuewenhua.com';\nvar TX_API_DEFAULT = 'https:\/\/tybook.taoyuewenhua.net';\nvar TX_CDN_DEFAULT = 'https:\/\/scdn.taoyuewenhua.net';\nvar TX_UA = 'okhttp\/5.1.0';\n\/\/ 内置线路池:全部来自 2026-11-12 真机抓包 + 实测可达,不臆造域名。\n\/\/ ★不收录 https:\/\/app.taoyuewenhua.net:该域仅用于 assetlinks.json(manifest 实锤),\n\/\/   实测根路径 403,放进线路池会误导用户切到不可用线路。\n\/\/ ★收录 m.taoyuewenhua.com 作为官方落地页(实测 200\/7069B),但它不是 API 线路,\n\/\/   切换时 TX_switch 会把它当发布页展示,不会替换 txApiHost(见下)。\nvar TX_FALLBACK = [\n  'https:\/\/tybook.taoyuewenhua.net',\n  'https:\/\/scdn.taoyuewenhua.net',\n  'https:\/\/m.taoyuewenhua.com',\n  'http:\/\/scdn.taoyuewenhua.net'\n];\n\nfunction login() { return 'ok'; }   \/\/ 必须实现,否则点右上角 √ 抛 \"Function login not implements!!!\"\n\nfunction TX_toast(t) {\n  try { java.longToast(String(t)); } catch (e) { try { java.toast(String(t)); } catch (e2) {} }\n}\nfunction TX_gv(k, def) {\n  var v = '';\n  try { v = String(source.get(k)); } catch (e) { v = ''; }\n  if (v === '' || v === 'null' || v === 'undefined') v = def || '';\n  return v;\n}\nfunction TX_norm(u) {\n  var s = String(u || '').trim().replace(\/\\\/+$\/, '');\n  if (!s) return '';\n  if (s.indexOf('http') !== 0) s = 'https:\/\/' + s;\n  var m = \/^(https?:\\\/\\\/[^\/]+)\/.exec(s);\n  return m ? m[1] : s;\n}\nfunction TX_of(u) {\n  var m = \/^https?:\\\/\\\/([^\/]+)\/.exec(String(u));\n  return m ? m[1] : String(u);\n}\nfunction TX_hdr() { return JSON.stringify({'User-Agent': TX_UA}); }\nfunction TX_refresh() {\n  try { java.reLoginView(false); } catch (e) {\n    try { java.reUiView(false); } catch (e2) {}\n  }\n}\n\n\/\/ ---------- 发布页输入框 ----------\nfunction TX_pub() {\n  var p = TX_norm(TX_gv('txPub', TX_PUB_DEFAULT));\n  return p || TX_PUB_DEFAULT;\n}\nfunction TX_pubFrom(r) {\n  var v = '';\n  try { if (r && r.get) v = String(r.get('发布页') || ''); } catch (e0) {}\n  if (!v) { try { v = String(r['发布页'] || ''); } catch (eA) {} }\n  if (!v) { try { if (result && result.get) v = String(result.get('发布页') || ''); } catch (e1) {} }\n  if (!v) { try { if (result) v = String(result['发布页'] || ''); } catch (eB) {} }\n  if (!v || !v.trim()) v = TX_PUB_DEFAULT;\n  var p = TX_norm(v);\n  TX_toast('⏳ 正在应用发布页:' + TX_of(p) + ' …');\n  try { source.put('txPub', p); } catch (e) { TX_toast('❌ 写入失败:' + e); return; }\n  TX_toast('✅ 发布页已设为 ' + TX_of(p) + ',请点「🧭 一键从发布页获取网址路线」');\n  TX_refresh();\n}\n\n\/\/ ---------- 线路池持久化 ----------\nfunction TX_setFound(list) {\n  var out = [], i, u;\n  for (i = 0; i < list.length && out.length < TX_MAX; i++) {\n    u = TX_norm(list[i]);\n    if (u && out.indexOf(u) < 0) out.push(u);\n  }\n  if (!out.length) out = TX_FALLBACK.slice(0);\n  try { source.put('txFound', out.join(' ')); } catch (e) {}\n  for (i = 0; i < TX_MAX; i++) { try { source.put('txDelay' + i, ''); } catch (e2) {} }\n  return out;\n}\nfunction TX_found() {\n  var s = TX_gv('txFound', '');\n  if (!s) return TX_setFound(TX_FALLBACK);\n  var a = s.split(\/\\s+\/), out = [], i;\n  for (i = 0; i < a.length && out.length < TX_MAX; i++) {\n    var u = TX_norm(a[i]);\n    if (u.indexOf('http') === 0 && out.indexOf(u) < 0) out.push(u);\n  }\n  return out.length ? out : TX_setFound(TX_FALLBACK);\n}\n\/\/ ★不能用 .map(TX_host):jsLib 的 TX_host(sv) 需要显式 source 参数,作为 map 回调会拿到\n\/\/   (element,index,array) 三个实参导致返回错值甚至抛错,中断按钮后半段 Toast。统一改 TX_of。\nfunction TX_foundText() { return TX_found().map(TX_of).join(' \/ '); }\n\n\/\/ ---------- 🧭 一键从发布页获取网址路线 ----------\n\/\/ 分阶段 Toast:开始 → 抓取 → 解析 → 写池 → 完成 \/ 失败兜底\nfunction TX_fetchPub() {\n  var pub = TX_pub();\n  TX_toast('🧭 阶段1\/4:正在打开发布页 ' + TX_of(pub) + ' …');\n  var raw = '', err = '', ms = 0;\n  try {\n    var t0 = Date.now();\n    var r0 = java.connect(pub + '\/', TX_hdr(), 12000);\n    ms = Date.now() - t0;\n    raw = String(r0.body());\n    if (r0.code && r0.code() !== 200) err = 'HTTP ' + r0.code();\n  } catch (e) { err = String(e).substring(0, 90); }\n  if (!err && (raw.length < 20 || raw.indexOf('Exception') === 0)) err = '内容异常(' + raw.length + 'B)';\n  if (err) {\n    TX_toast('⚠ 阶段1失败:' + err + ';载入内置 ' + TX_FALLBACK.length + ' 条兜底线路');\n    TX_setFound(TX_FALLBACK);\n    TX_toast('✅ 已载入内置线路,请点「⏱ 检测路线延迟」测速');\n    TX_refresh(); return;\n  }\n  TX_toast('🔓 阶段2\/4:发布页已获取(' + raw.length + 'B \/ ' + ms + 'ms),正在解析同族域名…');\n  var lines = [], m, u, hn;\n  \/\/ 从发布页正文抽取 taoyuewenhua.com\/net 同族域名(排除纯资源\/图床)\n  var re = \/https?:\\\/\\\/([a-z0-9][a-z0-9.\\-]*\\.taoyuewenhua\\.(?:com|net))\/gi;\n  while ((m = re.exec(raw)) !== null) {\n    u = TX_norm(m[0]); hn = TX_of(u);\n    if (lines.indexOf(u) < 0) lines.push(u);\n  }\n  \/\/ 发布页自身 host 也算一条路线\n  var self = TX_norm(pub);\n  if (self && lines.indexOf(self) < 0) lines.push(self);\n  \/\/ ★池首固定为「当前正在用的 API 主线路」,发布页解析出的域追加在后:\n  \/\/   避免用户打开面板看到的第 1 个按钮是落地页(不是 API),误点后以为切了线路。\n  var lines2 = [TX_norm(TX_gv('txApiHost', TX_API_DEFAULT))];\n  var i;\n  for (i = 0; i < lines.length && lines2.length < TX_MAX; i++) {\n    if (lines2.indexOf(lines[i]) < 0) lines2.push(lines[i]);\n  }\n  \/\/ 内置兜底补齐(真机抓包实锤的线路)\n  for (i = 0; i < TX_FALLBACK.length && lines2.length < TX_MAX; i++) {\n    if (lines2.indexOf(TX_FALLBACK[i]) < 0) lines2.push(TX_FALLBACK[i]);\n  }\n  lines = lines2;\n  TX_toast('🔎 阶段3\/4:解析到 ' + lines.length + ' 条候选,正在写入线路按钮…');\n  var got = TX_setFound(lines);\n  TX_toast('✅ 阶段4\/4:共 ' + got.length + ' 条路线 → ' + got.map(TX_of).join(' \/ '));\n  TX_toast('📌 延时尚未写入,请点「⏱ 检测路线延迟(ms写入路线按钮)」');\n  TX_refresh();\n}\n\n\/\/ ---------- ⏱ 检测路线延迟(ms 写进各网址按钮) ----------\n\/\/ 判活按线路类型分别走真实可用路径(全部为非搜索接口,用户主动点击才触发):\n\/\/   · tybook API 线路 → 现算 sign 的 \/auth\/account.do(游客账户探针,返回 errcode:0)\n\/\/   · scdn CDN 线路   → 免签名静态目录 \/app\/chapters\/{bid}.json\n\/\/   · 官方落地页 m.   → 根路径 HTML(200 \/ 7069B)\n\/\/ ★坑(实测):tybook 根路径返回 39B 的 {\"errcode\":202,\"errmsg\":\"Empty app id\"},\n\/\/   用「根路径 + 长度阈值」判活会把正常 API 线路误标成「内容异常」;必须用带完整\n\/\/   参数的签名接口。java.connect 失败时返回异常堆栈串而非抛异常,须显式排除。\nfunction TX_probeUrl(u) {\n  if (u.indexOf('scdn.taoyuewenhua.net') > -1) {\n    return u + '\/app\/chapters\/yw_20120838908848304.json';\n  }\n  if (u.indexOf('tybook.taoyuewenhua.net') > -1) {\n    return TX_url(java, TX_params({}), u, '\/auth\/account.do');\n  }\n  return u + '\/';\n}\nfunction TX_testAll() {\n  var list = TX_found();\n  TX_toast('⏱ 阶段1\/3:开始测速,共 ' + list.length + ' 条路线(非搜索接口,不触发搜索风控)…');\n  var i, r, b, ms, tag, best = -1, bestMs = 999999;\n  for (i = 0; i < list.length; i++) {\n    ms = -1; tag = '✗ 不可达';\n    try {\n      var t0 = Date.now();\n      r = java.connect(TX_probeUrl(list[i]), TX_hdr(), 10000);\n      b = String(r.body());\n      if (b.indexOf('Exception') > -1 || b.indexOf('timeout') > -1) {\n        tag = '✗ 不可达';\n      } else if (r.code && (r.code() < 200 || r.code() >= 400)) {\n        tag = '✗ HTTP\/' + r.code();\n      } else if (b.indexOf('\"errcode\":0') > -1 || b.indexOf('\"chapterList\"') > -1 || b.length > 500) {\n        ms = Date.now() - t0;\n        try { if (r.callTime) { var ct = Number(r.callTime()); if (ct > 0) ms = ct; } } catch (eC) {}\n        tag = ms + 'ms';\n        if (ms < bestMs) { bestMs = ms; best = i; }\n      } else {\n        tag = '✗ 内容异常(' + b.length + 'B)';\n      }\n    } catch (e) { tag = '✗ ' + String(e).substring(0, 24); }\n    try { source.put('txDelay' + i, tag); } catch (e2) {}\n    TX_toast('  · 路线' + (i + 1) + ' ' + TX_of(list[i]) + ' → ' + tag);\n  }\n  TX_toast('📝 阶段2\/3:延时已写入各网址按钮,正在刷新面板…');\n  if (best >= 0) {\n    try { source.put('txBest', String(best)); } catch (e3) {}\n    try { source.put('txDelay' + best, '⚡' + bestMs + 'ms'); } catch (e4) {}\n  }\n  TX_refresh();\n  TX_toast('✅ 阶段3\/3:测速完成' + (best >= 0 ? ',最快 ' + TX_of(list[best]) + ' ⚡' + bestMs + 'ms(未自动切换,请手动点路线按钮)' : ',全部不可达'));\n}\n\n\/\/ ---------- 🔍 检查登录状态 ----------\n\/\/ 本源是游客态:无账号体系。该按钮给出「是否需要登录」的明确结论 + 凭证健康度。\n\/\/ 只打 \/auth\/account.do(非搜索接口),不会触发搜索风控。\nfunction TX_checkLogin() {\n  TX_toast('🔍 阶段1\/3:正在检查游客凭证与接口连通性…');\n  var p = TX_params({});\n  var api = TX_gv('txApiHost', TX_API_DEFAULT);\n  var u = TX_url(java, p, api, '\/auth\/account.do');\n  var r = null, b = '', code = 0;\n  try { r = java.connect(u, TX_hdr(), 10000); b = String(r.body()); code = Number(r.code()); }\n  catch (e) { b = 'Exception ' + e; }\n  var ok = code >= 200 && code < 400 && b.indexOf('\"errcode\":0') > -1;\n  var isVip = \/\"isVip\":true\/.test(b);\n  TX_toast('📡 阶段2\/3:\/auth\/account.do → HTTP ' + code + ' \/ ' + b.length + 'B');\n  if (ok) {\n    TX_toast('✅ 阶段3\/3:游客凭证有效,无需登录(isVip=' + isVip + ');搜索\/详情\/目录\/正文均可用');\n  } else {\n    TX_toast('❌ 阶段3\/3:接口异常 ' + b.slice(0, 100) + ';请在「路线」里切换到 ⚡ 最快线路后重试');\n  }\n  try { source.put('txLoginCheck', ok ? '游客态正常' : '异常'); } catch (e2) {}\n  TX_refresh();\n}\nfunction TX_web() {\n  TX_toast('🌐 正在打开官方落地页 ' + TX_of(TX_pub()) + ' …');\n  try { java.openUrl(TX_pub()); TX_toast('✅ 已调起浏览器'); }\n  catch (e) { TX_toast('⚠ 无法调起浏览器:' + String(e).slice(0, 80) + ';地址:' + TX_pub()); }\n}\n\n\/\/ ---------- 线路切换(各网站一个按钮) ----------\n\/\/ 切线路时同时写 txApiHost \/ txCdnHost:若选中的是 scdn 则只切 CDN,API 保持默认;\n\/\/ 其余线路切 API。这样「各网站点击切换」对真实生效的域名语义正确。\nfunction TX_switch(n) {\n  var list = TX_found();\n  if (n < 1 || n > list.length) { TX_toast('⚠ 路线' + n + ' 不存在,当前只有 ' + list.length + ' 条'); return; }\n  var u = list[n - 1];\n  TX_toast('⏳ 正在切换到路线' + n + ':' + TX_of(u) + ' …');\n  try {\n    if (u.indexOf('scdn.taoyuewenhua.net') > -1) {\n      \/\/ CDN 线路:只切目录域名,API 保持默认\n      source.put('txCdnHost', u);\n      source.put('txApiHost', TX_API_DEFAULT);\n    } else if (u.indexOf('tybook.taoyuewenhua.net') > -1) {\n      \/\/ 主 API 线路:切 API,CDN 回默认\n      source.put('txApiHost', u);\n      source.put('txCdnHost', TX_CDN_DEFAULT);\n    } else {\n      \/\/ 官方落地页等「非 API 域」:只记发布页,不动 API\/CDN,\n      \/\/ 避免用户误切到 403 的 assetlinks 域导致整源不可用。\n      source.put('txPub', u);\n    }\n    source.put('txLine', String(n));\n  } catch (e) { TX_toast('❌ 写入失败:' + e); return; }\n  TX_toast('✅ 已切换到 ' + TX_of(u) + ';请重新搜索\/刷新书城验证');\n  TX_refresh();\n}\n\n\/\/ ---------- viewName 动态文本(只读 source,不碰 java) ----------\n\/\/ ★viewName 为 JS 表达式时,求值结果经 normalizeJsResult「原样显示、不剥引号」\n\/\/   (源码级实锤),因此这两个函数返回纯文本,禁止自带外层单引号。\nfunction TX_lineBtn(n) {\n  var list = TX_found();\n  if (n > list.length) return '— 备用位 —';\n  var u = list[n - 1];\n  var d = TX_gv('txDelay' + (n - 1), '');\n  var cur = '';\n  try { cur = String(source.get('txLine') || ''); } catch (e) {}\n  var mark = (String(n) === cur) ? '● ' : '○ ';\n  return mark + TX_of(u) + (d ? ' ' + d : '');\n}\nfunction TX_curLineText() {\n  var api = TX_gv('txApiHost', TX_API_DEFAULT);\n  var cdn = TX_gv('txCdnHost', TX_CDN_DEFAULT);\n  var st = TX_gv('txLoginCheck', '未检查');\n  return '当前 API:' + TX_of(api) + ' | CDN:' + TX_of(cdn) + ' | 状态:' + st;\n}",
    "respondTime": 180000,
    "ruleBookInfo": {
        "author": "$.authorName",
        "coverUrl": "$.coverUrl",
        "init": "$.data",
        "intro": "<br>\n📂 分类:{{$.categoryName}}\n🎗 标签:{{$.tagList##\\s##,}}\n👤 主角:{{$.roleList##\\s##,}}\n🕰 更新:{{java.timeFormat(java.getString('$.latestChapterUpdateTime'))}}\n📖 字数:{{$.allWords}}\n📝 简介:\n{{$.intro}}",
        "kind": "{{$.categoryName}}\n{{java.getString('$.status')=='50'?'完结':'连载'}}\n{{$.tagList##\\s##·}}",
        "lastChapter": "$.latestChapter",
        "name": "$.title",
        "tocUrl": "@js:\n\/\/ {{$.bid}} 由 Legado 模板先求值,再进 @js\nTX_tocUrl(java, source, '{{$.bid}}');",
        "wordCount": "$.allWords"
    },
    "ruleContent": {
        "content": "$.data.content"
    },
    "ruleExplore": {
        "author": "$.authorName",
        "bookList": "$.data.bookList||$.data.books",
        "bookUrl": "sourceId@js:\n\/\/ result = 该条书籍 JSON 的 sourceId 字段值(前缀规则 sourceId@js: 已取出)\nTX_bookUrl(java, source, result);",
        "coverUrl": "$.coverUrl",
        "intro": "$.intro",
        "kind": "{{$.categoryName}}\n{{java.getString('$.status')=='50'?'完结':'连载'}}\n{{$.tagList##\\s##·}}",
        "name": "$.title",
        "wordCount": "$.allWords"
    },
    "ruleSearch": {
        "author": "$.authorName",
        "bookList": "$.data.bookList",
        "bookUrl": "sourceId@js:\n\/\/ result = 该条书籍 JSON 的 sourceId 字段值(前缀规则 sourceId@js: 已取出)\nTX_bookUrl(java, source, result);",
        "coverUrl": "$.coverUrl",
        "intro": "$.intro",
        "kind": "{{$.categoryName}}\n{{java.getString('$.status')=='50'?'完结':'连载'}}\n{{$.tagList##\\s##·}}",
        "name": "$.title",
        "wordCount": "$.allWords"
    },
    "ruleToc": {
        "chapterList": "$.data.chapterList",
        "chapterName": "$.title",
        "chapterUrl": "@js:\nTX_contentUrl(java, source, '{{$.bid}}', '{{$.cid}}');",
        "updateTime": "{{$.words}} 字 · {{java.timeFormat(java.getString('$.modifiedOn'))}}"
    },
    "searchUrl": "@js:\nTX_search(java, source, key, page);",
    "variableComment": "本源无需填写变量;线路切换请用「菜单→登录」里的路线按钮。",
    "weight": 0
}
广告