看书阁
看书阁
haharen333 (12965)7天前
更新日志请点击登录,更新书源中查看
基础使用教程:
1、搜索:
1)基础搜索:在发现页长按该书源,点击搜索,直接搜,搜索范围(全部来源小说)
2)进阶搜索:在登陆-书源设置中选择指定模式/来源,可搜索指定模式/来源内容
3)快捷搜索:使用特殊关键词(x,t,m,d,@)搜索
x:书名@来源 如(x:十日终焉@番茄)
t:书名@来源 如(t:十日终焉@番茄)
m:书名@来源 如(m:十日终焉@番茄)
d:书名@来源 如(d:十日终焉@番茄)
其中:x表示小说(可省略),t表示听书,m表示漫画,d表示短剧
冒号支持中文英文
2、发现页:
在书源设置中选择指定模式或者单个来源,然后刷新发现页即可
3、更多使用方式长按发现页书源选择登录一目了然
4、用户后台有视频教程
{
"bookSourceComment": "更新日志请点击登录,更新书源中查看\n\n基础使用教程:\n1、搜索:\n 1)基础搜索:在发现页长按该书源,点击搜索,直接搜,搜索范围(全部来源小说)\n 2)进阶搜索:在登陆-书源设置中选择指定模式\/来源,可搜索指定模式\/来源内容\n 3)快捷搜索:使用特殊关键词(x,t,m,d,@)搜索\n x:书名@来源 如(x:十日终焉@番茄)\n t:书名@来源 如(t:十日终焉@番茄)\n m:书名@来源 如(m:十日终焉@番茄)\n d:书名@来源 如(d:十日终焉@番茄)\n 其中:x表示小说(可省略),t表示听书,m表示漫画,d表示短剧\n 冒号支持中文英文\n \n2、发现页:\n 在书源设置中选择指定模式或者单个来源,然后刷新发现页即可\n \n3、更多使用方式长按发现页书源选择登录一目了然\n4、用户后台有视频教程\n ",
"bookSourceGroup": "看书阁",
"bookSourceName": "看书阁",
"bookSourceType": 0,
"bookSourceUrl": "看书阁",
"bookUrlPattern": "https?:\\\/\\\/(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z0-9-]+(?::\\d+)?\\\/detail.*",
"customButton": false,
"customOrder": 1,
"enabled": true,
"enabledCookieJar": false,
"enabledExplore": true,
"eventListener": false,
"exploreUrl": "<js>\nvar open_argument = source.getVariable();\nvar base_url = getArguments(open_argument, 'server') || '';\nvar ms = getArguments(open_argument, 'tone_id') || '';\nvar source_type = getArguments(open_argument, 'source_type') || '男频';\nvar tab = getArguments(open_argument, 'find_tab') || getArguments(open_argument, 'tab') || '小说';\nvar sources = getArguments(open_argument, 'sources') || '番茄';\n\n\nfunction checkEnv() {\n let isModified = false;\n try {\n new Packages.io.legato.kazusa.utils.TimeoutCancellationException('');\n isModified = true;\n } catch (e) {\n isModified = typeof source.loginUi == 'function' ? false : true;\n }\n\n return isModified;\n}\n\nvar islyc = checkEnv();\nlet device_type = '安卓';\ntry {\n\t try {\n\t java.qread();\n\t device_type =\"轻阅读\";\n\t } catch {\n\t \tjava.deviceID();\n device_type = \"苹果\";\n\t \t}\n \n} catch (e) {\n try {\n java.androidId();\n device_type = \"安卓\";\n } catch (e) {\n device_type = \"安卓\";\n }\n}\n\nif (islyc && device_type == '安卓'){\n\tsources = getArguments(open_argument, 'find_source') || sources;\n\tsetArguments('find_source', sources);\n\t}\nvar sdtoken;\ntry {\n var loginInfoMap = source.getLoginInfoMap ? source.getLoginInfoMap() : {};\n sdtoken = String(loginInfoMap['手动填写番茄token(可不填)'] || '');\n} catch (e) {\n sdtoken = '';\n}\n\nvar rawCookie = getFanqieCookie() || sdtoken;\nvar match = rawCookie.match(\/sessionid=[^;]+\/);\nvar fqcookie = match ? match[0] : '';\nvar fqssionid = '';\nif (!fqcookie && (sources == '番茄' || sources == '全部')) {\n java.toast('您还未登陆番茄账号,无法同步数据哦!');\n} else {\n fqssionid = getSessionId(fqcookie)\n}\nvar fqsjurl = base_url + \"\/bookshelf?page={{page}}&ssionid=\" + fqssionid;\nvar fqtjurl = base_url + \"\/fqrecommend?page={{page}}&ssionid=\" + fqssionid;\nvar fqlsurl = base_url + \"\/fqhistory?page={{page}}&ssionid=\" + fqssionid;\n\nvar groupDatas = [];\nvar infoData = [];\n\n\nvar hasValidCookie = fqcookie.length > 0;\n\nif (hasValidCookie && (sources == '番茄' || sources == '全部')) {\n function groupQuery() {\n try {\n var url = base_url + \"\/group_name?ssionid=\" + fqssionid;\n var res = java.ajax(url);\n var response = JSON.parse(res);\n\n if (!(response && response.data)) {}\n\n response.data.forEach(function(group) {\n var keys = Object.keys(group);\n if (keys.length > 0) {\n var key = keys[0];\n var value = group[key];\n if (value && value.length) {\n var option = {\n \"method\": \"POST\",\n \"body\": {\n \"book_ids\": value,\n \"page\": \"{{page}}\"\n }\n };\n groupDatas.push({\n title: key,\n url: base_url + \"\/bookshelf,\" + JSON.stringify(option),\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 0.45\n }\n });\n }\n }\n });\n\n if (groupDatas.length % 2 !== 0) {\n groupDatas.push({\n title: \"--\",\n url: \"\",\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 0.45\n }\n });\n }\n } catch (e) {\n java.longToast(\"番茄登录过期,已隐藏番茄书架\" + fqssionid);\n }\n }\n\n try {\n java.longToast(\"正在加载分组数据...\");\n var userUrl = base_url + \"\/fquser?ssionid=\" + fqssionid;\n var userRes = java.ajax(userUrl);\n var userData = JSON.parse(userRes);\n\n var userName = (userData && userData.data && userData.data.name) ? userData.data.name : '未知用户';\n if (!userName.includes('未知用户')) {\n infoData = [{\n title: \"番茄个人中心\",\n url: fqsjurl,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 0.45\n }\n }, {\n title: \"个性推荐(番茄)\",\n url: fqtjurl,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 0.45\n }\n }, {\n title: \"历史阅读(番茄)\",\n url: fqlsurl,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 0.45\n }\n }];\n }\n groupQuery();\n } catch (e) {\n java.longToast(\"番茄登录过期,已隐藏番茄书架\");\n }\n}\n\nvar style_list = [];\ntry {\n var durl = `${base_url}\/discovestyle?source=${sources}&source_type=${source_type}&tab=${tab}`;\n var res = java.ajax(durl);\n var result = JSON.parse(res);\n style_list = result.data || [];\n if (result.msg) {\n java.toast(result.msg);\n }\n} catch (e) {\n java.toast(\"发现样式获取失败\");\n}\nlet qtcookie = cookie.getCookie(base_url);\nlet qtop = {\n method: \"GET\",\n headers: {\n cookie: qtcookie\n },\n};\n\n\n\nqtop = JSON.stringify(qtop);\nlet qtsjurl = base_url + '\/get_book_shelf,' + qtop;\n\nlet qtsj = [];\n\nif (islyc && device_type == '安卓') {\n try {\n qtsj.push(createFilter(\n \"线路\",\n host,\n base_url,\n \"server\",\n 1\n ));\n qtsj.push(createFilter(\n \"模式\",\n [\"小说\", \"听书\", \"短剧\", \"漫画\"],\n tab,\n \"find_tab\",\n 0.33\n ));\n qtsj.push(createFilter(\n \"频道\",\n [\"男频\", \"女频\"],\n source_type,\n \"source_type\",\n 0.33\n ));\n let source_list = ['番茄', '七猫', '书旗', '塔读', '江湖', '小米', '米读', 'QQ阅读', '酷我小说','幻梦轻小说', '西瓜小说', '69书吧', '顶点', '爱下电子书', '伪69', '超会专属小说', '番薯小说', '阅友小说', '笔趣阁22', '微山书院', '中华典藏', '老福特'];\n if (tab == '听书') {\n source_list = ['番茄', '七猫', '喜马拉雅', '酷我'];\n }\n if (tab == '漫画') {\n source_list = ['全面漫画', '番茄', '包子漫画', '歪瑞古德', 'G社漫画'];\n }\n if (tab == '短剧') {\n source_list = ['番茄', '七猫', '河马', '旺旺短剧'];\n }\n qtsj.push(createFilter(\n \"平台\",\n source_list,\n sources,\n \"find_source\",\n 1\n ));\n const excludeTitles = ['点击登录可切换来源', '切换后长按刷新即可'];\n style_list = style_list.filter(item => !excludeTitles.includes(item.title));\n } catch {}\n} else {\n\tif (device_type == '安卓'){\n\tqtsj.push({\n \"title\": \"下载阅读Σ最新测试版\",\n \"url\": '{\\{java.longToast(\"本书源推荐使用最新版洛娅橙改版阅读Σ\\\\n请安装后重新导入书源\");java.startBrowser(\"https:\/\/loyc.xyz\/c\/legado.html#download\",\"下载阅读Σ最新测试版\");}}',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 1\n }\n })}\n\t}\n\nqtsj.push({\n title: \"大灰狼个人中心\",\n url: qtsjurl,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 0.45\n }\n})\n\nvar finalData = infoData.concat(groupDatas, style_list);\nfinalData = qtsj.concat(finalData);\nJSON.stringify(finalData);\n<\/js>",
"header": "{ \"User-Agent\":\"Mozilla\/5.0 (Linux; Android 6.0; Nexus 5 Build\/MRA58N) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/138.0.0.0 Mobile Safari\/537.36 Edg\/138.0.0.0\" }",
"jsLib": "var host = [\n 'http:\/\/219.154.201.122:5006',\n 'https:\/\/api.langge.cf',\n 'https:\/\/v2.czyl.cf',\n 'https:\/\/20.langge.tk',\n 'https:\/\/v4.czyl.cf', \n 'https:\/\/v5.czyl.cf',\n 'https:\/\/v7.czyl.cf',\n 'https:\/\/v8.czyl.cf',\n 'https:\/\/v9.czyl.cf',\n 'https:\/\/v10.czyl.cf'\n];\n\nfunction getArguments(open_argument, key) {\n try {\n open_argument = JSON.parse(open_argument);\n } catch (e) {\n open_argument = {\n tab: \"小说\",\n tone_id: \"默认音色\",\n server: host[0],\n sources: \"全部\",\n source_type: \"男频\",\n };\n }\n if (key) {\n return open_argument[key];\n } else {\n return open_argument;\n }\n}\n\nfunction setArguments(key, value) {\n const {\n source\n } = this;\n let open_argument = source.getVariable();\n open_argument = getArguments(open_argument, '');\n open_argument[key] = value;\n open_argument = JSON.stringify(open_argument);\n source.setVariable(open_argument);\n return open_argument;\n}\n\nfunction decrypt(Text) {\n return Text;\n}\n\nfunction paraForAndroid(content, sources, ydv) {\n let {\n java,\n cache,\n source\n } = this;\n let plcolor = getArguments(source.getVariable(), \"plcolor\");\n if (!plcolor) {\n plcolor = \"#A9A9A9\";\n }\n let tccolor = getArguments(source.getVariable(), \"tccolor\");\n if (!tccolor) {\n tccolor = \"#ffffff\";\n }\n\n const createSvg = this.createSvg.bind(this);\n\n return content.replace(\/<p>(.*?)(?:<comment ident=\"([^\"]*)\" count=\"([^\"]*)\" \\\/>)?<\\\/p>\/g,\n (match, text, url, count) => {\n if (url && count) {\n const click = 0;\n cache.putMemory(url, click);\n const encodedUrl = url;\n return `<p>${text}<img src=\"${createSvg(count, plcolor,tccolor,encodedUrl,sources,ydv)}\"><\/p>`;\n } else {\n return `<p>${text}<\/p>`;\n }\n }\n );\n}\n\n\nfunction showCmt(url, sources, ydv) {\n let {\n java,\n cache\n } = this;\n const currentTime = Date.now();\n const click = cache.getFromMemory(url);\n let isqread = false;\n try {\n java.qread();\n isqread = true;\n } catch (e) {}\n if (click < 1 && !isqread && !ydv) {\n cache.putMemory(url, click + 1);\n return;\n } else {\n if (isqread) {\n java.startBrowserDp(url, sources + '段评');\n } else if (ydv) {\n let paradata = new Date().toLocaleDateString();\n let htmlContent = cache.get(paradata);\n if (!htmlContent || String(htmlContent).indexOf(\"评论\") == -1) {\n java.longToast(`\\n${paradata} 初始化段评页面,请稍等~`);\n try {\n htmlContent = java.ajax(url);\n } catch {\n htmlContent = paraHtml(url);\n }\n \n cache.put(paradata, htmlContent);\n java.longToast(`\\n${paradata} 初始化段评页面成功~`);\n }\n java.showBrowser(\n url,\n htmlContent,\n `window.java=java;`,\n JSON.stringify({\n \"expandedCornersRadius\": 20,\n \"dismissOnTouchOutside\": true,\n \"isDraggable\": true,\n \"shouldDimBackground\": true,\n \"backgroundDimAmount\": 0.5,\n \"hardwareAccelerated\": true,\n \"isNestedScrollingEnabled\": true,\n \"isGestureInsetBottomIgnored\": true,\n \"setFitToContents\": true,\n \"isHideable\": true,\n \"heightPercentage\": 0.8\n })\n );\n } else {\n java.startBrowser(url, sources + '段评');\n }\n }\n}\n\n\n\nfunction createSvg(number, color, tccolor, encodedUrl, sources, ydv) {\n var displayText = number > 99 ? \"99+\" : number.toString();\n var loginInfoMap = {};\n\n if (this.source && typeof this.source.getLoginInfoMap == 'function') {\n loginInfoMap = this.source.getLoginInfoMap() || {};\n }\n\n var bubbleStyle = String(getArguments(this.source.getVariable(), \"pstyle\") || loginInfoMap['段评气泡样式(0-4)'] || '0');\n var svg;\n if (bubbleStyle == '0') {\n svg = `<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"100\" height=\"90\" viewBox=\"0 0 25 17\" fill=\"none\" style=\"color: ${color}; opacity: 1;\">\n <path d=\"M24 14.5v-12a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v7.528a2 2 0 0 1-.211.894l-2.065 4.13a1 1 0 0 0 .894 1.448H22a2 2 0 0 0 2-2z\" stroke=\"currentColor\" style=\"stroke: ${color}; stroke-opacity: 0.5; stroke-width: 1px; stroke-linejoin: round; fill: ${tccolor}; fill-opacity: 0.5;\"><\/path>\n <text x=\"13.5\" y=\"12\" text-anchor=\"middle\" alignment-baseline=\"auto\" font-size=\"10\" fill=\"currentColor\" font-family=\"Arial, sans-serif\" font-weight=\"500\" style=\"fill: ${color}; opacity: 1;\">${displayText}<\/text>\n <\/svg>`;\n } else if (bubbleStyle == '1') {\n svg = `<svg t=\"1760002253572\" class=\"icon\" viewBox=\"-150 0 1224 1224\" version=\"1.1\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\" p-id=\"1490\" width=\"800\" height=\"800\">\n<g transform=\"rotate(90 512 512) scale(1 1.2)\">\n<path d=\"M512 938.666667c-27.733333 0-55.466667-12.8-72.533333-34.133334l-46.933334-55.466666c-14.933333-17.066667-34.133333-25.6-57.6-25.6h-110.933333c-76.8 0-138.666667-61.866667-138.666667-138.666667V224C85.333333 147.2 147.2 85.333333 224 85.333333h573.866667c76.8 0 138.666667 61.866667 138.666666 138.666667v460.8c0 76.8-61.866667 138.666667-138.666666 138.666667h-108.8c-21.333333 0-42.666667 8.533333-55.466667 25.6l-49.066667 55.466666c-17.066667 21.333333-44.8 34.133333-72.533333 34.133334zM224 149.333333C183.466667 149.333333 149.333333 183.466667 149.333333 224v460.8c0 40.533333 34.133333 74.666667 74.666667 74.666667h110.933333c40.533333 0 78.933333 17.066667 104.533334 49.066666l46.933333 55.466667c6.4 6.4 14.933333 10.666667 25.6 10.666667 8.533333 0 19.2-4.266667 23.466667-10.666667l49.066666-55.466667c25.6-29.866667 64-46.933333 104.533334-46.933333h108.8c40.533333 0 74.666667-34.133333 74.666666-74.666667V224C874.666667 183.466667 840.533333 149.333333 800 149.333333h-576z\" fill=\"${color}\" p-id=\"1530\"><\/path> \n<\/g>\n <text x=\"460\" y=\"650\" font-family=\"Arial, sans-serif\" text-anchor=\"middle\" dominant-baseline=\"middle\" font-size=\"400\" fill=\"${color}\">${displayText}<\/text>\n <\/svg>\n`;\n } else if (bubbleStyle == '2') {\n svg = `<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"100\" height=\"90\" viewBox=\"6 10 88 76\" style=\"color: ${color}; opacity: 1;\">\n <path d=\"M12,12 L88,12 Q92,12 92,16 L92,68 Q92,72 88,72 L28,72 L12,84 L12,72 Q8,72 8,68 L8,16 Q8,12 12,12 Z\" stroke=\"currentColor\" stroke-width=\"4\" stroke-linejoin=\"round\" style=\"stroke: ${color}; stroke-opacity: 0.5; stroke-width: 2.5px; stroke-linejoin: round; fill: ${tccolor}; fill-opacity: 0.5;\"><\/path>\n <text x=\"50\" y=\"60\" font-family=\"Arial, sans-serif\" text-anchor=\"middle\" font-size=\"45\" fill=\"currentColor\" font-weight=\"500\" alignment-baseline=\"auto\" style=\"fill: ${color}; opacity: 1;\">${displayText}<\/text>\n <\/svg>`;\n } else if (bubbleStyle == '3') {\n svg = `<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"100\" height=\"90\" viewBox=\"0 0 25 17\" fill=\"none\" style=\"color: ${color}; opacity: 1;\">\n <rect x=\"0.5\" y=\"0.5\" width=\"24\" height=\"16\" rx=\"2.5\" ry=\"2.5\" stroke=\"currentColor\" style=\"stroke: ${color}; stroke-opacity: 0.5; stroke-width: 1px; stroke-linejoin: round; fill: ${tccolor}; fill-opacity: 0.5;\"><\/rect>\n <text x=\"12.5\" y=\"12\" text-anchor=\"middle\" alignment-baseline=\"auto\" font-size=\"10\" fill=\"currentColor\" font-family=\"Arial, sans-serif\" font-weight=\"500\" style=\"fill: ${color}; opacity: 1;\">${displayText}<\/text>\n <\/svg>`;\n } else if (bubbleStyle == '4') {\n svg = `<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"100\" height=\"90\" viewBox=\"0 0 25 17\" fill=\"none\" style=\"color: ${color}; opacity: 1;\">\n <path stroke=\"currentColor\" stroke-width=\"1.00\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-miterlimit=\"4.00\" style=\"stroke: ${color}; stroke-opacity: 0.5; stroke-width: 1px; stroke-linejoin: round; fill: ${tccolor}; fill-opacity: 0.5;\" d=\"M 7.5 0.5 L 16.5 0.5 A 7 7 0 0 1 23.5 7.5 L 23.5 7.5 A 7 7 0 0 1 16.5 14.5 L 8.5 14.5 L 5.192 16.5 L 5.5 14.5 A 7 7 30 0 1 0.5 7.5 L 0.5 7.5 A 7 7 0 0 1 7.5 0.5 Z\"\/>\n <text fill=\"${color}\" font-family=\"Arial, sans-serif\" font-size=\"10\" font-weight=\"500\" text-anchor=\"middle\" dy=\"0.35em\" x=\"12.00\" y=\"7.50\">${displayText}<\/text>\n <\/svg>`;\n } else if (bubbleStyle == '5') {\n displayText = number > 99 ? \"99\" : number.toString();\n svg = `<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"100\" height=\"90\" viewBox=\"0 0 25 17\" fill=\"none\" style=\"color: ${color}; opacity: 1;\">\n <circle cx=\"12.5\" cy=\"8.5\" r=\"8\" stroke=\"currentColor\" style=\"stroke: ${color}; stroke-opacity: 0.5; stroke-width: 1px; stroke-linejoin: round; fill: ${tccolor}; fill-opacity: 0.5;\"><\/circle>\n <text x=\"13\" y=\"12\" text-anchor=\"middle\" alignment-baseline=\"auto\" font-size=\"10\" fill=\"currentColor\" font-family=\"Arial, sans-serif\" font-weight=\"500\" style=\"fill: ${color}; opacity: 1;\">${displayText}<\/text>\n <\/svg>`;\n } else {\n svg = `<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"100\" height=\"90\" viewBox=\"0 0 25 17\" fill=\"none\" style=\"color: ${color}; opacity: 1;\">\n <path d=\"M24 14.5v-12a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v7.528a2 2 0 0 1-.211.894l-2.065 4.13a1 1 0 0 0 .894 1.448H22a2 2 0 0 0 2-2z\" stroke=\"currentColor\" style=\"stroke: ${color}; stroke-opacity: 0.5; stroke-width: 1px; stroke-linejoin: round; fill: ${tccolor}; fill-opacity: 0.5;\"><\/path>\n <text x=\"13.5\" y=\"12\" text-anchor=\"middle\" alignment-baseline=\"auto\" font-size=\"10\" fill=\"currentColor\" font-family=\"Arial, sans-serif\" font-weight=\"500\" style=\"fill: ${color}; opacity: 1;\">${displayText}<\/text>\n <\/svg>`;\n }\n var jc = 'js';\n if (ydv) {\n jc = 'click'\n }\n var encodedSvg = this.java.base64Encode(svg);\n\n return 'data:image\/svg+xml;base64,' + encodedSvg + ',{\"' + jc + '\":\"showCmt(\\'' + encodedUrl + '\\', \\'' + sources + '\\', ' + ydv + ')\",\"style\":\"text\"}';\n}\n\n\nfunction cleanHTML(html) {\n let result = html\n .replace(\/<header[^>]*>[\\s\\S]*?<\\\/header>\/gi, \"\")\n .replace(\/<div class=\"tt-title\"[^>]*>[\\s\\S]*?<\\\/div>\/gi, \"\")\n .replace(\/<(?!\\\/?p\\b|\\\/?img\\b)[^>]+>\/gi, \"\");\n result = result.replace(\/<\\\/?p[^>]*>\/g, \"\\n\");\n return result.replace(\/\\n+\/g, \"\\n\").trim();\n}\n\n\nfunction getBaseUrl(url) {\n if (!url) {\n return null;\n }\n url = String(url);\n if (url.match(\/https?:\\\/\\\/\/i)) {\n var index = url.indexOf(\"\/\", 9);\n return index == -1 ? url : url.substring(0, index);\n }\n return null;\n}\n\nfunction isIPv4Address(ip) {\n ip = String(ip);\n let parts = ip.split(\".\");\n if (parts.length !== 4) return false;\n\n for (let part of parts) {\n if (!\/^\\d+$\/.test(part)) return false; \/\/ 必须是数字\n if (part.length > 1 && part[0] === \"0\") return false; \/\/ 禁止前导零\n let num = parseInt(part, 10);\n if (num < 0 || num > 255) return false; \/\/ 范围检查\n }\n return true;\n}\n\nfunction isIPv6Address(ip) {\n ip = String(ip);\n \/\/ 处理双冒号(最多出现一次)\n if (ip.includes(\":::\")) return false;\n let doubleColonCount = (ip.match(\/::\/g) || []).length;\n if (doubleColonCount > 1) return false;\n\n \/\/ 分割成组\n let groups = ip.split(\":\");\n let validGroupCount = 8;\n let actualGroupCount = groups.filter((g) => g !== \"\").length;\n\n \/\/ 验证组数\n if (doubleColonCount === 1) {\n if (actualGroupCount > validGroupCount - 1) return false;\n } else {\n if (groups.length !== validGroupCount) return false;\n }\n\n \/\/ 验证每组内容\n for (let group of groups) {\n if (group === \"\") continue; \/\/ 跳过空组(双冒号部分)\n if (!\/^[0-9a-fA-F]{1,4}$\/.test(group)) return false; \/\/ 1-4位十六进制\n }\n return true;\n}\n\nfunction isIPAddress(input) {\n return isIPv4Address(input) || isIPv6Address(input);\n}\n\nfunction getSessionId(cookieString) {\n const match = cookieString.match(\/sessionid=([^;]+)\/);\n return match ? match[1] : null;\n}\n\nfunction getKey(key) {\n let parts = key.split(\";\");\n for (let part of parts) {\n if (part.includes(\"qttoken\")) {\n return part.split(\"=\")[1];\n }\n }\n return \"\";\n}\n\nfunction getFanqieCookie() {\n const {\n cookie\n } = this;\n try {\n return String(cookie.getCookie('fanqienovel.com') || java.getCookie('fanqienovel.com') || '');\n } catch (e) {\n return '';\n }\n}\n\n\nfunction paraForiOS(html, sources) {\n return html.replace(\n \/<p>(.*?)(?:<comment ident=\"([^\"]*)\" count=\"([^\"]*)\" \\\/>)?<\\\/p>\/g,\n function(match, text, url, count) {\n if (url && count) {\n const encodedUrl = url.replace(\/&\/g, '&');\n return `<div rs-native>${text}<comment count=\"${count}\" onPress=\"java.showReadingBrowser('${encodedUrl}','${sources}段评')\"><\/div>`;\n } else {\n return `<div rs-native>${text}<\/div>`;\n }\n }\n );\n}\n\nfunction removeAllImgTags(htmlString) {\n const imgTagRegex = \/<img\\b[^>]*>|<\\\/img>|<img\\b[^>]*\\\/>\/gi;\n return htmlString.replace(imgTagRegex, '');\n}\n\n\n\nfunction paraHtml(url) {\n let burl = url.split('?');\n let baser_url = burl[0].replace(\"\/get_para_review\", \"\");\n url = burl[1];\n return `<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n <link rel=\"shortcut icon\" href=\"${baser_url}\/static\/favicon.ico\" type=\"image\/x-icon\">\n <link rel=\"stylesheet\" href=\"${baser_url}\/static\/css\/all.min.css\">\n <title>段落评论<\/title>\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n -webkit-tap-highlight-color: transparent;\n }\n\n body {\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"PingFang SC\", \"Hiragino Sans GB\", \"Microsoft YaHei\", sans-serif;\n background: #f5f5f5;\n color: #333;\n line-height: 1.5;\n min-height: 100vh;\n padding-bottom: 70px;\n }\n\n \/* 顶部栏 *\/\n .header {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n z-index: 100;\n background: #fff;\n padding: 12px 16px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n border-bottom: 1px solid #eee;\n box-shadow: 0 1px 3px rgba(0,0,0,0.05);\n }\n\n .header-left {\n display: flex;\n align-items: center;\n gap: 12px;\n }\n\n .header-back {\n font-size: 20px;\n color: #666;\n cursor: pointer;\n padding: 4px;\n }\n\n .header-title {\n font-size: 16px;\n font-weight: 600;\n color: #333;\n }\n\n \/* 排序按钮 *\/\n .sort-buttons {\n display: flex;\n gap: 8px;\n }\n\n .sort-btn {\n padding: 6px 14px;\n font-size: 13px;\n border-radius: 16px;\n cursor: pointer;\n background: #f5f5f5;\n color: #666;\n border: none;\n transition: all 0.2s;\n font-weight: 500;\n }\n\n .sort-btn.active {\n background: #07c160;\n color: #fff;\n }\n\n .sort-btn:hover:not(.active) {\n background: #eee;\n }\n\n \/* 评论按钮样式 *\/\n .comment-btn {\n padding: 6px 5px;\n font-size: 13px;\n border-radius: 0;\n cursor: pointer;\n background: transparent;\n color: #07c160;\n border: none;\n transition: all 0.2s;\n font-weight: 500;\n white-space: nowrap;\n flex-shrink: 0;\n box-shadow: none;\n }\n\n .comment-btn:hover {\n color: #05a850;\n transform: none;\n box-shadow: none;\n }\n\n .comment-btn:active {\n transform: none;\n box-shadow: none;\n }\n\n \/* 确保头部布局不会变形 *\/\n .header {\n flex-wrap: wrap;\n }\n\n .header-left {\n flex-shrink: 1;\n overflow: hidden;\n min-width: 0;\n }\n\n .header-title {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 180px;\n }\n\n .sort-buttons {\n flex-shrink: 1;\n min-width: 0;\n }\n\n \/* 排序按钮 *\/\n .sort-btn {\n padding: 5px 10px;\n font-size: 12px;\n }\n\n \/* 评论按钮样式 *\/\n .comment-btn {\n padding: 5px 8px;\n font-size: 12px;\n }\n\n \/* 响应式调整 *\/\n @media (max-width: 480px) {\n .header {\n padding: 10px 12px;\n }\n\n .header-left {\n gap: 8px;\n }\n\n .header-title {\n font-size: 14px;\n max-width: 150px;\n }\n\n .sort-buttons {\n gap: 6px;\n }\n\n .sort-btn {\n padding: 4px 10px;\n font-size: 11px;\n }\n\n .comment-btn {\n padding: 4px 6px;\n font-size: 11px;\n }\n\n .theme-toggle {\n width: 32px;\n height: 32px;\n margin-left: 8px;\n }\n }\n\n \/* 主容器 *\/\n .main-container {\n padding: 60px 0 0;\n }\n\n \/* 段落引用 *\/\n .paragraph-quote {\n background: #fff;\n padding: 16px;\n margin: 12px;\n border-radius: 8px;\n border-left: 3px solid #07c160;\n font-size: 14px;\n color: #666;\n line-height: 1.7;\n }\n\n \/* 热门评论区域 *\/\n .hot-section {\n margin: 12px;\n }\n\n .section-header {\n display: flex;\n align-items: center;\n padding: 8px 0;\n margin-bottom: 8px;\n }\n\n .section-header i {\n color: #ff6b6b;\n margin-right: 8px;\n font-size: 16px;\n }\n\n .section-title {\n font-size: 15px;\n font-weight: 600;\n color: #333;\n }\n\n .section-count {\n color: #999;\n font-size: 13px;\n margin-left: 8px;\n font-weight: normal;\n }\n\n \/* 评论列表 *\/\n .comment-list {\n background: #fff;\n border-radius: 8px;\n overflow: hidden;\n }\n\n \/* 评论卡片 *\/\n .comment-item {\n padding: 16px;\n border-bottom: 1px solid #f0f0f0;\n }\n\n .comment-item:last-child {\n border-bottom: none;\n }\n\n .comment-main {\n display: flex;\n gap: 12px;\n }\n\n .comment-avatar {\n flex-shrink: 0;\n }\n\n .comment-avatar img {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n object-fit: cover;\n background: #f0f0f0;\n }\n\n .comment-body {\n flex: 1;\n min-width: 0;\n }\n\n .comment-header {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n margin-bottom: 6px;\n }\n\n .comment-username {\n font-size: 14px;\n font-weight: 500;\n color: #333;\n margin-right: 8px;\n }\n\n .comment-tag {\n font-size: 10px;\n padding: 2px 6px;\n border-radius: 4px;\n margin-right: 6px;\n font-weight: 500;\n }\n\n .tag-author {\n background: linear-gradient(135deg, #ff6b6b, #ff8787);\n color: #fff;\n }\n\n .tag-vip {\n background: linear-gradient(135deg, #ffa726, #ffcc02);\n color: #7c5200;\n }\n\n .tag-custom {\n background: linear-gradient(135deg, #4fc3f7, #29b6f6);\n color: #fff;\n }\n\n .comment-content {\n font-size: 15px;\n color: #333;\n line-height: 1.6;\n word-break: break-word;\n margin-bottom: 10px;\n }\n\n .comment-content .bq {\n width: 20px;\n height: 20px;\n vertical-align: middle;\n margin: 0 1px;\n }\n\n .comment-image {\n max-width: 200px;\n max-height: 200px;\n border-radius: 8px;\n margin-bottom: 10px;\n cursor: pointer;\n }\n\n .comment-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .comment-meta {\n display: flex;\n align-items: center;\n gap: 16px;\n }\n\n .comment-time {\n font-size: 12px;\n color: #999;\n }\n\n .comment-reply-btn {\n font-size: 12px;\n color: #666;\n cursor: pointer;\n padding: 4px 8px;\n border-radius: 4px;\n transition: background 0.2s;\n }\n\n .comment-reply-btn:hover {\n background: #f5f5f5;\n }\n\n .comment-likes {\n display: flex;\n align-items: center;\n gap: 4px;\n color: #999;\n font-size: 13px;\n }\n\n .comment-likes i {\n font-size: 14px;\n }\n\n \/* 展开回复 *\/\n .expand-replies {\n display: flex;\n align-items: center;\n margin-top: 12px;\n padding-left: 52px;\n color: #576b95;\n font-size: 13px;\n cursor: pointer;\n }\n\n .expand-replies::before {\n content: '';\n width: 20px;\n height: 1px;\n background: #ddd;\n margin-right: 8px;\n }\n\n .expand-replies i {\n margin-left: 4px;\n font-size: 10px;\n transition: transform 0.3s;\n }\n\n .expand-replies.expanded i {\n transform: rotate(180deg);\n }\n\n \/* 回复列表 *\/\n .reply-list {\n margin-top: 12px;\n padding-left: 52px;\n display: none;\n }\n\n .reply-list.show {\n display: block;\n }\n\n .reply-item {\n padding: 12px 0;\n border-bottom: 1px solid #f5f5f5;\n }\n\n .reply-item:last-child {\n border-bottom: none;\n }\n\n .reply-main {\n display: flex;\n gap: 10px;\n }\n\n .reply-avatar img {\n width: 28px;\n height: 28px;\n border-radius: 50%;\n object-fit: cover;\n background: #f0f0f0;\n }\n\n .reply-body {\n flex: 1;\n min-width: 0;\n }\n\n .reply-header {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n margin-bottom: 4px;\n }\n\n .reply-username {\n font-size: 13px;\n font-weight: 500;\n color: #333;\n }\n\n .reply-to {\n font-size: 13px;\n color: #999;\n margin: 0 4px;\n }\n\n .reply-to-username {\n font-size: 13px;\n color: #576b95;\n }\n\n .reply-content {\n font-size: 14px;\n color: #333;\n line-height: 1.5;\n margin-bottom: 6px;\n }\n\n .reply-content .bq {\n width: 18px;\n height: 18px;\n vertical-align: middle;\n margin: 0 1px;\n }\n\n .reply-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .reply-time {\n font-size: 11px;\n color: #999;\n }\n\n .reply-likes {\n display: flex;\n align-items: center;\n gap: 3px;\n color: #999;\n font-size: 12px;\n }\n\n \/* 默认头像 *\/\n .default-avatar {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n display: flex;\n align-items: center;\n justify-content: center;\n color: #fff;\n font-size: 18px;\n }\n\n .default-avatar.small {\n width: 28px;\n height: 28px;\n font-size: 12px;\n }\n\n \/* 底部输入栏 *\/\n .bottom-bar {\n position: fixed;\n bottom: 0;\n \n left: 0;\n right: 0;\n background: #fff;\n padding: 10px 16px;\n padding-bottom: 20px;\n display: flex;\n align-items: center;\n gap: 12px;\n border-top: 1px solid #eee;\n z-index: 100;\n }\n\n .input-wrapper {\n flex: 1;\n display: flex;\n align-items: center;\n background: #f5f5f5;\n border-radius: 20px;\n padding: 8px 16px;\n cursor: pointer;\n }\n\n .input-wrapper span {\n color: #999;\n font-size: 14px;\n }\n\n .bottom-actions {\n display: flex;\n align-items: center;\n gap: 16px;\n }\n\n .bottom-action-btn {\n font-size: 22px;\n color: #666;\n cursor: pointer;\n transition: color 0.2s;\n }\n\n .bottom-action-btn:hover {\n color: #333;\n }\n\n \/* 加载状态 *\/\n .loading {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 60px 20px;\n color: #999;\n }\n\n .loading-spinner {\n width: 32px;\n height: 32px;\n border: 3px solid #f0f0f0;\n border-top-color: #07c160;\n border-radius: 50%;\n animation: spin 0.8s linear infinite;\n margin-bottom: 12px;\n }\n\n @keyframes spin {\n to { transform: rotate(360deg); }\n }\n\n \/* 空状态 *\/\n .empty {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 80px 20px;\n color: #999;\n }\n\n .empty i {\n font-size: 48px;\n margin-bottom: 16px;\n opacity: 0.3;\n }\n\n .empty-title {\n font-size: 15px;\n color: #666;\n margin-bottom: 8px;\n }\n\n .empty-desc {\n font-size: 13px;\n color: #999;\n }\n\n \/* 没有更多 *\/\n .no-more {\n text-align: center;\n padding: 20px;\n color: #999;\n font-size: 13px;\n }\n\n \/* 评论弹窗 *\/\n .modal-overlay {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.5);\n z-index: 200;\n display: none;\n align-items: flex-start;\n justify-content: center;\n overflow: hidden;\n padding-top: 180px;\n }\n\n .modal-overlay.show {\n display: flex;\n }\n\n .modal-content {\n background: #fff;\n width: 90%;\n max-width: 500px;\n border-radius: 16px;\n padding: 20px;\n animation: slideDown 0.3s ease;\n transition: transform 0.3s ease;\n max-height: 80vh;\n box-shadow: 0 10px 40px rgba(0,0,0,0.2);\n }\n\n @keyframes slideDown {\n from {\n transform: translateY(-100%);\n }\n to {\n transform: translateY(0);\n }\n }\n\n .modal-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 16px;\n }\n\n .modal-title {\n font-size: 16px;\n font-weight: 600;\n }\n\n .modal-close {\n font-size: 24px;\n color: #999;\n cursor: pointer;\n padding: 4px;\n }\n\n .modal-textarea-wrapper {\n position: relative;\n margin-bottom: 12px;\n }\n\n .modal-textarea {\n width: 100%;\n min-height: 120px;\n border: 1px solid #eee;\n border-radius: 8px;\n padding: 12px;\n padding-bottom: 40px;\n font-size: 15px;\n resize: none;\n outline: none;\n }\n\n .modal-textarea:focus {\n border-color: #07c160;\n }\n\n .emoji-toggle-btn {\n position: absolute;\n bottom: 10px;\n right: 10px;\n font-size: 22px;\n color: #999;\n cursor: pointer;\n background: none;\n border: none;\n padding: 4px;\n transition: color 0.2s;\n }\n\n .emoji-toggle-btn:hover {\n color: #07c160;\n }\n\n \/* 表情面板 *\/\n .emoji-panel {\n position: absolute;\n bottom: 45px;\n right: 0;\n background: #fff;\n border: 1px solid #eee;\n border-radius: 8px;\n box-shadow: 0 4px 20px rgba(0,0,0,0.15);\n padding: 12px;\n width: 320px;\n max-height: 280px;\n overflow-y: auto;\n z-index: 10;\n display: none;\n }\n\n .emoji-panel.show {\n display: block;\n }\n\n .emoji-grid {\n display: grid;\n grid-template-columns: repeat(6, 1fr);\n gap: 6px;\n }\n\n .emoji-btn {\n padding: 8px;\n border: none;\n background: transparent;\n cursor: pointer;\n border-radius: 6px;\n transition: background 0.2s;\n display: flex;\n align-items: center;\n justify-content: center;\n aspect-ratio: 1;\n }\n\n .emoji-btn:hover {\n background: #f0f0f0;\n }\n\n .emoji-btn img {\n width: 24px;\n height: 24px;\n }\n\n .modal-footer {\n display: flex;\n align-items: center;\n justify-content: space-between;\n }\n\n .char-count {\n font-size: 13px;\n color: #999;\n }\n\n .char-count.over {\n color: #ff4d4f;\n }\n\n .submit-btn {\n background: #07c160;\n color: #fff;\n border: none;\n padding: 8px 24px;\n border-radius: 20px;\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n }\n\n .submit-btn:disabled {\n background: #ccc;\n cursor: not-allowed;\n }\n\n \/* 图片预览 *\/\n .image-preview {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.9);\n z-index: 300;\n display: none;\n align-items: center;\n justify-content: center;\n }\n\n .image-preview.show {\n display: flex;\n }\n\n .image-preview img {\n max-width: 90%;\n max-height: 90%;\n border-radius: 8px;\n }\n\n .image-preview-close {\n position: absolute;\n top: 20px;\n right: 20px;\n font-size: 32px;\n color: #fff;\n cursor: pointer;\n }\n\n \/* Toast *\/\n .toast {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n background: rgba(0, 0, 0, 0.75);\n color: #fff;\n padding: 12px 24px;\n border-radius: 8px;\n font-size: 14px;\n z-index: 400;\n display: none;\n }\n\n .toast.show {\n display: block;\n }\n\n \/* 隐藏 *\/\n .hidden {\n display: none !important;\n }\n\n \/* 深色模式 *\/\n html.dark, body.dark {\n background: #1a1a1a;\n color: #e0e0e0;\n }\n\n \/* 确保在深色模式下整个页面都是深色背景 *\/\n .dark {\n background: #1a1a1a;\n }\n\n .dark .header {\n background: #2d2d2d;\n border-bottom-color: #3d3d3d;\n }\n\n .dark .header-title {\n color: #e0e0e0;\n }\n\n .dark .header-back {\n color: #999;\n }\n\n .dark .sort-btn {\n background: #3d3d3d;\n color: #999;\n }\n\n .dark .sort-btn:hover:not(.active) {\n background: #4d4d4d;\n }\n\n .dark .sort-btn.active {\n background: #07c160;\n color: #fff;\n }\n\n .dark .comment-btn {\n background: transparent;\n color: #07c160;\n box-shadow: none;\n }\n\n .dark .comment-btn:hover {\n background: transparent;\n color: #05a850;\n box-shadow: none;\n }\n\n .dark .comment-btn:active {\n background: transparent;\n box-shadow: none;\n }\n\n .dark .paragraph-quote {\n background: #2d2d2d;\n color: #aaa;\n }\n\n .dark .comment-list {\n background: #2d2d2d;\n }\n\n .dark .comment-item {\n border-bottom-color: #3d3d3d;\n }\n\n .dark .comment-username {\n color: #e0e0e0;\n }\n\n .dark .comment-content {\n color: #ccc;\n }\n\n .dark .comment-time {\n color: #777;\n }\n\n .dark .comment-likes {\n color: #777;\n }\n\n .dark .expand-replies {\n color: #5b9bd5;\n }\n\n .dark .expand-replies::before {\n background: #4d4d4d;\n }\n\n .dark .reply-item {\n border-bottom-color: #3d3d3d;\n }\n\n .dark .reply-username {\n color: #e0e0e0;\n }\n\n .dark .reply-content {\n color: #bbb;\n }\n\n .dark .reply-to-username {\n color: #5b9bd5;\n }\n\n .dark .reply-time,\n .dark .reply-likes {\n color: #666;\n }\n\n .dark .bottom-bar {\n background: #2d2d2d;\n border-top-color: #3d3d3d;\n }\n\n .dark .input-wrapper {\n background: #3d3d3d;\n }\n\n .dark .input-wrapper span {\n color: #777;\n }\n\n .dark .bottom-action-btn {\n color: #999;\n }\n\n .dark .modal-content {\n background: #2d2d2d;\n }\n\n .dark .modal-title {\n color: #e0e0e0;\n }\n\n .dark .modal-textarea {\n background: #3d3d3d;\n border-color: #4d4d4d;\n color: #e0e0e0;\n }\n\n .dark .modal-textarea:focus {\n border-color: #07c160;\n }\n\n .dark .emoji-panel {\n background: #2d2d2d;\n border-color: #4d4d4d;\n }\n\n .dark .emoji-btn:hover {\n background: #3d3d3d;\n }\n\n .dark .section-title {\n color: #e0e0e0;\n }\n\n .dark .loading {\n color: #777;\n }\n\n .dark .loading-spinner {\n border-color: #3d3d3d;\n border-top-color: #07c160;\n }\n\n .dark .empty {\n color: #666;\n }\n\n .dark .empty-title {\n color: #999;\n }\n\n .dark .no-more {\n color: #666;\n }\n\n .dark .default-avatar {\n background: linear-gradient(135deg, #4a5568 0%, #2d3748 100%);\n }\n\n \/* 深色模式切换按钮 *\/\n .theme-toggle {\n width: 36px;\n height: 36px;\n border-radius: 50%;\n border: none;\n background: #f5f5f5;\n color: #666;\n font-size: 16px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s;\n margin-left: 12px;\n }\n\n .theme-toggle:hover {\n background: #eee;\n }\n\n .dark .theme-toggle {\n background: #3d3d3d;\n color: #ffc107;\n }\n\n .dark .theme-toggle:hover {\n background: #4d4d4d;\n }\n\n \/* ==================== PC端响应式优化 ==================== *\/\n @media (min-width: 768px) {\n body {\n background: #e8e8e8;\n }\n\n .dark body,\n body.dark {\n background: #121212;\n }\n\n .header {\n max-width: 800px;\n left: 50%;\n transform: translateX(-50%);\n border-radius: 0 0 12px 12px;\n box-shadow: 0 2px 12px rgba(0,0,0,0.08);\n }\n\n .main-container {\n max-width: 800px;\n margin: 0 auto;\n padding-top: 72px;\n background: transparent;\n }\n\n .paragraph-quote {\n margin: 16px 0;\n border-radius: 12px;\n box-shadow: 0 2px 8px rgba(0,0,0,0.04);\n }\n\n .hot-section {\n margin: 16px 0;\n }\n\n .comment-list {\n border-radius: 12px;\n box-shadow: 0 2px 8px rgba(0,0,0,0.04);\n }\n\n .comment-item {\n padding: 20px 24px;\n }\n\n .comment-item:hover {\n background: #fafafa;\n }\n\n .dark .comment-item:hover {\n background: #363636;\n }\n\n .comment-avatar img,\n .default-avatar {\n width: 48px;\n height: 48px;\n font-size: 20px;\n }\n\n .comment-main {\n gap: 16px;\n }\n\n .comment-username {\n font-size: 15px;\n }\n\n .comment-content {\n font-size: 15px;\n line-height: 1.7;\n }\n\n .comment-image {\n max-width: 300px;\n max-height: 300px;\n }\n\n .expand-replies {\n padding-left: 64px;\n }\n\n .reply-list {\n padding-left: 64px;\n }\n\n .reply-avatar img,\n .default-avatar.small {\n width: 32px;\n height: 32px;\n font-size: 14px;\n }\n\n .reply-main {\n gap: 12px;\n }\n\n .reply-username {\n font-size: 14px;\n }\n\n .reply-content {\n font-size: 14px;\n line-height: 1.6;\n }\n\n .bottom-bar {\n max-width: 800px;\n left: 50%;\n transform: translateX(-50%);\n border-radius: 12px 12px 0 0;\n box-shadow: 0 -2px 12px rgba(0,0,0,0.08);\n }\n\n .modal-content {\n max-width: 600px;\n margin: 0 auto;\n border-radius: 16px;\n margin-bottom: 20px;\n }\n\n \/* 更多评论区域 *\/\n #moreSection {\n margin: 16px 0;\n }\n\n \/* 加载状态 *\/\n .loading,\n .empty {\n background: #fff;\n border-radius: 12px;\n margin: 16px 0;\n }\n\n .dark .loading,\n .dark .empty {\n background: #2d2d2d;\n }\n\n .no-more {\n padding: 30px;\n }\n\n \/* 列表容器 *\/\n .main-container > div[style*=\"margin: 0 12px\"] {\n margin: 0 !important;\n }\n }\n\n \/* 更大屏幕 *\/\n @media (min-width: 1200px) {\n .header,\n .main-container,\n .bottom-bar {\n max-width: 900px;\n }\n\n .comment-item {\n padding: 24px 32px;\n }\n\n .comment-avatar img,\n .default-avatar {\n width: 52px;\n height: 52px;\n }\n\n .expand-replies,\n .reply-list {\n padding-left: 68px;\n }\n }\n\n \/* 超宽屏幕双栏布局(可选) *\/\n @media (min-width: 1400px) {\n .header,\n .main-container,\n .bottom-bar {\n max-width: 1000px;\n }\n }\n <\/style>\n<\/head>\n<body>\n <!-- 顶部栏 -->\n <div class=\"header\">\n <div class=\"header-left\">\n <span class=\"header-title\" id=\"headerTitle\">评论<\/span>\n <\/div>\n <div class=\"sort-buttons\" id=\"sortButtons\">\n <button class=\"comment-btn\" id=\"commentBtn\" onclick=\"openCommentModal()\" style=\"display:none;\">\n <i class=\"fas fa-pen\"><\/i> 去评论\n <\/button>\n <button class=\"sort-btn active\" id=\"sortByHot\" onclick=\"sortComments('hot')\">\n <i class=\"fas fa-fire\"><\/i> 最热\n <\/button>\n <button class=\"sort-btn\" id=\"sortByTime\" onclick=\"sortComments('time')\">\n <i class=\"fas fa-clock\"><\/i> 最新\n <\/button>\n\n <button class=\"theme-toggle\" id=\"themeToggle\" onclick=\"toggleTheme()\">\n <i class=\"fas fa-moon\" id=\"themeIcon\"><\/i>\n <\/button>\n <\/div>\n <\/div>\n\n <!-- 主容器 -->\n <div class=\"main-container\">\n <!-- 段落内容 -->\n <div class=\"paragraph-quote hidden\" id=\"paragraphQuote\"><\/div>\n\n <!-- 热门评论区域(仅七猫) -->\n <div class=\"hot-section hidden\" id=\"hotSection\">\n <div class=\"section-header\">\n <i class=\"fas fa-fire\"><\/i>\n <span class=\"section-title\">热门评论<\/span>\n <span class=\"section-count\" id=\"hotCount\"><\/span>\n <\/div>\n <div class=\"comment-list\" id=\"hotCommentList\"><\/div>\n <\/div>\n\n <!-- 更多评论标题(仅七猫) -->\n <div class=\"hot-section hidden\" id=\"moreSection\">\n <div class=\"section-header\">\n <i class=\"fas fa-clock\" style=\"color: #07c160;\"><\/i>\n <span class=\"section-title\">更多评论<\/span>\n <span class=\"section-count\" id=\"moreCount\"><\/span>\n <\/div>\n <\/div>\n\n <!-- 评论列表 -->\n <div style=\"margin: 0 12px;\">\n <div class=\"comment-list\" id=\"commentList\"><\/div>\n <\/div>\n\n <!-- 加载状态 -->\n <div class=\"loading\" id=\"loading\">\n <div class=\"loading-spinner\"><\/div>\n <span>加载中...<\/span>\n <\/div>\n\n <!-- 空状态 -->\n <div class=\"empty hidden\" id=\"empty\">\n <i class=\"fas fa-comment-slash\"><\/i>\n <div class=\"empty-title\">暂无评论<\/div>\n <div class=\"empty-desc\">快来发表第一条评论吧<\/div>\n <\/div>\n\n <!-- 没有更多 -->\n <div class=\"no-more hidden\" id=\"noMore\">— 没有更多评论了 —<\/div>\n <\/div>\n\n <!-- 底部输入栏(仅番茄显示) -->\n <div class=\"bottom-bar hidden\" id=\"bottomBar\">\n <div class=\"input-wrapper\" onclick=\"openCommentModal()\">\n <span>趣评千万条,你也来...<\/span>\n <\/div>\n <div class=\"bottom-actions\">\n <i class=\"far fa-smile bottom-action-btn\" onclick=\"openCommentModal()\"><\/i>\n <\/div>\n <\/div>\n\n <!-- 评论弹窗 -->\n <div class=\"modal-overlay\" id=\"commentModal\" onclick=\"closeCommentModal(event)\">\n <div class=\"modal-content\" onclick=\"event.stopPropagation()\">\n <div class=\"modal-header\">\n <span class=\"modal-title\">发表评论<\/span>\n <i class=\"fas fa-times modal-close\" onclick=\"closeCommentModal()\"><\/i>\n <\/div>\n <div class=\"modal-textarea-wrapper\">\n <textarea class=\"modal-textarea\" id=\"commentTextarea\" placeholder=\"写下你的想法...\" maxlength=\"500\"><\/textarea>\n <button type=\"button\" class=\"emoji-toggle-btn\" id=\"emojiToggleBtn\" onclick=\"toggleEmojiPanel(event)\">\n <i class=\"far fa-smile\"><\/i>\n <\/button>\n <div class=\"emoji-panel\" id=\"emojiPanel\">\n <div class=\"emoji-grid\" id=\"emojiGrid\"><\/div>\n <\/div>\n <\/div>\n <div class=\"modal-footer\">\n <span class=\"char-count\" id=\"charCount\">0\/500<\/span>\n <button class=\"submit-btn\" id=\"submitBtn\" onclick=\"submitComment()\">发表<\/button>\n <\/div>\n <\/div>\n <\/div>\n\n <!-- 图片预览 -->\n <div class=\"image-preview\" id=\"imagePreview\" onclick=\"closeImagePreview()\">\n <i class=\"fas fa-times image-preview-close\"><\/i>\n <img id=\"previewImage\" src=\"\" alt=\"\">\n <\/div>\n\n <!-- Toast -->\n <div class=\"toast\" id=\"toast\"><\/div>\n\n <script>\n \/\/ 配置\n const urlParams = new URLSearchParams(\"${url}\");\n const config = {\n bookId: urlParams.get('book_id'),\n itemId: urlParams.get('item_id'),\n currentPara: urlParams.get('para'),\n ssionid: urlParams.get('ssionid'),\n source: urlParams.get('source') || '番茄',\n cursor: '',\n lastCursor: '', \/\/ 保存上一个cursor用于错误恢复\n hasMore: false,\n isLoading: false,\n currentSort: 'hot',\n comments: [],\n hotComments: [],\n allComments: [],\n loadRetryCount: 0, \/\/ 加载重试次数\n maxRetryCount: 3 \/\/ 最大重试次数\n };\n\n \/\/ 表情映射\n const emojiMap = {\n \"[微笑]\": \"${baser_url}\/static\/emoji\/emoji_dr_1.png\",\n \"[偷笑]\": \"${baser_url}\/static\/emoji\/emoji_dr_2.png\",\n \"[笑]\": \"${baser_url}\/static\/emoji\/emoji_dr_3.png\",\n \"[什么]\": \"${baser_url}\/static\/emoji\/emoji_dr_4.png\",\n \"[害羞]\": \"${baser_url}\/static\/emoji\/emoji_dr_5.png\",\n \"[爱慕]\": \"${baser_url}\/static\/emoji\/emoji_dr_6.png\",\n \"[飞吻]\": \"${baser_url}\/static\/emoji\/emoji_dr_7.png\",\n \"[奸笑]\": \"${baser_url}\/static\/emoji\/emoji_dr_8.png\",\n \"[尬笑]\": \"${baser_url}\/static\/emoji\/emoji_dr_9.png\",\n \"[思考]\": \"${baser_url}\/static\/emoji\/emoji_dr_10.png\",\n \"[撇嘴]\": \"${baser_url}\/static\/emoji\/emoji_dr_11.png\",\n \"[做鬼脸]\": \"${baser_url}\/static\/emoji\/emoji_dr_12.png\",\n \"[酷]\": \"${baser_url}\/static\/emoji\/emoji_dr_13.png\",\n \"[翻白眼]\": \"${baser_url}\/static\/emoji\/emoji_dr_14.png\",\n \"[惊呆]\": \"${baser_url}\/static\/emoji\/emoji_dr_15.png\",\n \"[震惊]\": \"${baser_url}\/static\/emoji\/emoji_dr_16.png\",\n \"[送心]\": \"${baser_url}\/static\/emoji\/emoji_dr_17.png\",\n \"[委屈]\": \"${baser_url}\/static\/emoji\/emoji_dr_18.png\",\n \"[快哭了]\": \"${baser_url}\/static\/emoji\/emoji_dr_19.png\",\n \"[笑哭]\": \"${baser_url}\/static\/emoji\/emoji_dr_20.png\",\n \"[哭]\": \"${baser_url}\/static\/emoji\/emoji_dr_21.png\",\n \"[大笑]\": \"${baser_url}\/static\/emoji\/emoji_dr_22.png\",\n \"[舔屏]\": \"${baser_url}\/static\/emoji\/emoji_dr_23.png\",\n \"[怒]\": \"${baser_url}\/static\/emoji\/emoji_dr_24.png\",\n \"[捂脸]\": \"${baser_url}\/static\/emoji\/emoji_dr_25.png\",\n \"[吐]\": \"${baser_url}\/static\/emoji\/emoji_dr_26.png\",\n \"[恐惧]\": \"${baser_url}\/static\/emoji\/emoji_dr_27.png\",\n \"[抓狂]\": \"${baser_url}\/static\/emoji\/emoji_dr_28.png\",\n \"[敬礼]\": \"${baser_url}\/static\/emoji\/emoji_dr_29.png\",\n \"[石化]\": \"${baser_url}\/static\/emoji\/emoji_dr_30.png\",\n \"[OK]\": \"${baser_url}\/static\/emoji\/emoji_dr_31.png\",\n \"[赞]\": \"${baser_url}\/static\/emoji\/emoji_dr_32.png\",\n \"[爱心]\": \"${baser_url}\/static\/emoji\/emoji_dr_33.png\",\n \"[伤心]\": \"${baser_url}\/static\/emoji\/emoji_dr_34.png\",\n \"[KISS]\": \"${baser_url}\/static\/emoji\/emoji_dr_35.png\",\n \"[懂了]\": \"${baser_url}\/static\/emoji\/emoji_dr_36.png\",\n \"[探究]\": \"${baser_url}\/static\/emoji\/emoji_dr_37.png\",\n \"[重拳出击]\": \"${baser_url}\/static\/emoji\/emoji_dr_38.png\",\n \"[吃瓜]\": \"${baser_url}\/static\/emoji\/emoji_dr_39.png\",\n \"[盯]\": \"${baser_url}\/static\/emoji\/emoji_dr_40.png\",\n \"[你细品]\": \"${baser_url}\/static\/emoji\/emoji_dr_41.png\",\n \"[赶稿中]\": \"${baser_url}\/static\/emoji\/emoji_dr_42.png\",\n \"[注意]\": \"${baser_url}\/static\/emoji\/emoji_dr_43.png\",\n \"[饱了]\": \"${baser_url}\/static\/emoji\/emoji_dr_44.png\",\n \"[码住]\": \"${baser_url}\/static\/emoji\/emoji_dr_45.png\",\n \"[学会了]\": \"${baser_url}\/static\/emoji\/emoji_dr_46.png\",\n \"[顶帖]\": \"${baser_url}\/static\/emoji\/emoji_dr_47.png\",\n \"[求爆更]\": \"${baser_url}\/static\/emoji\/emoji_dr_48.png\",\n \"[求关注]\": \"${baser_url}\/static\/emoji\/emoji_dr_49.png\",\n \"[我也强推]\": \"${baser_url}\/static\/emoji\/emoji_dr_50.png\",\n \"[雀食神作]\": \"${baser_url}\/static\/emoji\/emoji_dr_51.png\",\n \"[已种草]\": \"${baser_url}\/static\/emoji\/emoji_dr_52.png\",\n \"[书架加一]\": \"${baser_url}\/static\/emoji\/emoji_dr_53.png\",\n \"[歪头笑哭]\": \"${baser_url}\/static\/emoji\/emoticon_emoji_wtxk.png\",\n \"[神评]\": \"${baser_url}\/static\/emoji\/emoticon_xiaoqi_sp.png\",\n \"[狗头]\": \"${baser_url}\/static\/emoji\/emoticon_emoji_doge.png\",\n \"[哈哈]\": \"${baser_url}\/static\/emoji\/emoticon_emoji_hhh.png\",\n \"[流泪]\": \"${baser_url}\/static\/emoji\/emoticon_emoji_tears.png\",\n \"[可爱]\": \"${baser_url}\/static\/emoji\/emoticon_emoji_cute.png\",\n \"[色]\": \"${baser_url}\/static\/emoji\/emoticon_emoji_smimi.png\",\n \"[调皮]\": \"${baser_url}\/static\/emoji\/emoticon_emoji_tpi.png\",\n \"[小七_神评]\": \"${baser_url}\/static\/emoji\/emoticon_xiaoqi_sp.png\",\n \"[小七_狗头]\": \"${baser_url}\/static\/emoji\/emoticon_xiaoqi_gt.png\",\n \"[小七_鄙视]\": \"${baser_url}\/static\/emoji\/emoticon_xiaoqi_bs.png\",\n \"[小七_高兴]\": \"${baser_url}\/static\/emoji\/emoticon_xiaoqi_gx.png\",\n \"[小七_黑人问号]\": \"${baser_url}\/static\/emoji\/emoticon_xiaoqi_hrwh.png\",\n \"[小七_厉害]\": \"${baser_url}\/static\/emoji\/emoticon_xiaoqi_lh.png\",\n \"[小七_流泪]\": \"${baser_url}\/static\/emoji\/emoticon_xiaoqi_ll.png\",\n \"[小七_OK]\": \"${baser_url}\/static\/emoji\/emoticon_xiaoqi_ok.png\",\n \"[小七_色]\": \"${baser_url}\/static\/emoji\/emoticon_xiaoqi_s.png\",\n \"[小七_伤心]\": \"${baser_url}\/static\/emoji\/emoticon_xiaoqi_sx.png\",\n \"[小七_调皮]\": \"${baser_url}\/static\/emoji\/emoticon_xiaoqi_tq.png\",\n \"[小七_委屈]\": \"${baser_url}\/static\/emoji\/emoticon_xiaoqi_wh.png\",\n \"[小七_笑哭]\": \"${baser_url}\/static\/emoji\/emoticon_xiaoqi_xk.png\",\n \"[小七_笑嘻嘻]\": \"${baser_url}\/static\/emoji\/emoticon_xiaoqi_xxy.png\",\n \"[小七_斜眼笑]\": \"${baser_url}\/static\/emoji\/emoticon_xiaoqi_xyx.png\",\n \"[小七_赞美]\": \"${baser_url}\/static\/emoji\/emoticon_xiaoqi_zm.png\",\n \"[眯眼笑]\": \"${baser_url}\/static\/emoji\/emoticon_xiaoqi_xxy.png\"\n };\n\n \/\/ 初始化\n document.addEventListener('DOMContentLoaded', () => {\n initTheme();\n initSortButtons();\n\n \/\/ 隐藏底部输入栏,使用顶部评论按钮\n document.body.style.paddingBottom = '20px';\n\n document.getElementById('commentTextarea').addEventListener('input', updateCharCount);\n\n initEmojiPanel(); \/\/ 初始化表情面板\n initInfiniteScroll();\n loadComments();\n });\n\n function initTheme() {\n let readerSettings = localStorage.getItem('readerSettings');\n let theme = localStorage.getItem('theme');\n if (readerSettings) {\n try {\n const parsed = JSON.parse(readerSettings);\n if (parsed.theme !== undefined) {\n theme = parsed.theme;\n }\n } catch {}\n }\n \n \/\/ 设置默认值为auto\n if (!theme) {\n theme = 'auto';\n localStorage.setItem('theme', 'auto');\n }\n \n updateTheme(theme);\n }\n\n function updateTheme(theme) {\n const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;\n let isDark;\n \n if (theme === 'auto') {\n isDark = prefersDark;\n } else {\n isDark = theme === 'dark';\n }\n \n if (isDark) {\n document.documentElement.classList.add('dark');\n document.body.classList.add('dark');\n } else {\n document.documentElement.classList.remove('dark');\n document.body.classList.remove('dark');\n }\n \n updateThemeIcon(theme);\n }\n\n function updateThemeIcon(theme) {\n const icon = document.getElementById('themeIcon');\n \n if (theme === 'auto') {\n icon.className = 'fas fa-adjust';\n } else if (theme === 'dark') {\n icon.className = 'fas fa-sun';\n } else {\n icon.className = 'fas fa-moon';\n }\n }\n\n function toggleTheme() {\n let currentTheme = localStorage.getItem('theme');\n let newTheme;\n \n if (currentTheme === 'auto') {\n \/\/ From auto to opposite of current system preference\n const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;\n newTheme = prefersDark ? 'light' : 'dark';\n } else if (currentTheme === 'light') {\n newTheme = 'dark';\n } else {\n newTheme = 'auto';\n }\n \n localStorage.setItem('theme', newTheme);\n updateTheme(newTheme);\n }\n\n \/\/ Listen for system theme changes in auto mode\n window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {\n const currentTheme = localStorage.getItem('theme');\n if (currentTheme === 'auto') {\n updateTheme('auto');\n }\n });\n\n function initInfiniteScroll() {\n let scrollTimeout;\n let lastScrollTime = 0;\n \n window.addEventListener('scroll', () => {\n const now = Date.now();\n if (now - lastScrollTime < 100) return; \/\/ 节流,100ms内不重复处理\n lastScrollTime = now;\n \n clearTimeout(scrollTimeout);\n scrollTimeout = setTimeout(() => {\n if (config.isLoading || !config.hasMore) return;\n \n const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;\n const windowHeight = window.innerHeight;\n const documentHeight = document.documentElement.scrollHeight;\n const distance = documentHeight - (scrollTop + windowHeight);\n\n \/\/ 增加触发距离,避免过早加载\n if (distance < 100 && distance > -50) {\n loadComments();\n }\n }, 150); \/\/ 防抖,150ms后执行\n }, { passive: true });\n }\n\n function initSortButtons() {\n const sortByHot = document.getElementById('sortByHot');\n if (config.source === '七猫') {\n sortByHot.innerHTML = '<i class=\"fas fa-history\"><\/i> 最早';\n }\n }\n\n function initEmojiPanel() {\n const emojiGrid = document.getElementById('emojiGrid');\n if (!emojiGrid) return;\n\n Object.entries(emojiMap).forEach(([text, src]) => {\n const btn = document.createElement('button');\n btn.type = 'button';\n btn.className = 'emoji-btn';\n btn.innerHTML = \\`<img src=\"\\${src}\" alt=\"\\${text}\">\\`;\n btn.onclick = (e) => {\n e.preventDefault();\n e.stopPropagation();\n insertEmoji(text);\n };\n emojiGrid.appendChild(btn);\n });\n }\n\n function toggleEmojiPanel(e) {\n e.preventDefault();\n e.stopPropagation();\n document.getElementById('emojiPanel').classList.toggle('show');\n }\n\n function insertEmoji(text) {\n const textarea = document.getElementById('commentTextarea');\n const start = textarea.selectionStart;\n const end = textarea.selectionEnd;\n const textBefore = textarea.value.substring(0, start);\n const textAfter = textarea.value.substring(end);\n textarea.value = textBefore + text + textAfter;\n textarea.focus();\n textarea.selectionStart = textarea.selectionEnd = start + text.length;\n updateCharCount();\n document.getElementById('emojiPanel').classList.remove('show');\n }\n\n function sortComments(sortType) {\n if (config.currentSort === sortType) return;\n config.currentSort = sortType;\n\n document.querySelectorAll('.sort-btn').forEach(btn => btn.classList.remove('active'));\n if (sortType === 'time') {\n document.getElementById('sortByTime').classList.add('active');\n } else {\n document.getElementById('sortByHot').classList.add('active');\n }\n\n let sortedComments = [...config.allComments];\n \n if (config.source === '七猫') {\n if (sortType === 'hot') {\n sortedComments.reverse();\n }\n } else {\n if (sortType === 'hot') {\n sortedComments.sort((a, b) => (b.like_count || 0) - (a.like_count || 0));\n } else {\n sortedComments.sort((a, b) => (b.create_timestamp || 0) - (a.create_timestamp || 0));\n }\n }\n\n config.comments = sortedComments;\n renderComments(sortedComments, false);\n }\n\n async function loadComments(retryCount = 0) {\n if (config.isLoading) return;\n \n \/\/ 预检查:如果已经确定没有更多数据,直接返回\n if (!config.cursor && config.comments.length > 0) {\n console.log('没有更多数据可加载(cursor为空)');\n return;\n }\n \n \/\/ 如果已经有大量数据但cursor为空,认为已经加载完毕\n if (!config.cursor && config.comments.length > 20) {\n console.log('数据量充足且cursor为空,停止加载');\n config.hasMore = false;\n return;\n }\n \n config.isLoading = true;\n\n const isFirstLoad = !config.cursor;\n if (!isFirstLoad) {\n document.getElementById('noMore').classList.add('hidden');\n showLoadingMore(true);\n }\n\n try {\n let url = \\`${baser_url}\/para_review?book_id=\\${config.bookId}&item_id=\\${config.itemId}¶=\\${config.currentPara}&source=\\${encodeURIComponent(config.source)}\\`;\n \n if (config.cursor) {\n url += \\`&cursor=\\${encodeURIComponent(config.cursor)}\\`;\n }\n\n \/\/ 添加超时控制\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 10000); \/\/ 10秒超时\n\n const response = await fetch(url, {\n signal: controller.signal,\n headers: {\n 'Accept': 'application\/json',\n 'Content-Type': 'application\/json'\n }\n });\n \n clearTimeout(timeoutId);\n \n if (!response.ok) {\n throw new Error(\\`HTTP \\${response.status}: \\${response.statusText}\\`);\n }\n \n const result = await response.json();\n\n document.getElementById('loading').classList.add('hidden');\n showLoadingMore(false);\n\n if (result.code === 0 && result.data) {\n const data = result.data;\n\n if (isFirstLoad) {\n \/\/ 修复:如果 total 为 0 但实际有评论,使用评论数组长度\n const commentCount = data.total || \n (data.comments?.length || 0) + (data.hot_comments?.length || 0);\n document.getElementById('headerTitle').textContent = commentCount > 0 \n ? \\`\\${commentCount}条评论\\` \n : '评论';\n }\n\n if (isFirstLoad && data.paragraph_content) {\n const paraEl = document.getElementById('paragraphQuote');\n paraEl.textContent = data.paragraph_content.length > 100 \n ? data.paragraph_content.substring(0, 100) + '...' \n : data.paragraph_content;\n paraEl.classList.remove('hidden');\n }\n\n if (isFirstLoad && data.hot_comments && data.hot_comments.length > 0) {\n config.hotComments = data.hot_comments;\n renderHotComments(data.hot_comments);\n document.getElementById('hotSection').classList.remove('hidden');\n document.getElementById('moreSection').classList.remove('hidden');\n document.getElementById('moreCount').textContent = \\`(\\${data.total || 0})\\`;\n }\n\n \/\/ 处理评论数据(包括空数据情况)\n const comments = data.comments || [];\n if (comments.length > 0) {\n if (isFirstLoad) {\n config.allComments = [...comments];\n config.comments = [...comments];\n renderComments(comments, false);\n } else {\n config.allComments = config.allComments.concat(comments);\n config.comments = config.comments.concat(comments);\n renderComments(comments, true);\n }\n }\n\n \/\/ 更严谨的状态判断\n const hasMoreFromServer = data.has_more;\n const nextCursor = data.next_cursor || '';\n \n \/\/ 如果服务器明确返回hasMore=false,或者没有下一页游标且当前页数据为空,则停止加载\n config.hasMore = hasMoreFromServer === true && nextCursor !== '' && comments.length > 0;\n \n \/\/ 特殊情况:如果服务器返回hasMore=true但没有数据,可能是后端bug,保守处理\n if (hasMoreFromServer === true && comments.length === 0) {\n console.warn('服务器返回hasMore=true但没有数据,停止加载以避免死循环');\n config.hasMore = false;\n }\n \n config.lastCursor = config.cursor; \/\/ 保存上一个cursor\n config.cursor = nextCursor;\n\n \/\/ 调试日志\n console.log(\\`加载完成: 当前页\\${comments.length}条, hasMore=\\${config.hasMore}, cursor='\\${config.cursor}'\\`);\n \n if (!config.hasMore && config.comments.length > 0) {\n document.getElementById('noMore').classList.remove('hidden');\n console.log('已到达最后一页,停止加载更多');\n }\n\n if (config.comments.length === 0 && config.hotComments.length === 0) {\n document.getElementById('empty').classList.remove('hidden');\n }\n } else {\n if (isFirstLoad) {\n document.getElementById('empty').classList.remove('hidden');\n }\n }\n } catch (error) {\n console.error('加载评论失败:', error);\n document.getElementById('loading').classList.add('hidden');\n showLoadingMore(false);\n \n \/\/ 更详细的错误处理\n let errorMsg = '加载失败,请重试';\n if (error.name === 'AbortError') {\n errorMsg = '请求超时,请检查网络后重试';\n } else if (error.message.includes('HTTP')) {\n errorMsg = '服务器错误,请稍后重试';\n } else if (error.message.includes('Failed to fetch')) {\n errorMsg = '网络连接失败,请检查网络';\n }\n \n showToast(errorMsg);\n \n \/\/ 重置cursor,允许重试\n if (!isFirstLoad) {\n config.cursor = config.lastCursor || '';\n }\n \n \/\/ 自动重试机制\n if (retryCount < config.maxRetryCount && !isFirstLoad) {\n const retryDelay = Math.min(1000 * Math.pow(2, retryCount), 5000); \/\/ 指数退避,最大5秒\n console.log(\\`加载失败,\\${retryDelay\/1000}秒后重试(\\${retryCount + 1}\/\\${config.maxRetryCount})\\`);\n \n setTimeout(() => {\n loadComments(retryCount + 1);\n }, retryDelay);\n \n \/\/ 显示重试提示\n showToast(\\`加载失败,\\${retryDelay\/1000}秒后自动重试\\`);\n return; \/\/ 不重置isLoading,让重试逻辑处理\n }\n } finally {\n config.isLoading = false;\n }\n }\n\n function showLoadingMore(show) {\n let loadingMore = document.getElementById('loadingMore');\n if (!loadingMore) {\n loadingMore = document.createElement('div');\n loadingMore.id = 'loadingMore';\n loadingMore.className = 'loading';\n loadingMore.style.padding = '20px';\n loadingMore.style.opacity = '0';\n loadingMore.style.transition = 'opacity 0.3s ease';\n loadingMore.innerHTML = '<div class=\"loading-spinner\" style=\"width:24px;height:24px;margin-bottom:8px;\"><\/div><span style=\"font-size:13px;\">加载更多...<\/span>';\n document.querySelector('.main-container').appendChild(loadingMore);\n \n \/\/ 强制重排后添加动画\n setTimeout(() => {\n loadingMore.style.opacity = '1';\n }, 10);\n }\n \n if (show) {\n loadingMore.style.display = 'flex';\n setTimeout(() => {\n loadingMore.style.opacity = '1';\n }, 10);\n } else {\n loadingMore.style.opacity = '0';\n setTimeout(() => {\n loadingMore.style.display = 'none';\n }, 300);\n }\n }\n\n function renderHotComments(comments) {\n const container = document.getElementById('hotCommentList');\n container.innerHTML = '';\n document.getElementById('hotCount').textContent = \\`(\\${comments.length})\\`;\n\n comments.forEach(comment => {\n container.appendChild(createCommentElement(comment, 'hot'));\n });\n }\n\n function renderComments(comments, append = false) {\n const container = document.getElementById('commentList');\n if (!append) {\n container.innerHTML = '';\n }\n\n comments.forEach(comment => {\n container.appendChild(createCommentElement(comment, 'normal'));\n });\n }\n\n function createCommentElement(comment, type) {\n const div = document.createElement('div');\n div.className = 'comment-item';\n div.dataset.commentId = comment.comment_id;\n\n \/\/ 用户标签\n let tagsHtml = '';\n if (comment.user && comment.user.is_author) {\n tagsHtml += '<span class=\"comment-tag tag-author\">作者<\/span>';\n }\n if (comment.user && comment.user.is_vip) {\n tagsHtml += '<span class=\"comment-tag tag-vip\">VIP<\/span>';\n }\n if (comment.user && comment.user.tags) {\n comment.user.tags.forEach(tag => {\n tagsHtml += \\`<span class=\"comment-tag tag-custom\">\\${tag}<\/span>\\`;\n });\n }\n\n \/\/ 图片\n let imageHtml = '';\n if (comment.image_url) {\n imageHtml = \\`<img class=\"comment-image\" src=\"\\${comment.image_url}\" onclick=\"openImagePreview('\\${comment.image_url}')\" alt=\"\" onerror=\"this.style.display='none'\">\\`;\n }\n\n \/\/ 展开回复逻辑\n let expandHtml = '';\n let replyListHtml = '';\n \n const inlineReplyCount = (comment.replies && comment.replies.length) || 0;\n const serverReplyCount = comment.reply_count || 0;\n \n \/\/ 只有当有回复时才显示展开按钮\n if (serverReplyCount > 0 || inlineReplyCount > 0) {\n const uniqueId = \\`\\${type}-\\${comment.comment_id}\\`;\n const totalReplies = Math.max(serverReplyCount, inlineReplyCount);\n \n \/\/ 七猫:始终从接口获取回复(内嵌回复只是预览)\n if (config.source === '七猫') {\n expandHtml = \\`\n <div class=\"expand-replies\" onclick=\"toggleReplies('\\${uniqueId}', '\\${comment.comment_id}')\">\n 展开\\${totalReplies}条回复\n <i class=\"fas fa-chevron-down\"><\/i>\n <\/div>\n \\`;\n replyListHtml = \\`<div class=\"reply-list\" id=\"replies-\\${uniqueId}\"><\/div>\\`;\n } else if (inlineReplyCount > 0) {\n \/\/ 番茄\/塔读:有内嵌回复,默认收起,标记已加载\n replyListHtml = \\`<div class=\"reply-list\" id=\"replies-\\${uniqueId}\" data-loaded=\"true\">\\${renderRepliesHtml(comment.replies)}<\/div>\\`;\n expandHtml = \\`\n <div class=\"expand-replies\" onclick=\"toggleReplies('\\${uniqueId}', '\\${comment.comment_id}')\">\n 展开\\${totalReplies}条回复\n <i class=\"fas fa-chevron-down\"><\/i>\n <\/div>\n \\`;\n } else {\n \/\/ 没有内嵌回复,需要从接口加载\n expandHtml = \\`\n <div class=\"expand-replies\" onclick=\"toggleReplies('\\${uniqueId}', '\\${comment.comment_id}')\">\n 展开\\${totalReplies}条回复\n <i class=\"fas fa-chevron-down\"><\/i>\n <\/div>\n \\`;\n replyListHtml = \\`<div class=\"reply-list\" id=\"replies-\\${uniqueId}\"><\/div>\\`;\n }\n }\n\n \/\/ 头像\n const userName = comment.user?.user_name || '匿名用户';\n const userAvatar = comment.user?.user_avatar || '';\n const avatarHtml = userAvatar \n ? \\`<img src=\"\\${userAvatar}\" alt=\"\" onerror=\"this.style.visibility='hidden'\">\\`\n : \\`<div class=\"default-avatar\"><i class=\"fas fa-user\"><\/i><\/div>\\`;\n\n div.innerHTML = \\`\n <div class=\"comment-main\">\n <div class=\"comment-avatar\">\n \\${avatarHtml}\n <\/div>\n <div class=\"comment-body\">\n <div class=\"comment-header\">\n <span class=\"comment-username\">\\${userName}<\/span>\n \\${tagsHtml}\n <\/div>\n <div class=\"comment-content\">\\${convertEmoji(comment.content || '')}<\/div>\n \\${imageHtml}\n <div class=\"comment-footer\">\n <div class=\"comment-meta\">\n <span class=\"comment-time\">\\${comment.create_time || ''}<\/span>\n \\${config.source === '番茄' ? '<span class=\"comment-reply-btn\" style=\"display:none;\">回复<\/span>' : ''}\n <\/div>\n <div class=\"comment-likes\">\n <i class=\"far fa-heart\"><\/i>\n <span>\\${comment.like_count || 0}<\/span>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n \\${expandHtml}\n \\${replyListHtml}\n \\`;\n\n return div;\n }\n\n function renderRepliesHtml(replies) {\n if (!replies || replies.length === 0) return '';\n\n return replies.map(reply => {\n let replyToHtml = '';\n if (reply.reply_to_user && reply.reply_to_user.user_name) {\n replyToHtml = \\`\n <span class=\"reply-to\">回复<\/span>\n <span class=\"reply-to-username\">\\${reply.reply_to_user.user_name}<\/span>\n \\`;\n }\n\n let tagsHtml = '';\n if (reply.user && reply.user.is_author) {\n tagsHtml += '<span class=\"comment-tag tag-author\" style=\"margin-left:6px;\">作者<\/span>';\n }\n\n const userName = reply.user?.user_name || '匿名';\n const userAvatar = reply.user?.user_avatar || '';\n const avatarHtml = userAvatar \n ? \\`<img src=\"\\${userAvatar}\" alt=\"\" onerror=\"this.style.visibility='hidden'\">\\`\n : \\`<div class=\"default-avatar small\"><i class=\"fas fa-user\"><\/i><\/div>\\`;\n\n return \\`\n <div class=\"reply-item\">\n <div class=\"reply-main\">\n <div class=\"reply-avatar\">\n \\${avatarHtml}\n <\/div>\n <div class=\"reply-body\">\n <div class=\"reply-header\">\n <span class=\"reply-username\">\\${userName}<\/span>\n \\${tagsHtml}\n \\${replyToHtml}\n <\/div>\n <div class=\"reply-content\">\\${convertEmoji(reply.content || '')}<\/div>\n <div class=\"reply-footer\">\n <span class=\"reply-time\">\\${reply.create_time || ''}<\/span>\n <div class=\"reply-likes\">\n <i class=\"far fa-heart\"><\/i>\n <span>\\${reply.like_count || 0}<\/span>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n <\/div>\n \\`;\n }).join('');\n }\n\n async function toggleReplies(uniqueId, commentId) {\n const container = document.getElementById(\\`replies-\\${uniqueId}\\`);\n const expandLink = document.querySelector(\\`[onclick*=\"toggleReplies('\\${uniqueId}'\"]\\`);\n const isExpanded = container.classList.contains('show');\n\n if (isExpanded) {\n \/\/ 收起\n container.classList.remove('show');\n const replyCount = container.querySelectorAll('.reply-item').length;\n expandLink.innerHTML = \\`展开\\${replyCount}条回复 <i class=\"fas fa-chevron-down\"><\/i>\\`;\n expandLink.classList.remove('expanded');\n } else {\n \/\/ 展开\n if (container.dataset.loaded === 'true') {\n \/\/ 已有数据,直接显示\n container.classList.add('show');\n expandLink.innerHTML = '收起回复 <i class=\"fas fa-chevron-down\"><\/i>';\n expandLink.classList.add('expanded');\n } else {\n \/\/ 需要加载\n expandLink.innerHTML = '加载中... <i class=\"fas fa-spinner fa-spin\"><\/i>';\n container.classList.add('show');\n\n try {\n const url = \\`${baser_url}\/para_review?book_id=\\${config.bookId}&item_id=\\${config.itemId}&comment_id=\\${commentId}&source=\\${encodeURIComponent(config.source)}\\`;\n const response = await fetch(url);\n const result = await response.json();\n\n if (result.code === 0 && result.data && result.data.replies && result.data.replies.length > 0) {\n container.innerHTML = renderRepliesHtml(result.data.replies);\n container.dataset.loaded = 'true';\n expandLink.innerHTML = '收起回复 <i class=\"fas fa-chevron-down\"><\/i>';\n expandLink.classList.add('expanded');\n } else {\n container.innerHTML = '<div style=\"padding:16px;color:#999;font-size:13px;text-align:center;\">暂无回复<\/div>';\n container.dataset.loaded = 'true';\n expandLink.innerHTML = '收起 <i class=\"fas fa-chevron-down\"><\/i>';\n expandLink.classList.add('expanded');\n }\n } catch (error) {\n console.error('加载回复失败:', error);\n container.innerHTML = '<div style=\"padding:16px;color:#ff4d4f;font-size:13px;text-align:center;\">加载失败,点击重试<\/div>';\n container.classList.remove('show');\n expandLink.innerHTML = '展开回复 <i class=\"fas fa-chevron-down\"><\/i>';\n }\n }\n }\n }\n\n function convertEmoji(text) {\n if (!text) return '';\n Object.entries(emojiMap).forEach(([key, src]) => {\n const regex = new RegExp(key.replace(\/[.*+?^\\${}()|[\\\\]\\\\\\\\]\/g, '\\\\\\\\$&'), 'g');\n text = text.replace(regex, \\`<img src=\"\\${src}\" class=\"bq\" alt=\"\\${key}\">\\`);\n });\n return text;\n }\n\n function openCommentModal() {\n if (config.source !== '番茄') {\n showToast('当前平台暂不支持评论');\n return;\n }\n document.getElementById('commentModal').classList.add('show');\n document.getElementById('commentTextarea').focus();\n }\n\n function closeCommentModal(event) {\n if (event && event.target !== document.getElementById('commentModal')) return;\n document.getElementById('commentModal').classList.remove('show');\n document.getElementById('emojiPanel').classList.remove('show');\n }\n\n function updateCharCount() {\n const textarea = document.getElementById('commentTextarea');\n const charCount = document.getElementById('charCount');\n const length = textarea.value.length;\n charCount.textContent = \\`\\${length}\/500\\`;\n charCount.classList.toggle('over', length > 500);\n }\n\n async function submitComment() {\n const content = document.getElementById('commentTextarea').value.trim();\n if (!content) {\n showToast('请输入评论内容');\n return;\n }\n if (content.length > 500) {\n showToast('评论内容不能超过500字');\n return;\n }\n\n try {\n const url = \\`${baser_url}\/post_idea_review?ssionid=\\${config.ssionid}\\`;\n const response = await fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application\/json' },\n body: JSON.stringify({\n text: content,\n book_id: config.bookId,\n item_id: config.itemId,\n para: config.currentPara,\n para_content: content\n })\n });\n const result = await response.json();\n\n if (result.code === 0) {\n showToast('发表成功');\n closeCommentModal();\n document.getElementById('commentTextarea').value = '';\n updateCharCount();\n config.cursor = '';\n config.allComments = [];\n config.comments = [];\n config.hotComments = [];\n loadComments();\n } else {\n showToast(result.msg || '发表失败');\n }\n } catch (error) {\n console.error('发表评论失败:', error);\n showToast('发表失败,请重试');\n }\n }\n\n function openImagePreview(url) {\n document.getElementById('previewImage').src = url;\n document.getElementById('imagePreview').classList.add('show');\n }\n\n function closeImagePreview() {\n document.getElementById('imagePreview').classList.remove('show');\n }\n\n function showToast(message) {\n const toast = document.getElementById('toast');\n toast.textContent = message;\n toast.classList.add('show');\n setTimeout(() => toast.classList.remove('show'), 2000);\n }\n <\/script>\n<\/body>\n<\/html>`\n}\n\n\n\/\/按钮切换时改变源变量并刷新发现\nfunction show(m, t) {\n const {\n java,\n source\n } = this;\n \/\/源变量为空时刷新发现,重置源变量\n source.getVariable()==''?java.refreshExplore():'';\/\/\n var data = JSON.parse(source.getVariable());\n \/\/java.log(data);\n data[t] = m;\n \/\/java.log(JSON.stringify(data));\n source.setVariable(JSON.stringify(data, null, 2));\n java.refreshExplore();\n}\n\n\/**\n * 创建筛选器配置\n * @param {string} title - 筛选器标题\n * @param {string[]} chars - 选项列表\n * @param {string} defaultVal - 默认值\n * @param {string} paramKey - URL参数名\n * @param {number} size - 布局占比(0-1之间,如0.33表示33%)\n * @returns {Object} 筛选器配置对象\n *\/\nfunction createFilter(title, chars, defaultVal, paramKey, size) {\n return {\n title: title,\n type: \"select\",\n chars: chars,\n default: defaultVal,\n action: `show(infoMap['${title}'],'${paramKey}')`,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: size\n }\n };\n}",
"lastUpdateTime": "1776955514899",
"loginUi": "[{\n \"name\": \"邮箱\",\n \"type\": \"text\"\n }, {\n \"name\": \"密码\",\n \"type\": \"password\"\n }, {\n \"name\": \"♥登录书源\",\n \"type\": \"button\",\n \"action\": \"login(true)\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"🔐注册书源\",\n \"type\": \"button\",\n \"action\": \"register()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"⛔️清空设置\",\n \"type\": \"button\",\n \"action\": \"reCookieSettings()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"✨应用设置\",\n \"type\": \"button\",\n \"action\": \"applyCookieSettings()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \" 🔚 退出登录\",\n \"type\": \"button\",\n \"action\": \"logout()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },\n {\n \"name\": \"🏝用户后台\",\n \"type\": \"button\",\n \"action\": \"loginqt()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \" 🗑 清除设备\",\n \"type\": \"button\",\n \"action\": \"clearDevice()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },\n {\n \"name\": \"🔮 检测登录\",\n \"type\": \"button\",\n \"action\": \"checkStatus()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"☕打赏享福利\",\n \"type\": \"button\",\n \"action\": \"vip()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"❇️ 更新书源\",\n \"type\": \"button\",\n \"action\": \"renderVersionPage()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },\n {\n \"name\": \"==下方配置为书源设置页面无法打开时使用==\",\n \"type\": \"button\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 1\n }\n }, {\n \"name\": \"📑更少简介\",\n \"type\": \"button\",\n \"action\": \"set_info()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"📝段评开关\",\n \"type\": \"button\",\n \"action\": \"paracomment('fqpara')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"📚 同步书架\",\n \"type\": \"button\",\n \"action\": \"set_reading()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"♋️ 男\/女频道\",\n \"type\": \"button\",\n \"action\": \"set_source_type()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"💢 强制搜索全部\",\n \"type\": \"button\",\n \"action\": \"disabledSources()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"🍅番茄登录\",\n \"type\": \"button\",\n \"action\": \"fq_login()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"➿️ 图片显示\",\n \"type\": \"button\",\n \"action\": \"closeImg()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"📐 使用教程\",\n \"type\": \"button\",\n \"action\": \"jc()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"🗂当前模式\",\n \"type\": \"button\",\n \"action\": \"get_media()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 1\n }\n }, {\n \"name\": \"📖小说模式\",\n \"type\": \"button\",\n \"action\": \"set_media('小说')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"🔊听书模式\",\n \"type\": \"button\",\n \"action\": \"set_media('听书')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"🏞漫画模式\",\n \"type\": \"button\",\n \"action\": \"set_media('漫画')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"🖲短剧模式\",\n \"type\": \"button\",\n \"action\": \"set_media('短剧')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"🎚切换服务器\",\n \"type\": \"button\",\n \"action\": \"set_server()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"♻️检测当前服务器\",\n \"type\": \"button\",\n \"action\": \"checkNet()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"⚕️本地\/服务器 \",\n \"type\": \"button\",\n \"action\": \"get_proxy()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"📌永久发布页📌\",\n \"type\": \"button\",\n \"action\": \"api()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"↓↓下方可切换来源用于搜索\/发现页↓↓\",\n \"type\": \"button\",\n \"action\": \"get_media()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 1\n }\n }, {\n \"name\": \"💖我来推荐\",\n \"type\": \"button\",\n \"action\": \"put_book()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"全部\",\n \"type\": \"button\",\n \"action\": \"set_source('全部')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('番茄')\",\n 'name': '番茄',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('69书吧')\",\n 'name': '69书吧',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('七猫')\",\n 'name': '七猫',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('百度')\",\n 'name': '百度',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('起点')\",\n 'name': '起点(第三方)',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('得间')\",\n 'name': '得间',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('塔读')\",\n 'name': '塔读',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('书旗')\",\n 'name': '书旗',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('QQ')\",\n 'name': 'QQ',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('猫眼')\",\n 'name': '猫眼',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('搜书神器')\",\n 'name': '搜书神器',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('得奇')\",\n 'name': '得奇',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('伪69')\",\n 'name': '伪69',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('爱下电子书')\",\n 'name': '爱下电子书',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('小米')\",\n 'name': '小米',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('星星小说')\",\n 'name': '星星小说',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('笔趣阁22')\",\n 'name': '笔趣阁22',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('顶点')\",\n 'name': '顶点',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('幻梦轻小说')\",\n 'name': '幻梦轻小说',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('江湖')\",\n 'name': '江湖',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('歪瑞古德')\",\n 'name': '歪瑞古德漫画',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('河马')\",\n 'name': '河马短剧',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('知乎')\",\n 'name': '知乎',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('vip')\",\n 'name': '下方为VIP专属书源(点击此处搜所有vip)',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 1\n }\n }, {\n 'action': \"set_source('喜马拉雅')\",\n 'name': '喜马拉雅',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('svip')\",\n 'name': '下方为SVIP专属书源(点击此处搜所有svip)',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 1\n }\n }, {\n 'action': \"set_source('超会专属短剧')\",\n 'name': '超会专属短剧',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('超会专属小说')\",\n 'name': '超会专属小说',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('酷我')\",\n 'name': '酷我',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('台湾小说')\",\n 'name': '台湾小说',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('3A小说')\",\n 'name': '3A小说',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('包子漫画')\",\n 'name': '包子漫画',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, { \t \t\n 'action': \"set_source('QQ阅读')\",\n 'name': 'QQ(会员书籍免费)',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('久久小说')\",\n 'name': '久久小说',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \t 'action': \"set_source('追更人')\",\n 'name': '追更人',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('漫画屋')\",\n 'name': '漫画屋',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('漫画网')\",\n 'name': '漫画网',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('52书库')\",\n 'name': '52书库',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('腐小说')\",\n 'name': '腐小说',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('完本小说')\",\n 'name': '完本小说',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('盐选文库')\",\n 'name': '盐选文库',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, { \t \t\n 'action': \"set_source('有度中文')\",\n 'name': '有度中文',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('三零读书')\",\n 'name': '三零读书',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('鹿鹿')\",\n 'name': '鹿鹿',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('阅友小说')\",\n 'name': '阅友小说',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('星空小说')\",\n 'name': '星空小说',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('全免漫画')\",\n 'name': '全免漫画',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('365小说')\",\n 'name': '365小说',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('冷冷文学')\",\n 'name': '冷冷文学',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('万相书城')\",\n 'name': '万相书城',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('幻梦轻小说')\",\n 'name': '幻梦轻小说',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\t\n 'action': \"set_source('独步小说')\",\n 'name': '独步小说',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('福书网')\",\n 'name': '福书网',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('速读谷')\",\n 'name': '速读谷',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('全本同人')\",\n 'name': '全本同人',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('漫客栈')\",\n 'name': '漫客栈',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('河马')\",\n 'name': '河马短剧',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \t 'action': \"set_source('极速影视')\",\n 'name': '极速影视',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n 'action': \"set_source('旺旺短剧')\",\n 'name': '旺旺短剧',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, { \n \n 'action': \"set_source('毒舌影视')\",\n 'name': '毒舌影视',\n 'type': 'button',\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n }, {\n \"name\": \"自定义搜索源(多个用英文,分割)\",\n \"type\": \"text\"\n }, {\n \"name\": \"自定义服务器(可不填)\",\n \"type\": \"text\"\n }, {\n \"name\": \"自定义评论颜色(可不填)\",\n \"type\": \"text\"\n }, {\n \"name\": \"段评气泡样式(0-4)\",\n \"type\": \"text\"\n }, {\n \"name\": \"手动填写番茄token(可不填)\",\n \"type\": \"text\"\n }\n]",
"loginUrl": "\/\/ 当前书源版本号,切勿修改,否则影响更新的识别\nconst localVersion = '5.4.23.1';\n\nlet controlUrl = host[0];\n\nfunction login(flag) {\n if (flag == undefined) {\n result = JSON.parse(source.getLoginInfo())\n } else {\n java.longToast(\"\\n\\n💞正在登录中...\")\n putLoginInfo(JSON.stringify(result))\n }\n let base_url = getArgument('server')\n let zdyserver = String(result['自定义服务器(可不填)']);\n if (zdyserver.includes('http')) {\n setArgument('server', zdyserver);\n if (getKey(String(cookie.getCookie(base_url)))) {\n let cookies = cookie.getCookie(base_url)\n try {\n cookie.removeCookie(base_url)\n } catch (e) {}\n cookie.setCookie(zdyserver, cookies)\n }\n java.toast(`\\n\\n当前服务器为自定义服务器\\n${zdyserver}\\n\\n切换服务器请先清空服务器地址中的数据`);\n }\n let plcolor = String(getArgument('plcolor') || result['自定义评论颜色(可不填)'] || '#000000');\n if (plcolor) {\n setArgument('plcolor', plcolor);\n } else {\n setArgument('plcolor', '#000000');\n }\n let zdysources = String(result['自定义搜索源(多个用英文,分割)'] || '');\n if (zdysources) {\n setArgument('sources', zdysources);\n };\n base_url = getArgument('server')\n let register_email = String(result['邮箱'])\n let password = String(result['密码'])\n let key = String(result['密钥'] || '')\n\n if ((register_email && password || key) && !String(getKey(String(cookie.getCookie(base_url))))) {\n try {\n cookie.removeCookie(base_url)\n } catch (e) {}\n let deviceKey = '';\n try {\n deviceKey = java.deviceID();\n } catch (e) {\n deviceKey = java.androidId();\n };\n let deviceId = deviceKey;\n if (register_email && password) {\n let options = JSON.stringify({\n method: 'POST',\n headers: {\n 'Content-Type': 'application\/json'\n },\n body: JSON.stringify({\n register_email: result['邮箱'],\n password: result['密码']\n })\n })\n try {\n let data = JSON.parse(java.ajax(`${base_url}\/login_api,${options}`))\n if (data.code == 0) {\n \/\/java.toast(deviceId)\n java.toast(\"\\n\\n✅️登录成功\")\n cookie.setCookie(base_url, `qttoken=${data.key};deviceId=${deviceId}`)\n result['密钥'] = data.key\n setArgument('qttoken', data.key);\n putLoginInfo(JSON.stringify(result))\n } else {\n java.toast('\\n\\n💔' + data.msg || \"登录失败,请重试!\")\n }\n } catch (e) {\n java.toast(\"\\n\\n💔登录失败,请重试!\\n\" + e.message)\n }\n } else {\n cookie.setCookie(base_url, `qttoken=${key};deviceId=${deviceId}`)\n let res = java.ajax(`${base_url}\/user_api,{\"method\":\"POST\",\"headers\":{\"cookie\":\"${cookie.getCookie(base_url)}\"}}`)\n try {\n res = JSON.parse(res)\n if (res.id != undefined) {\n setArgument('qttoken', key);\n java.toast('\\n\\n密钥登录成功')\n result['邮箱'] = res.email\n putLoginInfo(JSON.stringify(result))\n } else {\n throw new Error()\n }\n } catch (e) {\n java.log(e)\n java.toast(\"\\n\\n💔登录失败\")\n }\n }\n } else if (flag && String(getKey(String(cookie.getCookie(base_url))))) {\n java.toast(\"\\n\\n当前✅️已登录,请🚫退出登录后重新登录\");\n \/\/checkStatus();\n } else if (flag) {\n java.toast(\"\\n\\n⛔️请先填写邮箱和密码\");\n }\n}\n\nfunction objectToCookieString(obj) {\n if (!obj || typeof obj !== 'object') {\n return '';\n }\n return Object.entries(obj)\n .map(([key, value]) => {\n const stringValue = String(value);\n const encodedKey = encodeURIComponent(key);\n const encodedValue = encodeURIComponent(stringValue);\n return `${encodedKey}=${encodedValue}`;\n })\n .join('; ');\n}\n\nfunction control() {\n controlUrla = getArgument('controlUrl');\n if (controlUrla) {\n controlUrla = findFirstAvailableHost([controlUrla]);\n }\n if (!controlUrla) {\n controlUrla = findFirstAvailableHost(host) || controlUrl;\n }\n setArgument('controlUrl', controlUrla);\n try {\n let open_argument = source.getVariable();\n open_argument = JSON.parse(open_argument);\n let control_cookie = objectToCookieString(open_argument);\n cookie.setCookie(controlUrla, control_cookie);\n } catch {}\n \/\/java.toast(cookie.getCookie(controlUrla))\n java.startBrowserAwait(controlUrla + '\/control', '大灰狼小说设置');\n applyCookieSettings();\n}\n\nfunction applyCookieSettings() {\n let controlUrla = getArgument('controlUrl') || controlUrl;\n var cookieStr = String(cookie.getCookie(controlUrla));\n\n var cookieData = {};\n if (cookieStr && cookieStr !== 'null' && cookieStr !== 'undefined' && cookieStr !== '') {\n var pairs = cookieStr.split('; ');\n for (var i = 0; i < pairs.length; i++) {\n var pair = pairs[i];\n var eqIndex = pair.indexOf('=');\n if (eqIndex > -1) {\n var key = pair.substring(0, eqIndex).trim();\n var value = decodeURIComponent(pair.substring(eqIndex + 1).trim());\n cookieData[key] = value;\n setArgument(key, value);\n }\n }\n }\n\n var displayConfig = [{\n key: 'server',\n label: '服务器',\n defaultValue: host[0]\n },\n {\n key: 'proxy',\n label: '访问模式',\n defaultValue: '云端'\n },\n {\n key: 'tab',\n label: '阅读模式',\n defaultValue: '小说'\n },\n {\n key: 'source_type',\n label: '阅读频道',\n defaultValue: '男频'\n },\n {\n key: 'sources',\n label: '书源选择',\n defaultValue: '全部'\n },\n {\n key: 'fqpara',\n label: '段评显示',\n defaultValue: 'on'\n },\n {\n key: 'disabled_sources',\n label: '强制搜索',\n defaultValue: '0'\n },\n {\n key: 'reading',\n label: '同步书架',\n defaultValue: '0'\n },\n {\n key: 'info',\n label: '完整简介',\n defaultValue: 'on'\n }\n ];\n\n var valueMap = {\n \/\/ 访问模式\n '本地': '💻 本地',\n '云端': '☁️ 云端',\n\n \/\/ 开关状态\n 'on': '✅ 开启',\n 'off': '❌ 关闭',\n '1': '✅ 开启',\n '0': '❌ 关闭',\n\n \/\/ 频道类型\n '男频': '👨 男频',\n '女频': '👩 女频',\n\n \/\/ 内容类型\n '小说': '📖 小说',\n '听书': '🎧 听书',\n '漫画': '🎨 漫画',\n '短剧': '🎬 短剧',\n\n \/\/ 来源类型\n 'vip': '💎 VIP专属',\n 'svip': '👑 SVIP专属',\n '推荐': '⭐ 网友推荐',\n '全部': '📚 全部'\n };\n\n\n var displayLines = [];\n\n for (var i = 0; i < displayConfig.length; i++) {\n var config = displayConfig[i];\n var value = cookieData[config.key] || config.defaultValue;\n\n if (!cookieData[config.key]) {\n setArgument(config.key, config.defaultValue);\n }\n\n var displayValue = valueMap[value] || value;\n if (config.key === 'sources' && displayValue.length > 25) {\n displayValue = displayValue.substring(0, 25) + '...';\n }\n\n displayLines.push(config.label + ': ' + displayValue);\n }\n\n \/\/ 构建提示模板\n var template = '✨ 当前配置\\n';\n template += '━━━━━━━━━━━━━━━━\\n';\n template += displayLines.join('\\n');\n template += '\\n━━━━━━━━━━━━━━━━\\n';\n template += '❤️当前应用的 *⚙️长按书源登录设置* 中的配置哦❤️'\n\n \/\/ 显示提示\n java.longToast(template);\n\n return displayLines.length;\n}\n\n\nfunction reCookieSettings() {\n let controlUrla = getArgument('controlUrl') || controlUrl;\n cookie.removeCookie(controlUrla);\n applyCookieSettings();\n\n}\n\/\/ 检测服务器\nfunction checkNet() {\n let url = getArgument('server');\n java.longToast(`\\n\\n♻️正在检测:${url}\\n请稍等~`);\n let date1 = new Date().getTime();\n let html = java.ajax(url + '\/health');\n let date2 = new Date().getTime();\n let t = date2 - date1;\n let res = {};\n try {\n res = JSON.parse(html);\n } catch {}\n let c = res.status != \"healthy\";\n let code = 1;\n let time = t \/ 1000 + 's';\n let logTime = '【' + url + '】\\n┋┋\\n' + '解析时间:' + time;\n if (c || t > 5000) {\n java.longToast('\\n💔【访问失败提示】\\n' + '┏┅━┅━┅━┅━┅┅━┅━┅┓\\n┋┋\\n' + logTime + '\\n┋┋\\n♣️当前接口无法访问(可能被墙)♣️\\n┋┋\\n请切换其他接口\/切换网络环境\\n┋┋' + '\\n┗┅━┅━┅━┅━┅┅━┅━┅┛');\n } else if (t < 1000) {\n java.longToast('\\n💖【网络环境优良】\\n' + '┏┅━┅━┅━┅━┅┅━┅━┅┓\\n┋┋\\n' + logTime + '\\n┋┋\\n❤️延迟低,推荐使用此接口❤️\\n┋┋\\n网络环境优良,请继续保持状态\\n┋┋' + '\\n┗┅━┅━┅━┅━┅┅━┅━┅┛');\n } else if (t >= 1000 && t < 2000) {\n java.longToast('\\n💛【网络环境一般】\\n' + '┏┅━┅━┅━┅━┅┅━┅━┅┓\\n┋┋\\n' + logTime + '\\n┋┋\\n♦️延迟一般,勉强可使用♦️\\n┋┋\\n请切换其他接口或切换网络环境\\n┋┋' + '\\n┗┅━┅━┅━┅━┅┅━┅━┅┛');\n } else if (t >= 2000 && t < 5000) {\n java.longToast('\\n💔【网络环境堪忧】\\n' + '┏┅━┅━┅━┅━┅┅━┅━┅┓\\n┋┋\\n' + logTime + '\\n┋┋\\n♠延迟过高,不建议使用♠\\n┋┋\\n请切换其他接口或切换网络环境\\n┋┋' + '\\n┗┅━┅━┅━┅━┅┅━┅━┅┛');\n }\n}\n\n\nfunction findFirstAvailableHost(hosts) {\n \/\/ 按顺序检测,找到第一个可用的就返回\n for (var i = 0; i < hosts.length; i++) {\n var host = hosts[i];\n\n try {\n java.longToast(\"🔍 正在检测: \" + host);\n\n var start = new Date().getTime();\n var response = java.ajax(host + \"\/health\");\n var end = new Date().getTime();\n var time = end - start;\n\n \/\/ 检查是否可用\n if (String(response).indexOf(\"大灰狼\") !== -1 && time <= 5000) {\n \/\/ 显示结果\n var status = \"✅ 可用\";\n if (time < 1000) {\n status = \"💚 优秀\";\n } else if (time < 2000) {\n status = \"💛 一般\";\n } else {\n status = \"🧡 较慢\";\n }\n java.longToast(status + \"\\n主机: \" + host + \"\\n延迟: \" + time + \"ms\");\n return host; \/\/ 直接返回找到的主机\n }\n } catch (e) {\n \/\/ 这个主机失败,继续下一个\n }\n }\n\n \/\/ 所有主机都不可用\n java.longToast(\"❌ 没有可用的主机\");\n return null;\n}\n\nfunction isVips(res) {\n let isVIP = '';\n let vipEndTime = res.vip_end_time;\n let formattedDate = '';\n\n if (vipEndTime && vipEndTime !== 0) {\n let date = new Date(vipEndTime * 1000);\n formattedDate = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;\n let currentTime = Math.floor(Date.now() \/ 1000);\n let remainingDays = Math.ceil((vipEndTime - currentTime) \/ (24 * 60 * 60));\n if (currentTime > vipEndTime) {\n isVIP = `${res.vip_level === 1 ? \"VIP\" : \"SVIP\"} (已过期)`;\n } else if (remainingDays <= 7) {\n isVIP = `${res.vip_level === 1 ? \"VIP\" : \"SVIP\"} 剩余${remainingDays}天`;\n } else {\n if (vipEndTime < 1912946812) {\n isVIP = `${res.vip_level === 1 ? \"VIP\" : \"SVIP\"}(${formattedDate})`;\n } else {\n isVIP = `${res.vip_level === 1 ? \"VIP\" : \"SVIP\"} (永久)`;\n }\n }\n } else {\n isVIP = '您尚未开通VIP';\n }\n return isVIP;\n}\n\nfunction checkStatus() {\n java.longToast('\\n\\n♻️检测中...');\n let base_url = getArgument('server')\n let res = java.ajax(`${base_url}\/user_api,{\"method\":\"POST\",\"headers\":{\"cookie\":\"${cookie.getCookie(base_url)}\"}}`)\n try {\n res = JSON.parse(res)\n if (res.id != undefined) {\n result['邮箱'] = res.email\n putLoginInfo(JSON.stringify(result))\n let devices\n try {\n devices = Object.keys(JSON.parse(res.device)).length;\n } catch (e) {\n devices = res.device ? 1 : 0;\n }\n let isVip = isVips(res);\n tips = `\n┏┅┅┅┅┅┅┱┄┄┄┄┄┄┄┄┄┄┐\n 🧢昵称 ${res.nickname.padEnd(20,\"\\t\") || \"未设置\".padEnd(20,\"\\t\")}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n ✉️邮箱 ${res.email.replace(\/(.{3}).*?@\/,\"$1***@\").padEnd(20,\"\\t\")}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n 🔑密钥 ${(`${res.user_key.substring(0,4)}***${res.user_key.slice(-4)}`).padEnd(20,\"\\t\")}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n 📅注册时间 ${java.timeFormat(res.register_time*1000).padEnd(20,\"\\t\")}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n 🗒️今日阅读 ${(java.timeFormat(new Date()).slice(0,10)==java.timeFormat(res.last_read_time * 1000).slice(0,10)?res.day_read_count:0).toString().padEnd(20,\"\\t\")}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n 📚累计阅读 ${res.all_read_count.toString().padEnd(20,\"\\t\")}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n 🕓最后阅读 ${(res.last_read_time != 0?java.timeFormat(res.last_read_time * 1000):'未阅读').padEnd(20,\"\\t\")}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n 📱在线设备 ${devices.toString().padEnd(20,\"\\t\")}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n 👑会员状态 ${isVip.padEnd(20,\"\\t\")}\n┣┅┅┅┅┅┅╉┄┄┄┄┄┄┄┄┄┄┤\n 🚫封禁状态 ${res.is_banned?'已封禁':'正常 '} \n┗┅┅┅┅┅┅┹┄┄┄┄┄┄┄┄┄┄┘\n`\n java.log(tips)\n java.longToast(tips)\n } else {\n throw new Error(res.msg)\n }\n } catch (e) {\n \/\/java.log(e)\n java.toast(\"\\n检测登录失败\\n\" + e.message)\n }\n}\n\nfunction clearDevice() {\n let base_url = getArgument('server')\n let res = java.ajax(`${base_url}\/clear,{\"method\":\"POST\",\"headers\":{\"cookie\":\"${cookie.getCookie(base_url)}\"}}`)\n res = JSON.parse(res);\n java.toast(res.code == 0 ? \"\\n\\n📴设备清除成功\" : res.msg)\n Packages.java.lang.Thread.sleep(500)\n checkStatus()\n}\n\/\/ 保存登录UI信息\nfunction putLoginInfo(info) {\n try {\n let key = java.androidId()\n let encodeStr = Packages.android.util.Base64.encodeToString(java.createSymmetricCrypto(\"AES\", key).encrypt(info), 2)\n cache.put(`userInfo_${source.getKey()}`, encodeStr)\n return true\n } catch (e) {\n java.log(e)\n return source.putLoginInfo(info)\n }\n}\n\n\/\/ 用户后台\nfunction loginqt() {\n let base_url = getArgument('server');\n let ck = String(cookie.getKey(base_url, \"qttoken\"));\n if (!ck) {\n java.longToast('\\n\\n🚫请先登录!')\n } else {\n java.startBrowserAwait(getArgument('server') + '\/user', '大灰狼小说后台');\n }\n}\n\n\/\/ 用户注册\nfunction register() {\n let base_url = getArgument('server');\n java.startBrowserAwait(getArgument('server') + '\/register', '大灰狼小说注册');\n}\n\n\/\/登录番茄\nfunction fq_login() {\n try {\n cookie.removeCookie(\"snssdk.com\");\n cookie.removeCookie(\"fanqienovel.com\")\n } catch (e) {}\n try {\n java.startBrowserAwait(\"https:\/\/fanqienovel.com\/\", \"登录\")\n } catch (e) {\n java.toast(e)\n }\n var cookie_ = \"sessionid=\" + (String(cookie.getKey(\"fanqienovel.com\", \"sessionid\")) ? String(cookie.getKey(\"fanqienovel.com\", \"sessionid\")) : source.getLoginInfoMap()['手动登录Token'])\n let user\n try {\n user = JSON.parse(java.ajax(\"https:\/\/fanqienovel.com\/api\/user\/info\/v2,\" + JSON.stringify({\n method: \"GET\",\n headers: {\n \"Cookie\": cookie_\n }\n }))).data.name\n } catch (e) {\n java.log(e)\n }\n if (!cookie_ || cookie_ == \"sessionid=\" || !user) {\n java.toast(\"未获取到登录凭据,登录失败\")\n return false\n }\n java.toast(\"\\n\\n欢迎 \" + user + \"\\n登录成功!\")\n return true\n}\n\n\/\/退出登录\nfunction logout() {\n cookie.removeCookie(\"fanqienovel.com\");\n cookie.removeCookie(\"snssdk.com\");\n cookie.removeCookie(\"69shuba.com\");\n \/*\n let servers = host\n for (let server of servers) {\n \ttry{removeCookie(server )} catch(e){cookie.removeCookie(server )}\n }\n *\/\n try {\n cookie.removeCookie(getArgument('server'))\n } catch (e) {}\n java.toast(\"退出登录成功\");\n}\n\n\/\/获取参数\nfunction getArgument(key) {\n let open_argument = source.getVariable();\n open_argument = getArguments(open_argument, '');\n return open_argument[key];\n}\n\n\/\/设置参数\nfunction setArgument(key, value) {\n let open_argument = source.getVariable();\n open_argument = getArguments(open_argument, '');\n open_argument[key] = value;\n open_argument = JSON.stringify(open_argument);\n source.setVariable(open_argument);\n return open_argument;\n}\n\n\/\/ 设置本地or云端访问\nfunction get_proxy() {\n let proxy = getArgument('proxy');\n if (proxy == '本地') {\n setArgument('proxy', '云端');\n java.longToast('\\n所有数据采用\\n\\n服务器网络访问\\n\\n如果发现用不了,请切换本地网络访问,如69书吧');\n } else {\n setArgument('proxy', '本地');\n java.longToast('\\n所有数据采用\\n\\n本地网络访问\\n\\n如果发现用不了,请开启网络代理,如69书吧');\n }\n}\n\n\nfunction closeImg() {\n let close_img = getArgument('close_img');\n if (close_img == 'on') {\n setArgument('close_img', 'off');\n java.longToast('\\n显示文中图片');\n } else {\n setArgument('close_img', 'on');\n java.longToast('\\n不显示文中图片');\n }\n}\n\n\/\/设置男女频\nfunction set_source_type() {\n let source_type = getArgument(\"source_type\");\n if (source_type == '女频') {\n setArgument(\"source_type\", '男频');\n java.toast(\"\\n发现页已设置为:🙋♂️男频\");\n } else {\n setArgument(\"source_type\", '女频');\n java.toast(\"\\n发现页已设置为:🙋♀️女频\");\n }\n\n}\n\nfunction set_reading() {\n let reading = getArgument(\"reading\");\n if (reading == '1') {\n setArgument(\"reading\", '0');\n java.toast(\"\\n\\n大灰狼书架记录同步已关闭!\");\n } else {\n setArgument(\"reading\", '1');\n java.longToast(\"\\n\\n大灰狼书架记录同步已开启!\\n刷新阅读书架即可将书架书籍同步到大灰狼书架!\");\n }\n\n}\n\n\/\/首页\nfunction api() {\n java.startBrowserAwait('http:\/\/svip.langge.cf', \"首页\");\n}\n\n\/\/打赏\nfunction vip() {\n let base_url = getArgument('server');\n let ck = String(cookie.getKey(base_url, \"qttoken\"));\n if (!ck) {\n java.longToast('\\n\\n🚫请先登录!')\n } else {\n java.startBrowserAwait(getArgument('server') + '\/coffee', '大灰狼小说会员开通');\n }\n}\n\nvar server = getArgument('server');\n\n\/\/设置搜索媒体\nfunction set_media(media) {\n const mediaConfig = {\n '喜马拉雅': ['听书'],\n '番茄': '*',\n '福利小说': ['小说'],\n '如漫画': ['漫画'],\n '包子漫画': ['漫画'],\n '九妖漫画': ['漫画'],\n '绅士漫画': ['漫画'],\n '福利漫画': ['漫画'],\n '好看漫画': ['漫画'],\n '六月听书': ['听书'],\n '海洋听书': ['听书'],\n '酷我听书': ['听书'],\n '七猫': ['小说', '听书', '短剧'],\n '河马': ['短剧'],\n '超会专属短剧': ['短剧'],\n '歪瑞古德': ['漫画'],\n '毒舌影视': ['短剧'],\n '全部': '*', \/\/ 允许所有模式\n '默认': ['小说']\n };\n\n const source = getArgument('sources');\n const allowedModes = mediaConfig[source] || mediaConfig['默认'];\n let targetMedia = mediaConfig['默认'][0];\n let isAllowed = false;\n\n if (allowedModes === '*' || allowedModes.includes(media)) {\n targetMedia = media;\n isAllowed = true;\n } else if (Array.isArray(allowedModes)) {\n targetMedia = allowedModes[0];\n }\n\n const message = isAllowed ?\n `\\n\\n已切换至:${targetMedia}\\n请重新搜索书籍!` :\n `\\n\\n目前${source}:不支持【${media}】模式!\\n已自动切换至:${targetMedia}`;\n\n setArgument('tab', targetMedia);\n java.toast(message);\n}\n\n\/\/获取搜索媒体\nfunction get_media() {\n let media = getArgument('tab');\n let source = getArgument('sources');\n if (media == '') {\n media = '全部';\n }\n var tishi = '\\n\\n当前服务器:' + getArgument('server')\n java.longToast(`\\n\\n当前使用源:${source}-${media}${tishi}`);\n}\n\n\n\/\/设置服务器\nfunction set_server() {\n putLoginInfo(JSON.stringify(result))\n let zdyserver;\n let base_url = getArgument('server')\n try {\n zdyserver = String(result['自定义服务器(可不填)']);\n if (zdyserver.includes('http')) {\n setArgument('server', zdyserver);\n if (getKey(String(cookie.getCookie(base_url)))) {\n let cookies = cookie.getCookie(base_url)\n try {\n cookie.removeCookie(base_url)\n } catch (e) {}\n cookie.setCookie(zdyserver, cookies)\n }\n java.toast(`\\n\\n当前服务器为自定义服务器\\n${zdyserver}\\n\\n切换服务器请先清空服务器地址中的数据`);\n } else {\n zdyserver = '';\n }\n } catch (error) {\n zdyserver = '';\n }\n if (!zdyserver) {\n const servers = host\n const currentServer = getArgument('server') || '';\n const currentIndex = servers.indexOf(currentServer);\n\n const nextIndex = currentIndex >= 0 ? (currentIndex + 1) % servers.length : 0;\n const nextServer = servers[nextIndex];\n\n setArgument('server', nextServer);\n if (getKey(String(cookie.getCookie(currentServer)))) {\n let cookies = cookie.getCookie(currentServer)\n try {\n cookie.removeCookie(currentServer)\n } catch (e) {}\n cookie.setCookie(nextServer, cookies)\n }\n java.longToast(`\\n\\n服务器【${nextIndex+1}】:${nextServer}`);\n }\n}\n\n\/\/获取音色\nvar tone_id = getArgument('tone_id');\n\nfunction get_tone_id(arg) {\n var datadist = {\n \"0\": \"默认音色\",\n \"-1\": \"阅读模式\",\n \"-2\": \"漫画模式\",\n \"51\": \"多人发音\",\n \"1\": \"甜美少女\",\n \"2\": \"清亮青叔\",\n \"5\": \"开朗青年\",\n \"6\": \"温柔淑女\",\n \"4\": \"成熟大叔\",\n \"74\": \"大叔升级\",\n \"30\": \"优雅御姐\"\n };\n var tone_id = datadist[arg] || arg;\n var tishi = '\\n\\n当前音色:' + tone_id;\n java.toast(tishi);\n}\n\n\n\/\/设置来源\nfunction set_source(sources) {\n let zdysources = String(result['自定义搜索源(多个用英文,分割)'] || '');\n if (zdysources.length > 1 && zdysources != 'undefined') {\n java.toast('\\n\\n请先清空自定义源再设置');\n } else {\n setArgument('sources', sources);\n set_media('小说');\n java.toast(`\\n\\n当前来源已切换为:\\n${sources}\\n\\n切换后请重新搜索`);\n }\n}\n\n\/\/ 设置简介\nfunction set_info() {\n var info = getArgument('info');\n if (info == 'off') {\n setArgument('info', 'on');\n java.toast('\\n\\n已恢复详情页详细简介');\n } else {\n setArgument('info', 'off');\n java.toast('\\n\\n已精简详情页简介');\n }\n}\n\n\/\/ 番茄段评\nfunction paracomment() {\n var fqpara = getArgument('fqpara');\n if (fqpara != 'on') {\n setArgument('fqpara', 'on');\n java.longToast(\"\\n\\n段评已开启\\n\\n长按刷新段后面的图片即可\\n\\n如果图片不显示,刷新无反应\\n请更新测试版阅读app\");\n } else {\n setArgument('fqpara', 'off');\n java.longToast('\\n\\n段评已关闭');\n }\n}\n\n\/\/ 强制搜索被禁用的源\nfunction disabledSources() {\n var disabled_sources = getArgument('disabled_sources');\n if (disabled_sources == '1') {\n setArgument('disabled_sources', '0');\n java.longToast('\\n\\n强制搜索禁用的源已关闭');\n } else {\n setArgument('disabled_sources', '1');\n setArgument('sources', '全部');\n java.longToast(\"\\n\\n强制搜索禁用的源已开启\\n\\n注意:开启后搜索时间会变长!\");\n }\n}\n\n\/\/ 我要推荐\nfunction put_book() {\n java.startBrowserAwait(getArgument('server') + '\/put_book', '我来推荐');\n}\n\n\n\/\/ 使用教程\nfunction jc(){\n\t let html = `<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>使用教程<\/title>\n <style>\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n }\n\n body {\n background-color: #f5f5f5;\n padding: 20px;\n }\n\n .container {\n max-width: 800px;\n margin: 0 auto;\n }\n\n h1 {\n text-align: center;\n color: #333;\n margin-bottom: 30px;\n padding-bottom: 10px;\n border-bottom: 2px solid #4CAF50;\n }\n\n .tutorial-card {\n background-color: white;\n border-radius: 8px;\n box-shadow: 0 2px 15px rgba(0, 0, 0, 0.1);\n padding: 25px;\n margin-bottom: 25px;\n transition: transform 0.3s ease, box-shadow 0.3s ease;\n }\n\n .tutorial-card:hover {\n transform: translateY(-5px);\n box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15);\n }\n\n .card-title {\n color: #4CAF50;\n margin-bottom: 15px;\n font-size: 1.4em;\n }\n\n .card-content {\n line-height: 1.6;\n color: #555;\n }\n\n .keyword-list {\n background-color: #f9f9f9;\n border-left: 4px solid #4CAF50;\n padding: 15px;\n margin-top: 15px;\n border-radius: 0 4px 4px 0;\n font-family: monospace;\n }\n\n .keyword-item {\n margin-bottom: 8px;\n }\n <\/style>\n<\/head>\n<body>\n <div class=\"container\">\n <h1>使用教程<\/h1>\n\n <div class=\"tutorial-card\">\n <h2 class=\"card-title\">搜索功能<\/h2>\n <div class=\"card-content\">\n <p><strong>1) 基础搜索:<\/strong>在发现页长按该书源,点击搜索,可直接搜索全部来源的小说。<\/p>\n <p><strong>2) 进阶搜索:<\/strong>在“登陆-书源设置”中选择指定模式或来源,即可搜索指定内容。<\/p>\n <p><strong>3) 快捷搜索:<\/strong>使用特殊关键词进行搜索。<\/p>\n <div class=\"keyword-list\">\n <p class=\"keyword-item\">x:书名@来源 (例如: x:十日终焉@番茄)<\/p>\n <p class=\"keyword-item\">t:书名@来源 (例如: t:十日终焉@番茄)<\/p>\n <p class=\"keyword-item\">m:书名@来源 (例如: m:十日终焉@番茄)<\/p>\n <p class=\"keyword-item\">d:书名@来源 (例如: d:十日终焉@番茄)<\/p>\n <\/div>\n <p>其中,x表示小说(可省略),t表示听书,m表示漫画,d表示短剧。冒号支持中文和英文。<\/p>\n <\/div>\n <\/div>\n\n <div class=\"tutorial-card\">\n <h2 class=\"card-title\">发现页<\/h2>\n <div class=\"card-content\">\n <p>在书源设置中选择指定模式或者单个来源,然后刷新发现页即可。<\/p>\n <\/div>\n <\/div>\n\n <div class=\"tutorial-card\">\n <h2 class=\"card-title\">更多使用方式<\/h2>\n <div class=\"card-content\">\n <p>进入书源设置页面应该能一目了然。<\/p>\n <\/div>\n <\/div>\n <\/div>\n<\/body>\n<\/html>`;\n\t java.startBrowser(`data:text\/html;base64,${java.base64Encode(html)}`, '大灰狼书源教程');\n\t}\n\n\/\/ 书源更新\nfunction renderVersionPage() {\n let yd = '';\n let html = `\n<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\" \/>\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \/>\n <title>书源更新<\/title>\n <!-- Font Awesome 图标库 -->\n <link rel=\"stylesheet\" href=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/font-awesome\/6.4.0\/css\/all.min.css\" \/>\n <style>\n :root {\n --primary-gradient: linear-gradient(135deg, #4e6ef2, #6b2dd8);\n --latest-gradient: linear-gradient(135deg, #8e2de2 0%, #4a00e0 50%, #d4af37 100%);\n --success-color: #28c76f;\n --warning-color: #ff9f43;\n --error-color: #ea5455;\n --text-main: #1f2937;\n --text-secondary: #6b7280;\n --card-bg: #ffffff;\n --border-color: #e5e7eb;\n --light-bg: #f9fafb;\n --shadow: 0 4px 12px rgba(78, 110, 242, 0.1);\n --shadow-hover: 0 6px 18px rgba(78, 110, 242, 0.2);\n --glow-shadow: 0 0 25px rgba(142, 45, 226, 0.5), 0 0 50px rgba(212, 175, 55, 0.3);\n --modal-bg: rgba(31, 41, 55, 0.8);\n --modal-content-bg: #ffffff;\n }\n\n * {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n font-family: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;\n }\n\n body {\n background: linear-gradient(135deg, #eef2ff, #f5f7ff);\n color: var(--text-main);\n min-height: 100vh;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 16px;\n }\n\n \/* 加载动画 *\/\n .loading-wrapper {\n text-align: center;\n animation: fadeIn 0.3s ease;\n }\n\n .loading-spinner {\n width: 50px;\n height: 50px;\n border: 4px solid rgba(78, 110, 242, 0.3);\n border-top-color: #4e6ef2;\n border-radius: 50%;\n margin: 0 auto 20px;\n animation: spin 1s linear infinite;\n }\n\n .loading-text {\n color: var(--text-main);\n font-size: 16px;\n font-weight: 500;\n }\n\n @keyframes spin {\n to { transform: rotate(360deg); }\n }\n\n @keyframes fadeIn {\n from { opacity: 0; transform: translateY(20px); }\n to { opacity: 1; transform: translateY(0); }\n }\n\n @keyframes slideIn {\n from { opacity: 0; transform: translateY(30px); }\n to { opacity: 1; transform: translateY(0); }\n }\n\n @keyframes pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.7; }\n }\n\n @keyframes gradientAnimation {\n 0% { background-position: 0% 50%; }\n 50% { background-position: 100% 50%; }\n 100% { background-position: 0% 50%; }\n }\n\n @keyframes breathe {\n 0%, 100% { \n transform: scale(1);\n box-shadow: var(--glow-shadow), var(--shadow);\n }\n 50% { \n transform: scale(1.02);\n box-shadow: 0 0 30px rgba(142, 45, 226, 0.6), 0 0 60px rgba(212, 175, 55, 0.4), var(--shadow);\n }\n }\n\n @keyframes shimmer {\n 0% {\n background-position: -200% center;\n }\n 100% {\n background-position: 200% center;\n }\n }\n\n \/* 主容器 *\/\n .container {\n width: 100%;\n max-width: 420px;\n background: var(--card-bg);\n border-radius: 24px;\n overflow: hidden;\n box-shadow: var(--shadow);\n position: relative;\n z-index: 1;\n animation: slideIn 0.5s ease;\n display: none;\n }\n\n \/* 头部 *\/\n .header {\n background: var(--primary-gradient);\n color: #ffffff;\n padding: 24px 16px;\n text-align: center;\n position: relative;\n overflow: hidden;\n }\n\n .header::before {\n content: '';\n position: absolute;\n top: -30px;\n left: -30px;\n width: 80px;\n height: 80px;\n background: rgba(255, 255, 255, 0.15);\n border-radius: 50%;\n }\n\n .header::after {\n content: '';\n position: absolute;\n bottom: -60px;\n right: -60px;\n width: 150px;\n height: 150px;\n background: rgba(255, 255, 255, 0.1);\n border-radius: 50%;\n }\n\n .header h1 {\n font-size: 1.4rem;\n font-weight: 700;\n margin-bottom: 8px;\n position: relative;\n z-index: 2;\n }\n\n .header p {\n font-size: 0.9rem;\n opacity: 0.9;\n line-height: 1.4;\n position: relative;\n z-index: 2;\n }\n\n .header-icon {\n font-size: 48px;\n margin-bottom: 10px;\n display: inline-block;\n animation: bounce 2s ease infinite;\n }\n\n @keyframes bounce {\n 0%, 100% { transform: translateY(0); }\n 50% { transform: translateY(-10px); }\n }\n\n \/* 版本对比 *\/\n .version-comparison {\n display: flex;\n flex-wrap: nowrap;\n gap: 12px;\n padding: 16px;\n margin-top: 8px;\n position: relative;\n z-index: 10;\n }\n\n .version-card {\n flex: 1;\n min-width: 45%;\n background: var(--card-bg);\n border-radius: 16px;\n padding: 28px 16px 16px;\n box-shadow: var(--shadow);\n text-align: center;\n position: relative;\n transition: transform 0.3s ease, box-shadow 0.3s ease;\n overflow: hidden;\n border: 1px solid rgba(120, 130, 240, 0.1);\n }\n\n .version-card:hover {\n transform: translateY(-4px);\n box-shadow: var(--shadow-hover);\n }\n\n .version-card.current-version {\n background: linear-gradient(135deg, #ffffff 0%, #f8f9ff 100%);\n border: 1px solid rgba(78, 110, 242, 0.15);\n }\n\n .version-card.current-version:hover {\n box-shadow: 0 6px 20px rgba(78, 110, 242, 0.15);\n }\n\n .version-card.current-version h3,\n .version-card.current-version .version-number,\n .version-card.current-version .version-date {\n color: var(--text-main);\n }\n\n .version-card.latest-version {\n background: var(--latest-gradient);\n background-size: 300% 300%;\n box-shadow: var(--glow-shadow), var(--shadow);\n color: #fff;\n z-index: 2;\n animation: gradientAnimation 6s ease infinite, breathe 3s ease-in-out infinite;\n position: relative;\n overflow: hidden;\n }\n\n .version-card.latest-version::before {\n content: '';\n position: absolute;\n top: -50%;\n left: -50%;\n width: 200%;\n height: 200%;\n background: linear-gradient(\n 90deg,\n transparent,\n rgba(255, 255, 255, 0.3),\n transparent\n );\n transform: rotate(45deg);\n animation: shimmer 3s infinite;\n }\n\n .version-card.latest-version h3,\n .version-card.latest-version .version-number,\n .version-card.latest-version .version-date {\n color: #fff;\n text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\n position: relative;\n z-index: 1;\n }\n\n .version-status {\n position: absolute;\n top: 6px;\n right: 6px;\n padding: 3px 7px;\n font-size: 0.65rem;\n font-weight: 600;\n border-radius: 6px;\n color: #fff;\n line-height: 1.2;\n white-space: nowrap;\n z-index: 2;\n }\n\n .version-card.latest-version .version-status {\n background: rgba(255, 255, 255, 0.25);\n backdrop-filter: blur(5px);\n border: 1px solid rgba(255, 255, 255, 0.3);\n color: #fff;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);\n }\n\n .status-outdated { background: var(--warning-color); }\n .status-latest { background: var(--success-color); }\n .status-invalid { background: var(--error-color); }\n\n .version-card h3 {\n font-size: 0.9rem;\n color: var(--text-secondary);\n margin-bottom: 8px;\n font-weight: 500;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n }\n\n .version-number {\n font-size: 1.25rem;\n font-weight: 700;\n color: var(--text-main);\n margin: 8px 0;\n transition: all 0.3s ease;\n font-family: 'Courier New', monospace;\n }\n\n .version-card.latest-version .version-number {\n font-size: 1.4rem;\n transform: scale(1.05);\n text-shadow: \n 0 2px 4px rgba(0, 0, 0, 0.3),\n 0 0 10px rgba(212, 175, 55, 0.8),\n 0 0 20px rgba(212, 175, 55, 0.5);\n animation: pulse-glow 2s ease-in-out infinite;\n }\n\n @keyframes pulse-glow {\n 0%, 100% {\n text-shadow: \n 0 2px 4px rgba(0, 0, 0, 0.3),\n 0 0 10px rgba(212, 175, 55, 0.8),\n 0 0 20px rgba(212, 175, 55, 0.5);\n }\n 50% {\n text-shadow: \n 0 2px 4px rgba(0, 0, 0, 0.3),\n 0 0 15px rgba(212, 175, 55, 1),\n 0 0 30px rgba(212, 175, 55, 0.7);\n }\n }\n\n .version-date {\n font-size: 0.8rem;\n color: var(--text-secondary);\n margin-top: 4px;\n }\n\n \/* 版本对比指示器 *\/\n .version-indicator {\n position: absolute;\n left: 50%;\n top: 50%;\n transform: translate(-50%, -50%);\n z-index: 5;\n width: 32px;\n height: 32px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);\n color: white;\n }\n\n .version-indicator.update-needed {\n background: var(--error-color);\n box-shadow: 0 2px 8px rgba(234, 84, 85, 0.4);\n animation: pulse-indicator 1.5s infinite;\n }\n\n .version-indicator.is-latest {\n background: var(--success-color);\n box-shadow: 0 2px 8px rgba(40, 199, 111, 0.4);\n }\n\n @keyframes pulse-indicator {\n 0% { transform: translate(-50%, -50%) scale(1); }\n 50% { transform: translate(-50%, -50%) scale(1.1); }\n 100% { transform: translate(-50%, -50%) scale(1); }\n }\n\n \/* 内容区 *\/\n .content-container {\n padding: 16px;\n }\n\n \/* 状态提示 *\/\n .status-alert {\n background: var(--card-bg);\n border-radius: 16px;\n box-shadow: var(--shadow);\n margin-bottom: 16px;\n overflow: hidden;\n border: 1px solid rgba(120, 130, 240, 0.1);\n padding: 12px 16px;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 10px;\n font-weight: 500;\n font-size: 14px;\n animation: slideIn 0.5s ease 0.3s backwards;\n }\n\n .status-alert i {\n font-size: 20px;\n }\n\n .status-alert.update-available {\n background: linear-gradient(135deg, #ffeaa7 0%, #fdcb6e 100%);\n color: #d63031;\n box-shadow: 0 4px 15px rgba(253, 203, 110, 0.4);\n }\n\n .status-alert.up-to-date {\n background: linear-gradient(135deg, #55efc4 0%, #00b894 100%);\n color: white;\n box-shadow: 0 4px 15px rgba(0, 184, 148, 0.4);\n }\n\n \/* 更新容器 *\/\n .update-container {\n background: var(--card-bg);\n border-radius: 16px;\n box-shadow: var(--shadow);\n margin-bottom: 16px;\n overflow: hidden;\n border: 1px solid rgba(120, 130, 240, 0.1);\n animation: slideIn 0.5s ease 0.4s backwards;\n }\n\n .update-header {\n background: var(--light-bg);\n padding: 12px 16px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n border-bottom: 1px solid var(--border-color);\n }\n\n .update-header h2 {\n font-size: 1rem;\n font-weight: 600;\n color: var(--text-main);\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .update-header h2 i {\n color: #4e6ef2;\n }\n\n .update-tag {\n background: rgba(78, 110, 242, 0.1);\n color: #4e6ef2;\n padding: 4px 8px;\n border-radius: 8px;\n font-size: 0.75rem;\n font-weight: 600;\n }\n\n .update-content {\n padding: 16px;\n }\n\n .update-date {\n font-weight: 600;\n color: #4e6ef2;\n margin-bottom: 12px;\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 8px 0;\n border-bottom: 1px dashed #e0e0e0;\n }\n\n .update-text {\n margin: 8px 0;\n position: relative;\n padding-left: 16px;\n line-height: 1.5;\n color: var(--text-main);\n font-size: 0.95rem;\n white-space: pre-wrap;\n word-break: break-word;\n }\n\n .update-text::before {\n content: '•';\n position: absolute;\n left: 0;\n font-weight: bold;\n color: #4e6ef2;\n font-size: 1.2rem;\n line-height: 1;\n }\n\n \/* 历史日志 *\/\n .history-container {\n background: var(--card-bg);\n border-radius: 16px;\n box-shadow: var(--shadow);\n margin-bottom: 16px;\n border: 1px solid rgba(120, 130, 240, 0.1);\n animation: slideIn 0.5s ease 0.5s backwards;\n }\n\n .history-header {\n background: var(--light-bg);\n padding: 12px 16px;\n display: flex;\n justify-content: space-between;\n align-items: center;\n border-bottom: 1px solid var(--border-color);\n cursor: pointer;\n user-select: none;\n }\n\n .history-header:hover {\n opacity: 0.8;\n }\n\n .history-header h2 {\n font-size: 1rem;\n font-weight: 600;\n color: var(--text-main);\n display: flex;\n align-items: center;\n gap: 8px;\n }\n\n .history-header h2 i {\n color: #4e6ef2;\n }\n\n .toggle-history {\n background: none;\n border: none;\n color: var(--text-secondary);\n cursor: pointer;\n font-weight: 500;\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 0.85rem;\n transition: color 0.2s ease;\n }\n\n .toggle-history:hover {\n color: #4e6ef2;\n }\n\n .history-content {\n padding: 0 16px;\n max-height: 0;\n overflow: hidden;\n transition: max-height 0.4s ease, padding 0.4s ease;\n }\n\n .history-content.expanded {\n max-height: 60vh;\n overflow-y: auto;\n padding: 16px;\n scrollbar-width: thin;\n scrollbar-color: #4e6ef2 #f0f0f0;\n }\n\n .history-content.expanded::-webkit-scrollbar {\n width: 6px;\n }\n\n .history-content.expanded::-webkit-scrollbar-track {\n background: #f0f0f0;\n border-radius: 4px;\n }\n\n .history-content.expanded::-webkit-scrollbar-thumb {\n background: #4e6ef2;\n border-radius: 4px;\n }\n\n .history-content.expanded::-webkit-scrollbar-thumb:hover {\n background: #3a56d0;\n }\n\n .history-item {\n margin-bottom: 16px;\n padding-bottom: 16px;\n border-bottom: 1px dashed var(--border-color);\n }\n\n .history-item:last-child {\n border-bottom: none;\n margin-bottom: 0;\n padding-bottom: 0;\n }\n\n .history-date {\n font-weight: 600;\n color: var(--text-main);\n margin-bottom: 8px;\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 0.9rem;\n background: rgba(78, 110, 242, 0.05);\n padding: 6px 10px;\n border-radius: 6px;\n }\n\n .history-text {\n margin: 8px 0;\n padding-left: 16px;\n line-height: 1.4;\n color: var(--text-secondary);\n position: relative;\n font-size: 0.9rem;\n white-space: pre-wrap;\n word-break: break-word;\n }\n\n .history-text::before {\n content: '•';\n position: absolute;\n left: 0;\n color: #4e6ef2;\n font-weight: bold;\n font-size: 1.2rem;\n line-height: 1;\n }\n\n \/* 按钮组 *\/\n .button-group {\n display: flex;\n flex-direction: column;\n gap: 10px;\n margin-bottom: 16px;\n }\n\n .button {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 10px;\n padding: 14px 28px;\n text-align: center;\n font-size: 1rem;\n border: none;\n border-radius: 12px;\n text-decoration: none;\n background: var(--primary-gradient);\n color: white;\n font-weight: 600;\n transition: all 0.3s ease;\n box-shadow: var(--shadow);\n position: relative;\n overflow: hidden;\n cursor: pointer;\n }\n\n .button i {\n font-size: 1rem;\n }\n\n .button::after {\n content: '';\n position: absolute;\n top: -50%;\n left: -50%;\n width: 200%;\n height: 200%;\n background: rgba(255, 255, 255, 0.1);\n transform: rotate(30deg);\n transition: all 0.6s ease;\n pointer-events: none;\n }\n\n .button:hover {\n transform: translateY(-3px);\n box-shadow: var(--shadow-hover);\n }\n\n .button:hover::after {\n transform: rotate(30deg) translate(20%, 20%);\n }\n\n .button:active {\n transform: scale(0.95);\n }\n\n \/* 错误状态 *\/\n .error-state {\n text-align: center;\n padding: 40px 20px;\n color: var(--text-main);\n }\n\n .error-icon {\n font-size: 64px;\n margin-bottom: 20px;\n color: var(--error-color);\n }\n\n .error-text {\n font-size: 16px;\n line-height: 1.6;\n margin-bottom: 20px;\n }\n\n .retry-button {\n background: var(--primary-gradient);\n color: white;\n padding: 12px 30px;\n border-radius: 12px;\n border: none;\n font-weight: 600;\n cursor: pointer;\n transition: all 0.3s ease;\n font-size: 14px;\n box-shadow: var(--shadow);\n }\n\n .retry-button:hover {\n transform: translateY(-2px);\n box-shadow: var(--shadow-hover);\n }\n\n .retry-button:active {\n transform: scale(0.95);\n }\n\n \/* 装饰元素 *\/\n .decoration {\n position: absolute;\n z-index: 0;\n pointer-events: none;\n }\n\n .decoration.circle {\n width: 120px;\n height: 120px;\n border-radius: 50%;\n background: rgba(107, 45, 216, 0.05);\n top: 10%;\n left: 10%;\n }\n\n .decoration.square {\n width: 80px;\n height: 80px;\n transform: rotate(45deg);\n background: rgba(78, 110, 242, 0.05);\n bottom: 10%;\n right: 10%;\n }\n\n \/* 响应式 *\/\n @media (max-width: 768px) {\n body {\n padding: 12px;\n }\n\n .container {\n max-width: 100%;\n border-radius: 20px;\n }\n\n .header {\n padding: 20px 15px;\n }\n\n .header h1 {\n font-size: 1.3rem;\n }\n\n .header-icon {\n font-size: 40px;\n }\n\n .version-comparison {\n flex-direction: row;\n flex-wrap: nowrap;\n gap: 10px;\n padding: 12px;\n margin-top: 6px;\n overflow-x: auto;\n }\n\n .version-card {\n min-width: 45%;\n padding: 26px 12px 12px;\n }\n\n \/* 移动端减弱呼吸动效 *\/\n .version-card.latest-version {\n animation: gradientAnimation 6s ease infinite;\n }\n\n .version-status {\n top: 5px;\n right: 5px;\n padding: 2px 5px;\n font-size: 0.6rem;\n }\n\n .version-number {\n font-size: 1.1rem;\n }\n\n .version-card.latest-version .version-number {\n font-size: 1.2rem;\n }\n\n .update-header h2, .history-header h2 {\n font-size: 0.9rem;\n }\n\n .button {\n padding: 12px 24px;\n font-size: 0.95rem;\n }\n\n .history-content.expanded {\n max-height: 50vh;\n -webkit-overflow-scrolling: touch;\n }\n }\n\n @media (max-width: 380px) {\n .header h1 {\n font-size: 1.2rem;\n }\n\n .version-number {\n font-size: 1rem;\n }\n\n .version-card.latest-version .version-number {\n font-size: 1.1rem;\n }\n\n .button {\n padding: 11px;\n font-size: 0.9rem;\n }\n }\n <\/style>\n<\/head>\n<body>\n <div class=\"decoration circle\"><\/div>\n <div class=\"decoration square\"><\/div>\n\n <div id=\"loading\" class=\"loading-wrapper\">\n <div class=\"loading-spinner\"><\/div>\n <div class=\"loading-text\"><i class=\"fas fa-search\"><\/i> 正在检查更新...<\/div>\n <\/div>\n\n <div class=\"container\" id=\"container\">\n <div class=\"header\">\n <div class=\"header-icon\"><i class=\"fas fa-book\"><\/i><\/div>\n <h1>大灰狼书源更新<\/h1>\n <p>推荐使用阅读Sigma版本<br>正式版可能存在兼容性问题-<a href='https:\/\/legado.langge.cf'>下载<\/a><\/p>\n <\/div>\n\n <div class=\"version-comparison\">\n <div class=\"version-card current-version\">\n <div class=\"version-status status-outdated\" id=\"currentStatus\">待检查<\/div>\n <h3><i class=\"fas fa-cube\"><\/i> 当前版本<\/h3>\n <div class=\"version-number\" id=\"currentVersion\">-<\/div>\n <div class=\"version-date\">您的当前版本<\/div>\n <\/div>\n\n <div class=\"version-indicator update-needed\" id=\"versionIndicator\" style=\"display: none;\">\n <i class=\"fas fa-arrow-right\"><\/i>\n <\/div>\n\n <div class=\"version-card latest-version\">\n <div class=\"version-status status-latest\" id=\"latestStatus\">最新版本<\/div>\n <h3><i class=\"fas fa-star\"><\/i> 最新版本<\/h3>\n <div class=\"version-number\" id=\"latestVersion\">-<\/div>\n <div class=\"version-date\">可用最新版本<\/div>\n <\/div>\n <\/div>\n\n <div class=\"content-container\">\n <div class=\"status-alert\" id=\"statusAlert\" style=\"display: none;\"><\/div>\n\n <div id=\"latestLogContainer\" style=\"display: none;\">\n <div class=\"update-container\">\n <div class=\"update-header\">\n <h2><i class=\"fas fa-bolt\"><\/i> 最新更新<\/h2>\n <div class=\"update-tag\">最新发布<\/div>\n <\/div>\n <div class=\"update-content\">\n <div class=\"update-date\" id=\"latestLogDate\"><\/div>\n <div class=\"update-text\" id=\"latestLogContent\"><\/div>\n <\/div>\n <\/div>\n <\/div>\n\n <div class=\"button-group\" id=\"buttonGroup\" style=\"display: none;\"><\/div>\n\n <div class=\"history-container\" id=\"logs\" style=\"display: none;\">\n <div class=\"history-header\" onclick=\"toggleLogs()\">\n <h2><i class=\"fas fa-history\"><\/i> 历史更新 <span id=\"historyCount\"><\/span><\/h2>\n <button class=\"toggle-history\" id=\"toggleButton\">\n <span id=\"toggleText\">展开历史<\/span>\n <i class=\"fas fa-chevron-down\" id=\"toggleIcon\"><\/i>\n <\/button>\n <\/div>\n <div class=\"history-content\" id=\"logList\"><\/div>\n <\/div>\n <\/div>\n <\/div>\n\n <script>\n let logsCollapsed = true;\n\n function toggleLogs() {\n logsCollapsed = !logsCollapsed;\n const logList = document.getElementById('logList');\n const toggleText = document.getElementById('toggleText');\n const toggleIcon = document.getElementById('toggleIcon');\n \n if (logsCollapsed) {\n logList.classList.remove('expanded');\n toggleText.textContent = '展开历史';\n toggleIcon.className = 'fas fa-chevron-down';\n } else {\n logList.classList.add('expanded');\n toggleText.textContent = '收起历史';\n toggleIcon.className = 'fas fa-chevron-up';\n }\n }\n\n (async function() {\n const loading = document.getElementById('loading');\n const container = document.getElementById('container');\n const currentVersion = document.getElementById('currentVersion');\n const latestVersion = document.getElementById('latestVersion');\n const currentStatus = document.getElementById('currentStatus');\n const latestStatus = document.getElementById('latestStatus');\n const versionIndicator = document.getElementById('versionIndicator');\n const statusAlert = document.getElementById('statusAlert');\n const buttonGroup = document.getElementById('buttonGroup');\n const latestLogContainer = document.getElementById('latestLogContainer');\n const latestLogDate = document.getElementById('latestLogDate');\n const latestLogContent = document.getElementById('latestLogContent');\n const logsContainer = document.getElementById('logs');\n const logList = document.getElementById('logList');\n const historyCount = document.getElementById('historyCount');\n\n const localVer = '${String(localVersion)}';\n\n \/\/ 统一的服务器配置 - 方便维护\n const serverConfig = {\n main: {\n name: '主线路',\n icon: 'rocket',\n baseUrl: 'https:\/\/sy.langge.cf',\n downloadPath: '\/download\/%E5%AE%89%E5%8D%93%E9%98%85%E8%AF%BBapp-%E5%A4%A7%E7%81%B0%E7%8B%BC%E8%9E%8D%E5%90%884.0(vip%E5%AE%8C%E5%85%A8%E7%89%88).json'\n },\n backup1: {\n name: '备用线路1',\n icon: 'box',\n baseUrl: 'https:\/\/api.langge.cf',\n downloadPath: '\/sy\/download\/%E5%AE%89%E5%8D%93%E9%98%85%E8%AF%BBapp-%E5%A4%A7%E7%81%B0%E7%8B%BC%E8%9E%8D%E5%90%884.0(vip%E5%AE%8C%E5%85%A8%E7%89%88).json'\n },\n backup2: {\n name: '备用线路2',\n icon: 'satellite',\n baseUrl: 'https:\/\/20.langge.tk',\n downloadPath: '\/sy\/download\/%E5%AE%89%E5%8D%93%E9%98%85%E8%AF%BBapp-%E5%A4%A7%E7%81%B0%E7%8B%BC%E8%9E%8D%E5%90%884.0(vip%E5%AE%8C%E5%85%A8%E7%89%88).json'\n },\n backup3: {\n name: '备用线路3',\n icon: 'link',\n baseUrl: 'http:\/\/219.154.201.122:5006',\n downloadPath: '\/sy\/download\/%E5%AE%89%E5%8D%93%E9%98%85%E8%AF%BBapp-%E5%A4%A7%E7%81%B0%E7%8B%BC%E8%9E%8D%E5%90%884.0(vip%E5%AE%8C%E5%85%A8%E7%89%88).json'\n },\n backup4: {\n name: '备用线路4',\n icon: 'bolt',\n baseUrl: 'https:\/\/v2.czyl.cf',\n downloadPath: '\/sy\/download\/%E5%AE%89%E5%8D%93%E9%98%85%E8%AF%BBapp-%E5%A4%A7%E7%81%B0%E7%8B%BC%E8%9E%8D%E5%90%884.0(vip%E5%AE%8C%E5%85%A8%E7%89%88).json'\n },\n backup5: {\n name: '备用线路5',\n icon: 'globe',\n baseUrl: 'https:\/\/v10.czyl.cf',\n downloadPath: '\/sy\/download\/%E5%AE%89%E5%8D%93%E9%98%85%E8%AF%BBapp-%E5%A4%A7%E7%81%B0%E7%8B%BC%E8%9E%8D%E5%90%884.0(vip%E5%AE%8C%E5%85%A8%E7%89%88).json'\n },\n backup6: {\n name: '备用线路6',\n icon: 'broadcast-tower',\n baseUrl: 'https:\/\/v4.czyl.cf',\n downloadPath: '\/sy\/download\/%E5%AE%89%E5%8D%93%E9%98%85%E8%AF%BBapp-%E5%A4%A7%E7%81%B0%E7%8B%BC%E8%9E%8D%E5%90%884.0(vip%E5%AE%8C%E5%85%A8%E7%89%88).json'\n }\n };\n\n \/\/ 版本比较函数\n function compareVersions(vs) {\n const normalize = (v) => {\n return v.split('.').map(n => {\n const num = parseInt(n, 10);\n return isNaN(num) ? 0 : num;\n });\n };\n\n const parts1 = normalize(localVer);\n const parts2 = normalize(vs);\n const maxLength = Math.max(parts1.length, parts2.length);\n \n for (let i = 0; i < maxLength; i++) {\n const num1 = parts1[i] || 0;\n const num2 = parts2[i] || 0;\n if (num1 > num2) return 1;\n if (num1 < num2) return -1;\n }\n return 0;\n }\n\n async function fetchVersionData() {\n \/\/ 使用统一配置中除主线路外的备用线路进行版本检查\n const serversToCheck = Object.values(serverConfig).filter(s => s.baseUrl.includes('czyl.cf') || s.baseUrl.includes('219.154'));\n \n for (const server of serversToCheck) {\n try {\n const response = await fetch(server.baseUrl + '\/version', { timeout: 2000 });\n if (response.ok) {\n return await response.json();\n }\n } catch (e) {\n console.warn(\\`接口失败:\\${server.baseUrl}\\`, e);\n }\n }\n throw new Error('所有更新接口都请求失败');\n }\n\n function showError(message) {\n loading.innerHTML = \\`\n <div class=\"error-state\">\n <div class=\"error-icon\"><i class=\"fas fa-exclamation-triangle\"><\/i><\/div>\n <div class=\"error-text\">\\${message}<\/div>\n <button class=\"retry-button\" onclick=\"location.reload()\"><i class=\"fas fa-redo\"><\/i> 重试<\/button>\n <\/div>\n \\`;\n }\n\n try {\n const data = await fetchVersionData();\n const cloudVersion = String(data.version3);\n const updateLog = data.update_log || {};\n\n \/\/ 显示版本信息\n currentVersion.textContent = \\`v\\${localVer}\\`;\n latestVersion.textContent = \\`v\\${cloudVersion}\\`;\n\n \/\/ 处理日志\n const logEntries = Object.entries(updateLog);\n if (logEntries.length > 0) {\n \/\/ 显示最新日志\n const [latestDate, latestContent] = logEntries[0];\n latestLogDate.innerHTML = \\`<i class=\"fas fa-calendar-alt\"><\/i> \\${latestDate}\\`;\n latestLogContent.textContent = latestContent;\n latestLogContainer.style.display = 'block';\n\n \/\/ 显示历史日志\n if (logEntries.length > 1) {\n const historyLogs = logEntries.slice(1);\n historyCount.textContent = \\`(\\${historyLogs.length}条)\\`;\n logList.innerHTML = historyLogs.map(([date, content]) => \\`\n <div class=\"history-item\">\n <div class=\"history-date\">\n <i class=\"fas fa-calendar-day\"><\/i>\n <span>\\${date}<\/span>\n <\/div>\n <div class=\"history-text\">\\${content}<\/div>\n <\/div>\n \\`).join('');\n logsContainer.style.display = 'block';\n }\n }\n\n \/\/ 检查更新状态\n const compareResult = compareVersions(cloudVersion);\n \n \/\/ 显示版本指示器\n versionIndicator.style.display = 'flex';\n \n if (compareResult === -1) {\n \/\/ 需要更新\n currentStatus.textContent = '待更新';\n currentStatus.className = 'version-status status-outdated';\n versionIndicator.className = 'version-indicator update-needed';\n versionIndicator.innerHTML = '<i class=\"fas fa-arrow-right\"><\/i>';\n\n \/\/ 使用统一配置生成下载按钮\n buttonGroup.innerHTML = Object.values(serverConfig).map(server => {\n const fullUrl = server.baseUrl + server.downloadPath;\n return \\`\n <a href=\"yuedu:\/\/booksource\/importonline?src=\\${encodeURIComponent(fullUrl)}\" class=\"button\">\n <i class=\"fas fa-\\${server.icon}\"><\/i>\n <span>\\${server.name}<\/span>\n <\/a>\n \\`;\n }).join('');\n buttonGroup.style.display = 'flex';\n } else {\n \/\/ 已是最新版本\n currentStatus.textContent = '最新';\n currentStatus.className = 'version-status status-latest';\n versionIndicator.className = 'version-indicator is-latest';\n versionIndicator.innerHTML = '<i class=\"fas fa-check\"><\/i>';\n \n statusAlert.className = 'status-alert up-to-date';\n statusAlert.innerHTML = '<i class=\"fas fa-check-circle\"><\/i> <div>您已是最新版本<\/div>';\n statusAlert.style.display = 'flex';\n }\n\n \/\/ 显示主容器,隐藏加载\n loading.style.display = 'none';\n container.style.display = 'block';\n\n } catch (err) {\n console.error('版本检查失败:', err);\n showError('<i class=\"fas fa-exclamation-circle\"><\/i> 检查更新失败,请稍后重试<br><small>' + err.message + '<\/small>');\n }\n })();\n <\/script>\n<\/body>\n<\/html>\n`;\n java.startBrowser(`data:text\/html;base64,${java.base64Encode(html)}`, '大灰狼书源更新');\n}",
"respondTime": 180000,
"ruleBookInfo": {
"author": "$.author",
"canReName": "1",
"coverUrl": "$.thumb_url",
"init": "<js>\nif (String(baseUrl).startsWith(\"data:\")) {\n let res = JSON.parse(java.hexDecodeToString(result));\n let book_id = res.book_id;\n let tab = res.tab;\n let sources = res.sources;\n let url = res.url;\n let html = \"\";\n let proxy = getArguments(source.getVariable(), \"proxy\");\n if (url != \"\" && proxy == \"本地\") {\n if (sources == '69书吧') {\n let ck69 = String(cookie.getCookie('https:\/\/www.69shuba.com'));\n let headers = {\n \t \"Referer\": url,\n \"Cookie\": ck69,\n \"User-Agent\": java.getWebViewUA()\n }; \n let op = JSON.stringify({\n \"headers\": headers\n });\n html = java.ajax(`${url},${op}`);\n } else {\n html = java.ajax(url);\n }\n \/\/java.log(html);\n if (html.includes(\"Just a moment...\") && sources == '69书吧') {\n cookie.removeCookie('https:\/\/www.69shuba.com');\n java.longToast('需要真人验证,请进入任意书籍详情页过验证');\n html = java.startBrowserAwait(url, \"需要真人验证,请进入任意书籍详情页过验证\").body();\n\n java.log(html);\n }\n }\n let base_url = getArguments(source.getVariable(), \"server\");\n let op = {\n method: \"POST\",\n body: {\n html: html\n }\n };\n op = JSON.stringify(op);\n let varia = String(book.getVariable('custom'));\n if (varia == 'null') {\n varia = '';\n }\n varia = JSON.stringify({\n 'custom': varia\n });\n \/\/varia = java.base64Encode(varia);\n java.log(`${base_url}\/detail?book_id=${book_id}&source=${sources}&tab=${tab}&variable=${varia},${op}`);\n result = java.ajax(`${base_url}\/detail?book_id=${book_id}&source=${sources}&tab=${tab}&variable=${varia},${op}`);\n}\nresult\n<\/js>$.data",
"intro": "<js>\nlet {\n book_id,\n source: sources,\n tab,\n book_tts,\n tags,\n role,\n last_chapter_title,\n last_chapter_update_time,\n word_number,\n status,\n score,\n abstract,\n copyright_info\n} = result;\nlet proxy = getArguments(source.getVariable(), \"proxy\");\nif (proxy == \"本地\") {\n proxy = \"本地网络\";\n} else {\n proxy = \"服务器网络\";\n}\njava.put(\"book_detail\", JSON.stringify(result));\nlet base_url = getArguments(source.getVariable(), \"server\");\nlet key3 = \"\";\ntry {\n let cookieValue =\n String(cookie.getCookie(base_url)) || String(java.getCookie(base_url));\n key3 = getKey(cookieValue);\n} catch (e) {\n key3 = \"\";\n}\n\nif (key3 == \"\") {\n java.log(\"当前服务器未查询到登录状态,尝试查询其他服务器登录状态...\");\n let cookieValue;\n for (let h of host) {\n try {\n cookieValue = String(cookie.getCookie(h)) || String(java.getCookie(h));\n key3 = getKey(cookieValue);\n } catch (e) {\n key3 = \"\";\n }\n if (key3) {\n java.log(`已在${h}登录,退出查询,正在转移登录状态到当前服务${base_url}`);\n \/\/java.log(cookieValue)\n cookie.removeCookie(h);\n cookie.removeCookie(base_url);\n cookie.setCookie(base_url, cookieValue);\n break;\n }\n }\n}\n\nif (book.readConfig == null || book.readConfig.useReplaceRule == null) {\n book.setUseReplaceRule(false);\n}\n\nlet nickname = '账号状态:⚠️ 未登录 | 点击右上角 🔖 登录';\ntry {\n let opcx = {\n method: \"GET\",\n headers: {\n cookie: 'qttoken=' + key3 + ';'\n },\n };\n opcx = JSON.stringify(opcx);\n let user_info = JSON.parse(java.ajax(base_url + '\/get_avatar,' + opcx));\n if (user_info.code == 0) {\n if (user_info.nickname) {\n nickname = '欢迎回来:' + user_info.nickname\n } else {\n nickname = '欢迎回来:' + user_info.email + \"(请前往用户后台设置用户名)\"\n }\n }\n} catch (e) {\n if (key) {\n nickname = '账号状态:已登录'\n }\n};\n\nlet loginStatus = nickname;\n\nlet lightDivider = \"❇️───────❇️───────❇️\";\nlet heavyDivider = \"‎\\n‎\";\n\nlet isValid = (value) => String(value).length > 1;\nlet ctitle = book.durChapterTitle || '未开始';\nlet info = `\n 📡 当前服务:${base_url}\n 🔑 ${loginStatus}\n 🏷 数据来源:${sources}\n 🔄 当前模式:${tab}\n ⚙️ 访问模式:${proxy}\n 📖 阅读至:${ctitle}\n`;\n\nif (tab == \"听书\") {\n \/\/let toneId = getArguments(source.getVariable(), \"tone_id\");\n let toneId = String(book.getVariable('custom'));\n if (toneId == '') {\n \ttoneId= '4';\n \t}\n if (isValid(book_tts)) {\n info += `${lightDivider}\n 🎵 音色配置:${toneId}\n ✨ AI音色请点击右上角书籍变量填写相关值,真人听书请重新搜索选择带有主播的书籍\n ${book_tts}\n`;\n }\n}\n\nlet basicInfo = \"\";\nlet addBasicInfo = (value, prefix, icon) => {\n if (isValid(value)) basicInfo += ` ${icon} ${prefix} ${value}\\n`;\n};\n\naddBasicInfo(tags, \"书籍分类:\", \"🌈\");\naddBasicInfo(role, \"书籍主角:\", \"👑\");\naddBasicInfo(last_chapter_title, \"最新章节:\", \"📚\");\naddBasicInfo(last_chapter_update_time, \"更新时间:\", \"⏳\");\naddBasicInfo(word_number, \"书籍字数:\", \"📊\");\naddBasicInfo(status, \"书籍状态:\", \"🚩\");\naddBasicInfo(score, \"书籍评分:\", \"⭐\");\n\nif (basicInfo) info += `${lightDivider}\\n${basicInfo}`;\n\nif (isValid(abstract)) {\n let indentedAbstract = abstract\n .split(\"\\n\")\n .map((line) => ` ${line}`)\n .join(\"\\n\");\n info += `${heavyDivider}\n 📖 书籍简介:\n${indentedAbstract}\n`;\n} else {\n info += `${heavyDivider}`;\n}\n\nif (isValid(copyright_info)) {\n info += `${lightDivider}\n © ${copyright_info}\n`;\n} else {\n info += `${lightDivider}`;\n}\n\ninfo += `\n${heavyDivider}\n 💠💠💠 数据更新于 ${new Date().toLocaleString()} 💠💠💠\n`;\nlet jjinfo = getArguments(source.getVariable(), \"info\");\n\nif (jjinfo != \"off\") {\n info = String(info)\n .split(\"\\n\")\n .map((line) => line.replace(\/^ {4}\/, \"\"))\n .join(\"\\n\");\n} else {\n basicInfo = \"\";\n addBasicInfo(last_chapter_title, \"最新章节:\", \"📚\");\n addBasicInfo(last_chapter_update_time, \"更新时间:\", \"⏳\");\n addBasicInfo(word_number, \"书籍字数:\", \"📊\");\n addBasicInfo(status, \"书籍状态:\", \"🚩\");\n addBasicInfo(score, \"书籍评分:\", \"⭐\");\n if (isValid(abstract)) {\n let indentedAbstract = abstract\n .split(\"\\n\")\n .map((line) => ` ${line}`)\n .join(\"\\n\");\n basicInfo += `\n \\n‎\n 📖 书籍简介:\n${indentedAbstract}\n`;\n } else {\n basicInfo += `${heavyDivider}`;\n }\n info = `‎\\n🏷 数据来源:${sources}\\n` + basicInfo;\n}\n<\/js>",
"lastChapter": "{{$.source}} {{$.last_chapter_title}} {{$.last_chapter_update_time}}",
"name": "$.book_name",
"tocUrl": "<js>\nlet book_id = result.book_id;\nlet sources = result.source;\nlet tab = result.tab || \"小说\";\nlet url = result.toc_url || \"\";\nvar sdtoken;\ntry {\n var loginInfoMap = source.getLoginInfoMap ? source.getLoginInfoMap() : {};\n sdtoken = String(loginInfoMap['手动填写番茄token(可不填)'] || '');\n} catch (e) {\n sdtoken = '';\n}\nvar rawCookie = getFanqieCookie() || sdtoken;\nvar match = rawCookie.match(\/sessionid=[^;]+\/);\nvar fqcookie = match ? match[0] : '';\nvar fqssionid = '';\nif (fqcookie) {\n fqssionid = getSessionId(fqcookie);\n};\nsetArguments('fqssionid', fqssionid);\njava.put(\"tab\", tab);\njava.put(\"book_id\", book_id);\nlet qtcatalog = {\n book_id: book_id,\n sources: sources,\n tab: tab,\n url: url,\n};\nqtcatalog = java.base64Encode(JSON.stringify(qtcatalog));\n`data:;base64,${qtcatalog},{\"type\":\"qingtian2\"}`;\n<\/js>",
"wordCount": "$.word_number"
},
"ruleContent": {
"content": "<js>\njava.log(result)\nresult = String(java.hexDecodeToString(result));\nlet res;\nif (result.match(\/大灰狼融合\/)) {\n result = result.split(\"大灰狼融合4\");\n res = {\n item_id: result[0],\n tab: result[1],\n title: result[2],\n sources: result[3],\n url: \"\"\n };\n} else {\n res = JSON.parse(result);\n}\n\nfunction checkEnv() {\n let isModified = false;\n try {\n new Packages.io.legato.kazusa.utils.TimeoutCancellationException('');\n isModified = true;\n } catch (e) {\n isModified = typeof source.loginUi == 'function' ? false : true;\n }\n \n return isModified;\n}\n\nvar islyc = checkEnv();\ntry {\nif (islyc) {\n\tbook.imageStyle = 'FULL';\n\t} else {\n\t\tbook.imageStyle = 'TEXT';\n\t\t}\n} catch {}\nlet varia1 = String(book.getVariable('custom'));\nif (varia1 == 'null') {\n varia1 = '';\n}\nlet varia = JSON.stringify({\n 'custom': varia1\n});\n\/\/ varia = java.base64Encode(varia);\nlet book_id = res.book_id;\nlet item_id = res.item_id;\nlet tab = res.tab;\nlet title = res.title;\nlet sources = res.sources;\nlet url = res.url;\nlet html = \"\";\nlet proxy = getArguments(source.getVariable(), \"proxy\");\nif (url != \"\" && proxy == \"本地\") {\n if (sources == '69书吧') {\n let ck69 = String(cookie.getCookie('https:\/\/www.69shuba.com'));\n let headers = {\n \"Referer\": url,\n \"Cookie\": ck69,\n \"User-Agent\": java.getWebViewUA()\n };\n let op = JSON.stringify({\n \"headers\": headers\n });\n java.log(url);\n java.log(op);\n html = java.ajax(`${url},${op}`);\n } else {\n html = java.ajax(url);\n }\n java.log('dddddddddddddddddddddddddddd')\n java.log(html);\n if (html.includes(\"Just a moment...\") && sources == '69书吧' && book.durChapterIndex === chapter.index) {\n cookie.removeCookie('https:\/\/www.69shuba.com');\n java.longToast('需要真人验证,请进入任意书籍详情页过验证');\n html = java.startBrowserAwait(url, \"需要真人验证,请进入任意书籍详情页过验证\").body();\n java.log('cscscscs')\n java.log(html);\n }\n};\nlet content = \"\";\nlet data;\n\/\/let tone_id = getArguments(source.getVariable(), \"tone_id\");\nlet tone_id = varia1;\nif (tone_id == '') {\n tone_id = '4';\n}\nlet base_url = getArguments(source.getVariable(), \"server\");\nlet fqpara = getArguments(source.getVariable(), \"fqpara\");\nlet close_img = getArguments(source.getVariable(), \"close_img\");\nlet device, device_type;\ntry {\n device = java.deviceID();\n device_type = \"苹果\";\n} catch (e) {\n try {\n device = java.androidId();\n device_type = \"安卓\";\n } catch (e) {\n device = \"\";\n device_type = \"安卓\";\n }\n}\n\nlet qtcookie = cookie.getCookie(base_url);\ntry {\n\t let device2 = String(cookie.getKey(base_url, \"device\"));\n\t if (!device2){\n\t \tdevice2 = device;\n\t \t}\n qtcookie = `qttoken=${String(cookie.getKey(base_url, \"qttoken\"))}; deviceId=${device2};`\n} catch (e) {java.log(e)}\n\nvar params = {\n html: html,\n item_id: item_id,\n source: sources,\n tab: tab,\n tone_id: tone_id,\n variable: varia,\n version: '4.11.5.1'\n};\n\nvar content_url = '\/content';\nif ((sources == \"番茄\" || sources == \"七猫\" || sources == \"塔读\" || sources == \"QQ阅读\" || sources == \"svip_QQ阅读\") && fqpara && tab == \"小说\") {\n content_url = '\/content?review=1';\n}\n\n\/\/var signInfo = generateComplexSignature('POST', content_url, {}, APP_SECRET, params);\n\n\n\/\/var signedParams = copyObject(params);\n\/\/signedParams.timestamp = signInfo.timestamp;\n\/\/signedParams.nonce = signInfo.nonce;\n\/\/signedParams.sign = signInfo.sign;\n\n\nvar op = {\n method: \"POST\",\n body: JSON.stringify(params),\n headers: {\n cookie: qtcookie,\n 'Content-Type': 'application\/json'\n }\n};\n\nop = JSON.stringify(op);\njava.log(op);\njava.log(cookie.getCookie(base_url))\ndata = java.ajax(base_url + content_url + `,${op}`);\ntry {\n data = JSON.parse(data);\n if (data.msg) {\n java.toast(data.msg);\n }\n} catch (e) {}\ncontent = data.content\n\nif (close_img == 'on') {\n\tcontent = removeAllImgTags(content);\n\t}\n\nif ((sources == \"番茄\" || sources == \"七猫\" || sources == \"塔读\" || sources == \"QQ阅读\" || sources == \"svip_QQ阅读\") && fqpara == \"on\" && tab == \"小说\") {\n var sdtoken;\n try {\n var loginInfoMap = source.getLoginInfoMap ? source.getLoginInfoMap() : {};\n sdtoken = String(loginInfoMap['手动填写番茄token(可不填)'] || '');\n } catch (e) {\n sdtoken = '';\n }\n var rawCookie = getFanqieCookie() || sdtoken;\n var match = rawCookie.match(\/sessionid=[^;]+\/);\n var fqcookie = match ? match[0] : '';\n var fqssionid = '';\n if (!fqcookie) {} else {\n fqssionid = getSessionId(fqcookie)\n }\n content = content\n .replace(\/ident=\"\/g, 'ident=\"' + base_url)\n .replace(\/book_id=\/g, 'book_id=' + book_id + '&ssionid=' + fqssionid);\n if (device_type == \"苹果\") {\n content = paraForiOS(content, sources);\n } else {\n content = paraForAndroid(content, sources,islyc);\n }\n}\ndata = JSON.stringify({\n content: content,\n});\nif (device_type == \"安卓\" && (tab == \"短剧\" || tab == \"视频\" || sources.includes('毒舌影视') || sources.includes('NT动漫'))) {\n data = {\n content: `【右上角刷新】开启播放(下一集请切换下一章刷新)\\n播放直链:\\n${content}`,\n };\n data = JSON.stringify(data);\n if (book.durChapterIndex === chapter.index) {\n let video_url = `${base_url}\/online_video?book_id=${book_id}&source=${sources}&tab=${tab}`;\n if (sources.includes('毒舌影视') || sources.includes('NT动漫')) {\n video_url = content;\n };\n java.startBrowser(video_url, title);\n java.toast(\"正在加载视频...\");\n }\n\n}\ndata;\n<\/js>$.content"
},
"ruleExplore": {
"author": "$.author",
"bookList": "$.data",
"bookUrl": "<js>\nlet book_id = result.book_id;\nlet sources = result.source;\nlet tab = result.tab || '小说';\nlet url = result.toc_url || '';\n\nlet qtdetail = {\n book_id: book_id,\n sources: sources,\n tab: tab,\n url: url\n}\nqtdetail = java.base64Encode(JSON.stringify(qtdetail));\n`data:;base64,${qtdetail},{\"type\":\"qingtian\"}`\n<\/js>",
"coverUrl": "$.thumb_url",
"intro": "$.abstract",
"kind": "{{$.category}}\n{{$.score}}\n{{$.status}}\n{{$.source}}\n{{$.tags}}",
"lastChapter": "{{$.last_chapter_title}} • {{$.last_update_time}}",
"name": "$.book_name",
"wordCount": "$.word_number"
},
"ruleSearch": {
"author": "$.author",
"bookList": "$.data",
"bookUrl": "<js>\nlet book_id = result.book_id;\nlet sources = result.source;\nlet tab = result.tab || '小说';\nlet url = result.toc_url || '';\n\nlet qtdetail = {\n book_id: book_id,\n sources: sources,\n tab: tab,\n url: url\n}\nqtdetail = java.base64Encode(JSON.stringify(qtdetail));\n`data:;base64,${qtdetail},{\"type\":\"qingtian\"}`\n<\/js>",
"checkKeyWord": "我的26岁女房客@番茄",
"coverUrl": "$.thumb_url",
"intro": "$.abstract",
"kind": "{{$.status}},{{$.score}},{{$.tags}},{{$.last_chapter_update_time}}",
"lastChapter": "{{$.source}} {{$.last_chapter_title}}",
"name": "$.book_name##(别名:.*?)",
"wordCount": "$.word_number"
},
"ruleToc": {
"chapterList": "<js>\nlet res = JSON.parse(java.hexDecodeToString(result));\nif (res.method) {\n res = Object.fromEntries(\n res.body\n .replace(\"source\", \"sources\")\n .split(\"&\")\n .map((query) => query.split(\"=\"))\n );\n res.url = \"\";\n}\nlet book_id = res.book_id;\njava.put('book_id', book_id);\nlet tab = res.tab;\nlet sources = res.sources;\nlet url = res.url;\nlet html = \"\";\nlet proxy = getArguments(source.getVariable(), \"proxy\");\nif (url != \"\" && proxy == \"本地\") {\n if (sources == '69书吧') {\n let ck69 = String(cookie.getCookie('https:\/\/www.69shuba.com'));\n let headers = {\n \t \"Referer\": url,\n \"Cookie\": ck69,\n \"User-Agent\": java.getWebViewUA()\n };\n let op = JSON.stringify({\n \"headers\": headers\n });\n html = java.ajax(`${url},${op}`); \n } else {\n html = java.ajax(url);\n }\n \/\/java.log(html);\n if (html.includes(\"Just a moment...\") && sources == '69书吧') {\n cookie.removeCookie('https:\/\/www.69shuba.com');\n java.longToast('需要真人验证,请进入任意书籍详情页过验证');\n html = java.startBrowserAwait(url, \"需要真人验证,请进入任意书籍详情页过验证\").body();\n\n java.log(html);\n }\n};\nlet base_url = getArguments(source.getVariable(), \"server\");\nlet op = {\n method: \"POST\",\n body: {\n html: html\n }\n};\nop = JSON.stringify(op);\nlet varia = String(book.getVariable('custom'));\nif (varia == 'null') {\n varia = '';\n}\nvaria = JSON.stringify({\n 'custom': varia\n});\n\/\/ varia = java.base64Encode(varia);\n\/\/java.log(`${base_url}\/catalog?book_id=${book_id}&source=${sources}&tab=${tab}&variable=${varia},${op}`);\nlet data = java.ajax(\n `${base_url}\/catalog?book_id=${book_id}&source=${sources}&tab=${tab}&variable=${varia},${op}`\n);\nlet device, device_type;\ntry {\n device = java.deviceID();\n device_type = \"苹果\";\n} catch (e) {\n try {\n device = java.androidId();\n device_type = \"安卓\";\n } catch (e) {\n device = \"\";\n device_type = \"安卓\";\n }\n}\n\nif (tab == \"小说\") {\n if (device_type == \"安卓\") {\n book.type = 8;\n } else {\n book.type = 0;\n }\n} else if (tab == \"听书\") {\n if (device_type == \"安卓\") {\n book.type = 32;\n } else {\n book.type = 1;\n }\n} else if (tab == \"漫画\") {\n if (device_type == \"安卓\") {\n book.type = 64;\n } else {\n book.type = 2;\n }\n} else if (tab == \"短剧\") {\n if (device_type == \"安卓\") {\n book.type = 8;\n } else {\n book.type = 3;\n }\n} else {\n if (device_type == \"安卓\") {\n book.type = 8;\n } else {\n book.type = 0;\n }\n}\nlet qtcookie = cookie.getCookie(base_url);\nlet reading = getArguments(source.getVariable(), \"reading\");\n\nif (book && book.order != 0 && reading == '1') {\n try {\n if (!data || !book.variable || !base_url || !qtcookie) {\n java.log('缺少必要参数');\n } else {\n let parsedData;\n try {\n parsedData = JSON.parse(data);\n } catch (e) {\n java.log('解析data数据失败:' + e);\n }\n \n if (parsedData && parsedData.data && Array.isArray(parsedData.data) && parsedData.data[book.durChapterIndex]) {\n let ritem = parsedData.data[book.durChapterIndex];\n let bookInfoObj;\n try {\n bookInfoObj = JSON.parse(book.variable);\n } catch (e) {\n java.log('解析book.variable失败:' + e);\n }\n \n if (bookInfoObj) {\n let book_info;\n try {\n book_info = JSON.parse(bookInfoObj.book_detail || JSON.stringify(bookInfoObj));\n } catch (e) {\n java.log('解析book_detail失败:' + e);\n }\n \n if (book_info && typeof book_info === 'object') {\n let rurl = base_url + '\/add_book_to_book_shelf,';\n book_info['read_status'] = 1;\n book_info['last_chapter_item_id'] = ritem.item_id || '';\n book_info['last_chapter_title'] = ritem.title || '';\n let rop = {\n method: \"POST\",\n headers: {\n cookie: qtcookie\n },\n body: book_info\n };\n let check_book_url = base_url + '\/check_book_in_book_shelf,';\n let checkResponse;\n \n try {\n checkResponse = java.ajax(check_book_url + JSON.stringify(rop));\n } catch (e) {\n java.log('检查书籍请求失败:' + e);\n }\n \n if (checkResponse) {\n let check_data;\n try {\n check_data = JSON.parse(checkResponse).data;\n } catch (e) {\n java.log('解析检查响应失败:' + e);\n }\n try {\n if (check_data && check_data.id) {\n book_info['id'] = check_data.id;\n let uurl = base_url + '\/update_book_shelf,';\n java.ajax(uurl + JSON.stringify(rop));\n } else {\n java.ajax(rurl + JSON.stringify(rop));\n }\n } catch (e) {\n java.log('书架操作失败:' + e);\n }\n }\n }\n }\n }\n }\n } catch (error) {\n java.log('书籍同步流程异常:' + error);\n java.longToast('\\n同步阅读进度失败,但不影响阅读,可以前往登录关闭书架同步功能。');\n }\n}\ndata;\n<\/js>$.data",
"chapterName": "$.title",
"chapterUrl": "<js>\nlet tab = result.tab;\nlet sources = result.source;\nlet title = result.title;\nlet item_id = result.item_id;\nlet book_id = java.get(\"book_id\");\nlet url = result.toc_url || \"\";\nlet qtcontent = {\n book_id: book_id,\n item_id: item_id,\n title: title,\n sources: sources,\n tab: tab,\n url: url,\n};\nqtcontent = java.base64Encode(JSON.stringify(qtcontent));\nif (sources == '卷') {\n content_url = item_id\n} else if ((sources == \"番茄\" || sources == \"七猫\" || sources == \"塔读\") && tab == \"小说\") {\n var base_url = getArguments(source.getVariable(), \"server\") || \"\";\n var sdtoken;\n try {\n var loginInfoMap = source.getLoginInfoMap ? source.getLoginInfoMap() : {};\n sdtoken = String(loginInfoMap['手动填写番茄token(可不填)'] || '');\n } catch (e) {\n sdtoken = '';\n }\n var rawCookie = getFanqieCookie() || sdtoken;\n var match = rawCookie.match(\/sessionid=[^;]+\/);\n var fqcookie = match ? match[0] : '';\n var fqssionid = '';\n if (!fqcookie) {} else {\n fqssionid = getSessionId(fqcookie)\n }\n let sourcess = sources.replace('svip_', '');\n content_url = `data:;base64,${qtcontent},{\"type\":\"qingtian3\",\"js\":\"book ? result : '${base_url}\/get_review?book_id=${book_id}&item_id=${item_id}&ssionid=${fqssionid}&source=${sourcess}'\"}`;\n} else {\n content_url = `data:;base64,${qtcontent},{\"type\":\"qingtian3\"}`;\n}\n<\/js>",
"updateTime": "$.first_pass_time"
},
"searchUrl": "<js>\nlet base_url = getArguments(source.getVariable(), 'server');\nlet media;\nlet sources = getArguments(source.getVariable(), 'sources') || getArguments(source.getVariable(), 'source');\nlet disabled_sources = getArguments(source.getVariable(), 'disabled_sources') || '0';\nif (String(key).startsWith(\"m:\") || String(key).startsWith(\"m:\")) {\n media = \"漫画\"\n key = key.slice(2)\n} else if (String(key).startsWith(\"t:\") || String(key).startsWith(\"t:\")) {\n media = \"听书\"\n key = key.slice(2)\n} else if (String(key).startsWith(\"d:\") || String(key).startsWith(\"d:\")) {\n media = \"短剧\"\n key = key.slice(2)\n} else if (String(key).startsWith(\"x:\") || String(key).startsWith(\"x:\")) {\n media = \"小说\"\n key = key.slice(2)\n} else {\n media = getArguments(source.getVariable(), 'tab') || getArguments(source.getVariable(), 'media');\n}\nif (key.includes('@')) {\n var parts = key.split('@');\n key = parts[0];\n sources = parts[1] || sources;\n}\nlet qtcookie = cookie.getCookie(base_url);\nlet op = {\n method: \"GET\",\n headers: {\n cookie: qtcookie\n },\n};\nop = JSON.stringify(op);\n`${base_url}\/search?title=${key}&tab=${media}&source=${sources}&page={{page}}&disabled_sources=${disabled_sources},${op}`\n<\/js>",
"weight": 0
}