📚书山聚合

https://search.shusan.icu

书山 (1935)3小时前

2026.2.3日更新:
未登录状态下将禁止搜索
🚫版本为必要更新,否则后续会无法搜索


❤️‍🔥登录必看❤️‍🔥
1.输入邮箱密码后必须点✓保存生效,
否则读取不到输入的账号密码
2.跳转卡顿在设置→其他设置中清除WebView缓存

🔔适配兼容
IOS仅适配源阅

🔥注:阅读请使用最新测试版
1.所有源站每20秒最多12章,把预下载调到0-3之间使用
💯[打赏VIP无请求限制,专属书源]
2.自定义源站示例:番茄小说,七猫小说  多个源站用英文,号分隔
源站名参考 登录URL //书源配置列表 v参数
3.源站带有❇️图标表示只能指定或@搜索

4.搜索模式:
书名@来源 多个来源用英文,号分隔
示例: 
系统@番茄小说,多个源站使用英文,号分隔
书名@类型 返回对应类型所有源站数据,可直接使用@方式搜索或者点击登陆切换模式
示例: 
系统@小说  听书  漫画  短剧等
默认书名排序
指定作者优先:
作者名@作者   作者名@作者,源站
二维码导入
{
    "bookSourceComment": "2026.2.3日更新:\n未登录状态下将禁止搜索\n🚫版本为必要更新,否则后续会无法搜索\n\n\n❤️‍🔥登录必看❤️‍🔥\n1.输入邮箱密码后必须点✓保存生效,\n否则读取不到输入的账号密码\n2.跳转卡顿在设置→其他设置中清除WebView缓存\n\n🔔适配兼容\nIOS仅适配源阅\n\n🔥注:阅读请使用最新测试版\n1.所有源站每20秒最多12章,把预下载调到0-3之间使用\n💯[打赏VIP无请求限制,专属书源]\n2.自定义源站示例:番茄小说,七猫小说  多个源站用英文,号分隔\n源站名参考 登录URL \/\/书源配置列表 v参数\n3.源站带有❇️图标表示只能指定或@搜索\n\n4.搜索模式:\n书名@来源 多个来源用英文,号分隔\n示例: \n系统@番茄小说,多个源站使用英文,号分隔\n书名@类型 返回对应类型所有源站数据,可直接使用@方式搜索或者点击登陆切换模式\n示例: \n系统@小说  听书  漫画  短剧等\n默认书名排序\n指定作者优先:\n作者名@作者   作者名@作者,源站",
    "bookSourceGroup": "聚合书源",
    "bookSourceName": "📚书山聚合",
    "bookSourceType": 0,
    "bookSourceUrl": "https:\/\/search.shusan.icu",
    "bookUrlPattern": "https?:\\\/\\\/(?:[a-zA-Z0-9-]+\\.)*shusan\\.icu\\\/details.*",
    "customButton": false,
    "customOrder": -1,
    "enabled": true,
    "enabledCookieJar": true,
    "enabledExplore": true,
    "eventListener": false,
    "exploreUrl": "<js>\nlet config = (() => {\n    try {\n        let cfg = JSON.parse(source.getVariable())[0] || {};\n        if (!cfg.host || !cfg.gender) {\n            cfg = {gender: \"boy\", host: getServerHost()};\n            source.setVariable(JSON.stringify([cfg]));\n            java.toast(\"配置初始化完成\");\n        }\n        return cfg;\n    } catch(e) {\n        let cfg = {gender: \"boy\", host: getServerHost()};\n        source.setVariable(JSON.stringify([cfg]));\n        return cfg;\n    }\n})();\n    \nlet rawVariable = source.getVariable();\nlet 个人中心 = 1;\nlet obj = (title, url, type) => ({\n  title: title,\n  url: url,\n  style: { layout_flexGrow: 1, layout_flexBasisPercent: type }\n});\n\nlet sort = [];\nlet push = (title, url, type) => sort.push(obj(title, url, type));\n\nlet currentType = \"\";\nlet currentSource = \"\";\ntry {\n  let vArray = JSON.parse(rawVariable);\n  if (vArray.length > 0) {\n    let v = vArray[0];\n    currentType = v.gender || \"\";\n    currentSource = v.source || \"\";\n  }\n} catch(e) {\n  currentType = \"\";\n  currentSource = \"\";\n}\n\nconst getGenderDisplayName = (gender) => {\n  if (gender == \"boy\") return \"男频\";\n  if (gender == \"girl\") return \"女频\";\n  return \"\";\n};\n\nconst genderDisplay = getGenderDisplayName(currentType);\n\npush(`当前为【${currentSource || \"番茄小说\"}】${genderDisplay ? `【${genderDisplay}】` : ''}`, \"\", 1);\n\nif (currentSource == \"晋江\") {\n\t   sort.push({\n        title: \"📝点击签到\",\n        url: `${getServerHost()}\/jj_signin`,\n        style: { layout_flexGrow: 1, layout_flexBasisPercent: 0.29 }\n    });\n    \n    sort.push({\n        title: \"📂我的收藏\",\n        url: `${getServerHost()}\/jjbookshelf?token=`,\n        style: { layout_flexGrow: 1, layout_flexBasisPercent: 0.29 }\n    });\n}\n\nconst excludedSources = [\"小说\", \"听书\", \"漫画\", \"视频\", \"短剧\", \"音频\",\"番茄小说\"];\n\nif (currentSource && !excludedSources.includes(currentSource)) {\n  try {\n    let apiUrl = `${getServerHost()}\/type_api?source=${currentSource}`;\n    if (currentType) {\n      apiUrl += `&gender=${currentType}`;\n    }\n    \n    let response = JSON.parse(java.ajax(apiUrl));\n    \n    if (response.data && response.data.found) {\n      response.data.found.forEach(item => {\n        let finalUrl = \"\";\n        if (item.url) {\n          finalUrl = `${getServerHost()}\/type_api?source=${currentSource}&page={{page}}&url=${item.url}`;\n          if (currentType) {\n            finalUrl += `&gender=${currentType}`;\n          }\n        }\n        sort.push({\n          title: item.title,\n          url: finalUrl,\n          style: item.style || { layout_flexGrow: 1, layout_flexBasisPercent: 0.29 }\n        });\n      });\n    } else {\n      push(`【${currentSource}】${genderDisplay ? `【${genderDisplay}】` : ''}暂无数据`, \"\", 1);\n    }\n  } catch(e) {\n    push(`【${currentSource}】${genderDisplay ? `【${genderDisplay}】` : ''}暂无数据`, \"\", 1);\n  }\n  JSON.stringify(sort);\n} else {\n  let recommendUrl = getServerHost() + \"\/read_recommend?session=\" + getSessionId();\n  push('个性推荐', recommendUrl, 0.29);\n  push('巅峰榜单', 'https:\/\/fanqienovel.com\/api\/author\/misc\/top_book_list\/v1\/?limit=100&offset={{(page-1)*100}}', 0.29);\n  push('出版榜单', 'https:\/\/fanqienovel.com\/api\/node\/publication\/list?page_index={{(page-1)*100}}&page_count=100', 0.29);\n  push('爆更榜单', 'https:\/\/api-lf.fanqiesdk.com\/api\/novel\/channel\/homepage\/rank\/rank_list\/v2\/?aid=13&limit=50&offset={{(page-1)*100}}&side_type=15&type=1', 0.29);\n  push('黑马榜单', 'https:\/\/api-lf.fanqiesdk.com\/api\/novel\/channel\/homepage\/rank\/rank_list\/v2\/?aid=13&limit=50&offset={{(page-1)*100}}&side_type=13&type=1', 0.29);\n  push('热搜榜单', 'https:\/\/api-lf.fanqiesdk.com\/api\/novel\/channel\/homepage\/rank\/rank_list\/v2\/?aid=13&limit=50&offset={{(page-1)*100}}&side_type=12&type=1', 0.29);\n\n  let showCategories = currentType == \"girl\" ? [0] : [1];\n  \n  showCategories.forEach(genderCode => {\n    let isMale = genderCode == 1;\n    let genderLabel = isMale ? \"男频\" : \"女频\";\n    \n    let categories = isMale ? [\n      {id: 1141, name: \"西方奇幻\"},\n      {id: 1140, name: \"东方仙侠\"},\n      {id: 8, name: \"科幻末世\"},\n      {id: 261, name: \"都市日常\"},\n      {id: 124, name: \"都市修真\"},\n      {id: 1014, name: \"都市高武\"},\n      {id: 273, name: \"历史古代\"},\n      {id: 27, name: \"战神赘婿\"},\n      {id: 263, name: \"都市种田\"},\n      {id: 258, name: \"传统玄幻\"},\n      {id: 272, name: \"历史脑洞\"},\n      {id: 539, name: \"悬疑脑洞\"},\n      {id: 262, name: \"都市脑洞\"},\n      {id: 257, name: \"玄幻脑洞\"},\n      {id: 751, name: \"悬疑灵异\"},\n      {id: 504, name: \"抗战谍战\"},\n      {id: 746, name: \"游戏体育\"},\n      {id: 718, name: \"动漫衍生\"},\n      {id: 1016, name: \"男频衍生\"}\n    ] : [\n      {id: 1139, name: \"古风世情\"},\n      {id: 8, name: \"科幻末世\"},\n      {id: 746, name: \"游戏体育\"},\n      {id: 1015, name: \"女频衍生\"},\n      {id: 248, name: \"玄幻言情\"},\n      {id: 23, name: \"种田\"},\n      {id: 79, name: \"年代\"},\n      {id: 267, name: \"现言脑洞\"},\n      {id: 246, name: \"宫斗宅斗\"},\n      {id: 539, name: \"悬疑脑洞\"},\n      {id: 253, name: \"古言脑洞\"},\n      {id: 24, name: \"快穿\"},\n      {id: 749, name: \"青春甜宠\"},\n      {id: 745, name: \"星光璀璨\"},\n      {id: 747, name: \"女频悬疑\"},\n      {id: 750, name: \"职场婚恋\"},\n      {id: 748, name: \"豪门总裁\"},\n      {id: 1017, name: \"民国言情\"}\n    ];\n    \n    push(`❤️${genderLabel}阅读榜❤️`, \"\", 1);\n    \n    categories.forEach(cat => {\n      sort.push({\n        title: cat.name,\n        url: `${getServerHost()}\/style_top?rank_list_type=3&offset={{(page-1)*10}}&limit=10&category_id=${cat.id}&gender=${genderCode}&rankMold=2`,\n        style: { layout_flexGrow: 1, layout_flexBasisPercent: 0.29 }\n      });\n    });\n    \n    push(`❤️${genderLabel}新书榜❤️`, \"\", 1);\n    \n    categories.forEach(cat => {\n      sort.push({\n        title: cat.name,\n        url: `${getServerHost()}\/style_top?rank_list_type=3&offset={{(page-1)*10}}&limit=10&category_id=${cat.id}&gender=${genderCode}&rankMold=2`,\n        style: { layout_flexGrow: 1, layout_flexBasisPercent: 0.29 }\n      });\n    });\n  });\n\n  let fqCategoryArr = [];\n  showCategories = currentType == \"girl\" ? [0] : [1];\n\n  showCategories.forEach(i => {\n    try {\n      let response = JSON.parse(java.ajax(`${getServerHost()}\/type_style?new_category_tab=${i}`));\n      let $ = response.data.category_tab_data;\n      \n      let tabName = $.tab_name || (i == 0 ? \"女频\" : \"男频\");\n      fqCategoryArr.push(obj(`❤️${tabName}分类❤️`, null, 1));\n      \n      $.cell_data.forEach(c => {\n        fqCategoryArr.push(obj(c.cell_name, null, 1));\n        \n        if (c.atom_data && Array.isArray(c.atom_data)) {\n          c.atom_data.slice(1).forEach(a => {\n            let d = a.category_data;\n            if (d && d.name) {\n              let cid = d.category_id;\n\n              let gender, genre_type;\n              if (i == 0) {\n                gender = 0;\n                genre_type = 0;\n              } else if (i == 1) {\n                gender = 1;\n                genre_type = 0;\n              } else {\n                gender = 1;\n                genre_type = 0;\n              }\n              \n              fqCategoryArr.push({\n                title: d.name,\n                url: `${getServerHost()}\/type_style?category_id=${cid}&genre_type=${genre_type}&gender=${gender}&offset={{(page-1)*100}}&selected_items=`,\n                style: {\n                  layout_flexGrow: 1,\n                  layout_flexBasisPercent: 0.29\n                }\n              });\n            }\n          });\n        }\n      });\n    } catch (e) {\n      fqCategoryArr.push(obj(\"分类加载失败,请重试\", null, 1));\n    }\n  });\n\n  fqCategoryArr.forEach(item => {\n    if (item.style && item.style.layout_flexBasisPercent) {\n      push(item.title, item.url, item.style.layout_flexBasisPercent);\n    } else {\n      sort.push({\n        title: item.title,\n        url: item.url,\n        style: {\n          layout_flexGrow: 1,\n          layout_flexBasisPercent: 0.29\n        }\n      });\n    }\n  });\n\n  let hasValidSession = false;\n  let username = \"\";\n\n  try {\n    let uinfo = java.ajax(getServerHost() + \"\/book_user?session=\" + getSessionId());\n    uinfo = JSON.parse(uinfo);\n    \n    if (uinfo.data.name) {\n      username = uinfo.data.name;\n      hasValidSession = true;\n    }\n  } catch (e) {\n  }\n\n  if (!hasValidSession) {\n    java.toast(\"番茄登录已过期,请重新登录\");\n  }\n\n  let gro = [];\n  let pushGro = (title, url, type) => gro.push(obj(title, url, type));\n\n  let sArr = [];\n\n  if (hasValidSession) {\n    try {\n      let book_shelf_info = JSON.parse(java.ajax(getServerHost() + \"\/book_shelf?session=\" + getSessionId()));\n\n      if (book_shelf_info.code == 0) {\n        个人中心 = 1;\n        \n        let groups_bookids = { \"未分组\": [] };\n        book_shelf_info.data.book_shelf_info.forEach(i => {\n          if (!groups_bookids[i.group_name ? i.group_name : \"未分组\"]) \n            groups_bookids[i.group_name] = [];\n          groups_bookids[i.group_name ? i.group_name : \"未分组\"].push(i.book_id);\n        });\n\n        Object.keys(groups_bookids).forEach(k => {\n          pushGro(k, \"https:\/\/fanqienovel.com\/fqbookshelf\/groupName\/\" + k, 0.4);\n        });\n        if (Object.keys(groups_bookids).length % 2 != 0) pushGro(\"占位\", \"\", 0.4);\n        \n        sArr.push(obj(username + '的个人中心', '', 1));\n        sArr.push(obj(\"我的书架\", \"https:\/\/fanqienovel.com\/fqbookshelf\", 1));\n        sArr = sArr.concat(gro);\n        sArr.push(obj(\"阅读历史\", getServerHost() + \"\/read_history?session=\" + getSessionId() + \"&page={{page}}\", 1));\n      } else {\n        个人中心 = 0;\n        java.toast(\"获取书架信息失败,登录可能已过期\");\n      }\n    } catch (e) {\n      个人中心 = 0;\n      java.toast(\"番茄登录过期,已隐藏个人中心\");\n    }\n  } else {\n    个人中心 = 0;\n    java.toast(\"您还未登录番茄账号,无法同步数据\");\n  }\n\n  sort = sArr.concat(sort);\n  JSON.stringify(sort);\n}\n<\/js>",
    "header": "@js:\nJSON.stringify({\n\t\"User-Agent\":java.getWebViewUA(),\n\t\"X-Novel-Token\": \"SHUSAN_READ_2025\"\n\t})",
    "jsLib": "function deviceType() {\n    let {\n        java\n    } = this;\n    try {\n        java.deviceID();\n        return false;\n    } catch (e) {\n        try {\n            java.androidId();\n            return true;\n        } catch (e) {\n            return false;\n        }\n    }\n}\n\nfunction checkEnv() {\n    let {\n        java,\n        source\n    } = this;\n    let isModified = false;\n    try {\n        new Packages.io.legato.kazusa.utils.TimeoutCancellationException('');\n        isModified = true;\n    } catch (e) {\n        isModified = typeof source.loginUi == 'function' ? false : true;\n    }\n\n    try {\n        java.put(\"is_modified_version\", isModified ? \"true\" : \"false\");\n    } catch (e) {}\n\n    return isModified;\n}\n\nfunction isQRead() {\n    let {\n        java\n    } = this;\n    try {\n        return java.qread && java.qread() == \"1\";\n    } catch (e) {\n        return false;\n    }\n}\n\nfunction getServerHost() {\n    let {\n        source\n    } = this;\n    try {\n        const config = JSON.parse(source.getVariable());\n        return config?.[0]?.host || source.bookSourceUrl;\n    } catch (e) {\n        return source.bookSourceUrl;\n    }\n}\n\nfunction getSessionId() {\n    const {\n        cookie,\n        source\n    } = this;\n    try {\n        let cookieHeader = String(cookie.getCookie('fanqienovel.com'));\n        let sessionId = cookieHeader.match(\/sessionid=([^;]+)\/)?.[1] || null;\n        if (sessionId) {\n            return sessionId;\n        }\n    } catch (e) {}\n    try {\n        let loginInfo = source.getLoginInfoMap() || {};\n        return loginInfo['番茄登录Token'] || \"\";\n    } catch (e) {\n        return \"\";\n    }\n}\n\nfunction splitArray(input, size) {\n    let output = []\n    for (let i = 0; i < input.length; i += size) {\n        output.push(input.slice(i, i + size))\n    }\n    return output\n}\n\nfunction getCommentIconColor() {\n    let {\n        source\n    } = this;\n    let color = \"#666666\";\n    let loginInfo = source.getLoginInfoMap() || \"\";\n    if (loginInfo['段评图标颜色'] && loginInfo['段评图标颜色'].startsWith('#')) {\n        color = loginInfo['段评图标颜色'];\n    }\n    return color;\n}\n\nfunction wrapContentForIOS(content) {\n    const pureContent = content.trim();\n    if (deviceType.call(this)) {\n        return pureContent;\n    }\n    let lines = pureContent.split('\\n').map(line => {\n        if (line.trim()) {\n            const trimLine = line.trim().replace(\/\\s+\/g, ' ');\n            return `<span rs-native>${trimLine}<\/span>`;\n        }\n        return line;\n    });\n    return lines.join('\\n');\n}\n\nfunction buildCommentUrl(sourceType, bid, cid, para) {\n    let host = getServerHost.call(this);\n    let isModifiedVersion = checkEnv.call(this);\n    let aidParam = (!deviceType.call(this) || isQRead.call(this) || isModifiedVersion) ? \"&aid=web\" : \"\";\n\n    switch (sourceType) {\n        case 'jj':\n            return host + '\/jj_comment?bid=' + bid + '&cid=' + cid + '&para=' + para + aidParam;\n        case 'qm':\n            return host + '\/qm_comment?item_id=' + cid + '&book_id=' + bid + '&paragraph_id=' + para + aidParam;\n        case 'fq':\n        default:\n            return host + '\/idea_comment?item_id=' + cid + '&book_id=' + bid + '&para=' + para + '&sessionid=' + getSessionId.call(this) + aidParam;\n    }\n}\n\nfunction getCommentSourceName(sourceType) {\n    switch (sourceType) {\n        case 'jj':\n            return '晋江';\n        case 'qm':\n            return '七猫';\n        case 'fq':\n        default:\n            return '番茄';\n    }\n}\n\nfunction startCommentBrowser(url, title) {\n    let {\n        java\n    } = this;\n    if (isQRead.call(this)) {\n        java.startBrowserDp(url, title);\n    } else {\n        let isModifiedVersion = checkEnv.call(this);\n        if (isModifiedVersion) {\n            try {\n                let htmlContent = java.ajax(url);\n                java.showBrowser(\n                    url,\n                    htmlContent,\n                    `window.java=java;`,\n                    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                        \"isGestureInsetBottomIgnored\": true,\n                        \"setFitToContents\": false,\n                        \"heightPercentage\": 0.75,\n                        \"isHideable\": true\n                    })\n                );\n            } catch (e) {\n                java.showBrowser(\n                    url,\n                    null,\n                    `window.java=java;`,\n                    JSON.stringify({\n                        \"expandedCornersRadius\": 20,\n                        \"dismissOnTouchOutside\": true,\n                        \"hardwareAccelerated\": true,\n                        \"isNestedScrollingEnabled\": true,\n                        \"isGestureInsetBottomIgnored\": true,\n                        \"heightPercentage\": 0.75,\n                        \"isHideable\": true\n                    })\n                );\n            }\n        } else {\n            java.startBrowser(url, title);\n        }\n    }\n}\n\nfunction createCommentHtmlTag(displayText, url, sourceName) {\n    const encodedUrl = url.replace(\/&\/g, '&amp;');\n    return '<comment count=\"' + displayText + '\" onPress=\"java.' +\n        (isQRead.call(this) ? 'startBrowserDp' : 'startBrowser') +\n        '(\\'' + encodedUrl + '\\',\\'' + sourceName + '段评\\')\" \/>';\n}\n\nfunction createSvg(number, color, bid, cid, para, sourceType = 'fq') {\n    let {\n        java\n    } = this;\n    var displayText = number > 99 ? \"99+\" : number;\n    var date = String(Date.now()).match(\/(\\d{6}$)\/)[1];\n\n    let isIOS = !deviceType.call(this);\n    if (isIOS) {\n        let commentUrl = buildCommentUrl.call(this, sourceType, bid, cid, para);\n        let sourceName = getCommentSourceName(sourceType);\n        return createCommentHtmlTag.call(this, displayText, commentUrl, sourceName);\n    }\n\n    let isQingRead = isQRead.call(this);\n    let isModifiedVersion = checkEnv.call(this);\n\n    var svg;\n    if (isQingRead || isModifiedVersion) {\n        svg = '<svg width=\"160\" height=\"120\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\"><path d=\"M 55 10 L 120 10 Q 150 10 150 40 L 150 80 Q 150 110 120 110 L 55 110 Q 25 110 25 80 L 25 75 L 3 60 L 25 45 L 25 40 Q 25 10 55 10 Z\" fill=\"none\" stroke=\"' + color + '\" stroke-width=\"5\" stroke-linejoin=\"round\"\/><text x=\"87\" y=\"77\" font-family=\"Arial, sans-serif\" text-anchor=\"middle\" dominant-baseline=\"middle\" font-size=\"50\" font-weight=\"bold\" fill=\"' + color + '\">' + displayText + '<\/text><\/svg>';\n    } else {\n        svg = '<svg width=\"1000\" height=\"909\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">' +\n            '<path d=\"M80,80 h840 a60,60 0 0 1 60,60 v580 a60,60 0 0 1 -60,60 h-620 l-140,90 v-90 h-80 a60,60 0 0 1 -60,-60 v-580 a60,60 0 0 1 60,-60 z\" ' +\n            'fill=\"none\" stroke=\"' + color + '\" stroke-width=\"16\" stroke-linejoin=\"round\"\/>' +\n            '<text x=\"500\" y=\"450\" font-family=\"Arial, sans-serif\" text-anchor=\"middle\" ' +\n            'font-size=\"360\" fill=\"' + color + '\" dy=\"0.3em\">' + displayText + '<\/text>' +\n            '<\/svg>';\n    }\n\n    var encodedSvg = java.base64Encode(svg);\n\n    let jsFunction;\n    switch (sourceType) {\n        case 'jj':\n            jsFunction = 'showJJCmt';\n            break;\n        case 'qm':\n            jsFunction = 'showQmCmt';\n            break;\n        case 'fq':\n        default:\n            jsFunction = 'showCmt';\n            break;\n    }\n\n    if (isModifiedVersion) {\n        return 'data:image\/svg+xml;base64,' + encodedSvg + ',{\\'click\\':\\'' + jsFunction + '(\"' + bid + '\",\"' + cid + '\",\"' + para + '\",\"' + date + '\")\\',\\'style\\':\\'TEXT\\'}';\n    } else {\n        return 'data:image\/svg+xml;base64,' + encodedSvg + ',{\\'js\\':\\'' + jsFunction + '(\"' + bid + '\",\"' + cid + '\",\"' + para + '\",\"' + date + '\")\\',\\'style\\':\\'TEXT\\'}';\n    }\n}\n\nfunction checkClickRateLimit(lastCallKey) {\n    let {\n        cache\n    } = this;\n    let lastCallTime = cache.getFromMemory(lastCallKey) || 0;\n    let currentTime = Date.now();\n    if (currentTime - lastCallTime < 1000) {\n        return false;\n    }\n    cache.putMemory(lastCallKey, currentTime);\n    return true;\n}\n\nfunction handleCommentClick(sourceType, bid, cid, para, date, cacheKey) {\n    let {\n        cache\n    } = this;\n    let isModifiedVersion = checkEnv.call(this);\n    let isQingRead = isQRead.call(this);\n\n    if (!isModifiedVersion && !isQingRead) {\n        let mname = cacheKey;\n        let load = (cache.getFromMemory(mname) ?? \"-\").split(\"-\");\n        if (load[0] != \"1\" || load[1] != date) {\n            cache.putMemory(mname, \"1-\" + date);\n            return;\n        }\n        let lastCallKey = `last_call_${sourceType}_${bid}_${cid}_${para}`;\n        if (!checkClickRateLimit.call(this, lastCallKey)) {\n            return;\n        }\n    }\n\n    let commentUrl = buildCommentUrl.call(this, sourceType, bid, cid, para);\n    let title = isModifiedVersion ? getCommentSourceName(sourceType) + \"段评\" : (cache.getFromMemory(cacheKey + \"-text\") ?? getCommentSourceName(sourceType) + \"段评\");\n    startCommentBrowser.call(this, commentUrl, title);\n}\n\nfunction getComments(content, bid, cid, sourceType = 'fq', contentMd5 = null) {\n    let {\n        java,\n        cache,\n        source\n    } = this;\n    let host = getServerHost.call(this);\n    try {\n        let lines = String(content)\n            .replace(new RegExp('(<img[^>]*?>)\\n', 'g'), \"$1\")\n            .split(\"\\n\")\n            .filter(line => line.trim());\n\n        let isModifiedVersion = checkEnv.call(this);\n        let isQingRead = isQRead.call(this);\n\n        if (sourceType == 'jj') {\n            let apiUrl = `${host}\/proxy_jjidea?bid=${bid}&cid=${cid}&token=${JJtoken.call(this)}`;\n            let response = java.ajax(apiUrl);\n            let data = JSON.parse(response).data;\n\n            data.forEach((item) => {\n                let para = item.paragraph_id;\n                let count = item.comment_total;\n\n                let paraNum = parseInt(para);\n                if (paraNum <= 0 || paraNum > lines.length) {\n                    return;\n                }\n\n                let adjustedPara = paraNum - 1;\n\n                if (!isModifiedVersion && !isQingRead) {\n                    cache.putMemory(`jj-${bid}-${cid}-${para}-text`, lines[adjustedPara]);\n                }\n\n                let color = getCommentIconColor.call(this);\n                let commentTag = createSvg.call(this, count, color, bid, cid, para, sourceType);\n                if (deviceType.call(this)) {\n                    lines[adjustedPara] += `<img src=\"${commentTag}\">`;\n                } else {\n                    lines[adjustedPara] += commentTag;\n                }\n            });\n        } else if (sourceType == 'qm') {\n            if (!contentMd5) return wrapContentForIOS.call(this, content);\n\n            let apiUrl = `${host}\/proxy_qmidea?action=paragraph_bubbles&book_id=${bid}&item_id=${cid}&content_md5=${contentMd5}`;\n            let response = java.ajax(apiUrl);\n            let data = JSON.parse(response).data;\n\n            if (data.chapters && data.chapters.length > 0) {\n                let chapterData = data.chapters[0];\n                if (chapterData.bubbles) {\n                    chapterData.bubbles.forEach((bubble, bubbleIndex) => {\n                        let paraMd5 = bubble.p;\n                        let count = parseInt(bubble.c) || 0;\n\n                        if (count > 0 && bubbleIndex < lines.length) {\n                            let paraIndex = bubbleIndex;\n\n                            if (!isModifiedVersion && !isQingRead) {\n                                cache.putMemory(`qm-${bid}-${cid}-${paraMd5}-text`, lines[paraIndex]);\n                            }\n\n                            let color = getCommentIconColor.call(this);\n                            let commentTag = createSvg.call(this, count, color, bid, cid, paraMd5, sourceType);\n                            if (deviceType.call(this)) {\n                                lines[paraIndex] += `<img src=\"${commentTag}\">`;\n                            } else {\n                                lines[paraIndex] += commentTag;\n                            }\n                        }\n                    });\n                }\n            }\n        } else {\n            let apiUrl = `${host}\/proxy_idea?item_id=${cid}`;\n            let comments = java.ajax(apiUrl);\n            let raw = JSON.parse(comments).data.data;\n\n            Object.keys(raw).forEach((x) => {\n                let lineIndex = parseInt(x);\n\n                if (lineIndex >= lines.length || lineIndex < 0) {\n                    return;\n                }\n\n                let lineContent = lines[lineIndex];\n\n                let textOnly = String(lineContent)\n                    .replace(new RegExp('<[^>]*>', 'g'), '')\n                    .replace(\/&nbsp;\/g, ' ')\n                    .trim();\n\n                if (!textOnly) {\n                    return;\n                }\n\n                if (!isModifiedVersion && !isQingRead) {\n                    cache.putMemory(`fq-${bid}-${cid}-${x}-text`, textOnly || lineContent);\n                }\n\n                let color = getCommentIconColor.call(this);\n                let commentTag = createSvg.call(this, raw[x].count, color, bid, cid, x, sourceType);\n                if (deviceType.call(this)) {\n                    lines[lineIndex] += `<img src=\"${commentTag}\">`;\n                } else {\n                    lines[lineIndex] += commentTag;\n                }\n            });\n        }\n\n        let result = lines.join(\"\\n\");\n        return wrapContentForIOS.call(this, result);\n    } catch (e) {\n        return wrapContentForIOS.call(this, content);\n    }\n}\n\nfunction showJJCmt(bid, cid, para, date) {\n    handleCommentClick.call(this, 'jj', bid, cid, para, date, `jj-${bid}-${cid}-${para}`);\n}\n\nfunction showCmt(bid, cid, para, date) {\n    handleCommentClick.call(this, 'fq', bid, cid, para, date, `fq-${bid}-${cid}-${para}`);\n}\n\nfunction showQmCmt(bid, cid, para, date) {\n    handleCommentClick.call(this, 'qm', bid, cid, para, date, `qm-${bid}-${cid}-${para}`);\n}\n\nfunction getSecretKey() {\n    const {\n        java,\n        source\n    } = this;\n    try {\n        let apiKey = \"\";\n        const headerValue = source.getLoginHeader();\n        if (headerValue && String(headerValue).length >= 10) {\n            apiKey = String(headerValue);\n        }\n        if (!apiKey || apiKey.length < 10) {\n            const storedKey = java.get(\"user_api_key\");\n            if (storedKey && String(storedKey).length >= 10) {\n                apiKey = String(storedKey);\n            }\n        }\n        if (!apiKey || apiKey.length < 10) {\n            java.toast(\"❌ 未登录,请先登录\");\n            return \"\";\n        }\n        return java.base64Encode(apiKey);\n    } catch (e) {\n        java.toast(\"❌ 获取密钥失败\");\n        return \"\";\n    }\n}\n\nfunction getSortKey() {\n    let {\n        java\n    } = this;\n    const sortKey = java.get(\"sortkey\") || '';\n    if (!sortKey) {\n        java.toast(\"请先设置排序参数\");\n    }\n    return sortKey;\n}\n\nfunction getBookIdFromUrl(baseUrl) {\n    let {\n        java,\n        source\n    } = this;\n    let finalUrl;\n    let urlMatch = baseUrl.match(\/url=([^&]*)\/);\n    if (urlMatch && urlMatch[1]) {\n        finalUrl = java.base64Decode(urlMatch[1]);\n    } else {\n        let fqurl = JSON.parse(java.base64Decode(baseUrl.split(\",\")[1]));\n        finalUrl = java.base64Decode(fqurl.url);\n    }\n    let bookIdMatch = finalUrl.match(\/book_id=(\\d{19})\/);\n    return bookIdMatch ? bookIdMatch[1] : null;\n}\n\nfunction getBookData(bookId) {\n    let {\n        java,\n        source\n    } = this;\n    let newUrl = \"https:\/\/api5-normal-sinfonlineb.fqnovel.com\/reading\/bookapi\/multi-detail\/v\/?aid=1967&iid=1&version_code=999&book_id=\" + bookId;\n    let response = java.ajax(newUrl);\n    let responseData = JSON.parse(response);\n    if (Array.isArray(responseData.data)) {\n        return responseData.data[0];\n    } else {\n        return responseData.data;\n    }\n}\n\nfunction parseAuthors(authorsData) {\n    let {\n        java,\n        source\n    } = this;\n    let authors = [];\n    try {\n        authors = authorsData ? JSON.parse(authorsData) : [];\n    } catch (e) {\n        authors = authorsData ? authorsData.replace(\/[\\[\\]\"]\/g, '').split(',') : [];\n    }\n    return authors.length > 0 ? (authors[0].AuthorName || authors[0] || \"\") : \"\";\n}\n\nfunction getToneInfo(bookId) {\n    if (!bookId) return \"\";\n    try {\n        let tones = \"\\n===================\\n🎧AI音色编号信息: \";\n        let info = JSON.parse(java.ajax(`https:\/\/reading.snssdk.com\/reading\/bookapi\/audio\/toneinfo\/?book_id=${bookId}&aid=1967`));\n        for (let i of (info.data.tts_tones ? info.data.tts_tones : [])) {\n            tones += `\\n${i.id} - ${i.title}${i.description ? \"(\" + i.description + \")\" : \"\"}`;\n        }\n        return tones;\n    } catch (e) {\n        return \"\";\n    }\n}\n\nfunction syncReadingHistory(bookId) {\n    let {\n        java,\n        source\n    } = this;\n    if (bookId) {\n        var sessionid = getSessionId.call(this);\n        if (sessionid && sessionid.trim() !== \"\") {\n            var syncUrl = getServerHost.call(this) + \"\/update_history?book_id=\" + bookId + \"&sessionid=\" + sessionid;\n            java.ajax(syncUrl);\n        }\n    }\n}\n\nfunction getJjwxcNovelIdFromUrl(baseUrl) {\n    let {\n        java,\n        source\n    } = this;\n    let finalUrl;\n    try {\n        let urlMatch = baseUrl.match(\/url=([^&]*)\/);\n        if (urlMatch && urlMatch[1]) {\n            finalUrl = java.base64Decode(urlMatch[1]);\n        } else {\n            let fqurl = JSON.parse(java.base64Decode(baseUrl.split(\",\")[1]));\n            finalUrl = java.base64Decode(fqurl.url);\n        }\n        let novelIdMatch = finalUrl.match(\/novelId=(\\d+)\/);\n        if (novelIdMatch) {\n            return novelIdMatch[1];\n        }\n    } catch (e) {}\n    return null;\n}\n\nfunction buildChapterUrl(x, catalog, device) {\n    let {\n        java\n    } = this;\n    const isFanqieApp = ['番茄小说', '番茄短剧', '番茄听书', '番茄畅听'].includes(catalog.source);\n    const urlToMatch = x.url || catalog.url;\n    const bookId = urlToMatch.match(\/book_id=(\\d{19})\\b\/)?.[1] || null;\n    const itemId = urlToMatch.match(\/item_id=(\\d+)\/)?.[1] || null;\n    let contentUrlParams = `chapter?cid=${x.cid}&source=${catalog.source}&device=${device}`;\n    if (!isFanqieApp) {\n        contentUrlParams += `&url=${catalog.url}`;\n    } else {\n        contentUrlParams += `&book_id=${bookId}&item_id=${itemId}`;\n        if (['番茄听书', '番茄畅听'].includes(catalog.source)) {\n            const toneId = Number((this.source.getLoginInfoMap())['听书AI音色编号']) || 1;\n            contentUrlParams += `&tone_id=${toneId}`;\n        }\n    }\n    if (catalog.source == '七猫') {\n        const qmBookId = x.url.match(\/book_id=(\\d+)\/)?.[1] || null;\n        const qmChapterId = x.url.match(\/item_id=(\\d+)\/)?.[1] || null;\n        contentUrlParams += `&${x.url}`;\n        if (qmBookId && qmChapterId) {\n            const qmCommentUrl = `${getServerHost.call(this)}\/qm_comment?book_id=${qmBookId}&item_id=${qmChapterId}`;\n            return `data:contentUrl;base64,${java.base64Encode(contentUrlParams)},{\"type\":\"qingci\",\"js\":\"book ? result : '${qmCommentUrl}'\"}`;\n        } else {\n            return `data:contentUrl;base64,${java.base64Encode(contentUrlParams)},{\"type\":\"qingci\"}`;\n        }\n    } else {\n        let commentPart = '{\"type\":\"qingci\"}';\n        if (catalog.source == '晋江') {\n            const novelIdMatch = x.url.match(\/novelId=(\\d+)\/);\n            const chapterIdMatch = x.url.match(\/chapterId=(\\d+)\/);\n            if (novelIdMatch && chapterIdMatch) {\n                const novelId = novelIdMatch[1];\n                const chapterId = chapterIdMatch[1];\n                const jjCommentUrl = `${getServerHost.call(this)}\/jj_comment?novelId=${novelId}&chapterId=${chapterId}`;\n                commentPart = `{\"type\":\"qingci\",\"js\":\"book ? result : '${jjCommentUrl}'\"}`;\n            }\n        } else if (catalog.source == '番茄小说') {\n            const fanqieCommentUrl = `${getServerHost.call(this)}\/comment?sessionid=${getSessionId.call(this)}&item_id=${itemId}&book_id=${bookId}`;\n            commentPart = `{\"type\":\"qingci\",\"js\":\"book ? result : '${fanqieCommentUrl}'\"}`;\n        } else if (catalog.source == '云端') {\n            const decodedUrl = java.base64Decode(catalog.url);\n            const commentUrl = decodedUrl.replace('share', 'comment');\n            commentPart = `{\"type\":\"qingci\",\"js\":\"book ? result : '${commentUrl}&pt=3g'\"}`;\n        }\n        return `data:contentUrl;base64,${java.base64Encode(contentUrlParams)},${commentPart}`;\n    }\n}\n\nfunction showTabTips(catalog) {\n    var tabTips = {\n        audio: '听书',\n        video: '视频',\n        comic: '漫画'\n    };\n    if (tabTips[catalog.tab]) {\n        this.java.toast('当前为' + tabTips[catalog.tab] + '模式');\n    }\n}\n\nfunction buildSearchResult(key, sourceParam, host) {\n    let {\n        java\n    } = this;\n    if (key && key.length == 19 && !Number.isNaN(parseInt(key))) {\n        return handleFanqieSearch.call(this, key, host);\n    } else if (key && key.length == 7 && !Number.isNaN(parseInt(key))) {\n        return handleJjwxcSearch.call(this, key, host);\n    } else {\n        return host + '\/search?login=search&key={{key}}&page={{page}}' + sourceParam;\n    }\n}\n\nfunction handleFanqieSearch(key, host) {\n    let {\n        java\n    } = this;\n    var u = host + `\/detail?book_id=${key}`;\n    var kdy = java.base64Encode(u);\n    var response = JSON.parse(java.ajax(u));\n    var nam = response.data.book_name || (response.data[0] && response.data[0].book_name) || '未知';\n    return host + `\/details?url=${kdy}&source=番茄小说&name=${encodeURIComponent(nam)}`;\n}\n\nfunction decode(data) {\n    const {\n        java\n    } = this;\n    const encryptedBytes = java.base64DecodeToByteArray(data);\n    const crypto = java.createSymmetricCrypto(\"DES\/CBC\/PKCS5Padding\", \"K7bM2nXy\", \"tQ5v9rS1\");\n    return crypto.decryptStr(encryptedBytes);\n}",
    "lastUpdateTime": "1770091293130",
    "loginUi": "[\n{\n         \t\"name\": \"邮箱\",\n         \"type\": \"text\",\n         \"action\": \"\"\n  },\n  {\n         \"name\": \"密码\",\n         \"type\": \"password\",\n         \"action\": \"\"\n  },\n  {\n        \"name\":  \"⭕账号登录\",\n        \"type\": \"button\",\n        \"action\": \"login()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n    }\n  },\n  {\n        \t\"name\": \"🔑注册书源\",\n        \"type\": \"button\",\n        \"action\": \"key()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\":  \"🔘退出登录\",\n        \"type\": \"button\",\n        \"action\": \"user_logout()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n    }\n  },\n  {\n        \"name\":  \"🔐修改密码\",\n        \"type\": \"button\",\n        \"action\": \"password()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n    }\n  },\n  {\n        \t\"name\": \"🔮信息查询\",\n        \"type\": \"button\",\n        \"action\": \"get_user()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n         \"name\": \"👤用户后台\",\n         \"type\": \"button\",\n         \"action\": \"user_center()\",\n         \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n    }\n  },\n  {\n        \"name\": \"🔍聚合搜索\",\n        \"type\": \"button\",\n        \"action\": \"sou0()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🧭书源更新\",\n        \"type\": \"button\",\n        \"action\": \"version()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n    \t    \"name\": \"☕打赏享福利\",\n        \"type\": \"button\",\n        \"action\": \"vip()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\":0.4\n        }\n    },\n    {\n        \t\"name\": \"📚书山发布页\",\n        \"type\": \"button\",\n        \"action\": \"fb()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n       }\n    },\n    {\n        \"name\": \"自定义源站\",\n        \"type\": \"text\"\n    },\n    {\n        \"name\": \"❤️段评开关\",\n        \"type\": \"button\",\n        \"action\": \"toggleParacomment('paras')\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n    \t \"name\": \"⚡TXT下载开关\",\n        \"type\": \"button\",\n        \"action\": \"toggleFqdown('fqdown')\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"👨‍🦱男 频\",\n        \"type\": \"button\",\n        \"action\": \"boy()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"👩‍🦰女 频\",\n        \"type\": \"button\",\n        \"action\": \"girl()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🎚服务器切换\",\n        \"type\": \"button\",\n        \"action\": \"sethost()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"♻️服务器检测\",\n        \"type\": \"button\",\n        \"action\": \"checkCurrentServer()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \t\"name\": \"🍅番茄登录\",\n        \"type\": \"button\",\n        \"action\": \"fq_login()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"❌番茄退出\",\n        \"type\": \"button\",\n        \"action\": \"logout()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \t\"name\": \"🍅记录同步\",\n        \"type\": \"button\",\n        \"action\": \"toggleBookSync()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n    \t   \"name\": \"🍅发现排序筛选\",\n       \"type\": \"button\",\n       \"action\": \"SortFilter()\",\n       \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n  }\n       },\n    {\n        \"name\": \"↓   切换\/查询当前模式   ↓\",\n        \"type\": \"button\",\n        \"action\": \"get_cx()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 1\n        }\n    },\n    {\n        \"name\": \"📖小说模式\",\n        \"type\": \"button\",\n        \"action\": \"type1()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🎧听书模式\",\n        \"type\": \"button\",\n        \"action\": \"type2()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🏞️漫画模式\",\n        \"type\": \"button\",\n        \"action\": \"type3()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"▶️视频模式\",\n        \"type\": \"button\",\n        \"action\": \"type4()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n    \t\n        \t\"name\": \"↓   专  属VIP源   站   ↓\",\n        \"type\": \"button\",\n        \"action\": \"\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 1\n        }\n    },\n    {       \n        \"name\": \"🍻半夏中文❇️\",\n        \"type\": \"button\",\n        \"action\": \"sou11()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🫧60看书\",\n        \"type\": \"button\",\n        \"action\": \"sou10()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🌤️69书吧com\",\n        \"type\": \"button\",\n        \"action\": \"sou12()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🌤️69书吧co\",\n        \"type\": \"button\",\n        \"action\": \"sou34()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🐧企鹅看书\",\n        \"type\": \"button\",\n        \"action\": \"sou8()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \t\"name\": \"♟️书旗文学\",\n        \"type\": \"button\",\n        \"action\": \"sou9()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🍵茶马小说\",\n        \"type\": \"button\",\n        \"action\": \"sou15()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"📖全本小说\",\n        \"type\": \"button\",\n        \"action\": \"sou42()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n     },\n     {\n        \t\"name\": \"🍻精品文学\",\n        \"type\": \"button\",\n        \"action\": \"sou46()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \t\"name\": \"🥕萝卜小说\",\n        \"type\": \"button\",\n        \"action\": \"sou45()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \t\"name\": \"✒️笔趣阁\",\n        \"type\": \"button\",\n        \"action\": \"sou17()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n     \t    \"name\": \"🃏3A中文\",\n         \"type\": \"button\",\n         \"action\": \"sou44()\",\n         \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n         \t\"name\": \"🐼大熊猫\",\n         \"type\": \"button\",\n         \"action\": \"sou43()\",\n         \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n     \t   \"name\": \"🌽速读谷\",\n        \"type\": \"button\",\n        \"action\": \"sou31()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n    \t    \"name\": \"🛟万相书城\",\n        \"type\": \"button\",\n        \"action\": \"sou48()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \t\"name\": \"☁云端书库\",\n        \"type\": \"button\",\n        \"action\": \"sou41()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n       \t\"name\": \"📻爱听FM\",\n        \"type\": \"button\",\n        \"action\": \"sou40()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \t\"name\": \"🎧喜马拉雅\",\n        \"type\": \"button\",\n        \"action\": \"sou47()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n            \"name\": \"↓   免   费   源   站   ↓\",\n        \"type\": \"button\",\n        \"action\": \"\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 1\n        }\n    },\n    {\n        \"name\": \"🍅番茄小说\",\n        \"type\": \"button\",\n        \"action\": \"sou1()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🍅番茄听书[AI音色]\",\n        \"type\": \"button\",\n        \"action\": \"sou2()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🎧番茄畅听\",\n        \"type\": \"button\",\n        \"action\": \"sou3()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🍅番茄漫画\",\n        \"type\": \"button\",\n        \"action\": \"sou4()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🍅番茄短剧\",\n        \"type\": \"button\",\n        \"action\": \"sou5()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"💫起点中文\",\n        \"type\": \"button\",\n        \"action\": \"sou7()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🐱七猫小说\",\n        \"type\": \"button\",\n        \"action\": \"sou6()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n    \t\"name\": \"🐱七猫短剧\",\n        \"type\": \"button\",\n        \"action\": \"sou33()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n    \t         \"name\": \"🦉猫眼看书\",\n        \"type\": \"button\",\n        \"action\": \"sou21()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🍰淘小说\",\n        \"type\": \"button\",\n        \"action\": \"sou24()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🍒追书神器\",\n        \"type\": \"button\",\n        \"action\": \"sou19()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🍋得间小说\",\n        \"type\": \"button\",\n        \"action\": \"sou13()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🥨爱下电子书\",\n        \"type\": \"button\",\n        \"action\": \"sou16()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🌾小米阅读\",\n        \"type\": \"button\",\n        \"action\": \"sou20()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n     {\n        \"name\": \"🍭米读\",\n        \"type\": \"button\",\n        \"action\": \"sou18()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🎊疯读\",\n        \"type\": \"button\",\n        \"action\": \"sou23()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"⚖️圣武书屋\",\n        \"type\": \"button\",\n        \"action\": \"sou22()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.45\n        }\n    },\n    {\n        \"name\": \"🍂知乎盐选\",\n        \"type\": \"button\",\n        \"action\": \"sou14()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.45\n        }\n    },\n    {\n         \"name\": \"🌸NT动漫\",\n         \"type\": \"button\",\n         \"action\": \"sou35()\",\n         \"style\": {\n         \"layout_flexGrow\": 1,\n         \"layout_flexBasisPercent\": 0.4\n    }\n   },\n   {\n        \"name\": \"🌀919言情\",\n        \"type\": \"button\",\n        \"action\": \"sou32()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },{\n    \t    \"name\": \"🍉西瓜小说\",\n        \"type\": \"button\",\n        \"action\": \"sou30()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🐾百度小说\",\n        \"type\": \"button\",\n        \"action\": \"sou36()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n    \t    \"name\": \"🌊酷我小说\",\n        \"type\": \"button\",\n        \"action\": \"sou37()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \t\"name\": \"🐰思兔阅读\",\n        \"type\": \"button\",\n        \"action\": \"sou25()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \t\"name\": \"🍥包子漫画\",\n        \"type\": \"button\",\n        \"action\": \"sou29()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🌝全面漫画\",\n        \"type\": \"button\",\n        \"action\": \"sou39()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"听书AI音色编号\",\n        \"type\": \"text\"\n    },\n    {\n        \"name\": \"段评图标颜色\",\n        \"type\": \"text\"\n    },\n    {\n        \"name\": \"段评气泡样式\",\n        \"type\": \"text\"\n    },\n  {\n        \"name\": \"番茄登录Token\",\n        \"type\": \"text\"\n    }\n]",
    "loginUrl": "\/\/ 请勿改动版本号,否则影响更新识别\nconst localVersion = \"5.12\";\n\nvar hosts = [\n    \"https:\/\/search.shusan.icu\",\n    \"https:\/\/jo.shusan.icu\",\n    \"http:\/\/1.94.248.5:7001\"\n];\n\nvar sourceList = [\n    {n:\"sou0\",v:null,m:\"🔍聚合搜索\"},\n    {n:\"sou1\",v:\"番茄小说\",m:\"番茄小说\"},\n    {n:\"sou2\",v:\"番茄听书\",m:\"番茄听书\"},\n    {n:\"sou3\",v:\"番茄畅听\",m:\"番茄畅听\"},\n    {n:\"sou4\",v:\"番茄漫画\",m:\"番茄漫画\"},\n    {n:\"sou5\",v:\"番茄短剧\",m:\"番茄短剧\"},\n    {n:\"sou6\",v:\"七猫\",m:\"🐱七猫\"},\n    {n:\"sou7\",v:\"起点\",m:\"💫起点中文\"},\n    {n:\"sou8\",v:\"企鹅看书\",m:\"🐧企鹅看书\"},\n    {n:\"sou9\",v:\"书旗\",m:\"♟️书旗\"},\n    {n:\"sou10\",v:\"60看书\",m:\"60看书\"},\n    {n:\"sou11\",v:\"半夏\",m:\"半夏小说\"},\n    {n:\"sou12\",v:\"69书吧\",m:\"69书吧com\"},\n    {n:\"sou13\",v:\"得间\",m:\"得间\"},\n    {n:\"sou14\",v:\"知乎\",m:\"知乎盐选\"},\n    {n:\"sou15\",v:\"茶马\",m:\"茶马小说\"},\n    {n:\"sou16\",v:\"爱下电子书\",m:\"爱下电子书\"},\n    {n:\"sou17\",v:\"笔趣阁\",m:\"笔趣阁22\"},\n    {n:\"sou18\",v:\"米读\",m:\"米读\"},\n    {n:\"sou19\",v:\"追书神器\",m:\"追书神器\"},\n    {n:\"sou20\",v:\"小米阅读\",m:\"小米阅读\"},\n    {n:\"sou21\",v:\"猫眼看书\",m:\"猫眼看书\"},\n    {n:\"sou22\",v:\"圣武书屋\",g:\"girl\",m:\"圣武书屋\"},\n    {n:\"sou23\",v:\"疯读\",m:\"疯读小说\"},\n    {n:\"sou24\",v:\"淘小说\",m:\"淘小说\"},\n    {n:\"sou25\",v:\"思兔\",m:\"思兔阅读\"},\n    {n:\"sou26\",v:\"甜梦文库\",g:\"girl\",m:\"甜梦文库\"},\n    {n:\"sou27\",v:\"三七轻小说\",m:\"三七轻小说\"},\n    {n:\"sou28\",v:\"歪瑞古德\",m:\"歪瑞古德\"},\n    {n:\"sou29\",v:\"包子漫画\",m:\"包子漫画\"},\n    {n:\"sou30\",v:\"西瓜\",m:\"🍉西瓜小说\"},        \n    {n:\"sou31\",v:\"速读谷\",m:\"速读谷\"},\n    {n:\"sou32\",v:\"919\",m:\"919言情\"},\n    {n:\"sou33\",v:\"七猫短剧\",m:\"🐱七猫短剧\"},\n    {n:\"sou34\",v:\"69书吧co\",m:\"69书吧co\"},\n    {n:\"sou35\",v:\"nt动漫\",m:\"🌸NT动漫\"},\n    {n:\"sou36\",v:\"百度\",m:\"百度小说\"},\n    {n:\"sou37\",v:\"酷我\",m:\"酷我小说\"},\n    {n:\"sou38\",v:\"笔趣阁78\",m:\"笔趣阁78\"},\n    {n:\"sou39\",v:\"全面漫画\",m:\"全面漫画\"},\n    {n:\"sou40\",v:\"爱听\",m:\"爱听FM\"},\n    {n:\"sou41\",v:\"云端\",m:\"云端书库\"},\n    {n:\"sou42\",v:\"全本\",m:\"全本小说\"},\n    {n:\"sou43\",v:\"大熊猫\",m:\"大熊猫\"},\n    {n:\"sou44\",v:\"3a中文\",m:\"3A中文\"},\n    {n:\"sou45\",v:\"萝卜\",m:\"萝卜小说\"},\n    {n:\"sou46\",v:\"精品\",m:\"精品文学\"},\n    {n:\"sou47\",v:\"喜马拉雅\",m:\"喜马拉雅\"},\n    {n:\"sou48\",v:\"万相书城\",m:\"万相书城\"},\n    {n:\"type1\",v:\"小说\",m:\"小说模式\"},\n    {n:\"type2\",v:\"听书\",m:\"听书模式\"},\n    {n:\"type3\",v:\"漫画\",m:\"漫画模式\"},\n    {n:\"type4\",v:\"视频\",m:\"视频模式\"}\n];\n\nfunction hasValidCustomSource() {\n    const value = (source.getLoginInfoMap())['自定义源站'];\n    return !(value == null || value == undefined || value == \"\" || String(value).trim() == \"\");\n}\n\nfunction showCustomSourceAlert() {\n    const customSource = (source.getLoginInfoMap())['自定义源站'];\n    java.toast(\n        \"\\n⚠️ 自定义源站配置提醒 ⚠️\\n\" +\n        \"──────────────────\\n\" +\n        \"检测到已设置自定义源站: \\n✨\" +\n        \"【\" + (customSource || \"\") + \"】✨\\n\" +\n        \"──────────────────\\n\" +\n        \"请前往「书源登录」清除配置\\n\" +\n        \"才能使指定源站切换生效\"\n    );\n}\n\nfunction login(){\n    const loginInfo = source.getLoginInfoMap();\n    const email = loginInfo['邮箱'];\n    const password = loginInfo['密码'];\n    \n    if (!email || !password) {\n        java.toast(\"\\n❌ 请先填写账号和密码并点击✓保存后登录\");\n        return;\n    }\n    \n    java.toast(\"正在登录...\");\n    const host = getServerHost();\n    const url = host + '\/login.php';\n    const body = \"email=\" + encodeURIComponent(email) + \"&password=\" + encodeURIComponent(password);\n    \n    try {\n        const response = java.ajax(url + \",\" + JSON.stringify({\n            method: \"POST\",\n            body: body,\n            timeout: 10000\n        }));\n        \n        const result = JSON.parse(response);\n        \n        if (result.code == 200) {\n            const user = result.data.user;\n            const apiKey = user.api_key;\n            \n            source.putLoginHeader(apiKey);\n            \n            const nickname = user.nickname || \"用户\";\n            const maskedEmail = email.includes('@') ? \n                email.substring(0, 3) + \"****\" + email.substring(email.indexOf('@')) : \n                email;\n            \n            java.toast(\n                \"\\n───────────────\\n\" +\n                \"🌟 登录成功 🌟\\n\" +\n                \"───────────────\\n\" +\n                \"欢迎回来,\" + nickname + \"\\n\" +\n                \"邮箱:\" + maskedEmail + \"\\n\" +\n                \"───────────────\"\n            );\n        } else {\n            const errorCode = result.code || \"未知\";\n            const errorMessage = result.message || \"未知错误\";\n            \n            java.toast(\n                \"\\n───────────────\\n\" +\n                \"❌ 登录失败 ❌\\n\" +\n                \"───────────────\\n\" +\n                \"错误代码:\" + errorCode + \"\\n\" +\n                \"错误信息:\" + errorMessage + \"\\n\" +\n                \"───────────────\"\n            );\n        }\n        \n    } catch (e) {\n        java.toast(\n            \"\\n───────────────\\n\" +\n            \"❌ 网络错误 ❌\\n\" +\n            \"───────────────\\n\" +\n            \"连接服务器失败\\n\" +\n            \"请检查网络连接\\n\" +\n            \"───────────────\"\n        );\n    }\n}\n\nfunction user_logout() {\n    let hadLogin = false;\n    \n    if (source.getLoginHeader() && String(source.getLoginHeader()).trim().length > 0) {\n        hadLogin = true;\n    }\n    \n    if (java.get(\"user_api_key\") && String(java.get(\"user_api_key\")).trim().length > 0) {\n        hadLogin = true;\n    }\n    \n    source.putLoginHeader(\"\");\n    java.put(\"user_api_key\", \"\");\n    \n    if (!source.getLoginHeader() || String(source.getLoginHeader()).trim().length === 0) {\n        if (!java.get(\"user_api_key\") || String(java.get(\"user_api_key\")).trim().length === 0) {\n            java.toast(hadLogin ? \"\\n❗ 已成功退出登录\" : \"\\nℹ️ 当前未登录状态\");\n        }\n    }\n}\n\nfunction updateConfig(key, value, message, gender) {\n    if (key == \"source\" && hasValidCustomSource()) {\n        showCustomSourceAlert();\n        return;\n    }\n    \n    try {\n        var config = JSON.parse(source.getVariable());\n        if (!config || !Array.isArray(config) || config.length === 0) {\n            config = [{}];\n        }\n        config[0][key] = value;\n        if (gender !== undefined) {\n            config[0][\"gender\"] = gender;\n        }\n        source.setVariable(JSON.stringify(config));\n        java.toast(message || \"设置已更新\");\n    } catch(e) {\n        var newConfig = {[key]: value};\n        if (gender !== undefined) {\n            newConfig[\"gender\"] = gender;\n        }\n        source.setVariable(JSON.stringify([newConfig]));\n        java.toast(message || \"初始化设置\");\n    }\n}\n\nfunction get_user() {\n    try {\n        var encodedKey = getSecretKey();\n        if (!encodedKey) {\n            java.toast(\"❌ 未获取到密钥,请先登录\");\n            return;\n        }\n\n        var timestamp = Math.floor(Date.now() \/ 1000);\n        var apiUrl = getServerHost() + '\/query_key.php?key=' + encodedKey + '&t=' + timestamp;\n        \n        var response = java.ajax(apiUrl + \",\" + JSON.stringify({\n            method: \"GET\",\n            timeout: 10000\n        }));\n        \n        var data = JSON.parse(response);\n        \n        if (data.data) {\n            var userInfo = data.data;\n            \n            var statusDisplay = \"\";\n            switch(userInfo.status) {\n                case \"正常\":\n                    statusDisplay = \"✅ 正常\";\n                    break;\n                case \"会员\":\n                    statusDisplay = \"👑会员\";\n                    break;\n                case \"封禁\":\n                    statusDisplay = \"🚫 封禁\";\n                    break;\n                default:\n                    statusDisplay = userInfo.status;\n            }\n            \n            var email = userInfo.email || \"未绑定\";\n            var maskedEmail = \"未绑定\";\n            if (email !== \"未绑定\" && email.includes(\"@\")) {\n                var parts = email.split(\"@\");\n                var username = parts[0];\n                var domain = parts[1];\n                if (username.length > 2) {\n                    maskedEmail = username.substring(0, 2) + \"****@\" + domain;\n                } else {\n                    maskedEmail = username + \"****@\" + domain;\n                }\n            }\n            \n            var nickname = userInfo.nickname || \"未设置\";\n            if (nickname === \"未设置\") {\n                nickname = \"👤 \" + nickname;\n            } else {\n                nickname = \"👤 \" + nickname;\n            }\n            \n            var displayContent = \n                \"\\n────────────\\n\" +\n                \"📊 用 户 信 息 📊\\n\" +\n                \"────────────\\n\" +\n                nickname + \"\\n\" +\n                \"────────────\\n\" +\n                \"📧 绑定邮箱\\n\" + maskedEmail + \"\\n\" +\n                \"────────────\\n\" +\n                \"📊 累计阅读\\n\" + userInfo.total_usage + \" 次\\n\" +\n                \"────────────\\n\" +\n                \"📈 今日阅读\\n\" + userInfo.today_usage + \" 次\\n\" +\n                \"────────────\\n\" +\n                \"🔄 账户状态\\n\" + statusDisplay + \"\\n\" +\n                \"────────────\\n\" +\n                \"📅 注册时间\\n\" + userInfo.created_at + \"\\n\";\n            \n            if (userInfo.is_banned) {\n                displayContent += \"────────────\\n\" +\n                                \"🚫 封禁信息\\n\" + userInfo.banned_info + \"\\n\";\n            }\n            \n            if (userInfo.last_used !== \"从未使用\") {\n                displayContent += \"────────────\\n\" +\n                                \"⏰ 最后使用\\n\" + userInfo.last_used + \"\\n\";\n            }\n            \n            if (userInfo.is_member) {\n                displayContent += \"────────────\\n\" +\n                                \"👑会员状态\\n 尊贵VIP会员\\n\";\n            }\n            \n            displayContent += \"────────────\\n\" +\n                            \"✨ 查询完成\";\n            \n            java.longToast(displayContent);\n            \n        } else {\n            var errorMsg = data.message || \"未知错误\";\n            \n            java.longToast(\n                \"❌ 查询失败:\" + errorMsg\n            );\n        }\n        \n    } catch (e) {\n        java.longToast(\n            \"❌ 网络连接异常,请稍后重试\"\n        );\n    }\n}\n\nfunction get_cx() {\n    try {\n        var configStr = source.getVariable();\n        if (!configStr || configStr.trim() == \"\") {\n            configStr = JSON.stringify([{host: hosts[0]}]);\n            source.setVariable(configStr);\n        }\n        var config = JSON.parse(configStr);\n        \n        if (!Array.isArray(config) || config.length === 0) {\n            config = [{host: hosts[0]}];\n            source.setVariable(JSON.stringify(config));\n        }\n        \n        var currentConfig = config[0] || {};\n        var currentHost = currentConfig.host || hosts[0];\n        var currentSource = (!currentConfig.source || currentConfig.source == null) ? \n            \"☁️ 聚合搜索 ☁️\" : \n            (sourceList.find(item => item.v == currentConfig.source)?.m || currentConfig.source);\n        \n        var currentGender = currentConfig.gender == \"boy\" ? \" 男🧸频 \" : \n            (currentConfig.gender == \"girl\" ? \" 女🎀频 \" : \" 未设置 \");\n        \n        var currentTone = currentConfig.sz ? \"音色\" + data[parseInt(currentConfig.sz) || 1][0] : \"默认音色\";\n        \n        var customSource = (source.getLoginInfoMap())['自定义源站'] || \"未设置\"; \n        var customSourceStatus = hasValidCustomSource() ? \"🔗 \" + customSource : \"当前未设置,示例:米读,书旗\";\n        \n        var username = \"未登录\";\n        try {\n            var cookie_ = String(cookie.getKey(\"fanqienovel.com\", \"sessionid\")) || \n                         (source.getLoginInfoMap())['番茄登录Token'];\n            if (cookie_) {\n                var userInfo = JSON.parse(java.ajax(\"https:\/\/fanqienovel.com\/api\/user\/info\/v2,\" + JSON.stringify({\n                    method: \"GET\", \n                    headers: { \"Cookie\": \"sessionid=\" + cookie_ },\n                    timeout: 5000\n                })));\n                username = userInfo.data?.name || \"已登录(未获取到用户名)\";\n            }\n        } catch (e) {\n            username = \"登录状态检查失败\";\n        }\n\n        var message = \n            \"\\n───────────────\\n\" +\n            \"☑️  配  置  状  态 ☑️\\n\" +\n            \"───────────────\\n\" +\n            \"🔘 服  务  器 🔘\\n\" + currentHost + \"\\n\" +\n            \"───────────────\\n\" +\n            \"🌋 书  源 🌋\\n\" + currentSource + \"\\n\" +\n            \"───────────────\\n\" +\n            \"🏔️ 偏  好 🏔️\\n\" + currentGender + \"\\n\" +\n            \"───────────────\\n\" +\n            \"🎧 AI 音 色 🎧\\n\" + currentTone + \"\\n\" +\n            \"───────────────\\n\" +\n            \"❇️ 自 定 义 源 ❇️\\n\" + customSourceStatus + \"\\n\" +\n            \"───────────────\\n\" +\n            \"🍅 番 茄 账 号 🍅\\n\" + username + \"\\n\" +\n            \"───────────────\\n\" +\n            \"\\n✨ 提示:点击按钮切换配置\";\n            \n        java.longToast(message);\n    } catch(e) {\n        source.setVariable(JSON.stringify([{host: hosts[0]}]));\n        java.toast(\"⚠️ 配置初始化完成,请重试\");\n    }\n}\n\nthis.sethost = function() {\n    try {\n        var config = JSON.parse(source.getVariable());\n        if (!config || !Array.isArray(config) || config.length == 0) {\n            config = [{host: hosts[0]}];\n        }\n        \n        var currentHost = config[0].host || hosts[0];\n        var currentIndex = hosts.indexOf(currentHost);\n        var newIndex = (currentIndex + 1) % hosts.length;\n        var newHost = hosts[newIndex];\n        config[0].host = newHost;\n        source.setVariable(JSON.stringify(config));\n        java.toast(\"切换到:\" + newHost);\n    } catch(e) {\n        source.setVariable(JSON.stringify([{host: hosts[0]}]));\n        java.toast(\"已初始化:\" + hosts[0]);\n    }\n};\n\nfor (var i = 0; i < sourceList.length; i++) {\n    (function() {\n        var s = sourceList[i];\n        this[s.n] = function() {\n            if (hasValidCustomSource()) {\n                showCustomSourceAlert();\n                return;\n            } if (s.g) {\n                updateConfig(\"source\", s.v, \"\\n已切换\" + (s.v ? '到❇️' : '') + s.m +\"❇️\\n刷新发现页生效\", s.g);\n            } else {\n                updateConfig(\"source\", s.v, \"\\n已切换\" + (s.v ? '到❇️' : '') + s.m +\"❇️\\n刷新发现页生效\");\n            }\n        };\n    }).call(this);\n}\n\n\/\/ 检测当前服务器状态\nfunction checkCurrentServer() {\n    try {\n        var currentHost = getServerHost();\n        java.longToast(`\\n\\n♻️正在检测当前服务器:${currentHost}\\n请稍等~`);\n        let date1 = new Date().getTime();\n        let html = java.ajax(currentHost);\n        let date2 = new Date().getTime();\n        let t = date2 - date1;\n        let c = String(html).indexOf('书山');\n        let time = t \/ 1000 + 's';\n        let logTime = '【' + currentHost + '】\\n┋┋\\n' + '解析时间:' + time;\n        \n        if (c == -1 || t > 5000) {\n            java.longToast('\\n💔【访问失败提示】\\n' + '┏┅━┅━┅━┅━┅┅━┅━┅┓\\n┋┋\\n' + logTime + '\\n┋┋\\n♣️当前接口无法访问(可能被墙)♣️\\n┋┋\\n请切换其他接口\/切换网络环境\\n┋┋' + '\\n┗┅━┅━┅━┅━┅┅━┅━┅┛');\n        } else if (t < 1000) {\n            java.longToast('\\n💖【网络环境优良】\\n' + '┏┅━┅━┅━┅━┅┅━┅━┅┓\\n┋┋\\n' + logTime + '\\n┋┋\\n❤️延迟低,推荐使用此接口❤️\\n┋┋\\n网络环境优良,请继续保持状态\\n┋┋' + '\\n┗┅━┅━┅━┅━┅┅━┅━┅┛');\n        } else if (t >= 1000 && t < 2000) {\n            java.longToast('\\n💛【网络环境一般】\\n' + '┏┅━┅━┅━┅━┅┅━┅━┅┓\\n┋┋\\n' + logTime + '\\n┋┋\\n♦️延迟一般,勉强可使用♦️\\n┋┋\\n请切换其他接口或切换网络环境\\n┋┋' + '\\n┗┅━┅━┅━┅━┅┅━┅━┅┛');\n        } else if (t >= 2000 && t < 5000) {\n            java.longToast('\\n💔【网络环境堪忧】\\n' + '┏┅━┅━┅━┅━┅┅━┅━┅┓\\n┋┋\\n' + logTime + '\\n┋┋\\n♠延迟过高,不建议使用♠\\n┋┋\\n请切换其他接口或切换网络环境\\n┋┋' + '\\n┗┅━┅━┅━┅━┅┅━┅━┅┛');\n        }\n    } catch(e) {\n        java.longToast('\\n❌【服务器检测失败】\\n' + '┏┅━┅━┅━┅━┅┅━┅━┅┓\\n┋┋\\n检测过程中发生错误\\n┋┋\\n请检查网络连接或服务器状态\\n┋┋' + '\\n┗┅━┅━┅━┅━┅┅━┅━┅┛');\n    }\n}\n\n\/\/打赏\nfunction vip() { java.startBrowserAwait(getServerHost() + '\/coffee', \"喝咖啡\"); }\nfunction fb() { java.startBrowserAwait('https:\/\/fb.shushan.vip', \"发布页\"); }\nfunction key() { java.startBrowserAwait(getServerHost() + '\/key', \"获取密钥\"); }\nfunction version() { java.startBrowserAwait(getServerHost() + '\/version?id=' + localVersion, \"版本检查\"); }\n\nfunction Map(e) {\n    if (e == \"source\" && hasValidCustomSource()) {\n        showCustomSourceAlert();\n        return (source.getLoginInfoMap())['自定义源站'];\n    }\n    var infomap = source.getLoginInfoMap();\n    const value = infomap[e];\n    if (!value) {\n        java.longToast(\"需要填写密钥\");\n    }\n    return value;\n}\n\nfunction fq_login() {\n    try { \tjava.startBrowserAwait(\"https:\/\/fanqienovel.com\/\", \"登录\");\n    } catch (e) {\n        java.toast(e);\n    }\n    try {\n        cookie.removeCookie(\"snssdk.com\");\n    } catch (e) {}\n    \n    let cookieHeader = String(cookie.getCookie('fanqienovel.com'));\njava.toast(cookieHeader);\n    \n    const match = cookieHeader.match(\/sessionid=([^;]+)\/);\n    java.toast(match);\n    let sessionId = match ? match[1] : null;\n\n    let cookieString = sessionId ? \"sessionid=\" + sessionId : source.getLoginInfoMap()['番茄登录Token'] || \"\";\n    java.toast(cookieString);\n    let user;\n    try {\n        user = JSON.parse(java.ajax(\"https:\/\/fanqienovel.com\/api\/user\/info\/v2,\" + JSON.stringify({\n            method: \"GET\",\n            headers: { \"Cookie\": cookieString }\n        }))).data.name;\n    } catch (e) { \n        java.log(e); \n    }\n    \n    if (!cookieString || cookieString == \"sessionid=\" || !user) {\n        java.toast(\"未获取到登录凭据,登录失败\");\n        return false;\n    }\n    \n    java.toast(\"\\n\\n欢迎 \" + user + \"\\n登录成功!\");\n    return true;\n}\n\nfunction logout() {\n    cookie.removeCookie(\"fanqienovel.com\");\n    cookie.removeCookie(\"snssdk.com\");\n    if (String(cookie.getKey(\"fanqienovel.com\", \"sessionid\")) || \n        (source.getLoginInfoMap())['手动登录Token']) {\n        java.toast(\"请手动移除填写的Token\");\n    } else {\n        java.toast(\"退出登录成功\");\n    }\n}\n\nfunction toggleParacomment() {\n    const key = \"yunpara\";\n    let status = java.get(key) ?? \"off\";\n    if (status == \"on\") {\n        java.put(key, \"off\");\n        java.toast(\"\\n🍅番茄,🐱七猫\\n❌段评已关闭\");\n    } else {\n        java.put(key, \"on\");\n        java.toast(\"\\n🍅番茄,🐱七猫\\n✅段评已开启\");\n    }\n}\n\nfunction toggleFqdown(a) {\n    let status = java.get(a) ?? \"off\";\n    if (status == \"off\") {\n        java.put(a, \"on\");\n        java.toast(\"\\n🍅番茄小说\\n✅下载模式已开启\");\n    } else {\n        java.put(a, \"off\");\n        java.toast(\"\\n🍅番茄小说\\n❌下载模式已关闭\");\n    }\n}\n\nfunction toggleBookSync() {\n    let status = java.get(\"book_sync\") ?? \"off\";\n    if (status == \"off\") {\n        java.put(\"book_sync\", \"on\");\n        java.toast(\"\\n🍅番茄小说\\n✅已开启 - 将自动同步阅读记录\");\n    } else {\n        java.put(\"book_sync\", \"off\");\n        java.toast(\"\\n番茄小说\\n❌已关闭 - 停止同步阅读记录\");\n    }\n}\n\nfunction SortFilter() {\n    try {\n        var savedParams = java.get(\"sortkey\");\n        savedParams = savedParams ? savedParams : \"\";\n        \n        if (savedParams.startsWith('\">')) {\n            savedParams = savedParams.substring(2);\n        } else if (savedParams.startsWith('>')) {\n            savedParams = savedParams.substring(1);\n        }\n        \n        let html = `<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\"><title>🍅标签排序筛选<\/title><style>\n            *{margin:0;padding:0;box-sizing:border-box}\n            body{font-family:'PingFang SC','Microsoft YaHei',sans-serif;background:#f8f9fa;color:#333;max-width:700px;margin:0 auto;padding:16px}\n            .container{background:white;border-radius:16px;box-shadow:0 4px 20px rgba(0,0,0,0.08);padding:24px}\n            .header{margin-bottom:24px;text-align:center}\n            h1{color:#ff6b35;font-size:24px;font-weight:600;margin-bottom:8px}\n            .subtitle{color:#666;font-size:14px;margin-bottom:16px}\n            .current-section{background:#f0f7ff;border-radius:12px;padding:16px;margin-bottom:24px;border:1px solid #e3f2fd;position:relative}\n            .current-label{color:#1976d2;font-size:14px;font-weight:500;margin-bottom:8px}\n            .current-value{color:#2c3e50;font-size:16px;font-weight:500;min-height:24px;word-break:break-all}\n            .section{margin-bottom:28px}\n            .section-title{display:flex;align-items:center;margin-bottom:16px;font-size:16px;font-weight:600;color:#2c3e50}\n            .section-icon{margin-right:8px;font-size:18px}\n            .tip{font-size:12px;color:#ff6b35;margin-left:8px;background:#fff0e6;padding:2px 8px;border-radius:4px}\n            .buttons-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(100px,1fr));gap:10px}\n            .filter-btn{padding:10px 0;font-size:14px;border:1px solid #e0e0e0;border-radius:8px;background:white;color:#555;cursor:pointer;transition:all 0.2s;font-weight:500;text-align:center}\n            .filter-btn:hover{transform:translateY(-2px);box-shadow:0 4px 8px rgba(0,0,0,0.1)}\n            .filter-btn.selected{background:#ff6b35;color:white;border-color:#ff6b35;box-shadow:0 2px 8px rgba(255,107,53,0.3)}\n            .filter-btn.selected:hover{background:#ff5a22;box-shadow:0 4px 12px rgba(255,107,53,0.4)}\n            .action-hint{text-align:center;color:#666;font-size:13px;margin-top:32px;padding:12px;background:#f8f9fa;border-radius:8px;border:1px dashed #ddd}\n            .action-hint strong{color:#ff6b35}\n            .clear-btn{width:100%;background:#f8f9fa;color:#666;border:1px solid #ddd;border-radius:8px;padding:12px;font-size:14px;cursor:pointer;transition:all 0.2s;margin-top:32px}\n            .clear-btn:hover{background:#ffefeb;color:#ff6b35;border-color:#ff6b35}\n            @media (max-width:480px){.buttons-grid{grid-template-columns:repeat(3,1fr)}}\n        <\/style><\/head><body><div class=\"container\">\n            <div class=\"header\">\n                <h1>🍅 标签排序筛选<\/h1>\n                <div class=\"subtitle\">选择筛选条件,右上角\"✓\"确认生效<\/div>\n            <\/div>\n            \n            <div class=\"current-section\">\n                <div class=\"current-label\">当前选择:<\/div>\n                <div id=\"result-output\">${savedParams ? savedParams : \"未选择\"}<\/div>\n            <\/div>\n            \n            <div class=\"section\">\n                <div class=\"section-title\">\n                    <span class=\"section-icon\">📏<\/span> 字数参数 <span class=\"tip\">单选<\/span>\n                <\/div>\n                <div class=\"buttons-grid\" id=\"length-btns\"><\/div>\n            <\/div>\n            \n            <div class=\"section\">\n                <div class=\"section-title\">\n                    <span class=\"section-icon\">📈<\/span> 状态参数 <span class=\"tip\">单选<\/span>\n                <\/div>\n                <div class=\"buttons-grid\" id=\"status-btns\"><\/div>\n            <\/div>\n            \n            <div class=\"section\">\n                <div class=\"section-title\">\n                    <span class=\"section-icon\">🔝<\/span> 排序参数 <span class=\"tip\">单选<\/span>\n                <\/div>\n                <div class=\"buttons-grid\" id=\"sort-btns\"><\/div>\n            <\/div>\n            \n            <button onclick=\"clearAll()\" class=\"clear-btn\">\n                🗑️ 清除所有选择\n            <\/button>\n            \n            <div class=\"action-hint\">\n                提示:选择完成后点击右上角 <strong>✓<\/strong> 按钮确认设置\n            <\/div>\n        <\/div><script>\nvar currentParams = document.getElementById('result-output').textContent.trim();\nvar selectedItems = [];\n\nif (currentParams && currentParams !== \"未选择\" && currentParams !== \"undefined\") {\n    selectedItems = currentParams.split(\", \");\n}\n\nvar config={\nlengths:[\"10万以下\",\"30万以下\",\"50万以下\",\"30万以上\",\"50万以上\",\"100万以上\",\"200万以上\",\"300万以上\",\"500万以上\"],\nstatuses:[\"完结\",\"连载\",\"半年完结\",\"3天更新\",\"7天更新\",\"1月更新\"],\nsorts:[\"热门\",\"新书\",\"评分\",\"字数\"]\n};\nvar categorySelection={\nlength:null,\nstatus:null,\nsort:null\n};\nfunction initCategorySelection(){\nif(selectedItems.length>0){\nfor(var i=0;i<selectedItems.length;i++){\nvar item=selectedItems[i];\nif(config.lengths.includes(item)){\ncategorySelection.length=item;\n}else if(config.statuses.includes(item)){\ncategorySelection.status=item;\n}else if(config.sorts.includes(item)){\ncategorySelection.sort=item;\n}}}}\ninitCategorySelection();\nfunction initButtons(containerId,items,category){\nvar container=document.getElementById(containerId);\nvar buttonsHTML='';\nfor(var i=0;i<items.length;i++){\nvar name=items[i];\nvar isSelected=categorySelection[category]===name;\nbuttonsHTML+='<button onclick=\"toggleSelection(\\\\''+name+'\\\\',\\\\''+category+'\\\\')\" class=\"filter-btn'+(isSelected?' selected':'')+'\">'+name+'<\/button>';\n}\ncontainer.innerHTML=buttonsHTML;\n}\nfunction toggleSelection(name,category){\nif(categorySelection[category]===name){\ncategorySelection[category]=null;\n}else{\ncategorySelection[category]=name;\n}\nupdateButtonStates();\nupdateOutput();\n}\nfunction updateButtonStates(){\nfor(var category in categorySelection){\nvar containerId=category+'-btns';\nvar container=document.getElementById(containerId);\nif(container){\nvar buttons=container.getElementsByTagName('button');\nfor(var i=0;i<buttons.length;i++){\nvar btn=buttons[i];\nvar btnName=btn.textContent;\nbtn.className='filter-btn'+(categorySelection[category]===btnName?' selected':'');\n}}}}\nfunction updateOutput(){\nvar allSelected=[];\nfor(var category in categorySelection){\nif(categorySelection[category]){\nallSelected.push(categorySelection[category]);}}\nvar resultText=allSelected.length>0?allSelected.join(\", \"):\"未选择\";\ndocument.getElementById('result-output').textContent=resultText;\n}\nfunction clearAll(){\ncategorySelection={length:null,status:null,sort:null};\nupdateButtonStates();\nupdateOutput();\n}\ninitButtons('length-btns',config.lengths,'length');\ninitButtons('status-btns',config.statuses,'status');\ninitButtons('sort-btns',config.sorts,'sort');\nupdateOutput();\n<\/script><\/body><\/html>`;\n        \n        let base64 = `data:text\/html;base64,${java.base64Encode(html)}`;\n        let body = java.startBrowserAwait(base64,\"书籍筛选\",false).body();\n        \n        let resultDivStart = body.indexOf('<div id=\"result-output\">');\n        let selectedItems = \"\";\n        \n        if (resultDivStart > -1) {\n            let contentStart = body.indexOf('>', resultDivStart);\n            if (contentStart > -1) {\n                contentStart += 1;\n                let contentEnd = body.indexOf('<\/div>', contentStart);\n                if (contentEnd > contentStart) {\n                    selectedItems = body.substring(contentStart, contentEnd).trim();\n                    \n                    if (selectedItems === \"未选择\") {\n                        selectedItems = \"\";\n                    } else {\n                        if (selectedItems.startsWith('\">')) {\n                            selectedItems = selectedItems.substring(2);\n                        } else if (selectedItems.startsWith('>')) {\n                            selectedItems = selectedItems.substring(1);\n                        }\n                    }\n                }\n            }\n        }\n        \n        java.put(\"sortkey\", selectedItems);\n        \n        if (selectedItems === \"\") {\n            java.toast(\"已清除所有筛选条件\");\n        } else {\n            java.toast(\"设置成功,该设置仅🍅番茄小说发现页生效\");\n        }\n        \n    } catch(e) {\n        java.log(e);\n    }\n}\n\nfunction boy() { updateConfig(\"gender\", \"boy\", \"\\n已设置为\\n👨‍🦱男频\\n刷新发现页生效\"); }\nfunction girl() { updateConfig(\"gender\", \"girl\", \"\\n已设置为\\n👩‍🦰女频\\n刷新发现页生效\"); }\nfunction user_center() { java.startBrowserAwait(getServerHost() + '\/user_center?key=' + getSecretKey(), \"用户中心\");}\nfunction password() { java.startBrowserAwait(getServerHost() + '\/forgot_password', \"修改密码\"); }\nfunction recommend() { \n    updateConfig(\"gender\", \"recommend\", \"\\n已设置为\\n❇️网友推荐❇️\\n刷新发现页生效\");\n    updateConfig(\"source\", \"推荐\"); }\nfunction tj() {\n  java.startBrowserAwait(getServerHost() + '\/login?key=' + getSecretKey(), \"书籍推荐\");}",
    "respondTime": 180000,
    "ruleBookInfo": {
        "author": "$.author",
        "coverUrl": "$.cover",
        "downloadUrls": "$.book_url\n@js:\neval(String(source.loginUrl));\nlet url = java.base64Decode(result);\nlet book_id = url.match(\/\\d{19}\/)?.[0];\nif (!book_id) throw new Error(\"未找到有效书籍ID\");\nlet getkey = java.base64Encode(Map('密钥'));\nlet downloadUrl = getServerHost() + `\/down?key=${getkey}&book_id=${book_id}`;\njava.toast('\\n如果提示Unexpected webFileData,请等待几秒后点击阅读导入…\\n在线阅读关闭⚡下载模式后刷新即可');\ndownloadUrl;",
        "init": "<js>\nif (String(baseUrl).startsWith(\"data:\")) {\n    let res = JSON.parse(java.hexDecodeToString(result));\n    let {source: source_name, url: book_url, name: title} = res;\n    \n\/\/ 仅在番茄小说且开启下载模式时设置\nif (source_name == \"番茄小说\" &&   \n\t   java.get(\"fqdown\") == \"on\") {\n    book.type = 128;\n}\n\n    let request = getServerHost() +`\/details?source=${source_name}&url=${book_url}&name=${title}`;\n    \/\/java.log(request);\n    result = java.ajax(request);\n    \/\/java.log(result);\n}\nresult;\n<\/js>\n$.data",
        "intro": "&nbsp;&nbsp;\n✨ 源站:{{$.source}}{{\"\\n\"+\"​\"}}\n{{$.desc}}\n<js>\nconst tomatoSources = [\"番茄小说\", \"番茄听书\", \"番茄畅听\"];\n\nif (tomatoSources.includes(\"{{$.source}}\")) {\n    try {\n        let bookId = getBookIdFromUrl(baseUrl);\n        if (bookId) {\n            let bookData = getBookData(bookId);\n            \n            if (bookData && bookData.tomato_book_status !== '3') {\n                \/\/ 检查同步开关状态,只有开启时才执行同步\n                let syncStatus = java.get(\"book_sync\") ?? \"off\";\n                if (syncStatus == \"on\") {\n                    syncReadingHistory(bookData.book_id);\n                }\n                \n                let bookName = bookData.original_book_name || bookData.book_name || \"\";\n                let aliasName = bookData.book_flight_alias_name || bookData.sub_title || \"\";\n                let createTime = bookData.create_time ? bookData.create_time.split('T')[0] : \"\";\n                let updateTime = bookData.last_chapter_update_time ? java.timeFormat(parseInt(bookData.last_chapter_update_time)*1000) : \"\";\n                \n                let authors = [];\n                try {\n                    authors = bookData.original_authors ? JSON.parse(bookData.original_authors) : [];\n                } catch(e) {\n                    authors = bookData.roles ? bookData.roles.replace(\/[\\[\\]\"]\/g,'').split(',') : [];\n                }\n                let authorName = authors.length > 0 ? (authors[0].AuthorName || authors[0] || \"\") : \"\";\n                \n                let tags = bookData.pure_category_tags || bookData.tags || \"\";\n                let readCount = bookData.read_count || \"0\";\n                let score = bookData.score || \"0\";\n                let last_chapter_title = bookData.last_chapter_title || \"暂无\";\n                let creation_status = bookData.creation_status || \"未知\";\n                \n                let bookStatus = \"正常\";\n                if (bookData.book_search_visible == 'false') {\n                    bookStatus = \"下架\";\n                } else if (bookData.tomato_book_status == '3') {\n                    bookStatus = \"小黑屋\";\n                }\n                \n                let abstract = bookData.book_abstract_v2 || bookData.abstract || \"\";\n                let copyrightInfo = bookData.copyright_info || \"\";\n                \n                let output = `&nbsp;&nbsp;\n✨ 源站:{{$.source}}{{\"\\n\"+\"​\"}}\n📕 源名:${bookName}`;\n                \n                if (aliasName) {\n                    output += `\\n📖 别名:${aliasName}`;\n                }\n                \n                output += `\\n✏️ 开坑:${createTime} \n🧭 更新:${updateTime}&lrm;\n👤 作者:${authorName}\n⭐ 评分:  ${score}分\n📖 状态:  连载${creation_status}完结\n🏷️ 标签:${tags}\n✅ 最新章节: ${last_chapter_title}\n👁️ 在线:${readCount}人在读\n🔗 书籍状态:${bookStatus}`;\n                \n                \/\/ 显示同步状态\n                output += `\\n🔄 阅读记录:${syncStatus == \"on\" ? \"✅已开启同步\" : \"❌已关闭同步\"}`;\n                \n                if ([\"番茄听书\"].includes(\"{{$.source}}\")) {\n                    try {\n                        let tones = \"\\n===================\\n🎧AI音色编号信息: \";\n                        let info = JSON.parse(java.ajax(`https:\/\/reading.snssdk.com\/reading\/bookapi\/audio\/toneinfo\/?book_id=${bookData.book_id}&aid=1967`));\n                        for (let i of (info.data.tts_tones ? info.data.tts_tones : [])) {\n                            tones += `\\n${i.id} - ${i.title}${i.description ? \"(\" + i.description + \")\" : \"\"}`;\n                        }\n                        output += tones;\n                    } catch (e) {\n                    }\n                }\n                \n                output += `{{\"\\n\"+\"​\"}}\n📜 简介:${abstract}{{\"\\n\"+\"​\"}}`;\n                \n                if (copyrightInfo) {\n                    output += `\\n📍 ${copyrightInfo.split(',')[0]}。`;\n                }\n                \n                result = output;\n            }\n        }\n    } catch (e) {\n    }\n}\nresult;\n<\/js>##连载0|1完结@js:result\n.replace(\"男生2女生\", \"出版\")\n.replace(\"连载4完结\",\"断更\")\n.replace(\"连载-1完结\",\"未知\");",
        "kind": "$.tags",
        "lastChapter": "$.latestChapterTitle",
        "name": "$.title",
        "tocUrl": "<js>\nlet catalog = {\n    source: result.source,\n    url: result.book_url,\n    name: result.title,\n    tab: result.tab || \"novel\"\n};\n\nvar tabTips = {audio: '听书', video: '视频', comic: '漫画'}; \nif (tabTips[catalog.tab]) {\n    java.toast('当前为' + tabTips[catalog.tab] + '模式');\n}\n\nyunurl = java.base64Encode(JSON.stringify(catalog));\nresult = `data:catalogUrl;base64,${yunurl},{\"type\":\"shushan\"}`;\n<\/js>",
        "wordCount": "$.wordCount"
    },
    "ruleContent": {
        "content": "<js>eval(function(p,a,c,k,e,r){e=function(c){return(c<62?'':e(parseInt(c\/62)))+((c=c%62)>35?String.fromCharCode(c+29):c.toString(36))};if('0'.replace(0,e)==0){while(c--)r[e(c)]=k[c];k=[function(e){return r[e]||e}];e=function(){return'([4-8bcfh-mo-rt-wyzB-RTUW-Z]|1\\\\w)'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\\\b'+e(c)+'\\\\b','g'),k[c]);return p}('4(1b()){k{5 1c=7.X(\"is_modified_version\");4(1c==\"1d\"&&!1e.1f(1g)){v.Y=\"1h\"}l{v.Y=\"FULL\"}}m(e){4(!1e.1f(1g)){v.Y=\"1h\"}}}h w=3;h 1i=\"请尝试切换服务器\";function O(y,z=0){k{5 i=F.P(7.Z(y));4(i.q.1j.G(1i)){4(z<w){7.o(\"⚠️正在尝试重新获取 (\"+(z+1)+\"\/\"+w+\")\");Q O(y,z+1)}l{7.o(\"⚠️重试\"+w+\"次后仍失败,请稍后再试\");Q i}}Q i}m(e){4(z<w){7.o(\"请求失败,正在重试(\"+(z+1)+\"\/\"+w+\")\");Q O(y,z+1)}l{7.o(\"⚠️请求失败\"+w+\"次: \"+e.message);throw e;}}}4(1b()){k{eval(String(10.loginUrl));5 H=F.P(10.12());5 R=Date.R();5 1k=2*60*60*1000;4(!H[0].13||(R-H[0].13)>1k){H[0].13=R;10.setVariable(F.stringify(H));5 c=F.P(7.Z(T()+\"\/p?vid=sy\"));4(c.p){4(c.1m&&c.1m.toString().toLowerCase()==\"U\"&&c.p>I){7.14(p());7.o(\"请更新书源!下载页面已打开。\")}5 B=c.p>I?\"\\\\n✨发现新版本V\"+c.p+\" (当前V\"+I+\")\"+(c.B?\"\\\\n\"+c.B:\\'\\'):c.p<I?\"\\\\n⚠️版本异常\\\\n本地V\"+I+\" > 云端V\"+c.p+\"\\\\n请检查书源配置\":\\'\\';4(B)7.o(B)}l 4(c.B){7.o(c.B)}}}m(e){}}5 6=7.hexDecodeToString(8);5 f=6.r(\/f=(\\\\d+)\/)?.[1];5 j=6.r(\/j=(\\\\d+)\/)?.[1];5 t=C.y.r(\/t=(\\\\d+)\/)?.[1];5 u=C.y.r(\/u=(\\\\d+)\/)?.[1];5 D=1n;5 E=1n;4(\/七猫\/.b(6)){E=6.r(\/E=([a-f0-9]+)\/)?.[1]}4(\/七猫短剧\/.b(6)){k{h 15=6.r(\/y=([^&]+)\/)?.[1];4(15){h 1o=7.1p(15);D=1o.r(\/D=(\\\\d+)\/)?.[1]}}m(e){}}5 1q=6;5 J=T()+\\'\/\\'+1q+\\'&1r=\\'+1s()+\\'&p=11\\';4(\/晋江\/.b(6)&&t&&u){J+=\\'&t=\\'+t+\\'&u=\\'+u;5 16=JJtoken()||\"\";4(16){J+=\\'&token=\\'+16}h 1t=\"chapter_refresh_force\";h 1u=7.X(1t)||\"off\";4(1u==\"on\"){J+=\"&refresh=U\"}}5 i=O(J);8=i.q.1j;5 W=i.q.W||\"\";4(6.G(\\'K=\\')){5 18=6.r(\/K=(\\\\d+)\/);4(18&&j){k{5 19=F.P(7.Z(T()+\\'\/audio?K=\\'+18[1]+\\'&item_ids=\\'+j));4(19.q?.[0]?.1v){8=19.q[0].1v}}m(e){}}}l{5 1w=\/^[A-Za-z0-9+\/]*={0,2}$\/.b(8);4(1w){k{8=decode(8)}m(e){k{8=7.1p(8)}m(e2){}}}}5 1x=v.12(\"1y\")||\"\";5 1z=7.X(\"yunpara\")||\"on\";5 L=1d;4(1z==\"on\"){4(\/晋江\/.b(6)&&t&&u){L=1x.G(\"开启段评\")}l 4(\/七猫\/.b(6)&&f&&j&&E){L=U}l 4(\/番茄小说\/.b(6)&&f&&j&&!6.G(\\'K=\\')){L=U}}4((\/番茄短剧\/.b(6)&&f)||(\/七猫短剧\/.b(6)&&D)){k{h 1A=\/番茄短剧\/.b(6)?\\'f\\':\\'D\\';h 1B=\/番茄短剧\/.b(6)?f:D;h 1C=T()+\\'\/player?\\'+1A+\\'=\\'+1B+\\'&1r=\\'+1s();4(v.1D==C.1E){7.14(1C,C.1F||\\'视频播放\\');7.o(\\'视频加载中...\\')}8=\"▶️刷新正文进入播放界面\"}m(e){}}4(W==\"video\"&&v.1D==C.1E&&!\/番茄短剧|七猫短剧\/.b(6)){5 1G=8;7.14(1G,C.1F||\\'视频播放\\');7.o(\\'视频加载中...\\');8=\"▶️刷新正文进入播放界面\"}4(L){4(\/晋江\/.b(6)&&t&&u){8=1a(8,t,u,\\'jj\\')}l 4(\/七猫\/.b(6)&&f&&j&&E){8=1a(8,f,j,\\'qm\\',E)}l 4(\/番茄小说\/.b(6)&&f&&j&&!6.G(\\'K=\\')){8=1a(8,f,j,\\'fq\\')}}5 M=i.q.M||\"\";4(W==\"novel\"&&M&&M.trim()!==\"\"){8+=\"\\\\n\"+M}4(\/晋江\/.b(6)&&i.q.1H){k{h 1I=v.12(\"1y\");5 N=\/关闭作话\/.b(1I)?\"\":i.q.1H;N=N.replace(\/(?:.*(?:蟹蟹|扔了|感谢|投出|灌溉营养液|送的|谢谢).*?(?:手榴|月票|霸王票|小天使|火箭炮|深水鱼雷|浅水炸弹|地雷|营养液)([xX]\\\\d+)*.*|非常感谢大家对我的支持,我会继续努力的!|.*?瓶[;~]|\".*?\"(?:营养液|手榴弹|月票|霸王票|小天使|火箭炮|深水鱼雷|浅水炸弹|地雷)x\\\\d+|读者.*?(月石|营养液).*|地雷感谢:[\\\\s\\\\S]+)\/g,\\'\\');4(\/[\\\\u4e00-\\\\u9fa5]\/.b(N)){8+=\"\\\\n【📢作者有话说】\\\\n\"+N}}m(e){}}8;',[],107,'||||if|let|contenturl|java|result|||test|versionData|||book_id||const|response|item_id|try|else|catch||toast|version|data|match||novelId|chapterId|book|MAX_RETRIES||url|retryCount||msg|chapter|qm_id|content_md5|JSON|includes|config|localVersion|contentUrl|tone_id|isParagraphEnabled|notice|saybody|fetchWithRetry|parse|return|now||getServerHost|true||tab|get|imageStyle|ajax|source||getVariable|lastUpdateCheck|startBrowser|urlParam|jjToken||toneId|apiData|getComments|deviceType|envVersion|false|isQRead|call|this|TEXT|ERROR_MESSAGE|content|fourHours||force|null|decodedUrl|base64Decode|baseUrl|key|getSecretKey|refreshKey|refreshStatus|main_url|isBase64|customVariable|custom|yunparaStatus|paramName|paramValue|videoUrl|durChapterIndex|index|title|normalVideoUrl|sayBody|bbb'.split('|'),0,{}))<\/js>",
        "replaceRegex": "##\\(第\\d+\/\\d+页\\)|\\(本章完\\)|\\(\\)"
    },
    "ruleExplore": {
        "author": "$.author",
        "bookList": "<js>\nfunction getBookIdFull(url) {\n    const {java} = this;\n    let $ = JSON.parse(url).data;\n    let arr = [];\n    if ($.book_shelf_info && $.book_shelf_info.length > 0) {\n        arr = $.book_shelf_info.map(item => item.book_id);\n    } else if ($.data_list && $.data_list.length > 0) {\n        arr = $.data_list.map(item => item.book_id_str);\n    } else {\n        java.toast(\"获取 book_id 失败,你可能需要登录!\");\n    }\n    return arr;\n}\n\nlet session = getSessionId()\n\ngetShelf = () => {\n    let book_shelf_info = java.ajax(getServerHost() + \"\/book_shelf?session=\" + session)\n    \n    bid = getBookIdFull(book_shelf_info)\n    \n    let id_list = splitArray(bid, 200)\n    let urls = []\n    id_list.forEach(i => {\n        urls.push(getServerHost() +\"\/detail?book_id=\" + i.join(\",\") + \"&sessionid=\" + session)\n    })\n    res = java.ajaxAll(urls)\n\n    let resp = {book_info: []}\n    res.forEach(r => {\n        resp.book_info = resp.book_info.concat(JSON.parse(r.body()).data)\n    })\n\n    return resp\n}\n\nfunction getByGroupName(name) {\n    let book_shelf_info = JSON.parse(java.ajax(getServerHost() +\"\/book_shelf?session=\" + session))\n    \n    let group_bookids = {\n        \"未分组\": []\n    }\n    book_shelf_info.data.book_shelf_info.forEach(i => {\n        if (!group_bookids[i.group_name ? i.group_name : \"未分组\"]) group_bookids[i.group_name] = []\n        group_bookids[i.group_name ? i.group_name : \"未分组\"].push(i.book_id)\n    })\n    \n    if (!group_bookids[decodeURIComponent(name)]) return {data: []}\n    \n    let book_ids = splitArray(group_bookids[decodeURIComponent(name)], 200)\n    let urls = []\n\n    book_ids.forEach(i => { \turls.push(\"https:\/\/api5-normal-sinfonlineb.fqnovel.com\/reading\/bookapi\/multi-detail\/v\/?aid=1967&iid=1&version_code=999&book_id=\" + i.join(\",\"))\n    })\n    \n    res = java.ajaxAll(urls)\n\n    let resp = {book_info: []}\n    res.forEach(r => {\n        resp.book_info = resp.book_info.concat(JSON.parse(r.body()).data)\n    })\n\n    return resp\n}\n\nfunction getByTabIndex(index) {\n    let url = _mlsec.requestHeader(\n        \"bookmall\/tab\",\n        \"version_name=5.8.9.32\",\n        null,\n        \"sessionid=\" + session\n    )\n    let all = JSON.parse(java.ajax(url))\n    let tab = all.data.tab_item[0].cell_data[index].cell_data\n    if (!tab) tab = []\n    let bookList = []\n    for (let i of tab) {\n        bookList = bookList.concat(i.book_data)\n    }\n    return { book_info: bookList }\n}\n\nfunction normalizeResponse(data) {\n    if (data.data && data.data.book_list && Array.isArray(data.data.book_list)) {\n        return data.data.book_list;\n    }\n    \n    if (data.book_info) return data.book_info;\n    if (data.data && data.data.book_info) return data.data.book_info;\n    if (data.data && Array.isArray(data.data)) return data.data;\n    if (data.list) return data.list;\n    if (data.book_list) return data.book_list;\n    if (data.data && data.data.publication_list) return data.data.publication_list;\n    if (data.result) return data.result;\n    if (data.data && data.data.result) return data.data.result;\n    if (data.data && data.data.cell_view && data.data.cell_view.book_data) {\n        return data.data.cell_view.book_data;\n    }\n    if (data.data && data.data.records) return data.data.records;\n    if (data.data && data.data.list) return data.data.list;\n    \n    return [];\n}\n\nif (baseUrl.includes(\"jj_signin\")) {\n    let decodedToken = java.base64Decode(JJtoken());\n    let timestamp = Date.now();\n    let enbody = timestamp + ':' + decodedToken;\n    let encodedEnbody = encode(enbody);\n    \n    let signUrl = \"https:\/\/app.jjwxc.org\/androidapi\/signin,{\\\"method\\\":\\\"POST\\\",\\\"body\\\":\\\"versionCode=313&sign=\" + encodedEnbody + \"\\\"}\";\n    \n    let signinResult = java.ajax(signUrl);\n    \n    let result = JSON.parse(signinResult);\n    let message = result.message || \"签到请求完成\";\n    \n    let responseData = {\n        \"isSuccess\": result.code == \"200\",\n        \"errorMsg\": result.code == \"200\" ? \"success\" : \"fail\",\n        \"data\": [{\n            \"title\": message\n        }]\n    };\n    \n    JSON.stringify(responseData);\n} else if (baseUrl.includes(\"jjbookshelf\")) {\n    let token = JJtoken();\n    let jjUrl = baseUrl + token;\n    result = JSON.parse(java.ajax(jjUrl));\n    JSON.stringify(result);\n} else if (baseUrl.includes(\"type_style\")) {\n    let sortKey = getSortKey() || '';\n    let newUrl = baseUrl + sortKey;\n    result = JSON.parse(java.ajax(newUrl));\n    \n    let normalizedData = normalizeResponse(result);\n    JSON.stringify({data: normalizedData});\n} else if (baseUrl.endsWith(\"bookshelf\")) {\n    result = getShelf(\"bookshelf\/info\")\n} else if(\/read_history\/.test(baseUrl)) {\n    let pageMatch = baseUrl.match(\/page=(\\d+)\/);\n    let page = pageMatch ? pageMatch[1] : '1';\n    \n    let history_response = java.ajax(getServerHost() + \"\/read_history?session=\" + session + \"&page=\" + page)\n    let history_data = JSON.parse(history_response)\n    \n    if (history_data.data.data_list) {\n        let book_ids = history_data.data.data_list.map(item => item.book_id_str)\n        \n        let id_list = splitArray(book_ids, 200)\n        let urls = []\n        id_list.forEach(i => {\n            urls.push(\"https:\/\/api5-normal-sinfonlineb.fqnovel.com\/reading\/bookapi\/multi-detail\/v\/?aid=1967&iid=1&version_code=999&book_id=\" + i.join(\",\"))\n        })\n        \n        let resp = {book_info: []}\n        urls.forEach(url => {\n            let detailResponse = java.ajax(url)\n            let detailData = JSON.parse(detailResponse)\n            if (detailData.data) {\n                resp.book_info = resp.book_info.concat(detailData.data)\n            }\n        })\n        \n        result = resp\n    } else {\n        result = {book_info: []}\n    }\n} else if (baseUrl.includes(\"style_top\")) {\n    result = JSON.parse(result);\n} else {\n    let w = baseUrl.split(\"\/\")\n    if (baseUrl.includes(\"groupName\")) {\n        result = getByGroupName(w[w.length - 1])\n    } else if (baseUrl.includes(\"tab\")) {\n        result = getByTabIndex(parseInt(w[w.length - 1]))\n    } else {\n        result = JSON.parse(result)\n        if (result.data && result.data.data) {\n            result = {book_info: result.data.data}\n        }\n    }\n}\n\nif (!baseUrl.includes(\"jjbookshelf\") && !baseUrl.includes(\"type_style\") && !baseUrl.includes(\"jj_signin\")) {\n    let normalizedData = normalizeResponse(result);\n    JSON.stringify({data: normalizedData})\n}\n<\/js>\n$.data[*]",
        "bookUrl": "<js>\nlet source = result.source;\nlet book_url = result.book_url;\nlet title = result.title || result.book_name || result.bookName;\nlet book_id = result.book_id || result.series_id || result.bookId;\n\nlet detail = {\n    source: source,\n    url: book_url,\n    name: title\n};\n\nif (book_id && \/^\\d{19}$\/.test(book_id)) {\n    detail.url = java.base64Encode(`https:\/\/api5-normal-sinfonlineb.fqnovel.com\/reading\/bookapi\/multi-detail\/v\/?aid=1967&iid=1&version_code=999&book_id=${book_id}`);\n    detail.source = \"番茄小说\";\n} else if (book_url) {\n    detail.url = java.base64Encode(book_url);\n}\n\nlet yunurl = java.base64Encode(JSON.stringify(detail));\n`data:detailsUrl;base64,${yunurl},{\"type\":\"shushan\"}`;\n<\/js>",
        "coverUrl": "$.audio_thumb_uri||$.thumb_url||$.thumbUri||$.cover",
        "intro": "$.recommend_reason||$.abstract||$.desc",
        "kind": "$.tags",
        "lastChapter": "$.lastChapterTitle||$.last_chapter_title",
        "name": "$.book_name||$.bookName||$.title",
        "wordCount": "$.word_number||$.WordsCount"
    },
    "ruleSearch": {
        "author": "$.author",
        "bookList": "<js>\nif (!getSecretKey()) {\n    result = JSON.stringify({\n        \"data\": [\n            {\n                \"title\": \"❗❗❗当前未登录状态\",\n                \"author\": \"请登录后使用\",\n                \"latestChapterTitle\":\"请注册账号并登陆\",\n                \"desc\":\"输入账号密码后需要点击✓登录生效\"\n            }\n        ]\n    });\n} \nresult;\n<\/js>\n$.data",
        "bookUrl": "<js>\nlet source = result.source;\nlet book_url = result.book_url;\nlet title = result.title;\n\nlet detail = {\n    source: source,\n    url: book_url,\n    name: title\n};\n\nyunurl = java.base64Encode(JSON.stringify(detail));\n`data:detailsUrl;base64,${yunurl},{\"type\":\"shushan\"}`\n<\/js>",
        "checkKeyWord": "我在精神病院学斩神@番茄小说",
        "coverUrl": "$.cover",
        "intro": "$.desc",
        "kind": "$.tags",
        "lastChapter": "{{$.source}}·{{$.latestChapterTitle}}",
        "name": "$.title",
        "wordCount": "$.wordCount"
    },
    "ruleToc": {
        "chapterList": "<js>\nlet res = JSON.parse(java.hexDecodeToString(result));\nlet catalog = {source: res.source, url: res.url, name: res.name, tab: res.tab || \"novel\"};\n\nshowTabTips(catalog);\n\nlet op = {method: \"POST\", body: catalog};\n\nlet catalogUrl = getServerHost() + `\/catalog,${JSON.stringify(op)}`;\nlet response = java.ajax(catalogUrl);\n\/\/ java.log(response);\n\nlet device = deviceType() ? 'android' : 'ios';\n\nif (device == 'android') {\n    var typeMap = {audio:32, comic:64};\n    book.type = catalog.tab in typeMap ? typeMap[catalog.tab] : 8;\n} else {\n    var typeMap = {audio:1, comic:2, video:3};\n    book.type = catalog.tab in typeMap ? typeMap[catalog.tab] : 0;\n}\n\nJSON.parse(response).data.map((x) => {\n    if (x.isVolume == true) {\n        x.url = \"\";\n        return x;\n    }\n    \n    x.url = buildChapterUrl(x, catalog, device);\n    return x;\n});\n<\/js>",
        "chapterName": "title",
        "chapterUrl": "url",
        "isVip": "isVip",
        "isVolume": "isVolume",
        "updateTime": "tag"
    },
    "searchUrl": "<js>\nif (!getSecretKey()) {\n    java.toast(\"请登录后使用\");\n    result = \"[]\";\n} else {\n    let config = (() => {\n        try {\n            let cfg = JSON.parse(source.getVariable())[0] || {};\n            if (!cfg.host || !cfg.gender) {\n                cfg = {gender: \"boy\", host: getServerHost()};\n                source.setVariable(JSON.stringify([cfg]));\n                java.toast(\"配置初始化完成\");\n            }\n            return cfg;\n        } catch(e) {\n            let cfg = {gender: \"boy\", host: getServerHost()};\n            source.setVariable(JSON.stringify([cfg]));\n            return cfg;\n        }\n    })();\n\n    var sourceVal = '';\n    var customSource = '';\n\n    try {\n        customSource = (source.getLoginInfoMap())['自定义源站'];\n    } catch(e) {\n        customSource = '';\n    }\n\n    if (customSource !== undefined && customSource !== null && String(customSource).trim() !== \"\") {\n        sourceVal = String(customSource).trim();\n    } else if (config.source) {\n        sourceVal = config.source;\n    }\n\n    var param = sourceVal ? '&source=' + encodeURIComponent(sourceVal) : '';\n\n    result = buildSearchResult(key, param, getServerHost());\n}\n<\/js>",
    "weight": 0
}
广告