🍅大灰狼聚合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, '&amp;')\n        .replace(\/<\/g, '&lt;')\n        .replace(\/>\/g, '&gt;')\n        .replace(\/\"\/g, '&quot;');\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(\/&quot;\/g, '\"')\n        .replace(\/&apos;|&lsquo;|&rsquo;\/g, \"'\")\n        .replace(\/&lt;\/g, '<')\n        .replace(\/&gt;\/g, '>')\n        .replace(\/&amp;\/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, '&amp;');\n                let displayCount = count;\n                const numCount = Number(count);\n                if (!isNaN(numCount) && numCount > 99) {\n                    displayCount = '99';\n                }\n                return `<div rs-native>${text}<comment count=\"${displayCount}\" onPress=\"java.showReadingBrowser('${encodedUrl}','${sources}段评')\"><\/div>`;\n            } else {\n                return `<div rs-native>${text}<\/div>`;\n            }\n        }\n    );\n    return result.replace(\/&#34;\/g, '\"');\n}\n\n\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 = \"&lrm;\\n&lrm;\";\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&lrm;\n    📖 书籍简介:\n${indentedAbstract}\n`;\n    } else {\n        basicInfo += `${heavyDivider}`;\n    }\n    info = `&lrm;\\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
}
广告