[{"bookSourceUrl":"http:\/\/vip.gyks.cf#光遇聚合","bookSourceName":"光遇聚合","enabledExplore":true,"enabled":true,"bookSourceGroup":"","author":"","help":false,"html":"<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>光遇书源<\/title>\n<\/head>\n<body>\n\n<\/body>\n\n<script>\n    var isCookieJar = true;\/\/ 不需要CookieJar请修改此处\n    class FlutterJSBridge {\n        constructor() {\n            this.init(); \/\/前台webview 里必须删除这行\n        }\n\n        init() {\n            if (window.flutter_inappwebview) {\n                this.isReady = true;\n                this.CookieJar();\n            } else {\n                window.addEventListener('flutterInAppWebViewPlatformReady', () => {\n                    this.isReady = true;\n                    console.log('JSBridge初始化完成');\n                    this.CookieJar();\n                });\n            }\n        }\n\n        \/\/通知原生页面初始化完成，仅在书源和tts生效，webview请勿使用，只有通知加载成功后才允许运行，否则会一直等待加载成功\n        async CookieJar() {\n            try {\n                await window.flutter_inappwebview.callHandler('CookieJar', isCookieJar);\n            } catch (error) {\n                console.error('汇报完成准备失败:', error);\n            }\n        }\n\n        \/\/获取应用编译版本\n        async getbuildNumber() {\n            try {\n                return await window.flutter_inappwebview.callHandler('buildNumber');\n            } catch (error) {\n                return 0;\n            }\n        }\n\n        \/\/获取应用版本\n        async getversion() {\n            try {\n                return await window.flutter_inappwebview.callHandler('version');\n            } catch (error) {\n                return \"0.0.0\";\n            }\n        }\n\n        \/\/将html转换成正文格式\n        async htmlToText(str) {\n            try {\n                return await window.flutter_inappwebview.callHandler('htmlToText', str);\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n        \/\/刷新正文页\n        async refreshContent(bookurl) {\n            try {\n                return await window.flutter_inappwebview.callHandler('refreshContent', bookurl);\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n        \/\/刷新发现页，如用户当前正处于发现页会直接刷新，不在发现页会清除发现页缓存\n        async refreshExplore() {\n            try {\n                return await window.flutter_inappwebview.callHandler('refreshExplore');\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n        \/\/显示软件气泡选择\n        async showsvg() {\n            try {\n                return await window.flutter_inappwebview.callHandler('showsvg');\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n        \/\/获取软件 svg 气泡\n        async getsvg() {\n            try {\n                return await window.flutter_inappwebview.callHandler('getsvg');\n            } catch (error) {\n                return [];\n            }\n        }\n\n\n        \/\/新增气泡\n        async addsvg(svg) {\n            try {\n                return await window.flutter_inappwebview.callHandler('addsvg', svg);\n            } catch (error) {\n                return [];\n            }\n        }\n\n        \/\/动态更新 html 代码，主要用于更新在线版地址，确保地址是最新的，此函数只能在书源中使用\n        async setHtml(str) {\n            try {\n                return await window.flutter_inappwebview.callHandler('setHtml', str);\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n        \/\/将简体字转成繁体字\n        async toTraditional(str) {\n            try {\n                return await window.flutter_inappwebview.callHandler('toTraditional', str);\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n\n        \/\/将繁体字转成简体字\n        async toSimplified(str) {\n            try {\n                return await window.flutter_inappwebview.callHandler('toSimplified', str);\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n        \/\/播放朗读引擎仅tts源生效\n        async voice() {\n            try {\n                return await window.flutter_inappwebview.callHandler('voice');\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n\n        \/\/获取设备唯一id\n        async getDeviceid() {\n            try {\n                return await window.flutter_inappwebview.callHandler('id');\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n        \/\/获取设备平台 此处返回 windows、macos、ios、ohos、android\n        async getDevice() {\n            try {\n                return await window.flutter_inappwebview.callHandler('device');\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n        \/\/获取轻悦时光登录用户名，没登录返回为空\n        async getLoginUser() {\n            try {\n                return await window.flutter_inappwebview.callHandler('getLoginUser');\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n        \/\/输出日志,前台webview请勿使用\n        \/\/str 为 String\n        async log(str) {\n            try {\n                return await window.flutter_inappwebview.callHandler('log', str);\n            } catch (error) {\n                return false;\n            }\n        }\n\n        \/\/书源调试时可输出 html 代码到前台\n        \/\/type 0 搜索源码 ， 1详情源码 ，2目录源码 ，3正文源码\n        \/\/str 为 String\n        \/\/type 为int\n        async text(type, str) {\n            try {\n                return await window.flutter_inappwebview.callHandler('text', type, str);\n            } catch (error) {\n                return false;\n            }\n        }\n\n        \/\/toast弹窗，显示3秒\n        \/\/str 为 String\n        async showToast(str) {\n            try {\n                return await window.flutter_inappwebview.callHandler('showToast', str);\n            } catch (error) {\n                return false;\n            }\n        }\n\n        \/\/长toast弹窗，显示10秒\n        \/\/str 为 String\n        async showLongToast(str) {\n            try {\n                return await window.flutter_inappwebview.callHandler('showLongToast', str);\n            } catch (error) {\n                return false;\n            }\n        }\n\n        \/\/webview 里禁止使用，webview请使用js获取ua （navigator.userAgent）\n        \/\/获取默认ua\n        async getWebViewUA() {\n            try {\n                return await window.flutter_inappwebview.callHandler('getWebViewUA');\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n        \/\/通过url打开外部应用\n        \/\/url 为 String\n        async openurl(url) {\n            try {\n                return await window.flutter_inappwebview.callHandler('openurl', url, \"\");\n            } catch (error) {\n                return false;\n            }\n        }\n\n        \/\/通过url打开外部应用并附带mimeType\n        \/\/url 为 String\n        \/\/mimeType 为 String\n        async openurlwithMimeType(url, mimeType) {\n            try {\n                return await window.flutter_inappwebview.callHandler('openurl', url, mimeType);\n            } catch (error) {\n                return false;\n            }\n        }\n\n        \/**\n         * 使用webView访问网络\n         * @param html 直接用webView载入的html, 如果html为空直接访问url\n         * @param url html内如果有相对路径的资源不传入url访问不了\n         * @param js 用来取返回值的js语句, 没有就返回整个源代码\n         * @param body 当参数不为空的时候，会以post请求，此时请务必在 header 中带上content-type\n         * @param header 请求的header头，此参数必须是json字符串\n         * @return 返回js获取的内容\n         *\/\n        async webview(url, js, html, body, header) {\n            try {\n                return await window.flutter_inappwebview.callHandler('webview', url, js, html, body, header, \"\", \"\");\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n        \/**\n         * overrideUrlRegex 为正则表达式\n         * 使用方法和上面的一样\n         * 但返回的内容为正则到的内容，如果无法正则到则返回 js 获取的内容，如果 js 为空则返回页面 html\n         *\/\n        async webViewGetOverrideUrl(url, js, html, body, header, overrideUrlRegex) {\n            try {\n                return await window.flutter_inappwebview.callHandler('webview', url, js, html, body, header, overrideUrlRegex, \"\");\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n        \/**\n         * 使用webView获取资源url\n         * urlregex 为正则表达式\n         * 使用方法和上面的一样\n         * 但返回的内容为正则到的内容，如果无法正则到则返回 js 获取的内容，如果 js 为空则返回页面 html\n         *\/\n        async webViewGetSource(url, js, html, body, header, urlregex) {\n            try {\n                return await window.flutter_inappwebview.callHandler('webview', url, js, html, body, header, \"\", urlregex);\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n        \/**\n         * 使用webView拦截 ajax\n         * ajaxregex 为正则表达式，通过 ajax 匹配 path\n         * 匹配成功返回 ajax 的结果 失败返回 html\n         *\/\n        async webViewGetAjax(url, html, body, header, ajaxregex) {\n            try {\n                return await window.flutter_inappwebview.callHandler('webviewajax', url, html, body, header, ajaxregex);\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n\n        \/**\n         * 启动前台 webview 访问链接并获取结束时的 html，可用于手工过盾\n         * @param url 网址\n         * @param title 标题\n         * @param header 请求的header头，此参数必须是json字符串\n         * @return 返回网页的内容\n         *\/\n        async startBrowser(url, title, header) {\n            try {\n                return await window.flutter_inappwebview.callHandler('startBrowser', url, title, header);\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n        \/**\n         * 启动前台 webview 并对每次打开的 url 进行拦截\n         * @param url 网址\n         * @param title 标题\n         * @param header 请求的header头，此参数必须是json字符串\n         *\/\n        async startBrowserWithShouldOverrideUrlLoading(url, title, header) {\n            try {\n                return await window.flutter_inappwebview.callHandler('startBrowserWithShouldOverrideUrlLoading', url, title, header);\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n        \/\/专门为段评设置的半屏显示，不返回任何东西\n        async startBrowserDp(url, title) {\n            try {\n                return await window.flutter_inappwebview.callHandler('startBrowserDp', url, title);\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n        \/\/仅前台webview可以使用，返回按钮，返回上一个页面\n        async back() {\n            try {\n                return await window.flutter_inappwebview.callHandler('back');\n            } catch (error) {\n                return false;\n            }\n        }\n\n        \/\/将 utf8字符串转到 gbk 并 url 编码\n        async utf8ToGbkUrlEncoded(str) {\n            try {\n                return await window.flutter_inappwebview.callHandler('utf8ToGbkUrlEncoded', str);\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n        \/*\n        * @param str为图片链接\n        * @param header 请求的header头，此参数必须是json字符串\n        * 此函数是让用户输入图片中的验证码，当链接为空则直接让用户输入验证码\n        *\/\n        async getVerificationCode(str, header) {\n            try {\n                return await window.flutter_inappwebview.callHandler('getVerificationCode', str, header);\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n        \/\/提交内容bookUrl,我会调用书源 info 函数来获取这本书的信息\n        async addbook(bookUrl) {\n            try {\n                return await window.flutter_inappwebview.callHandler('addbook', bookUrl);\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n        \/\/打开搜索页并用当前源搜索\n        async searchbook(key) {\n            try {\n                return await window.flutter_inappwebview.callHandler('searchbook', key);\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n        \/\/获取书本当前阅读章节index\n        async getdurChapterIndex(bookUrl) {\n            try {\n                return await window.flutter_inappwebview.callHandler('getdurChapterIndex', bookUrl);\n            } catch (error) {\n                return 0;\n            }\n        }\n\n        \/\/这里会返回 book 类或者空 ，如果正在阅读的书本不是当前书源或者没有阅读书本则返回空\n        async getReadBook() {\n            try {\n                return await window.flutter_inappwebview.callHandler('getReadBook');\n            } catch (error) {\n                return null;\n            }\n        }\n\n        \/\/utf8 字符串转base64\n        async base64encode(str) {\n            try {\n                return await window.flutter_inappwebview.callHandler('base64encode', str);\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n        \/\/base64 转utf8字符串\n        async base64decode(str) {\n            try {\n                return await window.flutter_inappwebview.callHandler('base64decode', str);\n            } catch (error) {\n                return \"\";\n            }\n        }\n\n\n    }\n\n    \/\/webview下isCookieJar必定true 会自动处理cookie\n    \/\/以下提交的url，headers,body 都必须为字符串,headers必须为json字符串\n    \/\/当followRedirects 为 false 时不处理重定向，当为 true 时会自动处理重定向 ，如不明白用途直接用 true 最佳\n    \/\/ 以下所有参数除当followRedirects外均为 String\n    \/\/ 如果需要使用http2协议 请在url 前添加 http2:\/\/ ，例如 http2:\/\/baidu.com\n    \/\/ 如果https一直被盾拦截 ，可以使用https2协议\n    class Http {\n        constructor() {\n            \/*\n             * 速率限制配置\n             * requestTimestamps: 存储请求时间戳的数组\n             * rateLimit: 速率限制，单位时间内最大请求数\n             * rateLimitWindow: 速率限制窗口，单位毫秒\n             *\/\n            this.open = false; \/\/ 是否开启速率限制\n            this.requestTimestamps = []; \/\/ 存储请求时间戳的数组\n            this.rateLimit = 5; \/\/ 速率限制，1000毫秒内最多5次请求\n            this.rateLimitWindow = 1000; \/\/ 速率限制窗口，1000毫秒\n        }\n\n        \/*\n         * 检查速率限制\n         * 实现方法：\n         * 1. 获取当前时间戳\n         * 2. 过滤掉超出时间窗口的时间戳\n         * 3. 检查是否超过速率限制\n         * 4. 如果超过限制，计算需要等待的时间并等待\n         * 5. 递归检查速率限制\n         * 6. 将当前时间戳添加到数组中\n         *\/\n        async checkRateLimit() {\n            if (!this.open) return;\n            const now = Date.now();\n            \/\/ 过滤掉超出时间窗口的时间戳\n            this.requestTimestamps = this.requestTimestamps.filter(timestamp => now - timestamp < this.rateLimitWindow);\n            \/\/ 检查是否超过速率限制\n            if (this.requestTimestamps.length >= this.rateLimit) {\n                \/\/ 计算需要等待的时间\n                const oldestTimestamp = this.requestTimestamps[0];\n                const waitTime = this.rateLimitWindow - (now - oldestTimestamp);\n                \/\/ 等待到速率限制可用\n                await new Promise(resolve => setTimeout(resolve, waitTime));\n                \/\/ 递归检查速率限制\n                return this.checkRateLimit();\n            }\n            \/\/ 将当前时间戳添加到数组中\n            this.requestTimestamps.push(now);\n        }\n\n\n        \/*\n         * 通用返回字段\n         * method post get 或者 head\n         * body 请求返回后的字节的 base64\n         * headers  map<String,List<String>> 可通过headers[\"\"]来或者\n         * statusCode 状态码\n         * statusMessage\n         * data 返回后的字节 格式化后的内容\n         *\/\n        async Get(url, headers, followRedirects) {\n            try {\n                await this.checkRateLimit();\n                return await window.flutter_inappwebview.callHandler('http', \"get\", url, \"\", JSON.stringify(headers), followRedirects, \"\");\n            } catch (error) {\n                return null;\n            }\n        }\n\n\n        async Head(url, headers, followRedirects) {\n            try {\n                await this.checkRateLimit();\n                return await window.flutter_inappwebview.callHandler('http', \"head\", url, \"\", JSON.stringify(headers), followRedirects, \"\");\n            } catch (error) {\n                return null;\n            }\n        }\n\n\n        async Post(url, headers, body, contenttype, followRedirects) {\n            try {\n                await this.checkRateLimit();\n                return await window.flutter_inappwebview.callHandler('http', \"post\", url, body, JSON.stringify(headers), followRedirects, contenttype);\n            } catch (error) {\n                return null;\n            }\n        }\n    }\n\n    class Cache {\n        constructor() {\n        }\n\n        async get(key) {\n            try {\n                return await window.flutter_inappwebview.callHandler('cache.get', key);\n            } catch (error) {\n                return null;\n            }\n        }\n\n        async set(key, value) {\n            try {\n                return await window.flutter_inappwebview.callHandler('cache.set', key, value);\n            } catch (error) {\n                return null;\n            }\n        }\n\n        async remove(key) {\n            try {\n                return await window.flutter_inappwebview.callHandler('cache.remove', key);\n            } catch (error) {\n                return null;\n            }\n        }\n\n        \/\/如果登录为弹窗格式的，里面输入框输入的内容可以通过这个函数获取，默认返回的json格式或者为空，需要自行转换\n        async getLoginInfo() {\n            return await this.get(\"LoginInfo\")\n        }\n\n        \/\/将修改后的弹窗输入内容报错 ，必须 JSON.stringify，不然会出错\n        async putLoginInfo(info) {\n            return await this.set(\"LoginInfo\", info)\n        }\n\n        \/\/获取书本变量\n        async getbookVariable(bookurl) {\n            return await this.get(bookurl)\n        }\n\n        \/\/写入书本变量\n        async setbookVariable(bookurl, value) {\n            return await this.set(bookurl, value)\n        }\n    }\n\n    class Cookie {\n        constructor() {\n        }\n\n        \/\/通过url获取当前url的所有cookie\n        async get(url) {\n            try {\n                return await window.flutter_inappwebview.callHandler('cookie.get', url);\n            } catch (error) {\n                return null;\n            }\n        }\n\n        \/\/通过url删除当前url的所有cookie\n        async remove(url) {\n            try {\n                return await window.flutter_inappwebview.callHandler('cookie.remove', url);\n            } catch (error) {\n                return null;\n            }\n        }\n\n\n        \/\/通过url保存当前url的所有cookie\n        async set(url, value) {\n            try {\n                return await window.flutter_inappwebview.callHandler('cookie.set', url, value);\n            } catch (error) {\n                return null;\n            }\n        }\n\n        \/\/设置单独一个cookie\n        async setCookie(url, key, value) {\n            try {\n                return await window.flutter_inappwebview.callHandler('cookie.setcookie', url, key, value);\n            } catch (error) {\n                return null;\n            }\n        }\n\n        \/\/通过 url 获取单个 cookie 的值\n        async getCookie(url, value) {\n            try {\n                return await window.flutter_inappwebview.callHandler('cookie.getCookie', url, value);\n            } catch (error) {\n                return null;\n            }\n        }\n    }\n\n    \/\/安全的创建一个 div 解析 html\n    function parseHTMLSafely(htmlStr) {\n        try {\n            \/\/ 在函数作用域内创建独立的临时容器\n            \/\/ 每个调用创建新的jQuery对象，互不影响\n            var tempDiv = document.createElement('div');\n            tempDiv.innerHTML = htmlStr;\n            return $(tempDiv);\n        } catch (e) {\n            flutterBridge.log(\"HTML解析错误:\" + e.message);\n            return $('<div>');\n        }\n    }\n\n    \/\/parseHTMLSafely 创建的用完后必须删除\n    function removeHTMLSafely(tempContainer) {\n        try {\n            tempContainer.innerHTML = '';\n            if (tempContainer.parentNode) {\n                tempContainer.parentNode.removeChild(tempContainer);\n            }\n        } catch (e) {\n            flutterBridge.log(\"HTML移除失败:\" + e.message);\n        }\n    }\n\n    \/\/移除 css js，创建parseHTMLSafely前如果用不上 cssjs 建议移除\n    function removeHTMLTags(htmlString) {\n        \/\/ 移除script标签\n        let result = htmlString.replace(\/<script\\b[^<]*(?:(?!<\\\/script>)<[^<]*)*<\\\/script>\/gi, '');\n        \/\/ 移除style标签\n        result = result.replace(\/<style\\b[^<]*(?:(?!<\\\/style>)<[^<]*)*<\\\/style>\/gi, '');\n        return result;\n    }\n\n<\/script>\n\n<script>\n    const flutterBridge = new FlutterJSBridge();\n    const cache = new Cache();\n    const http = new Http();\n    const cookie = new Cookie();\n    \/\/ getCloudSettings(true);\n\n    \/\/ 当前书源版本号，切勿修改，否则影响更新的识别\n    let localVersion = '26.6.4';\n    upHtml();\n    \/\/ 初始服务器列表\n    let hosts = [\n        'https:\/\/v1.gyks.cf',\n        'https:\/\/v2.gyks.cf',\n        'https:\/\/v3.gyks.cf',\n        'https:\/\/v4.gyks.cf',\n        'https:\/\/v5.gyks.cf',\n        'https:\/\/v6.gyks.cf',\n        'https:\/\/v7.gyks.cf',\n        'http:\/\/101.35.133.34:8888'\n    ];\n\n\n    \/\/ 初始化值\n    const defaultConfig = {\n        线路: hosts[0],\n        发现页来源: \"番茄\",\n        发现页类型: \"小说\"\n    };\n\n    \/\/ 获取正在使用的线路\n    async function BaseUrl() {\n        let hostsbk = (await getVariable('云端配置'))['hosts'] || hosts;\n        let h = await getVariable(\"线路\");\n        if (!h || String(h) === \"undefind\") {\n            h = hostsbk[0]\n        }\n        return h;\n    }\n\n    \/\/ 获取登陆token\n    async function getToken() {\n        let hostsbk = (await getVariable('云端配置'))['hosts'] || hosts;\n        try {\n            for (let h of hostsbk) {\n                let qttoken = await cookie.getCookie(h, \"qttoken\");\n                if (qttoken !== \"\" && qttoken !== null) {\n                    return qttoken;\n                }\n            }\n        } catch {\n        }\n        return \"\";\n    }\n\n    async function getFqToken() {\n        let sessionid = await cookie.getCookie(\"https:\/\/fanqienovel.com\", \"sessionid\");\n        if (sessionid !== \"\" && sessionid !== null) {\n            return sessionid;\n        }\n        return \"\";\n    }\n\n    \/\/ 切换线路\n    async function switchToNextLine(toast = \"\") {\n        const currentLine = await BaseUrl();\n        let hostsbk = (await getVariable('云端配置'))['hosts'] || hosts;\n        let currentIndex = hostsbk.findIndex(item => item === currentLine);\n        let nextIndex = (currentIndex + 1) % hostsbk.length;\n        const nextLine = hostsbk[nextIndex];\n        await setVariable(\"线路\", nextLine);\n        const notifySwitch = await getVariable(\"切换提醒\") || \"true\";\n        if (notifySwitch === 'true') {\n            flutterBridge.showToast(`${toast}\\n自动切换到:${nextLine}`)\n        }\n        return nextLine;\n    }\n\n    \/\/ 请求封装\n    async function request(url, method = \"GET\", body = {}, index = 0) {\n        let dataJson = {}\n        let urla = url;\n        if (!url.includes(\"http\")) {\n            urla = await BaseUrl() + urla\n        }\n        try {\n            let device = await flutterBridge.getDevice();\n            let qttoken = await getToken();\n            let headers = {\n                'cookie': `qttoken=${qttoken};device=${device};`,\n                'Content-Type': 'application\/json'\n            }\n            let data = \"\";\n            const startTime = new Date().getTime();\n            if (method === \"GET\") {\n                data = await http.Get(urla, headers);\n            } else {\n                data = await http.Post(urla, headers, JSON.stringify(body), \"application\/json\");\n            }\n            const endTime = new Date().getTime();\n            const timeoutSwitch = await getVariable(\"超时自动切换\") || \"true\";\n            let timeout = await getVariable(\"超时时间\") || \"5\";\n            timeout = parseInt(timeout) * 1000;\n            if (timeoutSwitch === 'true' && endTime - startTime > timeout) {\n                await switchToNextLine(\"线路超时\")\n            }\n            dataJson = data.data;\n            dataJson = JSON.parse(dataJson);\n            return dataJson;\n        } catch {\n            let hostsbk = (await getVariable('云端配置'))['hosts'] || hosts;\n            let autoSwitch = await getVariable(\"自动切换\") || \"true\";\n            if (hostsbk.length > index+1 && autoSwitch === 'true') {\n                await switchToNextLine(\"线路报错\")\n                return await request(url, method, body, index + 1)\n            }\n        }\n        return dataJson\n    }\n\n    async function upHtml() {\n        \/\/ flutterBridge.showToast(`正在检查更新`);\n        let html = await http.Get(`${await BaseUrl()}\/static\/source_config\/gyks.html`);\n        html = html.data;\n        \/\/ flutterBridge.showToast(html);\n        let couldVersion = \"0\"\n        let match = html.match(\/let localVersion = '([^\\']+)'\/);\n        if (match) {\n            couldVersion = match[1];\n        }\n        let compareResult = compareVersions(couldVersion);\n        if (compareResult === -1) {\n            \/\/ flutterBridge.showToast(`发现新版本：${couldVersion},当前版本：${localVersion},正在更新光遇聚合书源`);\n            await flutterBridge.setHtml(html);\n            flutterBridge.showToast(`光遇聚合书源发现新版本，已更新\\n${localVersion} → ${couldVersion}`);\n        } else {\n            \/\/ flutterBridge.showToast(`已是最新版本：${localVersion}`);\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(localVersion);\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    \/\/ 获取云端配置\n    async function getCloudSettings(r) {\n        if (r === undefined) r = false;\n        let c = await cache.get('gyks_config');\n        if (r || !c) {\n            await flutterBridge.showToast(`正在更新最新配置`);\n            try {\n                let url = `\/static\/source_config\/config.json`;\n                let js = await request(url);\n                let intc = parseInt(c);\n                let intv = parseInt(js['version']);\n                if (intc >= intv) {\n                    await flutterBridge.showToast(`已是最新配置：${js['version']}`);\n                    return;\n                } else if (!intc && intv) {\n                    await flutterBridge.showToast(`已初始化配置：${intv}`);\n                } else if (intc < intv || !intc && intv) {\n                    await flutterBridge.showToast(`已更新配置：${intc}→${intv}`);\n                } else {\n                    await flutterBridge.showToast(`获取配置失败：请切换线路再试试~`);\n                    return;\n                }\n                await cache.set('gyks_config', js['version'])\n                await setVariable('云端配置', js);\n                return js;\n            } catch (e) {\n                await flutterBridge.showToast(`获取最新配置失败：${e}`);\n            }\n        }\n    }\n\n    \/\/ 统一获取和解析变量\n    async function _getParsedVariable() {\n        let v = await cache.get(\"gyks_variable\");\n        try {\n            return JSON.parse(v);\n        } catch {\n            return defaultConfig;\n        }\n    }\n\n    \/\/ 还原\n    async function deleteVariable() {\n        await cache.remove(\"gyks_variable\");\n        await cache.remove('gyks_config');\n        await flutterBridge.showToast('所有设置已还原到初始化导入状态');\n    }\n\n    \/\/ 获取源变量\n    async function getVariable(k = \"\") {\n        const parsed = await _getParsedVariable();\n        if (k === \"\") {\n            return parsed;\n        }\n        let value = parsed[k];\n        if (value === undefined) {\n            value = defaultConfig[k];\n        }\n        return value !== undefined ? value : \"\";\n    }\n\n    \/\/ 设置源变量\n    async function setVariable(k, v, t = true) {\n        const vs = await getVariable();\n        vs[k] = v;\n        await cache.set(\"gyks_variable\", JSON.stringify(vs));\n        if (k !== '云端配置' && t) {\n            const notifySwitch = await getVariable(\"切换提醒\") || \"true\";\n            const notifyMsg = `设置 ${k} 为 ${v}`;\n            if (notifySwitch === 'true' && k === \"线路\") {\n                await flutterBridge.showToast(notifyMsg)\n            }\n        }\n    }\n\n\n    async function search(key, page) {\n        let moreSettings = await getVariable('更多设置');\n        let tab = moreSettings && moreSettings['搜索模式'] || '小说';\n        let sourcesKey = moreSettings && moreSettings[tab] || '全部';\n\n        let disabled_sources = moreSettings && moreSettings['强制搜索'] || \"0\";\n        if (disabled_sources === \"false\") {\n            disabled_sources = \"0\";\n        } else if (disabled_sources === \"true\") {\n            disabled_sources = \"1\";\n        }\n\n        let searchUrl = `\/search?title=${key}&tab=${tab}&source=${sourcesKey}&page=${page}&disabled_sources=${disabled_sources}`;\n        let data = await request(searchUrl);\n        let books_data = data.data;\n\n        let books = [];\n        for (var i = 0; i < books_data.length; i++) {\n            var book = books_data[i];\n            let type = 0;\n            if (book.tab === \"听书\") {\n                type = 1;\n            } else if (book.tab === \"小说\") {\n                type = 0;\n            } else if (book.tab === \"漫画\") {\n                type = 2;\n            }\n            books.push({\n                \"bookUrl\": `\/detail?book_id=${book.book_id}&source=${book.source}&tab=${book.tab}`,\n                \"name\": book.book_name,\n                \"author\": book.author,\n                \"kind\": [\n                    book.status,\n                    book.score,\n                    book.tags,\n                    typeof book.last_chapter_update_time === \"number\" && book.last_chapter_update_time > 946656000 && book.last_chapter_update_time < 4102444800\n                        ? new Date(book.last_chapter_update_time * 1000).toLocaleString(\"zh-CN\")\n                        : book.last_chapter_update_time\n                ].filter(v => v !== undefined && v !== null && v !== \"\").join(\",\"),\n                \"coverUrl\": book.thumb_url,\n                \"intro\": book.abstract,\n                \"tocUrl\": ``,\n                \"wordCount\": book.word_number,\n                \"type\": type,\n                \"latestChapterTitle\": `${book.source}:${book.latest_chapter_title || \"\"}`,\n            })\n        }\n        return JSON.stringify(books);\n    }\n\n    async function info(bookurl) {\n        let tone_id = await cache.getbookVariable(bookurl)\n        cache.set(\"tone_id\", tone_id || \"4\");\n        cache.set(\"variable\", tone_id || \"\");\n        var variable = JSON.stringify({\n            'custom': tone_id\n        });\n        let infoUrl = `${bookurl}&variable=${variable}`;\n        let data = await request(infoUrl, \"POST\");\n        let book_info = data.data;\n        let moreSettings = await getVariable('更多设置');\n        let full_abstract = moreSettings && moreSettings['完整简介'] || 'true';\n        let abstract = \"@html:\"\n        if (full_abstract === 'false') {\n            abstract += `\n                <style>\n                    .book-simple-theme { --bg-primary: rgba(30, 30, 30, 0.95); --bg-secondary: rgba(50, 50, 50, 0.9); --bg-card: rgba(70, 70, 70, 0.9); --text-primary: #f0f0f0; --text-secondary: #b0b0b0; --text-label: #5ce0b5; --border-color: #505050; max-width: 800px; margin: 0 auto; padding: 16px; border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', Arial, sans-serif; background-color: var(--bg-primary); backdrop-filter: blur(10px); }\n                    .book-simple-theme .book-header { border-bottom: 2px solid #4dabf7; padding-bottom: 10px; margin-bottom: 14px; }\n                    .book-simple-theme .book-title { margin: 0; color: #4dabf7; font-size: 20px; font-weight: 600; }\n                    .book-simple-theme .book-section { background-color: var(--bg-secondary); padding: 14px; border-radius: 8px; margin-bottom: 14px; }\n                    .book-simple-theme .book-section-pink { border-left: 4px solid #ff6b9d; }\n                    .book-simple-theme .book-section-blue { border-left: 4px solid #4dabf7; }\n                    .book-simple-theme .section-title { margin: 0 0 10px 0; font-size: 15px; font-weight: 600; color: var(--text-primary); }\n                    .book-simple-theme .section-title-pink { color: #ff6b9d; }\n                    .book-simple-theme .section-title-blue { color: #4dabf7; }\n                    .book-simple-theme .abstract-content { background-color: var(--bg-card); padding: 10px; border-radius: 6px; }\n                    .book-simple-theme .abstract-text { margin: 0; color: var(--text-primary); font-size: 13px; line-height: 1.7; text-align: justify; text-indent: 2em; }\n                    .book-simple-theme .info-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }\n                    .book-simple-theme .info-card { background-color: var(--bg-card); padding: 10px; border-radius: 6px; }\n                    .book-simple-theme .info-card-label { margin: 0 0 4px 0; color: var(--text-label); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }\n                    .book-simple-theme .info-card-value { margin: 0; font-size: 13px; line-height: 1.4; color: var(--text-primary); }\n                    .book-simple-theme .info-card-value-blue { color: #4dabf7; font-weight: 600; }\n                    .book-simple-theme .info-card-value-text { font-size: 12px; word-break: break-word; }\n                    .book-simple-theme.book-day, .book-simple-day { --bg-primary: rgba(255, 255, 255, 0.95) !important; --bg-secondary: rgba(248, 249, 250, 0.9) !important; --bg-card: rgba(255, 255, 255, 0.9) !important; --text-primary: #212529 !important; --text-secondary: #6c757d !important; --text-label: #42b993 !important; --border-color: #dee2e6 !important; max-width: 800px; margin: 0 auto; padding: 16px; border-radius: 12px; box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', Arial, sans-serif; background-color: var(--bg-primary) !important; }\n                    .book-simple-day .book-header { border-bottom: 2px solid #007bff !important; padding-bottom: 10px; margin-bottom: 14px; }\n                    .book-simple-day .book-title { margin: 0; color: #007bff; font-size: 20px; font-weight: 600; }\n                    .book-simple-day .book-section { background-color: var(--bg-secondary); padding: 14px; border-radius: 8px; margin-bottom: 14px; }\n                    .book-simple-day .book-section-pink { border-left: 4px solid #e83e8c; }\n                    .book-simple-day .book-section-blue { border-left: 4px solid #007bff; }\n                    .book-simple-day .section-title { margin: 0 0 10px 0; font-size: 15px; font-weight: 600; color: var(--text-primary); }\n                    .book-simple-day .section-title-pink { color: #e83e8c; }\n                    .book-simple-day .section-title-blue { color: #007bff; }\n                    .book-simple-day .abstract-content { background-color: var(--bg-card); padding: 10px; border-radius: 6px; }\n                    .book-simple-day .abstract-text { margin: 0; color: var(--text-primary); font-size: 13px; line-height: 1.7; text-align: justify; text-indent: 2em; }\n                    .book-simple-day .info-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }\n                    .book-simple-day .info-card { background-color: var(--bg-card); padding: 10px; border-radius: 6px; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08); }\n                    .book-simple-day .info-card-label { margin: 0 0 4px 0; color: var(--text-label); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }\n                    .book-simple-day .info-card-value { margin: 0; font-size: 13px; line-height: 1.4; color: var(--text-primary); }\n                    .book-simple-day .info-card-value-blue { color: #007bff; font-weight: 600; }\n                    .book-simple-day .info-card-value-text { font-size: 12px; word-break: break-word; }\n                    .book-simple-theme.book-dark, .book-simple-dark { --bg-primary: rgba(30, 30, 30, 0.95) !important; --bg-secondary: rgba(50, 50, 50, 0.9) !important; --bg-card: rgba(70, 70, 70, 0.9) !important; --text-primary: #f0f0f0 !important; --text-secondary: #b0b0b0 !important; --text-label: #5ce0b5 !important; --border-color: #505050 !important; max-width: 800px; margin: 0 auto; padding: 16px; border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', Arial, sans-serif; background-color: var(--bg-primary) !important; backdrop-filter: blur(10px); }\n                    .book-simple-dark .book-header { border-bottom: 2px solid #4dabf7 !important; padding-bottom: 10px; margin-bottom: 14px; }\n                    .book-simple-dark .book-title { margin: 0; color: #4dabf7; font-size: 20px; font-weight: 600; }\n                    .book-simple-dark .book-section { background-color: var(--bg-secondary); padding: 14px; border-radius: 8px; margin-bottom: 14px; }\n                    .book-simple-dark .book-section-pink { border-left: 4px solid #ff6b9d; }\n                    .book-simple-dark .book-section-blue { border-left: 4px solid #4dabf7; }\n                    .book-simple-dark .section-title { margin: 0 0 10px 0; font-size: 15px; font-weight: 600; color: var(--text-primary); }\n                    .book-simple-dark .section-title-pink { color: #ff6b9d; }\n                    .book-simple-dark .section-title-blue { color: #4dabf7; }\n                    .book-simple-dark .abstract-content { background-color: var(--bg-card); padding: 10px; border-radius: 6px; }\n                    .book-simple-dark .abstract-text { margin: 0; color: var(--text-primary); font-size: 13px; line-height: 1.7; text-align: justify; text-indent: 2em; }\n                    .book-simple-dark .info-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }\n                    .book-simple-dark .info-card { background-color: var(--bg-card); padding: 10px; border-radius: 6px; }\n                    .book-simple-dark .info-card-label { margin: 0 0 4px 0; color: var(--text-label); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }\n                    .book-simple-dark .info-card-value { margin: 0; font-size: 13px; line-height: 1.4; color: var(--text-primary); }\n                    .book-simple-dark .info-card-value-blue { color: #4dabf7; font-weight: 600; }\n                    .book-simple-dark .info-card-value-text { font-size: 12px; word-break: break-word; }\n                    .book-simple-theme .book-section-purple { border-left: 4px solid #9d6fd6; }\n                    .book-simple-theme .section-title-purple { color: #9d6fd6; }\n                    .book-simple-theme .tts-section { background-color: var(--bg-secondary); padding: 14px; border-radius: 8px; margin-bottom: 16px; }\n                    .book-simple-theme .tts-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }\n                    .book-simple-theme .tts-card { background-color: var(--bg-card); padding: 10px; border-radius: 6px; }\n                    .book-simple-theme .tts-label { margin: 0 0 4px 0; color: var(--text-label); font-size: 12px; }\n                    .book-simple-theme .tts-value { margin: 0; color: var(--text-primary); font-size: 13px; font-weight: 600; }\n                    .book-simple-theme .tts-tip { background-color: rgba(0, 0, 0, 0.7); border: 1px solid #ffc107; padding: 12px; border-radius: 6px; grid-column: 1 \/ -1; }\n                    .book-simple-theme .tts-tip-text { margin: 0; color: #ffffff; font-size: 14px; font-weight: 600; line-height: 1.8; }\n                    .book-simple-day .book-section-purple { border-left: 4px solid #6f42c1; }\n                    .book-simple-day .section-title-purple { color: #6f42c1; }\n                    .book-simple-day .tts-section { background-color: var(--bg-secondary); padding: 14px; border-radius: 8px; margin-bottom: 16px; }\n                    .book-simple-day .tts-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }\n                    .book-simple-day .tts-card { background-color: var(--bg-card); padding: 10px; border-radius: 6px; }\n                    .book-simple-day .tts-label { margin: 0 0 4px 0; color: var(--text-label); font-size: 12px; }\n                    .book-simple-day .tts-value { margin: 0; color: var(--text-primary); font-size: 13px; font-weight: 600; }\n                    .book-simple-day .tts-tip { background-color: rgba(0, 0, 0, 0.85); border: 1px solid #ffc107; padding: 12px; border-radius: 6px; grid-column: 1 \/ -1; }\n                    .book-simple-day .tts-tip-text { margin: 0; color: #ffffff; font-size: 14px; font-weight: 600; line-height: 1.8; }\n                    .book-simple-dark .book-section-purple { border-left: 4px solid #9d6fd6; }\n                    .book-simple-dark .section-title-purple { color: #9d6fd6; }\n                    .book-simple-dark .tts-section { background-color: var(--bg-secondary); padding: 14px; border-radius: 8px; margin-bottom: 16px; }\n                    .book-simple-dark .tts-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }\n                    .book-simple-dark .tts-card { background-color: var(--bg-card); padding: 10px; border-radius: 6px; }\n                    .book-simple-dark .tts-label { margin: 0 0 4px 0; color: var(--text-label); font-size: 12px; }\n                    .book-simple-dark .tts-value { margin: 0; color: var(--text-primary); font-size: 13px; font-weight: 600; }\n                    .book-simple-dark .tts-tip { background-color: rgba(0, 0, 0, 0.85); border: 1px solid #ffc107; padding: 12px; border-radius: 6px; grid-column: 1 \/ -1; }\n                    .book-simple-dark .tts-tip-text { margin: 0; color: #ffffff; font-size: 14px; font-weight: 600; line-height: 1.8; }\n                <\/style>\n                <div class=\"book-simple-theme book-qingtian-theme\">\n                    <div class=\"book-header\">\n                        <h2 class=\"book-title\">书籍信息<\/h2>\n                    <\/div>\n                    ${book_info.abstract ? `\n                    <div class=\"book-section book-section-pink\">\n                        <h3 class=\"section-title section-title-pink\">书籍简介<\/h3>\n                        <div class=\"abstract-content\">\n                            <p class=\"abstract-text\">${book_info.abstract.replace(\/\\n\/g, '<br>')}<\/p>\n                        <\/div>\n                    <\/div>\n                    ` : ''}\n                    <div class=\"book-section book-section-blue\">\n                        <div class=\"info-grid\">\n                            <div class=\"info-card\">\n                                <p class=\"info-card-label\">来源<\/p>\n                                <p class=\"info-card-value info-card-value-blue\">${book_info.source}<\/p>\n                            <\/div>\n                            ${book_info.latest_chapter_title ? `\n                            <div class=\"info-card\">\n                                <p class=\"info-card-label\">最新章节<\/p>\n                                <p class=\"info-card-value info-card-value-text\">${book_info.latest_chapter_title}<\/p>\n                            <\/div>\n                            ` : ''}\n                            ${book_info.last_chapter_update_time ? `\n                            <div class=\"info-card\">\n                                <p class=\"info-card-label\">更新时间<\/p>\n                                <p class=\"info-card-value info-card-value-text\">${book_info.last_chapter_update_time}<\/p>\n                            <\/div>\n                            ` : ''}\n                        <\/div>\n                    <\/div>\n\n                    ${book_info.tab === \"听书\" && book_info.book_tts ? `\n                    <div class=\"book-section book-section-purple\">\n                        <h3 class=\"section-title section-title-purple\">听书设置<\/h3>\n                        <div class=\"tts-grid\">\n                            <div class=\"tts-card\">\n                                <p class=\"tts-label\">当前音色<\/p>\n                                <p class=\"tts-value\">${tone_id || \"4\"}<\/p>\n                            <\/div>\n                            <div class=\"tts-tip\" style=\"grid-column: 1 \/ -1;\">\n                                <p class=\"tts-tip-text\"><strong>使用提示：<\/strong>AI音色请将音色值填入书籍变量然后刷新页面，真人音色请重新搜索选择对应主播的书籍<\/p>\n                            <\/div>\n                            <div class=\"tts-card\">\n                                <p class=\"tts-label\">可选音色<\/p>\n                                <p class=\"tts-value\">${book_info.book_tts.replace(\"\\n💠相关注意事项：\\n↓↓↓↓↓↓↓↓↓↓\\n\\n🎤本书可选音色：\\n\", \"\").replace(\/\\n\/g, '<br>')}<\/p>\n                            <\/div>\n                        <\/div>\n                    <\/div>\n                    ` : ''}\n                <\/div>`;\n        } else {\n            let user_info = {};\n            let qttoken = await getToken();\n            if (qttoken !== \"\") {\n                let user_data = await request(`\/get_avatar`);\n                user_info = user_data;\n                flutterBridge.log(JSON.stringify(user_info))\n            }\n            abstract += `\n                <style>\n                    .book-qingtian-mr-theme { --bg-primary: rgba(30, 30, 30, 0.95); --bg-secondary: rgba(50, 50, 50, 0.9); --bg-card: rgba(70, 70, 70, 0.9); --text-primary: #f0f0f0; --text-secondary: #b0b0b0; --text-label: #5ce0b5; --border-color: #505050; max-width: 800px; margin: 0 auto; padding: 16px; border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', Arial, sans-serif; background-color: var(--bg-primary); backdrop-filter: blur(10px); }\n                    .book-qingtian-mr-theme .book-header { border-bottom: 2px solid #4dabf7; padding-bottom: 10px; margin-bottom: 14px; }\n                    .book-qingtian-mr-theme .book-title { margin: 0; color: #4dabf7; font-size: 20px; font-weight: 600; }\n                    .book-qingtian-mr-theme .book-info-box { background-color: var(--bg-secondary); padding: 12px; border-radius: 8px; margin-bottom: 14px; border-left: 4px solid #4dabf7; }\n                    .book-qingtian-mr-theme .book-info-flex { display: flex; flex-wrap: wrap; gap: 8px 16px; align-items: center; font-size: 13px; }\n                    .book-qingtian-mr-theme .book-label { color: var(--text-label); font-weight: 600; }\n                    .book-qingtian-mr-theme .book-value { color: var(--text-primary); }\n                    .book-qingtian-mr-theme .book-section { background-color: var(--bg-secondary); padding: 14px; border-radius: 8px; margin-bottom: 14px; }\n                    .book-qingtian-mr-theme .book-section-pink { border-left: 4px solid #ff6b9d; }\n                    .book-qingtian-mr-theme .book-section-yellow { border-left: 4px solid #ffca28; }\n                    .book-qingtian-mr-theme .book-section-purple { border-left: 4px solid #9d6fd6; }\n                    .book-qingtian-mr-theme .section-title { margin: 0 0 10px 0; font-size: 15px; font-weight: 600; color: var(--text-primary); }\n                    .book-qingtian-mr-theme .section-title-pink { color: #ff6b9d; }\n                    .book-qingtian-mr-theme .section-title-purple { color: #9d6fd6; }\n                    .book-qingtian-mr-theme .abstract-content { background-color: var(--bg-card); padding: 10px; border-radius: 6px; }\n                    .book-qingtian-mr-theme .abstract-text { margin: 0; color: var(--text-primary); font-size: 13px; line-height: 1.7; text-align: justify; text-indent: 2em; }\n                    .book-qingtian-mr-theme .info-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }\n                    .book-qingtian-mr-theme .info-grid-title { grid-column: 1 \/ -1; margin-bottom: 8px; }\n                    .book-qingtian-mr-theme .info-title { margin: 0 0 8px 0; color: var(--text-primary); font-size: 15px; font-weight: 600; padding-bottom: 6px; border-bottom: 1px solid var(--border-color); }\n                    .book-qingtian-mr-theme .info-card { background-color: var(--bg-card); padding: 10px; border-radius: 6px; }\n                    .book-qingtian-mr-theme .info-card-label { margin: 0 0 4px 0; color: var(--text-label); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }\n                    .book-qingtian-mr-theme .info-card-value { margin: 0; font-size: 13px; line-height: 1.4; color: var(--text-primary); }\n                    .book-qingtian-mr-theme .info-card-value-blue { color: #4dabf7; font-weight: 600; }\n                    .book-qingtian-mr-theme .info-card-value-green { color: #3dd68c; }\n                    .book-qingtian-mr-theme .info-card-value-orange { color: #ffb74d; }\n                    .book-qingtian-mr-theme .info-card-value-text { font-size: 12px; word-break: break-word; }\n                    .book-qingtian-mr-theme .info-update { grid-column: 1 \/ -1; margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--border-color); }\n                    .book-qingtian-mr-theme .info-update-title { margin: 0 0 10px 0; color: var(--text-primary); font-size: 14px; font-weight: 600; }\n                    .book-qingtian-mr-theme .update-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }\n                    .book-qingtian-mr-theme .tts-section { background-color: var(--bg-secondary); padding: 14px; border-radius: 8px; margin-bottom: 16px; }\n                    .book-qingtian-mr-theme .tts-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }\n                    .book-qingtian-mr-theme .tts-card { background-color: var(--bg-card); padding: 10px; border-radius: 6px; }\n                    .book-qingtian-mr-theme .tts-label { margin: 0 0 4px 0; color: var(--text-label); font-size: 12px; }\n                    .book-qingtian-mr-theme .tts-value { margin: 0; color: var(--text-primary); font-size: 13px; font-weight: 600; }\n                    .book-qingtian-mr-theme .tts-tip { background-color: rgba(0, 0, 0, 0.7); border: 1px solid #ffc107; padding: 12px; border-radius: 6px; }\n                    .book-qingtian-mr-theme .tts-tip-text { margin: 0; color: #ffffff; font-size: 14px; font-weight: 600; line-height: 1.8; }\n                    .book-qingtian-mr-theme .text-login-success { color: #3dd68c; font-weight: 600; }\n                    .book-qingtian-mr-theme .text-login-fail { color: #ff6b6b; font-weight: 600; }\n                    .book-qingtian-mr-theme.book-day, .book-day { --bg-primary: rgba(255, 255, 255, 0.95) !important; --bg-secondary: rgba(248, 249, 250, 0.9) !important; --bg-card: rgba(255, 255, 255, 0.9) !important; --text-primary: #212529 !important; --text-secondary: #6c757d !important; --text-label: #42b993 !important; --border-color: #dee2e6 !important; max-width: 800px; margin: 0 auto; padding: 16px; border-radius: 12px; box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', Arial, sans-serif; background-color: var(--bg-primary) !important; }\n                    .book-qingtian-mr-theme.book-day ., .book-day .book-header { border-bottom: 2px solid #007bff !important; padding-bottom: 10px; margin-bottom: 14px; }\n                    .book-qingtian-mr-theme.book-day .book-title, .book-day .book-title { margin: 0; color: #007bff; font-size: 20px; font-weight: 600; }\n                    .book-day .book-info-box { background-color: var(--bg-secondary); padding: 12px; border-radius: 8px; margin-bottom: 14px; border-left: 4px solid #007bff; }\n                    .book-day .book-info-flex { display: flex; flex-wrap: wrap; gap: 8px 16px; align-items: center; font-size: 13px; }\n                    .book-day .book-label { color: var(--text-label); font-weight: 600; }\n                    .book-day .book-value { color: var(--text-primary); }\n                    .book-day .book-section { background-color: var(--bg-secondary); padding: 14px; border-radius: 8px; margin-bottom: 14px; }\n                    .book-day .book-section-pink { border-left: 4px solid #e83e8c; }\n                    .book-day .book-section-yellow { border-left: 4px solid #ffc107; }\n                    .book-day .book-section-purple { border-left: 4px solid #6f42c1; }\n                    .book-day .section-title { margin: 0 0 10px 0; font-size: 15px; font-weight: 600; color: var(--text-primary); }\n                    .book-day .section-title-pink { color: #e83e8c; }\n                    .book-day .section-title-purple { color: #6f42c1; }\n                    .book-day .abstract-content { background-color: var(--bg-card); padding: 10px; border-radius: 6px; }\n                    .book-day .abstract-text { margin: 0; color: var(--text-primary); font-size: 13px; line-height: 1.7; text-align: justify; text-indent: 2em; }\n                    .book-day .info-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }\n                    .book-day .info-grid-title { grid-column: 1 \/ -1; margin-bottom: 8px; }\n                    .book-day .info-title { margin: 0 0 8px 0; color: var(--text-primary); font-size: 15px; font-weight: 600; padding-bottom: 6px; border-bottom: 1px solid var(--border-color); }\n                    .book-day .info-card { background-color: var(--bg-card); padding: 10px; border-radius: 6px; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08); }\n                    .book-day .info-card-label { margin: 0 0 4px 0; color: var(--text-label); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }\n                    .book-day .info-card-value { margin: 0; font-size: 13px; line-height: 1.4; color: var(--text-primary); }\n                    .book-day .info-card-value-blue { color: #007bff; font-weight: 600; }\n                    .book-day .info-card-value-green { color: #28a745; }\n                    .book-day .info-card-value-orange { color: #ff9800; }\n                    .book-day .info-card-value-text { font-size: 12px; word-break: break-word; }\n                    .book-day .info-update { grid-column: 1 \/ -1; margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--border-color); }\n                    .book-day .info-update-title { margin: 0 0 10px 0; color: var(--text-primary); font-size: 14px; font-weight: 600; }\n                    .book-day .update-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }\n                    .book-day .tts-section { background-color: var(--bg-secondary); padding: 14px; border-radius: 8px; margin-bottom: 16px; }\n                    .book-day .tts-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }\n                    .book-day .tts-card { background-color: var(--bg-card); padding: 10px; border-radius: 6px; }\n                    .book-day .tts-label { margin: 0 0 4px 0; color: var(--text-label); font-size: 12px; }\n                    .book-day .tts-value { margin: 0; color: var(--text-primary); font-size: 13px; font-weight: 600; }\n                    .book-day .tts-tip { background-color: rgba(0, 0, 0, 0.85); border: 1px solid #ffc107; padding: 12px; border-radius: 6px; }\n                    .book-day .tts-tip-text { margin: 0; color: #ffffff; font-size: 14px; font-weight: 600; line-height: 1.8; }\n                    .book-day .text-login-success { color: #28a745; font-weight: 600; }\n                    .book-day .text-login-fail { color: #dc3545; font-weight: 600; }\n                    .book-qingtian-mr-theme.book-dark, .book-dark { --bg-primary: rgba(30, 30, 30, 0.95) !important; --bg-secondary: rgba(50, 50, 50, 0.9) !important; --bg-card: rgba(70, 70, 70, 0.9) !important; --text-primary: #f0f0f0 !important; --text-secondary: #b0b0b0 !important; --text-label: #5ce0b5 !important; --border-color: #505050 !important; max-width: 800px; margin: 0 auto; padding: 16px; border-radius: 12px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Microsoft YaHei', Arial, sans-serif; background-color: var(--bg-primary) !important; backdrop-filter: blur(10px); }\n                    .book-dark .book-header { border-bottom: 2px solid #4dabf7 !important; padding-bottom: 10px; margin-bottom: 14px; }\n                    .book-dark .book-title { margin: 0; color: #4dabf7; font-size: 20px; font-weight: 600; }\n                    .book-dark .book-info-box { background-color: var(--bg-secondary); padding: 12px; border-radius: 8px; margin-bottom: 14px; border-left: 4px solid #4dabf7; }\n                    .book-dark .book-info-flex { display: flex; flex-wrap: wrap; gap: 8px 16px; align-items: center; font-size: 13px; }\n                    .book-dark .book-label { color: var(--text-label); font-weight: 600; }\n                    .book-dark .book-value { color: var(--text-primary); }\n                    .book-dark .book-section { background-color: var(--bg-secondary); padding: 14px; border-radius: 8px; margin-bottom: 14px; }\n                    .book-dark .book-section-pink { border-left: 4px solid #ff6b9d; }\n                    .book-dark .book-section-yellow { border-left: 4px solid #ffca28; }\n                    .book-dark .book-section-purple { border-left: 4px solid #9d6fd6; }\n                    .book-dark .section-title { margin: 0 0 10px 0; font-size: 15px; font-weight: 600; color: var(--text-primary); }\n                    .book-dark .section-title-pink { color: #ff6b9d; }\n                    .book-dark .section-title-purple { color: #9d6fd6; }\n                    .book-dark .abstract-content { background-color: var(--bg-card); padding: 10px; border-radius: 6px; }\n                    .book-dark .abstract-text { margin: 0; color: var(--text-primary); font-size: 13px; line-height: 1.7; text-align: justify; text-indent: 2em; }\n                    .book-dark .info-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }\n                    .book-dark .info-grid-title { grid-column: 1 \/ -1; margin-bottom: 8px; }\n                    .book-dark .info-title { margin: 0 0 8px 0; color: var(--text-primary); font-size: 15px; font-weight: 600; padding-bottom: 6px; border-bottom: 1px solid var(--border-color); }\n                    .book-dark .info-card { background-color: var(--bg-card); padding: 10px; border-radius: 6px; }\n                    .book-dark .info-card-label { margin: 0 0 4px 0; color: var(--text-label); font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }\n                    .book-dark .info-card-value { margin: 0; font-size: 13px; line-height: 1.4; color: var(--text-primary); }\n                    .book-dark .info-card-value-blue { color: #4dabf7; font-weight: 600; }\n                    .book-dark .info-card-value-green { color: #3dd68c; }\n                    .book-dark .info-card-value-orange { color: #ffb74d; }\n                    .book-dark .info-card-value-text { font-size: 12px; word-break: break-word; }\n                    .book-dark .info-update { grid-column: 1 \/ -1; margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--border-color); }\n                    .book-dark .info-update-title { margin: 0 0 10px 0; color: var(--text-primary); font-size: 14px; font-weight: 600; }\n                    .book-dark .update-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }\n                    .book-dark .tts-section { background-color: var(--bg-secondary); padding: 14px; border-radius: 8px; margin-bottom: 16px; }\n                    .book-dark .tts-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; }\n                    .book-dark .tts-card { background-color: var(--bg-card); padding: 10px; border-radius: 6px; }\n                    .book-dark .tts-label { margin: 0 0 4px 0; color: var(--text-label); font-size: 12px; }\n                    .book-dark .tts-value { margin: 0; color: var(--text-primary); font-size: 13px; font-weight: 600; }\n                    .book-dark .tts-tip { background-color: rgba(0, 0, 0, 0.85); border: 1px solid #ffc107; padding: 12px; border-radius: 6px; }\n                    .book-dark .tts-tip-text { margin: 0; color: #ffffff; font-size: 14px; font-weight: 600; line-height: 1.8; }\n                    .book-dark .text-login-success { color: #3dd68c; font-weight: 600; }\n                    .book-dark .text-login-fail { color: #ff6b6b; font-weight: 600; }\n                <\/style>\n                <div class=\"book-qingtian-mr-theme book-qingtian-theme\">\n                    <div class=\"book-header\">\n                        <h2 class=\"book-title\">书籍信息<\/h2>\n                    <\/div>\n                    <div class=\"book-info-box\">\n                        <div class=\"book-info-flex\">\n                            <span class=\"book-label\">当前服务器：<\/span>\n                            <span class=\"book-value\">${await BaseUrl()}<\/span>\n                            <br>\n                            <span class=\"book-label\" style=\"margin-left: 8px;\">用户信息：<\/span>\n                            <span class=\"book-value\">\n                                ${(qttoken !== \"\") ?\n            (user_info.nickname ?\n                    `<span class=\"text-login-success\">${user_info.nickname}<\/span> (已登录)` : (user_info.email ?\n                            `<span class=\"book-value\">${user_info.email}<\/span> (已登录，无昵称)` : `<span class=\"book-value\">${user_info.msg}<\/span>`\n                    )\n\n            ) :\n            '<span class=\"text-login-fail\">未登录<\/span>'\n        }\n                            <\/span>\n                            <br>\n                            <span class=\"book-label\" style=\"margin-left: 8px;\">数据来源：<\/span>\n                            <span class=\"book-value\">${book_info.source}<\/span>\n                            <br>\n                            <span class=\"book-label\" style=\"margin-left: 8px;\">书籍类型：<\/span>\n                            <span class=\"book-value\">${book_info.tab}<\/span>\n                        <\/div>\n                    <\/div>\n                    ${book_info.abstract ? `\n                    <div class=\"book-section book-section-pink\">\n                        <h3 class=\"section-title section-title-pink\">书籍简介<\/h3>\n                        <div class=\"abstract-content\">\n                            <p class=\"abstract-text\">${book_info.abstract.replace(\/\\n\/g, '<br>')}<\/p >\n                        <\/div>\n                    <\/div>\n                    ` : ''}\n\n\n                    <div class=\"book-section book-section-yellow\">\n                        <div class=\"info-grid\">\n                            <div class=\"info-grid-title\">\n                                <h3 class=\"info-title\">书籍基本信息<\/h3>\n                            <\/div>\n\n                            <div class=\"info-card\">\n                                <p class=\"info-card-label\">书籍名称<\/p >\n                                <p class=\"info-card-value info-card-value-blue\" style=\"word-break: break-word;\">${book_info.book_name}<\/p >\n                            <\/div>\n\n                            <div class=\"info-card\">\n                                <p class=\"info-card-label\">作者<\/p >\n                                <p class=\"info-card-value info-card-value-text\">${book_info.author}<\/p >\n                            <\/div>\n\n                            ${book_info.status ? `\n                            <div class=\"info-card\">\n                                <p class=\"info-card-label\">状态<\/p >\n                                <p class=\"info-card-value ${book_info.status.includes('连载') ? 'info-card-value-green' : ''}\">${book_info.status}<\/p >\n                            <\/div>\n                            ` : ''}\n\n                            ${book_info.score ? `\n                            <div class=\"info-card\">\n                                <p class=\"info-card-label\">评分<\/p >\n                                <p class=\"info-card-value info-card-value-orange\">${book_info.score}<\/p >\n                            <\/div>\n                            ` : ''}\n\n                            ${book_info.tag ? `\n                            <div class=\"info-card\">\n                                <p class=\"info-card-label\">标签<\/p >\n                                <p class=\"info-card-value info-card-value-text\">${book_info.tag}<\/p >\n                            <\/div>\n                            ` : ''}\n\n                            ${book_info.role ? `\n                            <div class=\"info-card\">\n                                <p class=\"info-card-label\">主角<\/p >\n                                <p class=\"info-card-value info-card-value-text\">${book_info.role}<\/p >\n                            <\/div>\n                            ` : ''}\n\n                            <div class=\"info-update\">\n                                <h4 class=\"info-update-title\">更新信息<\/h4>\n                                <div class=\"update-grid\">\n                                    ${book_info.latest_chapter_title ? `\n                                    <div class=\"info-card\">\n                                        <p class=\"info-card-label\">最新章节<\/p >\n                                        <p class=\"info-card-value info-card-value-text\">${book_info.latest_chapter_title}<\/p >\n                                    <\/div>\n                                    ` : ''}\n\n                                    ${book_info.last_chapter_update_time ? `\n                                    <div class=\"info-card\">\n                                        <p class=\"info-card-label\">更新时间<\/p >\n                                        <p class=\"info-card-value info-card-value-text\">${book_info.last_chapter_update_time}<\/p >\n                                    <\/div>\n                                    ` : ''}\n\n                                    ${book_info.word_number ? `\n                                    <div class=\"info-card\">\n                                        <p class=\"info-card-label\">书籍字数<\/p >\n                                        <p class=\"info-card-value info-card-value-text\">${book_info.word_number}<\/p >\n                                    <\/div>\n                                    ` : ''}\n                                <\/div>\n                            <\/div>\n                        <\/div>\n                    <\/div>\n\n                    ${book_info.tab === \"听书\" && book_info.book_tts ? `\n                    <div class=\"tts-section book-section-purple\">\n                        <h3 class=\"section-title section-title-purple\">听书设置<\/h3>\n                        <div class=\"tts-grid\">\n                            <div class=\"tts-card\">\n                                <p class=\"tts-label\">当前音色<\/p >\n                                <p class=\"tts-value\">${tone_id || \"4\"}<\/p >\n                            <\/div>\n                            ${book_info.book_tts ? `\n                            <div class=\"tts-tip\">\n                                <p class=\"tts-tip-text\"><strong>使用提示：<\/strong>AI音色请将音色值填入书籍变量然后刷新页面，真人音色请重新搜索选择对应主播的书籍<\/p >\n                            <\/div>\n                            <div class=\"tts-card\">\n                                <p class=\"tts-label\">可选音色<\/p >\n                                <p class=\"tts-value\">${book_info.book_tts.replace(\"\\n💠相关注意事项：\\n↓↓↓↓↓↓↓↓↓↓\\n\\n🎤本书可选音色：\\n\", \"\").replace(\/\\n\/g, '<br>')}<\/p >\n                            <\/div>\n\n                            ` : ''}\n                        <\/div>\n                    <\/div>\n                    ` : ''}\n                <\/div>`;\n        }\n        var version = await flutterBridge.getbuildNumber();\n        if (version >= 97) {\n            abstract = abstract.replace(\/qingtian-theme\/g, '$theme');\n        }\n        let type = 0;\n        if (book_info.tab === \"听书\") {\n            type = 1;\n        } else if (book_info.tab === \"小说\") {\n            type = 0;\n        } else if (book_info.tab === \"漫画\") {\n            type = 2;\n        }\n        var book = {\n            \"bookUrl\": bookurl,\n            \"name\": book_info.book_name,\n            \"author\": book_info.author,\n            \"kind\": `${book_info.status},${book_info.score},${book_info.tags},${book_info.last_chapter_update_time}`,\n            \"coverUrl\": book_info.thumb_url,\n            \"intro\": abstract,\n            \"tocUrl\": JSON.stringify({\n                book_id: book_info.book_id,\n                source: book_info.source,\n                tab: book_info.tab\n            }),\n            \"wordCount\": book_info.word_number,\n            \"type\": type,\n            \"latestChapterTitle\": book_info.latest_chapter_title || \"\",\n        }\n        return JSON.stringify(book);\n    }\n\n    async function chapter(tocUrl, bookurl) {\n        tocUrl = JSON.parse(tocUrl);\n        var variable = await cache.get(\"variable\") || \"\";\n        let moreSettings = await getVariable('更多设置');\n        let is_last_source = moreSettings && moreSettings['目录显示来源'] || 'false';\n        variable = variable.trim();\n        if (variable !== \"\") {\n            variable = JSON.stringify({\n                'custom': variable\n            });\n        }\n        let catalogUrl = `\/catalog?book_id=${tocUrl.book_id}&source=${tocUrl.source}&tab=${tocUrl.tab}&variable=${variable}`;\n        flutterBridge.log(catalogUrl);\n        let data = await request(catalogUrl, \"POST\");\n        let catalog_data = data.data;\n        let chapters = [];\n\n        for (var i = 0; i < catalog_data.length; i++) {\n            let chapterName = catalog_data[i].title;\n            if (is_last_source === 'true') {\n                if (i === catalog_data.length - 1) {\n                    chapterName = `${catalog_data[i].source}:${chapterName}`;\n                }\n            }\n            chapters.push({\n                \"name\": chapterName,\n                \"chapterId\": JSON.stringify({\n                    item_id: catalog_data[i].item_id,\n                    source: tocUrl.source,\n                    tab: tocUrl.tab,\n                    book_id: tocUrl.book_id,\n                    index: i,\n                    title: catalog_data[i].title,\n                }),\n                \"index\": i,\n                \"isPay\": false,\n                \"isVip\": false,\n                \"isVolume\": false,\n                \"tag\": catalog_data[i].first_pass_time || \"\"\n            });\n        }\n        return JSON.stringify(chapters);\n    }\n\n    async function playButton(url) {\n        let playSvg = `<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\n     width=\"768\"\n     height=\"256\"\n     viewBox=\"0 0 768 256\"\n     fill=\"none\">\n\n  <defs>\n    <linearGradient id=\"bg\" x1=\"40\" y1=\"32\" x2=\"220\" y2=\"224\" gradientUnits=\"userSpaceOnUse\">\n      <stop stop-color=\"#6EE7B7\"\/>\n      <stop offset=\"1\" stop-color=\"#10B981\"\/>\n    <\/linearGradient>\n\n    <linearGradient id=\"glass\" x1=\"64\" y1=\"40\" x2=\"192\" y2=\"200\" gradientUnits=\"userSpaceOnUse\">\n      <stop stop-color=\"white\" stop-opacity=\"0.55\"\/>\n      <stop offset=\"1\" stop-color=\"white\" stop-opacity=\"0\"\/>\n    <\/linearGradient>\n\n    <linearGradient id=\"textGrad\" x1=\"0\" y1=\"0\" x2=\"200\" y2=\"0\" gradientUnits=\"userSpaceOnUse\">\n      <stop stop-color=\"#34D399\"\/>\n      <stop offset=\"1\" stop-color=\"#059669\"\/>\n    <\/linearGradient>\n\n    <filter id=\"shadow\" x=\"-50%\" y=\"-50%\" width=\"200%\" height=\"200%\">\n      <feDropShadow dx=\"0\" dy=\"10\" stdDeviation=\"12\" flood-opacity=\"0.18\"\/>\n    <\/filter>\n\n    <filter id=\"glow\" x=\"-50%\" y=\"-50%\" width=\"200%\" height=\"200%\">\n      <feGaussianBlur stdDeviation=\"6\" result=\"blur\"\/>\n      <feMerge>\n        <feMergeNode in=\"blur\"\/>\n        <feMergeNode in=\"SourceGraphic\"\/>\n      <\/feMerge>\n    <\/filter>\n  <\/defs>\n\n  <g transform=\"translate(256,10) scale(0.85)\">\n\n    <ellipse cx=\"128\" cy=\"214\" rx=\"62\" ry=\"14\" fill=\"#000\" opacity=\"0.08\"\/>\n\n    <g filter=\"url(#shadow)\">\n      <circle cx=\"128\" cy=\"120\" r=\"84\" fill=\"url(#bg)\"\/>\n    <\/g>\n\n    <circle cx=\"128\" cy=\"120\" r=\"70\"\n            stroke=\"white\"\n            stroke-opacity=\"0.25\"\n            stroke-width=\"2\"\/>\n\n    <g filter=\"url(#glow)\">\n      <path d=\"M110 88\n               C110 82 116 78 121 81\n               L168 110\n               C173 113 173 121 168 124\n               L121 153\n               C116 156 110 152 110 146\n               Z\"\n            fill=\"white\"\/>\n    <\/g>\n\n    <g transform=\"translate(128 220)\">\n      <text x=\"0\"\n            y=\"30\"\n            text-anchor=\"middle\"\n            font-size=\"18\"\n            font-weight=\"700\"\n            fill=\"url(#textGrad)\"\n            font-family=\"PingFang SC, Microsoft YaHei, sans-serif\"\n            letter-spacing=\"1\">\n        点击开始播放\n      <\/text>\n\n      <circle cx=\"-90\" cy=\"23\" r=\"3\" fill=\"#34D399\" opacity=\"0.7\"\/>\n      <circle cx=\"90\" cy=\"23\" r=\"3\" fill=\"#10B981\" opacity=\"0.7\"\/>\n    <\/g>\n\n  <\/g>\n<\/svg>`\n        let js = `{\"js\":\"showDj('${url}')\"}`;\n        return `<img src=\"data:image\/svg+xml;base64,${await flutterBridge.base64encode(playSvg)},${js}\"\/>`;\n    }\n\n    function showDj(url) {\n        flutterBridge.startBrowser(url);\n    }\n\n    function removeAllImgTags(htmlString) {\n        const imgTagRegex = \/<img\\b[^>]*>|<\\\/img>|<img\\b[^>]*\\\/>\/gi;\n        return htmlString.replace(imgTagRegex, '');\n    }\n\n\n    async function paraForQyd(content, book_id, sources) {\n        let server = await BaseUrl();\n        const regex = \/<p>(.*?)(?:<comment ident=\"([^\"]*)\" count=\"([^\"]*)\" \\\/>)?<\\\/p>\/g;\n        const matches = Array.from(content.matchAll(regex));\n\n        const replacements = await Promise.all(\n            matches.map(async (match) => {\n                if (match[2] && match[3]) {\n                    let lurl = match[2];\n                    if (!lurl.includes(book_id)) {\n                        lurl = lurl.replace(\"book_id=&\", \"book_id=\" + book_id + \"&\");\n                    }\n                    var dpnumber = match[3] > 99 ? \"99+\" : match[3].toString();\n                    const svgUrl = `<img src=\"dp:${dpnumber},{\"js\":\"showCmt('${server + lurl}', '${sources}')\"}\"\/>`\n                    return {search: match[0], replace: `${match[1]}${svgUrl}`};\n\n                }\n                return {search: match[0], replace: match[1]};\n            })\n        );\n\n        replacements.forEach(({search, replace}) => {\n            content = content.replace(search, replace);\n        });\n\n        return content;\n    }\n\n    function showCmt(url, sources, ident = \"段评\") {\n        flutterBridge.startBrowserDp(url, sources + ident);\n    }\n\n\n    async function syncBookShelf(book, data) {\n        if (!book) {\n            return;\n        }\n        \/\/ flutterBridge.showToast(JSON.stringify(book));\n        let book_order = book.order;\n        if (book_order != 0) {\n            book_order = 1;\n        } else {\n            book_order = 2;\n        }\n\n\n        try {\n\n            let book_info = {\n                book_name: book.name,\n                author: book.author,\n                \/\/abstract:book.abstract,\n                thumb_url: book.coverUrl,\n                book_id: data.book_id,\n                tab: data.tab,\n                source: data.source\n            };\n            if (!book_info || typeof book_info != \"object\") {\n                return;\n            }\n            const rurl = `\/add_book_to_book_shelf`;\n            book_info.read_status = book_order;\n            book_info.last_chapter_item_id = data.item_id;\n            book_info.last_chapter_title = data.title || \"\";\n            \/\/java.toast(JSON.stringify(book_info));\n            let check_data = null;\n            try {\n                const check_book_url = `\/check_book_in_book_shelf`;\n                check_data = await request(check_book_url, \"POST\", book_info);\n            } catch (e) {\n                flutterBridge.log(`检查书籍请求失败: ${e}`);\n            }\n\n            try {\n                if (check_data.data.id) {\n                    book_info.id = check_data.data.id;\n                    const uurl = `\/update_book_shelf`;\n                    let up = await request(uurl, \"POST\", book_info);\n                    flutterBridge.showToast(JSON.stringify(up));\n                } else {\n                    let up = await request(rurl, \"POST\", book_info);\n                    flutterBridge.showToast(JSON.stringify(up));\n                }\n            } catch (e) {\n                try {\n                    let up = await request(rurl, \"POST\", book_info);\n                    flutterBridge.showToast(JSON.stringify(up));\n                } catch (e) {\n                    flutterBridge.showToast(`书架操作失败: ${e}`);\n                }\n\n            }\n        } catch (error) {\n            flutterBridge.log(`书籍同步流程异常: ${error}`);\n            flutterBridge.showToast(\"同步阅读进度失败，但不影响阅读，可以前往登录关闭书架同步功能。\");\n        }\n    }\n\n\n    async function content(url, bookurl) {\n        url = JSON.parse(url);\n        var tone_id = await cache.get(\"tone_id\") || \"4\";\n        tone_id = tone_id.trim();\n        if (url.tab === \"听书\" && (url.source !== \"番茄\" || url.source !== \"七猫\")) {\n            flutterBridge.showToast(`当前播放音色：${tone_id}`);\n        }\n        var moreSettings = await getVariable(\"更多设置\") || {};\n        var show_img = moreSettings && moreSettings['显示图片'] || 'true';\n        var review = moreSettings && moreSettings[\"段评开关\"] || \"true\";\n        var reading = moreSettings && moreSettings['同步书架'] || 'false';\n        let book = await flutterBridge.getReadBook();\n        \/\/ flutterBridge.log(\"111111111111111111\")\n        \/\/ flutterBridge.showToast(await flutterBridge.getdurChapterIndex());\n        \/\/ flutterBridge.showToast((await flutterBridge.getdurChapterIndex()) == url.index);\n        \/\/ flutterBridge.showToast(`当前章节：${url.index},当前阅读：${await flutterBridge.getdurChapterIndex(bookurl)}`);\n        if ((await flutterBridge.getdurChapterIndex(bookurl)) == url.index && reading === \"true\") {\n            try {\n                await syncBookShelf(book, url);\n            } catch (error) {\n                flutterBridge.showToast(`同步阅读进度失败: ${error}`);\n                \/\/ flutterBridge.showToast(\"同步阅读进度失败，但不影响阅读，可以前往登录关闭书架同步功能。\");\n            }\n        }\n        if (review === \"false\") {\n            review = \"0\"\n        } else {\n            review = \"1\"\n        }\n        url[\"version\"] = localVersion;\n        url[\"tone_id\"] = tone_id;\n        let content_data = await request(`\/content?review=${review}`, \"POST\", url);\n        let result = content_data.content;\n        if (content_data.msg) {\n            flutterBridge.showToast(content_data.msg);\n        }\n        if (url.tab === \"小说\") {\n            result = result.replace(\/<img[^>]*>\/g, (match) => {\n                const srcMatch = match.match(\/\\s+src=\"([^\"]+)\"\/);\n                if (srcMatch) {\n                    const encodedSrc = srcMatch[1].replace(\/&amp;\/g, '&');\n                    return `<img src=\"${encodedSrc}\" \/>`;\n                }\n                return match;\n            });\n            if (show_img === \"false\") {\n                result = removeAllImgTags(result);\n            }\n            if (review === \"1\") {\n                result = await paraForQyd(result, url.book_id, url.source);\n            }\n\n\n        }\n        if (url.tab === \"短剧\") {\n            var play_url;\n            if (url.source !== \"毒舌影视\" && url.source !== \"NT动漫\") {\n                play_url = `${await BaseUrl()}\/online_video?book_id=${url.book_id}&source=${url.source}&tab=${url.tab}`;\n            } else {\n                play_url = result;\n            }\n            if (result && content_data.msg === \"\") {\n                result = `${await playButton(play_url)}`\n            } else {\n                if (content_data.msg) {\n                    result = content_data.msg;\n                } else {\n                    result = `播放地址为空，请联系管理员处理：\\n平台：${url.source}\\n类型：${url.tab}\\n书籍id:${url.book_id}\\n章节id：${url.item_id}`;\n                }\n\n            }\n\n        }\n        return result;\n    }\n\n    function startUrl(url, title, headers) {\n        flutterBridge.startBrowserWithShouldOverrideUrlLoading(url, title, headers);\n    }\n\n    function createFilter(chars, defaultVal, title, size) {\n        return {\n            title: title,\n            type: 2,\n            chars: chars,\n            default: defaultVal,\n            action: `show('${title}')`,\n            width: size\n        };\n    }\n\n    async function show(k) {\n        let v = await cache.get(k);\n        flutterBridge.showLongToast(v)\n\n        if (k === \"类型\") {\n            await setVariable(\"平台\", \"番茄\")\n        }\n        await setVariable(k, v);\n        await flutterBridge.refreshExplore();\n    }\n\n    let infoData = [];\n    let groupDatas = [];\n    let style_list_map = {};\n    async function getFqBookShelf(sources) {\n        let fqssionid = await getFqToken();\n        if (!fqssionid && sources === '番茄') {\n            await flutterBridge.showToast('您还未登陆番茄账号，无法同步数据哦！');\n        }\n        var fqsjurl = \"\/bookshelf?page={{page}}&ssionid=\" + fqssionid;\n        var fqtjurl = \"\/fqrecommend?page={{page}}&ssionid=\" + fqssionid;\n        var fqlsurl = \"\/fqhistory?page={{page}}&ssionid=\" + fqssionid;\n        var hasValidCookie = !!fqssionid;\n        if (sources !== '番茄') {\n            return;\n        }\n        if (!hasValidCookie) {\n            infoData = [{\n                \"title\": \"登录番茄\",\n                \"js\": `startUrl('https:\/\/fanqienovel.com','登录番茄',{\\\"User-Agent\\\":\\\"Mozilla\/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit\/605.1.15 (KHTML, like Gecko) Version\/16.6 Mobile\/15E148 Safari\/604.1 Edg\/138.0.0.0\\\"})`,\n                \"type\": 1,\n                \"width\": 1\n            }];\n            return;\n        }\n\n        if (hasValidCookie && sources === '番茄' && groupDatas.length < 1) {\n            async function groupQuery() {\n                let groupDatas1 = [];\n                try {\n                    var url = \"\/group_name?ssionid=\" + fqssionid;\n                    var response = await request(url);\n                    if (!(response && response.data)) {\n                    }\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                                groupDatas1.push({\n                                    title: key,\n                                    url: \"\/bookshelf##\" + JSON.stringify(option),\n\n                                });\n                            }\n                        }\n                    });\n                groupDatas = groupDatas1;\n                } catch (e) {\n                    await flutterBridge.showToast(\"番茄登录过期，已隐藏番茄书架\" + fqssionid);\n                }\n                \n            }\n\n            try {\n                await flutterBridge.showToast(\"正在加载番茄分组数据...\");\n                var userUrl = \"\/fquser?ssionid=\" + fqssionid;\n                var userData = await request(userUrl);\n                var userName = (userData && userData.data && userData.data.name) ? userData.data.name : '未知用户';\n                if (!userName.includes('未知用户')) {\n                    infoData = [\n                        {\n                            title: '番茄书架',\n                            url: fqsjurl,\n                            width: 1\n                        },\n                        {\n                            title: \"个性推荐\",\n                            url: fqtjurl\n                        }, {\n                            title: \"历史阅读\",\n                            url: fqlsurl\n                        }];\n                }\n                await groupQuery();\n                if (groupDatas.length > 0) {\n                    await flutterBridge.refreshExplore();\n                }\n                \n            } catch (e) {\n                flutterBridge.showToast(\"番茄登录过期，已隐藏番茄书架\");\n            }\n        }\n    }\n\n\n    async function getfinds() {\n      try {\n        let moreSettings = await getVariable('更多设置');\n        var source_type = await getVariable('频道') || '男频';\n        let tab = await getVariable(\"类型\") || \"小说\";\n        let sources = await getVariable(\"平台\") || moreSettings && moreSettings[tab] || '番茄';\n\n        let js;\n\n        js = await getVariable('云端配置');\n        if (!!!js) {\n            await getCloudSettings(true);\n            js = await getVariable('云端配置');\n        }\n\n        getFqBookShelf(sources);\n\n        let source_list = [];\n        try {\n            source_list = js[tab];\n        } catch (e) {\n            await flutterBridge.showToast(e);\n        }\n\n        if (!style_list_map[tab]) style_list_map[tab] = {};\n        if (!style_list_map[tab][source_type]) style_list_map[tab][source_type] = {};\n        var style_list = style_list_map[tab][source_type][sources] || [];\n        \/\/ flutterBridge.showLongToast(sources)\n\n        if (style_list.length < 3) {\n          \/\/ flutterBridge.showToast(\"正在加载发现列表...\");\n          try {\n              var durl = `\/discovestyle?source=${sources}&source_type=${source_type}&tab=${tab}`;\n              var result = await request(durl);\n              let baseUrl = await BaseUrl();\n              style_list = result.data || [];\n              style_list = style_list.map(item => ({\n                  ...item,\n                  title: (item.title || \"\").replace(\"☆.*・°\", \"\").replace(\"°・*.☆\", \"\"),\n                  url: item.url.replace(baseUrl, \"\") || \"\"\n              }));\n              if (style_list.length > 3) {\n                  if (!style_list_map[tab]) style_list_map[tab] = {};\n                  if (!style_list_map[tab][source_type]) style_list_map[tab][source_type] = {};\n                  style_list_map[tab][source_type][sources] = style_list;\n              }\n              if (result.msg) {\n                  await flutterBridge.showToast(result.msg);\n              }\n          } catch (e) {\n              await flutterBridge.showToast(\"发现样式获取失败\"+e);\n          }\n        }\n        let qtsjurl = '\/get_book_shelf';\n\n        let qtsj = [];\n\n        try {\n            let hostsbk = (await getVariable('云端配置'))['hosts'] || hosts;\n            qtsj.push(createFilter(\n                hostsbk,\n                await BaseUrl(),\n                \"线路\",\n                0\n            ));\n            qtsj.push(createFilter(\n                [\"小说\", \"听书\", \"短剧\", \"漫画\"],\n                tab,\n                \"类型\",\n                0\n            ));\n            qtsj.push(createFilter(\n                [\"男频\", \"女频\"],\n                source_type,\n                \"频道\",\n                0\n            ));\n            qtsj.push(createFilter(\n                source_list,\n                sources,\n                \"平台\",\n                0\n            ));\n\n            qtsj.push({\n                \"title\": \"更新配置\",\n                \"type\": 4,\n                \"action\": \"getCloudSettings(true)\",\n                \"width\": 0\n            })\n            qtsj.push({\n                \"title\": \"书源设置\",\n                \"type\": 4,\n                \"action\": \"getHtmlSettings()\",\n                \"width\": 0\n            })\n            const excludeTitles = ['点击登录可切换来源', '切换后长按刷新即可'];\n            style_list = style_list.filter(item => !excludeTitles.includes(item.title));\n        } catch {\n        }\n\n        let qttoken = await getToken();\n        if (qttoken.length > 10) {\n            qtsj.push({\n                title: \"晴天书架\",\n                url: qtsjurl,\n                width: 1\n            })\n        } else {\n            qtsj.push({\n                \"title\": \"登录晴天\",\n                \"js\": `startUrl('${await BaseUrl()}\/login','登录晴天',{\\\"User-Agent\\\":\\\"Mozilla\/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit\/605.1.15 (KHTML, like Gecko) Version\/16.6 Mobile\/15E148 Safari\/604.1 Edg\/138.0.0.0\\\"})`,\n                \"type\": 1,\n                \"width\": 1\n            })\n        }\n        var shgc = [\n            {\n                \"title\": \"书荒广场\",\n                \"url\": \"\",\n                \"width\": 3\n            }, {\n                \"title\": \"晴天\",\n                \"url\": `${await BaseUrl()}\/online_search`,\n                \/\/ \"js\": `startUrl('${await BaseUrl()}\/online_search','晴天',{\\\"User-Agent\\\":\\\"Mozilla\/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit\/605.1.15 (KHTML, like Gecko) Version\/16.6 Mobile\/15E148 Safari\/604.1 Edg\/138.0.0.0\\\"})`,\n                \"type\": 1,\n                \"useShouldOverrideUrlLoading\": true\n            }, {\n                \"title\": \"书旗\",\n                \"url\": \"https:\/\/t.shuqi.com\/v2\/label\",\n                \/\/ \"js\": \"startUrl('https:\/\/t.shuqi.com\/v2\/label','书旗',{\\\"User-Agent\\\":\\\"Mozilla\/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit\/605.1.15 (KHTML, like Gecko) Version\/16.6 Mobile\/15E148 Safari\/604.1 Edg\/138.0.0.0\\\"})\",\n                \"type\": 1,\n                \"useShouldOverrideUrlLoading\": true\n            }, {\n                \"title\": \"塔读\",\n                \"url\": \"https:\/\/m.tadu.com\/\",\n                \/\/ \"js\": \"startUrl('https:\/\/m.tadu.com\/','塔读',{\\\"User-Agent\\\":\\\"Mozilla\/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit\/605.1.15 (KHTML, like Gecko) Version\/16.6 Mobile\/15E148 Safari\/604.1 Edg\/138.0.0.0\\\"})\",\n                \"type\": 1,\n                \"useShouldOverrideUrlLoading\": true\n            }, {\n                \"title\": \"QQ阅读\",\n                \"url\": \"https:\/\/ubook.reader.qq.com\/\",\n                \/\/ \"js\": \"startUrl('https:\/\/ubook.reader.qq.com\/','QQ阅读',{\\\"User-Agent\\\":\\\"Mozilla\/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit\/605.1.15 (KHTML, like Gecko) Version\/16.6 Mobile\/15E148 Safari\/604.1 Edg\/138.0.0.0\\\"})\",\n                \"type\": 1,\n                \"useShouldOverrideUrlLoading\": true\n            }, {\n                \"title\": \"七猫\",\n                \"url\": \"https:\/\/www.qimao.com\/\",\n                \/\/ \"js\": \"startUrl('https:\/\/www.qimao.com\/','七猫',{\\\"User-Agent\\\":\\\"Mozilla\/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit\/605.1.15 (KHTML, like Gecko) Version\/16.6 Mobile\/15E148 Safari\/604.1 Edg\/138.0.0.0\\\"})\",\n                \"type\": 1,\n                \"useShouldOverrideUrlLoading\": true\n            }, {\n                \"title\": \"淘小说\",\n                \"url\": \"http:\/\/betam.taoyuewenhua.com\/\",\n                \/\/ \"js\": \"startUrl('http:\/\/betam.taoyuewenhua.com\/','淘小说',{\\\"User-Agent\\\":\\\"Mozilla\/5.0 (iPhone; CPU iPhone OS 16_6 like Mac OS X) AppleWebKit\/605.1.15 (KHTML, like Gecko) Version\/16.6 Mobile\/15E148 Safari\/604.1 Edg\/138.0.0.0\\\"})\",\n                \"type\": 1,\n                \"useShouldOverrideUrlLoading\": true\n            }\n        ];\n        if (sources === '番茄') {\n          var finalData = infoData.concat(groupDatas, shgc, style_list);\n        } else {\n          var infoDatas = [];\n          var finalData = infoDatas.concat(shgc, style_list);\n        }\n        finalData = qtsj.concat(finalData);\n        return JSON.stringify(finalData)\n        } catch (e) {\n            await flutterBridge.showToast(\"发现样式获取失败\"+e);\n        }\n    }\n\n\n\n\n    async function find(url, page) {\n        var books = [];\n        url = url.replace(\"{{page}}\", page);\n        let method = \"GET\";\n        let body = {};\n        try {\n            let option = url.split(\"##\")[1];\n            url = url.split(\"##\")[0];\n            option = JSON.parse(option);\n            method = option.method || \"GET\";\n            body = option.body || {};\n        } catch (e) {\n        }\n        var books_data = await request(url, method, body)\n        books_data = books_data.data;\n        for (var i = 0; i < books_data.length; i++) {\n\n            var book = books_data[i];\n            let type = 0;\n            if (book.tab === \"听书\") {\n                type = 1;\n            } else if (book.tab === \"小说\") {\n                type = 0;\n            } else if (book.tab === \"漫画\") {\n                type = 2;\n            }\n            books.push({\n                \"bookUrl\": `\/detail?book_id=${book.book_id}&source=${book.source}&tab=${book.tab}`,\n                \"name\": book.book_name,\n                \"author\": book.author,\n                \"kind\": [\n                    book.status,\n                    book.score,\n                    book.tags,\n                    typeof book.last_chapter_update_time === \"number\" && book.last_chapter_update_time > 946656000 && book.last_chapter_update_time < 4102444800\n                        ? new Date(book.last_chapter_update_time * 1000).toLocaleString(\"zh-CN\")\n                        : book.last_chapter_update_time\n                ].filter(v => v !== undefined && v !== null && v !== \"\").join(\",\"),\n                \"coverUrl\": book.thumb_url,\n                \"intro\": book.abstract,\n                \"tocUrl\": `\/detail?book_id=${book.book_id}&source=${book.source}&tab=${book.tab}`,\n                \"wordCount\": book.word_number,\n                \"type\": type,\n                \"latestChapterTitle\": `${book.source}:${book.latest_chapter_title || \"\"}`,\n            })\n        }\n\n        return JSON.stringify(books);\n    }\n\n\n    \/\/ 线路设置\n    async function getServerSettings() {\n        let data = await getVariable('线路');\n        let config = await getVariable('云端配置');\n        if (!config) {\n            try {\n                await getCloudSettings(true);\n                config = await getVariable('云端配置');\n            } catch {\n            }\n        }\n        let hostsbk = hosts;\n        try {\n            hostsbk = config['hosts'] || hosts;\n        } catch (e) {\n        }\n\n        let html = `\n        <!DOCTYPE html>\n        <html lang=\"zh-CN\">\n        <head>\n        <meta charset=\"UTF-8\">\n        <meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">\n        <title>光遇小说 - 线路设置<\/title>\n        <link rel=\"stylesheet\" href=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/font-awesome\/6.4.0\/css\/all.min.css\">\n        <style>\n        * {\n          margin: 0;\n          padding: 0;\n          box-sizing: border-box;\n          -webkit-tap-highlight-color: transparent;\n        }\n\n        :root {\n          --color-primary: #2D9D78;\n          --color-primary-light: #3DB893;\n          --bg-dark: #f5f7fa;\n          --bg-darker: #ffffff;\n          --bg-card: rgba(255, 255, 255, 0.95);\n          --bg-elevated: #f8fafb;\n          --text-primary: #1a1d23;\n          --text-secondary: #6b7280;\n          --border-color: rgba(0, 0, 0, 0.08);\n        }\n\n        [data-theme=\"dark\"] {\n          --bg-dark: #0A1628;\n          --bg-darker: #050B14;\n          --bg-card: #142235;\n          --bg-elevated: #1A2B42;\n          --text-primary: #FFFFFF;\n          --text-secondary: rgba(255, 255, 255, 0.7);\n          --border-color: rgba(255, 255, 255, 0.1);\n        }\n\n        html, body {\n          width: 100%;\n          min-height: 100vh;\n          font-family: -apple-system, BlinkMacSystemFont, sans-serif;\n          font-size: 16px;\n          color: var(--text-primary);\n          background: var(--bg-darker);\n          overflow-x: hidden;\n        }\n\n        .container {\n          padding: 16px;\n          max-width: 600px;\n          margin: 0 auto;\n        }\n\n        .main-card {\n          background: var(--bg-card);\n          border-radius: 20px;\n          box-shadow: 0 4px 24px rgba(0, 0, 0, 0.1);\n          border: 1px solid var(--border-color);\n          overflow: hidden;\n        }\n\n        .header {\n          padding: 20px 16px;\n          background: linear-gradient(135deg, var(--color-primary), var(--color-primary-light));\n          text-align: center;\n        }\n\n        .header-title {\n          font-size: 18px;\n          font-weight: 700;\n          color: white;\n          margin-bottom: 4px;\n        }\n\n        .header-desc {\n          font-size: 12px;\n          color: rgba(255, 255, 255, 0.8);\n        }\n\n        .last-check {\n          display: flex;\n          align-items: center;\n          gap: 8px;\n          font-size: 12px;\n          color: var(--text-secondary);\n          padding: 12px 16px;\n          border-bottom: 1px solid var(--border-color);\n        }\n\n        .last-check i {\n          color: var(--color-primary);\n        }\n\n        .server-list {\n          padding: 8px;\n        }\n\n        .server-card {\n          background: var(--bg-elevated);\n          border-radius: 12px;\n          padding: 14px 16px;\n          margin-bottom: 8px;\n          display: flex;\n          align-items: center;\n          gap: 12px;\n          cursor: pointer;\n          transition: all 0.2s ease;\n          border: 2px solid transparent;\n        }\n\n        .server-card:hover {\n          border-color: var(--color-primary);\n          background: rgba(45, 157, 120, 0.05);\n        }\n\n        .server-card.selected {\n          border-color: var(--color-primary);\n          background: rgba(45, 157, 120, 0.1);\n        }\n\n        .server-icon {\n          width: 36px;\n          height: 36px;\n          border-radius: 10px;\n          background: linear-gradient(135deg, rgba(45, 157, 120, 0.15), rgba(61, 184, 147, 0.1));\n          display: flex;\n          align-items: center;\n          justify-content: center;\n          font-size: 16px;\n          color: var(--color-primary);\n          flex-shrink: 0;\n        }\n\n        .server-card.selected .server-icon {\n          background: linear-gradient(135deg, var(--color-primary), var(--color-primary-light));\n          color: white;\n        }\n\n        .server-info {\n          flex: 1;\n          min-width: 0;\n        }\n\n        .server-url {\n          font-size: 14px;\n          font-weight: 700;\n          color: var(--text-primary);\n          word-break: break-all;\n          line-height: 1.4;\n        }\n\n        .server-details {\n          font-size: 11px;\n          color: var(--text-secondary);\n          margin-top: 2px;\n        }\n\n        .server-status {\n          padding: 4px 12px;\n          border-radius: 16px;\n          font-size: 11px;\n          font-weight: 700;\n          white-space: nowrap;\n          flex-shrink: 0;\n        }\n\n        .server-status.checking {\n          background: rgba(255, 193, 7, 0.2);\n          color: #f57f17;\n        }\n\n        .server-status.online {\n          background: rgba(76, 175, 80, 0.2);\n          color: #2e7d32;\n        }\n\n        .server-status.slow {\n          background: rgba(255, 193, 7, 0.2);\n          color: #f57f17;\n        }\n\n        .server-status.offline {\n          background: rgba(244, 67, 54, 0.2);\n          color: #c62828;\n        }\n\n        .server-status.not-support {\n          background: rgba(156, 163, 175, 0.2);\n          color: #6b7280;\n        }\n\n        .selected-badge {\n          width: 20px;\n          height: 20px;\n          border-radius: 50%;\n          background: var(--color-primary);\n          display: flex;\n          align-items: center;\n          justify-content: center;\n          flex-shrink: 0;\n        }\n\n        .selected-badge i {\n          color: white;\n          font-size: 12px;\n        }\n\n        .footer {\n          padding: 16px;\n          border-top: 1px solid var(--border-color);\n          text-align: center;\n        }\n\n        .footer-text {\n          font-size: 12px;\n          color: var(--text-secondary);\n          line-height: 1.5;\n        }\n\n        .footer-text .highlight {\n          color: var(--color-primary);\n          font-weight: 700;\n        }\n\n        \/* 自动切换开关样式 *\/\n        .auto-switch-section {\n          padding: 14px 16px;\n          border-bottom: 1px solid var(--border-color);\n          display: flex;\n          align-items: center;\n          justify-content: space-between;\n          background: var(--bg-elevated);\n        }\n\n        .auto-switch-section:hover {\n          background: rgba(45, 157, 120, 0.05);\n        }\n\n        .auto-switch-info {\n          flex: 1;\n          min-width: 0;\n        }\n\n        .auto-switch-title {\n          font-size: 14px;\n          font-weight: 600;\n          color: var(--text-primary);\n          display: flex;\n          align-items: center;\n          gap: 8px;\n        }\n\n        .auto-switch-title i {\n          color: var(--color-primary);\n          font-size: 14px;\n        }\n\n        .auto-switch-desc {\n          font-size: 11px;\n          color: var(--text-secondary);\n          margin-top: 3px;\n          line-height: 1.4;\n        }\n\n        \/* 开关基础样式 *\/\n        .switch {\n          position: relative;\n          display: inline-block;\n          width: 48px;\n          height: 28px;\n          flex-shrink: 0;\n        }\n\n        .switch input {\n          opacity: 0;\n          width: 0;\n          height: 0;\n        }\n\n        .switch-slider {\n          position: absolute;\n          cursor: pointer;\n          top: 0;\n          left: 0;\n          right: 0;\n          bottom: 0;\n          background-color: #d1d5db;\n          transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n          border-radius: 28px;\n          box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);\n        }\n\n        .switch-slider:before {\n          position: absolute;\n          content: \"\";\n          height: 22px;\n          width: 22px;\n          left: 3px;\n          bottom: 3px;\n          background-color: white;\n          transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n          border-radius: 50%;\n          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.1);\n        }\n\n        .switch input:checked + .switch-slider {\n          background: linear-gradient(135deg, var(--color-primary), var(--color-primary-light));\n          box-shadow: 0 2px 8px rgba(45, 157, 120, 0.3);\n        }\n\n        .switch input:checked + .switch-slider:before {\n          transform: translateX(20px);\n          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);\n        }\n\n        \/* 开关焦点状态 *\/\n        .switch input:focus + .switch-slider {\n          outline: 2px solid var(--color-primary);\n          outline-offset: 2px;\n        }\n\n        \/* 下拉框样式 *\/\n        .timeout-select {\n          padding: 6px 12px;\n          font-size: 13px;\n          font-weight: 600;\n          color: var(--text-primary);\n          background: var(--bg-card);\n          border: 2px solid var(--border-color);\n          border-radius: 10px;\n          cursor: pointer;\n          min-width: 70px;\n          appearance: none;\n          background-image: url(\"data:image\/svg+xml,%3Csvg xmlns='http:\/\/www.w3.org\/2000\/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236b7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C\/polyline%3E%3C\/svg%3E\");\n          background-repeat: no-repeat;\n          background-position: right 8px center;\n          background-size: 14px;\n        }\n\n        .timeout-select:focus {\n          outline: none;\n          border-color: var(--color-primary);\n        }\n\n        .timeout-select option {\n          background: var(--bg-darker);\n          color: var(--text-primary);\n        }\n\n        .hidden-settings {\n          display: none;\n        }\/* 提示文案样式 *\/\n        .tip-alert {\n          background: linear-gradient(135deg, rgba(255, 193, 7, 0.12) 0%, rgba(255, 152, 0, 0.08) 100%);\n          border: 1px solid rgba(255, 193, 7, 0.25);\n          border-radius: 16px;\n          padding: 16px 18px;\n          margin: 20px;\n          display: flex;\n          align-items: flex-start;\n          gap: 14px;\n          box-shadow: 0 4px 16px rgba(255, 193, 7, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.8);\n          position: relative;\n          overflow: hidden;\n        }\n\n        .tip-alert::before {\n          content: '';\n          position: absolute;\n          top: -50%;\n          right: -20%;\n          width: 100px;\n          height: 100px;\n          background: rgba(255, 193, 7, 0.1);\n          border-radius: 50%;\n        }\n\n        .tip-alert i {\n          color: #f97316;\n          font-size: 22px;\n          flex-shrink: 0;\n          margin-top: 2px;\n          position: relative;\n          z-index: 1;\n        }\n\n        .tip-alert p {\n          font-size: 13px;\n          color: var(--text-secondary);\n          line-height: 1.6;\n          margin: 0;\n          position: relative;\n          z-index: 1;\n        }\n\n        .tip-alert .highlight-text {\n          color: #ea580c;\n          font-weight: 700;\n        }\n\n        @media (max-width: 600px) {\n          .tip-alert {\n            margin: 16px;\n            padding: 14px 16px;\n          }\n        }\n        <\/style>\n        <\/head>\n        <body data-theme=\"light\">\n        <div class=\"container\">\n          <div class=\"main-card\">\n            <div class=\"header\">\n              <div class=\"header-title\">\n                <i class=\"fas fa-server\"><\/i>\n                <span style=\"margin-left: 8px;\">线路选择<\/span>\n              <\/div>\n              <div class=\"header-desc\">选择最快的服务器线路以获得最佳体验<\/div>\n            <\/div>\n            \n            <!-- 提示文案 -->\n            <div class=\"tip-alert\">\n              <p>设置完成后请点击右上角的<span class=\"highlight-text\">√<\/span>应用设置<\/p>\n            <\/div>\n            \n            <div class=\"footer\">\n              <div class=\"footer-text\">\n                当前选中: <span class=\"highlight\" id=\"currentServer\">--<\/span>\n              <\/div>\n            <\/div>\n            \n            <div class=\"last-check\">\n              <i class=\"fas fa-clock\"><\/i>\n              <span id=\"lastCheckTime\">最后检测: --:--:--<\/span>\n              <button id=\"refreshBtn\" style=\"margin-left: auto; padding: 4px 10px; font-size: 11px; font-weight: 600; color: var(--color-primary); background: rgba(45, 157, 120, 0.1); border: 1px solid var(--color-primary); border-radius: 6px; cursor: pointer;\">\n                <i class=\"fas fa-sync-alt\"><\/i>\n                刷新\n              <\/button>\n            <\/div>\n            \n            <div class=\"auto-switch-section\">\n              <div class=\"auto-switch-info\">\n                <div class=\"auto-switch-title\"><i class=\"fas fa-refresh\"><\/i>失败自动切换<\/div>\n                <div class=\"auto-switch-desc\">当前线路失败时自动尝试切换到其他可用线路<\/div>\n              <\/div>\n              <label class=\"switch\">\n                <input type=\"checkbox\" id=\"autoSwitchToggle\">\n                <span class=\"switch-slider\"><\/span>\n              <\/label>\n            <\/div>\n            \n            <div class=\"auto-switch-section\">\n              <div class=\"auto-switch-info\">\n                <div class=\"auto-switch-title\"><i class=\"fas fa-clock\"><\/i>超时自动切换<\/div>\n                <div class=\"auto-switch-desc\">请求超时后自动切换到其他可用线路<\/div>\n              <\/div>\n              <div style=\"display: flex; align-items: center; gap: 12px;\">\n                <label class=\"switch\">\n                  <input type=\"checkbox\" id=\"timeoutSwitchToggle\">\n                  <span class=\"switch-slider\"><\/span>\n                <\/label>\n                <select id=\"timeoutSelect\" class=\"timeout-select\">\n                  <option value=\"1\">1秒<\/option>\n                  <option value=\"2\">2秒<\/option>\n                  <option value=\"3\">3秒<\/option>\n                  <option value=\"4\">4秒<\/option>\n                  <option value=\"5\" selected>5秒<\/option>\n                  <option value=\"6\">6秒<\/option>\n                  <option value=\"7\">7秒<\/option>\n                  <option value=\"8\">8秒<\/option>\n                  <option value=\"9\">9秒<\/option>\n                  <option value=\"10\">10秒<\/option>\n                <\/select>\n              <\/div>\n            <\/div>\n            \n            <div class=\"auto-switch-section\">\n              <div class=\"auto-switch-info\">\n                <div class=\"auto-switch-title\"><i class=\"fas fa-bell\"><\/i>切换提醒弹窗<\/div>\n                <div class=\"auto-switch-desc\">线路切换时显示提醒弹窗通知<\/div>\n              <\/div>\n              <label class=\"switch\">\n                <input type=\"checkbox\" id=\"notifySwitchToggle\">\n                <span class=\"switch-slider\"><\/span>\n              <\/label>\n            <\/div>\n            \n            <div id=\"serverList\" class=\"server-list\"><\/div>\n            \n            <div class=\"hidden-settings\">\n              <span id=\"serverValue\">${data || hostsbk[0]}<\/span>\n              <span id=\"autoSwitchValue\">${await getVariable('自动切换') || 'true'}<\/span>\n              <span id=\"timeoutSwitchValue\">${await getVariable('超时自动切换') || 'true'}<\/span>\n              <span id=\"timeoutValue\">${await getVariable('超时时间') || '5'}<\/span>\n              <span id=\"notifySwitchValue\">${await getVariable('切换提醒') || 'true'}<\/span>\n            <\/div>\n          <\/div>\n        <\/div>\n\n        <script>\n        const HOSTS = ${JSON.stringify(hostsbk)};\n        let currentServer = '${data || hostsbk[0]}';\n\n        function renderServerList() {\n          const serverList = document.getElementById('serverList');\n          serverList.innerHTML = '';\n          \n          HOSTS.forEach((host, index) => {\n            const card = document.createElement('div');\n            card.className = 'server-card' + (currentServer === host ? ' selected' : '');\n            card.dataset.host = host;\n            card.dataset.index = index;\n            \n            card.innerHTML = \\`\n              <div class=\"server-icon\">\n                <i class=\"fas fa-server\"><\/i>\n              <\/div>\n              <div class=\"server-info\">\n                <div class=\"server-url\">\\${host}<\/div>\n                <div class=\"server-details\" id=\"details-\\${index}\">状态: 检测中<\/div>\n              <\/div>\n              <span class=\"server-status checking\" id=\"status-\\${index}\">\n                <i class=\"fas fa-spinner fa-spin\"><\/i>\n                <span>检测中<\/span>\n              <\/span>\n              \\${currentServer === host ? '<div class=\"selected-badge\"><i class=\"fas fa-check\"><\/i><\/div>' : ''}\n            \\`;\n            \n            card.addEventListener('click', () => {\n              selectServer(host);\n            });\n            \n            serverList.appendChild(card);\n          });\n        }\n\n        function selectServer(host) {\n          currentServer = host;\n          \n          document.querySelectorAll('.server-card').forEach(card => {\n            card.classList.remove('selected');\n            const badge = card.querySelector('.selected-badge');\n            if (badge) badge.remove();\n          });\n          \n          const selectedCard = document.querySelector(\\`[data-host=\"\\${host}\"]\\`);\n          if (selectedCard) {\n            selectedCard.classList.add('selected');\n            const icon = selectedCard.querySelector('.server-icon');\n            icon.style.background = 'linear-gradient(135deg, var(--color-primary), var(--color-primary-light))';\n            icon.style.color = 'white';\n            \n            const statusEl = selectedCard.querySelector('.server-status');\n            const newBadge = document.createElement('div');\n            newBadge.className = 'selected-badge';\n            newBadge.innerHTML = '<i class=\"fas fa-check\"><\/i>';\n            selectedCard.appendChild(newBadge);\n          }\n          \n          document.getElementById('currentServer').textContent = host;\n          document.getElementById('serverValue').textContent = host;\n        }\n\n        async function checkSingleServer(host, index) {\n          const currentProtocol = window.location.protocol;\n          const statusEl = document.getElementById(\\`status-\\${index}\\`);\n          const detailsEl = document.getElementById(\\`details-\\${index}\\`);\n          \n          try {\n            const serverProtocol = new URL(host).protocol;\n            if (currentProtocol === 'https:' && serverProtocol === 'http:') {\n              statusEl.className = 'server-status not-support';\n              statusEl.innerHTML = '<i class=\"fas fa-exclamation-triangle\"><\/i><span>不支持<\/span>';\n              detailsEl.textContent = '浏览器安全策略限制';\n              return;\n            }\n            \n            const startTime = performance.now();\n            const controller = new AbortController();\n            const timeout = setTimeout(() => controller.abort(), 5000);\n            \n            const response = await fetch(\\`\\${host}\/health?t=\\${Date.now()}\\`, {\n              signal: controller.signal,\n              method: 'GET',\n              mode: 'cors',\n              cache: 'no-cache'\n            });\n            \n            clearTimeout(timeout);\n            const responseTime = Math.round(performance.now() - startTime);\n            \n            if (response.ok) {\n              if (responseTime < 1000) {\n                statusEl.className = 'server-status online';\n                statusEl.innerHTML = \\`<i class=\"fas fa-check-circle\"><\/i><span>\\${responseTime}ms<\/span>\\`;\n              } else if (responseTime < 2000) {\n                statusEl.className = 'server-status slow';\n                statusEl.innerHTML = \\`<i class=\"fas fa-exclamation-circle\"><\/i><span>\\${responseTime}ms<\/span>\\`;\n              } else {\n                statusEl.className = 'server-status slow';\n                statusEl.innerHTML = '<i class=\"fas fa-exclamation-circle\"><\/i><span>缓慢<\/span>';\n              }\n              detailsEl.textContent = '运行正常';\n            } else {\n              statusEl.className = 'server-status offline';\n              statusEl.innerHTML = '<i class=\"fas fa-times-circle\"><\/i><span>异常<\/span>';\n              detailsEl.textContent = 'HTTP状态码: ' + response.status;\n            }\n          } catch (error) {\n            statusEl.className = 'server-status offline';\n            statusEl.innerHTML = '<i class=\"fas fa-times-circle\"><\/i><span>离线<\/span>';\n            detailsEl.textContent = '连接失败';\n          }\n        }\n\n        async function checkServers() {\n          HOSTS.forEach((_, index) => {\n            const statusEl = document.getElementById(\\`status-\\${index}\\`);\n            const detailsEl = document.getElementById(\\`details-\\${index}\\`);\n            if (statusEl && detailsEl) {\n              statusEl.className = 'server-status checking';\n              statusEl.innerHTML = '<i class=\"fas fa-spinner fa-spin\"><\/i><span>检测中<\/span>';\n              detailsEl.textContent = '状态: 检测中';\n            }\n          });\n          \n          const promises = HOSTS.map((host, index) => checkSingleServer(host, index));\n          await Promise.all(promises);\n          \n          const now = new Date();\n          document.getElementById('lastCheckTime').textContent = \\`最后检测: \\${now.toLocaleTimeString()}\\`;\n        }\n\n        document.getElementById('refreshBtn').addEventListener('click', checkServers);\n\n        document.addEventListener('DOMContentLoaded', () => {\n          renderServerList();\n          document.getElementById('currentServer').textContent = currentServer;\n          checkServers();\n          \n          const autoSwitchToggle = document.getElementById('autoSwitchToggle');\n          const autoSwitchValue = document.getElementById('autoSwitchValue').textContent;\n          autoSwitchToggle.checked = autoSwitchValue !== 'false';\n          \n          autoSwitchToggle.addEventListener('change', (e) => {\n            document.getElementById('autoSwitchValue').textContent = e.target.checked ? 'true' : 'false';\n          });\n          \n          const timeoutSwitchToggle = document.getElementById('timeoutSwitchToggle');\n          const timeoutSwitchValue = document.getElementById('timeoutSwitchValue').textContent;\n          timeoutSwitchToggle.checked = timeoutSwitchValue !== 'false';\n          \n          timeoutSwitchToggle.addEventListener('change', (e) => {\n            document.getElementById('timeoutSwitchValue').textContent = e.target.checked ? 'true' : 'false';\n          });\n          \n          const timeoutSelect = document.getElementById('timeoutSelect');\n          const timeoutValue = document.getElementById('timeoutValue').textContent;\n          timeoutSelect.value = timeoutValue;\n          \n          timeoutSelect.addEventListener('change', (e) => {\n            document.getElementById('timeoutValue').textContent = e.target.value;\n          });\n          \n          const notifySwitchToggle = document.getElementById('notifySwitchToggle');\n          const notifySwitchValue = document.getElementById('notifySwitchValue').textContent;\n          notifySwitchToggle.checked = notifySwitchValue !== 'false';\n          \n          notifySwitchToggle.addEventListener('change', (e) => {\n            document.getElementById('notifySwitchValue').textContent = e.target.checked ? 'true' : 'false';\n          });\n        });\n        <\\\/script>\n        <\\\/body>\n        <\\\/html>\n    `;\n        let body;\n        try {\n            body = await flutterBridge.startBrowser(`data:text\/html;base64,${await flutterBridge.base64encode(html)}`, '光遇服务器设置');\n        } catch (e) {\n            flutterBridge.showToast(\"设置出错~\");\n            return;\n        }\n\n        let host = hostsbk[0];\n        let autoSwitch = 'true';\n        let timeoutSwitch = 'true';\n        let timeout = '5';\n        let notifySwitch = 'true';\n        try {\n            const getSpanText = (spanId) => {\n                const regex = new RegExp('id=\"' + spanId + '\"\\s*>\\s*([^<]*?)\\s*<\\\/span>');\n                const match = body.match(regex);\n                return match ? match[1].trim() : \"\";\n            };\n            host = getSpanText('serverValue');\n            autoSwitch = getSpanText('autoSwitchValue') || 'true';\n            timeoutSwitch = getSpanText('timeoutSwitchValue') || 'true';\n            timeout = getSpanText('timeoutValue') || '5';\n            notifySwitch = getSpanText('notifySwitchValue') || 'true';\n        } catch (e) {\n            host = hostsbk[0];\n            autoSwitch = 'true';\n            timeoutSwitch = 'true';\n            timeout = '5';\n            notifySwitch = 'true';\n        }\n        if (host == \"\") {\n            flutterBridge.showToast(\"需要点击右上角√才能应用设置哦~\");\n            return;\n        }\n        const settingsText = `线路: ${host}\\n失败自动切换: ${autoSwitch == 'true' ? '开启' : '关闭'}\\n超时自动切换: ${timeoutSwitch == 'true' ? '开启' : '关闭'}\\n超时时间: ${timeout}秒\\n切换提醒弹窗: ${notifySwitch == 'true' ? '开启' : '关闭'}`;\n        await setVariable(\"线路\", host, false);\n        await setVariable(\"自动切换\", autoSwitch, false);\n        await setVariable(\"超时自动切换\", timeoutSwitch, false);\n        await setVariable(\"超时时间\", timeout, false);\n        await setVariable(\"切换提醒\", notifySwitch, false);\n        flutterBridge.showToast(\"\\n\" + settingsText);\n    }\n\n\n    \/\/ 段评设置页面\n    async function getSvgSettings() {\n        await flutterBridge.showsvg();\n        \/\/ let burl = await flutterBridge.getReadBook();\n        \/\/ await flutterBridge.refreshContent(burl);\n    }\n\n    \/\/ 更多设置\n    async function getHtmlSettings() {\n        let data = await getVariable('更多设置');\n        let config = await getVariable('云端配置');\n        if (!config) {\n            try {\n                await getCloudSettings(true);\n                config = await getVariable('云端配置');\n            } catch {\n            }\n        }\n        let html = `\n    <!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no\">\n<title>光遇小说 - 管理中心<\/title>\n<link rel=\"stylesheet\" href=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/font-awesome\/6.4.0\/css\/all.min.css\">\n<style>\n* {\n  margin: 0;\n  padding: 0;\n  box-sizing: border-box;\n  -webkit-tap-highlight-color: transparent;\n}\n\n:root {\n  --color-primary: #2D9D78;\n  --color-primary-light: #3DB893;\n  --color-primary-dark: #1E6F55;\n  --color-accent: #FFB74D;\n  --color-success: #4CAF50;\n  --color-error: #F44336;\n  --color-warning: #FFC107;\n  --color-info: #2196F3;\n\n  --bg-dark: #f0f3f8;\n  --bg-darker: #e8edf5;\n  --bg-card: rgba(255, 255, 255, 0.98);\n  --bg-elevated: #fafbfc;\n\n  --text-primary: #1a1d23;\n  --text-secondary: #6b7280;\n  --text-tertiary: #9ca3af;\n\n  --border-color: rgba(0, 0, 0, 0.06);\n}\n\n[data-theme=\"dark\"] {\n  --bg-dark: #0A1628;\n  --bg-darker: #050B14;\n  --bg-card: #142235;\n  --bg-elevated: #1A2B42;\n\n  --text-primary: #FFFFFF;\n  --text-secondary: rgba(255, 255, 255, 0.7);\n  --text-tertiary: rgba(255, 255, 255, 0.5);\n\n  --border-color: rgba(255, 255, 255, 0.08);\n}\n\nhtml, body {\n  width: 100%;\n  min-height: 100vh;\n  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n  font-size: 16px;\n  color: var(--text-primary);\n  background: linear-gradient(135deg, var(--bg-darker) 0%, var(--bg-dark) 100%);\n  background-attachment: fixed;\n  overflow-x: hidden;\n  transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.container {\n  padding: 24px 20px;\n  max-width: 820px;\n  margin: 0 auto;\n  animation: fadeIn 0.5s ease-out;\n}\n\n@keyframes fadeIn {\n  from { opacity: 0; transform: translateY(10px); }\n  to { opacity: 1; transform: translateY(0); }\n}\n\n.main-card {\n  background: var(--bg-card);\n  border-radius: 24px;\n  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08), 0 2px 8px rgba(0, 0, 0, 0.04), inset 0 1px 0 rgba(255, 255, 255, 0.8);\n  border: 1px solid var(--border-color);\n  overflow: hidden;\n  backdrop-filter: blur(20px);\n}\n\n\/* 主Tab切换 *\/\n.main-tabs {\n  display: flex;\n  background: linear-gradient(180deg, var(--bg-elevated) 0%, rgba(255, 255, 255, 0.5) 100%);\n  border-bottom: 1px solid var(--border-color);\n  position: relative;\n}\n\n.main-tabs::after {\n  content: '';\n  position: absolute;\n  bottom: 0;\n  left: 0;\n  right: 0;\n  height: 1px;\n  background: linear-gradient(90deg, transparent, var(--border-color), transparent);\n}\n\n.main-tab {\n  flex: 1;\n  padding: 20px 24px;\n  text-align: center;\n  font-size: 15px;\n  font-weight: 700;\n  color: var(--text-secondary);\n  cursor: pointer;\n  transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);\n  border-bottom: 3px solid transparent;\n  position: relative;\n  overflow: hidden;\n}\n\n.main-tab::before {\n  content: '';\n  position: absolute;\n  top: 0;\n  left: 50%;\n  transform: translateX(-50%) scaleX(0);\n  width: 60%;\n  height: 3px;\n  background: linear-gradient(90deg, var(--color-primary), var(--color-primary-light));\n  transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);\n  border-radius: 0 0 3px 3px;\n}\n\n.main-tab.active {\n  color: var(--color-primary);\n  background: linear-gradient(180deg, rgba(45, 157, 120, 0.06) 0%, transparent 100%);\n}\n\n.main-tab.active::before {\n  transform: translateX(-50%) scaleX(1);\n}\n\n.main-tab:hover {\n  color: var(--color-primary);\n  background: rgba(45, 157, 120, 0.04);\n}\n\n.tab-content {\n  padding: 20px;\n  display: none;\n  animation: slideIn 0.3s ease-out;\n}\n\n.tab-content.active {\n  display: block;\n}\n\n@keyframes slideIn {\n  from { opacity: 0; transform: translateY(8px); }\n  to { opacity: 1; transform: translateY(0); }\n}\n\n\/* 来源选择子Tab *\/\n.source-subtabs {\n  display: grid;\n  grid-template-columns: repeat(4, 1fr);\n  gap: 10px;\n  margin-bottom: 20px;\n}\n\n.source-subtab {\n  padding: 14px 10px;\n  text-align: center;\n  font-size: 12px;\n  font-weight: 600;\n  color: var(--text-secondary);\n  background: var(--bg-elevated);\n  border-radius: 14px;\n  cursor: pointer;\n  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n  border: 2px solid transparent;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  gap: 6px;\n}\n\n.source-subtab i {\n  font-size: 18px;\n  transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.source-subtab.novel.active {\n  color: white;\n  background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);\n  border-color: rgba(239, 68, 68, 0.5);\n  box-shadow: 0 8px 24px rgba(239, 68, 68, 0.35);\n  transform: translateY(-2px);\n}\n\n.source-subtab.audio.active {\n  color: white;\n  background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);\n  border-color: rgba(59, 130, 246, 0.5);\n  box-shadow: 0 8px 24px rgba(59, 130, 246, 0.35);\n  transform: translateY(-2px);\n}\n\n.source-subtab.comic.active {\n  color: white;\n  background: linear-gradient(135deg, #a855f7 0%, #9333ea 100%);\n  border-color: rgba(168, 85, 247, 0.5);\n  box-shadow: 0 8px 24px rgba(168, 85, 247, 0.35);\n  transform: translateY(-2px);\n}\n\n.source-subtab.drama.active {\n  color: white;\n  background: linear-gradient(135deg, #f97316 0%, #ea580c 100%);\n  border-color: rgba(249, 115, 22, 0.5);\n  box-shadow: 0 8px 24px rgba(249, 115, 22, 0.35);\n  transform: translateY(-2px);\n}\n\n.source-subtab:hover:not(.active) {\n  border-color: var(--color-primary);\n  background: rgba(45, 157, 120, 0.06);\n  transform: translateY(-1px);\n}\n\n.source-subtab.active i {\n  transform: scale(1.1);\n}\n\n\/* 来源选项 *\/\n.source-options {\n  display: grid;\n  grid-template-columns: repeat(auto-fill, minmax(125px, 1fr));\n  gap: 12px;\n}\n\n.source-option {\n  padding: 14px 16px;\n  background: var(--bg-elevated);\n  border-radius: 12px;\n  text-align: center;\n  font-size: 13px;\n  font-weight: 600;\n  color: var(--text-secondary);\n  cursor: pointer;\n  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n  border: 2px solid transparent;\n}\n\n.source-option:hover {\n  border-color: var(--color-primary);\n  background: rgba(45, 157, 120, 0.05);\n  transform: translateY(-2px);\n}\n\n.source-option.selected {\n  background: linear-gradient(135deg, rgba(45, 157, 120, 0.18), rgba(61, 184, 147, 0.08));\n  color: var(--color-primary);\n  border-color: var(--color-primary);\n  box-shadow: 0 4px 16px rgba(45, 157, 120, 0.2);\n  transform: translateY(-2px);\n}\n\n.source-option.all {\n  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n  color: white;\n  box-shadow: 0 4px 16px rgba(102, 126, 234, 0.3);\n}\n\n.source-option.all.selected {\n  background: linear-gradient(135deg, #764ba2 0%, #667eea 100%);\n  box-shadow: 0 6px 24px rgba(102, 126, 234, 0.45);\n  transform: translateY(-3px);\n}\n\n.source-option.all:hover {\n  border-color: transparent;\n  transform: translateY(-3px);\n}\n\n\/* 选中状态提示 *\/\n.selected-info {\n  margin-top: 20px;\n  padding: 16px 18px;\n  background: var(--bg-elevated);\n  border-radius: 12px;\n  font-size: 13px;\n  color: var(--text-secondary);\n  border: 1px solid var(--border-color);\n}\n\n.selected-info span {\n  color: var(--color-primary);\n  font-weight: 700;\n}\n\n\/* 设置项样式 *\/\n.setting-item {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  padding: 20px;\n  background: var(--bg-elevated);\n  border-radius: 16px;\n  margin-bottom: 12px;\n  border: 1px solid var(--border-color);\n  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n  position: relative;\n  overflow: hidden;\n}\n\n\n.setting-item:hover::before {\n  opacity: 1;\n}\n\n.setting-info {\n  flex: 1;\n  min-width: 0;\n}\n\n.setting-title {\n  font-size: 16px;\n  font-weight: 700;\n  color: var(--text-primary);\n  margin-bottom: 6px;\n}\n\n.setting-desc {\n  font-size: 13px;\n  color: var(--text-secondary);\n  line-height: 1.5;\n}\n\n\/* 开关样式 *\/\n.switch {\n  position: relative;\n  width: 54px;\n  height: 30px;\n  flex-shrink: 0;\n}\n\n.switch input {\n  opacity: 0;\n  width: 0;\n  height: 0;\n}\n\n.slider {\n  position: absolute;\n  cursor: pointer;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  background: linear-gradient(135deg, #e5e7eb 0%, #d1d5db 100%);\n  transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);\n  border-radius: 30px;\n  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1), inset 0 -2px 4px rgba(0, 0, 0, 0.05);\n}\n\n.slider:before {\n  position: absolute;\n  content: \"\";\n  height: 24px;\n  width: 24px;\n  left: 3px;\n  bottom: 3px;\n  background: linear-gradient(135deg, #ffffff 0%, #f3f4f6 100%);\n  transition: all 0.35s cubic-bezier(0.4, 0, 0.2, 1);\n  border-radius: 50%;\n  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.1);\n}\n\ninput:checked + .slider {\n  background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-light) 100%);\n  box-shadow: 0 4px 16px rgba(45, 157, 120, 0.35), inset 0 1px 0 rgba(255, 255, 255, 0.2);\n}\n\ninput:checked + .slider:before {\n  transform: translateX(24px);\n  background: linear-gradient(135deg, #ffffff 0%, #fefefe 100%);\n  box-shadow: 0 3px 8px rgba(0, 0, 0, 0.2), 0 1px 2px rgba(0, 0, 0, 0.1);\n}\n\n\/* 关键词高亮样式 *\/\n.highlight {\n  color: var(--color-primary);\n  font-weight: 700;\n  position: relative;\n}\n\n.highlight::after {\n  content: '';\n  position: absolute;\n  bottom: -2px;\n  left: 0;\n  width: 100%;\n  height: 2px;\n  background: linear-gradient(90deg, var(--color-primary), var(--color-primary-light));\n  border-radius: 2px;\n}\n\n.highlight-orange {\n  color: #f97316;\n  font-weight: 700;\n}\n\n.highlight-blue {\n  color: #3b82f6;\n  font-weight: 700;\n}\n\n.highlight-purple {\n  color: #a855f7;\n  font-weight: 700;\n}\n\n.highlight-red {\n  color: #ef4444;\n  font-weight: 700;\n}\n\n\/* 选择器样式 *\/\n.select-wrapper {\n  position: relative;\n  flex-shrink: 0;\n}\n\n.select-wrapper select {\n  appearance: none;\n  -webkit-appearance: none;\n  padding: 10px 34px 10px 14px;\n  font-size: 13px;\n  font-weight: 600;\n  color: var(--text-primary);\n  background: var(--bg-card);\n  border: 2px solid var(--border-color);\n  border-radius: 10px;\n  cursor: pointer;\n  min-width: 110px;\n  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.select-wrapper select:hover {\n  border-color: var(--color-primary);\n}\n\n.select-wrapper select:focus {\n  outline: none;\n  border-color: var(--color-primary);\n  box-shadow: 0 0 0 3px rgba(45, 157, 120, 0.1);\n}\n\n.select-wrapper::after {\n  content: '\\\\f078';\n  font-family: 'Font Awesome 6 Free';\n  font-weight: 900;\n  position: absolute;\n  right: 10px;\n  top: 50%;\n  transform: translateY(-50%);\n  color: var(--text-secondary);\n  pointer-events: none;\n  font-size: 13px;\n  transition: transform 0.3s ease;\n}\n\n.select-wrapper:hover::after {\n  transform: translateY(-50%) rotate(180deg);\n}\n\n\/* 提示文案样式 *\/\n.tip-alert {\n  background: linear-gradient(135deg, rgba(255, 193, 7, 0.12) 0%, rgba(255, 152, 0, 0.08) 100%);\n  border: 1px solid rgba(255, 193, 7, 0.25);\n  border-radius: 16px;\n  padding: 16px 18px;\n  margin: 20px;\n  display: flex;\n  align-items: flex-start;\n  gap: 14px;\n  box-shadow: 0 4px 16px rgba(255, 193, 7, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.8);\n  position: relative;\n  overflow: hidden;\n}\n\n.tip-alert::before {\n  content: '';\n  position: absolute;\n  top: -50%;\n  right: -20%;\n  width: 100px;\n  height: 100px;\n  background: rgba(255, 193, 7, 0.1);\n  border-radius: 50%;\n}\n\n.tip-alert i {\n  color: #f97316;\n  font-size: 22px;\n  flex-shrink: 0;\n  margin-top: 2px;\n  position: relative;\n  z-index: 1;\n}\n\n.tip-alert p {\n  font-size: 13px;\n  color: var(--text-secondary);\n  line-height: 1.6;\n  margin: 0;\n  position: relative;\n  z-index: 1;\n}\n\n.tip-alert .highlight-text {\n  color: #ea580c;\n  font-weight: 700;\n}\n\n\/* 响应式 *\/\n@media (max-width: 600px) {\n  .container {\n    padding: 16px 12px;\n  }\n\n  .main-card {\n    border-radius: 20px;\n  }\n\n  .tab-content {\n    padding: 16px;\n  }\n\n  .tip-alert {\n    margin: 16px;\n    padding: 14px 16px;\n  }\n\n  .source-subtabs {\n    grid-template-columns: repeat(2, 1fr);\n    gap: 8px;\n  }\n\n  .source-options {\n    grid-template-columns: repeat(auto-fill, minmax(105px, 1fr));\n    gap: 10px;\n  }\n\n  .setting-item {\n    padding: 16px;\n  }\n\n  .setting-title {\n    font-size: 15px;\n  }\n\n  .setting-desc {\n    font-size: 12px;\n  }\n}\n\n::-webkit-scrollbar {\n  width: 6px;\n  height: 6px;\n}\n\n::-webkit-scrollbar-track {\n  background: var(--bg-elevated);\n  border-radius: 3px;\n}\n\n::-webkit-scrollbar-thumb {\n  background: var(--text-tertiary);\n  border-radius: 3px;\n}\n\n::-webkit-scrollbar-thumb:hover {\n  background: var(--text-secondary);\n}\n<\/style>\n<\/head>\n<body data-theme=\"light\">\n<div class=\"container\">\n  <div class=\"main-card\">\n    <div class=\"tip-alert\">\n      <p>设置完成后请点击右上角的<span class=\"highlight-text\">√<\/span>应用设置<\/p>\n    <\/div>\n\n    <div class=\"main-tabs\">\n      <div class=\"main-tab active\" id=\"tabBookSource\">书源设置<\/div>\n      <div class=\"main-tab\" id=\"tabSource\">搜索设置<\/div>\n    <\/div>\n\n    <div class=\"tab-content active\" id=\"contentBookSource\">\n      <div class=\"setting-item\">\n        <div class=\"setting-info\">\n          <div class=\"setting-title\">搜索模式<\/div>\n          <div class=\"setting-desc\">切换后会搜索对应的类型，不推荐手动设置，可以在搜索的时候使用关键词：<span class=\"highlight-orange\">t:书名<\/span> 搜听书，<span class=\"highlight-blue\">d:书名<\/span> 搜短剧，<span class=\"highlight-purple\">m:书名<\/span> 搜漫画<\/div>\n        <\/div>\n        <div class=\"select-wrapper\">\n          <select id=\"searchMode\">\n            <option value=\"小说\">小说<\/option>\n            <option value=\"听书\">听书<\/option>\n            <option value=\"短剧\">短剧<\/option>\n            <option value=\"漫画\">漫画<\/option>\n          <\/select>\n        <\/div>\n      <\/div>\n\n      <div class=\"setting-item\">\n        <div class=\"setting-info\">\n          <div class=\"setting-title\">显示图片<\/div>\n          <div class=\"setting-desc\">关闭后，文章内的图片将会清除<\/div>\n        <\/div>\n        <label class=\"switch\">\n          <input type=\"checkbox\" id=\"showImageSwitch\" checked>\n          <span class=\"slider\"><\/span>\n        <\/label>\n      <\/div>\n\n      <div class=\"setting-item\">\n        <div class=\"setting-info\">\n          <div class=\"setting-title\">段评开关<\/div>\n           <div class=\"setting-desc\">仅 <span class=\"highlight-red\">番茄<\/span>、<span class=\"highlight\">七猫<\/span>、<span class=\"highlight-red\">塔读<\/span>、<span class=\"highlight\">QQ阅读<\/span> 支持<\/div>\n        <\/div>\n        <label class=\"switch\">\n          <input type=\"checkbox\" id=\"showParaSwitch\" checked>\n          <span class=\"slider\"><\/span>\n        <\/label>\n      <\/div>\n\n      <div class=\"setting-item\">\n        <div class=\"setting-info\">\n          <div class=\"setting-title\">完整简介<\/div>\n          <div class=\"setting-desc\">开启后，书籍详情页显示完整简介内容<\/div>\n        <\/div>\n        <label class=\"switch\">\n          <input type=\"checkbox\" id=\"showFullDescSwitch\" checked>\n          <span class=\"slider\"><\/span>\n        <\/label>\n      <\/div>\n\n      <div class=\"setting-item\">\n        <div class=\"setting-info\">\n          <div class=\"setting-title\">同步书架<\/div>\n          <div class=\"setting-desc\">开启后，刷新书籍详情页会将书架书籍同步到光遇网站网页端书架<\/div>\n        <\/div>\n        <label class=\"switch\">\n          <input type=\"checkbox\" id=\"syncBookshelfSwitch\">\n          <span class=\"slider\"><\/span>\n        <\/label>\n      <\/div>\n\n      <div class=\"setting-item\" style=\"display:none;\">\n        <div class=\"setting-info\">\n          <div class=\"setting-title\">网络模式<\/div>\n          <div class=\"setting-desc\">开启后部分来源会使用本地网络请求，如<span class=\"highlight-orange\">69书吧<\/span>，可能需要开梯子访问，不推荐开启<\/div>\n        <\/div>\n        <div class=\"select-wrapper\">\n          <select id=\"networkMode\">\n            <option value=\"服务器\">服务器<\/option>\n            <option value=\"本地\">本地<\/option>\n          <\/select>\n        <\/div>\n      <\/div>\n\n      <div class=\"setting-item\">\n        <div class=\"setting-info\">\n          <div class=\"setting-title\">强制搜索<\/div>\n          <div class=\"setting-desc\">开启后搜索时会 <span class=\"highlight-red\">强制搜索全部来源<\/span>，搜索时长会 <span class=\"highlight-orange\">明显增长<\/span>，若无必要不建议开启<\/div>\n        <\/div>\n        <label class=\"switch\">\n          <input type=\"checkbox\" id=\"forceSearchSwitch\">\n          <span class=\"slider\"><\/span>\n        <\/label>\n      <\/div>\n\n      <div class=\"setting-item\">\n        <div class=\"setting-info\">\n          <div class=\"setting-title\">目录显示来源<\/div>\n          <div class=\"setting-desc\">开启后，目录最后一个章节的标题将显示来源，方便换源的时候开启加载目录可以知道是哪个来源<\/div>\n        <\/div>\n        <label class=\"switch\">\n          <input type=\"checkbox\" id=\"showSourceInTocSwitch\">\n          <span class=\"slider\"><\/span>\n        <\/label>\n      <\/div>\n    <\/div>\n\n    <div class=\"tab-content\" id=\"contentSource\">\n      <div class=\"source-subtabs\">\n        <div class=\"source-subtab novel active\" id=\"subtabNovel\" data-type=\"小说\">\n          <i class=\"fas fa-book-open\"><\/i>\n          <span>小说<\/span>\n        <\/div>\n        <div class=\"source-subtab audio\" id=\"subtabAudio\" data-type=\"听书\">\n          <i class=\"fas fa-headphones\"><\/i>\n          <span>听书<\/span>\n        <\/div>\n        <div class=\"source-subtab comic\" id=\"subtabComic\" data-type=\"漫画\">\n          <i class=\"fas fa-image\"><\/i>\n          <span>漫画<\/span>\n        <\/div>\n        <div class=\"source-subtab drama\" id=\"subtabDrama\" data-type=\"短剧\">\n          <i class=\"fas fa-video\"><\/i>\n          <span>短剧<\/span>\n        <\/div>\n      <\/div>\n      <div class=\"selected-info\" style=\"margin-bottom: 10px; background: rgba(45, 157, 120, 0.08); border-color: rgba(45, 157, 120, 0.2);\">\n        推荐选择全部，如需要搜索特定的资源可以搜索的时候使用 <span class=\"highlight\">书名@来源<\/span> 进行搜索\n      <\/div>\n      <div id=\"sourceOptions\" class=\"source-options\"><\/div>\n      <div class=\"selected-info\" id=\"selectedInfo\">\n        当前选中: <span id=\"selectedCount\">0<\/span> 个来源\n      <\/div>\n    <\/div>\n\n    <div id=\"hiddenSettings\" style=\"display: none;\">\n      <script type=\"application\/json\" id=\"settingsJson\">\n        ${JSON.stringify(data || {\n            \"小说\": \"全部\",\n            \"听书\": \"全部\",\n            \"漫画\": \"全部\",\n            \"短剧\": \"全部\",\n            \"搜索模式\": \"小说\",\n            \"显示图片\": \"true\",\n            \"段评开关\": \"true\",\n            \"完整简介\": \"true\",\n            \"同步书架\": \"false\",\n            \"网络模式\": \"服务器\",\n            \"强制搜索\": \"false\",\n            \"目录显示来源\": \"false\"\n        })}\n      <\\\/script>\n      <div id=\"settingsDiv\">\n        <span id=\"novelSource\">${(data && data.小说) || '全部'}<\/span>\n        <span id=\"audioSource\">${(data && data.听书) || '全部'}<\/span>\n        <span id=\"comicSource\">${(data && data.漫画) || '全部'}<\/span>\n        <span id=\"dramaSource\">${(data && data.短剧) || '全部'}<\/span>\n        <span id=\"searchModeValue\">${(data && data.搜索模式) || '小说'}<\/span>\n        <span id=\"showImageSwitchValue\">${(data && data.显示图片) || 'true'}<\/span>\n        <span id=\"showParaSwitchValue\">${(data && data.段评开关) || 'true'}<\/span>\n        <span id=\"showFullDescSwitchValue\">${(data && data.完整简介) || 'true'}<\/span>\n        <span id=\"syncBookshelfSwitchValue\">${(data && data.同步书架) || 'false'}<\/span>\n        <span id=\"networkModeValue\">${(data && data.网络模式) || '服务器'}<\/span>\n        <span id=\"forceSearchSwitchValue\">${(data && data.强制搜索) || 'false'}<\/span>\n        <span id=\"showSourceInTocValue\">${(data && data.目录显示来源) || 'false'}<\/span>\n      <\/div>\n    <\/div>\n  <\/div>\n<\/div>\n\n<script>\nconst SOURCES = ${JSON.stringify(config)};\nlet settings = ${JSON.stringify(data)};\nlet currentType = '小说';\n\ndocument.getElementById('tabSource').addEventListener('click', () => {\n  document.getElementById('tabSource').classList.add('active');\n  document.getElementById('tabBookSource').classList.remove('active');\n  document.getElementById('contentSource').classList.add('active');\n  document.getElementById('contentBookSource').classList.remove('active');\n});\n\ndocument.getElementById('tabBookSource').addEventListener('click', () => {\n  document.getElementById('tabBookSource').classList.add('active');\n  document.getElementById('tabSource').classList.remove('active');\n  document.getElementById('contentBookSource').classList.add('active');\n  document.getElementById('contentSource').classList.remove('active');\n});\n\nconst subtabs = document.querySelectorAll('.source-subtab');\nsubtabs.forEach(tab => {\n  tab.addEventListener('click', () => {\n    subtabs.forEach(t => t.classList.remove('active'));\n    tab.classList.add('active');\n    currentType = tab.dataset.type;\n    renderSourceOptions();\n  });\n});\n\nfunction renderSourceOptions() {\n  const optionsContainer = document.getElementById('sourceOptions');\n  const key = \\`搜索\\${currentType}\\`;\n  const sources = SOURCES[key] || [];\n  const savedValue = settings[currentType] || '';\n  const selectedValues = savedValue === '全部' ? ['全部'] : (savedValue ? savedValue.split(',') : []);\n\n  optionsContainer.innerHTML = '';\n\n  const allOption = document.createElement('div');\n  allOption.className = \\`source-option all \\${selectedValues.includes('全部') ? 'selected' : ''}\\`;\n  allOption.textContent = '全部';\n  allOption.addEventListener('click', () => selectOption('全部'));\n  optionsContainer.appendChild(allOption);\n\n  sources.forEach(source => {\n    const option = document.createElement('div');\n    option.className = \\`source-option \\${selectedValues.includes(source) ? 'selected' : ''}\\`;\n    option.textContent = source;\n    option.addEventListener('click', () => selectOption(source));\n    optionsContainer.appendChild(option);\n  });\n\n  updateSelectedInfo();\n}\n\nfunction updateHiddenSettings() {\n  const jsonEl = document.getElementById('settingsJson');\n  if (jsonEl) {\n    jsonEl.textContent = JSON.stringify(settings);\n  }\n\n  const typeMap = {\n    '小说': 'novelSource',\n    '听书': 'audioSource',\n    '漫画': 'comicSource',\n    '短剧': 'dramaSource',\n    '搜索模式': 'searchModeValue',\n    '显示图片': 'showImageSwitchValue',\n    '段评开关': 'showParaSwitchValue',\n    '完整简介': 'showFullDescSwitchValue',\n    '同步书架': 'syncBookshelfSwitchValue',\n    '网络模式': 'networkModeValue',\n    '强制搜索': 'forceSearchSwitchValue',\n    '目录显示来源': 'showSourceInTocValue'\n  };\n\n  Object.keys(typeMap).forEach(type => {\n    const el = document.getElementById(typeMap[type]);\n    if (el) {\n      el.textContent = settings[type] != undefined ? settings[type] : (type.includes('开关') ? 'false' : '全部');\n    }\n  });\n}\n\nfunction setupBookSourceSettings() {\n  document.getElementById('searchMode').addEventListener('change', (e) => {\n    settings['搜索模式'] = e.target.value;\n    updateHiddenSettings();\n  });\n\n  document.getElementById('showImageSwitch').addEventListener('change', (e) => {\n    settings['显示图片'] = e.target.checked ? 'true' : 'false';\n    updateHiddenSettings();\n  });\n\n  document.getElementById('showParaSwitch').addEventListener('change', (e) => {\n    settings['段评开关'] = e.target.checked ? 'true' : 'false';\n    updateHiddenSettings();\n  });\n\n  document.getElementById('showFullDescSwitch').addEventListener('change', (e) => {\n    settings['完整简介'] = e.target.checked ? 'true' : 'false';\n    updateHiddenSettings();\n  });\n\n  document.getElementById('syncBookshelfSwitch').addEventListener('change', (e) => {\n    settings['同步书架'] = e.target.checked ? 'true' : 'false';\n    updateHiddenSettings();\n  });\n\n  document.getElementById('networkMode').addEventListener('change', (e) => {\n    settings['网络模式'] = e.target.value;\n    updateHiddenSettings();\n  });\n\n  document.getElementById('forceSearchSwitch').addEventListener('change', (e) => {\n    settings['强制搜索'] = e.target.checked ? 'true' : 'false';\n    updateHiddenSettings();\n  });\n\n  document.getElementById('showSourceInTocSwitch').addEventListener('change', (e) => {\n    settings['目录显示来源'] = e.target.checked ? 'true' : 'false';\n    updateHiddenSettings();\n  });\n}\n\nfunction initBookSourceSettings() {\n  const searchModeEl = document.getElementById('searchMode');\n  if (searchModeEl && settings['搜索模式']) {\n    searchModeEl.value = settings['搜索模式'];\n  }\n\n  const showImageEl = document.getElementById('showImageSwitch');\n  if (showImageEl) {\n    showImageEl.checked = settings['显示图片'] != 'false';\n  }\n\n  const showParaEl = document.getElementById('showParaSwitch');\n  if (showParaEl) {\n    showParaEl.checked = settings['段评开关'] != 'false';\n  }\n\n  const showFullDescEl = document.getElementById('showFullDescSwitch');\n  if (showFullDescEl) {\n    showFullDescEl.checked = settings['完整简介'] != 'false';\n  }\n\n  const syncBookshelfEl = document.getElementById('syncBookshelfSwitch');\n  if (syncBookshelfEl) {\n    syncBookshelfEl.checked = settings['同步书架'] === 'true';\n  }\n\n  const networkModeEl = document.getElementById('networkMode');\n  if (networkModeEl && settings['网络模式']) {\n    networkModeEl.value = settings['网络模式'];\n  }\n\n  const forceSearchEl = document.getElementById('forceSearchSwitch');\n  if (forceSearchEl) {\n    forceSearchEl.checked = settings['强制搜索'] === 'true';\n  }\n\n  const showSourceInTocEl = document.getElementById('showSourceInTocSwitch');\n  if (showSourceInTocEl) {\n    showSourceInTocEl.checked = settings['目录显示来源'] === 'true';\n  }\n}\n\nfunction selectOption(value) {\n  const key = \\`搜索\\${currentType}\\`;\n  const sources = SOURCES[key] || [];\n\n  if (value === '全部') {\n    settings[currentType] = '全部';\n    updateHiddenSettings();\n    renderSourceOptions();\n    return;\n  }\n\n  let currentValue = settings[currentType] || '';\n\n  if (currentValue === '全部') {\n    currentValue = '';\n  }\n\n  let values = currentValue ? currentValue.split(',') : [];\n\n  const index = values.indexOf(value);\n  if (index > -1) {\n    values.splice(index, 1);\n  } else {\n    values.push(value);\n  }\n\n  if (values.length === 0) {\n    settings[currentType] = '全部';\n  } else {\n    settings[currentType] = values.join(',');\n  }\n\n  updateHiddenSettings();\n  renderSourceOptions();\n}\n\nfunction updateSelectedInfo() {\n  const key = \\`搜索\\${currentType}\\`;\n  const sources = SOURCES[key] || [];\n  const savedValue = settings[currentType] || '';\n\n  if (savedValue === '全部') {\n    document.getElementById('selectedCount').textContent = '全部';\n  } else {\n    const count = savedValue ? savedValue.split(',').length : 0;\n    document.getElementById('selectedCount').textContent = count;\n  }\n}\n\ndocument.addEventListener('DOMContentLoaded', () => {\n  let savedSettings = {};\n  try {\n    const jsonEl = document.getElementById('settingsJson');\n    if (jsonEl) {\n      savedSettings = JSON.parse(jsonEl.textContent);\n    }\n  } catch (e) {\n    console.error('读取初始设置失败:', e);\n  }\n\n  settings = {\n    '小说': savedSettings['小说'] || '全部',\n    '听书': savedSettings['听书'] || '全部',\n    '漫画': savedSettings['漫画'] || '全部',\n    '短剧': savedSettings['短剧'] || '全部',\n    '搜索模式': savedSettings['搜索模式'] || '小说',\n    '显示图片': savedSettings['显示图片'] || 'true',\n    '段评开关': savedSettings['段评开关'] || 'true',\n    '完整简介': savedSettings['完整简介'] || 'false',\n    '同步书架': savedSettings['同步书架'] || 'false',\n    '网络模式': savedSettings['网络模式'] || '服务器',\n    '强制搜索': savedSettings['强制搜索'] || 'false',\n    '目录显示来源': savedSettings['目录显示来源'] || 'true'\n  };\n\n  initBookSourceSettings();\n  setupBookSourceSettings();\n  renderSourceOptions();\n});\n<\\\/script>\n<\\\/body>\n<\\\/html>\n    `\n        let body;\n        try {\n            body = await flutterBridge.startBrowser(`data:text\/html;base64,${await flutterBridge.base64encode(html)}`, '光遇书源设置');\n        } catch (e) {\n            flutterBridge.showToast(\"设置出错~\");\n            return;\n        }\n\n        let settings = {};\n        try {\n            const getSpanText = (spanId) => {\n                const regex = new RegExp('id=\"' + spanId + '\"\\s*>\\s*([^<]*?)\\s*<\\\/span>');\n                const match = body.match(regex);\n                return match ? match[1].trim() : \"\";\n            };\n            settings = {\n                '搜索模式': getSpanText('searchModeValue'),\n                '显示图片': getSpanText('showImageSwitchValue'),\n                '段评开关': getSpanText('showParaSwitchValue'),\n                '完整简介': getSpanText('showFullDescSwitchValue'),\n                '同步书架': getSpanText('syncBookshelfSwitchValue'),\n                \/\/ '网络模式': getSpanText('networkModeValue'),\n                '强制搜索': getSpanText('forceSearchSwitchValue'),\n                '目录显示来源': getSpanText('showSourceInTocValue'),\n                '小说': getSpanText('novelSource'),\n                '听书': getSpanText('audioSource'),\n                '漫画': getSpanText('comicSource'),\n                '短剧': getSpanText('dramaSource'),\n            };\n\n        } catch (e) {\n            settings = {\n                '搜索模式': '小说',\n                '显示图片': 'true',\n                '段评开关': 'true',\n                '完整简介': 'true',\n                '同步书架': 'false',\n                \/\/ '网络模式': '服务器',\n                '强制搜索': 'false',\n                '目录显示来源': 'false',\n                '小说': '全部',\n                '听书': '全部',\n                '漫画': '全部',\n                '短剧': '全部',\n            };\n        }\n        if (settings.搜索模式 !== \"\") {\n            const settingsText = Object.keys(settings).map(key => `${key}: ${settings[key]}`).join('\\n');\n            flutterBridge.showToast(settingsText);\n            await setVariable(\"更多设置\", settings, false)\n        } else {\n            flutterBridge.showToast(\"需要点击右上角√才能应用设置哦~\")\n        }\n\n    }\n\n\n    \/\/返回http开头的则任务登录链接会跳webview，其他的会按照json解析显示弹窗\n    async function getloginurl() {\n        return JSON.stringify([\n            {\n                \"name\": \"🔭 切换线路\",\n                \"type\": \"button\",\n                \"action\": \"getServerSettings()\"\n            }, {\n                \"name\": \"⚙️ 书源设置\",\n                \"type\": \"button\",\n                \"action\": \"getHtmlSettings()\"\n            }, {\n                \"name\": \"🗨 段评设置\",\n                \"type\": \"button\",\n                \"action\": \"getSvgSettings()\"\n            }, {\n                \"name\": \"邮箱\",\n                \"type\": \"text\"\n            }, {\n                \"name\": \"密码\",\n                \"type\": \"password\"\n            }, {\n                \"name\": \"🔅登录账号\",\n                \"type\": \"button\",\n                \"action\": \"login(true)\"\n            }, {\n                \"name\": \"🔐注册账号\",\n                \"type\": \"button\",\n                \"action\": \"register()\"\n            }, {\n                \"name\": \" 🔚 退出登录\",\n                \"type\": \"button\",\n                \"action\": \"logout()\"\n            },\n            {\n                \"name\": \"🏝用户后台\",\n                \"type\": \"button\",\n                \"action\": \"user()\"\n            }, {\n                \"name\": \"📴 清理设备\",\n                \"type\": \"button\",\n                \"action\": \"clearDevice()\"\n            }, {\n                \"name\": \"🪪 查看信息\",\n                \"type\": \"button\",\n                \"action\": \"checkStatus()\"\n            }, {\n                \"name\": \"🌷求打赏\",\n                \"type\": \"button\",\n                \"action\": \"vip()\"\n            }, {\n                \"name\": \"🎈永久发布页\",\n                \"type\": \"button\",\n                \"action\": \"fb()\"\n            }, {\n                \"name\": \"✳️ 更新配置\",\n                \"type\": \"button\",\n                \"action\": \"getCloudSettings(true)\"\n            }, {\n                \"name\": \"⛔‼️ 清空还原设置\",\n                \"type\": \"button\",\n                \"action\": \"deleteVariable()\"\n            }\n        ]);\n    }\n\n    async function register() {\n        let url = await BaseUrl() + \"\/login\"\n        await flutterBridge.startBrowser(url, \"光遇聚合\")\n    }\n\n    async function user() {\n        let token = await getToken();\n        if (String(token).length < 10) {\n            flutterBridge.showToast('🤔请先登陆');\n            return;\n        }\n        flutterBridge.startBrowser(await BaseUrl() + '\/user', '光遇看书用户后台', {\"cookie\": `qttoken=${token}`});\n    }\n\n    async function vip() {\n        let token = await getToken();\n        if (String(token).length < 10) {\n            flutterBridge.showToast('🤔请先登陆');\n            return;\n        }\n        flutterBridge.startBrowser(await BaseUrl() + '\/coffee', '光遇看书打赏', {\"cookie\": `qttoken=${token}`});\n    }\n\n    async function fb() {\n        flutterBridge.startBrowser('http:\/\/vip.gyks.cf', '发布页');\n    }\n\n    async function logout() {\n        let hostsbk = (await getVariable('云端配置'))['hosts'] || hosts;\n        for (let h of hostsbk) {\n            await cookie.remove(h);\n        }\n        await cookie.remove('fanqienovel.com');\n        flutterBridge.showToast('已退出登陆')\n    }\n\n    async function clearDevice() {\n        const token = await getToken();\n        if (String(token).length < 10) {\n            flutterBridge.showToast('\\n🤔请先登陆');\n            return;\n        }\n        let res = await request(`${await BaseUrl()}\/clear`, \"POST\");\n        flutterBridge.showToast(res.code === 0 ? \"📴设备清除成功\" : res.msg)\n    }\n\n    function formatDate(timestamp) {\n        const date = new Date(timestamp * 1000);\n        return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;\n    }\n\n    function isVips(res) {\n        const vipEndTime = res.vip_end_time;\n        const isVipValue = res.is_vip;\n\n        let vipType;\n        if (isVipValue == 0) {\n            return '普通用户';\n        } else if (isVipValue == 1) {\n            vipType = 'VIP';\n        } else {\n            vipType = 'SVIP';\n        }\n\n        if (!vipEndTime || vipEndTime == 0) {\n            return `${vipType} (已过期)`;\n        }\n\n        const currentTime = Math.floor(Date.now() \/ 1000);\n        const remainingDays = Math.ceil((vipEndTime - currentTime) \/ (24 * 60 * 60));\n\n        if (currentTime > vipEndTime) {\n            return `${vipType} (已过期)`;\n        }\n\n        if (remainingDays <= 7) {\n            return `${vipType} 剩余${remainingDays}天`;\n        }\n\n        if (vipEndTime >= 1912946812) {\n            return `${vipType} (永久)`;\n        }\n\n        return `${vipType}(${formatDate(vipEndTime)})`;\n    }\n\n    async function checkStatus() {\n        const token = await getToken();\n        if (String(token).length < 10) {\n            flutterBridge.showToast('🤔请先登陆');\n            return;\n        }\n        let result = JSON.parse(await cache.getLoginInfo());\n        flutterBridge.showToast('♻️查询中...');\n\n        try {\n            const res = await request(`${await BaseUrl()}\/user_api`, 'POST');\n\n            if (res.id === undefined) {\n                throw new Error(res.msg || '获取用户信息失败');\n            }\n\n            result.邮箱 = res.email;\n            await cache.putLoginInfo(JSON.stringify(result));\n\n            let devices = 0;\n            try {\n                devices = res.device ? Object.keys(JSON.parse(res.device)).length : 0;\n            } catch (e) {\n                devices = res.device ? 1 : 0;\n            }\n\n            const isVip = isVips(res);\n            const today = formatDate(new Date()).slice(0, 10);\n            const lastReadDate = res.last_read_time ? formatDate(res.last_read_time).slice(0, 10) : '';\n            const todayReadCount = today === lastReadDate ? res.day_read_count : 0;\n\n            const tips = `\n${await BaseUrl()}\n┏┅┅┅┅┅┅┱┄┄┄┄┄┄┄┄┄┄┐\n　 🧢昵称 　　　　${(res.nickname || '未设置').padEnd(20, '\\t')}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n　✉️邮箱　　　　${res.email.replace(\/(.{3}).*?@\/, '$1***@').padEnd(20, '\\t')}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n　🔑密钥　　　　${(`${res.user_key.substring(0, 4)}***${res.user_key.slice(-4)}`).padEnd(20, '\\t')}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n　📅注册时间　　${formatDate(res.register_time).padEnd(20, '\\t')}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n🗒️今日阅读　　${todayReadCount.toString().padEnd(20, '\\t')}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n📚累计阅读　　${res.all_read_count.toString().padEnd(20, '\\t')}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n🕓最后阅读　　${(res.last_read_time ? formatDate(res.last_read_time) : '未阅读').padEnd(20, '\\t')}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n📱在线设备　　${devices.toString().padEnd(20, '\\t')}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n　👑会员状态　　${isVip.padEnd(20, '\\t')}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n　🚫封禁状态　　${res.is_banned ? '已封禁' : '正常　'}　　　　　　　\n┗┅┅┅┅┅┅┹┄┄┄┄┄┄┄┄┄┄┘\n`;\n\n            flutterBridge.showToast(tips);\n\n        } catch (e) {\n            flutterBridge.showToast(`检测登录失败\\n${e.message}`);\n        }\n    }\n\n    async function setAllCookies(ck) {\n        let hostsbk = (await getVariable('云端配置'))['hosts'] || hosts;\n        for (let h of hostsbk) {\n            await cookie.set(h, ck);\n        }\n    }\n\n\n    \/\/如果登录 url 为非 http 开头的弹窗界面，每次修改完弹窗就会执行此函数\n    async function login(flag) {\n        const token = await getToken();\n        if (String(token).length > 10) {\n            flutterBridge.showToast(flag ? '当前✅️已登录，请🚫退出登录后重新登录' : '✅️已登录');\n            return true;\n        }\n        let result = JSON.parse(await cache.getLoginInfo())\n        if (flag === undefined) {\n\n        } else {\n            await cache.putLoginInfo(JSON.stringify(result));\n            flutterBridge.showToast(\"💞正在登录中...\")\n        }\n        let email = result.邮箱;\n        let pwd = result.密码;\n\n        if (!email || !pwd) {\n            flutterBridge.showToast()('请先输入账号密码！');\n            return false;\n        }\n        if (!email.includes('@')) {\n            flutterBridge.showToast('请输入正确的邮箱格式！');\n            return false;\n        }\n\n        try {\n            let url = `${await BaseUrl()}\/login_api`\n            let response = await request(url, \"POST\", {\n                register_email: email,\n                password: pwd\n            })\n            if (response.code === 0) {\n                await setAllCookies(`qttoken=${response.key}`);\n                flutterBridge.showToast(\"✅️登录成功\");\n                return true;\n            } else {\n                let msg = response.msg;\n                if (msg.includes('多次登录失败')) {\n                    flutterBridge.showToast('💔多次登录失败，请检查账号密码，切换线路后重试或等2小时后重试！');\n                } else {\n                    flutterBridge.showToast('💔' + (msg || \"登录失败，请重试！\"));\n                }\n                return false;\n            }\n        } catch (error) {\n            flutterBridge.showToast('💔登录失败，服务器错误，请更换线路后重试！\\n' + error);\n            return false;\n        }\n    }\n\n    async function pay(bookurl, url) {\n\n    }\n\n    \/\/ url 为图片的url,如果需要传递参数可以在图片后接json字符串，例如：http:\/\/127.0.0.1,{'headers':{'a':'b'}}\n    \/\/图片解密，image 为加密的图片的base64，执行的js必须是字符串所以这参数只能base64转码\n    \/\/这个函数得返回byteList List<int> ,并且能直接被Uint8List.fromList(byteList)接受\n    async function imagedecrypt(url, image) {\n\n    }\n\n    \/\/ 当调用startBrowserWithShouldOverrideUrlLoading时必须有此函数\n    \/\/ url 为每次打开的 url\n    \/\/ 返回 false 则会取消打开这个网页\n    async function shouldOverrideUrlLoading(url) {\n        \/\/ flutterBridge.showToast(url);\n\n        const base_url = await BaseUrl();\n\n        function extractBookId(url) {\n            if (url.includes('huanmengacg')) {\n                return url;\n            }\n\n            const patterns = [\n                \/[?&]book_id=([^&]+)\/,\n                \/page\\\/(\\d+)\/,\n                \/shuku\\\/(\\d+_\\d+|\\d+)(?:-\\d+)?\/,\n                \/query\\\/(\\d+)\/,\n                \/book\\\/(\\d+)\/,\n                \/album\\\/(\\d+)\/,\n                \/reader\\\/(\\d+)\/,\n                \/book-detail\\\/(\\d+)\/,\n                \/sourceId=([^&\\s]+)\/\n            ];\n\n            for (const pattern of patterns) {\n                const match = url.match(pattern);\n                if (match) {\n                    return match[1];\n                }\n            }\n\n            return null;\n        }\n\n        const urlPatterns = [\n            \/book_id=\\d+\/,\n            \/\\\/page\\\/\\d+\/,\n            \/shuku\\\/(\\d+_\\d+|\\d+)(?:-\\d+)?\/,\n            \/query\\\/(\\d+)\/,\n            \/book\\\/(\\d+)\/,\n            \/album\\\/(\\d+)\/,\n            \/reader\\\/(\\d+)\/,\n            \/book-detail\\\/(\\d+)\/,\n            \/sourceId=[^&\\s]+\/\n        ];\n\n        const needsProcessing = urlPatterns.some(pattern => pattern.test(url)) ||\n            url.includes('online_detail') ||\n            url.includes('book\/info');\n\n        if (needsProcessing) {\n            const bookId = extractBookId(url);\n\n            const sourceConfig = {\n                shuku: {source: '七猫'},\n                tadu: {source: '塔读'},\n                shuqi: {source: '书旗'},\n                ximalaya: {source: '喜马拉雅', tab: '听书'},\n                lrts: {source: '懒人听书', tab: '听书'},\n                qq: {source: 'QQ阅读'},\n                idejian: {source: '得间'},\n                sourceId: {source: '淘小说'},\n                huanmengacg: {source: '幻梦轻小说', tab: '小说'},\n                online_detail: {useOriginalUrl: true, replace: {'online_detail': 'detail'}}\n            };\n\n            let url2;\n            let matched = false;\n\n            for (const [key, config] of Object.entries(sourceConfig)) {\n                if (url.includes(key)) {\n                    if (config.useOriginalUrl) {\n                        url2 = url;\n                        for (const [oldStr, newStr] of Object.entries(config.replace || {})) {\n                            url2 = url2.replace(oldStr, newStr);\n                        }\n                    } else {\n                        const encodedBookId = encodeURI(await flutterBridge.base64encode(bookId));\n                        url2 = `${base_url}\/detail?book_id=${encodedBookId}&source=${config.source}`;\n                        if (config.tab) {\n                            url2 += `&tab=${config.tab}`;\n                        }\n                    }\n                    matched = true;\n                    break;\n                }\n            }\n\n            if (!matched) {\n                const encodedBookId = encodeURI(await flutterBridge.base64encode(bookId));\n                url2 = `${base_url}\/detail?book_id=${encodedBookId}&source=番茄`;\n            }\n\n            url2 = url2.replace(\/%3D\/g, '');\n            flutterBridge.showToast(\"添加书籍到书架：\" + url2);\n            flutterBridge.addbook(url2);\n            return false;\n        }\n\n        return true;\n    }\n\n\n    \/\/帮助内容，开启帮助后点击帮助将会显示函数反馈的内容\n    \/\/当前函数有三种反馈方式\n    \/\/1. http 开头的链接\n    \/\/2. 纯文字\n    \/\/3. @html: 开头的 html 内容\n    async function gethelp() {\n        return \"\";\n    }\n\n<\/script>\n\n<\/html>\n","login":true,"lastUpdateTime":"1780166808372"}]