🍅大灰狼聚合5.8.9.1(vip完全版)
大灰狼融合VIP5.0
大灰狼 (3463)3天前
更新日志请点击登录,更新书源中查看
书旗听书:登录书源后将模式切换为“听书”、来源切换为“书旗”,返回发现页长按刷新,即可使用官方“全部/男生/女生”听书发现。默认音色为 multi_role 多角色对话;详情页会显示本书可用音色及数字编号,右上角书籍变量填写编号即可切换。
基础使用教程:
1、搜索:
1)基础搜索:在发现页长按该书源,点击搜索,直接搜,搜索范围(全部来源小说)
2)进阶搜索:在登陆-书源设置中选择指定模式/来源,可搜索指定模式/来源内容
3)快捷搜索:使用特殊关键词(x,t,m,d,@)搜索
x:书名@来源 如(x:十日终焉@番茄)
t:书名@来源 如(t:十日终焉@番茄)
m:书名@来源 如(m:十日终焉@番茄)
d:书名@来源 如(d:十日终焉@番茄)
其中:x表示小说(可省略),t表示听书,m表示漫画,d表示短剧
冒号支持中文英文
2、发现页:
在书源设置中选择指定模式或者单个来源,然后刷新发现页即可
3、更多使用方式长按发现页书源选择登录一目了然
4、用户后台有视频教程
{
"bookSourceComment": "更新日志请点击登录,更新书源中查看\n\n书旗听书:登录书源后将模式切换为“听书”、来源切换为“书旗”,返回发现页长按刷新,即可使用官方“全部\/男生\/女生”听书发现。默认音色为 multi_role 多角色对话;详情页会显示本书可用音色及数字编号,右上角书籍变量填写编号即可切换。\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、更多使用方式长按发现页书源选择登录一目了然\n4、用户后台有视频教程\n ",
"bookSourceGroup": "大灰狼聚合",
"bookSourceName": "🍅大灰狼聚合5.8.9.1(vip完全版)",
"bookSourceType": 0,
"bookSourceUrl": "大灰狼融合VIP5.0",
"bookUrlPattern": "https?:\\\/\\\/(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z0-9-]+(?::\\d+)?\\\/detail.*",
"customButton": false,
"customOrder": 5,
"enabled": true,
"enabledCookieJar": false,
"enabledExplore": true,
"eventListener": false,
"exploreUrl": "<js>\nvar open_argument = source.getVariable();\nif (!open_argument || open_argument == '') {\n var initData = {\n tab: \"小说\",\n tone_id: \"默认音色\",\n shuqi_tone_id: \"multi_role\",\n server: host[0],\n sources: \"番茄\",\n source_type: \"男频\",\n find_source: \"番茄\",\n find_tab: \"小说\",\n fq_word_filter: \"不限\",\n fq_status_filter: \"不限\",\n fq_sort_filter: \"不限\",\n fqpara: \"on\",\n fqcommunity: \"on\",\n pstyle: \"0\"\n };\n source.setVariable(JSON.stringify(initData));\n open_argument = source.getVariable();\n}\nvar base_url = getArguments(open_argument, 'server') || host[0];\nvar ms = getArguments(open_argument, 'tone_id') || '';\nvar source_type = getArguments(open_argument, 'source_type') || '男频';\nvar tab = getArguments(open_argument, 'find_tab') || getArguments(open_argument, 'tab') || '小说';\nvar sources = getArguments(open_argument, 'sources') || '番茄';\nvar fxyjr = getArguments(open_argument, 'fxyjr');\nvar fq_word_filter = getArguments(open_argument, 'fq_word_filter') || '不限';\nvar fq_status_filter = getArguments(open_argument, 'fq_status_filter') || '不限';\nvar fq_sort_filter = getArguments(open_argument, 'fq_sort_filter') || '不限';\n\n\nfunction checkEnv() {\n let isModified = false;\n try {\n new Packages.io.legato.kazusa.utils.TimeoutCancellationException('');\n isModified = true;\n } catch (e) {\n isModified = typeof source.loginUi == 'function' ? false : true;\n }\n\n return isModified;\n}\n\nvar islyc = checkEnv();\nlet device_type = '安卓';\ntry {\n\t try {\n\t java.qread();\n\t device_type =\"轻阅读\";\n\t } catch {\n\t \tjava.deviceID();\n device_type = \"苹果\";\n\t \t}\n \n} catch (e) {\n try {\n java.androidId();\n device_type = \"安卓\";\n } catch (e) {\n device_type = \"安卓\";\n }\n}\n\nvar discoverySource = String(getArguments(open_argument, 'find_source') || '').trim();\n if (discoverySource.indexOf(',') >= 0) {\n discoverySource = discoverySource.split(',')[0].trim();\n }\n \/\/ “全部”只用于搜索;发现页必须落到一个真实平台,否则选择器会显示番茄但实际仍是全部。\n if (!discoverySource || discoverySource == '全部' || discoverySource == 'vip' || discoverySource == 'svip') {\n discoverySource = '番茄';\n }\n sources = discoverySource;\n setArguments('find_source', sources);\nvar sdtoken;\ntry {\n var loginInfoMap = source.getLoginInfoMap ? source.getLoginInfoMap() : {};\n sdtoken = String(loginInfoMap['手动填写番茄token(可不填)'] || '');\n} catch (e) {\n sdtoken = '';\n}\n\nvar rawCookie = getFanqieCookie() || sdtoken;\nvar match = rawCookie.match(\/sessionid=[^;]+\/);\nvar fqcookie = match ? match[0] : '';\nvar fqssionid = '';\nif (!fqcookie && (sources == '番茄' || sources == '全部')) {\n java.toast('您还未登陆番茄账号,无法同步数据哦!');\n} else {\n fqssionid = getSessionId(fqcookie)\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\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 = fqcookie.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.45\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.45\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: \"番茄个人中心\",\n url: fqsjurl,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 0.45\n }\n }, {\n title: \"个性推荐(番茄)\",\n url: fqtjurl,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 0.45\n }\n }, {\n title: \"历史阅读(番茄)\",\n url: fqlsurl,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 0.45\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\n \/\/ 番茄分类筛选只作用于小说的普通分类,不影响排行榜和其他来源。\n if (sources == '番茄' && tab == '小说') {\n var fqWordMap = {\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 };\n var fqStatusMap = {\n '不限': 'creation_status_default',\n '完结': 'creation_status_end',\n '连载': 'creation_status_loading',\n '半年内完结': 'creation_status_half_year_end',\n '3日内更新': 'creation_status_3day_update',\n '7日内更新': 'creation_status_7day_update',\n '1月内更新': 'creation_status_1month_update'\n };\n var fqSortMap = {\n '不限': 'sort_default',\n '热门': 'sort_hot',\n '新书': 'sort_new_book',\n '评分': 'sort_score',\n '字数': 'sort_word_number',\n '有声': 'sort_audio_book'\n };\n var hasActiveFilter = fq_word_filter != '不限' || fq_status_filter != '不限' || fq_sort_filter != '不限';\n if (hasActiveFilter) {\n var selectedItems = [\n fqWordMap[fq_word_filter] || fqWordMap['不限'],\n fqStatusMap[fq_status_filter] || fqStatusMap['不限'],\n fqSortMap[fq_sort_filter] || fqSortMap['不限']\n ].join(',');\n style_list.forEach(function(item) {\n if (!item || typeof item.url != 'string') return;\n var isCategory = item.url.indexOf('\/get_discover?') >= 0 &&\n item.url.indexOf('source=番茄') >= 0 &&\n item.url.indexOf('&type=') >= 0 &&\n item.url.indexOf('&bdtype=') < 0 &&\n item.url.indexOf('&is_ranking=1') < 0;\n if (isCategory && item.url.indexOf('&selected_items=') < 0) {\n item.url += '&selected_items=' + selectedItems;\n }\n });\n }\n }\n if (result.msg) {\n java.toast(result.msg);\n }\n} catch (e) {\n java.toast(\"发现样式获取失败\");\n}\nlet qtcookie = cookie.getCookie(base_url);\n\/\/let qtcookie = \"\"\nlet qtop = {\n method: \"GET\",\n headers: {\n cookie: qtcookie\n },\n};\n\n\n\nqtop = JSON.stringify(qtop);\nlet qtsjurl = base_url + '\/get_book_shelf,' + qtop;\n\nlet qtsj = [];\n\n\/\/ 仅 java.deviceID 可识别的旧 iOS 客户端自动使用兼容发现页;其他客户端默认使用新版。\nvar useModernExplore = fxyjr != \"on\" && device_type != '苹果';\nif (useModernExplore) {\n try {\n qtsj.push(createFilter(\n \"线路\",\n host,\n base_url,\n \"server\",\n 1\n ));\n qtsj.push(createFilter(\n \"模式\",\n [\"小说\", \"听书\", \"短剧\", \"漫画\"],\n tab,\n \"find_tab\",\n 0.33\n ));\n qtsj.push(createFilter(\n \"频道\",\n [\"男频\", \"女频\"],\n source_type,\n \"source_type\",\n 0.33\n ));\n let source_list = ['番茄', '七猫', '书旗', '塔读', '江湖', '小米', '米读', 'QQ阅读', '酷我小说','幻梦轻小说', '西瓜小说', '69书吧', '顶点', '爱下电子书', '伪69', '超会专属小说', '3A小说', '阅友小说', '笔趣阁22', '追更人', '久久小说', '52书库', '腐小说', '完本小说', '盐选文库', '有度中文', '三零读书', '鹿鹿', '星空小说', '365小说', '冷冷文学', '万相书城', '独步小说', '福书网', '速读谷', '全本同人'];\n if (tab == '听书') {\n source_list = ['番茄', '七猫', '书旗', '喜马拉雅', '酷我'];\n }\n if (tab == '漫画') {\n source_list = ['全免漫画', '番茄', '漫画屋', '漫画网', '漫客栈', '鸟韩漫', '新新漫画', '武芊漫画', '拷贝漫画', '漫画屋'];\n }\n if (tab == '短剧') {\n source_list = ['番茄', 'YY短剧', '东梨短剧', '星芽短剧', '极速影视'];\n }\n qtsj.push(createFilter(\n \"平台\",\n source_list,\n sources,\n \"find_source\",\n 1\n ));\n if (sources == '番茄' && tab == '小说') {\n qtsj.push(createFilter(\n \"字数\",\n [\"不限\", \"10万字以内\", \"30万字以内\", \"50万字以内\", \"30万字以上\", \"50万字以上\", \"100万字以上\", \"200万字以上\", \"300万字以上\", \"500万字以上\"],\n fq_word_filter,\n \"fq_word_filter\",\n 0.33\n ));\n qtsj.push(createFilter(\n \"状态\",\n [\"不限\", \"完结\", \"连载\", \"半年内完结\", \"3日内更新\", \"7日内更新\", \"1月内更新\"],\n fq_status_filter,\n \"fq_status_filter\",\n 0.33\n ));\n qtsj.push(createFilter(\n \"排序\",\n [\"不限\", \"热门\", \"新书\", \"评分\", \"字数\", \"有声\"],\n fq_sort_filter,\n \"fq_sort_filter\",\n 0.33\n ));\n }\n const excludeTitles = ['点击登录可切换来源', '切换后长按刷新即可'];\n style_list = style_list.filter(item => !excludeTitles.includes(item.title));\n } catch {}\n} else {\n\tif (device_type == '安卓'){\n\tqtsj.push({\n \"title\": \"下载阅读Sigma最新测试版\",\n \"url\": '{\\{java.longToast(\"本书源推荐使用最新版洛娅橙改版阅读Σ\\\\n请安装后重新导入书源\");java.startBrowser(\"https:\/\/legado.langge.cf\",\"下载阅读Sigma最新测试版\");}}',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 1\n }\n })}\n\t}\n\nif (qtcookie) {\nqtsj.push({\n title: \"大灰狼个人中心\",\n url: qtsjurl,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 0.45\n }\n})\n} else {\nqtsj.push({\n title: \"登录大灰狼书源\",\n url: `{\\{java.startBrowser(\"${base_url}\/login\",\"登录书源\");}}`,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 0.45\n }\n})\n}\n\n\nvar finalData = infoData.concat(groupDatas, style_list);\nfinalData = qtsj.concat(finalData);\nJSON.stringify(finalData);\n<\/js>",
"header": "{ \"User-Agent\":\"Mozilla\/5.0 (Linux; Android 6.0; Nexus 5 Build\/MRA58N) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/138.0.0.0 Mobile Safari\/537.36 Edg\/138.0.0.0\" }",
"jsLib": "var host = [\n 'https:\/\/v5.czyl.cf',\n 'https:\/\/api.langge.cf',\n 'http:\/\/219.154.201.122:5006',\n 'https:\/\/20.langge.tk',\n 'https:\/\/v4.czyl.cf',\n 'https:\/\/v2.czyl.cf',\n 'https:\/\/v7.czyl.cf',\n 'https:\/\/v8.czyl.cf',\n 'https:\/\/v9.czyl.cf',\n 'https:\/\/v10.czyl.cf'\n];\n\nfunction getArguments(open_argument, key) {\n try {\n open_argument = JSON.parse(open_argument);\n } catch (e) {\n open_argument = {\n tab: \"小说\",\n tone_id: \"默认音色\",\n shuqi_tone_id: \"multi_role\",\n server: host[0],\n sources: \"全部\",\n source_type: \"男频\",\n fqpara: \"on\",\n fqcommunity: \"on\",\n pstyle: \"0\",\n };\n }\n \/\/ 旧版本没有保存 fqpara 字段;缺失时按“开启”处理。\n \/\/ 用户明确选择 off 时仍然保留关闭状态。\n if (open_argument.fqpara === undefined || open_argument.fqpara === null || open_argument.fqpara === '') {\n open_argument.fqpara = 'on';\n }\n if (!open_argument.shuqi_tone_id || open_argument.shuqi_tone_id === '默认音色') {\n open_argument.shuqi_tone_id = 'multi_role';\n }\n if (key) {\n return open_argument[key];\n } else {\n return open_argument;\n }\n}\n\nfunction setArguments(key, value) {\n const {\n source\n } = this;\n let open_argument = source.getVariable();\n open_argument = getArguments(open_argument, '');\n open_argument[key] = value;\n open_argument = JSON.stringify(open_argument);\n source.setVariable(open_argument);\n return open_argument;\n}\n\nfunction getFanqieCookie() {\n var cookieManager = null;\n try {\n cookieManager = this.cookie;\n } catch (e) {}\n if (!cookieManager) {\n try {\n if (typeof cookie !== 'undefined') cookieManager = cookie;\n } catch (e) {}\n }\n if (!cookieManager || typeof cookieManager.getCookie !== 'function') return '';\n\n var hosts = [\n 'fanqienovel.com',\n 'https:\/\/fanqienovel.com\/',\n 'https:\/\/fanqienovel.com',\n 'https:\/\/api5-normal-sinfonlinec.fqnovel.com\/'\n ];\n var fallback = '';\n for (var i = 0; i < hosts.length; i++) {\n try {\n var value = String(cookieManager.getCookie(hosts[i]) || '');\n if (!fallback && value) fallback = value;\n if (\/(?:^|;\\s*)sessionid(?:_ss)?=\/.test(value)) return value;\n } catch (e) {}\n }\n return fallback;\n}\n\nfunction getSessionId(cookieValue) {\n var value = String(cookieValue || '');\n var match = value.match(\/(?:^|;\\s*)sessionid(?:_ss)?=([^;]+)\/);\n if (!match) return '';\n try {\n return decodeURIComponent(match[1]);\n } catch (e) {\n return match[1];\n }\n}\n\nfunction escapeFanqieCommunityText(value) {\n return String(value || '')\n .replace(\/&\/g, '&')\n .replace(\/<\/g, '<')\n .replace(\/>\/g, '>')\n .replace(\/\"\/g, '"');\n}\n\nfunction normalizeFanqieCommunityText(value) {\n return String(value || '').replace(\/\\s+\/g, ' ').trim();\n}\n\nfunction shortenFanqieCommunityText(value, maxLength) {\n var text = normalizeFanqieCommunityText(value);\n if (!maxLength || text.length <= maxLength) return text;\n return text.substring(0, Math.max(1, maxLength - 1)) + '\\u2026';\n}\n\nvar FANQIE_EMOJI_PATHS = {\n \"[微笑]\": \"\/static\/emoji\/emoji_dr_1.png\",\n \"[偷笑]\": \"\/static\/emoji\/emoji_dr_2.png\",\n \"[笑]\": \"\/static\/emoji\/emoji_dr_3.png\",\n \"[什么]\": \"\/static\/emoji\/emoji_dr_4.png\",\n \"[害羞]\": \"\/static\/emoji\/emoji_dr_5.png\",\n \"[爱慕]\": \"\/static\/emoji\/emoji_dr_6.png\",\n \"[飞吻]\": \"\/static\/emoji\/emoji_dr_7.png\",\n \"[奸笑]\": \"\/static\/emoji\/emoji_dr_8.png\",\n \"[尬笑]\": \"\/static\/emoji\/emoji_dr_9.png\",\n \"[思考]\": \"\/static\/emoji\/emoji_dr_10.png\",\n \"[撇嘴]\": \"\/static\/emoji\/emoji_dr_11.png\",\n \"[做鬼脸]\": \"\/static\/emoji\/emoji_dr_12.png\",\n \"[酷]\": \"\/static\/emoji\/emoji_dr_13.png\",\n \"[翻白眼]\": \"\/static\/emoji\/emoji_dr_14.png\",\n \"[惊呆]\": \"\/static\/emoji\/emoji_dr_15.png\",\n \"[震惊]\": \"\/static\/emoji\/emoji_dr_16.png\",\n \"[送心]\": \"\/static\/emoji\/emoji_dr_17.png\",\n \"[委屈]\": \"\/static\/emoji\/emoji_dr_18.png\",\n \"[快哭了]\": \"\/static\/emoji\/emoji_dr_19.png\",\n \"[笑哭]\": \"\/static\/emoji\/emoji_dr_20.png\",\n \"[哭]\": \"\/static\/emoji\/emoji_dr_21.png\",\n \"[大笑]\": \"\/static\/emoji\/emoji_dr_22.png\",\n \"[舔屏]\": \"\/static\/emoji\/emoji_dr_23.png\",\n \"[怒]\": \"\/static\/emoji\/emoji_dr_24.png\",\n \"[捂脸]\": \"\/static\/emoji\/emoji_dr_25.png\",\n \"[吐]\": \"\/static\/emoji\/emoji_dr_26.png\",\n \"[恐惧]\": \"\/static\/emoji\/emoji_dr_27.png\",\n \"[抓狂]\": \"\/static\/emoji\/emoji_dr_28.png\",\n \"[敬礼]\": \"\/static\/emoji\/emoji_dr_29.png\",\n \"[石化]\": \"\/static\/emoji\/emoji_dr_30.png\",\n \"[OK]\": \"\/static\/emoji\/emoji_dr_31.png\",\n \"[赞]\": \"\/static\/emoji\/emoji_dr_32.png\",\n \"[爱心]\": \"\/static\/emoji\/emoji_dr_33.png\",\n \"[伤心]\": \"\/static\/emoji\/emoji_dr_34.png\",\n \"[KISS]\": \"\/static\/emoji\/emoji_dr_35.png\",\n \"[懂了]\": \"\/static\/emoji\/emoji_dr_36.png\",\n \"[探究]\": \"\/static\/emoji\/emoji_dr_37.png\",\n \"[重拳出击]\": \"\/static\/emoji\/emoji_dr_38.png\",\n \"[吃瓜]\": \"\/static\/emoji\/emoji_dr_39.png\",\n \"[盯]\": \"\/static\/emoji\/emoji_dr_40.png\",\n \"[你细品]\": \"\/static\/emoji\/emoji_dr_41.png\",\n \"[赶稿中]\": \"\/static\/emoji\/emoji_dr_42.png\",\n \"[注意]\": \"\/static\/emoji\/emoji_dr_43.png\",\n \"[饱了]\": \"\/static\/emoji\/emoji_dr_44.png\",\n \"[码住]\": \"\/static\/emoji\/emoji_dr_45.png\",\n \"[学会了]\": \"\/static\/emoji\/emoji_dr_46.png\",\n \"[顶帖]\": \"\/static\/emoji\/emoji_dr_47.png\",\n \"[求爆更]\": \"\/static\/emoji\/emoji_dr_48.png\",\n \"[求关注]\": \"\/static\/emoji\/emoji_dr_49.png\",\n \"[我也强推]\": \"\/static\/emoji\/emoji_dr_50.png\",\n \"[雀食神作]\": \"\/static\/emoji\/emoji_dr_51.png\",\n \"[已种草]\": \"\/static\/emoji\/emoji_dr_52.png\",\n \"[书架加一]\": \"\/static\/emoji\/emoji_dr_53.png\",\n \"[歪头笑哭]\": \"\/static\/emoji\/emoticon_emoji_wtxk.png\",\n \"[神评]\": \"\/static\/emoji\/emoticon_xiaoqi_sp.png\",\n \"[狗头]\": \"\/static\/emoji\/emoticon_emoji_doge.png\",\n \"[哈哈]\": \"\/static\/emoji\/emoticon_emoji_hhh.png\",\n \"[流泪]\": \"\/static\/emoji\/emoticon_emoji_tears.png\",\n \"[可爱]\": \"\/static\/emoji\/emoticon_emoji_cute.png\",\n \"[色]\": \"\/static\/emoji\/emoticon_emoji_smimi.png\",\n \"[调皮]\": \"\/static\/emoji\/emoticon_emoji_tpi.png\",\n \"[小七_神评]\": \"\/static\/emoji\/emoticon_xiaoqi_sp.png\",\n \"[小七_狗头]\": \"\/static\/emoji\/emoticon_xiaoqi_gt.png\",\n \"[小七_鄙视]\": \"\/static\/emoji\/emoticon_xiaoqi_bs.png\",\n \"[小七_高兴]\": \"\/static\/emoji\/emoticon_xiaoqi_gx.png\",\n \"[小七_黑人问号]\": \"\/static\/emoji\/emoticon_xiaoqi_hrwh.png\",\n \"[小七_厉害]\": \"\/static\/emoji\/emoticon_xiaoqi_lh.png\",\n \"[小七_流泪]\": \"\/static\/emoji\/emoticon_xiaoqi_ll.png\",\n \"[小七_OK]\": \"\/static\/emoji\/emoticon_xiaoqi_ok.png\",\n \"[小七_色]\": \"\/static\/emoji\/emoticon_xiaoqi_s.png\",\n \"[小七_伤心]\": \"\/static\/emoji\/emoticon_xiaoqi_sx.png\",\n \"[小七_调皮]\": \"\/static\/emoji\/emoticon_xiaoqi_tq.png\",\n \"[小七_委屈]\": \"\/static\/emoji\/emoticon_xiaoqi_wh.png\",\n \"[小七_笑哭]\": \"\/static\/emoji\/emoticon_xiaoqi_xk.png\",\n \"[小七_笑嘻嘻]\": \"\/static\/emoji\/emoticon_xiaoqi_xxy.png\",\n \"[小七_斜眼笑]\": \"\/static\/emoji\/emoticon_xiaoqi_xyx.png\",\n \"[小七_赞美]\": \"\/static\/emoji\/emoticon_xiaoqi_zm.png\",\n \"[眯眼笑]\": \"\/static\/emoji\/emoticon_xiaoqi_xxy.png\"\n};\n\nfunction getFanqieEmojiMap(baseUrl) {\n var root = String(baseUrl || \"\").replace(\/\\\/$\/, \"\");\n var result = {};\n Object.keys(FANQIE_EMOJI_PATHS).forEach(function(name) {\n result[name] = root + FANQIE_EMOJI_PATHS[name];\n });\n return result;\n}\n\nvar FANQIE_CARD_EMOJI_TEXT = {\n \"[微笑]\":\"😊\",\"[偷笑]\":\"😏\",\"[笑]\":\"😁\",\"[什么]\":\"🤔\",\"[害羞]\":\"😳\",\"[爱慕]\":\"😍\",\"[飞吻]\":\"😘\",\"[奸笑]\":\"😏\",\"[尬笑]\":\"😅\",\"[思考]\":\"🤔\",\"[撇嘴]\":\"😒\",\"[做鬼脸]\":\"😜\",\"[酷]\":\"😎\",\"[翻白眼]\":\"🙄\",\"[惊呆]\":\"😲\",\"[震惊]\":\"😱\",\"[送心]\":\"❤️\",\"[委屈]\":\"🥺\",\"[快哭了]\":\"😭\",\"[笑哭]\":\"😂\",\"[哭]\":\"😢\",\"[大笑]\":\"😆\",\"[舔屏]\":\"😋\",\"[怒]\":\"😡\",\"[捂脸]\":\"🤦\",\"[吐]\":\"🤮\",\"[恐惧]\":\"😨\",\"[抓狂]\":\"😫\",\"[敬礼]\":\"🫡\",\"[石化]\":\"🗿\",\"[OK]\":\"👌\",\"[赞]\":\"👍\",\"[爱心]\":\"💖\",\"[伤心]\":\"💔\",\"[KISS]\":\"💋\",\"[懂了]\":\"✅\",\"[探究]\":\"🔎\",\"[重拳出击]\":\"👊\",\"[吃瓜]\":\"🍉\",\"[盯]\":\"👀\",\"[你细品]\":\"🤌\",\"[赶稿中]\":\"✍️\",\"[注意]\":\"⚠️\",\"[饱了]\":\"😋\",\"[码住]\":\"🔖\",\"[学会了]\":\"✅\",\"[顶帖]\":\"📌\",\"[求爆更]\":\"🙏\",\"[求关注]\":\"🔔\",\"[我也强推]\":\"🚀\",\"[雀食神作]\":\"👍\",\"[已种草]\":\"🌱\",\"[书架加一]\":\"📚+1\",\"[歪头笑哭]\":\"😂\",\"[神评]\":\"🌟\",\"[狗头]\":\"🐶\",\"[哈哈]\":\"😆\",\"[流泪]\":\"😭\",\"[可爱]\":\"🥰\",\"[色]\":\"😍\",\"[调皮]\":\"😜\",\"[小七_神评]\":\"🌟\",\"[小七_狗头]\":\"🐶\",\"[小七_鄙视]\":\"😒\",\"[小七_高兴]\":\"😊\",\"[小七_黑人问号]\":\"❓\",\"[小七_厉害]\":\"👍\",\"[小七_流泪]\":\"😭\",\"[小七_OK]\":\"👌\",\"[小七_色]\":\"😍\",\"[小七_伤心]\":\"💔\",\"[小七_调皮]\":\"😜\",\"[小七_委屈]\":\"🥺\",\"[小七_笑哭]\":\"😂\",\"[小七_笑嘻嘻]\":\"😁\",\"[小七_斜眼笑]\":\"😏\",\"[小七_赞美]\":\"👏\",\"[眯眼笑]\":\"😁\"\n};\n\nfunction splitFanqieGodNoteEmojis(value, baseUrl, sources) {\n var text = String(value || \"\").replace(\/\\[[^\\[\\]]+\\]\/g, function(token) {\n return FANQIE_CARD_EMOJI_TEXT[token] || token;\n }).replace(\/\\s+\/g, \" \").trim();\n return { text: text, images: \"\" };\n}\nfunction decodeFanqieNoteText(value) {\n return String(value || '')\n .replace(\/&#x([0-9a-f]+);\/gi, function(_match, code) {\n return String.fromCharCode(parseInt(code, 16));\n })\n .replace(\/&#([0-9]+);\/g, function(_match, code) {\n return String.fromCharCode(parseInt(code, 10));\n })\n .replace(\/"\/g, '\"')\n .replace(\/'|‘|’\/g, \"'\")\n .replace(\/<\/g, '<')\n .replace(\/>\/g, '>')\n .replace(\/&\/g, '&');\n}\n\n\/\/ 将后端返回的 <note> 神评标签转换成阅读\/源阅可渲染的 SVG 卡片。\nfunction getFanqieNoteAttribute(noteTag, name) {\n var reg = new RegExp(name + '=\\\"([^\\\"]*)\\\"');\n var match = String(noteTag || '').match(reg);\n if (!match) return '';\n try { return decodeFanqieNoteText(match[1]); } catch (e) { return match[1]; }\n}\n\nfunction cleanFanqieGodNoteText(value) {\n var text = String(value || '')\n .replace(\/<[^>]*>\/g, ' ')\n .replace(\/\\[fn=(\\d+)\\]\/g, '[表情]')\n .replace(\/\\s+\/g, ' ')\n .trim();\n return text;\n}\n\nfunction createFanqieGodNoteCard(noteTag, sources) {\n var noteUrl = getFanqieNoteAttribute(noteTag, 'ident');\n var rawNoteText = getFanqieNoteAttribute(noteTag, 'text');\n var noteText = cleanFanqieGodNoteText(rawNoteText);\n if (!noteText) return '';\n var originMatch = String(noteUrl || '').match(\/^(https?:\\\/\\\/[^\\\/]+)\/);\n var baseUrl = originMatch ? originMatch[1] : getArguments(this.source.getVariable(), 'server');\n var isShuqi = String(sources || '').indexOf('书旗') >= 0;\n var jc = 'js';\n try { if (this.checkEnv() == '改版') jc = 'click'; } catch (e) {}\n var actionUrl = String(noteUrl || '').replace(\/\\\\\/g, '\\\\\\\\').replace(\/'\/g, \"\\\\'\").replace(\/[\\r\\n]\/g, '');\n var title = String(sources || '番茄').replace(\/'\/g, '');\n var meta = '{\"' + jc + '\":\"showCmt(\\'' + actionUrl + '\\',\\'' + title + '\\',\\'段评\\')\",\"style\":\"FULL\"}';\n if (isShuqi) {\n var cardUrl = String(baseUrl || '').replace(\/\\\/$\/, '') + '\/shuqi_god_card_svg?text=' + encodeURIComponent(rawNoteText) + '&v=4';\n return '<img src=\"' + cardUrl + ',' + meta + '\">';\n }\n var emojiResult = splitFanqieGodNoteEmojis(rawNoteText, baseUrl, sources);\n var displayText = shortenFanqieCommunityText(cleanFanqieGodNoteText(emojiResult.text), 27);\n var svg = '<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"1000\" height=\"108\" viewBox=\"0 0 1000 108\">' +\n '<rect width=\"100%\" height=\"100%\" fill=\"rgba(255,255,255,0.55)\" rx=\"30\"\/>' +\n '<rect x=\"35\" y=\"28\" width=\"125\" height=\"52\" rx=\"16\" fill=\"#F06260\"\/>' +\n '<text x=\"97\" y=\"65\" font-size=\"32\" fill=\"#FFFFFF\" text-anchor=\"middle\" font-weight=\"bold\">神评论<\/text>' +\n '<text x=\"175\" y=\"67\" font-size=\"38\" fill=\"#333333\">' + escapeFanqieCommunityText(displayText) + '<\/text>' +\n '<\/svg>';\n return '<img src=\"data:image\/svg+xml;base64,' + this.java.base64Encode(svg) + ',' + meta + '\">';\n}\nfunction renderFanqieGodNoteCards(value, sources) {\n var runtime = this;\n return String(value || '').replace(\/<note\\b[^>]*>(?:<\\\/note>)?\/g, function(noteTag) {\n return createFanqieGodNoteCard.call(runtime, noteTag, sources) || '';\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\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\treturn img\n\t}\n\nlet svgs = [\"<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\n\/\/ 创建svg\n\nfunction showFanqieComment(url, title) {\n let { java } = this;\n title = title || '本章说';\n let pageUrl = String(url || '');\n if (!pageUrl) {\n try { java.toast('评论地址为空,请刷新本章'); } catch (e) {}\n return;\n }\n let islyc = this.checkEnv();\n try {\n if (islyc == '轻阅读') {\n java.startBrowserDp(pageUrl, title);\n return;\n }\n if (islyc == '改版') {\n let htmlContent = java.ajax(pageUrl);\n java.showBrowser(\n pageUrl,\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 return;\n }\n try {\n java.showReadingBrowser(pageUrl, title);\n return;\n } catch (e) {}\n java.startBrowser(pageUrl, title);\n } catch (e) {\n try { java.startBrowser(pageUrl, title); } catch (err) { java.log('打开番茄评论页失败:' + err); }\n }\n}\n\n\n\/\/ 生成本章说尾部卡片的点击协议,只负责打开官方评论聚合页,不复用段评点击逻辑。\nfunction endclick(url, title) {\n var jc = 'js';\n let islyc = this.checkEnv();\n if (islyc == '改版') {\n jc = 'click';\n }\n title = title || '本章说';\n return `\"${jc}\":\"showFanqieComment('${url}','${title}')\"`;\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 = renderFanqieGodNoteCards.call(this, content, sources);\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\/\/ 创建神评论\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 isQimaoComment = String(url || '').indexOf('\/qimao_comment?') >= 0;\n let htmlContent = isQimaoComment ? '' : cache.get(paradata+ident+\"new4\");\n if (!htmlContent || String(htmlContent).indexOf(\"评论\") == -1) {\n java.longToast(`\\n${paradata} 初始化${ident}页面,请稍等~`);\n try {\n htmlContent = java.ajax(url);\n } catch {\n\n }\n\n if (!isQimaoComment) cache.put(paradata+ident+\"new4\", htmlContent);\n java.longToast(`\\n${paradata} 初始化${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\/\/源阅段评\n\nfunction normalizeBubbleTemplateConfig(value, origin) {\n var result = {};\n if (!value || typeof value != 'object') return result;\n Object.keys(value).forEach(function(key) {\n var item = value[key];\n var data = item && typeof item == 'object' ? item : {svg: String(item || '')};\n var svg = String(data.svg || '');\n var renderPath = String(data.renderPath || data.renderUrl || '');\n if (svg.indexOf('<svg') < 0 && !renderPath) return;\n result[String(key)] = {\n name: String(data.name || ('样式 ' + key)),\n type: String(data.type || (renderPath ? 'image' : 'svg')),\n svg: svg,\n renderPath: renderPath,\n _origin: String(origin || '').replace(\/\\\/$\/, '')\n };\n });\n return result;\n}\n\nfunction getBubbleTemplates() {\n var now = Date.now();\n var memoryKey = '__bubble_templates_cache_v3__';\n try {\n if (this.cache && typeof this.cache.getFromMemory === 'function') {\n var cached = String(this.cache.getFromMemory(memoryKey) || '');\n if (cached) {\n var memory = JSON.parse(cached);\n if (memory && memory.templates && Object.keys(memory.templates).length && now - Number(memory.time || 0) < 30000) {\n return memory.templates;\n }\n }\n }\n } catch (e) {}\n var server = String(getArguments(this.source.getVariable(), 'server') || host[0]).replace(\/\\\/$\/, '');\n var servers = [server].concat(host || []).filter(function(value, index, values) {\n value = String(value || '').replace(\/\\\/$\/, '');\n return value && values.map(function(item) { return String(item || '').replace(\/\\\/$\/, ''); }).indexOf(value) == index;\n });\n var paths = ['\/api\/bubbles?_=','\/static\/js\/bubble_templates.json?_='];\n for (var i = 0; i < servers.length; i++) {\n var currentServer = String(servers[i]).replace(\/\\\/$\/, '');\n for (var pathIndex = 0; pathIndex < paths.length; pathIndex++) {\n try {\n var raw = String(this.java.ajax(currentServer + paths[pathIndex] + now) || '{}');\n var templates = normalizeBubbleTemplateConfig(JSON.parse(raw), currentServer);\n if (Object.keys(templates).length) {\n try {\n if (this.cache && typeof this.cache.putMemory === 'function') {\n this.cache.putMemory(memoryKey, JSON.stringify({ time: now, templates: templates }));\n }\n } catch (e) {}\n return templates;\n }\n } catch (e) {\n try { this.java.log('气泡模板加载失败: ' + e); } catch (_) {}\n }\n }\n }\n var fallback = {\n '0': {name: '经典气泡', type: 'svg', svg: svgs[0], renderPath: '', _origin: server}\n };\n try {\n if (this.cache && typeof this.cache.putMemory === 'function') {\n this.cache.putMemory(memoryKey, JSON.stringify({ time: now, templates: fallback }));\n }\n } catch (e) {}\n return fallback;\n}\n\nfunction createSvg(number, encodedUrl, sources) {\n var displayText = number > 99 ? \"99+\" : number.toString();\n let configuredColor = '';\n try {\n configuredColor = String(getArguments(this.source.getVariable(), \"plcolor\") || '').trim();\n } catch (e) {}\n let plcolor = \/^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$\/.test(configuredColor) ? configuredColor : \"#A7A7A7\";\n let bubbleStyle = String(getArguments(this.source.getVariable(), 'pstyle') || '0');\n let templates = getBubbleTemplates.call(this);\n if (!templates[bubbleStyle]) bubbleStyle = Object.keys(templates)[0] || '0';\n let selectedTemplate = templates[bubbleStyle] || templates['0'];\n var jc = 'js';\n let islyc = this.checkEnv();\n if (islyc == \"改版\") jc = 'click';\n let style = 'text';\n var action = '{\"' + jc + '\":\"showCmt(\\'' + encodedUrl + '\\', \\'' + sources + '\\' ,\\'段评\\')\",\"style\":\"' + style + '\"}';\n\n if (selectedTemplate && selectedTemplate.renderPath) {\n var renderPath = String(selectedTemplate.renderPath || '');\n var renderUrl = \/^https?:\\\/\\\/\/i.test(renderPath)\n ? renderPath\n : String(selectedTemplate._origin || getArguments(this.source.getVariable(), 'server') || host[0]).replace(\/\\\/$\/, '') + (renderPath.charAt(0) == '\/' ? renderPath : '\/' + renderPath);\n renderUrl += (renderUrl.indexOf('?') >= 0 ? '&' : '?') + 'count=' + encodeURIComponent(displayText) + '&color=' + encodeURIComponent(plcolor);\n return renderUrl + ',' + action;\n }\n\n let color = plcolor;\n let tccolor = 'transparent';\n let fontcolor = plcolor;\n let svg = selectedTemplate && selectedTemplate.svg ? selectedTemplate.svg : svgs[0];\n svg = svg.replace(\/\\{\\{color\\}\\}\/g, color)\n .replace(\/\\{\\{tccolor\\}\\}\/g, tccolor)\n .replace(\/\\{\\{count\\}\\}\/g, displayText)\n .replace(\/\\$color\/g, color)\n .replace(\/\\$tccolor\/g, tccolor)\n .replace(\/\\$fontcolor\/g, fontcolor)\n .replace(\/\\$displayText\/g, displayText);\n var encodedSvg = this.java.base64Encode(svg);\n return 'data:image\/svg+xml;base64,' + encodedSvg + ',' + action;\n}\n\n\n\/\/ 显示弹窗\n\n\n\nvar CompatibilityUtils = {\n hasJavaClass: function(className) {\n try {\n var parts = className.split('.');\n var obj = Packages;\n for (var i = 0; i < parts.length; i++) {\n obj = obj[parts[i]];\n if (!obj) return false;\n }\n return true;\n } catch (e) {\n return false;\n }\n },\n \n safeImport: function(javaImport) {\n var classes = [\n 'android.util.Base64',\n 'java.lang.String', \n 'java.net.URL',\n 'okhttp3.HttpUrl'\n ];\n \n classes.forEach(function(className) {\n if (this.hasJavaClass(className)) {\n try {\n var classObj = eval('Packages.' + className);\n javaImport.importClass(classObj);\n } catch (e) {\n java.log('无法导入类: ' + className);\n }\n }\n }.bind(this));\n \n return javaImport;\n }\n};\n\n\nvar javaImport = CompatibilityUtils.safeImport(new JavaImporter());\n\nwith(javaImport) {\n \n\n function getSubDomain(url) {\n let baseUrl = getBaseUrl(url);\n if (!baseUrl) {\n return url;\n }\n try {\n let mURL = URL(baseUrl);\n let host = mURL.host;\n if (isIPAddress(host)) return host;\n return HttpUrl.parse(baseUrl).topPrivateDomain() || host;\n } catch (e) {\n this.java.log(e);\n return baseUrl;\n }\n }\n\n function getDomain(url) {\n let baseUrl = getBaseUrl(url);\n if (!baseUrl) {\n return url;\n }\n try {\n return URL(baseUrl).host;\n } catch (e) {\n return baseUrl;\n }\n }\n \/**\n * 移除cookie\n *\/\n function removeCookie(url) {\n const {\n cookie\n } = this;\n cookie.removeCookie(url);\n let domains = [getDomain(url), getSubDomain(url)];\n domains.forEach((domain) => {\n cookie.removeCookie(domain);\n });\n }\n}\n\nfunction getBaseUrl(url) {\n if (!url) {\n return null;\n }\n url = String(url);\n if (url.match(\/https?:\\\/\\\/\/i)) {\n var index = url.indexOf(\"\/\", 9);\n return index == -1 ? url : url.substring(0, index);\n }\n return null;\n}\n\nfunction isIPv4Address(ip) {\n ip = String(ip);\n let parts = ip.split(\".\");\n if (parts.length !== 4) return false;\n\n for (let part of parts) {\n if (!\/^\\d+$\/.test(part)) return false; \/\/ 必须是数字\n if (part.length > 1 && part[0] === \"0\") return false; \/\/ 禁止前导零\n let num = parseInt(part, 10);\n if (num < 0 || num > 255) return false; \/\/ 范围检查\n }\n return true;\n}\n\nfunction isIPv6Address(ip) {\n ip = String(ip);\n \/\/ 处理双冒号(最多出现一次)\n if (ip.includes(\":::\")) return false;\n let doubleColonCount = (ip.match(\/::\/g) || []).length;\n if (doubleColonCount > 1) return false;\n\n \/\/ 分割成组\n let groups = ip.split(\":\");\n let validGroupCount = 8;\n let actualGroupCount = groups.filter((g) => g !== \"\").length;\n\n \/\/ 验证组数\n if (doubleColonCount === 1) {\n if (actualGroupCount > validGroupCount - 1) return false;\n } else {\n if (groups.length !== validGroupCount) return false;\n }\n\n \/\/ 验证每组内容\n for (let group of groups) {\n if (group === \"\") continue; \/\/ 跳过空组(双冒号部分)\n if (!\/^[0-9a-fA-F]{1,4}$\/.test(group)) return false; \/\/ 1-4位十六进制\n }\n return true;\n}\n\nfunction isIPAddress(input) {\n return isIPv4Address(input) || isIPv6Address(input);\n}\n\nfunction getKey(key) {\n let parts = key.split(\";\");\n for (let part of parts) {\n if (part.includes(\"qttoken\")) {\n return part.split(\"=\")[1];\n }\n }\n return \"\";\n}\n\nfunction paraForiOS(html, sources) {\n html = renderFanqieGodNoteCards.call(this, 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, '&');\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(\/"\/g, '\"');\n}\n\n\n\n\n\/\/按钮切换时改变源变量并刷新发现\nfunction show(m, t) {\n const {\n java,\n source\n } = this;\n \/\/源变量为空时刷新发现,重置源变量\n source.getVariable()==''?java.refreshExplore():'';\/\/\n var data = JSON.parse(source.getVariable());\n \/\/java.log(data);\n data[t] = m;\n if (t==\"find_tab\"){\n \tdata[\"find_source\"]=\"番茄\";\n \t}\n \/\/java.longToast(JSON.stringify(data));\n source.setVariable(JSON.stringify(data, null, 2));\n java.refreshExplore();\n}\n\n\/**\n * 创建筛选器配置\n * @param {string} title - 筛选器标题\n * @param {string[]} chars - 选项列表\n * @param {string} defaultVal - 默认值\n * @param {string} paramKey - URL参数名\n * @param {number} size - 布局占比(0-1之间,如0.33表示33%)\n * @returns {Object} 筛选器配置对象\n *\/\nfunction createFilter(title, chars, defaultVal, paramKey, size) {\n return {\n title: title,\n type: \"select\",\n chars: chars,\n default: defaultVal,\n action: `show(infoMap['${title}'],'${paramKey}')`,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: size\n }\n };\n}\n\nfunction removeAllImgTags(htmlString) {\n const imgTagRegex = \/<img\\b[^>]*>|<\\\/img>|<img\\b[^>]*\\\/>\/gi;\n return htmlString.replace(imgTagRegex, '');\n}\n\n\n\/\/ 书旗音色使用稳定数字编号;真实Key只在请求前转换。\nvar SHUQI_TONE_OPTIONS = [\n {number: '1', name: '多角色对话', key: 'multi_role'},\n {number: '2', name: '双角色对话', key: 'double_role'},\n {number: '3', name: '成熟男人', key: 'mingge'},\n {number: '4', name: '甜美少女', key: 'yifei'},\n {number: '5', name: '阳光青年', key: 'xiaohou'},\n {number: '6', name: '知性主播', key: 'qianxue'},\n {number: '7', name: '沉稳男士', key: 'SQ_M03_PB_1218_01'},\n {number: '8', name: '温婉淑女', key: 'F1029_PB_1128'},\n {number: '9', name: '清朗学长', key: 'SQ_iVc8TnhZXioTB52v'},\n {number: '10', name: '清冷校花', key: 'SQ_5Oh4F5zWa8EZbWvr'},\n {number: '11', name: '沉稳青叔', key: 'zh_male_quarkttsV1_novel_King196'},\n {number: '12', name: '博主小帅', key: 'zh-CN-YunxiNeural'},\n {number: '13', name: '博主小美', key: 'zh-CN-XiaochenNeural'},\n {number: '14', name: '敏敏郡主', key: 'zhaomin'},\n {number: '15', name: '无忌哥哥', key: 'wuji'}\n];\n\nfunction getShuqiToneOption(value) {\n value = String(value == null ? '' : value).trim();\n for (var i = 0; i < SHUQI_TONE_OPTIONS.length; i++) {\n var option = SHUQI_TONE_OPTIONS[i];\n if (value == option.number || value == option.name || value == option.key) return option;\n }\n return null;\n}\n\nfunction resolveShuqiToneId(value) {\n var option = getShuqiToneOption(value);\n return option ? option.key : String(value || 'multi_role');\n}\n\nfunction getShuqiToneNumber(value) {\n var option = getShuqiToneOption(value);\n return option ? option.number : String(value || '1');\n}\n\nfunction getShuqiToneName(value) {\n var option = getShuqiToneOption(value);\n return option ? option.name : String(value || '多角色对话');\n}\n",
"lastUpdateTime": "1786614086775",
"loginUi": "[\n {\n \"name\": \"邮箱\",\n \"type\": \"text\"\n },\n {\n \"name\": \"密码\",\n \"type\": \"password\"\n },\n {\n \"name\": \"♥登录书源\",\n \"type\": \"button\",\n \"action\": \"login(true)\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },\n {\n \"name\": \"🔐注册书源\",\n \"type\": \"button\",\n \"action\": \"register()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },\n {\n \"name\": \" 🔚 退出登录\",\n \"type\": \"button\",\n \"action\": \"logout()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },\n {\n \"name\": \"🏝用户后台\",\n \"type\": \"button\",\n \"action\": \"loginqt()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },\n {\n \"name\": \"⚙️ 书源设置中心\",\n \"type\": \"button\",\n \"action\": \"openSourceSettings()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 1\n }\n },\n {\n \"name\": \"🔮 检测登录\",\n \"type\": \"button\",\n \"action\": \"checkStatus()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },\n {\n \"name\": \"☕打赏享福利\",\n \"type\": \"button\",\n \"action\": \"vip()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },\n {\n \"name\": \"❇️ 更新书源\",\n \"type\": \"button\",\n \"action\": \"renderVersionPage()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },\n {\n \"name\": \"🍅番茄登录\",\n \"type\": \"button\",\n \"action\": \"fq_login()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },\n {\n \"name\": \"⛔️清空设置\",\n \"type\": \"button\",\n \"action\": \"reCookieSettings()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },\n {\n \"name\": \"✨设置检测\",\n \"type\": \"button\",\n \"action\": \"applyCookieSettings()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },\n {\n \"name\": \"📌永久发布页📌\",\n \"type\": \"button\",\n \"action\": \"api()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },\n {\n \"name\": \" 🗑 清除设备\",\n \"type\": \"button\",\n \"action\": \"clearDevice()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },\n {\n \"name\": \"🎚切换服务器\",\n \"type\": \"button\",\n \"action\": \"set_server()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },\n {\n \"name\": \"♻️检测当前服务器\",\n \"type\": \"button\",\n \"action\": \"checkNet()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },\n {\n \"name\": \"📐 使用教程\",\n \"type\": \"button\",\n \"action\": \"jc()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },\n {\n \"name\": \"📡 发现页兼容\",\n \"type\": \"button\",\n \"action\": \"fxyjr()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },\n {\n \"name\": \"自定义搜索源(多个用英文,分割)\",\n \"type\": \"text\"\n },\n {\n \"name\": \"自定义服务器(可不填)\",\n \"type\": \"text\"\n },\n {\n \"name\": \"自定义评论颜色(可不填)\",\n \"type\": \"text\"\n },\n {\n \"name\": \"手动填写番茄token(可不填)\",\n \"type\": \"text\"\n }\n]",
"loginUrl": "\/\/ 当前书源版本号,切勿修改,否则影响更新的识别\nconst localVersion = '5.8.9.1';\n\nlet controlUrl = host[0];\n\nfunction login(flag) {\n if (flag == undefined) {\n result = JSON.parse(source.getLoginInfo())\n } else {\n java.longToast(\"\\n\\n💞正在登录中...\")\n putLoginInfo(JSON.stringify(result))\n }\n let base_url = getArgument('server') || host[0]\n let zdyserver = String(result['自定义服务器(可不填)']);\n if (zdyserver.includes('http')) {\n setArgument('server', zdyserver);\n if (getKey(String(cookie.getCookie(base_url)))) {\n let cookies = cookie.getCookie(base_url)\n try {\n cookie.removeCookie(base_url)\n } catch (e) {}\n cookie.setCookie(zdyserver, cookies)\n }\n java.toast(`\\n\\n当前服务器为自定义服务器\\n${zdyserver}\\n\\n切换服务器请先清空服务器地址中的数据`);\n }\n let configuredPlcolor = String(result['自定义评论颜色(可不填)'] || '').trim();\n let plcolor = configuredPlcolor || '#A7A7A7';\n setArgument('plcolor', plcolor);\n let zdysources = String(result['自定义搜索源(多个用英文,分割)'] || '');\n if (zdysources) {\n setArgument('sources', zdysources);\n };\n base_url = getArgument('server')\n let register_email = String(result['邮箱'])\n let password = String(result['密码'])\n let key = String(result['密钥'] || '')\n\n if ((register_email && password || key) && !String(getKey(String(cookie.getCookie(base_url))))) {\n try {\n cookie.removeCookie(base_url)\n } catch (e) {}\n let deviceKey = '';\n try {\n deviceKey = java.deviceID();\n } catch (e) {\n deviceKey = java.androidId();\n };\n let deviceId = deviceKey;\n if (register_email && password) {\n let options = JSON.stringify({\n method: 'POST',\n headers: {\n 'Content-Type': 'application\/json'\n },\n body: JSON.stringify({\n register_email: result['邮箱'],\n password: result['密码']\n })\n })\n try {\n let data = JSON.parse(java.ajax(`${base_url}\/login_api,${options}`))\n if (data.code == 0) {\n \/\/java.toast(deviceId)\n java.toast(\"\\n\\n✅️登录成功\")\n cookie.setCookie(base_url, `qttoken=${data.key};deviceId=${deviceId}`);\n for (var _h = 0; _h < host.length; _h++) { try { cookie.setCookie(host[_h], `qttoken=${data.key};deviceId=${deviceId}`); } catch(_e){} }\n result['密钥'] = data.key\n setArgument('qttoken', data.key);\n putLoginInfo(JSON.stringify(result))\n } else {\n java.toast('\\n\\n💔' + data.msg || \"登录失败,请重试!\")\n }\n } catch (e) {\n java.toast(\"\\n\\n💔登录失败,请重试!\\n\" + e.message)\n }\n } else {\n cookie.setCookie(base_url, `qttoken=${key};deviceId=${deviceId}`)\n let res = java.ajax(`${base_url}\/user_api,{\"method\":\"POST\",\"headers\":{\"cookie\":\"${cookie.getCookie(base_url)}\"}}`)\n try {\n res = JSON.parse(res)\n if (res.id != undefined) {\n setArgument('qttoken', key);\n java.toast('\\n\\n密钥登录成功')\n result['邮箱'] = res.email\n putLoginInfo(JSON.stringify(result))\n } else {\n throw new Error()\n }\n } catch (e) {\n java.log(e)\n java.toast(\"\\n\\n💔登录失败\")\n }\n }\n } else if (flag && String(getKey(String(cookie.getCookie(base_url))))) {\n java.toast(\"\\n\\n当前✅️已登录,请🚫退出登录后重新登录\");\n \/\/checkStatus();\n } else if (flag) {\n java.toast(\"\\n\\n⛔️请先填写邮箱和密码\");\n }\n}\n\n\n\n\n\nfunction applyCookieSettings() {\n let controlUrla = getArgument('controlUrl') || controlUrl;\n var cookieStr = String(cookie.getCookie(controlUrla));\n\n var cookieData = {};\n if (cookieStr && cookieStr !== 'null' && cookieStr !== 'undefined' && cookieStr !== '') {\n var pairs = cookieStr.split('; ');\n for (var i = 0; i < pairs.length; i++) {\n var pair = pairs[i];\n var eqIndex = pair.indexOf('=');\n if (eqIndex > -1) {\n var key = pair.substring(0, eqIndex).trim();\n var value = decodeURIComponent(pair.substring(eqIndex + 1).trim());\n cookieData[key] = value;\n }\n }\n }\n\n var displayConfig = [{\n key: 'server',\n label: '服务器',\n defaultValue: host[0]\n },\n {\n key: 'proxy',\n label: '访问模式',\n defaultValue: '云端'\n },\n {\n key: 'tab',\n label: '阅读模式',\n defaultValue: '小说'\n },\n {\n key: 'fqcommunity',\n label: '本章说',\n defaultValue: 'on'\n },\n {\n key: 'sources',\n label: '书源选择',\n defaultValue: '全部'\n },\n {\n key: 'fqpara',\n label: '段评显示',\n defaultValue: 'on'\n },\n {\n key: 'disabled_sources',\n label: '强制搜索',\n defaultValue: '0'\n },\n {\n key: 'reading',\n label: '同步书架',\n defaultValue: '0'\n },\n {\n key: 'info',\n label: '完整简介',\n defaultValue: 'on'\n }\n ];\n\n var valueMap = {\n \/\/ 访问模式\n '本地': '💻 本地',\n '云端': '☁️ 云端',\n\n \/\/ 开关状态\n 'on': '✅ 开启',\n 'off': '❌ 关闭',\n '1': '✅ 开启',\n '0': '❌ 关闭',\n\n \/\/ 频道类型\n '男频': '👨 男频',\n '女频': '👩 女频',\n\n \/\/ 内容类型\n '小说': '📖 小说',\n '听书': '🎧 听书',\n '漫画': '🎨 漫画',\n '短剧': '🎬 短剧',\n\n \/\/ 来源类型\n 'vip': '💎 VIP专属',\n 'svip': '👑 SVIP专属',\n '推荐': '⭐ 网友推荐',\n '全部': '📚 全部'\n };\n\n\n var displayLines = [];\n\n for (var i = 0; i < displayConfig.length; i++) {\n var config = displayConfig[i];\n var argumentValue = getArgument(config.key);\n var cookieValue = cookieData[config.key];\n var value = argumentValue || cookieValue || config.defaultValue;\n\n \/\/ 检测只补齐缺失配置,不能用旧 Cookie 覆盖用户当前选择。\n if (!argumentValue) {\n setArgument(config.key, cookieValue || config.defaultValue);\n }\n\n var displayValue = valueMap[value] || value;\n if (config.key === 'sources' && displayValue.length > 25) {\n displayValue = displayValue.substring(0, 25) + '...';\n }\n\n displayLines.push(config.label + ': ' + displayValue);\n }\n\n \/\/ 构建提示模板\n var template = '✨ 当前配置\\n';\n template += '━━━━━━━━━━━━━━━━\\n';\n template += displayLines.join('\\n');\n template += '\\n━━━━━━━━━━━━━━━━\\n';\n template += '❤️当前应用的 *⚙️长按书源登录设置* 中的配置哦❤️'\n\n \/\/ 显示提示\n java.longToast(template);\n\n return displayLines.length;\n}\n\n\nfunction reCookieSettings() {\n var current = {};\n try {\n current = JSON.parse(String(source.getVariable() || '{}'));\n } catch (e) {}\n\n \/\/ 恢复书源默认配置;搜索默认全部来源,发现页默认番茄。\n var defaults = {\n _settings_version: 1,\n server: host[0],\n controlUrl: controlUrl,\n tab: '小说',\n sources: '全部',\n find_tab: '小说',\n find_source: '番茄',\n source_type: '男频',\n proxy: '云端',\n tone_id: '默认音色',\n shuqi_tone_id: 'multi_role',\n fq_word_filter: '不限',\n fq_status_filter: '不限',\n fq_sort_filter: '不限',\n fqpara: 'on',\n fqcommunity: 'on',\n reading: '0',\n info: 'on',\n close_img: 'off',\n disabled_sources: '0',\n fxyjr: 'off',\n plcolor: '#A7A7A7',\n pstyle: '0'\n };\n\n \/\/ 登录信息独立保存;源变量中的登录密钥也必须保留。\n if (current && current.qttoken) {\n defaults.qttoken = current.qttoken;\n }\n source.setVariable(JSON.stringify(defaults));\n\n \/\/ 只清除旧设置 Cookie,不能删除 qttoken、deviceId 或番茄登录 Cookie。\n var settingCookieKeys = [\n 'source_settings_result', 'server', 'proxy', 'tab', 'sources', 'source',\n 'media', 'find_tab', 'find_source', 'source_type', 'tone_id',\n 'shuqi_tone_id', 'fq_word_filter', 'fq_status_filter', 'fq_sort_filter',\n 'fqpara', 'fqcommunity', 'reading', 'info', 'close_img',\n 'disabled_sources', 'fxyjr', 'plcolor', 'pstyle'\n ];\n for (var i = 0; i < settingCookieKeys.length; i++) {\n try {\n cookie.setCookie(controlUrl, settingCookieKeys[i] + '=; Max-Age=0; Path=\/');\n } catch (e) {}\n }\n\n applyCookieSettings();\n try { java.refreshExplore(); } catch (e) {}\n}\n\/\/ 检测服务器\nfunction checkNet() {\n let url = getArgument('server');\n java.longToast(`\\n\\n♻️正在检测:${url}\\n请稍等~`);\n let date1 = new Date().getTime();\n let html = java.ajax(url + '\/health');\n let date2 = new Date().getTime();\n let t = date2 - date1;\n let res = {};\n try {\n res = JSON.parse(html);\n } catch {}\n let c = res.status != \"healthy\";\n let code = 1;\n let time = t \/ 1000 + 's';\n let logTime = '【' + url + '】\\n┋┋\\n' + '解析时间:' + time;\n if (c || t > 5000) {\n java.longToast('\\n💔【访问失败提示】\\n' + '┏┅━┅━┅━┅━┅┅━┅━┅┓\\n┋┋\\n' + logTime + '\\n┋┋\\n♣️当前接口无法访问(可能被墙)♣️\\n┋┋\\n请切换其他接口\/切换网络环境\\n┋┋' + '\\n┗┅━┅━┅━┅━┅┅━┅━┅┛');\n } else if (t < 1000) {\n java.longToast('\\n💖【网络环境优良】\\n' + '┏┅━┅━┅━┅━┅┅━┅━┅┓\\n┋┋\\n' + logTime + '\\n┋┋\\n❤️延迟低,推荐使用此接口❤️\\n┋┋\\n网络环境优良,请继续保持状态\\n┋┋' + '\\n┗┅━┅━┅━┅━┅┅━┅━┅┛');\n } else if (t >= 1000 && t < 2000) {\n java.longToast('\\n💛【网络环境一般】\\n' + '┏┅━┅━┅━┅━┅┅━┅━┅┓\\n┋┋\\n' + logTime + '\\n┋┋\\n♦️延迟一般,勉强可使用♦️\\n┋┋\\n请切换其他接口或切换网络环境\\n┋┋' + '\\n┗┅━┅━┅━┅━┅┅━┅━┅┛');\n } else if (t >= 2000 && t < 5000) {\n java.longToast('\\n💔【网络环境堪忧】\\n' + '┏┅━┅━┅━┅━┅┅━┅━┅┓\\n┋┋\\n' + logTime + '\\n┋┋\\n♠延迟过高,不建议使用♠\\n┋┋\\n请切换其他接口或切换网络环境\\n┋┋' + '\\n┗┅━┅━┅━┅━┅┅━┅━┅┛');\n }\n}\n\n\n\n\nfunction isVips(res) {\n let isVIP = '';\n let vipEndTime = res.vip_end_time;\n let formattedDate = '';\n\n if (vipEndTime && vipEndTime !== 0) {\n let date = new Date(vipEndTime * 1000);\n formattedDate = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;\n let currentTime = Math.floor(Date.now() \/ 1000);\n let remainingDays = Math.ceil((vipEndTime - currentTime) \/ (24 * 60 * 60));\n if (currentTime > vipEndTime) {\n isVIP = `${res.vip_level === 1 ? \"VIP\" : \"SVIP\"} (已过期)`;\n } else if (remainingDays <= 7) {\n isVIP = `${res.vip_level === 1 ? \"VIP\" : \"SVIP\"} 剩余${remainingDays}天`;\n } else {\n if (vipEndTime < 1912946812) {\n isVIP = `${res.vip_level === 1 ? \"VIP\" : \"SVIP\"}(${formattedDate})`;\n } else {\n isVIP = `${res.vip_level === 1 ? \"VIP\" : \"SVIP\"} (永久)`;\n }\n }\n } else {\n isVIP = '您尚未开通VIP';\n }\n return isVIP;\n}\n\nfunction checkStatus() {\n java.longToast('\\n\\n♻️检测中...');\n let base_url = getArgument('server')\n let res = java.ajax(`${base_url}\/user_api,{\"method\":\"POST\",\"headers\":{\"cookie\":\"${cookie.getCookie(base_url)}\"}}`)\n try {\n res = JSON.parse(res)\n if (res.id != undefined) {\n result['邮箱'] = res.email\n putLoginInfo(JSON.stringify(result))\n let devices\n try {\n devices = Object.keys(JSON.parse(res.device)).length;\n } catch (e) {\n devices = res.device ? 1 : 0;\n }\n let isVip = isVips(res);\n tips = `\n┏┅┅┅┅┅┅┱┄┄┄┄┄┄┄┄┄┄┐\n 🧢昵称 ${res.nickname.padEnd(20,\"\\t\") || \"未设置\".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 🗒️今日阅读 ${(java.timeFormat(new Date()).slice(0,10)==java.timeFormat(res.last_read_time * 1000).slice(0,10)?res.day_read_count:0).toString().padEnd(20,\"\\t\")}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n 📚累计阅读 ${res.all_read_count.toString().padEnd(20,\"\\t\")}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n 🕓最后阅读 ${(res.last_read_time != 0?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 java.log(tips)\n java.longToast(tips)\n } else {\n throw new Error(res.msg)\n }\n } catch (e) {\n \/\/java.log(e)\n java.toast(\"\\n检测登录失败\\n\" + e.message)\n }\n}\n\nfunction clearDevice() {\n let base_url = getArgument('server')\n let res = java.ajax(`${base_url}\/clear,{\"method\":\"POST\",\"headers\":{\"cookie\":\"${cookie.getCookie(base_url)}\"}}`)\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\/\/ 保存登录UI信息\nfunction putLoginInfo(info) {\n try {\n let key = java.androidId()\n let encodeStr = Packages.android.util.Base64.encodeToString(java.createSymmetricCrypto(\"AES\", key).encrypt(info), 2)\n cache.put(`userInfo_${source.getKey()}`, encodeStr)\n return true\n } catch (e) {\n java.log(e)\n return source.putLoginInfo(info)\n }\n}\n\n\/\/ 用户后台\nfunction loginqt() {\n let base_url = getArgument('server');\n let ck = String(cookie.getKey(base_url, \"qttoken\"));\n if (!ck) {\n java.longToast('\\n\\n🚫请先登录!')\n } else {\n java.startBrowserAwait(getArgument('server') + '\/user', '大灰狼小说后台');\n }\n}\n\n\/\/ 用户注册\nfunction register() {\n let base_url = getArgument('server');\n java.startBrowserAwait(getArgument('server') + '\/register', '大灰狼小说注册');\n}\n\n\/\/登录番茄\nfunction fq_login() {\n try {\n cookie.removeCookie(\"snssdk.com\");\n cookie.removeCookie(\"fanqienovel.com\")\n } catch (e) {}\n try {\n java.startBrowserAwait(\"https:\/\/fanqienovel.com\/\", \"登录\")\n } catch (e) {\n java.toast(e)\n }\n var cookie_ = \"sessionid=\" + (String(cookie.getKey(\"fanqienovel.com\", \"sessionid\")) ? String(cookie.getKey(\"fanqienovel.com\", \"sessionid\")) : source.getLoginInfoMap()['手动登录Token'])\n let user\n try {\n user = JSON.parse(java.ajax(\"https:\/\/fanqienovel.com\/api\/user\/info\/v2,\" + JSON.stringify({\n method: \"GET\",\n headers: {\n \"Cookie\": cookie_\n }\n }))).data.name\n } catch (e) {\n java.log(e)\n }\n if (!cookie_ || cookie_ == \"sessionid=\" || !user) {\n java.toast(\"未获取到登录凭据,登录失败\")\n return false\n }\n java.toast(\"\\n\\n欢迎 \" + user + \"\\n登录成功!\")\n return true\n}\n\n\/\/退出登录\nfunction logout() {\n cookie.removeCookie(\"fanqienovel.com\");\n cookie.removeCookie(\"snssdk.com\");\n cookie.removeCookie(\"69shuba.com\");\n \/*\n let servers = host\n for (let server of servers) {\n \ttry{removeCookie(server )} catch(e){cookie.removeCookie(server )}\n }\n *\/\n try {\n cookie.removeCookie(getArgument('server'))\n } catch (e) {}\n java.toast(\"退出登录成功\");\n}\n\n\/\/获取参数\nfunction getArgument(key) {\n let open_argument = source.getVariable();\n open_argument = getArguments(open_argument, '');\n return open_argument[key];\n}\n\n\/\/设置参数\nfunction setArgument(key, value) {\n let open_argument = source.getVariable();\n open_argument = getArguments(open_argument, '');\n open_argument[key] = value;\n open_argument = JSON.stringify(open_argument);\n source.setVariable(open_argument);\n return open_argument;\n}\n\n\/\/ 设置本地or云端访问\n\n\n\n\n\n\/\/设置男女频\n\n\n\n\nfunction fxyjr() {\n let fxyjr = getArgument('fxyjr');\n if (fxyjr != 'on') {\n setArgument('fxyjr', 'on');\n try { java.refreshExplore(); } catch (e) {}\n java.longToast('\\n显示旧版发现页');\n } else {\n setArgument('fxyjr', 'off');\n try { java.refreshExplore(); } catch (e) {}\n java.longToast('\\n显示新版发现页');\n }\n}\n\n\n\/\/设置男女频\n\n\n\n\n\/\/首页\n\/\/首页\nfunction api() {\n java.startBrowserAwait('http:\/\/svip.langge.cf', \"首页\");\n}\n\n\/\/打赏\nfunction vip() {\n let base_url = getArgument('server');\n let ck = String(cookie.getKey(base_url, \"qttoken\"));\n if (!ck) {\n java.longToast('\\n\\n🚫请先登录!')\n } else {\n java.startBrowserAwait(getArgument('server') + '\/coffee', '大灰狼小说会员开通');\n }\n}\n\n\n\/\/设置搜索媒体\n\n\n\/\/获取搜索媒体\n\n\n\n\/\/设置服务器\nfunction set_server() {\n putLoginInfo(JSON.stringify(result))\n let zdyserver;\n let base_url = getArgument('server')\n try {\n zdyserver = String(result['自定义服务器(可不填)']);\n if (zdyserver.includes('http')) {\n setArgument('server', zdyserver);\n if (getKey(String(cookie.getCookie(base_url)))) {\n let cookies = cookie.getCookie(base_url)\n try {\n cookie.removeCookie(base_url)\n } catch (e) {}\n cookie.setCookie(zdyserver, cookies)\n }\n java.toast(`\\n\\n当前服务器为自定义服务器\\n${zdyserver}\\n\\n切换服务器请先清空服务器地址中的数据`);\n } else {\n zdyserver = '';\n }\n } catch (error) {\n zdyserver = '';\n }\n if (!zdyserver) {\n const servers = host\n const currentServer = getArgument('server') || '';\n const currentIndex = servers.indexOf(currentServer);\n\n const nextIndex = currentIndex >= 0 ? (currentIndex + 1) % servers.length : 0;\n const nextServer = servers[nextIndex];\n\n setArgument('server', nextServer);\n if (getKey(String(cookie.getCookie(currentServer)))) {\n let cookies = cookie.getCookie(currentServer)\n try {\n cookie.removeCookie(currentServer)\n } catch (e) {}\n cookie.setCookie(nextServer, cookies)\n }\n java.longToast(`\\n\\n服务器【${nextIndex+1}】:${nextServer}`);\n }\n}\n\n\n\n\n\n\n\n\/\/获取其他听书来源音色\nvar tone_id = getArgument('tone_id');\n\n\n\n\n\/\/设置来源\n\n\n\/\/ 设置简介\n\n\n\/\/ 番茄段评\n\n\n\/\/ 番茄正文末尾的本章说开关\n\n\n\/\/ 强制搜索被禁用的源\n\n\n\/\/ 我要推荐\n\n\n\n\/\/ 使用教程\nfunction jc(){\n\t let html = `<!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 <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n }\n\n body {\n background-color: #f5f5f5;\n padding: 20px;\n }\n\n .container {\n max-width: 800px;\n margin: 0 auto;\n }\n\n h1 {\n text-align: center;\n color: #333;\n margin-bottom: 30px;\n padding-bottom: 10px;\n border-bottom: 2px solid #4CAF50;\n }\n\n .tutorial-card {\n background-color: white;\n border-radius: 8px;\n box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);\n padding: 25px;\n margin-bottom: 25px;\n transition: transform 0.3s ease, box-shadow 0.3s ease;\n }\n\n .tutorial-card:hover {\n transform: translateY(-5px);\n box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15);\n }\n\n .card-title {\n color: #4CAF50;\n margin-bottom: 15px;\n font-size: 1.4em;\n }\n\n .card-content {\n line-height: 1.6;\n color: #555;\n }\n\n .keyword-list {\n background-color: #f9f9f9;\n border-left: 4px solid #4CAF50;\n padding: 15px;\n margin-top: 15px;\n border-radius: 0 4px 4px 0;\n font-family: monospace;\n }\n\n .keyword-item {\n margin-bottom: 8px;\n }\n <\/style>\n<\/head>\n<body>\n <div class=\"container\">\n <h1>使用教程<\/h1>\n\n <div class=\"tutorial-card\">\n <h2 class=\"card-title\">搜索功能<\/h2>\n <div class=\"card-content\">\n <p><strong>1) 基础搜索:<\/strong>在发现页长按该书源,点击搜索,可直接搜索全部来源的小说。<\/p>\n <p><strong>2) 进阶搜索:<\/strong>在“登陆-书源设置”中选择指定模式或来源,即可搜索指定内容。<\/p>\n <p><strong>3) 快捷搜索:<\/strong>使用特殊关键词进行搜索。<\/p>\n <div class=\"keyword-list\">\n <p class=\"keyword-item\">x:书名@来源 (例如: x:十日终焉@番茄)<\/p>\n <p class=\"keyword-item\">t:书名@来源 (例如: t:十日终焉@番茄)<\/p>\n <p class=\"keyword-item\">m:书名@来源 (例如: m:十日终焉@番茄)<\/p>\n <p class=\"keyword-item\">d:书名@来源 (例如: d:十日终焉@番茄)<\/p>\n <\/div>\n <p>其中,x表示小说(可省略),t表示听书,m表示漫画,d表示短剧。冒号支持中文和英文。<\/p>\n <\/div>\n <\/div>\n\n <div class=\"tutorial-card\">\n <h2 class=\"card-title\">发现页<\/h2>\n <div class=\"card-content\">\n <p>在书源设置中选择指定模式或者单个来源,然后刷新发现页即可。<\/p>\n <\/div>\n <\/div>\n\n <div class=\"tutorial-card\">\n <h2 class=\"card-title\">更多使用方式<\/h2>\n <div class=\"card-content\">\n <p>进入书源设置页面应该能一目了然。<\/p>\n <\/div>\n <\/div>\n <\/div>\n<\/body>\n<\/html>`;\n\t java.startBrowser(`data:text\/html;base64,${java.base64Encode(html)}`, '大灰狼书源教程');\n\t}\n\n\/\/ 书源更新\nfunction renderVersionPage() {\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.langge.cf'>下载<\/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\n \/\/ 统一的服务器配置 - 方便维护\n const serverConfig = {\n main: {\n name: '主线路',\n icon: 'rocket',\n baseUrl: 'https:\/\/sy.langge.cf',\n downloadPath: '\/download\/%E5%AE%89%E5%8D%93%E9%98%85%E8%AF%BBapp-%E5%A4%A7%E7%81%B0%E7%8B%BC%E8%9E%8D%E5%90%884.0(vip%E5%AE%8C%E5%85%A8%E7%89%88).json'\n },\n backup1: {\n name: '备用线路1',\n icon: 'box',\n baseUrl: 'https:\/\/api.langge.cf',\n downloadPath: '\/sy\/download\/%E5%AE%89%E5%8D%93%E9%98%85%E8%AF%BBapp-%E5%A4%A7%E7%81%B0%E7%8B%BC%E8%9E%8D%E5%90%884.0(vip%E5%AE%8C%E5%85%A8%E7%89%88).json'\n },\n backup2: {\n name: '备用线路2',\n icon: 'satellite',\n baseUrl: 'https:\/\/20.langge.tk',\n downloadPath: '\/sy\/download\/%E5%AE%89%E5%8D%93%E9%98%85%E8%AF%BBapp-%E5%A4%A7%E7%81%B0%E7%8B%BC%E8%9E%8D%E5%90%884.0(vip%E5%AE%8C%E5%85%A8%E7%89%88).json'\n },\n backup3: {\n name: '备用线路3',\n icon: 'link',\n baseUrl: 'http:\/\/219.154.201.122:5006',\n downloadPath: '\/sy\/download\/%E5%AE%89%E5%8D%93%E9%98%85%E8%AF%BBapp-%E5%A4%A7%E7%81%B0%E7%8B%BC%E8%9E%8D%E5%90%884.0(vip%E5%AE%8C%E5%85%A8%E7%89%88).json'\n },\n backup4: {\n name: '备用线路4',\n icon: 'bolt',\n baseUrl: 'https:\/\/v2.czyl.cf',\n downloadPath: '\/sy\/download\/%E5%AE%89%E5%8D%93%E9%98%85%E8%AF%BBapp-%E5%A4%A7%E7%81%B0%E7%8B%BC%E8%9E%8D%E5%90%884.0(vip%E5%AE%8C%E5%85%A8%E7%89%88).json'\n },\n backup5: {\n name: '备用线路5',\n icon: 'globe',\n baseUrl: 'https:\/\/v10.czyl.cf',\n downloadPath: '\/sy\/download\/%E5%AE%89%E5%8D%93%E9%98%85%E8%AF%BBapp-%E5%A4%A7%E7%81%B0%E7%8B%BC%E8%9E%8D%E5%90%884.0(vip%E5%AE%8C%E5%85%A8%E7%89%88).json'\n },\n backup6: {\n name: '备用线路6',\n icon: 'broadcast-tower',\n baseUrl: 'https:\/\/v4.czyl.cf',\n downloadPath: '\/sy\/download\/%E5%AE%89%E5%8D%93%E9%98%85%E8%AF%BBapp-%E5%A4%A7%E7%81%B0%E7%8B%BC%E8%9E%8D%E5%90%884.0(vip%E5%AE%8C%E5%85%A8%E7%89%88).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\n async function fetchVersionData() {\n \/\/ 使用统一配置中除主线路外的备用线路进行版本检查\n const serversToCheck = Object.values(serverConfig).filter(s => s.baseUrl.includes('czyl.cf') || s.baseUrl.includes('219.154'));\n \n for (const server of serversToCheck) {\n try {\n const response = await fetch(server.baseUrl + '\/version', { 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 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();\n const cloudVersion = String(data.version3);\n const updateLog = data.update_log || {};\n\n \/\/ 显示版本信息\n currentVersion.textContent = \\`v\\${localVer}\\`;\n latestVersion.textContent = \\`v\\${cloudVersion}\\`;\n\n \/\/ 处理日志\n const logEntries = Object.entries(updateLog);\n if (logEntries.length > 0) {\n \/\/ 显示最新日志\n const [latestDate, latestContent] = logEntries[0];\n latestLogDate.innerHTML = \\`<i class=\"fas fa-calendar-alt\"><\/i> \\${latestDate}\\`;\n latestLogContent.textContent = latestContent;\n latestLogContainer.style.display = 'block';\n\n \/\/ 显示历史日志\n if (logEntries.length > 1) {\n const historyLogs = logEntries.slice(1);\n historyCount.textContent = \\`(\\${historyLogs.length}条)\\`;\n logList.innerHTML = historyLogs.map(([date, content]) => \\`\n <div class=\"history-item\">\n <div class=\"history-date\">\n <i class=\"fas fa-calendar-day\"><\/i>\n <span>\\${date}<\/span>\n <\/div>\n <div class=\"history-text\">\\${content}<\/div>\n <\/div>\n \\`).join('');\n logsContainer.style.display = 'block';\n }\n }\n\n \/\/ 检查更新状态\n const compareResult = compareVersions(cloudVersion);\n \n \/\/ 显示版本指示器\n versionIndicator.style.display = 'flex';\n \n if (compareResult === -1) {\n \/\/ 需要更新\n currentStatus.textContent = '待更新';\n currentStatus.className = 'version-status status-outdated';\n versionIndicator.className = 'version-indicator update-needed';\n versionIndicator.innerHTML = '<i class=\"fas fa-arrow-right\"><\/i>';\n\n \/\/ 使用统一配置生成下载按钮\n buttonGroup.innerHTML = Object.values(serverConfig).map(server => {\n const fullUrl = server.baseUrl + server.downloadPath;\n return \\`\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('');\n buttonGroup.style.display = 'flex';\n } else {\n \/\/ 已是最新版本\n currentStatus.textContent = '最新';\n currentStatus.className = 'version-status status-latest';\n versionIndicator.className = 'version-indicator is-latest';\n versionIndicator.innerHTML = '<i class=\"fas fa-check\"><\/i>';\n \n statusAlert.className = 'status-alert up-to-date';\n statusAlert.innerHTML = '<i class=\"fas fa-check-circle\"><\/i> <div>您已是最新版本<\/div>';\n statusAlert.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\/\/ 远程设置页面仅负责界面,用户配置仍保存在当前阅读软件的源变量中。\nfunction openSourceSettings() {\n var current = {};\n try {\n current = JSON.parse(String(source.getVariable() || '{}'));\n } catch (e) {}\n\n if (!current || typeof current !== 'object') current = {};\n if (!current.server) current.server = getArgument('server') || host[0];\n if (!current.shuqi_tone_id || current.shuqi_tone_id === '默认音色') {\n current.shuqi_tone_id = 'multi_role';\n }\n current.pstyle = String(current.pstyle || '0');\n\n var settingsNonce = String(new Date().getTime()) + '_' + String(Math.floor(Math.random() * 1000000));\n current._settings_nonce = settingsNonce;\n var initialSettingsJson = JSON.stringify(current);\n\n var baseUrl = String(current.server || host[0]).replace(\/\\\/+$\/, '');\n var settingsUrl = baseUrl + '\/source-settings#config=' + encodeURIComponent(JSON.stringify(current));\n var browserResult;\n var body = '';\n\n try {\n var settingsPage = String(java.ajax(baseUrl + '\/source-settings?_=' + String(new Date().getTime())) || '');\n var stateMarker = 'let state = parseInitialState();';\n if (settingsPage.indexOf(stateMarker) < 0) throw new Error('设置页面版本不兼容');\n\n var injectedState = 'let state = { ...defaults, ...JSON.parse(decodeURIComponent(\"' +\n encodeURIComponent(JSON.stringify(current)) + '\")) };';\n settingsPage = settingsPage.replace(stateMarker, injectedState);\n\n var relativeProbe = String.fromCharCode(96) + '$' + '{location.pathname}?probe=';\n var absoluteProbe = String.fromCharCode(96) + baseUrl + '\/source-settings?probe=';\n settingsPage = settingsPage.replace(relativeProbe, absoluteProbe);\n var openUrl = 'data:text\/html;base64,' + java.base64Encode(settingsPage);\n browserResult = java.startBrowserAwait(openUrl, '书源设置', false);\n\n body = browserResult && typeof browserResult.body === 'function'\n ? String(browserResult.body())\n : (browserResult && browserResult.body != null ? String(browserResult.body) : String(browserResult || ''));\n } catch (e) {\n java.longToast('\\n当前阅读版本无法打开设置中心,原登录页设置仍可使用。\\n' + e);\n return;\n }\n\n function decodeSafe(value) {\n try {\n return decodeURIComponent(String(value || '').trim());\n } catch (e) {\n return String(value || '').trim();\n }\n }\n\n function parseCookieString(cookieStr) {\n var output = {};\n cookieStr = String(cookieStr || '');\n if (!cookieStr || cookieStr == 'null' || cookieStr == 'undefined') return output;\n var pairs = cookieStr.split(';');\n for (var i = 0; i < pairs.length; i++) {\n var pair = pairs[i];\n var index = pair.indexOf('=');\n if (index < 0) continue;\n var key = decodeSafe(pair.substring(0, index));\n var value = decodeSafe(pair.substring(index + 1));\n output[key] = value;\n }\n return output;\n }\n\n function readSettingsResultFromBody(pageBody) {\n var match = String(pageBody || '').match(\/<script\\b[^>]*\\bid=[\"']source-settings-(?:final-)?result[\"'][^>]*>([\\s\\S]*?)<\\\/script>\/i);\n if (!match || !match[1]) return null;\n var raw = decodeSafe(match[1]);\n if (!raw) return null;\n return JSON.parse(raw);\n }\n\n function readSettingsResultFromUrl(result) {\n var candidates = [];\n try { candidates.push(String(result || '')); } catch (e) {}\n try {\n if (result && typeof result.url === 'function') candidates.push(String(result.url() || ''));\n else if (result && result.url != null) candidates.push(String(result.url));\n } catch (e) {}\n try {\n if (result && typeof result.getUrl === 'function') candidates.push(String(result.getUrl() || ''));\n } catch (e) {}\n\n for (var i = 0; i < candidates.length; i++) {\n var match = candidates[i].match(\/(?:#config=|[?&](?:config|source_settings_result)=)([^\\s\"'<>]+)\/i);\n if (!match || !match[1]) continue;\n try {\n var parsed = JSON.parse(decodeSafe(match[1]));\n if (parsed && typeof parsed === 'object') return parsed;\n } catch (e) {}\n }\n return null;\n }\n\n function readSettingsResultFromCookie(hostUrl) {\n var urls = [hostUrl, hostUrl + '\/', settingsUrl];\n var seen = {};\n for (var u = 0; u < urls.length; u++) {\n var target = String(urls[u] || '');\n if (!target || seen[target]) continue;\n seen[target] = true;\n var cookieStr = '';\n try {\n cookieStr = String(cookie.getCookie(target) || '');\n } catch (e) {}\n var cookieData = parseCookieString(cookieStr);\n if (cookieData.source_settings_result) {\n return JSON.parse(cookieData.source_settings_result);\n }\n }\n return null;\n }\n\n\n function readBubbleResultFromCookie(hostUrl) {\n var urls = [hostUrl, hostUrl + '\/', hostUrl + '\/static\/bubble_preview.html'];\n for (var urlIndex = 0; urlIndex < urls.length; urlIndex++) {\n var cookieString = '';\n try {\n cookieString = String(cookie.getCookie(urls[urlIndex]) || '');\n } catch (e) {}\n var cookieData = parseCookieString(cookieString);\n if (!cookieData.bubble_settings_result) continue;\n try {\n var parsed = JSON.parse(cookieData.bubble_settings_result);\n if (parsed && typeof parsed === 'object') return parsed;\n } catch (e) {}\n }\n return null;\n }\n\n try {\n var resultType = '';\n var next = readSettingsResultFromBody(body);\n if (next) resultType = '内嵌页面';\n if (!next) {\n next = readSettingsResultFromCookie(baseUrl);\n if (next) resultType = 'Cookie';\n }\n if (!next) {\n next = readSettingsResultFromUrl(browserResult);\n if (next) resultType = 'URL';\n }\n if (!next || typeof next !== 'object') throw new Error('未读取到设置结果');\n if (String(next._settings_nonce || '') !== settingsNonce) throw new Error('设置结果未更新');\n\n var bubbleHosts = [String(next.server || baseUrl).replace(\/\\\/+$\/, ''), baseUrl].concat(host || []);\n var checkedBubbleHosts = {};\n for (var bubbleHostIndex = 0; bubbleHostIndex < bubbleHosts.length; bubbleHostIndex++) {\n var bubbleHost = String(bubbleHosts[bubbleHostIndex] || '').replace(\/\\\/+$\/, '');\n if (!bubbleHost || checkedBubbleHosts[bubbleHost]) continue;\n checkedBubbleHosts[bubbleHost] = true;\n var bubbleResult = readBubbleResultFromCookie(bubbleHost);\n if (!bubbleResult) continue;\n if (String(bubbleResult._bubble_nonce || '') !== settingsNonce) continue;\n if (String(bubbleResult._bubble_applied || '') !== '1') continue;\n var bubbleStyle = String(bubbleResult.pstyle || '');\n if (\/^[A-Za-z0-9_-]{1,32}$\/.test(bubbleStyle)) next.pstyle = bubbleStyle;\n break;\n }\n if (resultType === 'URL' && JSON.stringify(next) === initialSettingsJson) {\n throw new Error('源阅仅返回了初始地址,未返回修改后的设置');\n }\n delete next._settings_nonce;\n if (!next.server) next.server = baseUrl;\n if (!next.shuqi_tone_id || next.shuqi_tone_id === '默认音色') next.shuqi_tone_id = 'multi_role';\n next.pstyle = String(next.pstyle || '0');\n source.setVariable(JSON.stringify(next));\n try { java.log('书源设置回传方式:' + resultType); } catch (ignore) {}\n java.longToast('\\n模式:' + (next.tab || '') + '\\n来源:' + (next.sources || '') + '\\n搜索、发现页或正文刷新后生效。');\n } catch (e) {\n java.longToast('\\n未读取到设置结果,已保留原配置。\\n' + e);\n }\n}",
"respondTime": 180000,
"ruleBookInfo": {
"author": "$.author",
"canReName": "1",
"coverUrl": "$.thumb_url",
"init": "<js>\nif (String(baseUrl).startsWith(\"data:\")) {\n let res = JSON.parse(java.hexDecodeToString(result));\n let book_id = res.book_id;\n let tab = res.tab;\n let sources = res.sources;\n let url = res.url;\n let html = \"\";\n let proxy = getArguments(source.getVariable(), \"proxy\");\n if (url != \"\" && proxy == \"本地\") {\n if (sources == '69书吧') {\n let ck69 = String(cookie.getCookie('https:\/\/www.69shuba.com'));\n let headers = {\n \t \"Referer\": url,\n \"Cookie\": ck69,\n \"User-Agent\": java.getWebViewUA()\n }; \n let op = JSON.stringify({\n \"headers\": headers\n });\n html = java.ajax(`${url},${op}`);\n } else {\n html = java.ajax(url);\n }\n \/\/java.log(html);\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 java.log(html);\n }\n }\n let base_url = getArguments(source.getVariable(), \"server\");\n let op = {\n method: \"POST\",\n body: {\n html: html\n }\n };\n op = JSON.stringify(op);\n let varia = String(book.getVariable('custom'));\n if (varia == 'null') {\n varia = '';\n }\n varia = JSON.stringify({\n 'custom': varia\n });\n \/\/varia = java.base64Encode(varia);\n java.log(`${base_url}\/detail?book_id=${book_id}&source=${sources}&tab=${tab}&variable=${varia},${op}`);\n try {\n result = java.ajax(`${base_url}\/detail?book_id=${book_id}&source=${sources}&tab=${tab}&variable=${varia},${op}`);\n JSON.parse(result);\n } catch(_err) {\n for (var _i = 0; _i < host.length; _i++) {\n if (host[_i] === base_url) continue;\n try {\n result = java.ajax(`${host[_i]}\/detail?book_id=${book_id}&source=${sources}&tab=${tab}&variable=${varia},${op}`);\n JSON.parse(result);\n setArguments(\"server\", host[_i]);\n java.longToast(\"\\n\\n线路故障,已自动切换到:\" + host[_i]);\n break;\n } catch(_e2) { continue; }\n }\n }\n}\nresult\n<\/js>$.data",
"intro": "<js>\nlet {\n book_id,\n source: sources,\n tab,\n book_tts,\n book_tts_dice,\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} = result;\nlet proxy = getArguments(source.getVariable(), \"proxy\");\nif (proxy == \"本地\") {\n proxy = \"本地网络\";\n} else {\n proxy = \"服务器网络\";\n}\njava.put(\"book_detail\", JSON.stringify(result));\nlet base_url = getArguments(source.getVariable(), \"server\");\nlet key3 = \"\";\ntry {\n let cookieValue =\n String(cookie.getCookie(base_url)) || String(java.getCookie(base_url));\n key3 = getKey(cookieValue);\n} catch (e) {\n key3 = \"\";\n}\n\nif (key3 == \"\") {\n java.log(\"当前服务器未查询到登录状态,尝试查询其他服务器登录状态...\");\n let cookieValue;\n for (let h of host) {\n try {\n cookieValue = String(cookie.getCookie(h)) || String(java.getCookie(h));\n key3 = getKey(cookieValue);\n } catch (e) {\n key3 = \"\";\n }\n if (key3) {\n java.log(`已在${h}登录,退出查询,正在转移登录状态到当前服务${base_url}`);\n \/\/java.log(cookieValue)\n cookie.removeCookie(h);\n cookie.removeCookie(base_url);\n cookie.setCookie(base_url, cookieValue);\n break;\n }\n }\n}\n\nif (book.readConfig == null || book.readConfig.useReplaceRule == null) {\n book.setUseReplaceRule(false);\n}\n\nlet nickname = '账号状态:⚠️ 未登录 | 点击右上角 🔖 登录';\ntry {\n let opcx = {\n method: \"GET\",\n headers: {\n cookie: 'qttoken=' + key3 + ';'\n },\n };\n opcx = JSON.stringify(opcx);\n let user_info = JSON.parse(java.ajax(base_url + '\/get_avatar,' + opcx));\n if (user_info.code == 0) {\n if (user_info.nickname) {\n nickname = '欢迎回来:' + user_info.nickname\n } else {\n nickname = '欢迎回来:' + user_info.email + \"(请前往用户后台设置用户名)\"\n }\n }\n} catch (e) {\n if (key) {\n nickname = '账号状态:已登录'\n }\n};\n\nlet loginStatus = nickname;\n\nlet lightDivider = \"❇️───────❇️───────❇️\";\nlet heavyDivider = \"‎\\n‎\";\n\nlet isValid = (value) => String(value).length > 1;\nlet ctitle = book.durChapterTitle || '未开始';\nlet info = `\n 📡 当前服务:${base_url}\n 🔑 ${loginStatus}\n 🏷 数据来源:${sources}\n 🔄 当前模式:${tab}\n ⚙️ 访问模式:${proxy}\n 📖 阅读至:${ctitle}\n`;\n\nif (tab == \"听书\") {\n let toneId = String(book.getVariable('custom'));\n if (toneId == 'null') toneId = '';\n let isShuqiAudio = sources == '书旗' || sources == 'svip_书旗';\n if (isShuqiAudio && (!toneId || toneId == '默认音色')) {\n toneId = getArguments(source.getVariable(), 'shuqi_tone_id') || 'multi_role';\n } else if (!toneId) {\n toneId = '4';\n }\n if (isShuqiAudio) {\n let toneName = getShuqiToneName(toneId);\n let toneNumber = getShuqiToneNumber(toneId);\n let supportedTones = [];\n if (book_tts_dice && typeof book_tts_dice == 'object') {\n supportedTones = Object.keys(book_tts_dice)\n .map((name) => {\n let value = String(book_tts_dice[name] || '');\n let known = getShuqiToneOption(value) || getShuqiToneOption(name);\n return known ? {number: known.number, name: known.name} : {number: value, name: name};\n })\n .filter((option) => \/^\\d+$\/.test(option.number));\n }\n if (!supportedTones.length) supportedTones = SHUQI_TONE_OPTIONS.slice();\n supportedTones.sort((left, right) => Number(left.number) - Number(right.number));\n let availableTones = supportedTones\n .map((option) => ' ' + option.number + '. ' + option.name)\n .join('\\n');\n let toneSupported = supportedTones.some((option) => option.number == toneNumber);\n let fallbackTip = toneSupported ? '' : '\\n ⚠️ 本书不支持当前音色,播放时会自动回退到可用音色';\n info += `${lightDivider}\n 🎵 当前音色:${toneName}\n 🔢 当前编号:${toneNumber}${fallbackTip}\n 🛠 修改方法:点击右上角“书籍变量”,只需填写下面的数字编号;留空使用书源登录页选择的音色\n 🎙 本书可用的书旗音色:\n${availableTones}\n`;\n } else if (isValid(book_tts)) {\n info += `${lightDivider}\n 🎵 音色配置:${toneId}\n ✨ AI音色请点击右上角书籍变量填写相关值,真人听书请重新搜索选择带有主播的书籍\n ${book_tts}\n`;\n }\n}\n\nlet basicInfo = \"\";\nlet addBasicInfo = (value, prefix, icon) => {\n if (isValid(value)) basicInfo += ` ${icon} ${prefix} ${value}\\n`;\n};\n\naddBasicInfo(tags, \"书籍分类:\", \"🌈\");\naddBasicInfo(role, \"书籍主角:\", \"👑\");\naddBasicInfo(last_chapter_title, \"最新章节:\", \"📚\");\naddBasicInfo(last_chapter_update_time, \"更新时间:\", \"⏳\");\naddBasicInfo(word_number, \"书籍字数:\", \"📊\");\naddBasicInfo(status, \"书籍状态:\", \"🚩\");\naddBasicInfo(score, \"书籍评分:\", \"⭐\");\n\nif (basicInfo) info += `${lightDivider}\\n${basicInfo}`;\n\nif (isValid(abstract)) {\n let indentedAbstract = abstract\n .split(\"\\n\")\n .map((line) => ` ${line}`)\n .join(\"\\n\");\n info += `${heavyDivider}\n 📖 书籍简介:\n${indentedAbstract}\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`;\nlet jjinfo = getArguments(source.getVariable(), \"info\");\n\nif (jjinfo != \"off\") {\n info = String(info)\n .split(\"\\n\")\n .map((line) => line.replace(\/^ {4}\/, \"\"))\n .join(\"\\n\");\n} else {\n basicInfo = \"\";\n addBasicInfo(last_chapter_title, \"最新章节:\", \"📚\");\n addBasicInfo(last_chapter_update_time, \"更新时间:\", \"⏳\");\n addBasicInfo(word_number, \"书籍字数:\", \"📊\");\n addBasicInfo(status, \"书籍状态:\", \"🚩\");\n addBasicInfo(score, \"书籍评分:\", \"⭐\");\n if (isValid(abstract)) {\n let indentedAbstract = abstract\n .split(\"\\n\")\n .map((line) => ` ${line}`)\n .join(\"\\n\");\n basicInfo += `\n \\n‎\n 📖 书籍简介:\n${indentedAbstract}\n`;\n } else {\n basicInfo += `${heavyDivider}`;\n }\n info = `‎\\n🏷 数据来源:${sources}\\n` + basicInfo;\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 tab = result.tab || \"小说\";\nlet url = result.toc_url || \"\";\nvar sdtoken;\ntry {\n var loginInfoMap = source.getLoginInfoMap ? source.getLoginInfoMap() : {};\n sdtoken = String(loginInfoMap['手动填写番茄token(可不填)'] || '');\n} catch (e) {\n sdtoken = '';\n}\nvar rawCookie = getFanqieCookie() || sdtoken;\nvar match = rawCookie.match(\/sessionid=[^;]+\/);\nvar fqcookie = match ? match[0] : '';\nvar fqssionid = '';\nif (fqcookie) {\n fqssionid = getSessionId(fqcookie);\n};\nsetArguments('fqssionid', fqssionid);\njava.put(\"tab\", tab);\njava.put(\"book_id\", book_id);\nlet qtcatalog = {\n book_id: book_id,\n sources: sources,\n tab: tab,\n url: url,\n};\nqtcatalog = java.base64Encode(JSON.stringify(qtcatalog));\n`data:;base64,${qtcatalog},{\"type\":\"qingtian2\"}`;\n<\/js>",
"wordCount": "$.word_number"
},
"ruleContent": {
"content": "<js>\njava.log(result)\nresult = String(java.hexDecodeToString(result));\nlet res;\nif (result.match(\/大灰狼融合\/)) {\n result = result.split(\"大灰狼融合4\");\n res = {\n item_id: result[0],\n tab: result[1],\n title: result[2],\n sources: result[3],\n url: \"\"\n };\n} else {\n res = JSON.parse(result);\n}\n\nfunction checkEnv() {\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\n try {\n java.deviceID();\n return \"苹果\";\n } catch (e) {}\n\n if (typeof source.loginUi == 'function') {\n return \"安卓\";\n }\n return \"改版\";\n}\n\nvar clientEnv = checkEnv();\nvar islyc = clientEnv == \"改版\";\ntry {\nif (islyc) {\n\tbook.imageStyle = 'FULL';\n\t} else {\n\t\tbook.imageStyle = 'TEXT';\n\t\t}\n} catch {}\nlet varia1 = String(book.getVariable('custom'));\nif (varia1 == 'null') {\n varia1 = '';\n}\nlet varia = JSON.stringify({\n 'custom': varia1\n});\n\/\/ varia = java.base64Encode(varia);\nlet book_id = res.book_id;\nlet item_id = res.item_id;\nlet tab = res.tab;\nlet title = res.title;\nlet sources = res.sources;\nlet url = res.url;\nlet html = \"\";\nlet proxy = getArguments(source.getVariable(), \"proxy\");\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({\n \"headers\": headers\n });\n java.log(url);\n java.log(op);\n html = java.ajax(`${url},${op}`);\n } else {\n html = java.ajax(url);\n }\n java.log('dddddddddddddddddddddddddddd')\n java.log(html);\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 java.log('cscscscs')\n java.log(html);\n }\n};\nlet content = \"\";\nlet data;\n\/\/let tone_id = getArguments(source.getVariable(), \"tone_id\");\nlet tone_id = varia1;\nlet isShuqiAudio = tab == '听书' && (sources == '书旗' || sources == 'svip_书旗');\nif (isShuqiAudio) {\n if (!tone_id || tone_id == '默认音色') {\n tone_id = getArguments(source.getVariable(), 'shuqi_tone_id') || 'multi_role';\n }\n tone_id = resolveShuqiToneId(tone_id);\n} else if (!tone_id) {\n tone_id = '4';\n}\nlet base_url = getArguments(source.getVariable(), \"server\");\nlet fqpara = getArguments(source.getVariable(), \"fqpara\");\nlet fqcommunity = getArguments(source.getVariable(), \"fqcommunity\");\nlet close_img = getArguments(source.getVariable(), \"close_img\");\nif (!fqcommunity) {\n fqcommunity = \"on\";\n}\nlet device, device_type;\ntry {\n device = java.deviceID();\n device_type = \"苹果\";\n} catch (e) {\n try {\n device = java.androidId();\n device_type = \"安卓\";\n } catch (e) {\n device = \"\";\n device_type = \"安卓\";\n }\n}\nlet isQiyue = device_type == \"苹果\" && clientEnv == \"改版\";\n\nlet qtcookie = cookie.getCookie(base_url);\ntry {\n\t let device2 = String(cookie.getKey(base_url, \"device\"));\n\t if (!device2){\n\t \tdevice2 = device;\n\t \t}\n qtcookie = `qttoken=${String(cookie.getKey(base_url, \"qttoken\"))}; deviceId=${device2};`\n} catch (e) {java.log(e)}\n\nvar params = {\n html: html,\n item_id: item_id,\n source: sources,\n tab: tab,\n tone_id: tone_id,\n variable: varia,\n version: '4.11.5.1',\n app: '\\u9605\\u8bfb\\u795e\\u8bc4\\u8868\\u60c5',\n god: fqpara == 'on'\n};\n\nvar content_url = '\/content';\nvar reviewSource = sources == \"svip_书旗\" ? \"书旗\" : sources;\nif ((reviewSource == \"番茄\" || reviewSource == \"七猫\" || reviewSource == \"书旗\" || reviewSource == \"塔读\" || reviewSource == \"QQ阅读\" || reviewSource == \"svip_QQ阅读\") && fqpara == \"on\" && tab == \"小说\") {\n content_url = '\/content?review=1';\n}\n\n\/\/var signInfo = generateComplexSignature('POST', content_url, {}, APP_SECRET, params);\n\n\n\/\/var signedParams = copyObject(params);\n\/\/signedParams.timestamp = signInfo.timestamp;\n\/\/signedParams.nonce = signInfo.nonce;\n\/\/signedParams.sign = signInfo.sign;\n\n\nvar op = {\n method: \"POST\",\n body: JSON.stringify(params),\n headers: {\n cookie: qtcookie,\n 'Content-Type': 'application\/json'\n }\n};\n\nop = JSON.stringify(op);\njava.log(op);\njava.log(cookie.getCookie(base_url))\ntry {\n data = java.ajax(base_url + content_url + `,${op}`);\n JSON.parse(data);\n} catch(_err) {\n for (var _i = 0; _i < host.length; _i++) {\n if (host[_i] === base_url) continue;\n try {\n data = java.ajax(host[_i] + content_url + `,${op}`);\n JSON.parse(data);\n setArguments(\"server\", host[_i]);\n try { var _oldCk = cookie.getCookie(base_url); if (_oldCk) cookie.setCookie(host[_i], _oldCk); } catch(_ce) {}\n base_url = host[_i];\n java.longToast(\"\\n\\n线路故障,已自动切换到:\" + host[_i]);\n break;\n } catch(_e2) { continue; }\n }\n}\ntry {\n data = JSON.parse(data);\n if (data.msg) {\n var contentMsg = String(data.msg);\n if (contentMsg.indexOf('runtime error: invalid memory address or nil pointer dereference') >= 0) {\n \/\/ 仅隐藏后端空指针内部错误,其他业务提示仍正常显示。\n java.log('正文接口空指针错误: ' + contentMsg);\n } else {\n java.toast(contentMsg);\n }\n }\n} catch (e) {\n java.log('正文响应解析失败: ' + String(e));\n}\ncontent = data && data.content ? data.content : \"\"\n\nif (close_img == 'on') {\n\tcontent = removeAllImgTags(content);\n\t}\n\nif ((reviewSource == \"番茄\" || reviewSource == \"七猫\" || reviewSource == \"书旗\" || reviewSource == \"塔读\" || reviewSource == \"QQ阅读\" || reviewSource == \"svip_QQ阅读\") && tab == \"小说\") {\n var sdtoken;\n try {\n var loginInfoMap = source.getLoginInfoMap ? source.getLoginInfoMap() : {};\n sdtoken = String(loginInfoMap['手动填写番茄token(可不填)'] || '');\n } catch (e) {\n sdtoken = '';\n }\n var rawCookie = getFanqieCookie() || sdtoken;\n var match = rawCookie.match(\/sessionid=[^;]+\/);\n var fqcookie = match ? match[0] : '';\n var fqssionid = '';\n if (!fqcookie) {} else {\n fqssionid = getSessionId(fqcookie)\n }\n content = content\n .replace(\/ident=\"\/g, `ident=\"${base_url}`)\n .replace(\/book_id=\/g, `book_id=${book_id}&ssionid=${fqssionid}`);\n if (sources == \"七猫\") {\n content = content.replace(\/ident=\"([^\"]*)\"\/g, function(full, reviewUrl) {\n if (reviewUrl.indexOf('\/qimao_comment?mode=paragraph&') < 0) {\n return full;\n }\n let paragraphUrl = reviewUrl\n .replace('\/qimao_comment?mode=paragraph&', '\/get_para_review?')\n .replace(\/([?&])paragraph_id=\/, '$1para=');\n return 'ident=\"' + paragraphUrl + '\"';\n });\n }\n if (fqpara == \"on\") {\n if (device_type == \"苹果\") {\n content = paraForiOS(content, sources);\n } else {\n content = paraForAndroid(content, sources);\n }\n }\n}\nif ((reviewSource == \"番茄\" || reviewSource == \"七猫\" || reviewSource == \"书旗\") && fqcommunity != \"off\" && tab == \"小说\") {\n let svg = `${base_url}\/chapter_review_svg?book_id=${book_id}&item_id=${item_id}&source=${encodeURIComponent(reviewSource)}&ssionid=${fqssionid}&cache=${Date.now()}`;\n let commentPageUrl;\n if (reviewSource == \"书旗\") {\n commentPageUrl = `${base_url}\/get_para_review?book_id=${book_id}&item_id=${item_id}&source=${encodeURIComponent(reviewSource)}&cache=${Date.now()}`;\n } else {\n let commentPagePath = reviewSource == \"七猫\" ? \"\/qimao_comment?mode=chapter\" : \"\/fanqie_comment?mode=chapter\";\n commentPageUrl = `${base_url}${commentPagePath}&book_id=${book_id}&item_id=${item_id}&source=${encodeURIComponent(reviewSource)}&tab=${encodeURIComponent(tab)}&ssionid=${fqssionid}&cache=${Date.now()}`;\n }\n let chapterSvg = '';\n let chapterSvgChecked = false;\n try {\n chapterSvg = java.ajax(svg);\n chapterSvgChecked = true;\n } catch (e) {}\n\n \/\/ 请求成功但没有 SVG,说明本章没有评论,不插入本章说图片。\n \/\/ 请求失败时保留原直链兜底,避免临时网络故障隐藏已有评论。\n let hasChapterCard = String(chapterSvg || '').indexOf('<svg') >= 0;\n if (!chapterSvgChecked) hasChapterCard = true;\n if (hasChapterCard) {\n if (device_type == \"苹果\" && chapterSvg) {\n svg = 'data:image\/svg+xml;base64,' + java.base64Encode(chapterSvg);\n } else {\n svg += '&img=1';\n }\n if (device_type == '苹果') {\n \/\/ iOS 不使用 qtbzs 专用类型,并同时提供两种点击字段,避免退化为图片预览。\n let chapterAction = 'showFanqieComment(' + JSON.stringify(commentPageUrl) + ',' + JSON.stringify('本章说') + ')';\n let chapterMeta = {style: 'FULL', click: chapterAction, js: chapterAction};\n content = content + `\\n<img src=\"${svg},${JSON.stringify(chapterMeta)}\">`;\n } else {\n \/\/ 安卓保留原有 qtbzs 协议,避免改变已经可用的点击行为。\n content = content + `\\n<img src=\"${svg},{'type':'qtbzs',${endclick(commentPageUrl, '本章说')},'style':'FULL'}\">`;\n }\n }\n}\ndata = JSON.stringify({\n content: content,\n});\nif (device_type == \"安卓\" && (tab == \"短剧\" || tab == \"视频\" || sources.includes('毒舌影视') || sources.includes('NT动漫'))) {\n data = {\n content: `【右上角刷新】开启播放(下一集请切换下一章刷新)\\n播放直链:\\n${content}`,\n };\n data = JSON.stringify(data);\n if (book.durChapterIndex === chapter.index) {\n let video_url = `${base_url}\/online_video?book_id=${book_id}&source=${sources}&tab=${tab}`;\n if (sources.includes('毒舌影视') || sources.includes('NT动漫')) {\n video_url = content;\n };\n java.startBrowser(video_url, title);\n java.toast(\"正在加载视频...\");\n }\n\n}\ndata;\n<\/js>$.content",
"imageStyle": "full"
},
"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 qtdetail = {\n book_id: book_id,\n sources: sources,\n tab: tab,\n url: url\n}\nqtdetail = java.base64Encode(JSON.stringify(qtdetail));\n`data:;base64,${qtdetail},{\"type\":\"qingtian\"}`\n<\/js>",
"coverUrl": "$.thumb_url",
"intro": "$.abstract",
"kind": "{{$.category}}\n{{$.score}}\n{{$.status}}\n{{$.source}}\n{{$.tags}}",
"lastChapter": "{{$.last_chapter_title}} • {{$.last_update_time}}",
"name": "$.book_name",
"wordCount": "$.word_number"
},
"ruleSearch": {
"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 qtdetail = {\n book_id: book_id,\n sources: sources,\n tab: tab,\n url: url\n}\nqtdetail = java.base64Encode(JSON.stringify(qtdetail));\n`data:;base64,${qtdetail},{\"type\":\"qingtian\"}`\n<\/js>",
"checkKeyWord": "我的26岁女房客@番茄",
"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>\nlet res = JSON.parse(java.hexDecodeToString(result));\nif (res.method) {\n res = Object.fromEntries(\n res.body\n .replace(\"source\", \"sources\")\n .split(\"&\")\n .map((query) => query.split(\"=\"))\n );\n res.url = \"\";\n}\nlet book_id = res.book_id;\njava.put('book_id', book_id);\nlet tab = res.tab;\nlet sources = res.sources;\nlet url = res.url;\nlet html = \"\";\nlet proxy = getArguments(source.getVariable(), \"proxy\");\nif (url != \"\" && proxy == \"本地\") {\n if (sources == '69书吧') {\n let ck69 = String(cookie.getCookie('https:\/\/www.69shuba.com'));\n let headers = {\n \t \"Referer\": url,\n \"Cookie\": ck69,\n \"User-Agent\": java.getWebViewUA()\n };\n let op = JSON.stringify({\n \"headers\": headers\n });\n html = java.ajax(`${url},${op}`); \n } else {\n html = java.ajax(url);\n }\n \/\/java.log(html);\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 java.log(html);\n }\n};\nlet base_url = getArguments(source.getVariable(), \"server\");\nlet op = {\n method: \"POST\",\n body: {\n html: html\n }\n};\nop = JSON.stringify(op);\nlet varia = String(book.getVariable('custom'));\nif (varia == 'null') {\n varia = '';\n}\nvaria = JSON.stringify({\n 'custom': varia\n});\n\/\/ varia = java.base64Encode(varia);\n\/\/java.log(`${base_url}\/catalog?book_id=${book_id}&source=${sources}&tab=${tab}&variable=${varia},${op}`);\nlet data = java.ajax(\n `${base_url}\/catalog?book_id=${book_id}&source=${sources}&tab=${tab}&variable=${varia},${op}`\n);\nlet catalogResult;\ntry {\n catalogResult = JSON.parse(data);\n} catch (e) {\n catalogResult = null;\n}\nif (catalogResult && Number(catalogResult.code) !== 0) {\n java.longToast('\\n\\n' + (catalogResult.msg || '获取目录失败'));\n data = JSON.stringify({data: []});\n}\nlet device, device_type;\ntry {\n device = java.deviceID();\n device_type = \"苹果\";\n} catch (e) {\n try {\n device = java.androidId();\n device_type = \"安卓\";\n } catch (e) {\n device = \"\";\n device_type = \"安卓\";\n }\n}\n\nif (tab == \"小说\") {\n if (device_type == \"安卓\") {\n book.type = 8;\n } else {\n book.type = 0;\n }\n} else if (tab == \"听书\") {\n if (device_type == \"安卓\") {\n book.type = 32;\n } else {\n book.type = 1;\n }\n} else if (tab == \"漫画\") {\n if (device_type == \"安卓\") {\n book.type = 64;\n } else {\n book.type = 2;\n }\n} else if (tab == \"短剧\") {\n if (device_type == \"安卓\") {\n book.type = 8;\n } else {\n book.type = 3;\n }\n} else {\n if (device_type == \"安卓\") {\n book.type = 8;\n } else {\n book.type = 0;\n }\n}\nlet qtcookie = cookie.getCookie(base_url);\nlet reading = getArguments(source.getVariable(), \"reading\");\n\nif (book && book.order != 0 && reading == '1') {\n try {\n if (!data || !book.variable || !base_url || !qtcookie) {\n java.log('缺少必要参数');\n } else {\n let parsedData;\n try {\n parsedData = JSON.parse(data);\n } catch (e) {\n java.log('解析data数据失败:' + e);\n }\n \n if (parsedData && parsedData.data && Array.isArray(parsedData.data) && parsedData.data[book.durChapterIndex]) {\n let ritem = parsedData.data[book.durChapterIndex];\n let bookInfoObj;\n try {\n bookInfoObj = JSON.parse(book.variable);\n } catch (e) {\n java.log('解析book.variable失败:' + e);\n }\n \n if (bookInfoObj) {\n let book_info;\n try {\n book_info = JSON.parse(bookInfoObj.book_detail || JSON.stringify(bookInfoObj));\n } catch (e) {\n java.log('解析book_detail失败:' + e);\n }\n \n if (book_info && typeof book_info === 'object') {\n let rurl = base_url + '\/add_book_to_book_shelf,';\n book_info['read_status'] = 1;\n book_info['last_chapter_item_id'] = ritem.item_id || '';\n book_info['last_chapter_title'] = ritem.title || '';\n let rop = {\n method: \"POST\",\n headers: {\n cookie: qtcookie\n },\n body: book_info\n };\n let check_book_url = base_url + '\/check_book_in_book_shelf,';\n let checkResponse;\n \n try {\n checkResponse = java.ajax(check_book_url + JSON.stringify(rop));\n } catch (e) {\n java.log('检查书籍请求失败:' + e);\n }\n \n if (checkResponse) {\n let check_data;\n try {\n check_data = JSON.parse(checkResponse).data;\n } catch (e) {\n java.log('解析检查响应失败:' + e);\n }\n try {\n if (check_data && check_data.id) {\n book_info['id'] = check_data.id;\n let uurl = base_url + '\/update_book_shelf,';\n java.ajax(uurl + JSON.stringify(rop));\n } else {\n java.ajax(rurl + JSON.stringify(rop));\n }\n } catch (e) {\n java.log('书架操作失败:' + e);\n }\n }\n }\n }\n }\n }\n } catch (error) {\n java.log('书籍同步流程异常:' + error);\n java.longToast('\\n同步阅读进度失败,但不影响阅读,可以前往登录关闭书架同步功能。');\n }\n}\ndata;\n<\/js>$.data",
"chapterName": "$.title",
"chapterUrl": "<js>\nlet tab = result.tab;\nlet sources = result.source;\nlet title = result.title;\nlet item_id = result.item_id;\nlet book_id = java.get(\"book_id\");\nlet url = result.toc_url || \"\";\nlet qtcontent = {\n book_id: book_id,\n item_id: item_id,\n title: title,\n sources: sources,\n tab: tab,\n url: url,\n};\nqtcontent = java.base64Encode(JSON.stringify(qtcontent));\nif (sources == '卷') {\n content_url = item_id\n} else {\n content_url = `data:;base64,${qtcontent},{\"type\":\"qingtian3\"}`;\n}\n<\/js>",
"updateTime": "$.first_pass_time"
},
"searchUrl": "<js>\nlet base_url = getArguments(source.getVariable(), 'server');\nlet media;\nlet sources = getArguments(source.getVariable(), 'sources') || getArguments(source.getVariable(), 'source');\nlet disabled_sources = getArguments(source.getVariable(), 'disabled_sources') || '0';\nif (String(key).startsWith(\"m:\") || String(key).startsWith(\"m:\")) {\n media = \"漫画\"\n key = key.slice(2)\n} else if (String(key).startsWith(\"t:\") || String(key).startsWith(\"t:\")) {\n media = \"听书\"\n key = key.slice(2)\n} else if (String(key).startsWith(\"d:\") || String(key).startsWith(\"d:\")) {\n media = \"短剧\"\n key = key.slice(2)\n} else if (String(key).startsWith(\"x:\") || String(key).startsWith(\"x:\")) {\n media = \"小说\"\n key = key.slice(2)\n} else {\n media = getArguments(source.getVariable(), 'tab') || getArguments(source.getVariable(), 'media');\n}\nif (key.includes('@')) {\n var parts = key.split('@');\n key = parts[0];\n sources = parts[1] || sources;\n}\nlet qtcookie = cookie.getCookie(base_url);\nlet op = {\n method: \"GET\",\n headers: {\n cookie: qtcookie\n },\n};\nop = JSON.stringify(op);\n`${base_url}\/search?title=${key}&tab=${media}&source=${sources}&page={{page}}&disabled_sources=${disabled_sources},${op}`\n<\/js>",
"weight": 0
}