Files
fanqie-go/shareBookSource.json
T
chenxin 221343fc72 📄 docs(bookSource): 收录光遇聚合书源作为参考基线
本次聚合书源(发现页样式/段评气泡/前缀搜索)的架构参考来源,便于后续对照维护;忽略 .codegraph 本地索引目录
2026-10-10 12:52:53 +08:00

68 lines
214 KiB
JSON
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
[
{
"bookSourceComment": "如果是用的官方版阅读,或者低版本阅读,推荐更新阅读以使用新功能,否则很多功能无法使用哦\n软件下载地址:https://legado.qingtian618.com\n推荐使用阅读R或者阅读Sigma\n\n\n晴天聚合重构,已适配所有安卓版本阅读\n目前不适配苹果端栖阅(可以正常阅读,发现页无法适配)\n\n更新日志请点击登录,更新书源中查看\n\n基础使用教程:\n1、搜索:\n 1)基础搜索:在发现页长按该书源,点击搜索,直接搜,搜索范围(全部来源小说)\n 2)进阶搜索:在登陆-书源设置中选择指定模式/来源,可搜索指定模式/来源内容\n 3)快捷搜索:使用特殊关键词(x,t,m,d,@)搜索\n x:书名@来源 如(x:十日终焉@番茄)\n t:书名@来源 如(t:十日终焉@番茄)\n m:书名@来源 如(m:十日终焉@番茄)\n d:书名@来源 如(d:十日终焉@番茄)\n 其中:x表示小说(可省略),t表示听书,m表示漫画,d表示短剧\n 冒号支持中文英文\n \n2、发现页:\n 在书源设置中选择指定模式或者单个来源,然后刷新发现页即可\n \n3、更多使用方式进入书源设置页面应该能一目了然",
"bookSourceGroup": "📖番茄小说",
"bookSourceName": "🔅光遇聚合",
"bookSourceType": 0,
"bookSourceUrl": "光遇聚合",
"bookUrlPattern": "https?:\\/\\/(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z0-9-]+(?::\\d+)?\\/detail.*",
"customButton": false,
"customOrder": 0,
"enabled": true,
"enabledCookieJar": true,
"enabledExplore": true,
"eventListener": false,
"exploreUrl": "<js>\n\n\nlet moreSettings = getVariable('更多设置');\nvar base_url = BaseUrl();\nvar source_type = getVariable('频道') || '男频';\nlet tab = moreSettings && moreSettings['搜索模式'] || '小说';\nlet sources = getVariable(\"发现页来源\") || moreSettings && moreSettings[tab] || '全部';\nlet islyc = checkEnv();\nlet wordNum = getVariable('字数') || '不限';\nlet update = getVariable('更新') || '不限';\nlet sort = getVariable('排序') || '综合';\nlet wordNumMap = {\n '不限': 'word_num_default',\n '10万字以内': 'word_num_lte10',\n '30万字以内': 'word_num_lte30',\n '50万字以内': 'word_num_lte50',\n '30万字以上': 'word_num_gte30',\n '50万字以上': 'word_num_gte50',\n '100万字以上': 'word_num_gte100',\n '200万字以上': 'word_num_gte200',\n '300万字以上': 'word_num_gte300',\n '500万字以上': 'word_num_gte500'\n};\nlet wordNumParam = wordNumMap[wordNum] || 'word_num_default';\nlet updateMap = {\n '不限': 'creation_status_default',\n '完结': 'creation_status_end',\n '半年内完结': 'creation_status_half_year_end',\n '连载中': 'creation_status_loading',\n '3日内更新': 'creation_status_3day_update',\n '7日内更新': 'creation_status_7day_update',\n '1月内更新': 'creation_status_1month_update'\n};\nlet updateParam = updateMap[update] || 'creation_status_default';\nlet sortMap = {\n '综合': 'sort_default',\n '新书': 'sort_new_book',\n '高分': 'sort_score',\n '字数': 'sort_word_number'\n};\nlet sortParam = sortMap[sort] || 'sort_default';\nlet selected_items = `${wordNumParam},${updateParam},${sortParam}`;\n\n\nif (islyc != \"苹果\" && islyc != \"安卓\"){\n\ttab = getVariable(\"发现页类型\");\n\t}\n\n\nlet js;\n\njs = getVariable('云端配置');\n//java.longToast(JSON.stringify(js))\nif (!!!js) {\n getCloudSettings(true);\n js = getVariable('云端配置');\n }\n\n\nlet source_list = [];\ntry{\nsource_list = js[tab];\n} catch (e) {\n\tjava.longToast(e);\n\t}\n\nlet fqssionid = getFqToken();\nif (!fqssionid && (sources == '番茄' || sources == '全部')) {\n java.toast('您还未登陆番茄账号,无法同步数据哦!');\n} \nvar fqsjurl = base_url + \"/bookshelf?page={{page}}&ssionid=\" + fqssionid;\nvar fqtjurl = base_url + \"/fqrecommend?page={{page}}&ssionid=\" + fqssionid;\nvar fqlsurl = base_url + \"/fqhistory?page={{page}}&ssionid=\" + fqssionid;\n\n\nvar groupDatas = [];\nvar infoData = [ ];\nif (sources == '番茄' || sources == '全部') {\ninfoData = [{\n \"title\": \"登录番茄\",\n \"url\": '{\\{java.startBrowser(\"https://fanqienovel.com/\",\"番茄登录\");}}',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.45\n }\n }];\n}\n\nvar hasValidCookie = fqssionid.length > 0;\n\nif (hasValidCookie && (sources == '番茄' || sources == '全部')) {\n function groupQuery() {\n try {\n var url = base_url + \"/group_name?ssionid=\" + fqssionid;\n var res = java.ajax(url);\n var response = JSON.parse(res);\n\n if (!(response && response.data)) {}\n\n response.data.forEach(function(group) {\n var keys = Object.keys(group);\n if (keys.length > 0) {\n var key = keys[0];\n var value = group[key];\n if (value && value.length) {\n var option = {\n \"method\": \"POST\",\n \"body\": {\n \"book_ids\": value,\n \"page\": \"{{page}}\"\n }\n };\n groupDatas.push({\n title: key,\n url: base_url + \"/bookshelf,\" + JSON.stringify(option),\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 0.25\n }\n });\n }\n }\n });\n\n if (groupDatas.length % 2 != 0) {\n groupDatas.push({\n title: \"--\",\n url: \"\",\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 0.25\n }\n });\n }\n if (groupDatas.length % 3 != 0) {\n groupDatas.push({\n title: \"--\",\n url: \"\",\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 0.25\n }\n });\n }\n } catch (e) {\n java.longToast(\"番茄登录过期,已隐藏番茄书架\" + fqssionid);\n }\n }\n\n try {\n java.longToast(\"正在加载番茄分组数据...\");\n var userUrl = base_url + \"/fquser?ssionid=\" + fqssionid;\n var userRes = java.ajax(userUrl);\n var userData = JSON.parse(userRes);\n\n var userName = (userData && userData.data && userData.data.name) ? userData.data.name : '未知用户';\n if (!userName.includes('未知用户')) {\n infoData = [{\n title: userName+'的番茄',\n url: '',\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 1\n }\n },\n {\n title: '番茄书架',\n url: fqsjurl,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 0.25\n }\n }, \n {\n title: \"个性推荐\",\n url: fqtjurl,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 0.25\n }\n }, {\n title: \"历史阅读\",\n url: fqlsurl,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 0.25\n }\n }];\n }\n groupQuery();\n } catch (e) {\n java.longToast(\"番茄登录过期,已隐藏番茄书架\");\n }\n}\n\nvar style_list = [];\ntry {\n var durl = `${base_url}/discovestyle?source=${sources}&source_type=${source_type}&tab=${tab}`;\n var res = java.ajax(durl);\n var result = JSON.parse(res);\n style_list = result.data || [];\n if (sources == \"番茄\" && islyc != \"苹果\" && islyc != \"安卓\") {\n for (var i = 0; i < style_list.length; i++) {\n if (style_list[i].title && style_list[i].title.indexOf(\"热门标签\") != -1) {\n style_list.splice(i, 0,\n createFilter(\n \"字数\",\n [\"不限\",\"10万字以内\",\"30万字以内\",\"50万字以内\",\"30万字以上\",\"50万字以上\",\"100万字以上\",\"200万字以上\",\"300万字以上\",\"500万字以上\"],\n \"不限\",\n \"字数\",\n 0.4,\n \"'字数'\"\n ),\n createFilter(\n \"更新\",\n [\"不限\",\"完结\",\"半年内完结\",\"连载中\",\"3日内更新\",\"7日内更新\",\"1月内更新\"],\n \"不限\",\n \"更新\",\n 0.4,\n \"'更新'\"\n ),\n createFilter(\n \"排序\",\n [\"综合\",\"新书\",\"高分\",\"字数\"],\n \"综合\",\n \"排序\",\n 0.1,\n \"'排序'\"\n )\n );\n break;\n }\n }\n }\n if (result.msg) {\n java.toast(result.msg);\n }\n} catch (e) {\n java.toast(\"发现样式获取失败\");\n}\nlet qttoken = getToken();\n//let qtcookie = \"\"\nlet qtop = {\n method: \"GET\",\n headers: {\n cookie: `qttoken=${qttoken}`\n },\n};\n\n\n\nqtop = JSON.stringify(qtop);\nlet qtsjurl = base_url + '/get_book_shelf,' + qtop;\n\nlet qtsj = [];\n\nif (islyc != \"苹果\" && islyc != \"安卓\") {\n try {\n let hostsbk = getVariable('云端配置')['hosts'] || hosts;\n qtsj.push(createFilter(\n \"线路\",\n hostsbk,\n base_url,\n \"线路\",\n 1,\n \"'线路'\"\n ));\n qtsj.push(createFilter(\n \"类型\",\n [\"小说\", \"听书\", \"短剧\", \"漫画\"],\n tab,\n \"发现页类型\",\n 0.4,\n \"'类型'\"\n ));\n qtsj.push(createFilter(\n \"频道\",\n [\"男频\", \"女频\"],\n source_type,\n \"频道\",\n 0.4,\n \"'频道'\"\n ));\n qtsj.push(createFilter(\n \"平台\",\n source_list,\n sources,\n \"发现页来源\",\n 1,\n \"'平台'\"\n ));\n let searchKey = getVariable('搜索关键词') || \"\";\n qtsj.push(createText(\n '搜索关键词', \n \"setVariable('搜索关键词',(infoMap['搜索关键词'] || (infoMap.get && infoMap.get('搜索关键词'))))\", \n searchKey , \n 0.5,\n \"'搜索书名或作者'\"\n ));\n \n qtsj.push(createButton(\n '🔍搜索', \n 'exploreSearch()', \n 0.3\n ));\n\n qtsj.push({\n \"title\": \"更新配置\",\n \"type\": \"button\",\n \"action\": \"getCloudSettings(true)\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.25\n }\n })\n qtsj.push({\n \"title\": \"更新书源\",\n \"type\": \"button\",\n \"action\": \"renderVersionPage()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.25\n }\n })\n qtsj.push({\n \"title\": \"书源设置\",\n \"type\": \"button\",\n \"action\": \"getHtmlSettings()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.25\n }\n })\n const excludeTitles = ['点击登录可切换来源', '切换后长按刷新即可'];\n style_list = style_list.filter(item => !excludeTitles.includes(item.title));\n style_list.forEach(item => {\n if (item.url && typeof item.url == 'string') {\n item.url += '&ssionid='+fqssionid+'&selected_items='+selected_items;\n }\n});\n } catch {}\n} else {\n\tif (islyc == '安卓'){\n\tqtsj.push({\n \"title\": \"下载阅读Sigma最新测试版\",\n \"url\": `{\\{java.longToast(\"本书源推荐使用最新版洛娅橙改版阅读Σ\\\\n请安装后重新导入书源\");java.startBrowser(\"https://legado.qingtian618.com\",\"下载阅读Sigma最新测试版\");}}`,\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 1\n }\n })}\n\t}\n\nif (qttoken.length > 10) {\nqtsj.push({\n title: \"晴天书架\",\n url: qtsjurl,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 1\n }\n})\n} else {\nqtsj.push({\n title: \"登录晴天书源\",\n url: `{\\{java.startBrowser(\"${base_url}/login\",\"登录书源\");}}`,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 1\n }\n})\n}\n\n\nvar finalData = infoData.concat(groupDatas, style_list);\nfinalData = qtsj.concat(finalData);\nJSON.stringify(finalData);\n\n\n</js>",
"jsLib": "// 当前书源版本号,切勿修改,否则影响更新的识别\nlet localVersion = '26.10.9';\n\n// 初始服务器列表\nlet hosts = [\n 'https://v1.qingtian618.com',\n 'https://cdn.qingtian618.com',\n 'https://v2.qingtian618.com',\n 'https://v3.qingtian618.com',\n 'https://v4.qingtian618.com',\n 'https://v5.qingtian618.com',\n 'https://v6.qingtian618.com',\n 'https://v7.qingtian618.com'\n];\n\nfunction isSpecialMedia (sources) {\n\tlet SOURCES_SPECIAL_MEDIA = ['毒舌影视', 'NT动漫'];\n\t return SOURCES_SPECIAL_MEDIA.some(src => sources.includes(src));\n\t}\n\nfunction buildDiscussionButton(url,sources) {\n let clickAction =\n `showCmt(\"${url}\",\"${sources}\",'本书讨论')`;\n\n return `\n <button style=\"\">💬 书籍讨论@onclick:${clickAction}</button>\n `;\n};\n\n// 初始化值\nconst defaultConfig = {\n 线路: hosts[0],\n 发现页来源: \"番茄\",\n 发现页类型: \"小说\"\n};\n\n// 获取云端配置\nfunction getCloudSettings(r) {\n if (r == undefined) r = false;\n const {\n java,\n cache\n } = this;\n let c = this.getVariable('云端配置').version;\n if (r || !c) {\n java.longToast(`\\n正在更新最新配置`);\n try {\n let url = `/static/source_config/config.json`;\n //\tjava.longToast(url)\n let js = this.request(url);\n js = JSON.parse(String(js));\n let intc = parseInt(c);\n let intv = parseInt(js['version']);\n if (intc >= intv) {\n \t java.longToast(`\\n已是最新配置:${js['version']}`);\n \t return;\n \t} else if (!intc && intv) {\n \t\tjava.longToast(`\\n已初始化配置:${intv}`);\n \t\t} else if (intc < intv || !intc && intv) {\n java.longToast(`\\n已更新配置:${intc}→${intv}`);\n } else {\n java.longToast(`\\n获取配置失败:请切换线路再试试~`);\n return;\n }\n this.setVariable('云端配置', js);\n \n return js;\n } catch (e) {\n java.longToast(`\\n获取最新配置失败:${e}`);\n }\n }\n}\n\n// 统一获取和解析变量\nfunction _getParsedVariable() {\n const {\n source,\n java\n } = this;\n let v = source.getVariable();\n try {\n return JSON.parse(v);\n } catch {\n return defaultConfig;\n }\n}\n\n// 获取设置变量\nfunction getLoginInfo(k) {\n const {\n source,\n java\n } = this;\n let res = source.getLoginInfoMap();\n if (!res) {\n res = defaultConfig;\n }\n if (res[k] == '❌') {\n return '0';\n } else if (res[k] == '✅') {\n return '1';\n }\n return res[k]\n}\n\n// 还原\nfunction deleteVariable() {\n const {\n source,\n java,\n cache\n } = this;\n source.setVariable('');\n cache.delete('gyksconfig');\n java.longToast('\\n所有设置已还原到初始化导入状态');\n}\n\n// 获取源变量\nfunction getVariable(k) {\n if (k == undefined) k = \"\";\n const { source } = this;\n let parsed = {}\n try {\n parsed = JSON.parse(source.getVariable());\n } catch {}\n if (k == \"\") {\n return parsed;\n }\n let value = parsed[k];\n if (value == undefined) {\n value = defaultConfig[k];\n }\n return value != undefined ? value : \"\";\n}\n\n// 设置源变量\nfunction setVariable(k, v, t) {\n if (t == undefined) t = true;\n const {\n source,\n java\n } = this;\n const vs = this.getVariable();\n vs[k] = v;\n source.setVariable(JSON.stringify(vs, null, 4));\n if (k != '云端配置' && t && k!='搜索关键词') {\n java.toast(`\\n设置 ${k} 为 ${v}`)\n }\n}\n\n// 获取正在使用的线路\nfunction BaseUrl() {\n let hostsbk = this.getVariable('云端配置')['hosts'] || hosts;\n let h = this.getVariable(\"线路\");\n if (!h || String(h) == \"undefind\") {\n h = hostsbk[0]\n }\n return h;\n}\n\n// 切换线路\nfunction switchToNextLine(toast) {\n if (toast == undefined) toast = false;\n const currentLine = this.BaseUrl();\n let hostsbk = this.getVariable('云端配置')['hosts'] || hosts;\n let currentIndex = hostsbk.findIndex(item => item == currentLine);\n let nextIndex = (currentIndex + 1) % hostsbk.length;\n const nextLine = hostsbk[nextIndex];\n this.setVariable(\"线路\", nextLine,false);\n let switchToast = this.getVariable(\"切换提醒\") || \"false\";\n if (toast && switchToast == \"true\") {\n this.java.longToast(`\\n${toast}\\n自动切换到:${nextLine}`)\n }\n return nextLine;\n}\n\n\n// 备用设置方案\nfunction ste(v) {\n const {\n java\n } = this;\n let moreSettings = this.getVariable(\"更多设置\") || {};\n let paraSettings = this.getVariable(\"段评设置\") || {};\n if (v==\"段评\"){\n if (paraSettings[\"段评开关\"] != \"false\"){\n paraSettings[\"段评开关\"]= \"false\";\n java.longToast(\"\\n❌段评已关闭\");\n }else{\n paraSettings[\"段评开关\"]= \"true\";\n java.longToast(\"\\n✅段评已开启\");\n }\n }\n if (v==\"小说\" || v==\"听书\" || v==\"漫画\" || v==\"短剧\"){\n moreSettings[\"搜索模式\"]=v;\n java.longToast(`\\n已设置搜索模式为 ${v}`);\n }\n if (v==\"图片\"){\n if (moreSettings[\"显示图片\"] != \"false\"){\n moreSettings[\"显示图片\"]= \"false\";\n java.longToast(\"\\n❌已关闭图片显示\");\n }else{\n moreSettings[\"显示图片\"]= \"true\";\n java.longToast(\"\\n✅已开启图片显示\");\n }\n }\n if (v==\"简介\"){\n if (moreSettings[\"完整简介\"] != \"false\"){\n moreSettings[\"完整简介\"]= \"false\";\n java.longToast(\"\\n❌已关闭完整简介\");\n }else{\n moreSettings[\"完整简介\"]= \"true\";\n java.longToast(\"\\n✅已开启完整简介\");\n }\n }\n if (v==\"同步\"){\n if (moreSettings[\"同步书架\"] != \"false\"){\n moreSettings[\"同步书架\"]= \"false\";\n java.longToast(\"\\n❌已关闭同步书架\");\n }else{\n moreSettings[\"同步书架\"]= \"true\";\n java.longToast(\"\\n✅已开启同步书架\");\n }\n }\n if (v==\"强制\"){\n if (moreSettings[\"强制搜索\"] != \"false\"){\n moreSettings[\"强制搜索\"]= \"false\";\n java.longToast(\"\\n❌已关闭强制搜索\");\n }else{\n moreSettings[\"强制搜索\"]= \"true\";\n java.longToast(\"\\n✅已开启强制搜索\");\n }\n }\n if (v==\"目录\"){\n if (moreSettings[\"目录显示来源\"] != \"false\"){\n moreSettings[\"目录显示来源\"]= \"false\";\n java.longToast(\"\\n❌已关闭目录显示来源\");\n }else{\n moreSettings[\"目录显示来源\"]= \"true\";\n java.longToast(\"\\n✅已开启目录显示来源\");\n }\n }\n if (v==\"网络\"){\n if (moreSettings[\"网络模式\"] != \"服务器\"){\n moreSettings[\"网络模式\"]= \"服务器\";\n java.longToast(\"\\n已切换为 服务器 模式\");\n }else{\n moreSettings[\"网络模式\"]= \"本地\";\n java.longToast(\"\\n已切换为 本地 模式\");\n }\n }\n this.setVariable(\"更多设置\", moreSettings, false);\n this.setVariable(\"段评设置\", paraSettings, false);\n}\n\nfunction parseBadMap(str) {\n str = str\n .replace(/^{|}$/g, '');\n\n const result = {};\n\n str.split(',').forEach(item => {\n const parts = item.split('=');\n\n if (parts.length === 2) {\n const key = parts[0].trim();\n const value = parts[1].trim();\n\n result[key] = value;\n }\n });\n\n return result;\n}\n\nfunction findItemIndex(data, item_id) {\n if (!data || !Array.isArray(data)) {\n return 0;\n }\n for (let i = 0; i < data.length; i++) {\n if (data[i] && data[i].item_id == item_id) {\n return i;\n }\n }\n return 0;\n}\n\n// 判断当前环境\nfunction checkEnv() {\n const {\n java,\n source\n } = this;\n\n try {\n java.qread();\n return \"轻阅读\";\n } catch (e) {}\n\n try {\n if (typeof java.reLoginView == 'function') {\n return \"改版\";\n }\n new Packages.io.legato.kazusa.utils.TimeoutCancellationException('');\n return \"改版\";\n } catch (e) {}\n try {\n java.deviceID();\n return \"苹果\";\n } catch (e) {}\n\n if (typeof source.loginUi == 'function') {\n return \"安卓\";\n }\n\n return \"改版\";\n}\n\n// 获取番茄token\nfunction getFqToken() {\n const {\n java,\n cookie\n } = this;\n\n try {\n let cookieValue = String(cookie.getCookie('fanqienovel.com')) || String(java.getCookie('fanqienovel.com'));\n let parts = cookieValue.split(\";\");\n for (let part of parts) {\n if (part.includes(\"sessionid\")) {\n return part.split(\"=\")[1];\n }\n }\n } catch {}\n return \"\";\n}\n\n// 获取登陆token\nfunction getToken() {\n const {\n java,\n cookie\n } = this;\n let hostsbk = this.getVariable('云端配置')['hosts'] || hosts;\n try {\n for (let h of hostsbk) {\n let cookieValue = String(cookie.getCookie(h)) || String(java.getCookie(h));\n let parts = cookieValue.split(\";\");\n for (let part of parts) {\n if (part.includes(\"qttoken\")) {\n return part.split(\"=\")[1];\n }\n }\n }\n } catch {}\n return \"\";\n}\n\n// 移除ck\nfunction removeAllCookies() {\n const {\n java,\n cookie\n } = this;\n let hostsbk = this.getVariable('云端配置')['hosts'] || hosts;\n for (let h of hostsbk) {\n cookie.removeCookie(h);\n }\n cookie.removeCookie('fanqienovel.com');\n java.toast('\\n已退出登陆')\n}\n\n// 设置ck\nfunction setAllCookies(ck) {\n const {\n cookie\n } = this;\n let hostsbk = this.getVariable('云端配置')['hosts'] || hosts;\n for (let h of hostsbk) {\n cookie.setCookie(h, ck);\n }\n}\n\n// 请求封装\nfunction request(url, method, body, req, index) {\n if (method == undefined) method = 'GET';\n if (body == undefined) body = {};\n if (req == undefined) req = true;\n if (index == undefined) index = 0;\n\n let urla = url;\n if (!url.includes('http')) {\n urla = this.BaseUrl() + url;\n }\n\n const { java } = this;\n let device = '';\n try {\n device = java.androidId();\n } catch {\n try {\n device = java.deviceID();\n } catch {}\n }\n\n let qttoken = this.getToken();\n let options = {\n method: method,\n headers: {\n 'cookie': `qttoken=${qttoken};deviceId=${device};`,\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(body)\n };\n\n urla = `${urla},${JSON.stringify(options)}`;\n java.log(urla);\n\n if (!req) {\n return urla;\n }\n\n const startTime = new Date().getTime();\n\n let data;\n let requestTimedOut = false;\n let requestFailed = false;\n\n try {\n data = java.ajax(urla);\n JSON.parse(data);\n } catch (error) {\n requestFailed = true;\n } finally {\n const endTime = new Date().getTime();\n const duration = endTime - startTime;\n let timeout = this.getVariable('超时时间') || '10';\n timeout = parseInt(timeout)*1000\n if (!requestFailed && duration > timeout ) {\n requestTimedOut = true;\n }\n }\n if (requestTimedOut) {\n let timeoutSwitch = this.getVariable('超时自动切换') || 'true';\n if (timeoutSwitch!= \"false\") {\n java.log('检测到当前线路较慢,已切换线路');\n this.switchToNextLine('⚠️当前线路较慢');\n }\n return data;\n }\n if (requestFailed) {\n java.log('线路报错,自动切换下一个');\n let hostsbk = this.getVariable('云端配置')['hosts'] || hosts;\n let autoSwitch = this.getVariable('自动切换') || 'true';\n\n if (hostsbk.length > index && autoSwitch != \"false\") {\n this.switchToNextLine('❌线路报错');\n return this.request(url, method, body, req, index + 1);\n } else {\n return \"\";\n }\n }\n return data;\n}\n\nfunction parseJsonSafely(str, defaultValue) {\n if (defaultValue == null) defaultValue = null;\n try {\n return JSON.parse(str);\n } catch (e) {\n return defaultValue;\n }\n}\n\n\n//去除图片\nfunction removeAllImgTags(htmlString) {\n const imgTagRegex = /<img\\b[^>]*>|<\\/img>|<img\\b[^>]*\\/>/gi;\n return htmlString.replace(imgTagRegex, '');\n}\n\n// 段评气泡生成\nfunction paraForAndroid(content, sources) {\n let {\n java,\n cache,\n source\n } = this;\n const createSvg = this.createSvg.bind(this);\n const createGod = this.createGod.bind(this);\n content = content.replace(\n /<p><img ident=\"([^\"]*)\" src=\"([^\"]*)\"\\s*\\/><\\/p>/g,\n (match, ident, src) => {\n return `<p>${createGod(ident, src)}</p>`;\n }\n );\n \n\n return content.replace(/<p>(.*?)(?:<comment ident=\"([^\"]*)\" count=\"([^\"]*)\" \\/>)?<\\/p>/g,\n (match, text, url, count) => {\n if (url && count) {\n const click = 0;\n cache.putMemory(url, click);\n const encodedUrl = url;\n return `<p>${text}<img src=\"${createSvg(count, encodedUrl,sources)}\"></p>`;\n } else {\n return `<p>${text}</p>`;\n }\n }\n );\n}\n// 创建神评论\nfunction createGod(url,svg) {\n var jc = 'js';\n let islyc = this.checkEnv()\n if (islyc == \"改版\") {\n jc = 'click'\n }\n let cz= `{\"${jc}\":\"showCmt('${url}','段评' )\",\"style\":\"FULL\"}`\n\tlet img = `<img src=\"${svg},${cz}\">`\n\tif (islyc == \"苹果\") {\n\t\timg = `<div rs-native><img onPress=\"java.showReadingBrowser('${url}','段评')\" src=\"${svg}\"></div>`\n \t}\n\treturn img\n\t}\n\nlet svgs = [\n \"<svg t='1760002253572' class='icon' viewBox='-150 0 1224 1224' version='1.1' xmlns='http://www.w3.org/2000/svg' p-id='1490' width='800' height='800'><g transform='rotate(90 512 512) scale(1 1.2)'><path d='M224 149.333333h576c40.533333 0 74.666667 34.133333 74.666667 74.666667v460.8c0 40.533333-34.133333 74.666667-74.666667 74.666667h-108.8c-40.533333 0-78.933333 17.066667-104.533334 46.933333l-49.066666 55.466667c-4.266667 6.4-14.933333 10.666667-23.466667 10.666667-10.666667 0-19.2-4.266667-25.6-10.666667l-46.933333-55.466667c-25.6-29.866667-64-46.933333-104.533334-46.933333h-110.933333c-40.533333 0-74.666667-34.133333-74.666667-74.666667V224c0-40.533333 34.133333-74.666667 74.666667-74.666667z' fill='$tccolor' p-id='1529'></path><path d='M512 938.666667c-27.733333 0-55.466667-12.8-72.533333-34.133334l-46.933334-55.466666c-14.933333-17.066667-34.133333-25.6-57.6-25.6h-110.933333c-76.8 0-138.666667-61.866667-138.666667-138.666667V224C85.333333 147.2 147.2 85.333333 224 85.333333h573.866667c76.8 0 138.666667 61.866667 138.666666 138.666667v460.8c0 76.8-61.866667 138.666667-138.666666 138.666667h-108.8c-21.333333 0-42.666667 8.533333-55.466667 25.6l-49.066667 55.466666c-17.066667 21.333333-44.8 34.133333-72.533333 34.133334zM224 149.333333C183.466667 149.333333 149.333333 183.466667 149.333333 224v460.8c0 40.533333 34.133333 74.666667 74.666667 74.666667h110.933333c40.533333 0 78.933333 17.066667 104.533334 49.066666l46.933333 55.466667c6.4 6.4 14.933333 10.666667 25.6 10.666667 8.533333 0 19.2-4.266667 23.466667-10.666667l49.066666-55.466667c25.6-29.866667 64-46.933333 104.533334-46.933333h108.8c40.533333 0 74.666667-34.133333 74.666666-74.666667V224C874.666667 183.466667 840.533333 149.333333 800 149.333333h-576z' fill='$color' p-id='1530'></path> </g><text x='460' y='650' font-family='Arial, sans-serif' text-anchor='middle' font-size='400' fill='$fontcolor'>$displayText</text></svg>\",\n\n \"<svg xmlns='http://www.w3.org/2000/svg' width='100' height='90' viewBox='0 0 25 17' fill='none' style='color: $color; opacity: 1;'><path stroke='currentColor' stroke-width='1.00' stroke-linecap='round' stroke-linejoin='round' stroke-miterlimit='4.00' style='stroke: $color; stroke-opacity: 0.5; stroke-width: 1px; stroke-linejoin: round; fill: $tccolor; fill-opacity: 0.5;' d='M 7.5 0.5 L 16.5 0.5 A 7 7 0 0 1 23.5 7.5 L 23.5 7.5 A 7 7 0 0 1 16.5 14.5 L 8.5 14.5 L 5.192 16.5 L 5.5 14.5 A 7 7 30 0 1 0.5 7.5 L 0.5 7.5 A 7 7 0 0 1 7.5 0.5 Z'/><text fill='$fontcolor' font-family='Arial, sans-serif' font-size='10' font-weight='500' text-anchor='middle' dy='0.35em' x='12.00' y='7.50'>$displayText</text></svg>\",\n\n \"<svg xmlns='http://www.w3.org/2000/svg' width='100' height='90' viewBox='0 0 25 17' fill='none' style='color: $color; opacity: 1;'><path d='M24 14.5v-12a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v7.528a2 2 0 0 1-.211.894l-2.065 4.13a1 1 0 0 0 .894 1.448H22a2 2 0 0 0 2-2z' stroke='currentColor' style='stroke: $color; stroke-opacity: 0.5; stroke-width: 1px; stroke-linejoin: round; fill: $tccolor; fill-opacity: 0.5;'></path><text x='13.5' y='12' text-anchor='middle' alignment-baseline='auto' font-size='10' fill='currentColor' font-family='Arial, sans-serif' font-weight='500' style='fill: $fontcolor; opacity: 1;'>$displayText</text></svg>\",\n\n\n\n \"<svg xmlns='http://www.w3.org/2000/svg' width='100' height='90' viewBox='6 10 88 76' style='color: $color; opacity: 1;'><path d='M12,12 L88,12 Q92,12 92,16 L92,68 Q92,72 88,72 L28,72 L12,84 L12,72 Q8,72 8,68 L8,16 Q8,12 12,12 Z' stroke='currentColor' stroke-width='4' stroke-linejoin='round' style='stroke: $color; stroke-opacity: 0.5; stroke-width: 2.5px; stroke-linejoin: round; fill: $tccolor; fill-opacity: 0.5;'></path><text x='50' y='60' font-family='Arial, sans-serif' text-anchor='middle' font-size='45' fill='currentColor' font-weight='500' alignment-baseline='auto' style='fill: $fontcolor; opacity: 1;'>$displayText</text></svg>\",\n\n\n\n \"<svg xmlns='http://www.w3.org/2000/svg' width='100' height='90' viewBox='0 0 25 17' fill='none' style='color: $color; opacity: 1;'><rect x='0.5' y='0.5' width='24' height='16' rx='2.5' ry='2.5' stroke='currentColor' style='stroke: $color; stroke-opacity: 0.5; stroke-width: 1px; stroke-linejoin: round; fill: $tccolor; fill-opacity: 0.5;'></rect><text x='12.5' y='12' text-anchor='middle' alignment-baseline='auto' font-size='10' fill='currentColor' font-family='Arial, sans-serif' font-weight='500' style='fill: $fontcolor; opacity: 1;'>$displayText</text></svg>\",\n\n\n\n\n `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 216 200'>\n <path fill='#802622' d='M110 0h14l1 1h8v1h4v1h4v1h4v1h3v1h2l1 1h2l1 1h1l1 1h2v1h2v1h2v1h2v1h1v1h2v1h2v1h1v1h1l1 1h1v1h1v1h2v1h1v1h1l1 1v1h2v1h1v1h1v1l1 1h1v1h1v1h1v1h1v2h1v1h1v1h1v1h1v1h1v2h1v1h1v1l1 1v1h1v2h1v1h1v2h1v1l1 1v1l1 1v1h1v2h1v2h1v2h1v3h1v2h1v3a5 5 0 0 0 1 2h1v1h-1v4h1v4h1v5h1v8h1v18h-1v7h-1v5h-1v2h-1l-1 2v3h-1v2l-1 1v3h-1v2h-1v2h-1v2h-1v2h-1v2h-1v2h-1v1l-1 1v1h-1v2h-1v1l-1 1v1h-1v1h-1v1h-1v2h-1l-1 1v1h-1v1h-1v1h-1v1h-1v1h-1v1h-1v1l-1 1h-1v1h-1l-1 1h-1 1v-1h1v-1h1v-1h1a1 1 0 0 1 0-1 2 2 0 0 1 1-1h1v-2h1v-1h1v-1h1v-1h1v-1l1-1v-1h1v-1h1v-1l1-1h1v-2h1v-1l1-1v-1h1v-1l1-1v-1h1v-2h1v-1l1-1v-2h1v-2h1v-2h1v-2h1v-3h1v-5h1v-4l1-1v-6h1v-11h1v-2h-1v-1h1v-1h-1 1v-2h-1v-7h-1v24h-1v3h-1v1h-1v4h-1v4h-1v3h-1v2h-1v3l-1 1v1h-1v2l-1 1-1 2v1h-1v1h-1v2h-1v2h-1v1h-1v2h-1v1h-1v1h-1v1h-1v2h-1v1h-2v2h-1l-1 1v1h-2v1h-1v1h-1v1h-1v1h-1l-1 1-1 1h-2v1h-1v-1h1v-1h2v-1h1v-1h1v-1h1v-1h1v-1h1v-1l1-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1l1-1v-1h1v-1h1v-2l1-1v-1h1v-1h1v-2l1-1v-1h1v-2l1-1v-1h1l-1-1h1v-2h1v-2l1-1v-3h1v-3l1-1v-3h1v-4l1-1v-10h1V98h-1v-9h-1v-4l-1-1v-4l-1-1v-4h-1v-3h-1a6 6 0 0 0 0-3h-1v-1l-1-1v-2h-1v-2h-1v-1h-1v-2h-1v-1h-1v-1l-1-1v-1h-2v-1 2a2 2 0 0 1-1-1v-2h-1v-1h-1v-2h-1v-1l-1-1-1-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1l-1-1h-1v-1l-1-1h-1v-1h-1v-1h-1v-1h-1v-1h-1l-1-1v-1h-2v-1h-1v-1h-1v-1h-2v-1a1 1 0 0 1-1 0v-1h-2v-1h-1l-1-1h-1v-1h-2v-1h-2v-1h-2v-1h-1v-1h-4v-1h-1v-1h-2l-1-1h-2v-1h-5v-1a1 1 0 0 1-1-1v1h-1v-1 1h-2l-1-1h-3v-1h-23 1-2v1h-1v-1h-2v1h-4v1h-2v2l1-1h4v-1h4v-1h23v1h6v1h4v1h4v1h3v1h3v1h2l1 1h2l1 1 1 1h1v1h1l2 1h2v1h1v1-1 1h2v1h1v1h2v1h1v1h1v1h1v1h1v1h1l1 1h1v1h1v2h1l1 1 1 1v1h1v1h1v1h1v2l1 1v1h1v1h1v1h1v1h1v2l1 1v1h1v2h1v1l1 1v1h1v2l1 1v2h1v2h1v1l1 2v3h1v4l1 1v5h1v9l1 1v6h-1v8h-1v1-16h-1v-2h1v-2h-1v-5h-1v-4h-1v-3l-1-1v-3a2 2 0 0 1-1-1v-2l-1-1v-1h-1v-3h-1v-1h-1v-2l-1-1v3h1v3h1v4h1v3l1 1v3h1v5h1v25h-1v6h-1v4l-1 1v2h-1v2h-1v3h-1v2h-1v2l-1 1v1h-1v2h-1v2h-1v2h-1v1h-1v2l-1 1-1 1v1h-1v1h-1v2h-1l-1 1v1h-1v1h-1v1h-1v1h-1v1l-1 1-1 1h-1v1h-1v1h-1v1h-1l-1 1h-1v1h-1v-1h1v-1h1v-1l1-1h1v-1l2-1v-1h1v-1l1-1v-1h1v-1h1v-1h1v-2h1v-1l1-1v-1h1v-1l1-1v-2h1v-2h1v-1l1-1v-1h1v-3h1v-2h1v-1h1v-2l1-1v-2h1v-3h1v-5h1v-5h1v5-21h-1v-6h-1v-5h-1v-3h-1v-3h-1v-2h-1v-3h-1v-1l-1-1v-2h-1v-1l-1-3h-1v-1h-1a1 1 0 0 1 0-1v-1h-1v-2h-1v-1h-1v-1l-1-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1h-1v-1l-1-1v-1h-2v-1h-1v-1h-1v-1h-1l-1-1h-1v-1h-1v-1h-1l-1-1h-1v-1h-2v-1h-2v-1h-1v-1h-2a2 2 0 0 0-1-1h-1v-1h-2l-1-1h-2v-1h-3v-1h-2l-2-1h-2v-1h-5v-1h-2 3-22v1h-5v1h-4v1h-2l-2 1h-2v1h-2l-1 1h-2v1h-1l-1 1h-1v1h-2v1h-1l-1 1h-1v1h-1l-1 1h-1v1h-1v1h-1l-1 1-1 1-1 1h-1v1h-1v1h-1v1h-1v1h-1v1h-1v2h-1v1h-1v1h-1v1h-1v2h-1v1h-1v1h-1v2h-1v1l-1 1v1h-1v2h-1v2h-1v2h-1v3h-1v3h-1v3l-1 1v3l-1 1v6h-1v12h1v5l1 3v3h1v4h1v3h1v3h1v3h1v2l1 1v1h1v2l1 2v1h1v1l1 1v1h1v1h1v2h1v1h1v2h1v1h1v1h1v1h1v2h1v1h1l1 1h1v1h1v1l1 1h1v1h2v1h1v1h1v1h2v1h2v1h1v1h1v1h2v1h7v1h1v2h1v2h-4l-3-1v1h1a3 3 0 0 1 2 1h1v1-1 1h1v-1h1v2h5v1h1v1h1v1h2v1h-2l1 1v1h-1 1v3h-1v2h-2 1v1h1v1h-1v2h-1 1v3h-1l-1 1h-1v1h-3v1h-2v-1h-1l-1-1h-1v-1 1h-5v1h-4v-1l-1-1h-1v1h-2v-1h-1v-1h-1v2h2v1h2v1h2l1 1h2v1h1v1h2v1h1v1h5v1h1v1h1v1l-1 1h-4v-1h-2v-1h-3v-1h-5v-1h-1v-1h-1v1h-1a1 1 0 0 1 0-1l1-1h1-1v-1h-2a2 2 0 0 0-1-1h-1v-1h-2v-1h-2v-1h-3v-1h-1v-1h-2v-1h-1v-1h-1v-1h-2v-1h-2v-1h-1v-1h-1v-1h-1v-1h-2v-1a2 2 0 0 1-1-1h-1v-1h-1v-1h-1v-1h-1v-1h-1l-1-1v-1h-1v-1h-1v-1h-1a1 1 0 0 1 0-1h-1v-1l-1-1-1-1v-1h-1v-1h-1v-1h-1v-1h-1v-2h-1v-1h-1v-1l-1-1v-1h-1v-2h-1v-1h-1v-2h-1v-1l-1-1v-1h-1v-2h-1v-1h-2v-2a1 1 0 0 1-1 0v-2h-1v-1h-1l-1 1v1h-1v-1h-1l-1-2h1v-5h-1v-4a2 2 0 0 1-1-1v-3H9v-3a2 2 0 0 1-1-1v-2H7v-3H6v-7H5v-1h1v-1h1v-5l-1-2v-7l1-2v-6h1v-1H7h2v1H8v8H7v9h1V91h1v-7h1v-4h1v-1a3 3 0 0 1 0-2h-1v-1h1l1-1-1-1h1v-2h1v-3h1v-6h1v-2h1v-1 1-1h1-1v-1h1v-1h1v-1h-1v-1h1v-1h1-1v-1h1v-2h1v-2h1v-1h-1v2h-1v1h-1v2h-1v-1l1-1v-2h1v-1h1v-1l1-1v-2h1v-1h1v-3h1v-1h2v-1h1v-1h1v-2h1v-1h1v-1h1v-2h1l1-1v-1h1v-1h1v-2h1v-1h1v-1h1v-1h1v-1h1v-1h1v-1h1l1-1h1v-1h1v-1h1v-1h1v-1h1l1-1h1v-1h1l1-1 1-1v1-1h1V8h2V7h1l1-1h1V5h2V4h1l1-1h2V2h2V1h2V0h1v1h-1l-1 1h-1v1h-2v1h-3v1h-1v1h-2v1h-1v1h-2v1h-2v1h-1v1h-1l-1 1-1 1h-1l-1 1h-1v1h-1v1h-1v1h-1v1h-1v1h-1l-1 1h-1v2h-1l-1 1v1h-2v1h-1v2h-1v1h-1v1h-1v1h-1v1l-1 1v1h-1v1h-1v1l-1 1v1h-1v2h-1v1h-1v2h-1v1h1v1h-1v1l-1 1h-1v1h-1v2l-1 1v2h-1v2h-1v2h-1v2h-1v3h-1v3h-1v3h-1v2l-1 1v3h-1v4H9v6H8v6H7v11-5h1v-3l1-2v-3h1v-4h1v-5l1-1v-3h1v-4h1v-3h1v-3h1v-3l1-2h1v-3h1v-1l1-1v-2h1v-1h1v-2h1v-1h1v-1l1-1v-1l1-1v-1h1v-1h1v-1h1v-1h1v-2h1v-1h1v-1h1v-1h1v-1h1v-2h1v-1h1l1-1h1v-1l1-1 1-1h1v-1h1v-1h1v-1h1v-1h1v-1h2v-1h1v-1h1v-1h2v-1h1l1-1v-1h2v-1h1l1-1h1v-1h2v-1h2v-1h2v-1h1l2-1h1V8h2V7h3V6h3V4h6V3h4V2h4l1-1h7V0h8z'/>\n <text x=\"115\" y=\"130\" font-family=\"Arial, sans-serif\" text-anchor=\"middle\" font-size=\"80\" font-weight=\"700\" fill='#802632'>$displayText</text>\n</svg>`\n];\n\n\n// 创建svg\nfunction createSvg(number, encodedUrl, sources) {\n var displayText = number > 99 ? \"99+\" : number.toString();\n let data = this.getVariable('段评设置');\n let svgstyle = data['段评样式'] || \"0\";\n let color = data['段评边框颜色'] || \"#A7A7A7\";\n let tccolor = data['段评填充颜色'] || \"transparent\";\n let fontcolor = data['段评字体颜色'] || \"#A7A7A7\";\n let zdy = svgs[0];\n try {\n zdy = data['自定义段评样式'][svgstyle] || svgs[0];\n } catch {}\n const styleMap = {\n \"0\": svgs[0],\n \"1\": svgs[1],\n \"2\": svgs[2],\n \"3\": svgs[3],\n \"4\": svgs[4],\n \"5\": svgs[5],\n };\n let svg = styleMap[svgstyle] || zdy;\n svg = svg.replace('$color', color)\n .replace('$tccolor', tccolor)\n .replace('$fontcolor', fontcolor)\n .replace('$displayText', displayText);\n var jc = 'js';\n let islyc = this.checkEnv()\n if (islyc == \"改版\") {\n jc = 'click'\n }\n let style = data['气泡增大'] == \"true\" ? 'TEXT' : 'text';\n var encodedSvg = this.java.base64Encode(svg);\n return `data:image/svg+xml;base64,${encodedSvg},{\"${jc}\":\"showCmt('${encodedUrl}', '${sources}' ,'段评')\",\"style\":\"${style}\"}`;\n}\n\n\n\n\n// 显示弹窗\nfunction showCmt(url, sources,ident) {\n let {\n java,\n cache\n } = this;\n if (ident == null) ident = '段评';\n const currentTime = Date.now();\n const click = cache.getFromMemory(url);\n let islyc = this.checkEnv()\n if (click < 1 && islyc == \"安卓\") {\n cache.putMemory(url, click + 1);\n return;\n } else {\n if (islyc == \"轻阅读\") {\n java.startBrowserDp(url, sources + ident);\n } else if (islyc == \"改版\") {\n let paradata = new Date().toLocaleDateString();\n let htmlContent = cache.get(paradata+ident+sources+\"new3\");\n if (!htmlContent || String(htmlContent).indexOf(\"评论\") == -1) {\n java.longToast(`\\n${paradata} 初始化${sources}${ident}页面,请稍等~`);\n try {\n htmlContent = java.ajax(url);\n } catch {\n\n }\n\n cache.put(paradata+ident+sources+\"new3\", htmlContent);\n java.longToast(`\\n${paradata} 初始化${sources}${ident}页面成功~`);\n }\n java.showBrowser(\n url,\n htmlContent,\n `window.java=java;`,\n JSON.stringify({\n \"expandedCornersRadius\": 20,\n \"dismissOnTouchOutside\": true,\n \"isDraggable\": true,\n \"shouldDimBackground\": true,\n \"backgroundDimAmount\": 0.5,\n \"hardwareAccelerated\": true,\n \"isNestedScrollingEnabled\": true,\n \"isGestureInsetBottomIgnored\": true,\n \"setFitToContents\": true,\n \"isHideable\": true,\n \"heightPercentage\": 0.8\n })\n );\n } else {\n java.startBrowser(url, sources + ident);\n }\n }\n}\n\n//源阅段评\nfunction paraForiOS(html, sources) {\n\t let result = html.replace(\n /<p><img ident=\"([^\"]*)\" src=\"([^\"]*)\"\\s*\\/><\\/p>/g,\n (match, ident, src) => {\n return `<div rs-native>${createGod(ident, src)}</div>`;\n }\n );\n result = result.replace(\n /<p>(.*?)(?:<comment ident=\"([^\"]*)\" count=\"([^\"]*)\" \\/>)?<\\/p>/g,\n function(match, text, url, count) {\n if (url && count) {\n const encodedUrl = url.replace(/&/g, '&amp;');\n let displayCount = count;\n const numCount = Number(count);\n if (!isNaN(numCount) && numCount > 99) {\n displayCount = '99';\n }\n return `<div rs-native>${text}<comment count=\"${displayCount}\" onPress=\"java.showReadingBrowser('${encodedUrl}','${sources}段评')\"></div>`;\n } else {\n return `<div rs-native>${text}</div>`;\n }\n }\n );\n return result.replace(/&#34;/g, '\"');\n}\n\n\nfunction endclick(url,sources) {\n\tif (sources == null) sources = '番茄';\n var jc = 'js';\n let islyc = this.checkEnv()\n if (islyc == \"改版\") {\n jc = 'click'\n }\n return `\"${jc}\":\"showCmt('${url}','${sources}','本章说' )\"`\n}\n\n\n//按钮切换时改变源变量并刷新发现\nfunction show(m, t) {\n const {\n java,\n source\n } = this;\n let data = {};\n try {\n data = JSON.parse(source.getVariable())\n } catch {}\n data[t] = m;\n if (t == \"发现页类型\") {\n data[\"发现页来源\"] = '番茄';\n }\n source.setVariable(JSON.stringify(data, null, 2));\n java.refreshExplore();\n}\n\nfunction createFilter(title, chars, defaultVal, paramKey, size, viewName) {\n if (viewName == undefined) viewName = \"'请选择类型'\";\n return {\n title: title,\n type: \"select\",\n chars: chars,\n default: defaultVal,\n \"viewName\": viewName,\n action: `show(infoMap['${title}'],'${paramKey}')`,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: size\n }\n };\n}\n\nfunction createLoginFilter(title, chars, defaultVal, paramKey, size, viewName) {\n const {\n source,\n java\n } = this;\n if (viewName == undefined) viewName = \"'请选择类型'\";\n \n let loginMap = source.getLoginInfoMap();\n //java.longToast(loginMap);\n return {\n name: title,\n type: \"select\",\n chars: chars,\n default: defaultVal,\n \"viewName\": viewName,\n action: `show('${loginMap[title]}','${paramKey}')`,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: size\n }\n };\n}\n\nfunction createText(title, action, defaultValue, size, viewName) {\n return {\n title: title,\n type: 'text',\n default: defaultValue || '',\n action: action,\n viewName: viewName || title,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: size\n }\n };\n }\nfunction createButton(title, action, size) {\n return {\n title: title,\n type: 'button',\n action: action,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: size\n }\n };\n }\n\nfunction exploreSearch() {\n let {java,source} = this;\n var key = this.getVariable('搜索关键词');\n var tab = this.getVariable('发现页类型') || '小说';\n var sources = this.getVariable('发现页来源') || '全部';\n if (!key) {\n try { java.toast('请输入搜索关键词'); } catch (e) {}\n return;\n }\n if (['t:', 'x:', 'd:', 'm:', 't:', 'x:', 'd:', 'm:'].some(p => key.startsWith(p))) {\n tab = '';\n}\nif(key.includes('@')) {\n\tsources=\"\";\n\t} else {\n\t\tsources = `@${sources}`\n\t\t}\n var tabMap = {\n \t'小说':'x:',\n \t'漫画':'m:',\n \t'听书':'t:',\n \t'短剧':'d:',\n \t'':''\n \t}\n key = `${tabMap[tab]}${key}${sources}`;\n try {\n java.searchBook(key, source.bookSourceName+\"::\"+source.getKey());\n } catch (e1) {\n try { java.open('search', key); } catch (e2) { try { java.toast('搜索失败'); } catch (e3) {} }\n }\n java.refreshExplore();\n}\n\n// 书源更新\nfunction renderVersionPage() {\n let {\n java\n } = this;\n let hostsbk = this.getVariable('云端配置')['hosts'] || hosts;\n let yd = '';\n let html = `\n<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>书源更新</title>\n <!-- Font Awesome 图标库 -->\n <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\" />\n <style>\n :root {\n --primary-gradient: linear-gradient(135deg, #4e6ef2, #6b2dd8);\n --latest-gradient: linear-gradient(135deg, #8e2de2 0%, #4a00e0 50%, #d4af37 100%);\n --success-color: #28c76f;\n --warning-color: #ff9f43;\n --error-color: #ea5455;\n --text-main: #1f2937;\n --text-secondary: #6b7280;\n --card-bg: #ffffff;\n --border-color: #e5e7eb;\n --light-bg: #f9fafb;\n --shadow: 0 4px 12px rgba(78, 110, 242, 0.1);\n --shadow-hover: 0 6px 18px rgba(78, 110, 242, 0.2);\n --glow-shadow: 0 0 25px rgba(142, 45, 226, 0.5), 0 0 50px rgba(212, 175, 55, 0.3);\n --modal-bg: rgba(31, 41, 55, 0.8);\n --modal-content-bg: #ffffff;\n }\n\n * {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;\n }\n\n body {\n background: linear-gradient(135deg, #eef2ff, #f5f7ff);\n color: var(--text-main);\n min-height: 100vh;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 16px;\n }\n\n /* 加载动画 */\n .loading-wrapper {\n text-align: center;\n animation: fadeIn 0.3s ease;\n }\n\n .loading-spinner {\n width: 50px;\n height: 50px;\n border: 4px solid rgba(78, 110, 242, 0.3);\n border-top-color: #4e6ef2;\n border-radius: 50%;\n margin: 0 auto 20px;\n animation: spin 1s linear infinite;\n }\n\n .loading-text {\n color: var(--text-main);\n font-size: 16px;\n font-weight: 500;\n }\n\n @keyframes spin {\n to { transform: rotate(360deg); }\n }\n\n @keyframes fadeIn {\n from { opacity: 0; transform: translateY(20px); }\n to { opacity: 1; transform: translateY(0); }\n }\n\n @keyframes slideIn {\n from { opacity: 0; transform: translateY(30px); }\n to { opacity: 1; transform: translateY(0); }\n }\n\n @keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.7; }\n }\n\n @keyframes gradientAnimation {\n 0% { background-position: 0% 50%; }\n 50% { background-position: 100% 50%; }\n 100% { background-position: 0% 50%; }\n }\n\n @keyframes breathe {\n 0%, 100% { \n transform: scale(1);\n box-shadow: var(--glow-shadow), var(--shadow);\n }\n 50% { \n transform: scale(1.02);\n box-shadow: 0 0 30px rgba(142, 45, 226, 0.6), 0 0 60px rgba(212, 175, 55, 0.4), var(--shadow);\n }\n }\n\n @keyframes shimmer {\n 0% {\n background-position: -200% center;\n }\n 100% {\n background-position: 200% center;\n }\n }\n\n /* 主容器 */\n .container {\n width: 100%;\n max-width: 420px;\n background: var(--card-bg);\n border-radius: 24px;\n overflow: hidden;\n box-shadow: var(--shadow);\n position: relative;\n z-index: 1;\n animation: slideIn 0.5s ease;\n display: none;\n }\n\n /* 头部 */\n .header {\n background: var(--primary-gradient);\n color: #ffffff;\n padding: 24px 16px;\n text-align: center;\n position: relative;\n overflow: hidden;\n }\n\n .header::before {\n content: '';\n position: absolute;\n top: -30px;\n left: -30px;\n width: 80px;\n height: 80px;\n background: rgba(255, 255, 255, 0.15);\n border-radius: 50%;\n }\n\n .header::after {\n content: '';\n position: absolute;\n bottom: -60px;\n right: -60px;\n width: 150px;\n height: 150px;\n background: rgba(255, 255, 255, 0.1);\n border-radius: 50%;\n }\n\n .header h1 {\n font-size: 1.4rem;\n font-weight: 700;\n margin-bottom: 8px;\n position: relative;\n z-index: 2;\n }\n\n .header p {\n font-size: 0.9rem;\n opacity: 0.9;\n line-height: 1.4;\n position: relative;\n z-index: 2;\n }\n\n .header-icon {\n font-size: 48px;\n margin-bottom: 10px;\n display: inline-block;\n animation: bounce 2s ease infinite;\n }\n\n @keyframes bounce {\n 0%, 100% { transform: translateY(0); }\n 50% { transform: translateY(-10px); }\n }\n\n /* 版本对比 */\n .version-comparison {\n display: flex;\n flex-wrap: nowrap;\n gap: 12px;\n padding: 16px;\n margin-top: 8px;\n position: relative;\n z-index: 10;\n }\n\n .version-card {\n flex: 1;\n min-width: 45%;\n background: var(--card-bg);\n border-radius: 16px;\n padding: 28px 16px 16px;\n box-shadow: var(--shadow);\n text-align: center;\n position: relative;\n transition: transform 0.3s ease, box-shadow 0.3s ease;\n overflow: hidden;\n border: 1px solid rgba(120, 130, 240, 0.1);\n }\n\n .version-card:hover {\n transform: translateY(-4px);\n box-shadow: var(--shadow-hover);\n }\n\n .version-card.current-version {\n background: linear-gradient(135deg, #ffffff 0%, #f8f9ff 100%);\n border: 1px solid rgba(78, 110, 242, 0.15);\n }\n\n .version-card.current-version:hover {\n box-shadow: 0 6px 20px rgba(78, 110, 242, 0.15);\n }\n\n .version-card.current-version h3,\n .version-card.current-version .version-number,\n .version-card.current-version .version-date {\n color: var(--text-main);\n }\n\n .version-card.latest-version {\n background: var(--latest-gradient);\n background-size: 300% 300%;\n box-shadow: var(--glow-shadow), var(--shadow);\n color: #fff;\n z-index: 2;\n animation: gradientAnimation 6s ease infinite, breathe 3s ease-in-out infinite;\n position: relative;\n overflow: hidden;\n }\n\n .version-card.latest-version::before {\n content: '';\n position: absolute;\n top: -50%;\n left: -50%;\n width: 200%;\n height: 200%;\n background: linear-gradient(\n 90deg,\n transparent,\n rgba(255, 255, 255, 0.3),\n transparent\n );\n transform: rotate(45deg);\n animation: shimmer 3s infinite;\n }\n\n .version-card.latest-version h3,\n .version-card.latest-version .version-number,\n .version-card.latest-version .version-date {\n color: #fff;\n text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\n position: relative;\n z-index: 1;\n }\n\n .version-status {\n position: absolute;\n top: 6px;\n right: 6px;\n padding: 3px 7px;\n font-size: 0.65rem;\n font-weight: 600;\n border-radius: 6px;\n color: #fff;\n line-height: 1.2;\n white-space: nowrap;\n z-index: 2;\n }\n\n .version-card.latest-version .version-status {\n background: rgba(255, 255, 255, 0.25);\n backdrop-filter: blur(5px);\n border: 1px solid rgba(255, 255, 255, 0.3);\n color: #fff;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);\n }\n\n .status-outdated { background: var(--warning-color); }\n .status-latest { background: var(--success-color); }\n .status-invalid { background: var(--error-color); }\n\n .version-card h3 {\n font-size: 0.9rem;\n color: var(--text-secondary);\n margin-bottom: 8px;\n font-weight: 500;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n }\n\n .version-number {\n font-size: 1.25rem;\n font-weight: 700;\n color: var(--text-main);\n margin: 8px 0;\n transition: all 0.3s ease;\n font-family: 'Courier New', monospace;\n }\n\n .version-card.latest-version .version-number {\n font-size: 1.4rem;\n transform: scale(1.05);\n text-shadow: \n 0 2px 4px rgba(0, 0, 0, 0.3),\n 0 0 10px rgba(212, 175, 55, 0.8),\n 0 0 20px rgba(212, 175, 55, 0.5);\n animation: pulse-glow 2s ease-in-out infinite;\n }\n\n @keyframes pulse-glow {\n 0%, 100% {\n text-shadow: \n 0 2px 4px rgba(0, 0, 0, 0.3),\n 0 0 10px rgba(212, 175, 55, 0.8),\n 0 0 20px rgba(212, 175, 55, 0.5);\n }\n 50% {\n text-shadow: \n 0 2px 4px rgba(0, 0, 0, 0.3),\n 0 0 15px rgba(212, 175, 55, 1),\n 0 0 30px rgba(212, 175, 55, 0.7);\n }\n }\n\n .version-date {\n font-size: 0.8rem;\n color: var(--text-secondary);\n margin-top: 4px;\n }\n\n /* 版本对比指示器 */\n .version-indicator {\n position: absolute;\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n z-index: 5;\n width: 32px;\n height: 32px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);\n color: white;\n }\n\n .version-indicator.update-needed {\n background: var(--error-color);\n box-shadow: 0 2px 8px rgba(234, 84, 85, 0.4);\n animation: pulse-indicator 1.5s infinite;\n }\n\n .version-indicator.is-latest {\n background: var(--success-color);\n box-shadow: 0 2px 8px rgba(40, 199, 111, 0.4);\n }\n\n @keyframes pulse-indicator {\n 0% { transform: translate(-50%, -50%) scale(1); }\n 50% { transform: translate(-50%, -50%) scale(1.1); }\n 100% { transform: translate(-50%, -50%) scale(1); }\n }\n\n /* 内容区 */\n .content-container {\n padding: 16px;\n }\n\n /* 状态提示 */\n .status-alert {\n background: var(--card-bg);\n border-radius: 16px;\n box-shadow: var(--shadow);\n margin-bottom: 16px;\n overflow: hidden;\n border: 1px solid rgba(120, 130, 240, 0.1);\n padding: 12px 16px;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 10px;\n font-weight: 500;\n font-size: 14px;\n animation: slideIn 0.5s ease 0.3s backwards;\n }\n\n .status-alert i {\n font-size: 20px;\n }\n\n .status-alert.update-available {\n background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%);\n color: #d63031;\n box-shadow: 0 4px 15px rgba(253, 203, 110, 0.4);\n }\n\n .status-alert.up-to-date {\n background: linear-gradient(135deg, #55efc4 0%, #00b894 100%);\n color: white;\n box-shadow: 0 4px 15px rgba(0, 184, 148, 0.4);\n }\n\n /* 更新容器 */\n .update-container {\n background: var(--card-bg);\n border-radius: 16px;\n box-shadow: var(--shadow);\n margin-bottom: 16px;\n overflow: hidden;\n border: 1px solid rgba(120, 130, 240, 0.1);\n animation: slideIn 0.5s ease 0.4s backwards;\n }\n\n .update-header {\n background: var(--light-bg);\n padding: 12px 16px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n border-bottom: 1px solid var(--border-color);\n }\n\n .update-header h2 {\n font-size: 1rem;\n font-weight: 600;\n color: var(--text-main);\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .update-header h2 i {\n color: #4e6ef2;\n }\n\n .update-tag {\n background: rgba(78, 110, 242, 0.1);\n color: #4e6ef2;\n padding: 4px 8px;\n border-radius: 8px;\n font-size: 0.75rem;\n font-weight: 600;\n }\n\n .update-content {\n padding: 16px;\n }\n\n .update-date {\n font-weight: 600;\n color: #4e6ef2;\n margin-bottom: 12px;\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 0;\n border-bottom: 1px dashed #e0e0e0;\n }\n\n .update-text {\n margin: 8px 0;\n position: relative;\n padding-left: 16px;\n line-height: 1.5;\n color: var(--text-main);\n font-size: 0.95rem;\n white-space: pre-wrap;\n word-break: break-word;\n }\n\n .update-text::before {\n content: '•';\n position: absolute;\n left: 0;\n font-weight: bold;\n color: #4e6ef2;\n font-size: 1.2rem;\n line-height: 1;\n }\n\n /* 历史日志 */\n .history-container {\n background: var(--card-bg);\n border-radius: 16px;\n box-shadow: var(--shadow);\n margin-bottom: 16px;\n border: 1px solid rgba(120, 130, 240, 0.1);\n animation: slideIn 0.5s ease 0.5s backwards;\n }\n\n .history-header {\n background: var(--light-bg);\n padding: 12px 16px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n border-bottom: 1px solid var(--border-color);\n cursor: pointer;\n user-select: none;\n }\n\n .history-header:hover {\n opacity: 0.8;\n }\n\n .history-header h2 {\n font-size: 1rem;\n font-weight: 600;\n color: var(--text-main);\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .history-header h2 i {\n color: #4e6ef2;\n }\n\n .toggle-history {\n background: none;\n border: none;\n color: var(--text-secondary);\n cursor: pointer;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 0.85rem;\n transition: color 0.2s ease;\n }\n\n .toggle-history:hover {\n color: #4e6ef2;\n }\n\n .history-content {\n padding: 0 16px;\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.4s ease, padding 0.4s ease;\n }\n\n .history-content.expanded {\n max-height: 60vh;\n overflow-y: auto;\n padding: 16px;\n scrollbar-width: thin;\n scrollbar-color: #4e6ef2 #f0f0f0;\n }\n\n .history-content.expanded::-webkit-scrollbar {\n width: 6px;\n }\n\n .history-content.expanded::-webkit-scrollbar-track {\n background: #f0f0f0;\n border-radius: 4px;\n }\n\n .history-content.expanded::-webkit-scrollbar-thumb {\n background: #4e6ef2;\n border-radius: 4px;\n }\n\n .history-content.expanded::-webkit-scrollbar-thumb:hover {\n background: #3a56d0;\n }\n\n .history-item {\n margin-bottom: 16px;\n padding-bottom: 16px;\n border-bottom: 1px dashed var(--border-color);\n }\n\n .history-item:last-child {\n border-bottom: none;\n margin-bottom: 0;\n padding-bottom: 0;\n }\n\n .history-date {\n font-weight: 600;\n color: var(--text-main);\n margin-bottom: 8px;\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 0.9rem;\n background: rgba(78, 110, 242, 0.05);\n padding: 6px 10px;\n border-radius: 6px;\n }\n\n .history-text {\n margin: 8px 0;\n padding-left: 16px;\n line-height: 1.4;\n color: var(--text-secondary);\n position: relative;\n font-size: 0.9rem;\n white-space: pre-wrap;\n word-break: break-word;\n }\n\n .history-text::before {\n content: '•';\n position: absolute;\n left: 0;\n color: #4e6ef2;\n font-weight: bold;\n font-size: 1.2rem;\n line-height: 1;\n }\n\n /* 按钮组 */\n .button-group {\n display: flex;\n flex-direction: column;\n gap: 10px;\n margin-bottom: 16px;\n }\n\n .button {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 10px;\n padding: 14px 28px;\n text-align: center;\n font-size: 1rem;\n border: none;\n border-radius: 12px;\n text-decoration: none;\n background: var(--primary-gradient);\n color: white;\n font-weight: 600;\n transition: all 0.3s ease;\n box-shadow: var(--shadow);\n position: relative;\n overflow: hidden;\n cursor: pointer;\n }\n\n .button i {\n font-size: 1rem;\n }\n\n .button::after {\n content: '';\n position: absolute;\n top: -50%;\n left: -50%;\n width: 200%;\n height: 200%;\n background: rgba(255, 255, 255, 0.1);\n transform: rotate(30deg);\n transition: all 0.6s ease;\n pointer-events: none;\n }\n\n .button:hover {\n transform: translateY(-3px);\n box-shadow: var(--shadow-hover);\n }\n\n .button:hover::after {\n transform: rotate(30deg) translate(20%, 20%);\n }\n\n .button:active {\n transform: scale(0.95);\n }\n\n /* 错误状态 */\n .error-state {\n text-align: center;\n padding: 40px 20px;\n color: var(--text-main);\n }\n\n .error-icon {\n font-size: 64px;\n margin-bottom: 20px;\n color: var(--error-color);\n }\n\n .error-text {\n font-size: 16px;\n line-height: 1.6;\n margin-bottom: 20px;\n }\n\n .retry-button {\n background: var(--primary-gradient);\n color: white;\n padding: 12px 30px;\n border-radius: 12px;\n border: none;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.3s ease;\n font-size: 14px;\n box-shadow: var(--shadow);\n }\n\n .retry-button:hover {\n transform: translateY(-2px);\n box-shadow: var(--shadow-hover);\n }\n\n .retry-button:active {\n transform: scale(0.95);\n }\n\n /* 装饰元素 */\n .decoration {\n position: absolute;\n z-index: 0;\n pointer-events: none;\n }\n\n .decoration.circle {\n width: 120px;\n height: 120px;\n border-radius: 50%;\n background: rgba(107, 45, 216, 0.05);\n top: 10%;\n left: 10%;\n }\n\n .decoration.square {\n width: 80px;\n height: 80px;\n transform: rotate(45deg);\n background: rgba(78, 110, 242, 0.05);\n bottom: 10%;\n right: 10%;\n }\n\n /* 响应式 */\n @media (max-width: 768px) {\n body {\n padding: 12px;\n }\n\n .container {\n max-width: 100%;\n border-radius: 20px;\n }\n\n .header {\n padding: 20px 15px;\n }\n\n .header h1 {\n font-size: 1.3rem;\n }\n\n .header-icon {\n font-size: 40px;\n }\n\n .version-comparison {\n flex-direction: row;\n flex-wrap: nowrap;\n gap: 10px;\n padding: 12px;\n margin-top: 6px;\n overflow-x: auto;\n }\n\n .version-card {\n min-width: 45%;\n padding: 26px 12px 12px;\n }\n\n /* 移动端减弱呼吸动效 */\n .version-card.latest-version {\n animation: gradientAnimation 6s ease infinite;\n }\n\n .version-status {\n top: 5px;\n right: 5px;\n padding: 2px 5px;\n font-size: 0.6rem;\n }\n\n .version-number {\n font-size: 1.1rem;\n }\n\n .version-card.latest-version .version-number {\n font-size: 1.2rem;\n }\n\n .update-header h2, .history-header h2 {\n font-size: 0.9rem;\n }\n\n .button {\n padding: 12px 24px;\n font-size: 0.95rem;\n }\n\n .history-content.expanded {\n max-height: 50vh;\n -webkit-overflow-scrolling: touch;\n }\n }\n\n @media (max-width: 380px) {\n .header h1 {\n font-size: 1.2rem;\n }\n\n .version-number {\n font-size: 1rem;\n }\n\n .version-card.latest-version .version-number {\n font-size: 1.1rem;\n }\n\n .button {\n padding: 11px;\n font-size: 0.9rem;\n }\n }\n </style>\n</head>\n<body>\n <div class=\"decoration circle\"></div>\n <div class=\"decoration square\"></div>\n\n <div id=\"loading\" class=\"loading-wrapper\">\n <div class=\"loading-spinner\"></div>\n <div class=\"loading-text\"><i class=\"fas fa-search\"></i> 正在检查更新...</div>\n </div>\n\n <div class=\"container\" id=\"container\">\n <div class=\"header\">\n <div class=\"header-icon\"><i class=\"fas fa-book\"></i></div>\n <h1>光遇书源更新</h1>\n<p>推荐使用阅读Sigma版本<br>正式版可能存在兼容性问题-<a href='https://legado.qingtian618.com' style='text-decoration: none; color: #2D9D78; font-weight: 600;'>下载</a></p>\n </div>\n\n <div class=\"version-comparison\">\n <div class=\"version-card current-version\">\n <div class=\"version-status status-outdated\" id=\"currentStatus\">待检查</div>\n <h3><i class=\"fas fa-cube\"></i> 当前版本</h3>\n <div class=\"version-number\" id=\"currentVersion\">-</div>\n <div class=\"version-date\">您的当前版本</div>\n </div>\n\n <div class=\"version-indicator update-needed\" id=\"versionIndicator\" style=\"display: none;\">\n <i class=\"fas fa-arrow-right\"></i>\n </div>\n\n <div class=\"version-card latest-version\">\n <div class=\"version-status status-latest\" id=\"latestStatus\">最新版本</div>\n <h3><i class=\"fas fa-star\"></i> 最新版本</h3>\n <div class=\"version-number\" id=\"latestVersion\">-</div>\n <div class=\"version-date\">可用最新版本</div>\n </div>\n </div>\n\n <div class=\"content-container\">\n <div class=\"status-alert\" id=\"statusAlert\" style=\"display: none;\"></div>\n\n <div id=\"latestLogContainer\" style=\"display: none;\">\n <div class=\"update-container\">\n <div class=\"update-header\">\n <h2><i class=\"fas fa-bolt\"></i> 最新更新</h2>\n <div class=\"update-tag\">最新发布</div>\n </div>\n <div class=\"update-content\">\n <div class=\"update-date\" id=\"latestLogDate\"></div>\n <div class=\"update-text\" id=\"latestLogContent\"></div>\n </div>\n </div>\n </div>\n\n <div class=\"button-group\" id=\"buttonGroup\" style=\"display: none;\"></div>\n\n <div class=\"history-container\" id=\"logs\" style=\"display: none;\">\n <div class=\"history-header\" onclick=\"toggleLogs()\">\n <h2><i class=\"fas fa-history\"></i> 历史更新 <span id=\"historyCount\"></span></h2>\n <button class=\"toggle-history\" id=\"toggleButton\">\n <span id=\"toggleText\">展开历史</span>\n <i class=\"fas fa-chevron-down\" id=\"toggleIcon\"></i>\n </button>\n </div>\n <div class=\"history-content\" id=\"logList\"></div>\n </div>\n </div>\n </div>\n\n <script>\n let logsCollapsed = true;\n\n function toggleLogs() {\n logsCollapsed = !logsCollapsed;\n const logList = document.getElementById('logList');\n const toggleText = document.getElementById('toggleText');\n const toggleIcon = document.getElementById('toggleIcon');\n \n if (logsCollapsed) {\n logList.classList.remove('expanded');\n toggleText.textContent = '展开历史';\n toggleIcon.className = 'fas fa-chevron-down';\n } else {\n logList.classList.add('expanded');\n toggleText.textContent = '收起历史';\n toggleIcon.className = 'fas fa-chevron-up';\n }\n }\n\n (async function() {\n const loading = document.getElementById('loading');\n const container = document.getElementById('container');\n const currentVersion = document.getElementById('currentVersion');\n const latestVersion = document.getElementById('latestVersion');\n const currentStatus = document.getElementById('currentStatus');\n const latestStatus = document.getElementById('latestStatus');\n const versionIndicator = document.getElementById('versionIndicator');\n const statusAlert = document.getElementById('statusAlert');\n const buttonGroup = document.getElementById('buttonGroup');\n const latestLogContainer = document.getElementById('latestLogContainer');\n const latestLogDate = document.getElementById('latestLogDate');\n const latestLogContent = document.getElementById('latestLogContent');\n const logsContainer = document.getElementById('logs');\n const logList = document.getElementById('logList');\n const historyCount = document.getElementById('historyCount');\n\n const localVer = '${String(localVersion)}';\n let hosts = \\`${hostsbk}\\`;\n hosts = hosts.split(',');\n \nconst icons = ['box', 'satellite', 'link', 'bolt', 'globe', 'broadcast-tower'];\n\n// 初始化serverConfig\nconst serverConfig = {\n main: {\n name: '主线路导入',\n icon: 'rocket',\n baseUrl: 'https://sy.qingtian618.com',\n downloadPath: '/download/光遇聚合.json'\n }\n};\n\nhosts.forEach((host, index) => {\n const backupNumber = index + 1;\n const key =\\`备用线路\\${backupNumber}\\`;\n\n serverConfig[key] = {\n name: \\`\\${key}导入\\`,\n icon: icons[index] || 'broadcast-tower',\n baseUrl: host,\n downloadPath: '/sy/download/光遇聚合.json'\n };\n});\n\n // 版本比较函数\n function compareVersions(vs) {\n const normalize = (v) => {\n return v.split('.').map(n => {\n const num = parseInt(n, 10);\n return isNaN(num) ? 0 : num;\n });\n };\n\n const parts1 = normalize(localVer);\n const parts2 = normalize(vs);\n const maxLength = Math.max(parts1.length, parts2.length);\n \n for (let i = 0; i < maxLength; i++) {\n const num1 = parts1[i] || 0;\n const num2 = parts2[i] || 0;\n if (num1 > num2) return 1;\n if (num1 < num2) return -1;\n }\n return 0;\n }\n\nasync function fetchVersionData() {\n const serversToCheck = Object.values(serverConfig);\n \n for (const server of serversToCheck) {\n try {\n const apiPath = server.baseUrl.includes('sy.qingtian618.com') \n ? '/fils-groups' \n : '/get_sy_log';\n \n const response = await fetch(server.baseUrl + apiPath, { timeout: 2000 });\n if (response.ok) {\n return await response.json();\n }\n } catch (e) {\n console.warn(\\`接口失败:\\${server.baseUrl}\\`, e);\n }\n }\n throw new Error('所有更新接口都请求失败');\n}\n\n\n function showError(message) {\n loading.innerHTML = \\`\n <div class=\"error-state\">\n <div class=\"error-icon\"><i class=\"fas fa-exclamation-triangle\"></i></div>\n <div class=\"error-text\">\\${message}</div>\n <button class=\"retry-button\" onclick=\"location.reload()\"><i class=\"fas fa-redo\"></i> 重试</button>\n </div>\n \\`;\n }\n\n try {\n const data = await fetchVersionData();\nconst legadoData = (data.data && data.data.find(item => item.key == 'legado_aggregate')) || {};\nconst cloudVersion = String(legadoData.current_version || '');\nconst updateLog = legadoData.update_log || [];\n\n// 显示版本信息\ncurrentVersion.textContent = \\`v\\${localVer}\\`;\nlatestVersion.textContent = \\`v\\${cloudVersion}\\`;\n\n// 处理日志(update_log 是数组格式)\nif (updateLog.length > 0) {\n// 显示最新日志\nconst latestLog = updateLog[0];\nlatestLogDate.innerHTML = \\`<i class=\"fas fa-calendar-alt\"></i> v\\${latestLog.version}\\`;\nlatestLogContent.innerHTML = latestLog.log;\nlatestLogContainer.style.display = 'block';\n\n// 显示历史日志\nif (updateLog.length > 1) {\nconst historyLogs = updateLog.slice(1);\nhistoryCount.textContent = \\`(\\${historyLogs.length}条)\\`;\nlogList.innerHTML = historyLogs.map(item => \\`\n<div class=\"history-item\">\n <div class=\"history-date\">\n <i class=\"fas fa-calendar-day\"></i>\n <span>v\\${item.version}</span>\n </div>\n <div class=\"history-text\">\\${item.log}</div>\n</div>\n\\`).join('');\nlogsContainer.style.display = 'block';\n}\n}\n\n// 检查更新状态\nconst compareResult = compareVersions(cloudVersion);\n\n// 显示版本指示器\nversionIndicator.style.display = 'flex';\n\nif (compareResult === -1) {\n// 需要更新\ncurrentStatus.textContent = '待更新';\ncurrentStatus.className = 'version-status status-outdated';\nversionIndicator.className = 'version-indicator update-needed';\nversionIndicator.innerHTML = '<i class=\"fas fa-arrow-right\"></i>';\n\n// 使用统一配置生成下载按钮\nbuttonGroup.innerHTML = Object.values(serverConfig).map(server => {\nconst fullUrl = server.baseUrl + server.downloadPath;\nreturn \\`\n<a href=\"yuedu://booksource/importonline?src=\\${encodeURIComponent(fullUrl)}\" class=\"button\">\n <i class=\"fas fa-\\${server.icon}\"></i>\n <span>\\${server.name}</span>\n</a>\n\\`;\n}).join('');\nbuttonGroup.style.display = 'flex';\n} else {\n// 已是最新版本\ncurrentStatus.textContent = '最新';\ncurrentStatus.className = 'version-status status-latest';\nversionIndicator.className = 'version-indicator is-latest';\nversionIndicator.innerHTML = '<i class=\"fas fa-check\"></i>';\n\nstatusAlert.className = 'status-alert up-to-date';\nstatusAlert.innerHTML = '<i class=\"fas fa-check-circle\"></i> <div>您已是最新版本</div>';\nstatusAlert.style.display = 'flex';\n}\n\n // 显示主容器,隐藏加载\n loading.style.display = 'none';\n container.style.display = 'block';\n\n } catch (err) {\n console.error('版本检查失败:', err);\n showError('<i class=\"fas fa-exclamation-circle\"></i> 检查更新失败,请稍后重试<br><small>' + err.message + '</small>');\n }\n })();\n </script>\n</body>\n</html>\n`;\n java.startBrowser(`data:text/html;base64,${java.base64Encode(html)}`, '光遇书源更新');\n}\n\n\n\n// 线路设置\nfunction getServerSettings() {\n const {java} = this;\n let data = this.getVariable('线路');\n //java.toast(data)\n let hostsbk = this.getVariable('云端配置')['hosts'] || hosts;\n \n let html = `\n<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">\n<title>光遇小说 - 线路设置</title>\n<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\">\n<style>\n* {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n -webkit-tap-highlight-color: transparent;\n}\n\n:root {\n --color-primary: #2D9D78;\n --color-primary-light: #3DB893;\n --bg-dark: #f5f7fa;\n --bg-darker: #ffffff;\n --bg-card: rgba(255, 255, 255, 0.95);\n --bg-elevated: #f8fafb;\n --text-primary: #1a1d23;\n --text-secondary: #6b7280;\n --border-color: rgba(0, 0, 0, 0.08);\n}\n\n[data-theme=\"dark\"] {\n --bg-dark: #0A1628;\n --bg-darker: #050B14;\n --bg-card: #142235;\n --bg-elevated: #1A2B42;\n --text-primary: #FFFFFF;\n --text-secondary: rgba(255, 255, 255, 0.7);\n --border-color: rgba(255, 255, 255, 0.1);\n}\n\nhtml, body {\n width: 100%;\n min-height: 100vh;\n font-family: -apple-system, BlinkMacSystemFont, sans-serif;\n font-size: 16px;\n color: var(--text-primary);\n background: var(--bg-darker);\n overflow-x: hidden;\n}\n\n.container {\n padding: 16px;\n max-width: 600px;\n margin: 0 auto;\n}\n\n.main-card {\n background: var(--bg-card);\n border-radius: 20px;\n box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1);\n border: 1px solid var(--border-color);\n overflow: hidden;\n}\n\n.header {\n padding: 20px 16px;\n background: linear-gradient(135deg, var(--color-primary), var(--color-primary-light));\n text-align: center;\n}\n\n.header-title {\n font-size: 18px;\n font-weight: 700;\n color: white;\n margin-bottom: 4px;\n}\n\n.header-desc {\n font-size: 12px;\n color: rgba(255, 255, 255, 0.8);\n}\n\n.last-check {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: var(--text-secondary);\n padding: 12px 16px;\n border-bottom: 1px solid var(--border-color);\n}\n\n.last-check i {\n color: var(--color-primary);\n}\n\n.server-list {\n padding: 8px;\n}\n\n.server-card {\n background: var(--bg-elevated);\n border-radius: 12px;\n padding: 14px 16px;\n margin-bottom: 8px;\n display: flex;\n align-items: center;\n gap: 12px;\n cursor: pointer;\n transition: all 0.2s ease;\n border: 2px solid transparent;\n}\n\n.server-card:hover {\n border-color: var(--color-primary);\n background: rgba(45, 157, 120, 0.05);\n}\n\n.server-card.selected {\n border-color: var(--color-primary);\n background: rgba(45, 157, 120, 0.1);\n}\n\n.server-icon {\n width: 36px;\n height: 36px;\n border-radius: 10px;\n background: linear-gradient(135deg, rgba(45, 157, 120, 0.15), rgba(61, 184, 147, 0.1));\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n color: var(--color-primary);\n flex-shrink: 0;\n}\n\n.server-card.selected .server-icon {\n background: linear-gradient(135deg, var(--color-primary), var(--color-primary-light));\n color: white;\n}\n\n.server-info {\n flex: 1;\n min-width: 0;\n}\n\n.server-url {\n font-size: 14px;\n font-weight: 700;\n color: var(--text-primary);\n word-break: break-all;\n line-height: 1.4;\n}\n\n.server-details {\n font-size: 11px;\n color: var(--text-secondary);\n margin-top: 2px;\n}\n\n.server-status {\n padding: 4px 12px;\n border-radius: 16px;\n font-size: 11px;\n font-weight: 700;\n white-space: nowrap;\n flex-shrink: 0;\n}\n\n.server-status.checking {\n background: rgba(255, 193, 7, 0.2);\n color: #f57f17;\n}\n\n.server-status.online {\n background: rgba(76, 175, 80, 0.2);\n color: #2e7d32;\n}\n\n.server-status.slow {\n background: rgba(255, 193, 7, 0.2);\n color: #f57f17;\n}\n\n.server-status.offline {\n background: rgba(244, 67, 54, 0.2);\n color: #c62828;\n}\n\n.server-status.not-support {\n background: rgba(156, 163, 175, 0.2);\n color: #6b7280;\n}\n\n.selected-badge {\n width: 20px;\n height: 20px;\n border-radius: 50%;\n background: var(--color-primary);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n\n.selected-badge i {\n color: white;\n font-size: 12px;\n}\n\n.footer {\n padding: 16px;\n border-top: 1px solid var(--border-color);\n text-align: center;\n}\n\n.footer-text {\n font-size: 12px;\n color: var(--text-secondary);\n line-height: 1.5;\n}\n\n.footer-text .highlight {\n color: var(--color-primary);\n font-weight: 700;\n}\n\n/* 自动切换开关样式 */\n.auto-switch-section {\n padding: 14px 16px;\n border-bottom: 1px solid var(--border-color);\n display: flex;\n align-items: center;\n justify-content: space-between;\n background: var(--bg-elevated);\n}\n\n.auto-switch-section:hover {\n background: rgba(45, 157, 120, 0.05);\n}\n\n.auto-switch-info {\n flex: 1;\n min-width: 0;\n}\n\n.auto-switch-title {\n font-size: 14px;\n font-weight: 600;\n color: var(--text-primary);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.auto-switch-title i {\n color: var(--color-primary);\n font-size: 14px;\n}\n\n.auto-switch-desc {\n font-size: 11px;\n color: var(--text-secondary);\n margin-top: 3px;\n line-height: 1.4;\n}\n\n/* 开关基础样式 */\n.switch {\n position: relative;\n display: inline-block;\n width: 48px;\n height: 28px;\n flex-shrink: 0;\n}\n\n.switch input {\n opacity: 0;\n width: 0;\n height: 0;\n}\n\n.switch-slider {\n position: absolute;\n cursor: pointer;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: #d1d5db;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n border-radius: 28px;\n box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);\n}\n\n.switch-slider:before {\n position: absolute;\n content: \"\";\n height: 22px;\n width: 22px;\n left: 3px;\n bottom: 3px;\n background-color: white;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n border-radius: 50%;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.1);\n}\n\n.switch input:checked + .switch-slider {\n background: linear-gradient(135deg, var(--color-primary), var(--color-primary-light));\n box-shadow: 0 2px 8px rgba(45, 157, 120, 0.3);\n}\n\n.switch input:checked + .switch-slider:before {\n transform: translateX(20px);\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);\n}\n\n/* 开关焦点状态 */\n.switch input:focus + .switch-slider {\n outline: 2px solid var(--color-primary);\n outline-offset: 2px;\n}\n\n/* 下拉框样式 */\n.timeout-select {\n padding: 6px 12px;\n font-size: 13px;\n font-weight: 600;\n color: var(--text-primary);\n background: var(--bg-card);\n border: 2px solid var(--border-color);\n border-radius: 10px;\n cursor: pointer;\n min-width: 70px;\n appearance: none;\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 8px center;\n background-size: 14px;\n}\n\n.timeout-select:focus {\n outline: none;\n border-color: var(--color-primary);\n}\n\n.timeout-select option {\n background: var(--bg-darker);\n color: var(--text-primary);\n}\n\n.hidden-settings {\n display: none;\n}/* 提示文案样式 */\n.tip-alert {\n background: linear-gradient(135deg, rgba(255, 193, 7, 0.12) 0%, rgba(255, 152, 0, 0.08) 100%);\n border: 1px solid rgba(255, 193, 7, 0.25);\n border-radius: 16px;\n padding: 16px 18px;\n margin: 20px;\n display: flex;\n align-items: flex-start;\n gap: 14px;\n box-shadow: 0 4px 16px rgba(255, 193, 7, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.8);\n position: relative;\n overflow: hidden;\n}\n\n.tip-alert::before {\n content: '';\n position: absolute;\n top: -50%;\n right: -20%;\n width: 100px;\n height: 100px;\n background: rgba(255, 193, 7, 0.1);\n border-radius: 50%;\n}\n\n.tip-alert i {\n color: #f97316;\n font-size: 22px;\n flex-shrink: 0;\n margin-top: 2px;\n position: relative;\n z-index: 1;\n}\n\n.tip-alert p {\n font-size: 13px;\n color: var(--text-secondary);\n line-height: 1.6;\n margin: 0;\n position: relative;\n z-index: 1;\n}\n\n.tip-alert .highlight-text {\n color: #ea580c;\n font-weight: 700;\n}\n\n@media (max-width: 600px) {\n .tip-alert {\n margin: 16px;\n padding: 14px 16px;\n }\n}\n</style>\n</head>\n<body data-theme=\"light\">\n<div class=\"container\">\n <div class=\"main-card\">\n <div class=\"header\">\n <div class=\"header-title\">\n <i class=\"fas fa-server\"></i>\n <span style=\"margin-left: 8px;\">线路选择</span>\n </div>\n <div class=\"header-desc\">选择最快的服务器线路以获得最佳体验</div>\n </div>\n \n <!-- 提示文案 -->\n <div class=\"tip-alert\">\n <p>设置完成后请点击右上角的<span class=\"highlight-text\">√</span>应用设置</p>\n </div>\n \n <div class=\"footer\">\n <div class=\"footer-text\">\n 当前选中: <span class=\"highlight\" id=\"currentServer\">--</span>\n </div>\n </div>\n \n <div class=\"last-check\">\n <i class=\"fas fa-clock\"></i>\n <span id=\"lastCheckTime\">最后检测: --:--:--</span>\n <button id=\"refreshBtn\" style=\"margin-left: auto; padding: 4px 10px; font-size: 11px; font-weight: 600; color: var(--color-primary); background: rgba(45, 157, 120, 0.1); border: 1px solid var(--color-primary); border-radius: 6px; cursor: pointer;\">\n <i class=\"fas fa-sync-alt\"></i>\n 刷新\n </button>\n </div>\n \n <div class=\"auto-switch-section\">\n <div class=\"auto-switch-info\">\n <div class=\"auto-switch-title\"><i class=\"fas fa-refresh\"></i>失败自动切换</div>\n <div class=\"auto-switch-desc\">当前线路失败时自动尝试切换到其他可用线路</div>\n </div>\n <label class=\"switch\">\n <input type=\"checkbox\" id=\"autoSwitchToggle\">\n <span class=\"switch-slider\"></span>\n </label>\n </div>\n \n <div class=\"auto-switch-section\">\n <div class=\"auto-switch-info\">\n <div class=\"auto-switch-title\"><i class=\"fas fa-clock\"></i>超时自动切换</div>\n <div class=\"auto-switch-desc\">请求超时后自动切换到其他可用线路</div>\n </div>\n <div style=\"display: flex; align-items: center; gap: 12px;\">\n <label class=\"switch\">\n <input type=\"checkbox\" id=\"timeoutSwitchToggle\">\n <span class=\"switch-slider\"></span>\n </label>\n <select id=\"timeoutSelect\" class=\"timeout-select\">\n <option value=\"1\">1秒</option>\n <option value=\"2\">2秒</option>\n <option value=\"3\">3秒</option>\n <option value=\"4\">4秒</option>\n <option value=\"5\">5秒</option>\n <option value=\"6\">6秒</option>\n <option value=\"7\">7秒</option>\n <option value=\"8\">8秒</option>\n <option value=\"9\">9秒</option>\n <option value=\"10\" selected>10秒</option>\n <option value=\"11\">11秒</option>\n <option value=\"12\">12秒</option>\n <option value=\"13\">13秒</option>\n <option value=\"14\">14秒</option>\n <option value=\"15\">15秒</option>\n </select>\n </div>\n </div>\n \n <div class=\"auto-switch-section\">\n <div class=\"auto-switch-info\">\n <div class=\"auto-switch-title\"><i class=\"fas fa-bell\"></i>切换提醒弹窗</div>\n <div class=\"auto-switch-desc\">线路切换时显示提醒弹窗通知</div>\n </div>\n <label class=\"switch\">\n <input type=\"checkbox\" id=\"notifySwitchToggle\">\n <span class=\"switch-slider\"></span>\n </label>\n </div>\n \n <div id=\"serverList\" class=\"server-list\"></div>\n \n <div class=\"hidden-settings\">\n <span id=\"serverValue\">${data || hostsbk[0]}</span>\n <span id=\"autoSwitchValue\">${this.getVariable('自动切换') || 'true'}</span>\n <span id=\"timeoutSwitchValue\">${this.getVariable('超时自动切换') || 'true'}</span>\n <span id=\"timeoutValue\">${this.getVariable('超时时间') || '10'}</span>\n <span id=\"notifySwitchValue\">${this.getVariable('切换提醒') || 'false'}</span>\n </div>\n </div>\n</div>\n\n<script>\nconst HOSTS = ${JSON.stringify(hostsbk)};\nlet currentServer = '${data || hostsbk[0]}';\n\nfunction renderServerList() {\n const serverList = document.getElementById('serverList');\n serverList.innerHTML = '';\n \n HOSTS.forEach((host, index) => {\n const card = document.createElement('div');\n card.className = 'server-card' + (currentServer === host ? ' selected' : '');\n card.dataset.host = host;\n card.dataset.index = index;\n \n card.innerHTML = \\`\n <div class=\"server-icon\">\n <i class=\"fas fa-server\"></i>\n </div>\n <div class=\"server-info\">\n <div class=\"server-url\">\\${host}</div>\n <div class=\"server-details\" id=\"details-\\${index}\">状态: 检测中</div>\n </div>\n <span class=\"server-status checking\" id=\"status-\\${index}\">\n <i class=\"fas fa-spinner fa-spin\"></i>\n <span>检测中</span>\n </span>\n \\${currentServer === host ? '<div class=\"selected-badge\"><i class=\"fas fa-check\"></i></div>' : ''}\n \\`;\n \n card.addEventListener('click', () => {\n selectServer(host);\n });\n \n serverList.appendChild(card);\n });\n}\n\nfunction selectServer(host) {\n currentServer = host;\n \n document.querySelectorAll('.server-card').forEach(card => {\n card.classList.remove('selected');\n const badge = card.querySelector('.selected-badge');\n if (badge) badge.remove();\n });\n \n const selectedCard = document.querySelector(\\`[data-host=\"\\${host}\"]\\`);\n if (selectedCard) {\n selectedCard.classList.add('selected');\n const icon = selectedCard.querySelector('.server-icon');\n icon.style.background = 'linear-gradient(135deg, var(--color-primary), var(--color-primary-light))';\n icon.style.color = 'white';\n \n const statusEl = selectedCard.querySelector('.server-status');\n const newBadge = document.createElement('div');\n newBadge.className = 'selected-badge';\n newBadge.innerHTML = '<i class=\"fas fa-check\"></i>';\n selectedCard.appendChild(newBadge);\n }\n \n document.getElementById('currentServer').textContent = host;\n document.getElementById('serverValue').textContent = host;\n}\n\nasync function checkSingleServer(host, index) {\n const currentProtocol = window.location.protocol;\n const statusEl = document.getElementById(\\`status-\\${index}\\`);\n const detailsEl = document.getElementById(\\`details-\\${index}\\`);\n \n try {\n const serverProtocol = new URL(host).protocol;\n if (currentProtocol === 'https:' && serverProtocol === 'http:') {\n statusEl.className = 'server-status not-support';\n statusEl.innerHTML = '<i class=\"fas fa-exclamation-triangle\"></i><span>不支持</span>';\n detailsEl.textContent = '浏览器安全策略限制';\n return;\n }\n \n const startTime = performance.now();\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), 5000);\n \n const response = await fetch(\\`\\${host}/health2?t=\\${Date.now()}\\`, {\n signal: controller.signal,\n method: 'GET',\n mode: 'cors',\n cache: 'no-cache'\n });\n \n clearTimeout(timeout);\n const responseTime = Math.round(performance.now() - startTime);\n \n if (response.ok) {\n if (responseTime < 1000) {\n statusEl.className = 'server-status online';\n statusEl.innerHTML = \\`<i class=\"fas fa-check-circle\"></i><span>\\${responseTime}ms</span>\\`;\n } else if (responseTime < 2000) {\n statusEl.className = 'server-status slow';\n statusEl.innerHTML = \\`<i class=\"fas fa-exclamation-circle\"></i><span>\\${responseTime}ms</span>\\`;\n } else {\n statusEl.className = 'server-status slow';\n statusEl.innerHTML = '<i class=\"fas fa-exclamation-circle\"></i><span>缓慢</span>';\n }\n detailsEl.textContent = '运行正常';\n } else {\n statusEl.className = 'server-status offline';\n statusEl.innerHTML = '<i class=\"fas fa-times-circle\"></i><span>异常</span>';\n detailsEl.textContent = 'HTTP状态码: ' + response.status;\n }\n } catch (error) {\n statusEl.className = 'server-status offline';\n statusEl.innerHTML = '<i class=\"fas fa-times-circle\"></i><span>离线</span>';\n detailsEl.textContent = '连接失败';\n }\n}\n\nasync function checkServers() {\n HOSTS.forEach((_, index) => {\n const statusEl = document.getElementById(\\`status-\\${index}\\`);\n const detailsEl = document.getElementById(\\`details-\\${index}\\`);\n if (statusEl && detailsEl) {\n statusEl.className = 'server-status checking';\n statusEl.innerHTML = '<i class=\"fas fa-spinner fa-spin\"></i><span>检测中</span>';\n detailsEl.textContent = '状态: 检测中';\n }\n });\n \n const promises = HOSTS.map((host, index) => checkSingleServer(host, index));\n await Promise.all(promises);\n \n const now = new Date();\n document.getElementById('lastCheckTime').textContent = \\`最后检测: \\${now.toLocaleTimeString()}\\`;\n}\n\ndocument.getElementById('refreshBtn').addEventListener('click', checkServers);\n\ndocument.addEventListener('DOMContentLoaded', () => {\n renderServerList();\n document.getElementById('currentServer').textContent = currentServer;\n checkServers();\n \n // 初始化自动切换开关\n const autoSwitchToggle = document.getElementById('autoSwitchToggle');\n const autoSwitchValue = document.getElementById('autoSwitchValue').textContent;\n autoSwitchToggle.checked = autoSwitchValue !== 'false';\n \n // 监听开关变化\n autoSwitchToggle.addEventListener('change', (e) => {\n document.getElementById('autoSwitchValue').textContent = e.target.checked ? 'true' : 'false';\n });\n \n // 初始化超时自动切换开关\n const timeoutSwitchToggle = document.getElementById('timeoutSwitchToggle');\n const timeoutSwitchValue = document.getElementById('timeoutSwitchValue').textContent;\n timeoutSwitchToggle.checked = timeoutSwitchValue !== 'false';\n \n // 监听超时自动切换开关变化\n timeoutSwitchToggle.addEventListener('change', (e) => {\n document.getElementById('timeoutSwitchValue').textContent = e.target.checked ? 'true' : 'false';\n });\n \n // 初始化超时时间下拉框\n const timeoutSelect = document.getElementById('timeoutSelect');\n const timeoutValue = document.getElementById('timeoutValue').textContent;\n timeoutSelect.value = timeoutValue;\n \n // 监听超时时间变化\n timeoutSelect.addEventListener('change', (e) => {\n document.getElementById('timeoutValue').textContent = e.target.value;\n });\n \n // 初始化切换提醒弹窗开关\n const notifySwitchToggle = document.getElementById('notifySwitchToggle');\n const notifySwitchValue = document.getElementById('notifySwitchValue').textContent;\n notifySwitchToggle.checked = notifySwitchValue !== 'false';\n \n // 监听切换提醒弹窗开关变化\n notifySwitchToggle.addEventListener('change', (e) => {\n document.getElementById('notifySwitchValue').textContent = e.target.checked ? 'true' : 'false';\n });\n});\n</script>\n</body>\n</html>\n `;\n let body;\n try {\n body = java.startBrowserAwait(`data:text/html;base64,${java.base64Encode(html)}`, '光遇服务器设置', false).body();\n } catch (e) {\n let islyc = this.checkEnv()\n if (islyc == \"安卓\") {\n this.switchToNextLine();\n } else {\n java.longToast(\"\\n请点击页面右上角√才能设置成功哦~\");\n }\n return;\n }\n \n let host = hostsbk[0];\n let autoSwitch = 'true';\n let timeoutSwitch = 'true';\n let timeout = '10';\n let notifySwitch = 'false';\n try {\n const getSpanText = (spanId) => {\n const regex = new RegExp('id=\"' + spanId + '\"\\s*>\\s*([^<]*?)\\s*<\\/span>');\n const match = body.match(regex);\n return match ? match[1].trim() : \"\";\n };\n host = getSpanText('serverValue') || hostsbk[0];\n autoSwitch = getSpanText('autoSwitchValue') || 'true';\n timeoutSwitch = getSpanText('timeoutSwitchValue') || 'true';\n timeout = getSpanText('timeoutValue') || '10';\n notifySwitch = getSpanText('notifySwitchValue') || 'false';\n } catch (e) {\n host = hostsbk[0];\n autoSwitch = 'true';\n timeoutSwitch = 'true';\n timeout = '10';\n notifySwitch = 'true';\n }\n const settingsText = `线路: ${host}\\n失败自动切换: ${autoSwitch == 'true' ? '开启' : '关闭'}\\n超时自动切换: ${timeoutSwitch == 'true' ? '开启' : '关闭'}\\n超时时间: ${timeout}秒\\n切换提醒弹窗: ${notifySwitch == 'true' ? '开启' : '关闭'}`;\n java.longToast(\"\\n\" + settingsText);\n this.setVariable(\"线路\", host, false);\n this.setVariable(\"自动切换\", autoSwitch, false);\n this.setVariable(\"超时自动切换\", timeoutSwitch, false);\n this.setVariable(\"超时时间\", timeout, false);\n this.setVariable(\"切换提醒\", notifySwitch, false);\n}\n\n\n\n\n// 段评设置页面\nfunction getSvgSettings() {\n const {\n java\n } = this;\n const islyc = this.checkEnv();\n if (islyc == \"苹果\") {\n java.longToast(\"\\n当前软件不支持设置段评样式\");\n return;\n }\n let data = this.getVariable('段评设置');\n let base_url = this.BaseUrl();\n let qttoken = this.getToken();\n\n let html = `\n<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">\n<title>光遇小说 - 段评设置</title>\n<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\">\n<style>\n* {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n -webkit-tap-highlight-color: transparent;\n}\n\n:root {\n --color-primary: #2D9D78;\n --color-primary-light: #3DB893;\n --color-primary-dark: #1E6F55;\n --color-accent: #FFB74D;\n --color-success: #4CAF50;\n --color-error: #F44336;\n --color-warning: #FFC107;\n --color-info: #2196F3;\n \n --bg-dark: #f5f7fa;\n --bg-darker: #ffffff;\n --bg-card: rgba(255, 255, 255, 0.95);\n --bg-elevated: #f8fafb;\n \n --text-primary: #1a1d23;\n --text-secondary: #6b7280;\n --text-tertiary: #9ca3af;\n \n --border-color: rgba(0, 0, 0, 0.08);\n \n --font-display: 'Libre Baskerville', serif;\n --font-body: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;\n}\n\n[data-theme=\"dark\"] {\n --bg-dark: #0A1628;\n --bg-darker: #050B14;\n --bg-card: #142235;\n --bg-elevated: #1A2B42;\n \n --text-primary: #FFFFFF;\n --text-secondary: rgba(255, 255, 255, 0.7);\n --text-tertiary: rgba(255, 255, 255, 0.5);\n \n --border-color: rgba(255, 255, 255, 0.1);\n}\n\nhtml, body {\n width: 100%;\n min-height: 100vh;\n font-family: var(--font-body);\n font-size: 16px;\n color: var(--text-primary);\n background: var(--bg-darker);\n overflow-x: hidden;\n}\n\n.container {\n padding: 20px;\n max-width: 800px;\n margin: 0 auto;\n}\n\n.main-card {\n background: var(--bg-card);\n border-radius: 20px;\n box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1);\n border: 1px solid var(--border-color);\n overflow: hidden;\n}\n/* 提示文案样式 */\n.tip-alert {\n background: linear-gradient(135deg, rgba(255, 193, 7, 0.12) 0%, rgba(255, 152, 0, 0.08) 100%);\n border: 1px solid rgba(255, 193, 7, 0.25);\n border-radius: 16px;\n padding: 16px 18px;\n margin: 20px;\n display: flex;\n align-items: flex-start;\n gap: 14px;\n box-shadow: 0 4px 16px rgba(255, 193, 7, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.8);\n position: relative;\n overflow: hidden;\n}\n\n.tip-alert::before {\n content: '';\n position: absolute;\n top: -50%;\n right: -20%;\n width: 100px;\n height: 100px;\n background: rgba(255, 193, 7, 0.1);\n border-radius: 50%;\n}\n\n.tip-alert i {\n color: #f97316;\n font-size: 22px;\n flex-shrink: 0;\n margin-top: 2px;\n position: relative;\n z-index: 1;\n}\n\n.tip-alert p {\n font-size: 13px;\n color: var(--text-secondary);\n line-height: 1.6;\n margin: 0;\n position: relative;\n z-index: 1;\n}\n\n.tip-alert .highlight-text {\n color: #ea580c;\n font-weight: 700;\n}\n/* 设置项样式 */\n.setting-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 16px;\n border-bottom: 1px solid var(--border-color);\n}\n\n.setting-item:last-child {\n border-bottom: none;\n}\n\n.setting-info {\n flex: 1;\n min-width: 0;\n}\n\n.setting-title {\n font-size: 15px;\n font-weight: 700;\n color: var(--text-primary);\n margin-bottom: 4px;\n}\n\n.setting-desc {\n font-size: 12px;\n color: var(--text-secondary);\n line-height: 1.4;\n}\n\n/* 开关样式 */\n.switch {\n position: relative;\n display: inline-block;\n width: 48px;\n height: 28px;\n flex-shrink: 0;\n}\n\n.switch input {\n opacity: 0;\n width: 0;\n height: 0;\n}\n\n.slider {\n position: absolute;\n cursor: pointer;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background-color: #ccc;\n transition: 0.3s;\n border-radius: 28px;\n}\n\n.slider:before {\n position: absolute;\n content: \"\";\n height: 22px;\n width: 22px;\n left: 3px;\n bottom: 3px;\n background-color: white;\n transition: 0.3s;\n border-radius: 50%;\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);\n}\n\ninput:checked + .slider {\n background: linear-gradient(135deg, var(--color-primary), var(--color-primary-light));\n}\n\ninput:checked + .slider:before {\n transform: translateX(20px);\n}\n\n/* 选择器样式 */\n.select-wrapper {\n position: relative;\n flex-shrink: 0;\n}\n\n.select-wrapper select {\n appearance: none;\n -webkit-appearance: none;\n padding: 8px 32px 8px 12px;\n font-size: 13px;\n font-weight: 600;\n color: var(--text-primary);\n background: var(--bg-elevated);\n border: 1px solid var(--border-color);\n border-radius: 8px;\n cursor: pointer;\n min-width: 120px;\n}\n\n.select-wrapper::after {\n content: '\\\\f078';\n font-family: 'Font Awesome 6 Free';\n font-weight: 900;\n position: absolute;\n right: 8px;\n top: 50%;\n transform: translateY(-50%);\n color: var(--text-secondary);\n pointer-events: none;\n font-size: 12px;\n}\n\n/* 颜色选择器样式 */\n.color-picker-wrapper {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-shrink: 0;\n}\n\n.color-input {\n width: 40px;\n height: 40px;\n border: none;\n border-radius: 8px;\n cursor: pointer;\n background: transparent;\n padding: 2px;\n}\n\n.color-input::-webkit-color-swatch-wrapper {\n padding: 0;\n}\n\n.color-input::-webkit-color-swatch {\n border-radius: 6px;\n border: 2px solid var(--border-color);\n}\n\n.color-text-input {\n width: 100px;\n padding: 8px 10px;\n font-size: 12px;\n font-weight: 600;\n color: var(--text-primary);\n background: var(--bg-elevated);\n border: 1px solid var(--border-color);\n border-radius: 6px;\n text-transform: uppercase;\n}\n\n.transparent-btn {\n padding: 8px 12px;\n font-size: 11px;\n font-weight: 600;\n color: var(--color-primary);\n background: rgba(45, 157, 120, 0.1);\n border: 1px solid var(--color-primary);\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.2s ease;\n}\n\n.transparent-btn:hover {\n background: rgba(45, 157, 120, 0.2);\n}\n\n/* 预览区域 */\n/* 自定义样式容器 */\n.custom-style-container {\n padding: 0 16px 16px;\n}\n\n.custom-style-container textarea {\n width: 100%;\n padding: 12px;\n font-size: 13px;\n font-family: monospace;\n color: var(--text-primary);\n background: var(--bg-card);\n border: 1px solid var(--border-color);\n border-radius: 8px;\n resize: vertical;\n min-height: 100px;\n box-sizing: border-box;\n}\n\n/* 添加样式按钮 */\n.add-style-btn {\n width: 36px;\n height: 36px;\n border-radius: 50%;\n background: linear-gradient(135deg, var(--color-primary), var(--color-primary-light));\n border: none;\n color: white;\n font-size: 16px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 2px 8px rgba(45, 157, 120, 0.3);\n transition: all 0.2s ease;\n}\n\n.add-style-btn:hover {\n transform: scale(1.1);\n box-shadow: 0 4px 12px rgba(45, 157, 120, 0.4);\n}\n\n.save-style-btn {\n padding: 8px 16px;\n border-radius: 8px;\n background: linear-gradient(135deg, var(--color-accent), #f59e0b);\n border: none;\n color: white;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 6px;\n box-shadow: 0 2px 8px rgba(251, 146, 60, 0.3);\n transition: all 0.2s ease;\n}\n\n.save-style-btn:hover {\n transform: translateY(-1px);\n box-shadow: 0 4px 12px rgba(251, 146, 60, 0.4);\n}\n\n.more-styles-btn {\n padding: 8px 12px;\n border-radius: 8px;\n background: linear-gradient(135deg, #6366f1, #8b5cf6);\n border: none;\n color: white;\n font-size: 13px;\n font-weight: 600;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 6px;\n box-shadow: 0 2px 8px rgba(99, 102, 241, 0.3);\n transition: all 0.2s ease;\n}\n\n.more-styles-btn:hover {\n transform: translateY(-1px);\n box-shadow: 0 4px 12px rgba(99, 102, 241, 0.4);\n}\n\n/* 自定义样式区域 */\n.custom-style-section {\n padding: 0 16px 16px;\n}\n\n.custom-styles-container {\n margin-top: 12px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.empty-custom-styles {\n text-align: center;\n padding: 20px;\n color: var(--text-secondary);\n font-size: 13px;\n background: var(--bg-elevated);\n border-radius: 12px;\n border: 1px dashed var(--border-color);\n}\n\n/* 自定义样式卡片 */\n.custom-style-card {\n background: var(--bg-elevated);\n border-radius: 12px;\n padding: 12px;\n border: 1px solid var(--border-color);\n}\n\n.custom-style-header {\n display: flex;\n align-items: center;\n gap: 12px;\n margin-bottom: 10px;\n}\n\n.style-name-input {\n flex: 1;\n padding: 6px 10px;\n font-size: 13px;\n font-weight: 600;\n color: var(--text-primary);\n background: var(--bg-card);\n border: 1px solid var(--border-color);\n border-radius: 6px;\n}\n\n.style-actions {\n display: flex;\n gap: 6px;\n}\n\n.style-btn {\n width: 28px;\n height: 28px;\n border: none;\n border-radius: 6px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n transition: all 0.2s ease;\n}\n\n.preview-btn {\n background: rgba(45, 157, 120, 0.1);\n color: var(--color-primary);\n}\n\n.preview-btn:hover {\n background: rgba(45, 157, 120, 0.2);\n}\n\n.delete-btn {\n background: rgba(244, 67, 54, 0.1);\n color: #f44336;\n}\n\n.delete-btn:hover {\n background: rgba(244, 67, 54, 0.2);\n}\n\n.style-code-textarea {\n width: 100%;\n padding: 10px;\n font-size: 12px;\n font-family: monospace;\n color: var(--text-primary);\n background: var(--bg-card);\n border: 1px solid var(--border-color);\n border-radius: 8px;\n resize: vertical;\n min-height: 80px;\n box-sizing: border-box;\n margin-bottom: 10px;\n}\n\n.style-preview-small {\n display: flex;\n justify-content: center;\n padding: 10px;\n background: var(--bg-card);\n border-radius: 8px;\n}\n\n.preview-svg-container svg {\n width: 50px;\n height: 45px;\n}\n\n.preview-section {\n padding: 20px;\n background: var(--bg-elevated);\n border-top: 1px solid var(--border-color);\n}\n\n.preview-title {\n font-size: 14px;\n font-weight: 700;\n color: var(--text-primary);\n margin-bottom: 16px;\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.preview-title i {\n color: var(--color-primary);\n}\n\n.preview-container {\n display: flex;\n flex-wrap: wrap;\n gap: 16px;\n justify-content: center;\n align-items: center;\n min-height: 150px;\n}\n\n.preview-item {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 8px;\n padding: 12px;\n background: var(--bg-card);\n border-radius: 12px;\n border: 1px solid var(--border-color);\n}\n\n.preview-item.selected {\n border-color: var(--color-primary);\n background: rgba(45, 157, 120, 0.05);\n}\n\n.preview-item svg {\n width: 60px;\n height: 54px;\n cursor: pointer;\n transition: transform 0.2s ease;\n}\n\n.preview-item svg:hover {\n transform: scale(1.1);\n}\n\n.preview-item .preview-label {\n font-size: 11px;\n color: var(--text-secondary);\n text-align: center;\n}\n\n.preview-item.selected .preview-label {\n color: var(--color-primary);\n font-weight: 600;\n}\n\n/* 高亮文本 */\n.highlight {\n color: var(--color-primary);\n font-weight: 700;\n}\n\n.highlight-red {\n color: #ef4444;\n font-weight: 700;\n}\n\n/* Toast提示样式 */\n.toast-container {\n position: fixed;\n top: 20px;\n right: 20px;\n z-index: 1000;\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.toast {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 14px 18px;\n border-radius: 12px;\n background: var(--bg-card);\n box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);\n border: 1px solid var(--border-color);\n min-width: 200px;\n max-width: 320px;\n animation: toastSlideIn 0.3s ease-out;\n}\n\n.toast.fade-out {\n animation: toastFadeOut 0.3s ease-out forwards;\n}\n\n@keyframes toastSlideIn {\n from {\n transform: translateX(100%);\n opacity: 0;\n }\n to {\n transform: translateX(0);\n opacity: 1;\n }\n}\n\n@keyframes toastFadeOut {\n from {\n opacity: 1;\n transform: translateX(0);\n }\n to {\n opacity: 0;\n transform: translateX(20px);\n }\n}\n\n.toast-icon {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 16px;\n flex-shrink: 0;\n}\n\n.toast-icon.success {\n background: rgba(76, 175, 80, 0.1);\n color: #4CAF50;\n}\n\n.toast-icon.error {\n background: rgba(244, 67, 54, 0.1);\n color: #F44336;\n}\n\n.toast-icon.warning {\n background: rgba(255, 193, 7, 0.1);\n color: #FFC107;\n}\n\n.toast-icon.info {\n background: rgba(33, 150, 243, 0.1);\n color: #2196F3;\n}\n\n.toast-icon.loading {\n background: rgba(45, 157, 120, 0.1);\n color: var(--color-primary);\n}\n\n.toast-content {\n flex: 1;\n font-size: 14px;\n color: var(--text-primary);\n font-weight: 500;\n}\n\n\n\n@keyframes spin {\n to { transform: rotate(360deg); }\n}\n\n/* 保存按钮加载状态 */\n.save-style-btn.loading {\n opacity: 0.7;\n pointer-events: none;\n}\n\n.save-style-btn.loading i {\n display: none;\n}\n\n.save-style-btn.loading .btn-text {\n display: none;\n}\n\n\n/* 响应式 */\n@media (max-width: 600px) {\n .container {\n padding: 12px;\n }\n \n .main-card {\n border-radius: 16px;\n }\n\n .tip-alert {\n margin: 16px;\n padding: 14px 16px;\n }\n \n .setting-item {\n padding: 14px;\n }\n \n .color-picker-wrapper {\n flex-direction: column;\n align-items: flex-end;\n }\n}\n</style>\n</head>\n<body data-theme=\"light\">\n<div class=\"container\">\n <div class=\"main-card\">\n <div class=\"tip-alert\">\n <p>设置完成后请点击右上角的<span class=\"highlight-text\">√</span>应用设置</p>\n </div>\n <!-- 设置项 -->\n <div class=\"setting-item\">\n <div class=\"setting-info\">\n <div class=\"setting-title\">段评</div>\n <div class=\"setting-desc\">仅 <span class=\"highlight-red\">番茄</span>、<span class=\"highlight\">七猫</span>、<span class=\"highlight-red\">书旗</span>、<span class=\"highlight\">塔读</span>、<span class=\"highlight-red\">QQ阅读</span> 支持</div>\n </div>\n <label class=\"switch\">\n <input type=\"checkbox\" id=\"duanpingSwitch\" checked>\n <span class=\"slider\"></span>\n </label>\n </div>\n \n <div class=\"setting-item\">\n <div class=\"setting-info\">\n <div class=\"setting-title\">神评论</div>\n <div class=\"setting-desc\">需要开启段评开关才可生效,仅支持 <span class=\"highlight-red\">番茄</span>、<span class=\"highlight\">七猫</span>、<span class=\"highlight-red\">书旗</span></div>\n </div>\n <label class=\"switch\">\n <input type=\"checkbox\" id=\"shenpingSwitch\" checked>\n <span class=\"slider\"></span>\n </label>\n </div>\n \n <div class=\"setting-item\">\n <div class=\"setting-info\">\n <div class=\"setting-title\">本章说</div>\n <div class=\"setting-desc\">仅支持 <span class=\"highlight-red\">番茄</span>、<span class=\"highlight\">七猫</span>、<span class=\"highlight-red\">书旗</span></div>\n </div>\n <label class=\"switch\">\n <input type=\"checkbox\" id=\"benzhangSwitch\" checked>\n <span class=\"slider\"></span>\n </label>\n </div>\n \n <div class=\"setting-item\">\n <div class=\"setting-info\">\n <div class=\"setting-title\">气泡增大</div>\n <div class=\"setting-desc\">开启后,气泡显示会加大一些</div>\n </div>\n <label class=\"switch\">\n <input type=\"checkbox\" id=\"bubbleLargeSwitch\">\n <span class=\"slider\"></span>\n </label>\n </div>\n \n <div class=\"setting-item\">\n <div class=\"setting-info\">\n <div class=\"setting-title\">段评样式</div>\n <div class=\"setting-desc\">选择段评显示的样式风格,下方可实时预览效果</div>\n </div>\n <div class=\"select-wrapper\">\n <select id=\"duanpingStyle\">\n <option value=\"0\">起点样式</option>\n <option value=\"1\">番茄样式</option>\n <option value=\"2\">精致样式</option>\n <option value=\"3\">对话样式</option>\n <option value=\"4\">方框样式</option>\n <option value=\"5\">圆圈样式</option>\n </select>\n </div>\n </div>\n \n <div class=\"setting-item\">\n <div class=\"setting-info\">\n <div class=\"setting-title\">边框颜色</div>\n <div class=\"setting-desc\">设置段评边框颜色</div>\n </div>\n <div class=\"color-picker-wrapper\">\n <input type=\"color\" id=\"borderColor\" value=\"#A7A7A7\" class=\"color-input\">\n <input type=\"text\" id=\"borderColorText\" value=\"#A7A7A7\" class=\"color-text-input\">\n </div>\n </div>\n \n <div class=\"setting-item\">\n <div class=\"setting-info\">\n <div class=\"setting-title\">字体颜色</div>\n <div class=\"setting-desc\">设置段评字体颜色</div>\n </div>\n <div class=\"color-picker-wrapper\">\n <input type=\"color\" id=\"fontColor\" value=\"#A7A7A7\" class=\"color-input\">\n <input type=\"text\" id=\"fontColorText\" value=\"#A7A7A7\" class=\"color-text-input\">\n </div>\n </div>\n \n <div class=\"setting-item\">\n <div class=\"setting-info\">\n <div class=\"setting-title\">填充颜色</div>\n <div class=\"setting-desc\">设置段评背景填充颜色</div>\n </div>\n <div class=\"color-picker-wrapper\">\n <input type=\"color\" id=\"fillColor\" value=\"#FFFFFF\" class=\"color-input\">\n <input type=\"text\" id=\"fillColorText\" value=\"transparent\" class=\"color-text-input\">\n <button id=\"fillColorTransparent\" class=\"transparent-btn\">透明</button>\n </div>\n </div>\n \n <!-- 自定义样式管理 -->\n <div class=\"custom-style-section\">\n <div class=\"setting-item\" style=\"flex-direction: column; align-items: flex-start; gap: 12px;\">\n <div class=\"setting-info\">\n <div class=\"setting-title\">自定义样式管理</div>\n <div class=\"setting-desc\">点击下方\"+\"添加新的自定义样式,支持自定义名称</div>\n </div>\n <div style=\"display: flex; gap: 10px; align-items: center; width: 100%; justify-content: flex-end;\">\n <button id=\"moreStylesBtn\" class=\"more-styles-btn\">\n <i class=\"fas fa-external-link-alt\"></i>\n <span class=\"btn-text\">更多样式</span>\n </button>\n <button id=\"saveCustomStyleBtn\" class=\"save-style-btn\">\n <i class=\"fas fa-save\"></i>\n <span class=\"btn-text\">保存自定义</span>\n </button>\n <button id=\"addCustomStyleBtn\" class=\"add-style-btn\">\n <i class=\"fas fa-plus\"></i>\n </button>\n </div>\n </div>\n \n <div id=\"customStylesContainer\" class=\"custom-styles-container\">\n </div>\n </div>\n \n <!-- 实时预览区域 -->\n <div class=\"preview-section\">\n <div class=\"preview-title\">\n <i class=\"fas fa-eye\"></i>\n <span>实时预览</span>\n </div>\n <div id=\"previewContainer\" class=\"preview-container\"></div>\n </div>\n </div>\n \n <!-- 隐藏的设置数据 -->\n <div id=\"hiddenSettings\" style=\"display: none;\">\n <div id=\"settingsDiv\">\n <span id=\"duanpingSwitchValue\">${(data && data.段评开关) || 'true'}</span>\n <span id=\"shenpingSwitchValue\">${(data && data.神评论开关) || 'true'}</span>\n <span id=\"benzhangSwitchValue\">${(data && data.本章说开关) || 'true'}</span>\n <span id=\"bubbleLargeSwitchValue\">${(data && data.气泡增大) || 'false'}</span>\n <span id=\"duanpingStyleValue\">${(data && data.段评样式) || '0'}</span>\n <span id=\"borderColorValue\">${(data && data.段评边框颜色) || '#A7A7A7'}</span>\n <span id=\"fontColorValue\">${(data && data.段评字体颜色) || '#A7A7A7'}</span>\n <span id=\"fillColorValue\">${(data && data.段评填充颜色) || 'transparent'}</span>\n <span id=\"baseUrlValue\">${base_url}</span>\n <span id=\"qttokenValue\">${qttoken}</span>\n </div>\n <textarea id=\"customStylesValue\" style=\"display: none;\">{}</textarea>\n </div>\n \n <!-- Toast提示容器 -->\n <div id=\"toastContainer\" class=\"toast-container\"></div>\n</div>\n\n<script>\n// SVG模板数据\nconst SVG_TEMPLATES = ${JSON.stringify(svgs)};\nconst STYLE_NAMES = ['起点样式', '番茄样式', '精致样式', '对话样式', '方框样式', '圆圈样式'];\n\n// Toast提示函数\nfunction showToast(message, type = 'info', duration = 2500) {\n const container = document.getElementById('toastContainer');\n const toast = document.createElement('div');\n toast.className = 'toast';\n \n const icons = {\n success: '<i class=\"fas fa-check\"></i>',\n error: '<i class=\"fas fa-times\"></i>',\n warning: '<i class=\"fas fa-exclamation-triangle\"></i>',\n info: '<i class=\"fas fa-info-circle\"></i>',\n loading: '<span class=\"loading-spinner\"></span>'\n };\n \n toast.innerHTML = \\`\n <div class=\"toast-icon \\${type}\">\\${icons[type] || icons.info}</div>\n <div class=\"toast-content\">\\${message}</div>\n \\`;\n \n container.appendChild(toast);\n \n setTimeout(() => {\n toast.classList.add('fade-out');\n setTimeout(() => {\n container.removeChild(toast);\n }, 300);\n }, duration);\n}\n\nfunction showAlert(message) {\n showToast(message, 'info');\n}\n\nfunction showSuccess(message) {\n showToast(message, 'success');\n}\n\nfunction showError(message) {\n showToast(message, 'error');\n}\n\nfunction showConfirm(message, onConfirm) {\n const confirm = window.confirm(message);\n if (confirm && onConfirm) {\n onConfirm();\n }\n}\n\n// 当前设置\nlet settings = {\n '段评开关': '${(data && data.段评开关) || 'true'}',\n '神评论开关': '${(data && data.神评论开关) || 'true'}',\n '本章说开关': '${(data && data.本章说开关) || 'true'}',\n '气泡增大': '${(data && data.气泡增大) || 'false'}',\n '段评样式': '${(data && data.段评样式) || '0'}',\n '段评边框颜色': '${(data && data.段评边框颜色) || '#A7A7A7'}',\n '段评字体颜色': '${(data && data.段评字体颜色) || '#A7A7A7'}',\n '段评填充颜色': '${(data && data.段评填充颜色) || 'transparent'}'\n};\n\n// 自定义样式对象 {\"自定义样式1\": \"<svg>...</svg>\", ...}\nlet customStyles = {};\nfunction initCustomStyles() {\n customStyles = {};\n}\nfunction fetchCustomStyles() {\n const baseUrl = document.getElementById('baseUrlValue').textContent;\n const qttoken = document.getElementById('qttokenValue').textContent;\n if (!baseUrl || !qttoken) {\n return;\n }\n const url = baseUrl + '/user_settings?set_type=legado_para&qttoken=' + encodeURIComponent(qttoken);\n fetch(url, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json'\n }\n }).then(response => response.json())\n .then(data => {\n if (data && data.data && data.data['自定义段评样式']) {\n customStyles = data.data['自定义段评样式'];\n }\n updateHiddenSettings();\n renderCustomStyles();\n updateStyleSelector();\n renderPreview();\n })\n .catch(err => {\n console.error('获取自定义段评样式失败:', err);\n });\n}\nfunction saveCustomStyles() {\n const saveBtn = document.getElementById('saveCustomStyleBtn');\n const baseUrl = document.getElementById('baseUrlValue').textContent;\n const qttoken = document.getElementById('qttokenValue').textContent;\n if (!baseUrl || !qttoken) {\n showAlert('无法获取配置信息');\n return;\n } \n const url = baseUrl + '/user_settings?qttoken=' + encodeURIComponent(qttoken);\n const body = {\n legado_para: {\n '段评开关': settings['段评开关'],\n '神评论开关': settings['神评论开关'],\n '本章说开关': settings['本章说开关'],\n '气泡增大': settings['气泡增大'],\n '段评样式': settings['段评样式'],\n '段评边框颜色': settings['段评边框颜色'],\n '段评字体颜色': settings['段评字体颜色'],\n '段评填充颜色': settings['段评填充颜色'],\n '自定义段评样式': customStyles\n }\n };\n fetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json'\n },\n body: JSON.stringify(body)\n }).then(response => response.json())\n .then(data => {\n saveBtn.classList.remove('loading');\n if (data && data.code === 0) {\n showSuccess('保存成功');\n } else {\n showError('保存失败: ' + (data && data.message || '未知错误'));\n }\n })\n .catch(err => {\n console.error('保存自定义段评样式失败:', err);\n showError('保存失败');\n });\n}\n\n// HTML转义函数\nfunction htmlEscape(str) {\n if (!str) return str;\n return str.replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#039;');\n}\n\n// HTML反转义函数\nfunction htmlUnescape(str) {\n if (!str) return str;\n return str.replace(/&amp;/g, '&')\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&quot;/g, '\"')\n .replace(/&#039;/g, \"'\");\n}\n\n// 更新隐藏设置\nfunction updateHiddenSettings() {\n document.getElementById('duanpingSwitchValue').textContent = settings['段评开关'];\n document.getElementById('shenpingSwitchValue').textContent = settings['神评论开关'];\n document.getElementById('benzhangSwitchValue').textContent = settings['本章说开关'];\n document.getElementById('bubbleLargeSwitchValue').textContent = settings['气泡增大'];\n document.getElementById('duanpingStyleValue').textContent = settings['段评样式'];\n document.getElementById('borderColorValue').textContent = settings['段评边框颜色'];\n document.getElementById('fontColorValue').textContent = settings['段评字体颜色'];\n document.getElementById('fillColorValue').textContent = settings['段评填充颜色'];\n document.getElementById('customStylesValue').textContent = htmlEscape(JSON.stringify(customStyles));\n}\n\n// 获取下一个自定义样式名称\nfunction getNextStyleName() {\n let count = 1;\n while (customStyles['自定义样式' + count]) {\n count++;\n }\n return '自定义样式' + count;\n}\n\n// 添加自定义样式\nfunction addCustomStyle() {\n const name = getNextStyleName();\n customStyles[name] = '<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"100\" height=\"90\" viewBox=\"0 0 25 17\" fill=\"none\"><rect x=\"0.5\" y=\"0.5\" width=\"24\" height=\"16\" rx=\"2.5\" ry=\"2.5\" stroke=\"$color\" style=\"stroke-opacity: 0.5; fill: $tccolor; fill-opacity: 0.5;\"/><text x=\"12.5\" y=\"12\" text-anchor=\"middle\" font-size=\"10\" fill=\"$fontcolor\" font-family=\"Arial, sans-serif\">$displayText</text></svg>';\n renderCustomStyles();\n updateHiddenSettings();\n updateStyleSelector();\n renderPreview();\n}\n\n// 获取选中的样式名称\nfunction getSelectedStyleName() {\n const styleValue = settings['段评样式'];\n // 如果是数字,返回默认样式名称\n if (!isNaN(styleValue)) {\n return STYLE_NAMES[parseInt(styleValue)] || styleValue;\n }\n // 如果是自定义样式,返回自定义名称\n return styleValue;\n}\n\n// 删除自定义样式\nfunction deleteCustomStyle(name) {\n showConfirm('确定删除 \"' + name + '\" 吗?', function() {\n delete customStyles[name];\n if (settings['段评样式'] === name) {\n settings['段评样式'] = '0';\n }\n renderCustomStyles();\n updateHiddenSettings();\n updateStyleSelector();\n renderPreview();\n });\n}\n\n// 更新自定义样式\nfunction updateCustomStyle(name, value) {\n customStyles[name] = value;\n updateHiddenSettings();\n renderPreview();\n}\n\n// 重命名自定义样式\nfunction renameCustomStyle(oldName, newName) {\n if (!newName || newName.trim() == '') return;\n if (customStyles[newName] && newName != oldName) {\n showAlert('名称已存在');\n return;\n }\n customStyles[newName] = customStyles[oldName];\n delete customStyles[oldName];\n // 如果当前选中的是旧名称,更新为新名称\n if (settings['段评样式'] === oldName) {\n settings['段评样式'] = newName;\n }\n renderCustomStyles();\n updateHiddenSettings();\n updateStyleSelector();\n renderPreview();\n}\n\n// 渲染自定义样式列表\nfunction renderCustomStyles() {\n const container = document.getElementById('customStylesContainer');\n container.innerHTML = '';\n \n if (Object.keys(customStyles).length === 0) {\n container.innerHTML = '<div class=\"empty-custom-styles\">暂无自定义样式,点击上方\"+\"添加</div>';\n return;\n }\n \n Object.keys(customStyles).forEach((name, index) => {\n const styleCard = document.createElement('div');\n styleCard.className = 'custom-style-card';\n // 转义SVG代码中的特殊字符\n const escapedSvg = customStyles[name]\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#039;');\n \n styleCard.innerHTML = \\`\n <div class=\"custom-style-header\">\n <input type=\"text\" class=\"style-name-input\" value=\"\\${name}\" data-original=\"\\${name}\">\n <div class=\"style-actions\">\n <button class=\"style-btn preview-btn\" title=\"预览\">\n <i class=\"fas fa-eye\"></i>\n </button>\n <button class=\"style-btn delete-btn\" title=\"删除\">\n <i class=\"fas fa-trash\"></i>\n </button>\n </div>\n </div>\n <textarea class=\"style-code-textarea\" rows=\"4\" placeholder=\"输入SVG代码...\">\\${escapedSvg}</textarea>\n <div class=\"style-preview-small\">\n <div class=\"preview-svg-container\"></div>\n </div>\n \\`;\n \n // 绑定事件\n const nameInput = styleCard.querySelector('.style-name-input');\n const codeTextarea = styleCard.querySelector('.style-code-textarea');\n const deleteBtn = styleCard.querySelector('.delete-btn');\n const previewBtn = styleCard.querySelector('.preview-btn');\n \n nameInput.addEventListener('change', () => {\n const newName = nameInput.value.trim();\n const originalName = nameInput.dataset.original;\n if (newName != originalName) {\n renameCustomStyle(originalName, newName);\n }\n });\n \n codeTextarea.addEventListener('input', () => {\n updateCustomStyle(name, codeTextarea.value);\n // 更新小预览\n updateSmallPreview(styleCard.querySelector('.preview-svg-container'), codeTextarea.value);\n });\n \n deleteBtn.addEventListener('click', () => deleteCustomStyle(name));\n \n previewBtn.addEventListener('click', () => {\n // 设置为当前选中样式(使用自定义名称)\n settings['段评样式'] = name;\n document.getElementById('duanpingStyle').value = name;\n updateHiddenSettings();\n renderPreview();\n });\n \n // 初始化小预览\n updateSmallPreview(styleCard.querySelector('.preview-svg-container'), customStyles[name]);\n \n container.appendChild(styleCard);\n });\n}\n\n// 更新小预览\nfunction updateSmallPreview(container, svgCode) {\n try {\n const preview = svgCode\n .split('$color').join(settings['段评边框颜色'])\n .split('$fontcolor').join(settings['段评字体颜色'])\n .split('$tccolor').join(settings['段评填充颜色'])\n .split('$displayText').join('99+');\n container.innerHTML = preview;\n } catch (e) {\n container.innerHTML = '<span style=\"color: #999; font-size: 12px;\">解析失败</span>';\n }\n}\n\n// 渲染预览\nfunction renderPreview() {\n const container = document.getElementById('previewContainer');\n container.innerHTML = '';\n \n // 添加默认样式\n SVG_TEMPLATES.forEach((svgTemplate, index) => {\n const svgContent = svgTemplate\n .split('$color').join(settings['段评边框颜色'])\n .split('$fontcolor').join(settings['段评字体颜色'])\n .split('$tccolor').join(settings['段评填充颜色'])\n .split('$displayText').join('99+');\n \n const item = document.createElement('div');\n item.className = \\`preview-item \\${settings['段评样式'] === String(index) ? 'selected' : ''}\\`;\n item.innerHTML = \\`\n \\${svgContent}\n <div class=\"preview-label\">\\${STYLE_NAMES[index]}</div>\n \\`;\n \n item.addEventListener('click', () => {\n settings['段评样式'] = String(index);\n document.getElementById('duanpingStyle').value = String(index);\n updateHiddenSettings();\n renderPreview();\n });\n \n container.appendChild(item);\n });\n \n // 添加自定义样式预览\n Object.keys(customStyles).forEach((name) => {\n const svgCode = customStyles[name];\n let svgContent;\n try {\n svgContent = svgCode\n .split('$color').join(settings['段评边框颜色'])\n .split('$fontcolor').join(settings['段评字体颜色'])\n .split('$tccolor').join(settings['段评填充颜色'])\n .split('$displayText').join('99+');\n } catch (e) {\n svgContent = '<svg width=\"60\" height=\"54\" viewBox=\"0 0 60 54\"><text x=\"30\" y=\"27\" text-anchor=\"middle\" fill=\"#999\" font-size=\"10\">解析失败</text></svg>';\n }\n \n const item = document.createElement('div');\n item.className = \\`preview-item \\${settings['段评样式'] === name ? 'selected' : ''}\\`;\n item.innerHTML = \\`\n \\${svgContent}\n <div class=\"preview-label custom-label\">\\${name}</div>\n \\`;\n \n item.addEventListener('click', () => {\n settings['段评样式'] = name;\n document.getElementById('duanpingStyle').value = name;\n updateHiddenSettings();\n renderPreview();\n });\n \n container.appendChild(item);\n });\n}\n\n// 初始化事件监听\nfunction setupEventListeners() {\n // 段评开关\n document.getElementById('duanpingSwitch').addEventListener('change', (e) => {\n settings['段评开关'] = e.target.checked ? 'true' : 'false';\n updateHiddenSettings();\n });\n \n // 神评论开关\n document.getElementById('shenpingSwitch').addEventListener('change', (e) => {\n settings['神评论开关'] = e.target.checked ? 'true' : 'false';\n updateHiddenSettings();\n });\n \n // 本章说开关\n document.getElementById('benzhangSwitch').addEventListener('change', (e) => {\n settings['本章说开关'] = e.target.checked ? 'true' : 'false';\n updateHiddenSettings();\n });\n \n // 气泡增大开关\n document.getElementById('bubbleLargeSwitch').addEventListener('change', (e) => {\n settings['气泡增大'] = e.target.checked ? 'true' : 'false';\n updateHiddenSettings();\n });\n \n // 段评样式选择\n document.getElementById('duanpingStyle').addEventListener('change', (e) => {\n settings['段评样式'] = e.target.value;\n updateHiddenSettings();\n renderPreview();\n });\n \n // 边框颜色选择器\n document.getElementById('borderColor').addEventListener('input', (e) => {\n const color = e.target.value;\n settings['段评边框颜色'] = color;\n document.getElementById('borderColorText').value = color;\n updateHiddenSettings();\n renderPreview();\n });\n \n // 边框颜色文本输入\n document.getElementById('borderColorText').addEventListener('input', (e) => {\n const color = e.target.value;\n if (/^#[0-9A-Fa-f]{6}$/.test(color) || /^#[0-9A-Fa-f]{3}$/.test(color)) {\n settings['段评边框颜色'] = color;\n document.getElementById('borderColor').value = color;\n updateHiddenSettings();\n renderPreview();\n }\n });\n \n // 字体颜色选择器\n document.getElementById('fontColor').addEventListener('input', (e) => {\n const color = e.target.value;\n settings['段评字体颜色'] = color;\n document.getElementById('fontColorText').value = color;\n updateHiddenSettings();\n renderPreview();\n });\n \n // 字体颜色文本输入\n document.getElementById('fontColorText').addEventListener('input', (e) => {\n const color = e.target.value;\n if (/^#[0-9A-Fa-f]{6}$/.test(color) || /^#[0-9A-Fa-f]{3}$/.test(color)) {\n settings['段评字体颜色'] = color;\n document.getElementById('fontColor').value = color;\n updateHiddenSettings();\n renderPreview();\n }\n });\n \n // 填充颜色选择器\n document.getElementById('fillColor').addEventListener('input', (e) => {\n const color = e.target.value;\n settings['段评填充颜色'] = color;\n document.getElementById('fillColorText').value = color;\n updateHiddenSettings();\n renderPreview();\n });\n \n // 填充颜色文本输入\n document.getElementById('fillColorText').addEventListener('input', (e) => {\n const color = e.target.value;\n if (color.toLowerCase() == 'transparent' || /^#[0-9A-Fa-f]{6}$/.test(color) || /^#[0-9A-Fa-f]{3}$/.test(color)) {\n settings['段评填充颜色'] = color;\n if (color.toLowerCase() != 'transparent') {\n document.getElementById('fillColor').value = color;\n }\n updateHiddenSettings();\n renderPreview();\n }\n });\n \n // 透明按钮\n document.getElementById('fillColorTransparent').addEventListener('click', () => {\n settings['段评填充颜色'] = 'transparent';\n document.getElementById('fillColorText').value = 'transparent';\n document.getElementById('fillColor').value = '#FFFFFF';\n updateHiddenSettings();\n renderPreview();\n });\n \n // 添加自定义样式按钮\n document.getElementById('addCustomStyleBtn').addEventListener('click', addCustomStyle);\n \n // 保存自定义样式按钮\n document.getElementById('saveCustomStyleBtn').addEventListener('click', saveCustomStyles);\n \n // 更多样式按钮\n document.getElementById('moreStylesBtn').addEventListener('click', () => {\n const baseUrl = document.getElementById('baseUrlValue').textContent;\n if (baseUrl) {\n window.open(baseUrl + '/user?tab=3&btype=1', '_blank');\n }\n });\n}\n\n// 初始化UI\nfunction initUI() {\n // 设置段评开关\n document.getElementById('duanpingSwitch').checked = settings['段评开关'] != 'false';\n \n // 设置神评论开关\n document.getElementById('shenpingSwitch').checked = settings['神评论开关'] != 'false';\n \n // 设置本章说开关\n document.getElementById('benzhangSwitch').checked = settings['本章说开关'] != 'false';\n \n // 设置气泡增大开关\n document.getElementById('bubbleLargeSwitch').checked = settings['气泡增大'] == 'true';\n \n // 设置段评样式\n document.getElementById('duanpingStyle').value = settings['段评样式'];\n \n // 设置边框颜色\n const borderColorValue = settings['段评边框颜色'];\n document.getElementById('borderColor').value = borderColorValue === 'transparent' ? '#A7A7A7' : borderColorValue;\n document.getElementById('borderColorText').value = borderColorValue;\n \n // 设置字体颜色\n const fontColorValue = settings['段评字体颜色'];\n document.getElementById('fontColor').value = fontColorValue === 'transparent' ? '#A7A7A7' : fontColorValue;\n document.getElementById('fontColorText').value = fontColorValue;\n \n // 设置填充颜色\n const fillColorValue = settings['段评填充颜色'];\n document.getElementById('fillColor').value = fillColorValue === 'transparent' ? '#FFFFFF' : fillColorValue;\n document.getElementById('fillColorText').value = fillColorValue;\n \n // 渲染自定义样式列表\n renderCustomStyles();\n \n // 更新段评样式选择器,添加自定义样式选项\n updateStyleSelector();\n}\n\n// 更新段评样式选择器\nfunction updateStyleSelector() {\n const select = document.getElementById('duanpingStyle');\n const currentValue = settings['段评样式'];\n \n // 移除所有非默认选项\n while (select.options.length > 6) {\n select.remove(6);\n }\n \n // 添加自定义样式选项\n Object.keys(customStyles).forEach((name) => {\n const option = document.createElement('option');\n option.value = name;\n option.textContent = name;\n select.appendChild(option);\n });\n \n // 设置当前选中值\n select.value = currentValue;\n}\n\n// 页面加载完成后初始化\ninitCustomStyles();\ninitUI();\nsetupEventListeners();\nrenderPreview();\nfetchCustomStyles();\n</script>\n</body>\n</html>\n `;\n let body;\n try {\n body = java.startBrowserAwait(`data:text/html;base64,${java.base64Encode(html)}`, '光遇段评设置', false).body();\n } catch (e) {\n if (islyc == \"安卓\") {\n java.longToast(\"\\n当前软件版本过低,请更新软件,或者使用底部备用设置方案~\");\n } else {\n java.longToast(\"\\n请点击页面右上角√才能设置成功哦~\");\n }\n return;\n }\n \n let settings = {};\n try {\n const getSpanText = (spanId) => {\n const regex = new RegExp('id=\"' + spanId + '\"\\s*>\\s*([^<]*?)\\s*<\\/span>');\n const match = body.match(regex);\n return match ? match[1].trim() : \"\";\n };\n const getTextareaValue = (textareaId) => {\n const regex = new RegExp('<textarea[^>]*id=\"' + textareaId + '\"[^>]*>([\\\\s\\\\S]*?)<\\/textarea>', 'i');\n const match = body.match(regex);\n return match ? match[1].trim() : \"\";\n };\n const htmlDecode = (str) => {\n if (!str) return str;\n return str.replace(/&amp;/g, '&')\n .replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>')\n .replace(/&quot;/g, '\"')\n .replace(/&#039;/g, \"'\");\n };\n\n const customStylesValue = getTextareaValue('customStylesValue') || '{}';\n const decodedStyles = htmlDecode(customStylesValue);\n\n settings = {\n '段评开关': getSpanText('duanpingSwitchValue'),\n '神评论开关': getSpanText('shenpingSwitchValue'),\n '本章说开关': getSpanText('benzhangSwitchValue'),\n '气泡增大': getSpanText('bubbleLargeSwitchValue'),\n '段评样式': getSpanText('duanpingStyleValue') || '0',\n '段评边框颜色': getSpanText('borderColorValue') || '#A7A7A7',\n '段评字体颜色': getSpanText('fontColorValue') || '#A7A7A7',\n '段评填充颜色': getSpanText('fillColorValue') || 'transparent',\n '自定义段评样式': JSON.parse(decodedStyles),\n };\n const settingsText = Object.keys(settings).map(key => `${key}: ${settings[key]}`).join('\\n');\n java.longToast(\"\\n\" + settingsText);\n } catch (e) {\n settings = {\n '段评开关': 'true',\n '神评论开关': 'true',\n '本章说开关': 'true',\n '气泡增大': 'false',\n '段评样式': '0',\n '段评边框颜色': '#A7A7A7',\n '段评字体颜色': '#A7A7A7',\n '段评填充颜色': 'transparent',\n '自定义段评样式': {},\n };\n }\n this.setVariable(\"段评设置\", settings, false);\n}\n\n\n// 更多设置\nfunction getHtmlSettings() {\n const {\n java\n } = this;\n let data = this.getVariable('更多设置');\n let config = this.getVariable('云端配置');\n if (!config) {\n try {\n this.getCloudSettings();\n config = this.getVariable('云端配置');\n } catch {}\n }\n let html = `\n <!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">\n<title>光遇小说 - 管理中心</title>\n<link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\">\n<style>\n* {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n -webkit-tap-highlight-color: transparent;\n}\n\n:root {\n --color-primary: #2D9D78;\n --color-primary-light: #3DB893;\n --color-primary-dark: #1E6F55;\n --color-accent: #FFB74D;\n --color-success: #4CAF50;\n --color-error: #F44336;\n --color-warning: #FFC107;\n --color-info: #2196F3;\n \n --bg-dark: #f0f3f8;\n --bg-darker: #e8edf5;\n --bg-card: rgba(255, 255, 255, 0.98);\n --bg-elevated: #fafbfc;\n \n --text-primary: #1a1d23;\n --text-secondary: #6b7280;\n --text-tertiary: #9ca3af;\n \n --border-color: rgba(0, 0, 0, 0.06);\n}\n\n[data-theme=\"dark\"] {\n --bg-dark: #0A1628;\n --bg-darker: #050B14;\n --bg-card: #142235;\n --bg-elevated: #1A2B42;\n \n --text-primary: #FFFFFF;\n --text-secondary: rgba(255, 255, 255, 0.7);\n --text-tertiary: rgba(255, 255, 255, 0.5);\n \n --border-color: rgba(255, 255, 255, 0.08);\n}\n\nhtml, body {\n width: 100%;\n min-height: 100vh;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n font-size: 16px;\n color: var(--text-primary);\n background: linear-gradient(135deg, var(--bg-darker) 0%, var(--bg-dark) 100%);\n background-attachment: fixed;\n overflow-x: hidden;\n transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.container {\n padding: 24px 20px;\n max-width: 820px;\n margin: 0 auto;\n animation: fadeIn 0.5s ease-out;\n}\n\n@keyframes fadeIn {\n from { opacity: 0; transform: translateY(10px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n.main-card {\n background: var(--bg-card);\n border-radius: 24px;\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08), 0 2px 8px rgba(0, 0, 0, 0.04), inset 0 1px 0 rgba(255, 255, 255, 0.8);\n border: 1px solid var(--border-color);\n overflow: hidden;\n backdrop-filter: blur(20px);\n}\n\n/* 主Tab切换 */\n.main-tabs {\n display: flex;\n background: linear-gradient(180deg, var(--bg-elevated) 0%, rgba(255, 255, 255, 0.5) 100%);\n border-bottom: 1px solid var(--border-color);\n position: relative;\n}\n\n.main-tabs::after {\n content: '';\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 1px;\n background: linear-gradient(90deg, transparent, var(--border-color), transparent);\n}\n\n.main-tab {\n flex: 1;\n padding: 20px 24px;\n text-align: center;\n font-size: 15px;\n font-weight: 700;\n color: var(--text-secondary);\n cursor: pointer;\n transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);\n border-bottom: 3px solid transparent;\n position: relative;\n overflow: hidden;\n}\n\n.main-tab::before {\n content: '';\n position: absolute;\n top: 0;\n left: 50%;\n transform: translateX(-50%) scaleX(0);\n width: 60%;\n height: 3px;\n background: linear-gradient(90deg, var(--color-primary), var(--color-primary-light));\n transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);\n border-radius: 0 0 3px 3px;\n}\n\n.main-tab.active {\n color: var(--color-primary);\n background: linear-gradient(180deg, rgba(45, 157, 120, 0.06) 0%, transparent 100%);\n}\n\n.main-tab.active::before {\n transform: translateX(-50%) scaleX(1);\n}\n\n.main-tab:hover {\n color: var(--color-primary);\n background: rgba(45, 157, 120, 0.04);\n}\n\n.tab-content {\n padding: 20px;\n display: none;\n animation: slideIn 0.3s ease-out;\n}\n\n.tab-content.active {\n display: block;\n}\n\n@keyframes slideIn {\n from { opacity: 0; transform: translateY(8px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n/* 来源选择子Tab */\n.source-subtabs {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n gap: 10px;\n margin-bottom: 20px;\n}\n\n.source-subtab {\n padding: 14px 10px;\n text-align: center;\n font-size: 12px;\n font-weight: 600;\n color: var(--text-secondary);\n background: var(--bg-elevated);\n border-radius: 14px;\n cursor: pointer;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n border: 2px solid transparent;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 6px;\n}\n\n.source-subtab i {\n font-size: 18px;\n transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.source-subtab.novel.active {\n color: white;\n background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);\n border-color: rgba(239, 68, 68, 0.5);\n box-shadow: 0 8px 24px rgba(239, 68, 68, 0.35);\n transform: translateY(-2px);\n}\n\n.source-subtab.audio.active {\n color: white;\n background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);\n border-color: rgba(59, 130, 246, 0.5);\n box-shadow: 0 8px 24px rgba(59, 130, 246, 0.35);\n transform: translateY(-2px);\n}\n\n.source-subtab.comic.active {\n color: white;\n background: linear-gradient(135deg, #a855f7 0%, #9333ea 100%);\n border-color: rgba(168, 85, 247, 0.5);\n box-shadow: 0 8px 24px rgba(168, 85, 247, 0.35);\n transform: translateY(-2px);\n}\n\n.source-subtab.drama.active {\n color: white;\n background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);\n border-color: rgba(249, 115, 22, 0.5);\n box-shadow: 0 8px 24px rgba(249, 115, 22, 0.35);\n transform: translateY(-2px);\n}\n\n.source-subtab:hover:not(.active) {\n border-color: var(--color-primary);\n background: rgba(45, 157, 120, 0.06);\n transform: translateY(-1px);\n}\n\n.source-subtab.active i {\n transform: scale(1.1);\n}\n\n/* 来源选项 */\n.source-options {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(125px, 1fr));\n gap: 12px;\n}\n\n.source-option {\n padding: 14px 16px;\n background: var(--bg-elevated);\n border-radius: 12px;\n text-align: center;\n font-size: 13px;\n font-weight: 600;\n color: var(--text-secondary);\n cursor: pointer;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n border: 2px solid transparent;\n}\n\n.source-option:hover {\n border-color: var(--color-primary);\n background: rgba(45, 157, 120, 0.05);\n transform: translateY(-2px);\n}\n\n.source-option.selected {\n background: linear-gradient(135deg, rgba(45, 157, 120, 0.18), rgba(61, 184, 147, 0.08));\n color: var(--color-primary);\n border-color: var(--color-primary);\n box-shadow: 0 4px 16px rgba(45, 157, 120, 0.2);\n transform: translateY(-2px);\n}\n\n.source-option.all {\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n box-shadow: 0 4px 16px rgba(102, 126, 234, 0.3);\n}\n\n.source-option.all.selected {\n background: linear-gradient(135deg, #764ba2 0%, #667eea 100%);\n box-shadow: 0 6px 24px rgba(102, 126, 234, 0.45);\n transform: translateY(-3px);\n}\n\n.source-option.all:hover {\n border-color: transparent;\n transform: translateY(-3px);\n}\n\n/* 选中状态提示 */\n.selected-info {\n margin-top: 20px;\n padding: 16px 18px;\n background: var(--bg-elevated);\n border-radius: 12px;\n font-size: 13px;\n color: var(--text-secondary);\n border: 1px solid var(--border-color);\n}\n\n.selected-info span {\n color: var(--color-primary);\n font-weight: 700;\n}\n\n/* 设置项样式 */\n.setting-item {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 20px;\n background: var(--bg-elevated);\n border-radius: 16px;\n margin-bottom: 12px;\n border: 1px solid var(--border-color);\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n position: relative;\n overflow: hidden;\n}\n\n\n.setting-item:hover::before {\n opacity: 1;\n}\n\n.setting-info {\n flex: 1;\n min-width: 0;\n}\n\n.setting-title {\n font-size: 16px;\n font-weight: 700;\n color: var(--text-primary);\n margin-bottom: 6px;\n}\n\n.setting-desc {\n font-size: 13px;\n color: var(--text-secondary);\n line-height: 1.5;\n}\n\n/* 开关样式 */\n.switch {\n position: relative;\n width: 54px;\n height: 30px;\n flex-shrink: 0;\n}\n\n.switch input {\n opacity: 0;\n width: 0;\n height: 0;\n}\n\n.slider {\n position: absolute;\n cursor: pointer;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: linear-gradient(135deg, #e5e7eb 0%, #d1d5db 100%);\n transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);\n border-radius: 30px;\n box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1), inset 0 -2px 4px rgba(0, 0, 0, 0.05);\n}\n\n.slider:before {\n position: absolute;\n content: \"\";\n height: 24px;\n width: 24px;\n left: 3px;\n bottom: 3px;\n background: linear-gradient(135deg, #ffffff 0%, #f3f4f6 100%);\n transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);\n border-radius: 50%;\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.1);\n}\n\ninput:checked + .slider {\n background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-light) 100%);\n box-shadow: 0 4px 16px rgba(45, 157, 120, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.2);\n}\n\ninput:checked + .slider:before {\n transform: translateX(24px);\n background: linear-gradient(135deg, #ffffff 0%, #fefefe 100%);\n box-shadow: 0 3px 8px rgba(0, 0, 0, 0.2), 0 1px 2px rgba(0, 0, 0, 0.1);\n}\n\n/* 关键词高亮样式 */\n.highlight {\n color: var(--color-primary);\n font-weight: 700;\n position: relative;\n}\n\n.highlight::after {\n content: '';\n position: absolute;\n bottom: -2px;\n left: 0;\n width: 100%;\n height: 2px;\n background: linear-gradient(90deg, var(--color-primary), var(--color-primary-light));\n border-radius: 2px;\n}\n\n.highlight-orange {\n color: #f97316;\n font-weight: 700;\n}\n\n.highlight-blue {\n color: #3b82f6;\n font-weight: 700;\n}\n\n.highlight-purple {\n color: #a855f7;\n font-weight: 700;\n}\n\n.highlight-red {\n color: #ef4444;\n font-weight: 700;\n}\n\n/* 选择器样式 */\n.select-wrapper {\n position: relative;\n flex-shrink: 0;\n}\n\n.select-wrapper select {\n appearance: none;\n -webkit-appearance: none;\n padding: 10px 34px 10px 14px;\n font-size: 13px;\n font-weight: 600;\n color: var(--text-primary);\n background: var(--bg-card);\n border: 2px solid var(--border-color);\n border-radius: 10px;\n cursor: pointer;\n min-width: 110px;\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.select-wrapper select:hover {\n border-color: var(--color-primary);\n}\n\n.select-wrapper select:focus {\n outline: none;\n border-color: var(--color-primary);\n box-shadow: 0 0 0 3px rgba(45, 157, 120, 0.1);\n}\n\n.select-wrapper::after {\n content: '\\\\f078';\n font-family: 'Font Awesome 6 Free';\n font-weight: 900;\n position: absolute;\n right: 10px;\n top: 50%;\n transform: translateY(-50%);\n color: var(--text-secondary);\n pointer-events: none;\n font-size: 13px;\n transition: transform 0.3s ease;\n}\n\n.select-wrapper:hover::after {\n transform: translateY(-50%) rotate(180deg);\n}\n\n/* 提示文案样式 */\n.tip-alert {\n background: linear-gradient(135deg, rgba(255, 193, 7, 0.12) 0%, rgba(255, 152, 0, 0.08) 100%);\n border: 1px solid rgba(255, 193, 7, 0.25);\n border-radius: 16px;\n padding: 16px 18px;\n margin: 20px;\n display: flex;\n align-items: flex-start;\n gap: 14px;\n box-shadow: 0 4px 16px rgba(255, 193, 7, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.8);\n position: relative;\n overflow: hidden;\n}\n\n.tip-alert::before {\n content: '';\n position: absolute;\n top: -50%;\n right: -20%;\n width: 100px;\n height: 100px;\n background: rgba(255, 193, 7, 0.1);\n border-radius: 50%;\n}\n\n.tip-alert i {\n color: #f97316;\n font-size: 22px;\n flex-shrink: 0;\n margin-top: 2px;\n position: relative;\n z-index: 1;\n}\n\n.tip-alert p {\n font-size: 13px;\n color: var(--text-secondary);\n line-height: 1.6;\n margin: 0;\n position: relative;\n z-index: 1;\n}\n\n.tip-alert .highlight-text {\n color: #ea580c;\n font-weight: 700;\n}\n\n/* 响应式 */\n@media (max-width: 600px) {\n .container {\n padding: 16px 12px;\n }\n \n .main-card {\n border-radius: 20px;\n }\n \n .tab-content {\n padding: 16px;\n }\n \n .tip-alert {\n margin: 16px;\n padding: 14px 16px;\n }\n \n .source-subtabs {\n grid-template-columns: repeat(2, 1fr);\n gap: 8px;\n }\n \n .source-options {\n grid-template-columns: repeat(auto-fill, minmax(105px, 1fr));\n gap: 10px;\n }\n \n .setting-item {\n padding: 16px;\n }\n \n .setting-title {\n font-size: 15px;\n }\n \n .setting-desc {\n font-size: 12px;\n }\n}\n\n::-webkit-scrollbar {\n width: 6px;\n height: 6px;\n}\n\n::-webkit-scrollbar-track {\n background: var(--bg-elevated);\n border-radius: 3px;\n}\n\n::-webkit-scrollbar-thumb {\n background: var(--text-tertiary);\n border-radius: 3px;\n}\n\n::-webkit-scrollbar-thumb:hover {\n background: var(--text-secondary);\n}\n</style>\n</head>\n<body data-theme=\"light\">\n<div class=\"container\">\n <div class=\"main-card\">\n <div class=\"tip-alert\">\n <p>设置完成后请点击右上角的<span class=\"highlight-text\">√</span>应用设置</p>\n </div>\n \n <div class=\"main-tabs\">\n <div class=\"main-tab active\" id=\"tabBookSource\">书源设置</div>\n <div class=\"main-tab\" id=\"tabSource\">搜索设置</div>\n </div>\n \n <div class=\"tab-content active\" id=\"contentBookSource\">\n <div class=\"setting-item\">\n <div class=\"setting-info\">\n <div class=\"setting-title\">搜索模式</div>\n <div class=\"setting-desc\">切换后会搜索对应的类型,不推荐手动设置,可以在搜索的时候使用关键词:<span class=\"highlight-orange\">t:书名</span> 搜听书,<span class=\"highlight-blue\">d:书名</span> 搜短剧,<span class=\"highlight-purple\">m:书名</span> 搜漫画</div>\n </div>\n <div class=\"select-wrapper\">\n <select id=\"searchMode\">\n <option value=\"小说\">小说</option>\n <option value=\"听书\">听书</option>\n <option value=\"短剧\">短剧</option>\n <option value=\"漫画\">漫画</option>\n </select>\n </div>\n </div>\n \n <div class=\"setting-item\">\n <div class=\"setting-info\">\n <div class=\"setting-title\">显示图片</div>\n <div class=\"setting-desc\">关闭后,文章内的图片将会清除</div>\n </div>\n <label class=\"switch\">\n <input type=\"checkbox\" id=\"showImageSwitch\" checked>\n <span class=\"slider\"></span>\n </label>\n </div>\n \n <div class=\"setting-item\">\n <div class=\"setting-info\">\n <div class=\"setting-title\">完整简介</div>\n <div class=\"setting-desc\">关闭后,书籍详情页会精简简介内容</div>\n </div>\n <label class=\"switch\">\n <input type=\"checkbox\" id=\"fullDescSwitch\" checked>\n <span class=\"slider\"></span>\n </label>\n </div>\n \n <div class=\"setting-item\">\n <div class=\"setting-info\">\n <div class=\"setting-title\">书籍讨论</div>\n <div class=\"setting-desc\">仅支持番茄、七猫,需要魔改版本阅读</div>\n </div>\n <label class=\"switch\">\n <input type=\"checkbox\" id=\"bookDiscussionSwitch\">\n <span class=\"slider\"></span>\n </label>\n </div>\n \n <div class=\"setting-item\">\n <div class=\"setting-info\">\n <div class=\"setting-title\">同步书架</div>\n <div class=\"setting-desc\">开启后,刷新书籍详情页会将书架书籍同步到光遇网站网页端书架</div>\n </div>\n <label class=\"switch\">\n <input type=\"checkbox\" id=\"syncBookshelfSwitch\">\n <span class=\"slider\"></span>\n </label>\n </div>\n \n <div class=\"setting-item\" id=\"syncPopupSection\" style=\"display: none;\">\n <div class=\"setting-info\">\n <div class=\"setting-title\">同步弹窗</div>\n <div class=\"setting-desc\">弹窗开关可以让您知晓同步状态,关闭后不会弹窗</div>\n </div>\n <label class=\"switch\">\n <input type=\"checkbox\" id=\"syncPopupSwitch\">\n <span class=\"slider\"></span>\n </label>\n </div>\n \n <div class=\"setting-item\">\n <div class=\"setting-info\">\n <div class=\"setting-title\">网络模式</div>\n <div class=\"setting-desc\">开启后部分来源会使用本地网络请求,如<span class=\"highlight-orange\">69书吧</span>,可能需要开梯子访问,不推荐开启</div>\n </div>\n <div class=\"select-wrapper\">\n <select id=\"networkMode\">\n <option value=\"服务器\">服务器</option>\n <option value=\"本地\">本地</option>\n </select>\n </div>\n </div>\n \n <div class=\"setting-item\">\n <div class=\"setting-info\">\n <div class=\"setting-title\">强制搜索</div>\n <div class=\"setting-desc\">开启后搜索时会 <span class=\"highlight-red\">强制搜索全部来源</span>,搜索时长会 <span class=\"highlight-orange\">明显增长</span>,若无必要不建议开启</div>\n </div>\n <label class=\"switch\">\n <input type=\"checkbox\" id=\"forceSearchSwitch\">\n <span class=\"slider\"></span>\n </label>\n </div>\n \n <div class=\"setting-item\">\n <div class=\"setting-info\">\n <div class=\"setting-title\">目录显示来源</div>\n <div class=\"setting-desc\">开启后,目录最后一个章节的标题将显示来源,方便换源的时候开启加载目录可以知道是哪个来源</div>\n </div>\n <label class=\"switch\">\n <input type=\"checkbox\" id=\"showSourceInTocSwitch\" checked>\n <span class=\"slider\"></span>\n </label>\n </div>\n </div>\n \n <div class=\"tab-content\" id=\"contentSource\">\n <div class=\"source-subtabs\">\n <div class=\"source-subtab novel active\" id=\"subtabNovel\" data-type=\"小说\">\n <i class=\"fas fa-book-open\"></i>\n <span>小说</span>\n </div>\n <div class=\"source-subtab audio\" id=\"subtabAudio\" data-type=\"听书\">\n <i class=\"fas fa-headphones\"></i>\n <span>听书</span>\n </div>\n <div class=\"source-subtab comic\" id=\"subtabComic\" data-type=\"漫画\">\n <i class=\"fas fa-image\"></i>\n <span>漫画</span>\n </div>\n <div class=\"source-subtab drama\" id=\"subtabDrama\" data-type=\"短剧\">\n <i class=\"fas fa-video\"></i>\n <span>短剧</span>\n </div>\n </div>\n <div class=\"selected-info\" style=\"margin-bottom: 10px; background: rgba(45, 157, 120, 0.08); border-color: rgba(45, 157, 120, 0.2);\">\n 推荐选择全部,如需要搜索特定的资源可以搜索的时候使用 <span class=\"highlight\">书名@来源</span> 进行搜索\n </div>\n <div id=\"sourceOptions\" class=\"source-options\"></div>\n <div class=\"selected-info\" id=\"selectedInfo\">\n 当前选中: <span id=\"selectedCount\">0</span> 个来源\n </div>\n </div>\n \n <div id=\"hiddenSettings\" style=\"display: none;\">\n <script type=\"application/json\" id=\"settingsJson\">\n ${JSON.stringify(data || {\"小说\":\"全部\",\"听书\":\"全部\",\"漫画\":\"全部\",\"短剧\":\"全部\",\"搜索模式\":\"小说\",\"显示图片\":\"true\",\"完整简介\":\"true\",\"书籍讨论\":\"false\",\"同步书架\":\"false\",\"同步弹窗\":\"true\",\"网络模式\":\"服务器\",\"强制搜索\":\"false\",\"目录显示来源\":\"true\"})}\n </script>\n <div id=\"settingsDiv\">\n <span id=\"novelSource\">${(data && data.小说) || '全部'}</span>\n <span id=\"audioSource\">${(data && data.听书) || '全部'}</span>\n <span id=\"comicSource\">${(data && data.漫画) || '全部'}</span>\n <span id=\"dramaSource\">${(data && data.短剧) || '全部'}</span>\n <span id=\"searchModeValue\">${(data && data.搜索模式) || '小说'}</span>\n <span id=\"showImageSwitchValue\">${(data && data.显示图片) || 'true'}</span>\n <span id=\"fullDescSwitchValue\">${(data && data.完整简介) || 'true'}</span>\n <span id=\"bookDiscussionSwitchValue\">${(data && data.书籍讨论) || 'false'}</span>\n <span id=\"syncBookshelfSwitchValue\">${(data && data.同步书架) || 'false'}</span>\n <span id=\"syncPopupSwitchValue\">${(data && data.同步弹窗) || 'true'}</span>\n <span id=\"networkModeValue\">${(data && data.网络模式) || '服务器'}</span>\n <span id=\"forceSearchSwitchValue\">${(data && data.强制搜索) || 'false'}</span>\n <span id=\"showSourceInTocValue\">${(data && data.目录显示来源) || 'true'}</span>\n </div>\n </div>\n </div>\n</div>\n\n<script>\nconst SOURCES = ${JSON.stringify(config)};\nlet settings = ${JSON.stringify(data)};\nlet currentType = '小说';\n\ndocument.getElementById('tabSource').addEventListener('click', () => {\n document.getElementById('tabSource').classList.add('active');\n document.getElementById('tabBookSource').classList.remove('active');\n document.getElementById('contentSource').classList.add('active');\n document.getElementById('contentBookSource').classList.remove('active');\n});\n\ndocument.getElementById('tabBookSource').addEventListener('click', () => {\n document.getElementById('tabBookSource').classList.add('active');\n document.getElementById('tabSource').classList.remove('active');\n document.getElementById('contentBookSource').classList.add('active');\n document.getElementById('contentSource').classList.remove('active');\n});\n\nconst subtabs = document.querySelectorAll('.source-subtab');\nsubtabs.forEach(tab => {\n tab.addEventListener('click', () => {\n subtabs.forEach(t => t.classList.remove('active'));\n tab.classList.add('active');\n currentType = tab.dataset.type;\n renderSourceOptions();\n });\n});\n\nfunction renderSourceOptions() {\n const optionsContainer = document.getElementById('sourceOptions');\n const key = \\`搜索\\${currentType}\\`;\n const sources = SOURCES[key] || [];\n const savedValue = settings[currentType] || '';\n const selectedValues = savedValue === '全部' ? ['全部'] : (savedValue ? savedValue.split(',') : []);\n \n optionsContainer.innerHTML = '';\n \n const allOption = document.createElement('div');\n allOption.className = \\`source-option all \\${selectedValues.includes('全部') ? 'selected' : ''}\\`;\n allOption.textContent = '全部';\n allOption.addEventListener('click', () => selectOption('全部'));\n optionsContainer.appendChild(allOption);\n \n sources.forEach(source => {\n const option = document.createElement('div');\n option.className = \\`source-option \\${selectedValues.includes(source) ? 'selected' : ''}\\`;\n option.textContent = source;\n option.addEventListener('click', () => selectOption(source));\n optionsContainer.appendChild(option);\n });\n \n updateSelectedInfo();\n}\n\nfunction updateHiddenSettings() {\n const jsonEl = document.getElementById('settingsJson');\n if (jsonEl) {\n jsonEl.textContent = JSON.stringify(settings);\n }\n \n const typeMap = {\n '小说': 'novelSource',\n '听书': 'audioSource',\n '漫画': 'comicSource',\n '短剧': 'dramaSource',\n '搜索模式': 'searchModeValue',\n '显示图片': 'showImageSwitchValue',\n '完整简介': 'fullDescSwitchValue',\n '书籍讨论': 'bookDiscussionSwitchValue',\n '同步书架': 'syncBookshelfSwitchValue',\n '同步弹窗': 'syncPopupSwitchValue',\n '网络模式': 'networkModeValue',\n '强制搜索': 'forceSearchSwitchValue',\n '目录显示来源': 'showSourceInTocValue'\n };\n \n Object.keys(typeMap).forEach(type => {\n const el = document.getElementById(typeMap[type]);\n if (el) {\n el.textContent = settings[type] != undefined ? settings[type] : (type.includes('开关') ? 'false' : '全部');\n }\n });\n}\n\nfunction setupBookSourceSettings() {\n document.getElementById('searchMode').addEventListener('change', (e) => {\n settings['搜索模式'] = e.target.value;\n updateHiddenSettings();\n });\n \n document.getElementById('showImageSwitch').addEventListener('change', (e) => {\n settings['显示图片'] = e.target.checked ? 'true' : 'false';\n updateHiddenSettings();\n });\n \n document.getElementById('fullDescSwitch').addEventListener('change', (e) => {\n settings['完整简介'] = e.target.checked ? 'true' : 'false';\n updateHiddenSettings();\n });\n \n document.getElementById('bookDiscussionSwitch').addEventListener('change', (e) => {\n settings['书籍讨论'] = e.target.checked ? 'true' : 'false';\n updateHiddenSettings();\n });\n \n document.getElementById('syncBookshelfSwitch').addEventListener('change', (e) => {\n settings['同步书架'] = e.target.checked ? 'true' : 'false';\n updateHiddenSettings();\n \n const syncPopupSection = document.getElementById('syncPopupSection');\n const syncPopupSwitch = document.getElementById('syncPopupSwitch');\n if (e.target.checked) {\n syncPopupSection.style.display = 'flex';\n if (settings['同步弹窗'] == undefined) {\n settings['同步弹窗'] = 'true';\n syncPopupSwitch.checked = true;\n updateHiddenSettings();\n }\n } else {\n syncPopupSection.style.display = 'none';\n }\n });\n \n document.getElementById('syncPopupSwitch').addEventListener('change', (e) => {\n settings['同步弹窗'] = e.target.checked ? 'true' : 'false';\n updateHiddenSettings();\n });\n \n document.getElementById('networkMode').addEventListener('change', (e) => {\n settings['网络模式'] = e.target.value;\n updateHiddenSettings();\n });\n \n document.getElementById('forceSearchSwitch').addEventListener('change', (e) => {\n settings['强制搜索'] = e.target.checked ? 'true' : 'false';\n updateHiddenSettings();\n });\n \n document.getElementById('showSourceInTocSwitch').addEventListener('change', (e) => {\n settings['目录显示来源'] = e.target.checked ? 'true' : 'false';\n updateHiddenSettings();\n });\n}\n\nfunction initBookSourceSettings() {\n const searchModeEl = document.getElementById('searchMode');\n if (searchModeEl && settings['搜索模式']) {\n searchModeEl.value = settings['搜索模式'];\n }\n \n const showImageEl = document.getElementById('showImageSwitch');\n if (showImageEl) {\n showImageEl.checked = settings['显示图片'] != 'false';\n }\n \n const fullDescEl = document.getElementById('fullDescSwitch');\n if (fullDescEl) {\n fullDescEl.checked = settings['完整简介'] != 'false';\n }\n \n const bookDiscussionEl = document.getElementById('bookDiscussionSwitch');\n if (bookDiscussionEl) {\n bookDiscussionEl.checked = settings['书籍讨论'] === 'true';\n }\n \n const syncBookshelfEl = document.getElementById('syncBookshelfSwitch');\n if (syncBookshelfEl) {\n syncBookshelfEl.checked = settings['同步书架'] === 'true';\n }\n \n const syncPopupSection = document.getElementById('syncPopupSection');\n const syncPopupSwitch = document.getElementById('syncPopupSwitch');\n if (syncPopupSwitch) {\n syncPopupSwitch.checked = settings['同步弹窗'] != 'false';\n }\n if (syncPopupSection) {\n syncPopupSection.style.display = (settings['同步书架'] === 'true') ? 'flex' : 'none';\n }\n \n const networkModeEl = document.getElementById('networkMode');\n if (networkModeEl && settings['网络模式']) {\n networkModeEl.value = settings['网络模式'];\n }\n \n const forceSearchEl = document.getElementById('forceSearchSwitch');\n if (forceSearchEl) {\n forceSearchEl.checked = settings['强制搜索'] === 'true';\n }\n \n const showSourceInTocEl = document.getElementById('showSourceInTocSwitch');\n if (showSourceInTocEl) {\n showSourceInTocEl.checked = settings['目录显示来源'] != 'false';\n }\n}\n\nfunction selectOption(value) {\n const key = \\`搜索\\${currentType}\\`;\n const sources = SOURCES[key] || [];\n \n if (value === '全部') {\n settings[currentType] = '全部';\n updateHiddenSettings();\n renderSourceOptions();\n return;\n }\n \n let currentValue = settings[currentType] || '';\n \n if (currentValue === '全部') {\n currentValue = '';\n }\n \n let values = currentValue ? currentValue.split(',') : [];\n \n const index = values.indexOf(value);\n if (index > -1) {\n values.splice(index, 1);\n } else {\n values.push(value);\n }\n \n if (values.length === 0) {\n settings[currentType] = '全部';\n } else {\n settings[currentType] = values.join(',');\n }\n \n updateHiddenSettings();\n renderSourceOptions();\n}\n\nfunction updateSelectedInfo() {\n const key = \\`搜索\\${currentType}\\`;\n const sources = SOURCES[key] || [];\n const savedValue = settings[currentType] || '';\n \n if (savedValue === '全部') {\n document.getElementById('selectedCount').textContent = '全部';\n } else {\n const count = savedValue ? savedValue.split(',').length : 0;\n document.getElementById('selectedCount').textContent = count;\n }\n}\n\ndocument.addEventListener('DOMContentLoaded', () => {\n let savedSettings = {};\n try {\n const jsonEl = document.getElementById('settingsJson');\n if (jsonEl) {\n savedSettings = JSON.parse(jsonEl.textContent);\n }\n } catch (e) {\n console.error('读取初始设置失败:', e);\n }\n \n settings = {\n '小说': savedSettings['小说'] || '全部',\n '听书': savedSettings['听书'] || '全部',\n '漫画': savedSettings['漫画'] || '全部',\n '短剧': savedSettings['短剧'] || '全部',\n '搜索模式': savedSettings['搜索模式'] || '小说',\n '显示图片': savedSettings['显示图片'] || 'true',\n '完整简介': savedSettings['完整简介'] || 'true',\n '书籍讨论': savedSettings['书籍讨论'] || 'false',\n '同步书架': savedSettings['同步书架'] || 'false',\n '同步弹窗': savedSettings['同步弹窗'] || 'true',\n '网络模式': savedSettings['网络模式'] || '服务器',\n '强制搜索': savedSettings['强制搜索'] || 'false',\n '目录显示来源': savedSettings['目录显示来源'] || 'true'\n };\n \n initBookSourceSettings();\n setupBookSourceSettings();\n renderSourceOptions();\n});\n</script>\n</body>\n</html>\n `\n let body;\n try {\n body = java.startBrowserAwait(`data:text/html;base64,${java.base64Encode(html)}`, '光遇书源设置', false).body();\n } catch (e) {\n let islyc = this.checkEnv();\n if (islyc == \"安卓\") {\n java.longToast(\"\\n当前软件版本过低,请更新软件,或者使用底部备用设置方案~\");\n } else {\n java.longToast(\"\\n请点击页面右上角√才能设置成功哦~\");\n }\n return;\n }\n \n let settings = {};\n try {\n const getSpanText = (spanId) => {\n const regex = new RegExp('id=\"' + spanId + '\"\\s*>\\s*([^<]*?)\\s*<\\/span>');\n const match = body.match(regex);\n return match ? match[1].trim() : \"\";\n };\n settings = {\n '搜索模式': getSpanText('searchModeValue'),\n '显示图片': getSpanText('showImageSwitchValue'),\n '完整简介': getSpanText('fullDescSwitchValue'),\n '书籍讨论': getSpanText('bookDiscussionSwitchValue'),\n '同步书架': getSpanText('syncBookshelfSwitchValue'),\n '同步弹窗': getSpanText('syncPopupSwitchValue'),\n '网络模式': getSpanText('networkModeValue'),\n '强制搜索': getSpanText('forceSearchSwitchValue'),\n '目录显示来源': getSpanText('showSourceInTocValue'),\n '小说': getSpanText('novelSource'),\n '听书': getSpanText('audioSource'),\n '漫画': getSpanText('comicSource'),\n '短剧': getSpanText('dramaSource'),\n };\n const settingsText = Object.keys(settings).map(key => `${key}: ${settings[key]}`).join('\\n');\n java.longToast(\"\\n\" + settingsText);\n\n } catch (e) {\n settings = {\n '搜索模式': '小说',\n '显示图片': 'true',\n '完整简介': 'true',\n '书籍讨论': 'false',\n '同步书架': 'false',\n '同步弹窗': 'true',\n '网络模式': '服务器',\n '强制搜索': 'false',\n '目录显示来源': 'true',\n '小说': '全部',\n '听书': '全部',\n '漫画': '全部',\n '短剧': '全部',\n };\n }\n this.setVariable(\"更多设置\", settings, false)\n}\n",
"lastUpdateTime": 1791479867070,
"loginUi": "[\n {\n \"name\": \"🔭 切换线路\",\n \"type\": \"button\",\n \"action\": \"getServerSettings()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 1\n }\n },{\n \"name\": \"⚙️ 书源设置\",\n \"type\": \"button\",\n \"action\": \"getHtmlSettings()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"🗨 段评设置\",\n \"type\": \"button\",\n \"action\": \"getSvgSettings()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"邮箱\",\n \"type\": \"text\"\n }, {\n \"name\": \"密码\",\n \"type\": \"password\"\n }, {\n \"name\": \"🔅登录账号\",\n \"type\": \"button\",\n \"action\": \"login(true)\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"🔐注册账号\",\n \"type\": \"button\",\n \"action\": \"register()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\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\": \"user()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"📴 清理设备\",\n \"type\": \"button\",\n \"action\": \"clearDevice()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🪪 查看信息\",\n \"type\": \"button\",\n \"action\": \"checkStatus()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🌷求打赏\",\n \"type\": \"button\",\n \"action\": \"vip()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🎈永久发布页\",\n \"type\": \"button\",\n \"action\": \"fb()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"✳️ 更新配置\",\n \"type\": \"button\",\n \"action\": \"_getCloudSettings()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"❇️ 更新书源\",\n \"type\": \"button\",\n \"action\": \"renderVersionPage()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"📤保存设置到云端\",\n \"type\": \"button\",\n \"action\": \"saveSettings()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \" 📨从云端获取设置\",\n \"type\": \"button\",\n \"action\": \"getSettings()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"⛔‼️ 清空还原设置\",\n \"type\": \"button\",\n \"action\": \"deleteVariable()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 1\n }\n }, {\n \"name\": \"===下方为备用设置方案===\",\n \"type\": \"button\",\n \"action\": \"\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 1\n }\n }, {\n \"name\": \"📖搜索小说\",\n \"type\": \"button\",\n \"action\": \"ste('小说')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"🎧搜索听书\",\n \"type\": \"button\",\n \"action\": \"ste('听书')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"🎥搜索短剧\",\n \"type\": \"button\",\n \"action\": \"ste('短剧')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"🎨搜索漫画\",\n \"type\": \"button\",\n \"action\": \"ste('漫画')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"段评开关\",\n \"type\": \"button\",\n \"action\": \"ste('段评')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.25\n }\n }, {\n \"name\": \"图片开关\",\n \"type\": \"button\",\n \"action\": \"ste('图片')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.25\n }\n }, {\n \"name\": \"简介开关\",\n \"type\": \"button\",\n \"action\": \"ste('简介')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.25\n }\n }, {\n \"name\": \"同步书架\",\n \"type\": \"button\",\n \"action\": \"ste('同步')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.25\n }\n }, {\n \"name\": \"强制搜索\",\n \"type\": \"button\",\n \"action\": \"ste('强制')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.25\n }\n }, {\n \"name\": \"网络模式\",\n \"type\": \"button\",\n \"action\": \"ste('网络')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.25\n }\n }, {\n \"name\": \"目录显示来源开关\",\n \"type\": \"button\",\n \"action\": \"ste('目录')\",\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\": \"搜索来源(支持的平台请前往源变量中查看,多个来源用英文逗号分割)\",\n \"type\": \"text\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 1\n }\n }, {\n \"name\": \"设置发现页来源\",\n \"type\": \"button\",\n \"action\": \"setFindSource()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"设置搜索来源\",\n \"type\": \"button\",\n \"action\": \"setSearchSource()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }\n]",
"loginUrl": "//登陆\nfunction login(flag) {\n const token = getToken();\n if (String(token).length > 10) {\n java.longToast(flag ? '\\n当前✅️已登录,请🚫退出登录后重新登录' : '\\n✅️已登录');\n return true;\n }\n if (flag == undefined) {\n result = JSON.parse(source.getLoginInfo())\n } else {\n java.longToast(\"\\n\\n💞正在登录中...\")\n }\n let email = result.邮箱;\n let pwd = result.密码;\n\n if (!email || !pwd) {\n java.longToast('\\n请先输入账号密码!');\n return false;\n }\n if (!email.includes('@')) {\n java.longToast('\\n请输入正确的邮箱格式!');\n return false;\n }\n try {\n let data = request('/login_api','POST',{\n register_email: email,\n password: pwd\n })\n const response = JSON.parse(data);\n if (response.code == 0) {\n setAllCookies(`qttoken=${response.key}`);\n java.longToast(\"\\n✅️登录成功\");\n return true;\n } else {\n let msg = response.msg;\n if (msg.includes('多次登录失败')) {\n java.longToast('\\n💔多次登录失败,请检查账号密码,切换线路后重试或等2小时后重试!');\n } else {\n java.longToast('\\n💔' + (msg || \"登录失败,请重试!\"));\n }\n return false;\n }\n } catch (error) {\n java.longToast('\\n💔登录失败,服务器错误,请更换线路后重试!\\n' + error);\n return false;\n }\n}\n\n// 用户注册\nfunction register() {\n java.startBrowserAwait(BaseUrl() + '/register', '光遇看书注册');\n}\n\n//用户后台\nfunction user() {\n const token = getToken();\n if (String(token).length < 10) {\n java.longToast('\\n🤔请先登陆');\n return;\n }\n java.startBrowserAwait(BaseUrl() + '/user', '光遇看书用户后台');\n}\n\n// 退出登陆\nfunction logout() {\n removeAllCookies();\n}\n\n// 获取云端配置\nfunction _getCloudSettings() {\n getCloudSettings(true)\n}\n\n//打赏\nfunction vip() {\n const token = getToken();\n if (String(token).length < 10) {\n java.longToast('\\n🤔请先登陆');\n return;\n }\n java.startBrowserAwait(BaseUrl() + '/coffee', '光遇聚合会员开通');\n}\n\nfunction formatDate(timestamp) {\n const date = new Date(timestamp * 1000);\n return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;\n}\n\nfunction isVips(res) {\n const vipEndTime = res.vip_end_time;\n const isVipValue = res.is_vip;\n\n let vipType;\n if (isVipValue == 0) {\n return '普通用户';\n } else if (isVipValue == 1) {\n vipType = 'VIP';\n } else {\n vipType = 'SVIP';\n }\n\n if (!vipEndTime || vipEndTime == 0) {\n return `${vipType} (已过期)`;\n }\n\n const currentTime = Math.floor(Date.now() / 1000);\n const remainingDays = Math.ceil((vipEndTime - currentTime) / (24 * 60 * 60));\n\n if (currentTime > vipEndTime) {\n return `${vipType} (已过期)`;\n }\n\n if (remainingDays <= 7) {\n return `${vipType} 剩余${remainingDays}天`;\n }\n\n if (vipEndTime >= 1912946812) {\n return `${vipType} (永久)`;\n }\n\n return `${vipType}(${formatDate(vipEndTime)})`;\n}\n\nfunction checkStatus() {\n const token = getToken();\n if (String(token).length < 10) {\n java.longToast('\\n🤔请先登陆');\n return;\n }\n java.longToast('\\n\\n♻️查询中...');\n\n try {\n const res = JSON.parse(request(`/user_api`, 'POST'));\n\n if (res.id == undefined) {\n throw new Error(res.msg || '获取用户信息失败');\n }\n\n result.邮箱 = res.email;\n \n source.putLoginInfo(JSON.stringify(result));\n\n let devices = 0;\n try {\n devices = res.device ? Object.keys(JSON.parse(res.device)).length : 0;\n } catch (e) {\n devices = res.device ? 1 : 0;\n }\n\n const isVip = isVips(res);\n const today = java.timeFormat(new Date()).slice(0, 10);\n const lastReadDate = res.last_read_time ? java.timeFormat(res.last_read_time * 1000).slice(0, 10) : '';\n const todayReadCount = today == lastReadDate ? res.day_read_count : 0;\n\n const tips = `\n${BaseUrl()}\n┏┅┅┅┅┅┅┱┄┄┄┄┄┄┄┄┄┄┐\n  🧢昵称     ${(res.nickname || '未设置').padEnd(20, '\\t')}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n ✉️邮箱    ${res.email.replace(/(.{3}).*?@/, '$1***@').padEnd(20, '\\t')}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n 🔑密钥    ${(`${res.user_key.substring(0, 4)}***${res.user_key.slice(-4)}`).padEnd(20, '\\t')}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n 📅注册时间  ${java.timeFormat(res.register_time * 1000).padEnd(20, '\\t')}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n🗒️今日阅读  ${todayReadCount.toString().padEnd(20, '\\t')}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n📚累计阅读  ${res.all_read_count.toString().padEnd(20, '\\t')}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n🕓最后阅读  ${(res.last_read_time ? java.timeFormat(res.last_read_time * 1000) : '未阅读').padEnd(20, '\\t')}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n📱在线设备  ${devices.toString().padEnd(20, '\\t')}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n 👑会员状态  ${isVip.padEnd(20, '\\t')}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n 🚫封禁状态  ${res.is_banned ? '已封禁' : '正常 '}       \n┗┅┅┅┅┅┅┹┄┄┄┄┄┄┄┄┄┄┘\n`;\n\n java.log(tips);\n java.longToast(tips);\n\n } catch (e) {\n java.toast(`\\n检测登录失败\\n${e.message}`);\n }\n}\n\nfunction clearDevice() {\n const token = getToken();\n if (String(token).length < 10) {\n java.longToast('\\n🤔请先登陆');\n return;\n }\n let res = request(`/clear`, \"POST\");\n res = JSON.parse(res);\n java.toast(res.code == 0 ? \"\\n\\n📴设备清除成功\" : res.msg)\n //Packages.java.lang.Thread.sleep(500)\n //checkStatus()\n}\n\nfunction fb() {\n java.startBrowser('https://vip.qingtian618.com', '光遇看书');\n}\n\nfunction setFindSource() {\n\tlet findSource = result[\"发现页来源(支持的平台请前往源变量中查看)\"];\n\tif (String(findSource)==\"\" || String(findSource) == \"undefined\") {\n\t\tfindSource = \"番茄\";\n\t\t}\n\tsetVariable(\"发现页来源\",findSource);\n java.longToast(`\\n已设置发现页来源为:${findSource}\\n设置后请刷新发现页!`)\n \n\t}\n\t\n\t\nfunction setSearchSource() {\n\tlet searchSource = result[\"搜索来源(支持的平台请前往源变量中查看,多个来源用英文逗号分割)\"];\n\tif (String(searchSource)==\"\" || String(searchSource) == \"undefined\") {\n\t\tsearchSource = '全部';\n\t\t}\n\tlet moreSettings = getVariable(\"更多设置\") || {};\n\tlet tab = moreSettings && moreSettings['搜索模式'] || '小说';\n\tmoreSettings[tab] = searchSource;\n\tsetVariable(\"更多设置\",moreSettings)\n java.longToast(`\\n已设置搜索来源为:${tab}-${moreSettings[tab]}\\n设置后请重新搜索`)\n\t}\n\nfunction saveSettings() {\n\t java.longToast(`\\n正在保存~`);\n let moreSettings = getVariable(\"更多设置\") || {};\n let body_more = {\n \"legado_more\":moreSettings\n }\n\tlet data_more = request('/user_settings','POST',body_more)\n const response_more = JSON.parse(data_more);\n let paraSettings = getVariable(\"段评设置\") || {};\n let body_para = {\n \"legado_para\":paraSettings\n }\n\tlet data_para = request('/user_settings','POST',body_para)\n const response_para = JSON.parse(data_para);\n java.longToast(`\\n书源设置:${response_more.msg}\\n段评设置:${response_para.msg}`);\n\t}\n \n function getSettings() {\n \tjava.longToast(`\\n正在获取~`);\n let more = false;\n let para = false;\n let data_more = request('/user_settings?set_type=legado_more','GET');\n const response_more = JSON.parse(data_more);\n if (response_more.data) {\n setVariable(\"更多设置\",response_more.data)\n more = true;\n }\n let data_para = request('/user_settings?set_type=legado_para','GET');\n const response_para = JSON.parse(data_para);\n if (response_para.data) {\n setVariable(\"段评设置\",response_para.data)\n para=true;\n }\n java.longToast(`\\n书源设置:${more?'应用成功':'应用失败'}\\n段评设置:${para?'应用成功':'应用失败'}`);\n }",
"respondTime": 180000,
"ruleBookInfo": {
"author": "$.author",
"canReName": "1",
"coverUrl": "$.thumb_url",
"init": "<js>\nlet moreSettings = getVariable('更多设置');\n\n\nfunction fetchBookDetail(result) {\n\t let res = {};\n\t //java.log(baseUrl)\n\t //java.longToast(result)\n\t if (String(baseUrl).startsWith(\"data:\")) {\n res = JSON.parse(java.hexDecodeToString(result));\n } else {\n \treturn result;\n \t}\n const book_id = res.book_id;\n const tab = res.tab;\n const sources = res.sources || res.source;\n const url = res.url;\n let html = \"\";\n const proxy = moreSettings && moreSettings['网络模式'] || '服务器';\n\n if (url && proxy == \"本地\") {\n html = fetchHtmlWithFallback(url, sources);\n }\n\n const varia = getAndFormatCustomVariable();\n const requestUrl = `/detail?book_id=${book_id}&source=${sources}&tab=${tab}&variable=${varia}`;\n\n java.log(requestUrl);\n return request(requestUrl, 'POST', { html: html });\n}\n\nfunction fetchHtmlWithFallback(url, sources) {\n let html;\n let ck69 = '';\n let headers = {};\n let op = '';\n\n if (sources == '69书吧') {\n ck69 = String(cookie.getCookie('https://www.69shuba.com'));\n headers = {\n \"Referer\": url,\n \"Cookie\": ck69,\n \"User-Agent\": java.getWebViewUA()\n };\n op = JSON.stringify({ \"headers\": headers });\n html = java.ajax(`${url},${op}`);\n } else {\n html = java.ajax(url);\n }\n\n if (html.includes(\"Just a moment...\") && sources == '69书吧') {\n cookie.removeCookie('https://www.69shuba.com');\n java.longToast('需要真人验证,请进入任意书籍详情页过验证');\n html = java.startBrowserAwait(url, \"需要真人验证,请进入任意书籍详情页过验证\").body();\n }\n\n return html;\n}\n\nfunction getAndFormatCustomVariable() {\n let varia = String(book.getVariable('custom'));\n if (varia === 'null') {\n varia = '';\n }\n return JSON.stringify({ 'custom': varia });\n}\nfetchBookDetail(result)\n</js>$.data",
"intro": "<js>\nconst {\n source:sources,\n book_id,\n tab,\n book_tts,\n tags,\n role,\n last_chapter_title,\n last_chapter_update_time,\n word_number,\n status,\n score,\n abstract,\n copyright_info,\n read_count,\n read_count_all\n} = result;\nlet moreSettings = getVariable('更多设置');\n\n\nconst proxy = (moreSettings && moreSettings['网络模式'] || '服务器') == \"本地\" ? \"本地网络\" : \"服务器网络\";\nconst discussion = moreSettings && moreSettings['书籍讨论'] || 'false';\nif ((book.readConfig && book.readConfig.useReplaceRule) == null) {\n book.setUseReplaceRule(false);\n}\n\nlet nickname = '账号状态:⚠️ 未登录 | 点击右上角 🔖 登录';\nlet qttoken = getToken();\nif (qttoken.length > 10) {\n nickname = '账号状态:✅已登录';\n}\ntry {\n const user_info = JSON.parse(request(`/get_avatar`, 'GET'));\n if (user_info.code === 0) {\n nickname = user_info.nickname \n ? `欢迎回来:${user_info.nickname}`\n : `欢迎回来:${user_info.email}(请前往用户后台设置用户名)`;\n }\n} catch (e) {\n \n}\n\nconst loginStatus = nickname;\nconst lightDivider = \"❇️───────❇️───────❇️\";\nconst heavyDivider = \"&lrm;\\n&lrm;\";\n\nconst isValid = (value) => String(value).length > 1;\nlet ctitle = book.durChapterTitle || '未开始';\n\nconst formatAbstract = (text) => {\n return text.split(\"\\n\").map(line => ` ${line}`).join(\"\\n\");\n};\n\nconst buildBasicInfo = (items) => {\n let info = \"\";\n for (let [value, prefix, icon] of items) {\n if (isValid(value)) {\n info += ` ${icon} ${prefix} ${value}\\n`;\n }\n }\n return info;\n};\n\nlet info = `\n 📡 当前服务:${BaseUrl()}\n 🔑 ${loginStatus}\n 🏷 数据来源:${sources}\n 🔄 当前模式:${tab}\n ⚙️ 访问模式:${proxy}\n 📖 阅读至:${ctitle}\n`;\n\nif (tab == \"听书\") {\n let toneId = String(book.getVariable('custom')) || '4';\n if (isValid(book_tts)) {\n info += `${lightDivider}\n 🎵 音色配置:${toneId}\n ✨ AI音色请点击右上角书籍变量填写相关值,真人听书请重新搜索选择带有主播的书籍\n ${book_tts}\n`;\n }\n}\n\nconst fullBasicInfo = buildBasicInfo([\n [tags, \"书籍分类:\", \"🌈\"],\n [role, \"书籍主角:\", \"👑\"],\n [last_chapter_title, \"最新章节:\", \"📚\"],\n [last_chapter_update_time, \"更新时间:\", \"⏳\"],\n [word_number, \"书籍字数:\", \"📝\"],\n [status, \"书籍状态:\", \"🚩\"],\n [score, \"书籍评分:\", \"⭐\"],\n [read_count, \"今阅读人数:\", \"📃\"],\n [read_count_all, \"总阅读人数:\", \"📈\"]\n]);\n\nif (fullBasicInfo) {\n info += `${lightDivider}\\n${fullBasicInfo}`;\n}\n\nif (isValid(abstract)) {\n info += `${heavyDivider}\n 📖 书籍简介:\n${formatAbstract(abstract)}\n`;\n} else {\n info += heavyDivider;\n}\n\nif (isValid(copyright_info)) {\n info += `${lightDivider}\n © ${copyright_info}\n`;\n} else {\n info += lightDivider;\n}\n\ninfo += `\n${heavyDivider}\n 数据更新于 ${new Date().toLocaleString()}\n`;\n\nconst abstractMode = moreSettings && moreSettings['完整简介'] || 'true';\nif (abstractMode == \"false\") {\n const simplifiedBasicInfo = buildBasicInfo([\n [last_chapter_title, \"最新章节:\", \"📚\"],\n [last_chapter_update_time, \"更新时间:\", \"⏳\"],\n [word_number, \"书籍字数:\", \"📊\"],\n [status, \"书籍状态:\", \"🚩\"],\n [score, \"书籍评分:\", \"⭐\"]\n ]);\n \n info = `\\n&lrm;${isValid(abstract) ? `📖 书籍简介:\\n${formatAbstract(abstract)}\\n${heavyDivider}` : ''}\n🏷 数据来源:${sources}\n${simplifiedBasicInfo}`;\n\n} else {\n info = info.split(\"\\n\").map(line => line.replace(/^ {4}/, \"\")).join(\"\\n\");\n}\nif (discussion=='true' && tab == '小说' && (sources=='番茄'||sources=='七猫'||sources.replace('svip_','')=='书旗')) {\n info = info.split(\"\\n\").map(line => line.replace(/^ {4}/, \"\")).join(\"<br>\");\n\tlet discussionUrl = `${BaseUrl()}/book_info?book_id=${book_id}&source=${sources}`;\n`<usehtml>${buildDiscussionButton(discussionUrl,sources)}<br>${info}</usehtml>`\n}\n</js>",
"lastChapter": "{{$.source}} {{$.last_chapter_title}} {{$.last_chapter_update_time}}",
"name": "$.book_name",
"tocUrl": "<js>\nlet book_id = result.book_id;\nlet sources = result.source;\nlet book_name = result.book_name;\nlet author = result.author;\nlet abstract = result.abstract;\nlet thumb_url = result.thumb_url;\nlet tab = result.tab || getVariable('类型');\nlet url = result.toc_url || \"\";\nlet gycatalog = {\n\t book_name,\n\t author,\n\t abstract,\n\t thumb_url,\n book_id,\n sources,\n tab,\n url,\n};\ngycatalog = java.base64Encode(JSON.stringify(gycatalog));\n`data:;base64,${gycatalog},{\"type\":\"gycatalog\"}`;\n</js>",
"wordCount": "$.word_number"
},
"ruleContent": {
"content": "<js>\nlet DEFAULT_TONE_ID = '4';\nlet CONTENT_URL = '/content';\nlet CONTENT_URL_WITH_REVIEW = '/content?review=1';\nlet VERSION = localVersion;\nlet SOURCES_WITH_REVIEW = ['番茄', '七猫', '塔读', 'QQ阅读', 'svip_QQ阅读','书旗','svip_书旗'];\nlet moreSettings = getVariable('更多设置');\nconst proxy = moreSettings && moreSettings['网络模式'] || '服务器';\nconst reading = moreSettings && moreSettings['同步书架'] || 'false';\nconst reading_toast = moreSettings && moreSettings['同步弹窗'] || 'true';\n\nresult = String(java.hexDecodeToString(result));\nlet res = JSON.parse(result);\nlet { book_id, item_id, tab, title, sources, url } = res;\n\nlet islyc = checkEnv();\ntry {\n book.imageStyle = islyc == '改版' ? 'FULL' : 'TEXT';\n} catch (e) {\n \n}\n\nconst syncBookShelf = (book) => {\n if (!book) {\n return;\n }\n let book_order = book.order;\n if (book_order!=0) {\n \t book_order = 1;\n \t} else {\n \t\t//book_order = 2;\n \t\treturn;\n \t\t}\n \n if (reading != \"true\") {\n return;\n }\n\n try {\n const ritem = item_id;\n \nlet book_info = {\n book_name:book.name,\n author:book.author,\n //abstract:book.abstract,\n thumb_url:book.coverUrl,\n book_id,\n tab,\n source: sources\n };\n if (!book_info || typeof book_info != \"object\") {\n return;\n }\n const rurl = `/add_book_to_book_shelf`;\n book_info.read_status = book_order;\n book_info.last_chapter_item_id = item_id;\n book_info.last_chapter_title = chapter.title || \"\";\n //java.toast(JSON.stringify(book_info));\n \ttry {\n \t\tconst check_book_url = `/check_book_in_book_shelf`;\n checkResponse = request(check_book_url, \"POST\", {book_id,source:sources,tab});\n} catch (e) {\n java.log(`检查书籍请求失败: ${e}`);\n}\n \n const check_data = parseJsonSafely(checkResponse);\n try {\n if (check_data.data.id) {\n book_info.id = check_data.data.id;\n const uurl = `/update_book_shelf`;\n let up = request(uurl, \"POST\", book_info);\n if (reading_toast == \"true\") {\n \t java.toast(up)\n \t}\n \n } else {\n let up = request(rurl, \"POST\", book_info);\n if (reading_toast == \"true\") {\n \t java.toast(up)\n \t}\n }\n } catch (e) {\n try {\n let up = request(rurl, \"POST\", book_info);\n java.toast(up)\n } catch (e) {\n if (reading_toast == \"true\") {\n \t java.toast(`书架操作失败: ${e}`);\n \t}\n }\n\n }\n } catch (error) {\n java.log(`书籍同步流程异常: ${error}`);\n \n if (reading_toast == \"true\") {\n \t java.longToast(\"\\n同步阅读进度失败,但不影响阅读,可以前往登录关闭书架同步功能。\");\n \t}\n }\n};\n\n//java.log(book)\n\n\nif (book.durChapterIndex == chapter.index) {\n\ttry {syncBookShelf(book)} catch {}\n\t}\n\n\n\n\nlet varia1 = String(book.getVariable('custom')) || '';\nlet varia = JSON.stringify({ custom: varia1 });\n\n\nlet html = '';\n\nif (url && proxy === '本地') {\n if (sources === '69书吧') {\n let ck69 = String(cookie.getCookie('https://www.69shuba.com'));\n let headers = {\n Referer: url,\n Cookie: ck69,\n 'User-Agent': java.getWebViewUA()\n };\n let op = JSON.stringify({ headers });\n html = java.ajax(`${url},${op}`);\n } else {\n html = java.ajax(url);\n }\n \n if (html.includes('Just a moment...') && sources == '69书吧' && book.durChapterIndex == chapter.index) {\n cookie.removeCookie('https://www.69shuba.com');\n java.longToast('需要真人验证,请进入任意书籍详情页过验证');\n html = java.startBrowserAwait(url, '需要真人验证,请进入任意书籍详情页过验证').body();\n }\n}\n\nlet tone_id = varia1 || DEFAULT_TONE_ID;\nlet base_url = BaseUrl();\nlet dpSettings = getVariable('段评设置');\nlet para = dpSettings && dpSettings['段评开关'] || 'true';\nlet godop = dpSettings && dpSettings['神评论开关'] || 'true';\nlet endop = dpSettings && dpSettings['本章说开关'] || 'true';\nlet show_img = moreSettings && moreSettings['显示图片'] || 'true';\n\n\nlet god = false;\n\nif ((sources == \"番茄\" || sources == '七猫' || sources=='svip_书旗') && godop == 'true' && tab == \"小说\") {\n\tgod = true\n\t}\nlet params = {\n html,\n item_id,\n source: sources,\n tab,\n tone_id,\n variable: varia,\n version: VERSION,\n god\n};\n\nlet hasReview = SOURCES_WITH_REVIEW.includes(sources) && para==\"true\" && tab == '小说';\nlet content_url = hasReview ? CONTENT_URL_WITH_REVIEW : CONTENT_URL;\n\nlet data = request(content_url, 'POST', params);\ntry {\n data = JSON.parse(data);\n if (data.msg) {\n java.toast(data.msg);\n }\n} catch (e) {\n //console.error('Parse content data failed:', e);\n}\n\nlet content = data.content || '';\n\nif (show_img == 'false') {\n content = removeAllImgTags(content);\n}\n\nlet fqssionid = getFqToken();\nlet deviceType = '安卓';\n try {\n \tjava.deviceID();\n \tdeviceType = '苹果'\n \t} catch {}\nif (hasReview) {\n content = content\n .replace(/ident=\"/g, `ident=\"${base_url}`)\n .replace(/book_id=/g, `book_id=${book_id}&ssionid=${fqssionid}`);\n \n content = deviceType == '苹果' \n ? paraForiOS(content, sources) \n : paraForAndroid(content, sources);\n}\nif ((sources == \"番茄\" || sources == \"七猫\"||sources.replace('svip_','')=='书旗') && endop == 'true' && tab == \"小说\") {\n let svg = `${base_url}/chapter_review_svg?book_id=${book_id}&item_id=${item_id}&source=${sources}&ssionid=${fqssionid}`;\n\tlet eurl = `${base_url}/get_review?book_id=${book_id}&item_id=${item_id}&source=${sources}`;\n if (deviceType == '苹果') {\n let chapterSvg = java.ajax(svg);\n svg = 'data:image/svg+xml;base64,'+java.base64Encode(chapterSvg);\n } else {\n svg += '&img=1'\n }\n let imgSvg = `\\n<img src=\"${svg},{'type':'qtbzs',${endclick(eurl,sources)},'style':'FULL'}\">`;\n if (islyc == '苹果') {\n \timgSvg = `\\n<div rs-native><img onPress=\"java.showReadingBrowser('${eurl}','${sources}本章说')\" src=\"${svg}\"></div>`;\n \t}\n\tcontent = content+ imgSvg;\n\t}\ndata = JSON.stringify({ content });\n\nlet isSpecialSource = isSpecialMedia(sources);\nlet isDramaTab = tab == '短剧';\nif (islyc != '苹果' && (isDramaTab || isSpecialSource)) {\n\tjava.log(isSpecialSource)\n if (!java.getAppVariant || isSpecialSource) {\n data = JSON.stringify({\n content: `【右上角刷新】开启播放(下一集请切换下一章刷新)\\n播放直链:\\n${content}`\n });\n if (book.durChapterIndex == chapter.index) {\n let video_url = `${base_url}/online_video?book_id=${book_id}&source=${sources}&tab=${tab}`;\n if (isSpecialSource) {\n video_url = content;\n }\n java.startBrowser(video_url, title);\n java.toast('正在加载视频...');\n }\n \n }\n\n\n}\n//java.log(data)\ndata;\n</js>$.content",
"title": "@js:\nresult = String(java.hexDecodeToString(result))\nJSON.parse(result)['title'].replace(JSON.parse(result)['sources']+':','')"
},
"ruleExplore": {
"author": "$.author",
"bookList": "$.data",
"bookUrl": "<js>\nlet book_id = result.book_id;\nlet sources = result.source;\nlet tab = result.tab || '小说';\nlet url = result.toc_url || '';\n\nlet gydetail = {\n book_id: book_id,\n sources: sources,\n tab: tab,\n url: url\n}\ngydetail = java.base64Encode(JSON.stringify(gydetail));\n`data:;base64,${gydetail},{\"type\":\"gydetail\"}`\n</js>",
"coverUrl": "$.thumb_url",
"intro": "$.abstract",
"kind": "{{$.status}},{{$.score}},{{$.tags}},{{$.last_chapter_update_time}}",
"lastChapter": "{{$.source}} {{$.last_chapter_title}}",
"name": "$.book_name##(别名:.*?)",
"wordCount": "$.word_number"
},
"ruleSearch": {
"author": "$.author",
"bookList": "<js>\nconst res = JSON.parse(java.hexDecodeToString(result));\nconst {\n key,\n tab,\n sourcesKey,\n page,\n disabled_sources\n} = res;\nlet url = `/search?title=${key}&tab=${tab}&source=${sourcesKey}&page=${page}&disabled_sources=${disabled_sources}`;\nrequest(url);\n</js>\n$.data",
"bookUrl": "<js>\nlet book_id = result.book_id;\nlet sources = result.source;\nlet tab = result.tab || '小说';\nlet url = result.toc_url || '';\n\nlet gydetail = {\n book_id: book_id,\n sources: sources,\n tab: tab,\n url: url\n}\ngydetail = java.base64Encode(JSON.stringify(gydetail));\n`data:;base64,${gydetail},{\"type\":\"gydetail\"}`\n</js>",
"checkKeyWord": "我的@番茄",
"coverUrl": "$.thumb_url",
"intro": "$.abstract",
"kind": "{{$.status}},{{$.score}},{{$.tags}},{{$.last_chapter_update_time}}",
"lastChapter": "{{$.source}} {{$.last_chapter_title}}",
"name": "$.book_name##(别名:.*?)",
"wordCount": "$.word_number"
},
"ruleToc": {
"chapterList": "<js>\nconst res = JSON.parse(java.hexDecodeToString(result));\nconst {\n\tbook_name,\n\tauthor,\n\tabstract,\n\tthumb_url,\n book_id,\n tab,\n sources,\n url\n} = res;\nlet durChapterIndex = book.durChapterIndex;\njava.put('book_id', book_id);\nlet html = \"\";\nlet moreSettings = getVariable('更多设置');\nconst proxy = moreSettings && moreSettings['网络模式'] || '服务器';\nconst showSource = moreSettings && moreSettings['目录显示来源'] || 'true';\nconst gytoken = getToken();\nconst reading = moreSettings && moreSettings['同步书架'] || 'false';\nconst reading_toast = moreSettings && moreSettings['同步弹窗'] || 'true';\n\nif (url && proxy == \"本地\") {\n if (sources == \"69书吧\") {\n const ck69 = String(cookie.getCookie(\"https://www.69shuba.com\"));\n const headers = {\n \"Referer\": url,\n \"Cookie\": ck69,\n \"User-Agent\": java.getWebViewUA()\n };\n const op = JSON.stringify({\n headers\n });\n html = java.ajax(`${url},${op}`);\n } else {\n html = java.ajax(url);\n }\n\n if (html.includes(\"Just a moment...\") && sources === \"69书吧\") {\n cookie.removeCookie(\"https://www.69shuba.com\");\n java.longToast(\"需要真人验证,请进入任意书籍详情页过验证\");\n html = java.startBrowserAwait(url, \"需要真人验证,请进入任意书籍详情页过验证\").body();\n java.log(html);\n }\n}\n\n\nconst customVar = String(book.getVariable(\"custom\"));\nconst varia = JSON.stringify({\n custom: customVar == \"null\" ? \"\" : customVar\n});\n\nconst queryString = `book_id=${encodeURIComponent(book_id)}&source=${encodeURIComponent(sources)}&tab=${encodeURIComponent(tab)}&variable=${encodeURIComponent(varia)}`;\nconst data = request(`/catalog?${queryString}`, \"POST\", {\n html\n});\n\nconst device_type = checkEnv();\n\nconst typeMap = {\n \"小说\": {\n \"苹果\": 0,\n default: 8\n },\n \"听书\": {\n \"苹果\": 1,\n default: 32\n },\n \"漫画\": {\n \"苹果\": 2,\n default: 64\n },\n \"短剧\": {\n \"苹果\": 3,\n \"备用\": 8,\n // 墨听:运行时恢复短剧媒体容器,但避免源级静态扫描直接命中视频类型常量\n default: java.getAppVariant && !isSpecialMedia(sources) ? (2 * 2) : 8\n }\n};\nconst currentType = typeMap[tab] || typeMap[\"小说\"];\nif (device_type=='苹果') {\nbook.type = currentType[device_type];\n} else {\nbook.type = currentType.default;\n}\n\n\n\nconst syncBookShelf = (data, book, gytoken) => {\n if (!book) {\n return;\n }\n let book_order = book.order;\n if (book_order!=0) {\n \t book_order = 1;\n \t} else {\n \t\tbook_order = 2;\n \t\t}\n if (reading != \"true\") {\n return;\n }\n\n try {\n if (!data || !gytoken) {\n java.log(\"缺少必要参数\");\n return;\n }\n\n const parsedData = parseJsonSafely(data);\n if (!parsedData || !parsedData.data || !Array.isArray(parsedData.data) || !parsedData.data[durChapterIndex]) {\n return;\n }\n\n const ritem = parsedData.data[durChapterIndex];\n \nlet book_info = {\n book_name,\n author,\n abstract,\n thumb_url,\n book_id,\n tab,\n source: sources\n };\n if (!book_info || typeof book_info != \"object\") {\n return;\n }\n const rurl = `/add_book_to_book_shelf`;\n book_info.read_status = book_order;\n book_info.last_chapter_item_id = ritem.item_id || \"\";\n book_info.last_chapter_title = ritem.title || \"\";\n //java.toast(JSON.stringify(book_info));\n \ttry {\n \t\tconst check_book_url = `/check_book_in_book_shelf`;\n checkResponse = request(check_book_url, \"POST\", {book_id,source:sources,tab});\n} catch (e) {\n java.log(`检查书籍请求失败: ${e}`);\n}\n \n const check_data = parseJsonSafely(checkResponse);\n try {\n let chapterIndex = findItemIndex(parsedData.data,check_data.data.last_chapter_item_id)\n durChapterIndex = chapterIndex;\n if (book.durChapterIndex < chapterIndex) {\n \tbook_info.last_chapter_item_id = check_data.data.last_chapter_item_id;\n \t}\n \n if (chapterIndex != 0 && book.durChapterIndex < chapterIndex) {\n book.durChapterIndex = chapterIndex;\n }\n if (check_data.data.id) {\n book_info.id = check_data.data.id;\n const uurl = `/update_book_shelf`;\n if (book_order==1){\n \tlet up = request(uurl, \"POST\", book_info);\n if (reading_toast == \"true\") {\n \t java.toast(up)\n \t }\n \t}\n \n } else {\n if (book_order==1){\n let up = request(rurl, \"POST\", book_info);\n if (reading_toast == \"true\") {\n \t java.toast(up)\n \t}\n }}\n } catch (e) {\n try {\n if (book_order==1){\n let up = request(rurl, \"POST\", book_info);\n if (reading_toast == \"true\") {\n \t java.toast(up)\n \t}}\n } catch (e) {\n if (reading_toast == \"true\") {\n \t java.toast(`书架操作失败: ${e}`);\n \t}\n }\n\n }\n } catch (error) {\n java.log(`书籍同步流程异常: ${error}`);\n if (reading_toast == \"true\") {\n \t java.longToast(\"\\n同步阅读进度失败,但不影响阅读,可以前往登录关闭书架同步功能。\");\n \t}\n \n }\n};\n\ntry {syncBookShelf(data, book, gytoken);}catch{}\n\n\nfunction processCatalogList(data) {\n let catalog_list = parseJsonSafely(data).data;\n\n\n if (catalog_list && catalog_list.length > 0) {\n\n let lastItem = catalog_list[catalog_list.length - 1];\n\n\n if (lastItem && typeof lastItem == 'object' && lastItem.source && lastItem.title) {\n\n lastItem.title = lastItem.source + ':' + lastItem.title;\n }\n }\n return JSON.stringify({\n 'data': catalog_list\n });\n}\n\nif (showSource == \"true\") {\n processCatalogList(data);\n} else {\n data;\n}\n\n</js>$.data",
"chapterName": "$.title",
"chapterUrl": "<js>\nlet { tab, source: sources, title, item_id } = result;\nlet book_id = java.get('book_id');\nlet base_url = BaseUrl();\nlet url = result.toc_url || \"\";\n\nlet gycontent = { book_id, item_id, title, sources, tab, url };\nlet encodedGycontent = java.base64Encode(JSON.stringify(gycontent));\n\nlet content_url;\n\nif (sources == '卷') {\n content_url = item_id;\n} else if (['番茄', '七猫', '塔读'].includes(sources) && tab == \"小说\") {\n const fqssionid = java.getCookie('fanqienovel.com', 'sessionid');\n const sourcess = sources.replace('svip_', '');\n content_url = `data:;base64,${encodedGycontent},${JSON.stringify({\n type: \"gycontent\",\n js: `book ? result : '${base_url}/get_review?book_id=${book_id}&item_id=${item_id}&ssionid=${fqssionid}&source=${sourcess}'`\n })}`;\n} else {\n content_url = `data:;base64,${encodedGycontent},${JSON.stringify({ type: \"gycontent\" })}`;\n}\n</js>",
"updateTime": "$.first_pass_time"
},
"searchUrl": "<js>\nlet moreSettings = getVariable('更多设置');\n\nlet tab = moreSettings && moreSettings['搜索模式'] || '小说';\n\nlet sourcesKey = moreSettings && moreSettings[tab] || '全部';\n\nlet disabled_sources = moreSettings && moreSettings['强制搜索'] || \"0\";\nif (disabled_sources == \"false\") {\n disabled_sources = \"0\";\n} else if (disabled_sources == \"true\") {\n disabled_sources = \"1\";\n}\nlet gysearch = {\n key: key,\n tab: tab,\n sourcesKey: sourcesKey,\n page: page,\n disabled_sources:disabled_sources\n}\ngysearch = java.base64Encode(JSON.stringify(gysearch));\n`data:;base64,${gysearch},{\"type\":\"gysearch\"}`;\n</js>",
"weight": 0
}
]