[{"bookSourceComment":"2026.8.8日更新：\n增加多设备管理\n增加晋江文学\n修复服务器检测问题\n\n\n\n❤️‍🔥登录必看❤️‍🔥\n1.输入邮箱密码后必须点✓保存生效，\n否则读取不到输入的账号密码\n2.跳转卡顿在设置→其他设置中清除WebView缓存\n\n🔔适配兼容\niOS 系统适配:源阅,栖阅,轻悦时光,用心读书\n📖获取书源请访问书山发布页\n     https:\/\/fb.shushan.vip\n\n阅读软件多平台聚合下载站\nhttps:\/\/legado.vossc.com\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":"书山聚合","bookUrlPattern":"https?:\\\/\\\/.*\\\/details.*","customButton":true,"customOrder":0,"enabled":true,"enabledCookieJar":true,"enabledExplore":true,"eventListener":true,"exploreUrl":"<js>\nlet config = getConfig.call(this);\nif (!config.host || !config.gender) {\n    config = { gender: \"boy\", host: getServerHost() };\n    setConfigs.call(this, config);\n    java.toast(\"配置初始化完成\");\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\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.found) {\n      response.data.found.forEach(item => {\n        let finalUrl = \"\";\n        if (item.url) {\n          if (item.url.includes('jj_signin') || item.url.includes('jjbookshelf')) {\n            finalUrl = item.url;\n          } else {\n            finalUrl = `${getServerHost()}\/type_api?source=${currentSource}&page={{page}}&url=${item.url}`;\n            if (currentType) {\n              finalUrl += `&gender=${currentType}`;\n            }\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.45);\n    push('猜你喜欢', 'https:\/\/fanqienovel.com\/tab\/cai', 0.45);\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: 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=1`,\n        style: { layout_flexGrow: 1, layout_flexBasisPercent: 0.29 }\n      });\n    });\n  });\n\n  let fqCategoryArr = [];\nshowCategories = currentType == \"girl\" ? [0] : [1];\n\nshowCategories.forEach(i => {\n  try {\n    let response = JSON.parse(java.ajax(`${getServerHost()}\/type_style?new_category_tab=${i}`));\n    let categoryGroups = response.data;\n    \n    categoryGroups.forEach(group => {\n      fqCategoryArr.push(obj(`❤️${group.name}❤️`, null, 1));\n      \n      group.data.forEach(category => {\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: category.name,\n          url: `${getServerHost()}\/type_style?category_id=${category.category_id}&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  } 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-Api-Key\": getSecretKey(),\n\t\"X-Novel-Token\": \"SHUSAN_READ_2025\"\n\t})","jsLib":"var hosts = [\n    \"https:\/\/v1.vossc.com\",\n    \"https:\/\/v2.vossc.com\",\n    \"https:\/\/v3.vossc.com\",\n    \"https:\/\/v4.vossc.com\",\n    \"http:\/\/1.94.248.5:7001\"\n];\n\nfunction getHosts() {\n    return hosts;\n}\n\nfunction getServerHost() {\n    let { source } = this;\n    try {\n        const config = JSON.parse(source.getVariable());\n        return config?.[0]?.host || getHosts()[0];\n    } catch (e) {\n        return getHosts()[0];\n    }\n}\n\nfunction getParagraphApiUrl() {\n    let host = getServerHost.call(this);\n    return host + '\/para';\n}\n\nfunction deviceType() {\n    let { java } = this;\n    try {\n        var id = java.androidId();\n        if (id && id !== '') {\n            return false;\n        }\n    } catch (e) {}\n    \n    try {\n        var id = java.deviceID();\n        if (id && id !== '') {\n            return true;\n        }\n    } catch (e) {}\n    \n    return false;\n}\n\nfunction checkEnv() {\n    let { java, source } = 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 { java } = this;\n    try {\n        return java.qread && java.qread() == \"1\";\n    } catch (e) {\n        return false;\n    }\n}\n\nfunction getSecretKey() {\n    let { java, source, cookie } = this;\n    try {\n        var cookieHeader = cookie.getCookie(getServerHost.call(this)) || '';\n        var tokenMatch = cookieHeader.match(\/X-Novel-Token=([^;]+)\/);\n        if (tokenMatch) {\n            return tokenMatch[1];\n        }\n        \n        const headerValue = source.getLoginHeader();\n        if (headerValue && headerValue.length >= 10) {\n            return java.base64Encode(headerValue);\n        }\n        \n        java.toast(\"❌ 未登录，请先登录\");\n        return \"\";\n    } catch (e) {\n        java.toast(\"❌ 获取登陆状态失败\");\n        return \"\";\n    }\n}\n\nfunction getConfig() {\n    let { source } = this;\n    try {\n        let configStr = source.getVariable();\n        if (!configStr || configStr.trim() === \"\") {\n            return { host: getServerHost.call(this) };\n        }\n        let config = JSON.parse(configStr);\n        if (!config || !Array.isArray(config) || config.length === 0) {\n            return { host: getServerHost.call(this) };\n        }\n        return config[0] || { host: getServerHost.call(this) };\n    } catch (e) {\n        return { host: getServerHost.call(this) };\n    }\n}\n\nfunction setConfig(key, value) {\n    let { source } = this;\n    try {\n        let config = getConfig.call(this);\n        config[key] = value;\n        source.setVariable(JSON.stringify([config]));\n        return true;\n    } catch (e) {\n        return false;\n    }\n}\n\nfunction setConfigs(obj) {\n    let { source } = this;\n    try {\n        let config = getConfig.call(this);\n        for (let k in obj) {\n            if (obj.hasOwnProperty(k)) {\n                config[k] = obj[k];\n            }\n        }\n        source.setVariable(JSON.stringify([config]));\n        return true;\n    } catch (e) {\n        return false;\n    }\n}\n\nfunction getSessionId() {\n    const { cookie, source } = 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    if (content == null || content == undefined) {\n        return '';\n    }\n    if (typeof content !== 'string') {\n        try {\n            content = String(content);\n        } catch (e) {\n            return '';\n        }\n    }\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 && line.trim()) {\n            const trimLine = line.trim().replace(\/\\s+\/g, ' ');\n            return `<div rs-native>${trimLine}<\/div>`;\n        }\n        return line || '';\n    });\n    return lines.join('\\n');\n}\n\nfunction createCommentHtmlTag(displayText, url, sourceName) {\n    const encodedUrl = url.replace(\/'\/g, \"\\\\'\");\n    return '<comment count=\"' + String(displayText) + '\" onPress=\"java.' +\n        (isQRead.call(this) ? 'startBrowserDp' : 'startBrowser') +\n        '(\\'' + encodedUrl + '\\',\\'' + sourceName + '段评\\')\" \/>';\n}\n\nfunction buildCommentUrl(sourceType, bid, cid, para) {\n    let host = getServerHost.call(this);\n\n    switch (sourceType) {\n        case 'jj':\n            return host + '\/jj_comment?bid=' + bid + '&cid=' + cid + '&para=' + para;\n        case 'qm':\n            return host + '\/qm_comment?item_id=' + cid + '&book_id=' + bid + '&paragraph_id=' + para;\n        case 'qq':\n            return host + '\/qq_comment?bid=' + bid + '&cid=' + cid + '&para=' + para;\n        case 'qd':\n            return host + '\/qd_comment?bid=' + bid + '&cid=' + cid + '&para=' + para;\n        case 'fq':\n        default:\n            return host + '\/idea_comment?item_id=' + cid + '&book_id=' + bid + '&para=' + para + '&sessionid=' + getSessionId.call(this);\n    }\n}\n\nfunction getCommentSourceName(sourceType) {\n    switch (sourceType) {\n        case 'jj':\n            return '晋江';\n        case 'qm':\n            return '七猫';\n        case 'qq':\n            return '企鹅';\n        case 'qd':\n            return '起点';\n        case 'fq':\n        default:\n            return '番茄';\n    }\n}\n\nfunction startCommentBrowser(url, title) {\n    let { java, source } = this;\n    \n    let userHeight = source.getLoginInfoMap().get(\"📐段评高度%\");\n    let heightPercentage = 0.75; \/\/ 默认\n    if (userHeight) {\n        let h = parseFloat(userHeight);\n        if (!isNaN(h)) {\n            if (h > 1) h = h \/ 100;\n            heightPercentage = Math.min(1, Math.max(0, h));\n        }\n    }\n    \n    let userRadius = source.getLoginInfoMap().get(\"📐段评圆角，填0-50之间\");\n    let cornerRadius = 20; \/\/ 默认20\n    if (userRadius) {\n        let r = parseInt(userRadius);\n        if (!isNaN(r)) {\n            cornerRadius = Math.min(50, Math.max(0, r)); \/\/ 限制在0-50之间\n        }\n    }\n    \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\": cornerRadius,\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\": heightPercentage,\n                        \"isHideable\": true\n                    })\n                );\n            } catch (e) {\n                java.showBrowser(\n                    url,\n                    null,\n                    `window.java=java;`,\n                    JSON.stringify({\n                        \"expandedCornersRadius\": cornerRadius,\n                        \"dismissOnTouchOutside\": true,\n                        \"hardwareAccelerated\": true,\n                        \"isNestedScrollingEnabled\": true,\n                        \"isGestureInsetBottomIgnored\": true,\n                        \"heightPercentage\": heightPercentage,\n                        \"isHideable\": true\n                    })\n                );\n            }\n        } else {\n            java.startBrowser(url, title);\n        }\n    }\n}\n\nfunction createSvg(number, color, bid, cid, para, sourceType = 'fq') {\n    let { java, source, cache } = 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    let customSvgRaw = source.getLoginInfoMap().get(\"自定义气泡SVG\");\n    \n    let customSvg = \"\";\n    if (customSvgRaw) {\n        if (typeof customSvgRaw == 'function') {\n            try { customSvg = customSvgRaw(); } catch (e) {}\n        } else if (typeof customSvgRaw == 'string') {\n            customSvg = customSvgRaw;\n        } else {\n            customSvg = String(customSvgRaw);\n        }\n    }\n    \n    const isValidSvg = customSvg && customSvg.trim().startsWith('<svg') && customSvg.trim().endsWith('<\/svg>');\n    \n    let imageStyle = cache.get(\"imgstyle\") ?? \"TEXT\";\n    \n    var svg;\n    if (isQingRead || isModifiedVersion) {\n        if (isValidSvg) {\n            svg = customSvg.replace(\/\\{\\{color\\}\\}\/g, color).replace(\/\\{\\{displayText\\}\\}\/g, displayText);\n        } else {\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        }\n    } else {\n        svg = isValidSvg ? customSvg : '<svg width=\"1000\" height=\"909\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\"><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\" fill=\"none\" stroke=\"' + color + '\" stroke-width=\"16\" stroke-linejoin=\"round\"\/><text x=\"500\" y=\"450\" font-family=\"Arial, sans-serif\" text-anchor=\"middle\" font-size=\"360\" fill=\"' + color + '\" dy=\"0.3em\">' + displayText + '<\/text><\/svg>';\n    }\n\n    var encodedSvg = java.base64Encode(svg);\n\n    const jsFunction = {\n        'jj': 'showJJCmt',\n        'qm': 'showQmCmt',\n        'qq': 'showQQCmt',\n        'qd': 'showQDCmt',\n        'fq': 'showCmt'\n    }[sourceType] || 'showCmt';\n\n    const baseData = `data:image\/svg+xml;base64,${encodedSvg},{'${isModifiedVersion ? 'click' : 'js'}':'${jsFunction}(\"${bid}\",\"${cid}\",\"${para}\",\"${date}\")','style':'${imageStyle}'}`;\n\n    if (sourceType == 'qq' && para == 0) return baseData;\n    return deviceType.call(this) ? baseData : `<img src=\"${baseData}\">`;\n}\n\nfunction createGodReviewSvg(reviews, bid, cid, paraId) {\n    let { java, source, cache } = this;\n    \n    if (!reviews || reviews.length == 0) return '';\n    \n    let review = reviews[0];\n    let content = review.content || '';\n    \n    content = content.replace(\/https?:\\\/\\\/[^\\s]+\/g, \"\")\n                     .replace(\/qdd\\.gg\\\/[^\\s]+\/g, \"\")\n                     .replace(\/\\[fn=\\d+\\]\/g, \"\");\n    \n    let displayText = content.trim();\n    let maxChars = 21;\n    \n    if (displayText.length > maxChars) {\n        displayText = displayText.substring(0, maxChars) + '…';\n    }\n    \n    if (!displayText) return '';\n    \n    let isModifiedVersion = checkEnv.call(this);\n    \n    let tagColor = \"#FF4444\";\n    \n    let textColor = \"#FF4444\";\n    try {\n        let loginInfo = source.getLoginInfoMap() || \"\";\n        if (loginInfo['神评文字颜色'] && loginInfo['神评文字颜色'].startsWith('#')) {\n            textColor = loginInfo['神评文字颜色'];\n        }\n    } catch(e) {}\n    \n    let borderColor = \"#CCCCCC\";\n    try {\n        let loginInfo = source.getLoginInfoMap() || \"\";\n        if (loginInfo['神评边框颜色'] && loginInfo['神评边框颜色'].startsWith('#')) {\n            borderColor = loginInfo['神评边框颜色'];\n        }\n    } catch(e) {}\n    \n    displayText = displayText.replace(\/&\/g, '&amp;')\n                             .replace(\/<\/g, '&lt;')\n                             .replace(\/>\/g, '&gt;')\n                             .replace(\/\"\/g, '&quot;')\n                             .replace(\/'\/g, '&apos;');\n\n    let svg = `<svg viewBox=\"0 0 600 60\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"display:block;margin-top:2px;width:100%;max-width:600px;height:auto;\">\n        <rect x=\"2\" y=\"2\" width=\"596\" height=\"56\" fill=\"rgba(255,255,255,0.10)\" rx=\"16\" stroke=\"${borderColor}\" stroke-width=\"0.8\"\/>\n        <rect x=\"16\" y=\"14\" width=\"60\" height=\"32\" rx=\"10\" fill=\"${tagColor}\"\/>\n        <text x=\"46\" y=\"36\" font-family=\"system-ui, -apple-system, sans-serif\" font-size=\"18\" text-anchor=\"middle\" font-weight=\"600\" fill=\"#FFFFFF\">神评论<\/text>\n        <text x=\"90\" y=\"40\" font-family=\"system-ui, -apple-system, sans-serif\" font-size=\"23\" font-weight=\"500\" fill=\"${textColor}\">${displayText}<\/text>\n    <\/svg>`;\n    \n    let encodedSvg = java.base64Encode(svg);\n    \n    let jsFunction = 'showQDCmt';\n    let date = String(Date.now()).match(\/(\\d{6}$)\/)[1];\n    \n    const baseData = `data:image\/svg+xml;base64,${encodedSvg},{'${isModifiedVersion ? 'click' : 'js'}':'${jsFunction}(\"${bid}\",\"${cid}\",\"${paraId}\",\"${date}\")','style':'full'}`;\n    \n    return `<img src=\"${baseData}\">`;\n}\n\nfunction createChapterEndSvg(bid, cid, totalCount, authorName, authorExpectText, commentList) {\n    let { java, source, cache } = this;\n    \n    let color = getCommentIconColor.call(this);\n    let isModifiedVersion = checkEnv.call(this);\n    \n    let countText = totalCount > 999 ? \"999+\" : String(totalCount);\n    \n    var EMOJI = {\n        1:\"👏\",2:\"🌹\",3:\"🤝\",4:\"😁\",5:\"😄\",6:\"🥺\",7:\"🙂\",8:\"😏\",9:\"😙\",10:\"👆🏻🐽\",\n        11:\"🙄\",12:\"😭\",13:\"😵\",14:\"😥\",15:\"🖕🏻\",16:\"🥵\",17:\"😓\",18:\"🤫\",19:\"😂\",20:\"😢\",\n        21:\"😍\",22:\"🤕🔨\",23:\"😑\",24:\"😫\",25:\"🤗\",26:\"🤪\",27:\"🙏\",28:\"😣\",29:\"💪\",30:\"💀\",\n        31:\"😳\",32:\"😎\",33:\"🤭\",34:\"😄👏\",35:\"👍🏻\",36:\"🤓\",37:\"😡\",38:\"🙁\",39:\"😄❓\",40:\"😞\",\n        41:\"😧\",42:\"💋\",43:\"☺️\",44:\"🤬\",45:\"😴\",46:\"🤠🚬\",47:\"😱\",48:\"🐷\",49:\"😪\",50:\"🤐\",\n        51:\"🥴\",52:\"🌙\",53:\"❤️\",54:\"🔪\",55:\"🎁\",56:\"💔\",57:\"👊🏻\",58:\"😒\",59:\"✌🏻️\",60:\"😮\",\n        61:\"🤨\",62:\"😴\",63:\"👏🏻\",64:\"🐲\",65:\"⭐\",66:\"🌧️\",67:\"🍉\",68:\"🍵\",69:\"🔥\",70:\"💯\"\n    };\n    \n    function removeFnEmoji(s) {\n        return s ? s.replace(\/\\[fn=\\d+\\]\/g, '') : '';\n    }\n    \n    function replaceFnEmoji(s) {\n        return s ? s.replace(\/\\[fn=(\\d+)\\]\/g, function(match, id) {\n            return EMOJI[parseInt(id)] || '';\n        }) : '';\n    }\n    \n    let commentListArray = [];\n    if (commentList && typeof commentList == 'object' && commentList.length !== undefined) {\n        commentListArray = commentList;\n    } else if (Array.isArray(commentList)) {\n        commentListArray = commentList;\n    }\n    \n    let maxComments = 3;\n    let validComments = [];\n    for (let i = 0; i < commentListArray.length && validComments.length < maxComments; i++) {\n        let c = commentListArray[i] || {};\n        let content = c.Content || c.content || '';\n        if (content && content.trim()) {\n            let userName = c.UserName || c.userName || c.nickname || '匿名';\n            let likeCount = c.AgreeAmount || c.agreeAmount || c.likeCount || 0;\n            let formattedContent = replaceFnEmoji(content.trim());\n            validComments.push({\n                userName: userName.replace(\/&\/g, '&amp;').replace(\/<\/g, '&lt;').replace(\/>\/g, '&gt;').replace(\/\"\/g, '&quot;').replace(\/'\/g, '&apos;'),\n                content: formattedContent.replace(\/&\/g, '&amp;').replace(\/<\/g, '&lt;').replace(\/>\/g, '&gt;').replace(\/\"\/g, '&quot;').replace(\/'\/g, '&apos;'),\n                likeCount: likeCount\n            });\n        }\n    }\n    \n    if (validComments.length == 0) {\n        var emptyHeight = 150;\n        var svg = '<svg viewBox=\"0 0 700 ' + emptyHeight + '\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"display:block;margin-top:16px;width:100%;max-width:700px;height:auto;\">\\n';\n        svg += '<rect x=\"0\" y=\"0\" width=\"700\" height=\"' + emptyHeight + '\" rx=\"18\" fill=\"none\" stroke=\"' + color + '\" stroke-width=\"0.5\" opacity=\"0.6\"\/>\\n';\n        svg += '<text x=\"32\" y=\"44\" font-family=\"system-ui, -apple-system, sans-serif\" font-size=\"24\" font-weight=\"700\" fill=\"' + color + '\" opacity=\"1\">💬 本章说<\/text>\\n';\n        svg += '<text x=\"668\" y=\"44\" text-anchor=\"end\" font-family=\"system-ui, -apple-system, sans-serif\" font-size=\"18\" font-weight=\"600\" fill=\"' + color + '\" opacity=\"0.9\">' + countText + '条评论 ›<\/text>\\n';\n        svg += '<line x1=\"28\" y1=\"58\" x2=\"672\" y2=\"58\" stroke=\"' + color + '\" stroke-width=\"0.3\" opacity=\"0.35\"\/>\\n';\n        svg += '<text x=\"350\" y=\"110\" text-anchor=\"middle\" font-family=\"system-ui, -apple-system, sans-serif\" font-size=\"16\" fill=\"' + color + '\" opacity=\"0.55\">还没有评论，快来抢沙发吧~<\/text>\\n';\n        svg += '<\/svg>';\n        \n        var encodedSvg = java.base64Encode(svg);\n        var jsFunction = 'showQDCmt';\n        var date = String(Date.now()).match(\/(\\d{6}$)\/)[1];\n        var baseData = 'data:image\/svg+xml;base64,' + encodedSvg + ',{\\'' + (isModifiedVersion ? 'click' : 'js') + '\\':\\'' + jsFunction + '(\"' + bid + '\",\"' + cid + '\",\"benzhang\",\"' + date + '\")\\',\\'style\\':\\'FULL\\'}';\n        return '\\n' + '<img src=\"' + baseData + '\">';\n    }\n    \n    let headerHeight = 100;\n    let cardHeight = 130;\n    let cardGap = 24;\n    let bottomPadding = -20;\n    let totalHeight = headerHeight + validComments.length * (cardHeight + cardGap) + bottomPadding;\n    \n    var svg = '<svg viewBox=\"0 0 700 ' + totalHeight + '\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"display:block;margin-top:16px;width:100%;max-width:700px;height:auto;\">\\n';\n    svg += '<rect x=\"0\" y=\"0\" width=\"700\" height=\"' + totalHeight + '\" rx=\"18\" fill=\"none\" stroke=\"' + color + '\" stroke-width=\"0.5\" opacity=\"0.6\"\/>\\n';\n    svg += '<text x=\"32\" y=\"44\" font-family=\"system-ui, -apple-system, sans-serif\" font-size=\"24\" font-weight=\"700\" fill=\"' + color + '\" opacity=\"1\">💬 本章说<\/text>\\n';\n    svg += '<text x=\"668\" y=\"44\" text-anchor=\"end\" font-family=\"system-ui, -apple-system, sans-serif\" font-size=\"18\" font-weight=\"600\" fill=\"' + color + '\" opacity=\"0.9\">' + countText + '条评论 ›<\/text>\\n';\n    svg += '<line x1=\"28\" y1=\"58\" x2=\"672\" y2=\"58\" stroke=\"' + color + '\" stroke-width=\"0.3\" opacity=\"0.35\"\/>\\n';\n    \n    validComments.forEach(function(c, idx) {\n        let yPos = 77 + idx * (cardHeight + cardGap);\n        let maxCharsPerLine = 23;\n        let contentDisplay = c.content;\n        \n        if (contentDisplay.length > maxCharsPerLine * 2) {\n            contentDisplay = contentDisplay.substring(0, maxCharsPerLine * 2) + '…';\n        }\n        \n        let hasTwoLines = contentDisplay.length > maxCharsPerLine;\n        let line1Text = hasTwoLines ? contentDisplay.substring(0, maxCharsPerLine) : contentDisplay;\n        let line2Text = hasTwoLines ? contentDisplay.substring(maxCharsPerLine) : '';\n        \n        let likeText = c.likeCount > 999 ? \"999+\" : String(c.likeCount);\n        let likeWidth = likeText.length * 13 + 50;\n        let likeX = 668 - likeWidth;\n        \n        let avatarColors = [\"#e07078\", \"#6090d0\", \"#50b878\", \"#d0a860\", \"#9060c8\"];\n        let avatarColor = avatarColors[idx % avatarColors.length];\n        let initial = c.userName.charAt(0);\n        \n        let cardBg = idx % 2 == 0 ? 'rgba(128,128,128,0.1)' : 'rgba(128,128,128,0.05)';\n        \n        svg += '<rect x=\"14\" y=\"' + yPos + '\" width=\"672\" height=\"' + cardHeight + '\" rx=\"14\" fill=\"' + cardBg + '\"\/>\\n';\n        svg += '<circle cx=\"50\" cy=\"' + (yPos + 34) + '\" r=\"19\" fill=\"' + avatarColor + '\" opacity=\"0.95\"\/>\\n';\n        svg += '<text x=\"50\" y=\"' + (yPos + 40) + '\" text-anchor=\"middle\" font-family=\"system-ui, -apple-system, sans-serif\" font-size=\"16\" font-weight=\"700\" fill=\"#ffffff\">' + initial + '<\/text>\\n';\n        svg += '<text x=\"82\" y=\"' + (yPos + 28) + '\" font-family=\"system-ui, -apple-system, sans-serif\" font-size=\"21\" font-weight=\"700\" fill=\"' + color + '\" opacity=\"1\">' + c.userName + '<\/text>\\n';\n        svg += '<text x=\"82\" y=\"' + (yPos + 72) + '\" font-family=\"system-ui, -apple-system, sans-serif\" font-size=\"24\" font-weight=\"400\" fill=\"' + color + '\" opacity=\"1\">　　' + line1Text + '<\/text>\\n';\n        if (hasTwoLines) {\n            svg += '<text x=\"82\" y=\"' + (yPos + 104) + '\" font-family=\"system-ui, -apple-system, sans-serif\" font-size=\"24\" font-weight=\"400\" fill=\"' + color + '\" opacity=\"1\">　　　' + line2Text + '<\/text>\\n';\n        }\n        svg += '<g transform=\"translate(' + likeX + ', ' + (yPos + 6) + ')\">\\n';\n        svg += '<rect x=\"0\" y=\"0\" width=\"' + likeWidth + '\" height=\"28\" rx=\"14\" fill=\"none\" stroke=\"' + color + '\" stroke-width=\"0.7\" opacity=\"0.7\"\/>\\n';\n        svg += '<g transform=\"translate(9, 4)\">\\n';\n        svg += '<path d=\"M12 8V4a3 3 0 0 0-3-3L5 10v10h10.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM6 20H3a2 2 0 0 1-2-2v-6a2 2 0 0 1 2-2h3\" fill=\"none\" stroke=\"' + color + '\" stroke-width=\"1.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" transform=\"scale(0.78)\" opacity=\"0.85\"\/>\\n';\n        svg += '<\/g>\\n';\n        svg += '<text x=\"' + (likeWidth \/ 2 + 8) + '\" y=\"19\" text-anchor=\"middle\" font-family=\"system-ui, -apple-system, sans-serif\" font-size=\"14\" font-weight=\"600\" fill=\"' + color + '\" opacity=\"0.85\">' + likeText + '<\/text>\\n';\n        svg += '<\/g>\\n';\n    });\n    \n    svg += '<\/svg>';\n    \n    var encodedSvg = java.base64Encode(svg);\n    var jsFunction = 'showQDCmt';\n    var date = String(Date.now()).match(\/(\\d{6}$)\/)[1];\n    var baseData = 'data:image\/svg+xml;base64,' + encodedSvg + ',{\\'' + (isModifiedVersion ? 'click' : 'js') + '\\':\\'' + jsFunction + '(\"' + bid + '\",\"' + cid + '\",\"benzhang\",\"' + date + '\")\\',\\'style\\':\\'FULL\\'}';\n    \n    return '\\n' + '<img src=\"' + baseData + '\">';\n}\n\nfunction checkClickRateLimit(lastCallKey) {\n    let { cache } = 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 { cache } = 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, extraParams = {}) {\n    let { java, cache, source } = this;\n    \n    if (content == null || content == undefined) {\n        return '';\n    }\n    if (typeof content !== 'string') {\n        try {\n            content = String(content);\n        } catch (e) {\n            return '';\n        }\n    }\n    \n    let apiBase = getParagraphApiUrl.call(this);\n    \n    try {\n        let lines = content\n            .replace(new RegExp('(<img[^>]*?>)\\n', 'g'), \"$1\")\n            .split(\"\\n\")\n            .filter(line => line && line.trim());\n\n        if (lines.length == 0) {\n            return wrapContentForIOS.call(this, content);\n        }\n\n        let isModifiedVersion = checkEnv.call(this);\n        let isQingRead = isQRead.call(this);\n        let isIOS = deviceType.call(this);\n\n        let apiUrl = buildParagraphUrl.call(this, apiBase, sourceType, bid, cid, contentMd5, extraParams);\n        if (!apiUrl) {\n            return wrapContentForIOS.call(this, content);\n        }\n\n        let response;\n        try {\n            response = java.ajax(apiUrl);\n        } catch (e) {\n            return wrapContentForIOS.call(this, content);\n        }\n\n        if (!response || (typeof response == 'string' && response.trim() == '')) {\n            return wrapContentForIOS.call(this, content);\n        }\n\n        let result;\n        try {\n            result = JSON.parse(response);\n        } catch (e) {\n            return wrapContentForIOS.call(this, content);\n        }\n\n        if (!result || result.code !== 0 || !result.data || !result.data.list) {\n            let msg = (result && result.msg) ? result.msg : '请求失败';\n            \/\/java.toast(msg);\n            return wrapContentForIOS.call(this, content);\n        }\n\n        let paragraphList = result.data.list;\n        if (!Array.isArray(paragraphList) || paragraphList.length == 0) {\n            return wrapContentForIOS.call(this, content);\n        }\n        \n        let reviews = [];\n        let chapterEndData = null;\n        \n        if (sourceType == 'qd') {\n            try {\n                if (result.data.reviews && Array.isArray(result.data.reviews)) {\n                    reviews = result.data.reviews;\n                }\n            } catch (e) {\n                reviews = [];\n            }\n            \n            try {\n                let host = getServerHost.call(this);\n                let authorUrl = host + '\/qd_comment?action=comments&bid=' + bid + '&cid=' + cid + '&para=1&author=1';\n                let authorResponse;\n                try {\n                    authorResponse = java.ajax(authorUrl);\n                } catch (e) {\n                    authorResponse = null;\n                }\n                \n                if (authorResponse) {\n                    let authorData;\n                    try {\n                        authorData = JSON.parse(authorResponse);\n                    } catch (e) {\n                        authorData = null;\n                    }\n                    \n                    if (authorData) {\n                        const activity = authorData?.Data?.Getchapteractivity;\n                        \n                        if (activity && activity.AuthorName) {\n                            var authorName = activity.AuthorName || \"\";\n                            var authorSay = activity.AuthorComments || \"\";\n                            var authorExpect = activity.AskMonthTicket?.AuthorExpectText || \"\";\n                            var displayText = authorSay || authorExpect || \"\";\n                            \n                            if (authorName && displayText) {\n                                authorName = authorName.replace(\/&\/g, '&amp;').replace(\/<\/g, '&lt;').replace(\/>\/g, '&gt;');\n                                displayText = displayText.replace(\/&\/g, '&amp;').replace(\/<\/g, '&lt;').replace(\/>\/g, '&gt;');\n                                \n                                var textLines = [];\n                                var temp = displayText;\n                                var maxChars = 30;\n                                while (temp.length > maxChars) {\n                                    textLines.push(temp.substring(0, maxChars));\n                                    temp = temp.substring(maxChars);\n                                }\n                                if (temp.length > 0) textLines.push(temp);\n                                \n                                var textHeight = textLines.length * 30;\n                                var totalHeight = 60 + textHeight;\n                                var avatarChar = authorName.charAt(0);\n                                var avatarColor = \"#FF6B6B\";\n                                \n                                var svg = '<svg viewBox=\"0 0 700 ' + totalHeight + '\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" style=\"display:block;margin-bottom:8px;width:100%;max-width:700px;height:auto;\">\\n';\n                                svg += '<rect x=\"0\" y=\"0\" width=\"700\" height=\"' + totalHeight + '\" fill=\"rgba(255,68,68,0.04)\" rx=\"12\" stroke=\"#FF4444\" stroke-width=\"0.3\"\/>\\n';\n                                svg += '<circle cx=\"50\" cy=\"38\" r=\"22\" fill=\"' + avatarColor + '\"\/>\\n';\n                                svg += '<text x=\"50\" y=\"44\" font-size=\"20\" text-anchor=\"middle\" font-weight=\"700\" fill=\"#FFFFFF\">' + avatarChar + '<\/text>\\n';\n                                svg += '<text x=\"86\" y=\"30\" font-size=\"17\" font-weight=\"700\" fill=\"#333333\">' + authorName + '<\/text>\\n';\n                                svg += '<text x=\"' + (86 + authorName.length * 17 + 6) + '\" y=\"30\" font-size=\"14\" font-weight=\"500\" fill=\"#FF4444\">作家的话<\/text>\\n';\n                                \n                                textLines.forEach(function(line, idx) {\n                                    svg += '<text x=\"86\" y=\"' + (60 + idx * 30) + '\" font-size=\"18\" font-weight=\"400\" fill=\"#666666\">' + line + '<\/text>\\n';\n                                });\n                                \n                                svg += '<\/svg>';\n                                \n                                var authorSvgBase64 = java.base64Encode(svg);\n                                var authorDataUrl = 'data:image\/svg+xml;base64,' + authorSvgBase64 + ',{\"style\":\"FULL\"}';\n                                lines.push('<img src=\"' + authorDataUrl + '\" \/>');\n                            }\n                        }\n                    }\n                }\n            } catch (e) {}\n            \n            try {\n                let host = getServerHost.call(this);\n                let chapterEndUrl = host + '\/paras\/proxy_paragraph?bid=' + bid + '&cid=' + cid;\n                let chapterEndResponse;\n                try {\n                    chapterEndResponse = java.ajax(chapterEndUrl);\n                } catch (e) {\n                    chapterEndResponse = null;\n                }\n                \n                if (chapterEndResponse) {\n                    let chapterEndResult;\n                    try {\n                        chapterEndResult = JSON.parse(chapterEndResponse);\n                    } catch (e) {\n                        chapterEndResult = null;\n                    }\n                    \n                    if (chapterEndResult) {\n                        const endActivity = chapterEndResult?.Data?.Getchapteractivity;\n                        const endComments = chapterEndResult?.Data?.Getchapterendcomments;\n                        if (endActivity && endComments) {\n                            chapterEndData = {\n                                totalCount: endComments.TotalCount || 0,\n                                authorName: endActivity.AuthorName || '',\n                                authorExpectText: endActivity.AskMonthTicket?.AuthorExpectText || '',\n                                commentList: endComments.DataList || []\n                            };\n                        }\n                    }\n                }\n            } catch (e) {}\n        }\n        \n        let reviewsByParagraph = {};\n        try {\n            reviews.forEach(review => {\n                let paraId = review.paragraph_id;\n                if (!reviewsByParagraph[paraId]) {\n                    reviewsByParagraph[paraId] = [];\n                }\n                reviewsByParagraph[paraId].push(review);\n            });\n        } catch (e) {}\n        \n        lines = lines.map(line => {\n            if (typeof line !== 'string') {\n                try {\n                    return String(line);\n                } catch (e) {\n                    return '';\n                }\n            }\n            return line;\n        });\n        \n        let hasTitleComment = false;\n        let titleCommentData = null;\n\n        paragraphList.forEach((item, index) => {\n            let paraId = item.paragraph_id || item.paragraphId;\n            let count = item.comment_count || item.textCount || 0;\n            let hot = (item.hot == 1 || item.is_hot_comment == 1 || item.isHotComment == true);\n            if (count <= 0) return;\n\n            let lineIndex;\n            if (sourceType == 'qq') {\n                if (paraId == 0) {\n                    hasTitleComment = true;\n                    titleCommentData = item;\n                    return;\n                }\n                lineIndex = parseInt(paraId) - 1;\n            } else if (sourceType == 'qd') {\n                if (paraId == -1) {\n                    hasTitleComment = true;\n                    titleCommentData = item;\n                    return;\n                }\n                lineIndex = parseInt(paraId) - 1;\n            } else if (sourceType == 'qm') {\n                lineIndex = index;\n            } else {\n                lineIndex = sourceType == 'jj' ? parseInt(paraId) - 1 : parseInt(paraId);\n            }\n\n            if (lineIndex < 0 || lineIndex >= lines.length) return;\n            \n            if (typeof lines[lineIndex] !== 'string') return;\n            \n            if (\/<img[^>]*>\/i.test(lines[lineIndex])) {\n                return;\n            }\n\n            if (sourceType == 'fq') {\n                let textOnly = String(lines[lineIndex])\n                    .replace(new RegExp('<[^>]*>', 'g'), '')\n                    .replace(\/&nbsp;\/g, ' ')\n                    .trim();\n                if (!textOnly) {\n                    return;\n                }\n                if (!isModifiedVersion && !isQingRead) {\n                    cache.putMemory(`fq-${bid}-${cid}-${paraId}-text`, textOnly);\n                }\n            }\n\n            if (!isModifiedVersion && !isQingRead && sourceType != 'fq') {\n                let cacheKey = `${sourceType}-${bid}-${cid}-${paraId}-text`;\n                cache.putMemory(cacheKey, lines[lineIndex]);\n            }\n\n            let color = hot ? \"#f06060\" : getCommentIconColor.call(this);\n            let commentTag = createSvg.call(this, count, color, bid, cid, paraId, sourceType);\n            \n            let reviewTag = '';\n            if (sourceType == 'qd' && reviewsByParagraph[paraId]) {\n                reviewTag = createGodReviewSvg.call(this, reviewsByParagraph[paraId], bid, cid, paraId);\n            }\n            \n            if (isIOS) {\n                lines[lineIndex] += commentTag;\n                if (reviewTag) {\n                    lines[lineIndex] += '<br>' + reviewTag;\n                }\n            } else {\n                if (commentTag.startsWith('data:image')) {\n                    lines[lineIndex] += `<img src=\"${commentTag}\">`;\n                } else {\n                    lines[lineIndex] += commentTag;\n                }\n                if (reviewTag) {\n                    if (reviewTag.startsWith('data:image')) {\n                        lines[lineIndex] += '\\n' + `<img src=\"${reviewTag}\">`;\n                    } else {\n                        lines[lineIndex] += '\\n' + reviewTag;\n                    }\n                }\n            }\n        });\n\n        if (hasTitleComment && titleCommentData) {\n            let titleCount = titleCommentData.comment_count || titleCommentData.textCount || 0;\n            let titleHot = (titleCommentData.hot == 1 || titleCommentData.is_hot_comment == 1 || titleCommentData.isHotComment == true);\n            let color = titleHot ? \"#f06060\" : getCommentIconColor.call(this);\n            let titleBubble = createSvg.call(this, titleCount, color, bid, cid, 0, sourceType);\n            cache.put(bid + \"_\" + cid, titleBubble);\n        }\n\n        let resultContent = lines.join(\"\\n\");\n        if (sourceType == 'qd' && chapterEndData && chapterEndData.totalCount > 0) {\n            let chapterEndSvg = createChapterEndSvg.call(this, bid, cid, chapterEndData.totalCount, chapterEndData.authorName, chapterEndData.authorExpectText, chapterEndData.commentList);\n            resultContent += chapterEndSvg;\n        }\n\n        return wrapContentForIOS.call(this, resultContent);\n    } catch (e) {\n        return wrapContentForIOS.call(this, content);\n    }\n}\n\neval(function(p,a,c,k,e,r){e=function(c){return 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'[0-9a-n]'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\\\b'+e(c)+'\\\\b','g'),k[c]);return p}('function buildParagraphUrl(0,g,3,1,b,4){c secretKey=getSecretKey.h(i);switch(g){5\\'j\\':c 8=`${0}?6=j&9=${3}&7=${1}`;d(4.e&&4.f){8+=`&e=${4.e}&f=${4.f}`}c a=JJtoken.h(i);d(a)8+=`&a=${a}`;2 8;5\\'k\\':d(!b)2 null;2`${0}?6=k&9=${3}&7=${1}&content_md5=${b}`;5\\'l\\':2`${0}?6=l&9=${3}&7=${1}`;5\\'m\\':2`${0}?6=m&9=${3}&7=${1}`;5\\'n\\':default:2`${0}?6=n&7=${1}`}}',[],24,'apiBase|cid|return|bid|extraParams|case|source|chapter_id|jjUrl|book_id|token|contentMd5|let|if|bookid|chapterid|sourceType|call|this|jj|qm|qq|qd|fq'.split('|'),0,{}))\n\nfunction showJJCmt(bid, cid, para, date) {\n    handleCommentClick.call(this, 'jj', bid, cid, para, date, `jj-${bid}-${cid}-${para}`);\n}\n\nfunction showQDCmt(bid, cid, para, date) {\n    handleCommentClick.call(this, 'qd', bid, cid, para, date, `qd-${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 showQQCmt(bid, cid, para, date) {\n    handleCommentClick.call(this, 'qq', bid, cid, para, date, `qq-${bid}-${cid}-${para}`);\n}\n\nfunction JJtoken() {\n    let { java, source } = this;\n    const loginInfo = source.getLoginInfoMap();\n    const key = loginInfo['晋江登录Token'] || '';\n    if (!key) {\n        java.toast(\"无法阅读未共享的章节和订阅付费章节\");\n    }\n    return java.base64Encode(key);\n}\n\nfunction getSortKey() {\n    let { java } = this;\n    const sortKey = java.get(\"sortkey\") || '';\n    if (!sortKey) {\n        java.toast(\"请先设置排序参数\");\n    }\n    return sortKey;\n}\n\nfunction getTtsTone(sourceType) {\n    let config = getConfig.call(this);\n    if (sourceType == 'fanqie') {\n        return config.fanqie_tts_tone || \"1\";\n    } else if (sourceType == 'shuqi') {\n        return config.shuqi_tts_tone || \"yifei\";\n    }\n    return \"\";\n}\n\nfunction extractIdFromUrl(baseUrl, regex) {\n    let { java } = this;\n    try {\n        let finalUrl = baseUrl;\n        if (baseUrl.includes(\"url=\")) {\n            let urlMatch = baseUrl.match(\/url=([^&]*)\/);\n            if (urlMatch) finalUrl = java.base64Decode(urlMatch[1]);\n        } else if (baseUrl.startsWith(\"data:\")) {\n            let data = JSON.parse(java.base64Decode(baseUrl.split(\",\")[1]));\n            finalUrl = java.base64Decode(data.url);\n        }\n        let idMatch = finalUrl.match(regex);\n        return idMatch ? idMatch[1] : null;\n    } catch (e) {\n        return null;\n    }\n}\n\nfunction getBookIdFromUrl(baseUrl) {\n    return extractIdFromUrl.call(this, baseUrl, \/book_id=(\\d{19})\/);\n}\n\nfunction getJjwxcNovelIdFromUrl(baseUrl) {\n    return extractIdFromUrl.call(this, baseUrl, \/[?&]novelId=(\\d{5,8})\/);\n}\n\nfunction getBookData(bookId) {\n    let { java, source } = this;\n    let newUrl = getServerHost.call(this) + '\/detail?book_id=' + bookId;\n    let response = java.ajax(newUrl);\n    let responseData = JSON.parse(response);\n    \n    if (Array.isArray(responseData.data)) {\n        return responseData.data[0];\n    } else if (responseData.data && typeof responseData.data == 'object') {\n        return responseData.data;\n    } else if (responseData.code == 0 && responseData.data) {\n        return responseData.data;\n    }\n    return null;\n}\n\nfunction parseAuthors(authorsData) {\n    let { java, source } = 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 syncReadingHistory(bookId) {\n    let { java, source } = 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 buildChapterUrl(x, catalog, device) {\n    let { java } = 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    \n    let chapterParams = {\n        cid: x.cid,\n        source: catalog.source\n    };\n    \n    if (catalog.source === '书旗听书') {\n        const bookIdMatch = x.url?.match(\/[?&]?bookid=(\\d+)\/);\n        const itemIdMatch = x.url?.match(\/[?&]?item_ids=(\\d+)\/);\n        if (bookIdMatch && itemIdMatch) {\n            chapterParams.bookid = bookIdMatch[1];\n            chapterParams.item_ids = itemIdMatch[1];\n        }\n    }\n    \n    if (!isFanqieApp) {\n        chapterParams.url = catalog.url;\n    } else {\n        chapterParams.book_id = bookId;\n        chapterParams.item_id = itemId;\n    }\n    \n    if (['企鹅看书', 'QQ阅读', '起点'].includes(catalog.source) && x.url && typeof x.url == 'string') {\n        const bMatch = x.url.match(\/bookid=(\\d+)\/);\n        const cMatch = x.url.match(\/chapterid=(\\d+)\/);\n        if (bMatch && cMatch) {\n            let bid = bMatch[1];\n            if (catalog.source == '企鹅看书') {\n                bid = bid.length > 2 ? bid.substring(2) : bid;\n            }\n            chapterParams.bookid = bid;\n            chapterParams.chapterid = cMatch[1];\n        }\n    }\n    \n    if (catalog.source == '晋江' || catalog.source == '半夏') {\n        const novelIdMatch = x.url?.match(\/novelId=(\\d+)\/);\n        const chapterIdMatch = x.url?.match(\/chapterId=(\\d+)\/);\n        if (novelIdMatch && chapterIdMatch) {\n            chapterParams.novelId = novelIdMatch[1];\n            chapterParams.chapterId = chapterIdMatch[1];\n        }\n    }\n    \n    if (catalog.source == '七猫') {\n        chapterParams.qm_url = x.url;\n    }\n    \n    if (catalog.source == '七猫短剧') {\n        try {\n            let decodedUrl = java.base64Decode(catalog.url);\n            let qmIdMatch = decodedUrl.match(\/qm_id=(\\d+)\/);\n            if (qmIdMatch) {\n                chapterParams.qm_id = qmIdMatch[1];\n            }\n        } catch (e) {}\n    }\n    \n    let commentPart = '{\"type\":\"susan\"}';\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        if (qmBookId && qmChapterId) {\n            const qmCommentUrl = `${getServerHost.call(this)}\/qm_comment?book_id=${qmBookId}&item_id=${qmChapterId}`;\n            commentPart = `{\"type\":\"susan\",\"js\":\"book ? result : '${qmCommentUrl}'\"}`;\n        }\n    } else if (catalog.source == '晋江' || catalog.source == '半夏') {\n        const novelId = chapterParams.novelId;\n        const chapterId = chapterParams.chapterId;\n        if (novelId && chapterId) {\n            let jjCommentUrl = `${getServerHost.call(this)}\/jj_comment?bid=${novelId}&cid=${chapterId}`;\n            if (catalog.source == '半夏') {\n                const bjBidMatch = x.url?.match(\/bookid=(\\d+)\/);\n                const bjCidMatch = x.url?.match(\/chapterid=(\\d+)\/);\n                if (bjBidMatch && bjCidMatch) {\n                    jjCommentUrl += `&bookid=${bjBidMatch[1]}&chapterid=${bjCidMatch[1]}`;\n                }\n            }\n            commentPart = `{\"type\":\"susan\",\"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\":\"susan\",\"js\":\"book ? result : '${fanqieCommentUrl}'\"}`;\n    } else if (catalog.source == '云端') {\n        const decodedUrl = java.base64Decode(catalog.url);\n        const commentUrl = decodedUrl.replace('pt=web', 'pt=3g');\n        commentPart = `{\"type\":\"susan\",\"js\":\"book ? result : '${commentUrl}'\"}`;\n    }\n    \n    let encodedParams = java.base64Encode(JSON.stringify(chapterParams));\n    return `data:chapterUrl;base64,${encodedParams},${commentPart}`;\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 { java } = this;\n    if (key && key.length == 19 && !Number.isNaN(parseInt(key))) {\n        return handleFanqieSearch.call(this, key, host);\n    } else if (key && (key.length == 6 || key.length == 7 || key.length == 8) && !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 { java } = 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 handleJjwxcSearch(key, host) {\n    let { java } = this;\n    var jjApiUrl = `http:\/\/app-cdn.jjwxc.net\/androidapi\/novelbasicinfo?novelId=${key}`;\n    var jjResponse = java.ajax(jjApiUrl);\n    var jjData = JSON.parse(jjResponse);\n    var novelName = jjData.novelName || '未知';\n    var encodedUrl = java.base64Encode(jjApiUrl);\n    return host + `\/details?url=${encodedUrl}&source=晋江&name=${encodeURIComponent(novelName)}`;\n}\n\nfunction encode(data) {\n    let { java } = this;\n    return java.createSymmetricCrypto(\"DES\/CBC\/PKCS5Padding\", String(\"KW8Dvm2N\"), String(\"1ae2c94b\")).encryptBase64(data)\n}\n\nfunction decode(data) {\n    let { java } = this;\n    try {\n        var encryptedBytes = java.base64DecodeToByteArray(data);\n        var key = \"G@tY3$jK7#mL2&pW8!\";\n        var iv = \"C!eH4&sM6@wP9^zX1?\";\n        var crypto = java.createSymmetricCrypto(\"AES\/CBC\/PKCS5Padding\", key, iv);\n        return crypto.decryptStr(encryptedBytes);\n    } catch (e) {\n        return data;\n    }\n}","lastUpdateTime":"1786275880515","loginUi":"[{\n        \"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        \"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        \"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        \"name\": \"☕打赏享福利\",\n        \"type\": \"button\",\n        \"action\": \"vip()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"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        \"name\": \"🥽SVG大小\",\n        \"type\": \"button\",\n        \"action\": \"imagestyle()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n    \t\"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        \"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\": \"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        \"name\": \"🍅记录同步\",\n        \"type\": \"button\",\n        \"action\": \"toggleBookSync()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🍅发现排序\",\n        \"type\": \"button\",\n        \"action\": \"SortFilter()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🎧 TTS音色\",\n        \"type\": \"button\",\n        \"action\": \"TTSSelector()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 1\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\n        \"name\": \"↓   专  属VIP源   站   ↓\",\n        \"type\": \"button\",\n        \"action\": \"\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 1\n        }\n    },\n    {\n        \"name\": \"🐧QQ阅读\",\n        \"type\": \"button\",\n        \"action\": \"sou51()\",\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        \"name\": \"📓晋江文学\",\n        \"type\": \"button\",\n        \"action\": \"sou55()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n       \"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\": \"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\": \"🍵茶马小说\",\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        \"name\": \"🌽速读谷\",\n        \"type\": \"button\",\n        \"action\": \"sou31()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🪷书耽网\",\n        \"type\": \"button\",\n        \"action\": \"sou52()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🛸得奇小说\",\n        \"type\": \"button\",\n        \"action\": \"sou49()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🛟万相书城\",\n        \"type\": \"button\",\n        \"action\": \"sou48()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🏔台湾小说\",\n        \"type\": \"button\",\n        \"action\": \"sou50()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"☁云端书库\",\n        \"type\": \"button\",\n        \"action\": \"sou41()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n    \t \"name\": \"🌧云图有声\",\n        \"type\": \"button\",\n        \"action\": \"sou53()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n\n        \"name\": \"📻书音FM\",\n        \"type\": \"button\",\n        \"action\": \"sou40()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"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\": \"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        \"name\": \"🐱七猫短剧\",\n        \"type\": \"button\",\n        \"action\": \"sou33()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"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.4\n        }\n    },\n    {\n        \"name\": \"🍂知乎盐选\",\n        \"type\": \"button\",\n        \"action\": \"sou14()\",\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        \"name\": \"🌊酷我小说\",\n        \"type\": \"button\",\n        \"action\": \"sou37()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"name\": \"🐰思兔阅读\",\n        \"type\": \"button\",\n        \"action\": \"sou25()\",\n        \"style\": {\n            \"layout_flexGrow\": 1,\n            \"layout_flexBasisPercent\": 0.4\n        }\n    },\n    {\n        \"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\": \"番茄登录Token\",\n        \"type\": \"text\"\n    }, {\n    \"name\": \"段评图标颜色\",\n    \"type\": \"text\"\n}, {\n    \"name\": \"番茄登录Token\",\n    \"type\": \"text\"\n}, {\n    \"name\": \"晋江登录Token\",\n    \"type\": \"password\"\n}, {\n    \"name\": \"🗑️清理缓存\",\n    \"type\": \"button\",\n    \"action\": \"cleanCache()\",\n    \"style\": {\n        \"layout_flexGrow\": 1,\n        \"layout_flexBasisPercent\": 0.45\n    }\n}, {\n    \"name\": \"🔄 强制刷新\",\n    \"type\": \"button\",\n    \"action\": \"ChapterRefresh()\",\n    \"style\": {\n        \"layout_flexGrow\": 1,\n        \"layout_flexBasisPercent\": 0.45\n    }\n}, {\n    \"name\": \"自定义气泡SVG\",\n    \"type\": \"text\",\n    \"style\": {\n        \"layout_flexGrow\": 1,\n        \"layout_flexBasisPercent\": 1\n    }\n}, {\n    \"name\": \"神评文字颜色\",\n    \"type\": \"text\",\n    \"style\": {\n        \"layout_flexGrow\": 1,\n        \"layout_flexBasisPercent\": 0.45\n    }\n}, {\n    \"name\": \"神评边框颜色\",\n    \"type\": \"text\",\n    \"style\": {\n        \"layout_flexGrow\": 1,\n        \"layout_flexBasisPercent\": 0.45\n    }\n}, {\n    \"name\": \"下面设置仅在阅读Sigma生效\",\n    \"type\": \"button\",\n    \"action\": \"\",\n    \"style\": {\n        \"layout_flexGrow\": 1,\n        \"layout_flexBasisPercent\": 1\n    }\n}, {\n    \"name\": \"📐段评高度%\",\n    \"type\": \"text\",\n    \"style\": {\n        \"layout_flexGrow\": 1,\n        \"layout_flexBasisPercent\": 1\n    }\n}, {\n    \"name\": \"📐段评圆角，填0-50之间\",\n    \"type\": \"text\",\n    \"style\": {\n        \"layout_flexGrow\": 1,\n        \"layout_flexBasisPercent\": 1\n    }\n}]","loginUrl":"\/\/请勿修改版本号\nconst localVersion = \"5.38\";\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:\"笔下小说\",m:\"笔下小说\"},\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:\"sou49\",v:\"得奇\",m:\"得奇小说\"},\n    {n:\"sou50\",v:\"台湾\",m:\"台湾小说\"},\n    {n:\"sou51\",v:\"QQ阅读\",m:\"🐧QQ阅读\"},\n    {n:\"sou52\",v:\"书耽网\",m:\"书耽网\"},\n    {n:\"sou53\",v:\"云图有声\",m:\"云图有声\"},\n    {n:\"sou54\",v:\"番薯小说\",m:\"番薯小说\"},\n    {n:\"sou55\",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 login(flag) {\n    const host = getServerHost();\n    \n    if (flag == undefined) {\n        const loginInfo = source.getLoginInfoMap();\n        var email = loginInfo['邮箱'];\n        var password = loginInfo['密码'];\n    } else {\n        source.putLoginInfo(JSON.stringify(result));\n        var email = result['邮箱'];\n        var password = result['密码'];\n    }\n    \n    if (!email || !password) {\n        java.toast(\"\\n❌ 请先填写账号和密码并点击✓保存后登录\");\n        return;\n    }\n    \n    java.toast(\"正在登录...\");\n    const url = host + '\/login';\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 data = JSON.parse(response);\n        \n        if (data.code == 200) {\n            const user = data.data.user;\n            const deviceInfo = data.data.device || {};\n            \n            source.putLoginHeader(user.api_key);\n            \n            const nickname = user.nickname || \"用户\";\n            const maskedEmail = email.includes('@') ? \n                email.substring(0, 3) + \"****\" + email.substring(email.indexOf('@')) : \n                email;\n            const memberStatus = (user.is_member || false) ? \"👑VIP会员\" : \"普通会员\";\n            const welcomeMsg = (user.is_member || false) ? \"欢迎回来，👑尊贵VIP会员\" : \"欢迎回来\";\n            \n            const onlineCount = deviceInfo.online_count || 0;\n            const deviceLimit = deviceInfo.device_limit || 1;\n            const totalCount = deviceInfo.total_count || 0;\n            \n            var deviceStatusText = \"\";\n            if (onlineCount >= deviceLimit && totalCount > 0) {\n                deviceStatusText = \"⚠️ 设备已满（\" + onlineCount + \"\/\" + deviceLimit + \"）\\n新设备登录将挤掉旧设备\";\n            } else if (onlineCount > 0) {\n                deviceStatusText = \"📱 在线设备：\" + onlineCount + \"\/\" + deviceLimit;\n            } else {\n                deviceStatusText = \"📱 当前无其他设备在线\";\n            }\n            \n            function maskDeviceId(deviceId) {\n                if (!deviceId || deviceId.length <= 8) return deviceId || '未知';\n                return deviceId.substring(0, 4) + \"****\" + deviceId.substring(deviceId.length - 4);\n            }\n            \n            function maskIp(ip) {\n                if (!ip) return '';\n                if (ip.indexOf('.') !== -1 && ip.indexOf(':') == -1) {\n                    var parts = ip.split('.');\n                    if (parts.length == 4) return parts[0] + '.' + parts[1] + '.*.*';\n                }\n                if (ip.indexOf(':') !== -1) {\n                    var parts = ip.split(':');\n                    var validParts = [];\n                    for (var i = 0; i < parts.length; i++) {\n                        if (parts[i] !== '') validParts.push(parts[i]);\n                    }\n                    if (validParts.length >= 4) return validParts.slice(0, 4).join(':') + ':*:*';\n                }\n                return ip;\n            }\n            \n            var deviceListText = \"\";\n            var deviceList = (deviceInfo && deviceInfo.list) ? deviceInfo.list : [];\n            if (deviceList && deviceList.length > 0) {\n                deviceListText = \"\\n── 设备列表 ──\";\n                for (var i = 0; i < deviceList.length; i++) {\n                    var dev = deviceList[i];\n                    var statusIcon = dev.is_online ? '🟢' : '⚫';\n                    var maskedId = maskDeviceId(dev.device_id);\n                    var maskedIp = maskIp(dev.ip_address);\n                    var ipText = maskedIp ? ' | ' + maskedIp : '';\n                    deviceListText += \"\\n\" + statusIcon + \" \" + maskedId + ipText;\n                }\n            }\n            \n            java.toast(\n                \"\\n───────────────\\n\" +\n                \"🌟 \" + welcomeMsg + \" 🌟\\n\" +\n                \"───────────────\\n\" +\n                \"昵称：\" + nickname + \"\\n\" +\n                \"邮箱：\" + maskedEmail + \"\\n\" +\n                \"状态：\" + memberStatus + \"\\n\" +\n                \"───────────────\\n\" +\n                deviceStatusText +\n                deviceListText + \"\\n\" +\n                \"───────────────\"\n            );\n        } else {\n            java.toast(\n                \"\\n───────────────\\n\" +\n                \"❌ 登录失败 ❌\\n\" +\n                \"───────────────\\n\" +\n                \"错误代码：\" + (data.code || \"未知\") + \"\\n\" +\n                \"错误信息：\" + (data.message || \"未知错误\") + \"\\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    }\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    try {\n        var apiUrl = getServerHost() + '\/api\/logout';\n        var response = java.ajax(apiUrl + \",\" + JSON.stringify({\n            method: \"POST\",\n            headers: {\n                \"Content-Type\": \"application\/json\"\n            },\n            timeout: 5000\n        }));\n    } catch (e) {}\n    \n    try {\n        cookie.removeCookie(getServerHost());\n    } catch (e) {}\n    \n    source.putLoginHeader(\"\");\n    \n    if (!source.getLoginHeader() || String(source.getLoginHeader()).trim().length == 0) {\n        java.toast(hadLogin ? \"\\n❗ 已成功退出登录\" : \"\\nℹ️ 当前未登录状态\");\n    }\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 updateConfig(key, value, message, gender) {\n    if (key == \"source\" && hasValidCustomSource()) {\n        showCustomSourceAlert();\n        return;\n    }\n    \n    if (gender !== undefined) {\n        setConfigs.call(this, {[key]: value, gender: gender});\n    } else {\n        setConfig.call(this, key, value);\n    }\n    java.toast(message || \"设置已更新\");\n}\n\nfunction get_user() {\n    try {\n        var apiKey = getSecretKey();\n        if (!apiKey) {\n            java.toast(\"❌ 未获取到密钥，请先登录\");\n            return;\n        }\n\n        var timestamp = Math.floor(Date.now() \/ 1000);\n        var apiUrl = getServerHost() + '\/login';\n        \n        var postData = {\n            key: apiKey,\n            timestamp: timestamp\n        };\n        \n        var response = java.ajax(apiUrl + \",\" + JSON.stringify({\n            method: \"POST\",\n            body: JSON.stringify(postData),\n            headers: {\n                \"Content-Type\": \"application\/json\"\n            },\n            timeout: 10000\n        }));\n        \n        var data = JSON.parse(response);\n        \n        if (data.data.user) {\n            var userInfo = data.data.user;\n            var deviceInfo = data.data.device || {};\n            \n            var statusDisplay = \"\";\n            switch(userInfo.status) {\n                case \"正常\":\n                    statusDisplay = \"✅ 正常\";\n                    break;\n                case \"会员\":\n                    statusDisplay = \"👑VIP会员\";\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            \/\/ 设备信息处理\n            var onlineCount = deviceInfo.online_count || 0;\n            var deviceLimit = deviceInfo.device_limit || 1;\n            var totalCount = deviceInfo.total_count || 0;\n            \n            var deviceStatusText = \"\";\n            if (onlineCount >= deviceLimit && totalCount > 0) {\n                deviceStatusText = \"⚠️ 设备已满（\" + onlineCount + \"\/\" + deviceLimit + \"）\";\n            } else if (onlineCount > 0) {\n                deviceStatusText = \"📱 在线设备：\" + onlineCount + \"\/\" + deviceLimit;\n            } else {\n                deviceStatusText = \"📱 当前无设备在线\";\n            }\n            \n            function maskDeviceId(deviceId) {\n                if (!deviceId || deviceId.length <= 8) return deviceId || '未知';\n                return deviceId.substring(0, 4) + \"****\" + deviceId.substring(deviceId.length - 4);\n            }\n            \n            function maskIp(ip) {\n                if (!ip) return '';\n                if (ip.indexOf('.') !== -1 && ip.indexOf(':') == -1) {\n                    var parts = ip.split('.');\n                    if (parts.length == 4) return parts[0] + '.' + parts[1] + '.*.*';\n                }\n                if (ip.indexOf(':') !== -1) {\n                    var parts = ip.split(':');\n                    var validParts = [];\n                    for (var i = 0; i < parts.length; i++) {\n                        if (parts[i] !== '') validParts.push(parts[i]);\n                    }\n                    if (validParts.length >= 4) return validParts.slice(0, 4).join(':') + ':*:*';\n                }\n                return ip;\n            }\n            \n            function formatTime(timeStr) {\n                if (!timeStr || timeStr == \"从未使用\") return \"未知\";\n                return timeStr;\n            }\n            \n            var deviceListText = \"\";\n            var deviceList = (deviceInfo && deviceInfo.list) ? deviceInfo.list : [];\n            if (deviceList && deviceList.length > 0) {\n                deviceListText = \"\\n── 设备列表 ──\";\n                for (var i = 0; i < deviceList.length; i++) {\n                    var dev = deviceList[i];\n                    var statusIcon = dev.is_online ? '🟢' : '⚫';\n                    var maskedId = maskDeviceId(dev.device_id);\n                    var maskedIp = maskIp(dev.ip_address);\n                    var deviceName = dev.device_name || '未知设备';\n                    var trustedIcon = dev.is_trusted ? ' 🔒' : '';\n                    var lastSeen = dev.last_seen || '未知';\n                    \n                    deviceListText += \"\\n\" + statusIcon + \" \" + maskedId + trustedIcon;\n                    deviceListText += \"\\n   📱 \" + deviceName + \" | \" + (maskedIp || '未知IP');\n                    deviceListText += \"\\n   ⏰ \" + lastSeen;\n                    if (i < deviceList.length - 1) {\n                        deviceListText += \"\\n\";\n                    }\n                }\n            } else {\n                deviceListText = \"\\n── 设备列表 ──\\n暂无设备记录\";\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 || 0) + \" 次\\n\" +\n                \"────────────\\n\" +\n                \"📈 今日阅读\\n\" + (userInfo.today_usage || 0) + \" 次\\n\" +\n                \"────────────\\n\" +\n                \"🔄 账户状态\\n\" + statusDisplay + \"\\n\" +\n                \"────────────\\n\" +\n                \"📅 注册时间\\n\" + (userInfo.created_at || \"未知\") + \"\\n\" +\n                \"────────────\\n\" +\n                \"🖥️ 设备管理\\n\" +\n                \"总数：\" + totalCount + \" | \" + deviceStatusText + \"\\n\" +\n                (deviceInfo.current_device_online ? \"🟢 当前设备在线\\n\" : \"\") +\n                (deviceInfo.is_kicked ? \"⚠️ 设备已被踢出：\" + deviceInfo.kicked_reason + \"\\n\" : \"\") +\n                deviceListText + \"\\n\";\n            \n            if (userInfo.is_banned) {\n                displayContent += \"────────────\\n\" +\n                                \"🚫 封禁信息\\n\" + (userInfo.banned_info || \"已封禁\") + \"\\n\";\n            }\n            \n            if (userInfo.last_used && 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            java.longToast(\"❌ 查询失败：\" + errorMsg);\n        }\n        \n    } catch (e) {\n        java.longToast(\"❌ 网络连接异常，请稍后重试\");\n    }\n}\n\nfunction get_cx() {\n    try {\n        var currentConfig = getConfig.call(this);\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        setConfig.call(this, 'host', getHosts[0]);\n        java.toast(\"⚠️ 配置初始化完成，请重试\");\n    }\n}\n\nthis.sethost = function() {\n    try {\n        var currentHost = getConfig.call(this).host || getHosts()[0];\n        var currentIndex = getHosts().indexOf(currentHost);\n        var newIndex = (currentIndex + 1) % getHosts().length;\n        var newHost = getHosts()[newIndex];\n        setConfig.call(this, 'host', newHost);\n        java.toast(\"\\n切换到：\\n\" + newHost + \"\");\n    } catch(e) {\n        setConfig.call(this, 'host', getHosts()[0]);\n        java.toast(\"\\n已初始化：\" + getHosts()[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\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 checkCurrentServer() {\n    try {\n        var currentHost = getServerHost() +'\/detection';\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\nfunction fq_login() {\n    try { \tjava.startBrowserAwait(\"https:\/\/fanqienovel.com\/main\/writer\/login\", \"登录\");\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'));\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    const manualToken = (source.getLoginInfoMap())['番茄登录Token'];\n    \n    const hasValidToken = manualToken && String(manualToken).trim().length > 0;\n    \n    if (hasValidToken) {\n        java.toast(\"请手动移除填写的Token\");\n    } else {\n        java.toast(\"退出登录成功\");\n    }\n}\n\nfunction imagestyle() {\n    let style = cache.get(\"imgstyle\") ?? \"TEXT\";\n    if (style == \"TEXT\") {\n        cache.put(\"imgstyle\", \"text\");\n        java.toast(\"\\n🥽段评气泡-小号样式\");\n    } else if (style == \"text\") {\n        cache.put(\"imgstyle\", \"TEXT\");\n        java.toast(\"\\n🥽段评气泡-大号样式\");\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🍅番茄，🌙晋江，🐱七猫，💮半夏，🐧QQ阅读，🐧企鹅看书，💫起点\\n❌段评已关闭\");\n    } else {\n        java.put(key, \"on\");\n        java.toast(\"\\n🍅番茄，🌙晋江，🐱七猫，💮半夏，🐧QQ阅读，🐧企鹅看书，💫起点\\n✅段评已开启，需要下载缓存时请关闭\");\n    }\n}\n\nfunction ChapterRefresh() {\n    const key = \"chapter_refresh_force\";\n    let status = java.get(key) ?? \"off\";\n    \n    if (status == \"on\") {\n        java.put(key, \"off\");\n        java.toast(\"\\n☁️ 已切换为正常阅读\");\n    } else {\n        java.put(key, \"on\");\n        java.toast(\"\\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 toggleFqdown(a) {\n    let status = java.get(a) ?? \"off\";\n    if (status == \"off\") {\n        java.put(a, \"on\");\n        java.toast(\"\\n🍅番茄小说\\nTXT下载功能已开启，在线阅读或换源需要关闭该功能\");\n    } else {\n        java.put(a, \"off\");\n        java.toast(\"\\n🍅番茄小说\\nTXT全本下载功能已关闭\");\n    }\n}\n\nfunction cleanCache() {\n    try {\n        var cleanUrl = getServerHost() + '\/cron\/cache';\n        \n        java.toast(\"正在清理缓存...\");\n        \n        var response = java.ajax(cleanUrl + \",\" + JSON.stringify({\n            method: \"GET\",\n            timeout: 5000\n        }));\n        \n        var result = JSON.parse(response);\n        \n        if (result.isSuccess) {\n            java.toast(\"✅ 缓存清理成功\");\n        } else {\n            java.toast(\"❌ 清理失败：\" + (result.errorMsg || \"未知错误\"));\n        }\n        \n    } catch (e) {\n        java.toast(\"❌ 清理失败：\" + e);\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 TTSSelector() {\n    try {\n        var config = getConfig.call(this);\n        var currentFanqieTone = config.fanqie_tts_tone || \"\";\n        var currentShuqiTone = config.shuqi_tts_tone || \"\";\n        var activeTab = config.tts_active_tab || \"fanqie\";\n        \n        var fanqieTones = [\n            { id: \"51\", name: \"多人对话\" },\n            { id: \"1\", name: \"甜美少女\" },\n            { id: \"2\", name: \"清亮青叔\" },\n            { id: \"4\", name: \"成熟大叔\" },\n            { id: \"5\", name: \"开朗青年\" },\n            { id: \"6\", name: \"温柔淑女\" },\n            { id: \"8\", name: \"风雅青叔\" },\n            { id: \"12\", name: \"清纯少女\" },\n            { id: \"17\", name: \"磁性青叔\" },\n            { id: \"29\", name: \"儒雅大叔\" },\n            { id: \"30\", name: \"优雅御姐\" },\n            { id: \"31\", name: \"斯文青叔\" },\n            { id: \"32\", name: \"知性主播\" },\n            { id: \"74\", name: \"成熟升级\" },\n            { id: \"80\", name: \"多人升级\" },\n            { id: \"100\", name: \"俏皮御姐\" },\n            { id: \"103\", name: \"双音灵动\" }\n        ];\n        \n        var shuqiTones = [\n            { id: \"multi_role\", name: \"多角色对话\" },\n            { id: \"double_role\", name: \"双角色对话\" },\n            { id: \"yifei\", name: \"甜美少女\" },\n            { id: \"mingge\", name: \"成熟男人\" },\n            { id: \"xiaohou\", name: \"阳光青年\" },\n            { id: \"qianxue\", name: \"知性主播\" },\n            { id: \"SQ_M03_PB_1218_01\", name: \"沉稳男士\" },\n            { id: \"F1029_PB_1128\", name: \"温婉淑女\" },\n            { id: \"zh-CN-YunxiNeural\", name: \"讲书博主小帅\" },\n            { id: \"zh-CN-XiaochenNeural\", name: \"讲书博主小美\" },\n            { id: \"SQ_iVc8TnhZXioTB52v\", name: \"清朗学长\" },\n            { id: \"SQ_5Oh4F5zWa8EZbWvr\", name: \"清冷校花\" },\n            { id: \"zh_male_quarkttsV1_novel_King196\", name: \"沉稳青叔\" }\n        ];\n        \n        function getToneName(id, source) {\n            var list = source == 'fanqie' ? fanqieTones : shuqiTones;\n            for (var i = 0; i < list.length; i++) {\n                if (list[i].id == id) {\n                    return list[i].name;\n                }\n            }\n            return id || \"未选择\";\n        }\n        \n        var fanqieDisplay = currentFanqieTone ? getToneName(currentFanqieTone, 'fanqie') : \"未选择\";\n        var shuqiDisplay = currentShuqiTone ? getToneName(currentShuqiTone, 'shuqi') : \"未选择\";\n        \n        var fanqieHtml = '';\n        for (var i = 0; i < fanqieTones.length; i++) {\n            var tone = fanqieTones[i];\n            var isSelected = (currentFanqieTone == tone.id);\n            fanqieHtml += '<button onclick=\"selectTone(\\'' + tone.id + '\\',\\'fanqie\\')\" data-id=\"' + tone.id + '\" class=\"tone-btn' + (isSelected ? ' selected' : '') + '\"><span class=\"tone-name\">' + tone.name + '<\/span><\/button>';\n        }\n        \n        var shuqiHtml = '';\n        for (var i = 0; i < shuqiTones.length; i++) {\n            var tone = shuqiTones[i];\n            var isSelected = (currentShuqiTone == tone.id);\n            shuqiHtml += '<button onclick=\"selectTone(\\'' + tone.id + '\\',\\'shuqi\\')\" data-id=\"' + tone.id + '\" class=\"tone-btn' + (isSelected ? ' selected' : '') + '\"><span class=\"tone-name\">' + tone.name + '<\/span><\/button>';\n        }\n        \n        var html = `<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\">\n<title>🎧 TTS音色选择<\/title>\n<link rel=\"stylesheet\" href=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/font-awesome\/6.4.0\/css\/all.min.css\">\n<style>\n*{margin:0;padding:0;box-sizing:border-box}\nbody{font-family:-apple-system,'PingFang SC','Microsoft YaHei',sans-serif;background:#f0f2f5;color:#333;padding:12px;max-width:720px;margin:0 auto}\n.container{background:#fff;border-radius:16px;box-shadow:0 4px 24px rgba(0,0,0,0.06);overflow:hidden}\n.header{background:linear-gradient(135deg,#ff6b35,#f7931e);padding:20px 24px;color:#fff}\n.header h1{font-size:22px;font-weight:700;display:flex;align-items:center;gap:10px}\n.header h1 i{font-size:24px}\n.header p{font-size:13px;opacity:0.9;margin-top:4px}\n.current-section{background:#f8f9fa;margin:16px 16px 0;padding:12px 16px;border-radius:10px;border:1px solid #e9ecef}\n.current-label{font-size:13px;color:#868e96;font-weight:500;margin-bottom:4px}\n.current-value{font-size:16px;font-weight:600;color:#212529;display:flex;justify-content:space-between;align-items:center}\n.current-value .source-tag{font-size:12px;font-weight:400;color:#868e96}\n.tabs{display:flex;border-bottom:2px solid #e9ecef;padding:0 16px;margin-top:12px;gap:0}\n.tab-btn{padding:12px 20px;border:none;background:transparent;font-size:14px;font-weight:600;color:#868e96;cursor:pointer;border-bottom:3px solid transparent;transition:all 0.2s;display:flex;align-items:center;gap:8px}\n.tab-btn i{font-size:16px}\n.tab-btn.active{color:#ff6b35;border-bottom-color:#ff6b35}\n.tab-btn:hover{color:#ff6b35}\n.tab-panel{display:none;padding:16px}\n.tab-panel.active{display:block}\n.tab-panel .panel-title{font-size:13px;color:#868e96;margin-bottom:12px;padding-left:4px}\n.tone-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:10px}\n.tone-btn{padding:12px 8px;border:2px solid #e9ecef;border-radius:12px;background:#fff;cursor:pointer;transition:all 0.2s;text-align:center;font-size:13px;font-weight:500;color:#495057}\n.tone-btn:hover{border-color:#ff6b35;transform:translateY(-2px);box-shadow:0 4px 12px rgba(255,107,53,0.15)}\n.tone-btn.selected{background:#ff6b35;border-color:#ff6b35;color:#fff;box-shadow:0 4px 12px rgba(255,107,53,0.25)}\n.clear-area{padding:0 16px 16px}\n.clear-btn{width:100%;padding:12px;border:2px dashed #dee2e6;border-radius:12px;background:transparent;font-size:14px;color:#868e96;cursor:pointer;transition:all 0.2s;display:flex;align-items:center;justify-content:center;gap:8px}\n.clear-btn:hover{border-color:#ff6b35;color:#ff6b35;background:#fff5f0}\n.action-hint{text-align:center;font-size:12px;color:#adb5bd;padding:12px 16px 16px;border-top:1px solid #f1f3f5}\n.action-hint i{color:#ff6b35}\n@media (max-width:480px){.tone-grid{grid-template-columns:repeat(3,1fr)}.tab-btn{padding:10px 14px;font-size:13px}}\n<\/style>\n<\/head>\n<body>\n<div class=\"container\">\n<div class=\"header\">\n<h1><i class=\"fas fa-headphones\"><\/i> TTS音色选择<\/h1>\n<p>番茄和书旗音色独立设置，切换Tab分别选择<\/p>\n<\/div>\n<div class=\"current-section\">\n<div class=\"current-label\"><i class=\"fas fa-check-circle\" style=\"color:#40c057;margin-right:6px\"><\/i>当前音色<\/div>\n<div class=\"current-value\">\n<span id=\"fanqie-display\">🍅 <span id=\"fanqie-value\">` + fanqieDisplay + `<\/span><\/span>\n<span id=\"shuqi-display\">📚 <span id=\"shuqi-value\">` + shuqiDisplay + `<\/span><\/span>\n<\/div>\n<\/div>\n<div class=\"tabs\">\n<button class=\"tab-btn active\" data-tab=\"fanqie\" onclick=\"switchTab('fanqie')\"><i class=\"fas fa-tomato\"><\/i> 番茄<\/button>\n<button class=\"tab-btn\" data-tab=\"shuqi\" onclick=\"switchTab('shuqi')\"><i class=\"fas fa-book\"><\/i> 书旗<\/button>\n<\/div>\n<div id=\"panel-fanqie\" class=\"tab-panel active\">\n<div class=\"panel-title\"><i class=\"fas fa-list-ul\" style=\"color:#ff6b35\"><\/i> 番茄音色列表<\/div>\n<div class=\"tone-grid\">` + fanqieHtml + `<\/div>\n<\/div>\n<div id=\"panel-shuqi\" class=\"tab-panel\">\n<div class=\"panel-title\"><i class=\"fas fa-list-ul\" style=\"color:#1976d2\"><\/i> 书旗音色列表<\/div>\n<div class=\"tone-grid\">` + shuqiHtml + `<\/div>\n<\/div>\n<div class=\"clear-area\">\n<button onclick=\"clearAll()\" class=\"clear-btn\"><i class=\"fas fa-trash-alt\"><\/i> 清除所有音色<\/button>\n<\/div>\n<div class=\"action-hint\"><i class=\"fas fa-check-circle\"><\/i> 选择完成后点击右上角 <strong>✓<\/strong> 确认<\/div>\n<\/div>\n<script>\nvar fanqieTone = '` + currentFanqieTone + `';\nvar shuqiTone = '` + currentShuqiTone + `';\nvar activeTab = '` + activeTab + `';\nvar fanqieMap = {};\nvar shuqiMap = {};\nvar fanqieList = ` + JSON.stringify(fanqieTones) + `;\nvar shuqiList = ` + JSON.stringify(shuqiTones) + `;\nfor (var i = 0; i < fanqieList.length; i++) { fanqieMap[fanqieList[i].id] = fanqieList[i].name; }\nfor (var i = 0; i < shuqiList.length; i++) { shuqiMap[shuqiList[i].id] = shuqiList[i].name; }\n\nfunction switchTab(tab) {\n    activeTab = tab;\n    document.querySelectorAll('.tab-btn').forEach(function(b) { b.classList.remove('active'); });\n    document.querySelector('.tab-btn[data-tab=\"' + tab + '\"]').classList.add('active');\n    document.querySelectorAll('.tab-panel').forEach(function(p) { p.classList.remove('active'); });\n    document.getElementById('panel-' + tab).classList.add('active');\n}\n\nfunction selectTone(id, source) {\n    if (source == 'fanqie') {\n        fanqieTone = id;\n        document.querySelectorAll('#panel-fanqie .tone-btn').forEach(function(b) {\n            b.classList.remove('selected');\n            if (b.getAttribute('data-id') == id) { b.classList.add('selected'); }\n        });\n        document.getElementById('fanqie-value').textContent = fanqieMap[id] || id;\n    } else {\n        shuqiTone = id;\n        document.querySelectorAll('#panel-shuqi .tone-btn').forEach(function(b) {\n            b.classList.remove('selected');\n            if (b.getAttribute('data-id') == id) { b.classList.add('selected'); }\n        });\n        document.getElementById('shuqi-value').textContent = shuqiMap[id] || id;\n    }\n}\n\nfunction clearAll() {\n    fanqieTone = '';\n    shuqiTone = '';\n    document.querySelectorAll('.tone-btn').forEach(function(b) { b.classList.remove('selected'); });\n    document.getElementById('fanqie-value').textContent = '未选择';\n    document.getElementById('shuqi-value').textContent = '未选择';\n}\n<\/script>\n<\/body>\n<\/html>`;\n        \n        var base64 = `data:text\/html;base64,${java.base64Encode(html)}`;\n        var body = java.startBrowserAwait(base64, \"TTS音色选择\", false).body();\n        \n        var newFanqieTone = '';\n        var newShuqiTone = '';\n        \n        var fanqieMatch = body.match(\/<span id=\"fanqie-value\">([^<]*)<\\\/span>\/);\n        if (fanqieMatch && fanqieMatch[1] && fanqieMatch[1] != '未选择') {\n            for (var i = 0; i < fanqieTones.length; i++) {\n                if (fanqieTones[i].name == fanqieMatch[1]) {\n                    newFanqieTone = fanqieTones[i].id;\n                    break;\n                }\n            }\n        }\n        \n        var shuqiMatch = body.match(\/<span id=\"shuqi-value\">([^<]*)<\\\/span>\/);\n        if (shuqiMatch && shuqiMatch[1] && shuqiMatch[1] != '未选择') {\n            for (var i = 0; i < shuqiTones.length; i++) {\n                if (shuqiTones[i].name == shuqiMatch[1]) {\n                    newShuqiTone = shuqiTones[i].id;\n                    break;\n                }\n            }\n        }\n        \n        setConfigs.call(this, {\n            fanqie_tts_tone: newFanqieTone,\n            shuqi_tts_tone: newShuqiTone\n        });\n        \n        var msg = '';\n        if (newFanqieTone && newShuqiTone) {\n            msg = '✅ 番茄: ' + getToneName(newFanqieTone, 'fanqie') + ' | 书旗: ' + getToneName(newShuqiTone, 'shuqi');\n        } else if (newFanqieTone) {\n            msg = '✅ 番茄音色: ' + getToneName(newFanqieTone, 'fanqie');\n        } else if (newShuqiTone) {\n            msg = '✅ 书旗音色: ' + getToneName(newShuqiTone, 'shuqi');\n        } else {\n            msg = '已清除所有TTS音色';\n        }\n        java.toast(msg);\n        \n    } catch(e) {\n        java.log(e);\n        java.toast(\"❌ 音色设置失败：\" + e);\n    }\n}\n\nfunction vip() { java.startBrowserAwait(getServerHost() + '\/coffee', \"喝咖啡\"); }\nfunction fb() { java.startBrowserAwait('https:\/\/fb.shushan.vip', \"发布页\"); }\nfunction key() { java.startBrowserAwait(getServerHost() + '\/user_login', \"登陆注册\"); }\nfunction user_center() { java.startBrowserAwait(getServerHost() + '\/user_center', \"用户中心\"); }\nfunction version() { java.startBrowserAwait(getServerHost() + '\/version?id=' + localVersion, \"版本检查\"); }\nfunction help() { java.startBrowserAwait(getServerHost() + '\/help', \"使用帮助\"); }\nfunction password() { java.startBrowserAwait(getServerHost() + '\/forgot_password', \"修改密码\"); }\nfunction boy() { updateConfig(\"gender\", \"boy\", \"\\n已设置为\\n🧸男频🧸\\n刷新发现页生效\"); }\nfunction girl() { updateConfig(\"gender\", \"girl\", \"\\n已设置为\\n🎀女频🎀\\n刷新发现页生效\"); }","respondTime":180000,"ruleBookInfo":{"author":"$.author","canReName":"1","coverUrl":"$.cover","downloadUrls":"{{$.book_url##[|]}}\n@js:\nlet url = java.base64Decode(result);\nlet source = \"{{$.source}}\".toLowerCase();\n\nif (source == \"番茄小说\") {\n    let book_id = url.match(\/\\d{19}\/)?.[0];\n    java.toast('\\n❗请等待正在下载变成下载完成后在导入');\n    \n    if (book_id) {\n        getServerHost() + `\/down?key={{getSecretKey()}}&book_id=${book_id}`;\n    } else {\n        url;\n    }\n} else if (source == \"爱去\") {\n    let bookIdMatch = url.match(\/txt-(\\d+)\\.htm\/);\n    if (bookIdMatch && bookIdMatch[1]) {\n        let bookId = bookIdMatch[1];\n        let requestUrl = 'https:\/\/www.aiqu666.com\/txt-xx\/softdownfree.asp?softid=' + bookId + '&ckm=mianfei';\n        let html = java.ajax(requestUrl);\n        let downloadSection = html.match(\/<img[^>]*icon_downloadserver[^>]*>([\\s\\S]*?)<span style=\"FONT-SIZE: 18px; COLOR: #0000ff\">下载帮助:\/);\n        \n        if (downloadSection) {\n            let firstMatch = downloadSection[1].match(\/第一下载地址[^<]*<a[^>]*href=\"([^\"]+\\.(txt|zip|rar|epub))\"\/);\n            if (firstMatch && firstMatch[1]) {\n                firstMatch[1];\n            } else {\n                let allLinks = downloadSection[1].match(\/href=\"([^\"]+\\.(txt|zip|rar|epub))\"\/g);\n                allLinks && allLinks.length > 1 ? allLinks[1].replace('href=\"', '').replace('\"', '') : '';\n            }\n        } else {\n            '';\n        }\n    } else {\n        url;\n    }\n} else {\n    url;\n}","init":"<js>\nif (String(baseUrl).startsWith(\"data:\")) {\n    let res = JSON.parse(java.hexDecodeToString(result));\n    \n    if (res.source == \"番茄小说\" && java.get(\"fqdown\") == \"on\") {\n        book.type = 128;\n        java.toast('\\n正在准备TXT，请等待后端缓存完毕后点击阅读导入…');\n    }\n\n    let request = getServerHost() + `\/details,${JSON.stringify({\n        method: \"POST\",\n        body: res\n    })}`;\n    \n    result = java.ajax(request);\n    let response = JSON.parse(result);\n    if (response.data.tab == \"download\") {\n       book.type = 128;\n    }\n} \nresult;\n<\/js>\n$.data","intro":"&nbsp;&nbsp;\n✨ 源站：{{$.source}}{{\"\\n\"+\"​\"}}\n{{$.desc}}\n<js>\nconst tomatoSources = [\"番茄小说\", \"番茄听书\", \"番茄畅听\"];\nconst jjwxcSources = [\"晋江\"];\n\nconst sourceName = \"{{$.source}}\";\n\nlet finalResult = result;\n\nif (tomatoSources.includes(sourceName)) {\n    try {\n        let bookId = getBookIdFromUrl(baseUrl);\n        if (bookId) {\n            let bookData = getBookData(bookId);\n            \n            if (bookData) {\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] : (bookData.first_online_time ? java.timeFormat(parseInt(Object.values(JSON.parse(bookData.first_online_time))[0])*1000).split(' ')[0] : \"\");\n                let updateTime = bookData.last_chapter_update_time ? java.timeFormat(parseInt(bookData.last_chapter_update_time)*1000) : \"\";\n                \n                let authorName = \"\";\n                if (bookData.original_authors) {\n                    try {\n                        let authorsParsed = JSON.parse(bookData.original_authors);\n                        authorName = Array.isArray(authorsParsed) ? authorsParsed.map(a => a.AuthorName || a.author_name || a).join(', ') : bookData.original_authors;\n                    } catch(e) {\n                        authorName = bookData.original_authors;\n                    }\n                } else {\n                    authorName = bookData.author || \"\";\n                }\n                \n                let roleName = \"\";\n                if (bookData.roles) {\n                    try {\n                        let rolesParsed = JSON.parse(bookData.roles);\n                        roleName = Array.isArray(rolesParsed) ? rolesParsed.join(', ') : bookData.roles;\n                    } catch(e) {\n                        roleName = bookData.roles;\n                    }\n                } else if (bookData.role) {\n                    roleName = bookData.role;\n                }\n                \n                let bookTagsData = bookData.pure_category_tags || bookData.tags || \"\";\n                let readCount = bookData.read_count || \"0\";\n                let bookStatus = bookData.book_search_visible == 'false' ? \"下架\" : \"正常\";\n                let abstract = bookData.book_abstract_v2 || bookData.abstract || \"\";\n                let copyrightInfo = bookData.copyright_info || \"\";\n                \n                let output = `&nbsp;&nbsp;\n✨ 源站：${sourceName}{{\"\\n\"+\"​\"}}\n📕 源名：${bookName}`;\n                \n                if (aliasName) output += `\\n📖 别名：${aliasName}`;\n                \n                output += `\\n✏️ 开坑：${createTime} \n🧭 更新：${updateTime}&lrm;\n👤 作者：${authorName}`;\n                \n                if (roleName) output += `\\n🎭 主角：${roleName}`;\n                \n                output += `\n🏷️ 标签：${bookTagsData}\n👁️ 在线：${readCount}人在读\n🔗 书籍状态：${bookStatus} `;\n                \n                output += `\\n🔄 阅读记录：${syncStatus == \"on\" ? \"✅已开启同步\" : \"❌已关闭同步\"}`;\n                \n                if (sourceName == \"番茄听书\") {\n                    try {\n                        let ttsUrl = `https:\/\/reading.snssdk.com\/reading\/bookapi\/audio\/toneinfo\/?book_id=${bookId}&aid=1967`;\n                        let response = java.ajax(ttsUrl);\n                        let data = JSON.parse(response);\n                        let ttsData = data.data;\n                        let toneLines = [];\n                        \n                        if (ttsData.recommend_tone) {\n                            for (var i = 0; i < ttsData.tts_tones.length; i++) {\n                                if (ttsData.tts_tones[i].id == ttsData.recommend_tone) {\n                                    toneLines.push('🎤 推荐音色：' + ttsData.tts_tones[i].title + ' (音色ID: ' + ttsData.tts_tones[i].id + ')');\n                                    break;\n                                }\n                            }\n                        }\n                        \n                        if (ttsData.tts_tones && ttsData.tts_tones.length > 0) {\n                            var availableTones = '';\n                            for (var j = 0; j < ttsData.tts_tones.length; j++) {\n                                if (j > 0) availableTones += '、';\n                                availableTones += ttsData.tts_tones[j].title + '(音色ID: ' + ttsData.tts_tones[j].id + ')';\n                            }\n                            toneLines.push('🎧 可用音色：' + availableTones);\n                        }\n                        \n                        if (toneLines.length > 0) {\n                            output += '\\n' + toneLines.join('\\n');\n                        }\n                    } catch (e) {}\n                }\n                \n                output += `{{\"\\n\"+\"​\"}}\n📜 简介：${abstract}{{\"\\n\"+\"​\"}}`;\n                \n                if (copyrightInfo) {\n                    output += `\\n📍 ${copyrightInfo.split('，')[0]}。`;\n                }\n                \n                finalResult = output;\n            }\n        }\n    } catch (e) {\n    }\n} else if (jjwxcSources.some(source => sourceName.includes(source))) {\n    try {\n        let novelId = getJjwxcNovelIdFromUrl(baseUrl);\n        \n        if (novelId) {\n            let currentVariable = book.getVariable(\"custom\") || \"\";\n            \n            if (!currentVariable.includes(\"开启段评\") && !currentVariable.includes(\"关闭段评\")) {\n                try {\n                    let checkUrl = `https:\/\/android.jjwxc.com\/app.jjwxc\/android\/reading\/authorNovelSetting\/getSetting?versionCode=381&novelid=${novelId}&setting_type=author_paragraph_comment_switch`;\n                    \n                    let request = `${checkUrl},${JSON.stringify({\n                        headers: {\n                            \"versiontype\": \"reading\",\n                            \"versionCode\": \"381\"\n                        }\n                    })}`;\n                    \n                    let ajaxResult = java.ajax(request);\n                    let checkResponse = JSON.parse(ajaxResult);\n                    \n                    let paraEnabled = checkResponse.data?.paragraph_switch == \"1\";\n                    \n                    let newVariable = currentVariable + (paraEnabled ? \"开启段评\" : \"关闭段评\");\n                    book.putVariable(\"custom\", newVariable);\n                    \n                } catch(e) {\n                    let newVariable = currentVariable + \"关闭段评\";\n                    book.putVariable(\"custom\", newVariable);\n                }\n            }\n        }\n    } catch (e) {\n    }\n}\nfinalResult;\n<\/js>","kind":"$.tags","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\nif (result.source == \"晋江\") {\n    let decodedUrl = java.base64Decode(result.book_url);\n    let match = decodedUrl.match(\/novelId=(\\d+)\/);\n    if (match) {\n        catalog.novelId = match[1];\n    }\n} else if (result.source == \"起点\") {\n    let decodedUrl = java.base64Decode(result.book_url);\n    let match = decodedUrl.match(\/bookId=(\\d+)\/);\n    if (match) {\n        catalog.bookid = match[1];\n    }\n}\n\nyunurl = java.base64Encode(JSON.stringify(catalog));\nresult = `data:catalogUrl;base64,${yunurl},{\"type\":\"susan\"}`;\n<\/js>","wordCount":"$.wordCount"},"ruleContent":{"callBackJs":"if (event == \"clickCustomButton\") {\n    if (chapter) {\n        java.toast(\"当前暂不支持\");\n        true;\n    } else {\n        let bookid = \"\";\n        let source = \"\";\n        let isCloud = false;\n\n        if (book && book.tocUrl) {\n            try {\n                let tocUrl = String(book.tocUrl);\n                let base64Str = tocUrl.replace(\/^data:catalogUrl;base64,\/, '');\n                let idx = base64Str.indexOf(',');\n                if (idx >= 0) base64Str = base64Str.substring(0, idx);\n                let catalog = JSON.parse(java.base64Decode(base64Str));\n                source = catalog.source || \"\";\n                if (source == \"云端\") {\n                    let url = String(java.base64Decode(catalog.url));\n                    if (url.indexOf(\"pt=\") >= 0) {\n                        url = url.replace(\/pt=\\w+\/, \"pt=3g\");\n                    } \n                    java.startBrowser(url, \"云端\");\n                    isCloud = true;\n                } else if (source == \"晋江\") {\n                    bookid = catalog.novelId || \"\";\n                } else if (source == \"起点\") {\n                    bookid = catalog.bookid || \"\";\n                }\n            } catch (e) {}\n        }\n\n        if (!isCloud) {\n            if (bookid) {\n                let targetUrl;\n                if (source == \"晋江\") {\n                    targetUrl = getServerHost() + \"\/jj_author?novelid=\" + bookid;\n                } else {\n                    targetUrl = getServerHost() + \"\/qd_role?bookid=\" + bookid;\n                }\n                java.showBrowser(\n                    targetUrl,\n                    null,\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.85,\n                        \"isHideable\": true\n                    })\n                );\n            } else {\n                java.toast(\"当前书籍不支持查看角色\");\n            }\n        }\n        true;\n    }\n}","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'([3-8bcf-mo-rt-zB-UWYZ]|1\\\\w)'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\\\b'+e(c)+'\\\\b','g'),k[c]);return p}('5 3=u.y(6.hexDecodeToString(g));5 M=13()?\\'ios\\':\\'14\\';5 m=\\'\\';4(M==\\'14\\'){o{m=6.15()}p(e){o{m=6.16()}p(e2){m=\\'\\'}}}b{o{m=6.16()}p(e){o{m=6.15()}p(e2){m=\\'\\'}}}4(!13()){o{eval(String(f.loginUrl));5 N=getConfig.18(19);5 C=Date.C();5 1a=2*60*60*1000;4(!N.O||(C-N.O)>1a){setConfig.18(19,\\'O\\',C);5 c=u.y(6.D(v()+\"\/j?vid=sy\"));4(c.j){4(c.1c&&c.1c.toString().toLowerCase()==\"1d\"&&c.j>z){6.P(j());6.k(\"请更新书源！下载页面已打开。\")}5 r=c.j>z?\"\\\\n✨发现新版本V\"+c.j+\" (当前V\"+z+\")\"+(c.r?\"\\\\n\"+c.r:\\'\\'):c.j<z?\"\\\\n⚠️版本异常\\\\n本地V\"+z+\" > 云端V\"+c.j+\"\\\\n请检查书源配置\":\\'\\';4(r)6.k(r)}b 4(c.r){6.k(c.r)}}}p(e){}}3.j=\"12\";4(6.1e(\"chapter_refresh_force\")==\"on\"){3.refresh=\\'1d\\'}4(3.f==\\'晋江\\'){3.token=JJtoken()||\"\"}4(3.f==\\'七猫\\'&&3.E){5 Q=3.E.R(\/l=(\\\\d+)\/);5 S=3.E.R(\/t=(\\\\d+)\/);5 T=3.E.R(\/F=([a-f0-9]+)\/);4(Q)3.l=Q[1];4(S)3.t=S[1];4(T)3.F=T[1]}4(3.f==\\'番茄听书\\'||3.f==\\'番茄畅听\\'){o{5 G=1g(\\'fanqie\\');5 H=v()+\\'\/1h?1i=\\'+G+\\'&U=\\'+3.t;5 h=u.y(6.D(H));4(h.8&&h.8[0]&&h.8[0].I){g=h.8[0].I}b{6.k(\"当前音色不支持该章节，请切换其他音色尝试\");g=\"\"}}p(e){6.k(\"获取音频失败，请切换音色或重试\");g=\"\"}}b 4(3.f==\\'书旗听书\\'){o{5 W=3.q||\\'\\';5 Y=3.U||\\'\\';5 G=1g(\\'shuqi\\');4(W&&Y){5 H=v()+\\'\/1h?q=\\'+W+\\'&U=\\'+Y+\\'&1i=\\'+G;5 h=u.y(6.D(H));4(h.8&&h.8[0]&&h.8[0].I){g=h.8[0].I}b{6.k(\"当前音色不支持该章节，请切换其他音色尝试\");g=\"\"}}}p(e){6.k(\"获取音频失败，请切换音色或重试\");g=\"\"}}b 4(3.f==\\'番茄短剧\\'||3.f==\\'七猫短剧\\'){5 B=\\'\\';4(3.f==\\'番茄短剧\\'&&3.l){B=v()+\\'\/1j?l=\\'+3.l+\\'&1k=\\'+Z()}b 4(3.f==\\'七猫短剧\\'&&3.10){B=v()+\\'\/1j?10=\\'+3.10+\\'&1k=\\'+Z()}4(B){4(11.1l==J.1m){6.P(B,J.1n||\\'视频播放\\');6.k(\\'视频加载中...\\');g=\"▶️刷新正文进入播放界面\"}b{g=\"▶️点击章节进入播放界面\"}}}b{5 1o=v()+`\/1p,${u.stringify({method:\"POST\",headers:{\"Content-1q\":\"application\/json\",\"X-Api-Key\":Z(),\"X-1r-1q\":M,\"X-1r-Id\":m},body:3})}`;5 i=u.y(6.D(1o));5 7=i.8.1p;5 1s=\/^[A-Za-z0-9+\/]*={0,2}$\/.test(7);4(1s){o{7=decode(7)}p(e){7=6.base64Decode(7)}}4(i.8&&i.8.tab==\"video\"){4(11.1l==J.1m){6.P(7,J.1n||\\'视频播放\\');6.k(\\'视频加载中...\\');7=\"▶️刷新正文进入播放界面\"}b{7=\"▶️点击章节进入播放界面\"}}4((6.1e(\"yunpara\")||\"off\")==\"on\"){5 s=3.f;4(s==\\'番茄小说\\'&&3.l&&3.t){7=w(7,3.l,3.t,\\'fq\\')}b 4(s==\\'晋江\\'&&3.K&&3.L){5 1t=11.getVariable(\"custom\")||\"\";4(!1t.includes(\"关闭段评\")){7=w(7,3.K,3.L,\\'jj\\',1v,{})}}b 4(s==\\'半夏\\'&&3.K&&3.L){7=w(7,3.K,3.L,\\'jj\\',1v,{q:3.q,x:3.x})}b 4(s==\\'七猫\\'&&3.l&&3.t&&3.F){7=w(7,3.l,3.t,\\'qm\\',3.F)}b 4((s==\\'企鹅看书\\'||s==\\'QQ阅读\\')&&3.q&&3.x){7=w(7,3.q,3.x,\\'qq\\')}b 4(s==\\'起点\\'&&3.q&&3.x){7=w(7,3.q,3.x,\\'qd\\')}}4(i.8&&i.8.1w){7+=\"\\\\n\"+i.8.1w}4(i.8&&i.8.1x){7+=\"\\\\n【📢作者有话说】\\\\n\"+i.8.1x}g=7}',[],96,'|||chapterParams|if|let|java|finalResult|data|||else|versionData|||source|result|apiData|response|version|toast|book_id|deviceId||try|catch|bookid|msg||item_id|JSON|getServerHost|getComments|chapterid|parse|localVersion||videoUrl|now|ajax|qm_url|content_md5|toneId|apiUrl|main_url|chapter|novelId|chapterId|deviceTypeStr|config|lastUpdateCheck|startBrowser|bookIdMatch|match|itemIdMatch|contentMd5Match|item_ids||bookId||itemId|getSecretKey|qm_id|book||deviceType|android|androidId|deviceID||call|this|fourHours||force|true|get||getTtsTone|audio|tone_id|player|key|durChapterIndex|index|title|chapterUrl|content|Type|Device|isBase64|customVar||null|notice|sayBody'.split('|'),0,{}))<\/js>","payAction":"\/\/java.log(baseUrl);\nnid = baseUrl.match(\/bid=(\\d+)\/)[1];\ncid = baseUrl.match(\/cid=(\\d+)\/)[1];\ntoken = java.base64Decode(JJtoken());\nenbody = String(Date.now())+\":\"+token+\":\"+nid;\nsign = encode(enbody)\nbody = \"versionCode=444&novelId=\"+nid+\"&chapters=\"+cid+\"&orderType=15&isautoBuy=0&autosubscribeStatus=0&notUseBackpoint=&sign=\"+sign;\nurl = \"https:\/\/android.jjwxc.net\/androidapi\/buy,\"\npost = {\n\t\"method\":\"POST\",\n\t\"body\":String(body)\n\t}\n a = java.ajax(url+JSON.stringify(post));\n if(\/购买成功\/.test(JSON.parse(a).message)){\n \tjava.longToast(JSON.parse(a).message);\n \tresult  = true\n \t}else{\n \t\tmm = JSON.parse(a).message==\"\"?JSON.parse(a).popupdata.popupmessage+\"\\n❗️进晋江app进行一次验证后❗️才能在阅读app使用购买功能\":JSON.parse(a).message;\n \t\tjava.longToast(mm);\n}","replaceRegex":"##\\(本章完\\)|\\(\\)|\\(第\\d+\/\\d+页\\)|(?:.*(?:蟹蟹|扔了|感谢|投出|灌溉营养液|送的|谢谢).*?(?:手榴|月票|霸王票|小天使|火箭炮|深水鱼雷|浅水炸弹|地雷|营养液)([xX]\\d+)*.*|.*?瓶[；~]|\".*?\"(?:营养液|手榴弹|月票|霸王票|小天使|火箭炮|深水鱼雷|浅水炸弹|地雷)x\\d+|读者.*?(月石|营养液).*|地雷感谢：[\\s\\S]+)","subContent":"@js:\nlet chapterParams = JSON.parse(java.hexDecodeToString(result));\nif(chapterParams.source=='番茄听书'){\n    try{\n        let toneId = getTtsTone('fanqie');\n        let apiUrl = getServerHost() + '\/audio_lrc?item_id=' + chapterParams.item_id;\n        if (toneId) {\n            apiUrl += '&tone_id=' + toneId;\n        }\n        result = java.ajax(apiUrl);\n    }catch(e){}\n}","title":"@js:\nlet isAndroid = deviceType();\nlet title = chapter.title || \"\";\n\nif (isAndroid) {\n    let chapterParams = JSON.parse(java.hexDecodeToString(result));\n    \n    let bookid = chapterParams.bookid || null;\n    let chapterid = chapterParams.chapterid || null;\n    \n    let yunparaStatus = java.get(\"yunpara\") || \"off\";\n    \n    let isModifiedVersion = checkEnv();\n    let isQingRead = isQRead();\n    \n    let isQQSource = chapterParams.source == 'QQ阅读' || chapterParams.source == '企鹅看书';\n    let isQDSource = chapterParams.source == '起点';\n    \n    if ((isModifiedVersion || isQingRead) && yunparaStatus == \"on\" && bookid && chapterid) {\n        if (isQQSource || isQDSource) {\n            let Bubble = cache.get(bookid + \"_\" + chapterid) || \"\";\n            title = title + Bubble;\n        }\n    }\n}\ntitle;"},"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(\",\"))\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(getServerHost() +\"\/detail?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 getGuessYouLike() {\n    let apiUrl = 'https:\/\/reading.snssdk.com\/reading\/bookapi\/bookmall\/tab\/v\/?version_name=5.8.9.32&device_id=1024&device_type=114514&iid=2048&aid=1967&channel=0&os_version=0&app_name=novelapp&version_code=58932&device_platform=android'\n    let response = java.ajax(apiUrl + ', ' + JSON.stringify({\n        headers: {\n            'Cookie': \"sessionid=\" + session\n        }\n    }))\n    let responseData = JSON.parse(response)\n    let finalResult = {\n        code: 0,\n        message: 'SUCCESS',\n        data: {\n            cell_view: {\n                book_data: []\n            }\n        }\n    }\n    if (responseData.data && responseData.data.tab_item && responseData.data.tab_item.length > 0 && responseData.data.tab_item[0].cell_data && responseData.data.tab_item[0].cell_data.length > 0 && responseData.data.tab_item[0].cell_data[0].cell_data && responseData.data.tab_item[0].cell_data[0].cell_data.length > 0 && responseData.data.tab_item[0].cell_data[0].cell_data[0].book_data) {\n        finalResult.data.cell_view.book_data = responseData.data.tab_item[0].cell_data[0].cell_data[0].book_data\n    }\n    return finalResult\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(getServerHost() +\"\/detail?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 if (baseUrl.includes(\"\/tab\/cai\")) {\n    result = getGuessYouLike();\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;\nlet cover = result.thumb_url || result.thumb_url || result.thumbUri || result.cover;\nlet desc = result.desc || result.abstract;\n\nlet detail = {\n    source: source,\n    url: book_url,\n    cover: cover,\n    desc: desc,\n    name: title\n};\n\nif (book_id && \/^\\d{19}$\/.test(book_id)) {\n    detail.cover = cover;\n    detail.desc = desc;\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\":\"susan\"}`;\n<\/js>","coverUrl":"$.thumb_url||$.audio_thumb_uri||$.thumbUri||$.cover","intro":"$.abstract||$.desc","kind":"$.tags","lastChapter":"$.lastChapterTitle||$.last_chapter_title||$.latestChapterTitle","name":"$.book_name||$.bookName||$.original_book_name||$.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                \"cover\": \"http:\/\/fc3tn.baidu.com\/it\/u=1573211303,3438572022&fm=203&src=787&fc_m=cvican\",\n                \"desc\":\"输入账号密码后需要点击✓登录生效\"\n            }\n        ]\n    });\n} \nresult;\n<\/js>\n$.data","bookUrl":"<js>\nlet source = String(result.source);\nlet book_url = result.book_url;\nlet title = result.title;\nlet cover = result.cover;\nlet desc = result.desc;\n\nlet detail = {\n    source: source,\n    url: book_url,\n    cover: cover,\n    desc: desc,\n    name: title\n};\n\nyunurl = java.base64Encode(JSON.stringify(detail));\n`data:detailsUrl;base64,${yunurl},{\"type\":\"susan\"}`;\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() ? 'ios' : 'android';\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);\n    return x;\n});\n<\/js>","chapterName":"title","chapterUrl":"url","isPay":"isPay","isVip":"isVip","isVolume":"isVolume","updateTime":"tag"},"searchUrl":"<js>\nif (!getSecretKey()) {\n    java.toast(\"请登录后使用\");\n    result = \"[]\";\n} else {\n    let config = getConfig.call(this);\n    if (!config.host || !config.gender) {\n        config = { gender: \"boy\", host: getServerHost() };\n        setConfigs.call(this, config);\n        java.toast(\"配置初始化完成\");\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}]