光遇聚合
http://vip.gyks.cf#光遇聚合
晴天 (8653)05/31 02:46
优化代码,修复bug
{
"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(\/&\/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"
}