📚书山聚合
书山聚合
书山 (1935)08/09 19:44
2026.8.8日更新:
增加多设备管理
增加晋江文学
修复服务器检测问题
❤️🔥登录必看❤️🔥
1.输入邮箱密码后必须点✓保存生效,
否则读取不到输入的账号密码
2.跳转卡顿在设置→其他设置中清除WebView缓存
🔔适配兼容
iOS 系统适配:源阅,栖阅,轻悦时光,用心读书
📖获取书源请访问书山发布页
https://fb.shushan.vip
阅读软件多平台聚合下载站
https://legado.vossc.com
🔥注:阅读请使用最新测试版
1.所有源站每20秒最多12章,把预下载调到0-3之间使用
💯[打赏VIP无请求限制,专属书源]
2.自定义源站示例:番茄小说,七猫 多个源站用英文,号分隔
源站名参考 登录URL //书源配置列表 v参数
3.源站带有❇️图标表示只能指定或@搜索
4.搜索模式:
书名@来源 多个来源用英文,号分隔
示例:
系统@番茄小说,多个源站使用英文,号分隔
书名@类型 返回对应类型所有源站数据,可直接使用@方式搜索或者点击登陆切换模式
示例:
系统@小说 听书 漫画 短剧等
默认书名排序
指定作者优先:
作者名@作者 作者名@作者,源站 {
"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;\n case 'qm':\n return host + '\/qm_comment?item_id=' + cid + '&book_id=' + bid + '¶graph_id=' + para;\n case 'qq':\n return host + '\/qq_comment?bid=' + bid + '&cid=' + cid + '¶=' + para;\n case 'qd':\n return host + '\/qd_comment?bid=' + bid + '&cid=' + cid + '¶=' + para;\n case 'fq':\n default:\n return host + '\/idea_comment?item_id=' + cid + '&book_id=' + bid + '¶=' + 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, '&')\n .replace(\/<\/g, '<')\n .replace(\/>\/g, '>')\n .replace(\/\"\/g, '"')\n .replace(\/'\/g, ''');\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, '&').replace(\/<\/g, '<').replace(\/>\/g, '>').replace(\/\"\/g, '"').replace(\/'\/g, '''),\n content: formattedContent.replace(\/&\/g, '&').replace(\/<\/g, '<').replace(\/>\/g, '>').replace(\/\"\/g, '"').replace(\/'\/g, '''),\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 + '¶=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, '&').replace(\/<\/g, '<').replace(\/>\/g, '>');\n displayText = displayText.replace(\/&\/g, '&').replace(\/<\/g, '<').replace(\/>\/g, '>');\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(\/ \/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": " \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 = ` \n✨ 源站:${sourceName}{{\"\\n\"+\"\"}}\n📕 源名:${bookName}`;\n \n if (aliasName) output += `\\n📖 别名:${aliasName}`;\n \n output += `\\n✏️ 开坑:${createTime} \n🧭 更新:${updateTime}‎\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¬UseBackpoint=&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
}