天悦小说网

https://www.xtyxsw.org/

autobcb_admin (12020)05/14 04:08

该用户很懒,什么介绍也没有写!
二维码导入(APP尚未完成该功能)
{
    "bookSourceUrl": "https:\/\/www.xtyxsw.org\/",
    "bookSourceName": "天悦小说网",
    "enabledExplore": true,
    "enabled": true,
    "bookSourceGroup": "",
    "author": "trae",
    "help": false,
    "html": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>天悦小说网<\/title>\n<\/head>\n<body>\n\n<\/body>\n<script src=\"https:\/\/vc.jd.com\/web\/js\/jquery-3.1.1.min.js\"><\/script>\n<script>\n  var isCookieJar=true;\n  class FlutterJSBridge {\n    constructor() {\n      this.init();\n    }\n\n    init() {\n      if (window.flutter_inappwebview) {\n        this.isReady = true;\n        this.CookieJar();\n      } else {\n        window.addEventListener('flutterInAppWebViewPlatformReady', () => {\n          this.isReady = true;\n          console.log('JSBridge初始化完成');\n          this.CookieJar();\n        });\n      }\n    }\n\n    async CookieJar() {\n      try {\n        await window.flutter_inappwebview.callHandler('CookieJar', isCookieJar);\n      } catch (error) {\n        console.error('汇报完成准备失败:', error);\n      }\n    }\n\n    async getbuildNumber() {\n      try { return await window.flutter_inappwebview.callHandler('buildNumber'); } catch (error) { return 0; }\n    }\n\n    async getversion() {\n      try { return await window.flutter_inappwebview.callHandler('version'); } catch (error) { return \"0.0.0\"; }\n    }\n    \n    async htmlToText(str) {\n      try { return await window.flutter_inappwebview.callHandler('htmlToText',str); } catch (error) { return \"\"; }\n    }\n    \n    async toTraditional(str) {\n      try { return await window.flutter_inappwebview.callHandler('toTraditional',str); } catch (error) { return \"\"; }\n    }\n    \n    async toSimplified(str) {\n      try { return await window.flutter_inappwebview.callHandler('toSimplified',str); } catch (error) { return \"\"; }\n    }\n\n    async voice() {\n      try { return await window.flutter_inappwebview.callHandler('voice'); } catch (error) { return \"\"; }\n    }\n   \n    async getDeviceid() {\n      try { return await window.flutter_inappwebview.callHandler('id'); } catch (error) { return \"\"; }\n    }\n\n    async getDevice() {\n      try { return await window.flutter_inappwebview.callHandler('device'); } catch (error) { return \"\"; }\n    }\n    \n    async getLoginUser() {\n      try { return await window.flutter_inappwebview.callHandler('getLoginUser'); } catch (error) { return \"\"; }\n    }\n\n    async log(str) {\n      try { return await window.flutter_inappwebview.callHandler('log',str); } catch (error) { return false; }\n    }\n\n    async text(type,str) {\n      try { return await window.flutter_inappwebview.callHandler('text',type,str); } catch (error) { return false; }\n    }\n\n    async showToast(str) {\n      try { return await window.flutter_inappwebview.callHandler('showToast',str); } catch (error) { return false; }\n    }\n    \n    async showLongToast(str) {\n      try { return await window.flutter_inappwebview.callHandler('showLongToast',str); } catch (error) { return false; }\n    }\n\n    async getWebViewUA() {\n      try { return await window.flutter_inappwebview.callHandler('getWebViewUA'); } catch (error) { return \"\"; }\n    }\n\n    async openurl(url) {\n      try { return await window.flutter_inappwebview.callHandler('openurl',url,\"\"); } catch (error) { return false; }\n    }\n\n    async openurlwithMimeType(url,mimeType) {\n      try { return await window.flutter_inappwebview.callHandler('openurl',url,mimeType); } catch (error) { return false; }\n    }\n\n    async webview(url,js,html,body,header) {\n      try { return await window.flutter_inappwebview.callHandler('webview',url,js,html,body,header,\"\",\"\"); } catch (error) { return \"\"; }\n    }\n\n    async webViewGetOverrideUrl(url,js,html,body,header,overrideUrlRegex) {\n      try { return await window.flutter_inappwebview.callHandler('webview',url,js,html,body,header,overrideUrlRegex,\"\"); } catch (error) { return \"\"; }\n    }\n\n    async webViewGetSource(url,js,html,body,header,urlregex) {\n      try { return await window.flutter_inappwebview.callHandler('webview',url,js,html,body,header,\"\",urlregex); } catch (error) { return \"\"; }\n    }\n    \n    async webViewGetAjax(url,html,body,header,ajaxregex) {\n      try { return await window.flutter_inappwebview.callHandler('webviewajax',url,html,body,header,ajaxregex); } catch (error) { return \"\"; }\n    }\n\n    async startBrowser(url,title,header) {\n      try { return await window.flutter_inappwebview.callHandler('startBrowser',url,title,header); } catch (error) { return \"\"; }\n    }\n    \n    async startBrowserWithShouldOverrideUrlLoading(url,title,header) {\n      try { return await window.flutter_inappwebview.callHandler('startBrowserWithShouldOverrideUrlLoading',url,title,header); } catch (error) { return \"\"; }\n    }\n\n    async startBrowserDp(url,title) {\n      try { return await window.flutter_inappwebview.callHandler('startBrowserDp',url,title); } catch (error) { return \"\"; }\n    }\n\n    async back() {\n      try { return await window.flutter_inappwebview.callHandler('back'); } catch (error) { return false; }\n    }\n\n    async utf8ToGbkUrlEncoded(str) {\n      try { return await window.flutter_inappwebview.callHandler('utf8ToGbkUrlEncoded',str); } catch (error) { return \"\"; }\n    }\n\n    async getVerificationCode(str,header) {\n      try { return await window.flutter_inappwebview.callHandler('getVerificationCode',str,header); } catch (error) { return \"\"; }\n    }\n    \n    async addbook(bookUrl) {\n      try { return await window.flutter_inappwebview.callHandler('addbook',bookUrl); } catch (error) { return \"\"; }\n    }\n    \n    async getdurChapterIndex(bookUrl) {\n      try { return await window.flutter_inappwebview.callHandler('getdurChapterIndex',bookUrl); } catch (error) { return 0; }\n    }\n    \n    async base64encode(str) {\n      try { return await window.flutter_inappwebview.callHandler('base64encode',str); } catch (error) { return \"\"; }\n    }\n    \n    async base64decode(str) {\n      try { return await window.flutter_inappwebview.callHandler('base64decode',str); } catch (error) { return \"\"; }\n    }\n  }\n\n  class Http {\n    constructor() {\n      this.open = false;\n      this.requestTimestamps = [];\n      this.rateLimit = 5;\n      this.rateLimitWindow = 1000;\n    }\n\n    async checkRateLimit() {\n      if(!this.open) return;\n      const now = Date.now();\n      this.requestTimestamps = this.requestTimestamps.filter(timestamp => now - timestamp < this.rateLimitWindow);\n      if (this.requestTimestamps.length >= this.rateLimit) {\n        const oldestTimestamp = this.requestTimestamps[0];\n        const waitTime = this.rateLimitWindow - (now - oldestTimestamp);\n        await new Promise(resolve => setTimeout(resolve, waitTime));\n        return this.checkRateLimit();\n      }\n      this.requestTimestamps.push(now);\n    }\n\n    async Get(url,headers,followRedirects) {\n      try { await this.checkRateLimit(); return await window.flutter_inappwebview.callHandler('http',\"get\",url,\"\",JSON.stringify(headers),followRedirects,\"\"); } catch (error) { return null; }\n    }\n\n    async Head(url,headers,followRedirects) {\n      try { await this.checkRateLimit(); return await window.flutter_inappwebview.callHandler('http',\"head\",url,\"\",JSON.stringify(headers),followRedirects,\"\"); } catch (error) { return null; }\n    }\n    \n    async Post(url,headers,body,contenttype,followRedirects) {\n      try { await this.checkRateLimit(); return await window.flutter_inappwebview.callHandler('http',\"post\",url,body,JSON.stringify(headers),followRedirects,contenttype); } catch (error) { return null; }\n    }\n  }\n\n  class Cache {\n    constructor() {}\n    async get(key) { try { return await window.flutter_inappwebview.callHandler('cache.get',key); } catch (error) { return null; } }\n    async set(key,value) { try { return await window.flutter_inappwebview.callHandler('cache.set',key,value); } catch (error) { return null; } }\n    async remove(key) { try { return await window.flutter_inappwebview.callHandler('cache.remove',key); } catch (error) { return null; } }\n    async getLoginInfo(){ return await this.get(\"LoginInfo\") }\n    async putLoginInfo(info){ return await this.set(\"LoginInfo\",info) }\n    async getbookVariable(bookurl){ return await this.get(bookurl) }\n    async setbookVariable(bookurl,value){ return await this.set(bookurl,value) }\n  }\n\n  class Cookie {\n    constructor() {}\n    async get(url) { try { return await window.flutter_inappwebview.callHandler('cookie.get',url); } catch (error) { return null; } }\n    async remove(url) { try { return await window.flutter_inappwebview.callHandler('cookie.remove',url); } catch (error) { return null; } }\n    async set(url,value) { try { return await window.flutter_inappwebview.callHandler('cookie.set',url,value); } catch (error) { return null; } }\n    async setCookie(url,key,value) { try { return await window.flutter_inappwebview.callHandler('cookie.setcookie',url,key,value); } catch (error) { return null; } }\n    async getCookie(url,value) { try { return await window.flutter_inappwebview.callHandler('cookie.getCookie',url,value); } catch (error) { return null; } }\n  }\n\n  function parseHTMLSafely(htmlStr) {\n    try { var tempDiv = document.createElement('div'); tempDiv.innerHTML = htmlStr; return $(tempDiv); } catch (e) { flutterBridge.log(\"HTML解析错误:\"+e.message); return $('<div>'); }\n  }\n\n  function removeHTMLSafely(tempContainer) {\n    try { tempContainer.innerHTML = ''; if (tempContainer.parentNode) { tempContainer.parentNode.removeChild(tempContainer); } } catch (e) { flutterBridge.log(\"HTML移除失败:\"+e.message); }\n  }\n\n  function removeHTMLTags(htmlString) {\n    let result = htmlString.replace(\/<script\\b[^<]*(?:(?!<\\\/script>)<[^<]*)*<\\\/script>\/gi, '');\n    result = result.replace(\/<style\\b[^<]*(?:(?!<\\\/style>)<[^<]*)*<\\\/style>\/gi, '');\n    return result;\n  }\n\n<\/script>\n\n<script>\n    const flutterBridge = new FlutterJSBridge();\n    const cache = new Cache();\n    const http = new Http();\n    const cookie = new Cookie();\n    var baseurl=\"https:\/\/www.xtyxsw.org\"\n    var header={\n        \"User-Agent\": \"Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/120.0.0.0 Safari\/537.36\",\n        \"Referer\": baseurl,\n        \"Accept\": \"text\/html,application\/xhtml+xml,application\/xml;q=0.9,*\/*;q=0.8\"\n    };\n\n    function normalizeUrl(url) {\n        if (!url) return \"\";\n        if (url.startsWith('http')) return url;\n        if (url.startsWith('\/')) return baseurl + url;\n        return baseurl + '\/' + url;\n    }\n\n    \/*\n     * search(key, page) - 搜索功能\n     * \n     * ⚠️ 重要发现:天悦小说网的搜索关键词会被编码成数字ID!\n     * 示例:\n     *   \"我的\" → 487982 → URL: \/search\/487982\/1.html\n     *   \"系统\" → 320182 → URL: \/search\/320182\/1.html\n     *   \"测试\" → 397748 → URL: \/search\/397748\/1.html\n     * \n     * 这不是标准的URL编码,而是网站特有的编码机制。\n     * 解决方案:使用WebView方式,让浏览器自动处理编码!\n     *\/\n    async function search(key, page) {\n        if(page > 1){\n            \/\/ 翻页时可以直接构造URL(因为已经有了数字ID)\n            \/\/ 但我们不知道ID,所以暂时只支持第1页\n            return \"[]\";\n        }\n\n        var mheader={\n            ...header,\n            \"Referer\": baseurl\n        };\n\n        flutterBridge.log(\"开始搜索: \" + key);\n\n        \/\/ ====== 方案:使用WebView自动执行搜索 ======\n        \/\/ 原理:\n        \/\/ 1. 先访问首页\n        \/\/ 2. 用JS自动填写搜索框\n        \/\/ 3. 自动点击搜索按钮\n        \/\/ 4. 让浏览器自己处理关键词编码\n        \/\/ 5. 拦截跳转后的搜索结果页面\n        \n        var injectJs = `\n            (function(){\n                try{\n                    \/\/ 查找搜索输入框(通过placeholder或其他特征)\n                    var input = null;\n                    var inputs = document.querySelectorAll('input[type=\"text\"], input:not([type])');\n                    for(var i=0; i<inputs.length; i++){\n                        if(inputs[i].placeholder && inputs[i].placeholder.indexOf('书名') !== -1){\n                            input = inputs[i];\n                            break;\n                        }\n                    }\n                    \n                    if(!input){\n                        \/\/ 备选:找第一个文本输入框\n                        input = document.querySelector('input[type=\"text\"]') || document.querySelector('input');\n                    }\n                    \n                    if(input){\n                        \/\/ 设置关键词\n                        input.value = '${key.replace(\/'\/g, \"\\\\'\")}';\n                        \n                        \/\/ 触发input事件(某些网站需要)\n                        input.dispatchEvent(new Event('input', {bubbles: true}));\n                        input.dispatchEvent(new Event('change', {bubbles: true}));\n                        \n                        \/\/ 查找并点击搜索按钮\n                        setTimeout(function(){\n                            \/\/ 尝试多种方式找到按钮\n                            var btn = null;\n                            \n                            \/\/ 方法1: input的下一个兄弟元素\n                            btn = input.nextElementSibling;\n                            if(btn && (btn.tagName === 'BUTTON' || btn.tagName === 'A' || btn.type === 'submit')){\n                                btn.click();\n                                return;\n                            }\n                            \n                            \/\/ 方法2: 查找包含\"搜索\"文字的按钮\/链接\n                            var elements = document.querySelectorAll('button, a, input[type=\"submit\"], input[type=\"button\"]');\n                            for(var j=0; j<elements.length; j++){\n                                var text = (elements[j].textContent || elements[j].value || '').trim();\n                                if(text.indexOf('搜索') !== -1 || text.indexOf('开始') !== -1){\n                                    elements[j].click();\n                                    return;\n                                }\n                            }\n                            \n                            \/\/ 方法3: 找form并提交\n                            var form = input.form || input.closest('form');\n                            if(form){\n                                form.submit();\n                            } else {\n                                \/\/ 最后尝试:模拟回车键\n                                var evt = new KeyboardEvent('keydown', {keyCode: 13, which: 13});\n                                input.dispatchEvent(evt);\n                            }\n                        }, 300);\n                        \n                        return true;\n                    } else {\n                        console.log('未找到搜索输入框');\n                        return false;\n                    }\n                } catch(e) {\n                    console.log('搜索注入JS出错: ' + e.message);\n                    return false;\n                }\n            })();\n        `;\n\n        try {\n            \/\/ ====== 正确的WebView搜索流程 ======\n            \/\/ 第1步:先获取首页HTML\n            flutterBridge.log(\"第1步: 获取首页HTML...\");\n            var homeResult = await http.Get(baseurl, mheader, true);\n            \n            if(!homeResult || !homeResult.data){\n                flutterBridge.showToast(\"获取首页失败\");\n                return \"[]\";\n            }\n            \n            var homeHtml = homeResult.data;\n            flutterBridge.text(0, homeHtml);  \/\/ 调试用:查看首页结构\n            flutterBridge.log(\"获取到首页HTML,长度: \" + homeHtml.length);\n\n            \/\/ 第2步:将搜索执行代码作为<script>标签插入HTML\n            \/\/ 注意:webview的js参数只支持简单一行代码,复杂逻辑必须写在html参数的<script>里!\n            \/\/ 构建搜索脚本\n            \/\/ 使用数组拼接避免引号嵌套问题\n            var k = key;  \/\/ 局部变量便于引用\n            \n            var scriptLines = [];\n            scriptLines.push(\"<\" + \"script>\");\n            scriptLines.push(\"var __search_keyword__ = \" + JSON.stringify(k) + \";\");\n            scriptLines.push(\"setTimeout(function(){\");\n            scriptLines.push(\"try{\");\n            scriptLines.push(\"var input = null;\");\n            scriptLines.push(\"var inputs = document.getElementsByTagName('input');\");\n            scriptLines.push(\"for(var i=0; i<inputs.length; i++){\");\n            scriptLines.push(\"if(inputs[i].placeholder && inputs[i].placeholder.indexOf('书名') !== -1){input = inputs[i]; break;}\");\n            scriptLines.push(\"}\");\n            scriptLines.push(\"if(!input){ input = document.getElementsByTagName('input')[0]; }\");\n            scriptLines.push(\"if(input){\");\n            scriptLines.push(\"input.value = __search_keyword__;\");  \/\/ 使用预定义的变量\n            scriptLines.push(\"input.dispatchEvent(new Event('input', {bubbles: true}));\");\n            scriptLines.push(\"input.dispatchEvent(new Event('change', {bubbles: true}));\");\n            scriptLines.push(\"setTimeout(function(){\");\n            scriptLines.push(\"var btn = input.nextElementSibling;\");\n            scriptLines.push(\"if(btn && (btn.tagName === 'BUTTON' || btn.tagName === 'A')){ btn.click(); return; }\");\n            scriptLines.push(\"var elements = document.getElementsByTagName('button');\");\n            scriptLines.push(\"for(var j=0; j<elements.length; j++){\");\n            scriptLines.push(\"var text = (elements[j].textContent || '').trim();\");\n            scriptLines.push(\"if(text.indexOf('搜索') !== -1 || text.indexOf('开始') !== -1){ elements[j].click(); return; }\");\n            scriptLines.push(\"}\");\n            scriptLines.push(\"var form = input.form;\");\n            scriptLines.push(\"if(form) form.submit();\");\n            scriptLines.push(\"}, 300);\");\n            scriptLines.push(\"}\");\n            scriptLines.push(\"} catch(e) { console.log('error: ' + e.message); }\");\n            scriptLines.push(\"}, 500);\");\n            scriptLines.push(\"<\" + \"\/script>\");\n            \n            var searchScript = scriptLines.join(\"\\n\");\n            \n            \/\/ 将脚本插入到HTML\n            \/\/ 注意:很多网站的HTML可能没有标准的<\/body>标签,所以需要多种策略\n            \n            var htmlWithScript = \"\";\n            \n            \/\/ 策略1: 尝试在<\/body>前插入\n            if (homeHtml.indexOf('<\/body>') !== -1) {\n                htmlWithScript = homeHtml.replace(\/<\\\/body>\/i, searchScript + '<\/body>');\n                flutterBridge.log(\"使用策略1: 在<\/body>前插入脚本\");\n            }\n            \/\/ 策略2: 尝试在<\/html>前插入\n            else if (homeHtml.indexOf('<\/html>') !== -1) {\n                htmlWithScript = homeHtml.replace(\/<\\\/html>\/i, searchScript + '<\/html>');\n                flutterBridge.log(\"使用策略2: 在<\/html>前插入脚本\");\n            }\n            \/\/ 策略3: 直接追加到HTML末尾\n            else {\n                htmlWithScript = homeHtml + \"\\n\" + searchScript;\n                flutterBridge.log(\"使用策略3: 追加到HTML末尾\");\n            }\n            \n            \/\/ 第3步:使用webview加载包含脚本的HTML\n            \/\/ 参数说明:\n            \/\/ - url: 首页URL(浏览器会基于此加载资源)\n            \/\/ - js: 简单的一行JS(这里不需要)\n            \/\/ - html: 包含完整搜索脚本的HTML\n            \/\/ - body: POST数据(空)\n            \/\/ - header: 请求头\n            flutterBridge.log(\"第2步: 使用webview加载并自动搜索...\");\n            \n            var htmlContent = await flutterBridge.webview(\n                baseurl,              \/\/ URL\n                \"\",                   \/\/ js(简单代码,这里用不到)\n                htmlWithScript,      \/\/ ⭐ 包含搜索脚本的HTML!\n                \"\",                   \/\/ body\n                JSON.stringify(mheader)  \/\/ header\n            );\n\n            if(!htmlContent){\n                flutterBridge.showToast(\"搜索请求失败\");\n                return \"[]\";\n            }\n           \/\/ flutterBridge.log(\"搜索结果HTML: \" + htmlWithScript);\n\n            flutterBridge.text(0, htmlContent);\n            flutterBridge.log(\"获取到搜索结果HTML,长度: \" + htmlContent.length);\n\n        } catch(e) {\n            flutterBridge.log(\"WebView搜索失败: \" + e.message);\n            \n            \/\/ 兜底方案:如果WebView失败,提示用户\n            flutterBridge.showToast(\"搜索功能暂时不可用\");\n            return \"[]\";\n        }\n\n        var books = [];\n        var processedUrls = new Set();\n        var $tempContainer = parseHTMLSafely(removeHTMLTags(htmlContent));\n\n        \/\/ 已验证书籍链接格式: \/read\/{书籍ID}\/\n        \/\/ 示例: \/read\/264234\/\n        var bookLinkMatcher = function(href) {\n            if (!href) return false;\n            return \/^\\\/read\\\/\\d+\\\/$\/.test(href);\n        };\n\n        \/\/ ====== 基于浏览器100%验证过的真实DOM结构 ======\n        \/\/ 验证发现每本书的结构:\n        \/\/ \n        \/\/ <div>  ← 每本书的容器(具体class待确认)\n        \/\/   <a href=\"\/read\/{书籍ID}\/\">\n        \/\/     <img src=\"https:\/\/img.xtyxsw.org\/{书籍ID}\/{图片ID}.jpg\" alt=\"书名\">\n        \/\/   <\/a>                              ← 封面图\n        \/\/   \n        \/\/   <h2>\n        \/\/     <a href=\"\/read\/{书籍ID}\/\">《 书名 》<\/a>\n        \/\/   <\/h2>                               ← 书名(带书名号)\n        \/\/   \n        \/\/   作者:<a href=\"\/author\/{作者}.html\">{作者名}<\/a>  ← 作者(链接格式:\/author\/xxx.html)\n        \/\/   \n        \/\/   最新更新:<a href=\"\/read\/{书籍ID}\/{章节ID}.html\">第{N}章 标题<\/a>  ← 最新章节\n        \/\/   \n        \/\/   <p>简介文本...<\/p>                   ← 简介\n        \/\/   \n        \/\/   <a href=\"\/read\/{书籍ID}\/\">开始阅读<\/a>    ← 阅读按钮\n        \/\/   <a href=\"#\">加入书架<\/a>               ← 加架按钮\n        \/\/ <\/div>\n\n        \/\/ 策略1: 查找包含 \/read\/{数字}\/ 格式链接的容器(这是最可靠的特征)\n        $tempContainer.find(\"div, li, section, article\").each(function() {\n            try{\n                var $item = $(this);\n                \n                \/\/ 必须包含书籍链接(\/read\/数字\/格式)- 这是识别书籍容器的核心特征!\n                var $bookLink = $item.find(\"a[href*='\/read\/']\").first();\n                var bookUrl = $bookLink.attr('href');\n                \n                if (!bookUrl || !bookLinkMatcher(bookUrl) || processedUrls.has(bookUrl)) { \n                    return true;  \/\/ 不是书籍容器或已处理过,跳过\n                }\n                \n                processedUrls.add(bookUrl);\n                \n                \/\/ ========== 1. 书名 ==========\n                \/\/ 已验证:在 <h2><a> 或直接 <a> 中,可能带书名号《》\n                var bookName = \"\";\n                \n                \/\/ 方法A:从h2标签获取\n                var $h2 = $item.find(\"h2\").first();\n                if($h2.length > 0){\n                    bookName = $h2.find(\"a\").first().attr('title') || $h2.find(\"a\").first().text() || $h2.text();\n                    bookName = bookName.trim();\n                }\n                \n                \/\/ 方法B:从书籍链接本身获取(如果h2没找到)\n                if(!bookName){\n                    bookName = $bookLink.attr('title') || $bookLink.text().trim();\n                }\n                \n                \/\/ 清理书名号\n                if(bookName){\n                    bookName = bookName.replace(\/[《》\"\"''【】\\(\\)]\/g, '').trim();\n                }\n                \n                if(!bookName || bookName.length < 2) return true;\n                \n                \/\/ ========== 2. 作者 ==========\n                \/\/ 已验证:<a href=\"\/author\/{作者名}.html\">作者名<\/a>\n                var author = \"\";\n                \n                \/\/ 方法A:查找 \/author\/ 格式的链接(最准确!)\n                $item.find(\"a[href*='\/author\/']\").each(function(){\n                    if(!author){\n                        author = $(this).text().trim();\n                        flutterBridge.log(\"找到作者链接: \" + author);\n                    }\n                });\n                \n                \/\/ 方法B:查找\"作者:\"后面的内容\n                if(!author){\n                    $item.find(\"*\").each(function(){\n                        if(author) return false;\n                        \n                        var text = $(this).text().trim();\n                        if(text.indexOf('作者') === 0 && text.length < 30){\n                            var match = text.match(\/作者[::\\s]*(.+)$\/);\n                            if(match && match[1]){\n                                author = match[1].trim();\n                                \/\/ 如果提取到的太长,可能不是纯作者名\n                                if(author.length > 10) author = \"\";  \n                            }\n                        }\n                    });\n                }\n                \n                \/\/ ========== 3. 封面图 ==========\n                \/\/ 已验证:<img src=\"https:\/\/img.xtyxsw.org\/{ID}\/{IMGID}.jpg\">\n                var coverUrl = \"\";\n                \n                \/\/ 查找img标签(优先找书籍链接内的img)\n                var $coverImg = $bookLink.find(\"img\").first();\n                if($coverImg.length === 0){\n                    $coverImg = $item.find(\"img\").first();\n                }\n                \n                if($coverImg.length > 0){\n                    coverUrl = $coverImg.attr('src') || $coverImg.attr('data-original') || \"\";\n                    \n                    \/\/ 验证是否是有效的图片URL(应该包含 img.xtyxsw.org 或其他图片域名)\n                    if(coverUrl && !coverUrl.match(\/^https?:\\\/\\\/(img\\.|.*\\.(jpg|png|gif|webp))\/i)){\n                        coverUrl = \"\";  \/\/ 不是有效图片URL\n                    }\n                }\n                \n                \/\/ ========== 4. 简介 ==========\n                \/\/ 已验证:在 <p> 或 <div> 中,长度适中,不包含按钮文字\n                var intro = \"\";\n                \n                \/\/ 查找可能的简介元素(排除掉包含按钮文字的元素)\n                $item.find(\"p, div, span\").each(function(){\n                    if(intro) return false;  \/\/ 已经找到了\n                    \n                    var $elem = $(this);\n                    var text = $elem.text().trim();\n                    \n                    \/\/ 简介特征:\n                    \/\/ - 长度 > 30字(排除短标签)\n                    \/\/ - 长度 < 500字(排除过长内容)\n                    \/\/ - 不包含按钮文字\n                    \/\/ - 不是<a>标签\n                    \/\/ - 不以\"作者\"、\"最新\"、\"开始\"、\"加入\"开头\n                    if(text.length > 30 && text.length < 500 &&\n                       !$elem.is(\"a\") &&\n                       !$elem.is(\"h1, h2, h3, h4\") &&\n                       text.indexOf('开始阅读') === -1 &&\n                       text.indexOf('加入书架') === -1 &&\n                       text.indexOf('TXT下载') === -1 &&\n                       text.indexOf('推荐本书') === -1 &&\n                       text.indexOf('投票') === -1 &&\n                       !text.startsWith('作者') &&\n                       !text.startsWith('最新更新') &&\n                       !text.startsWith('类别') &&\n                       !text.startsWith('状态')){\n                        \n                        intro = text;\n                    }\n                });\n                \n                \/\/ 截断过长简介\n                if(intro.length > 200) intro = intro.substring(0, 200);\n                \n                \/\/ ========== 5. 最新章节标题 ==========\n                \/\/ 已验证:<a href=\"\/read\/{书籍ID}\/{章节ID}.html\">第{N}章 标题<\/a>\n                var latestChapterTitle = \"\";\n                \n                \/\/ 查找所有 \/read\/ 链接(排除第一个,那是书籍详情链接)\n                $item.find(\"a[href*='\/read\/']\").not(\":first\").each(function(){\n                    if(latestChapterTitle) return false;  \/\/ 已找到\n                    \n                    var chText = $(this).text().trim();\n                    var chHref = $(this).attr('href') || \"\";\n                    \n                    \/\/ 最新章节特征:\n                    \/\/ - 以\"第\"开头\n                    \/\/ - 包含\"章\"\n                    \/\/ - 链接格式:\/read\/{id}\/{chapter_id}.html (不是 \/read\/{id}\/ 这种目录格式)\n                    if(chText.match(\/^第\\s*\\d+\\s*[章节篇]\/) && chHref.match(\/\\\/read\\\/\\d+\\\/\\d+\\.html$\/)){\n                        latestChapterTitle = chText;\n                        flutterBridge.log(\"找到最新章节: \" + latestChapterTitle);\n                    }\n                });\n                \n                \/\/ ========== 构建书籍对象 ==========\n                bookUrl = normalizeUrl(bookUrl);\n\n                var book={\n                    \"bookUrl\": bookUrl,\n                    \"name\": bookName,\n                    \"author\": author,\n                    \"kind\": \"\",\n                    \"coverUrl\": normalizeUrl(coverUrl),\n                    \"intro\": intro,\n                    \"tocUrl\": bookUrl,\n                    \"wordCount\": \"\",\n                    \"type\": 0,\n                    \"latestChapterTitle\": latestChapterTitle\n                };\n                \n                flutterBridge.log(\"✅ 提取书籍: \" + bookName + \" | 作者: \" + author + \" | 最新: \" + latestChapterTitle);\n                books.push(book);\n                \n            }catch(e){\n                flutterBridge.log(\"解析书籍出错: \" + e.message);\n            }\n        });\n\n        \/\/ 备选方案:如果没找到特定容器\n        if(books.length === 0) {\n            $tempContainer.find(\"a\").each(function() {\n                try{\n                    var bookUrl = $(this).attr('href');\n                    \n                    if (!bookUrl || processedUrls.has(bookUrl)) { return true; }\n                    if (!bookLinkMatcher(bookUrl)) { return true; }\n                    \n                    processedUrls.add(bookUrl);\n                    \n                    var bookName = $(this).attr('title') || $(this).text().trim();\n\n                    if(bookName && bookName.length > 1){\n                        bookUrl = normalizeUrl(bookUrl);\n\n                        var book={\n                            \"bookUrl\": bookUrl,\n                            \"name\": bookName,\n                            \"author\": \"\",\n                            \"kind\": \"\",\n                            \"coverUrl\": \"\",\n                            \"intro\": \"\",\n                            \"tocUrl\": bookUrl,\n                            \"wordCount\": \"\",\n                            \"type\": 0,\n                            \"latestChapterTitle\": \"\"\n                        };\n                        books.push(book);\n                    }\n                }catch(e){\n                    flutterBridge.log(\"解析书籍信息出错: \" + e.message);\n                }\n            });\n        }\n\n        removeHTMLSafely($tempContainer);\n        flutterBridge.log(\"找到 \" + books.length + \" 本书\");\n        return JSON.stringify(books);\n    }\n\n    \/*\n     * info(bookurl) - 书籍详情\n     * 已验证URL格式: \/read\/{书籍ID}\/\n     *\/\n    async function info(bookurl) {\n        var mheader={\n            ...header,\n            \"Referer\": baseurl\n        };\n\n        var htmlResult = await http.Get(bookurl, mheader, true);\n\n        if(!htmlResult || !htmlResult.data){\n            flutterBridge.showToast(\"获取信息失败\");\n            return JSON.stringify({});\n        }\n\n        var htmlContent = htmlResult.data;\n\n        flutterBridge.text(1, htmlContent);\n\n        var $tempContainer = parseHTMLSafely(removeHTMLTags(htmlContent));\n\n        var name = \"\";\n        var author = \"\";\n        var kind = \"\";\n        var intro = \"\";\n        var latestChapterTitle = \"\";\n        var coverUrl = \"\";\n\n        \/\/ 提取书名(h1标签)\n        name = $tempContainer.find(\"h1\").first().text().trim();\n        \n        \/\/ 从h1中分离作者(格式:\"书名 作者:xxx\" 或类似)\n        if(name && name.indexOf('作者') !== -1){\n            var authorMatch = name.match(\/作者[::\\s]*(.+?)(?:\\s|$)\/);\n            if(authorMatch && authorMatch[1]){\n                author = authorMatch[1].trim();\n                name = name.replace(\/作者[::\\s].+$\/, '').trim();\n            }\n        }\n\n        \/\/ 提取封面图\n        coverUrl = $tempContainer.find(\".cover img, .book-cover img, #fmimg img, img[class*='cover']\").first().attr('src') || \"\";\n        \n        \/\/ 遍历页面元素提取详细信息\n        $tempContainer.find(\"*\").each(function() {\n            var text = $(this).text().trim();\n\n            \/\/ 分类\n            if(!kind && text.length < 15) {\n                var categories = ['玄幻', '武侠', '都市', '历史', '科幻', '游戏', '女生', '其他', '言情', '军事'];\n                for(var i=0; i<categories.length; i++) {\n                    if(text.indexOf(categories[i]) !== -1) {\n                        kind = categories[i];\n                        break;\n                    }\n                }\n            }\n\n            \/\/ 最新章节\n            if(!latestChapterTitle && text.length < 60 && name) {\n                var chapterMatch = text.match(\/(第\\s*\\d+\\s*[章节篇])\/);\n                if(chapterMatch && text.indexOf(name) === -1) { \n                    latestChapterTitle = chapterMatch[1]; \n                }\n            }\n        });\n\n        \/\/ 提取简介\n        $tempContainer.find(\"#intro, .intro, .desc, .summary, .book-intro, #description\").each(function() {\n            if(intro) return false;\n            \n            var text = $(this).text().trim();\n            \n            if(text.length > 50 && text.length < 2000 && \n               text.indexOf(name) === -1 &&\n               text.indexOf('加入书架') === -1 &&\n               text.indexOf('马上阅读') === -1 &&\n               text.indexOf('推荐') === -1 &&\n               text.indexOf('催更') === -1) {\n                intro = text;\n            }\n        });\n\n        \/\/ 如果没找到简介,尝试从段落中获取\n        if(!intro) {\n            $tempContainer.find(\"p, div\").each(function() {\n                if(intro) return false;\n                \n                var text = $(this).text().trim();\n                \n                if(text.length > 50 && text.length < 2000 && \n                   text.indexOf(name) === -1 &&\n                   text.indexOf('加入书架') === -1 &&\n                   text.indexOf('马上阅读') === -1) {\n                    intro = text;\n                }\n            });\n        }\n\n        var book={\n            \"bookUrl\": bookurl,\n            \"name\": name,\n            \"author\": author,\n            \"kind\": kind,\n            \"coverUrl\": normalizeUrl(coverUrl),\n            \"intro\": intro,\n            \"tocUrl\": bookurl,\n            \"wordCount\": \"\",\n            \"type\": 0,\n            \"latestChapterTitle\": latestChapterTitle\n        };\n\n        removeHTMLSafely($tempContainer);\n        return JSON.stringify(book);\n    }\n\n    \/*\n     * chapter(tocUrl) - 章节列表\n     * 已验证: 章节列表一次性全部加载,无\"最新章节\"\/\"全部章节\"分区!\n     * 章节链接格式: \/read\/{书籍ID}\/{章节ID}.html\n     * 无展开按钮、无隐藏章节\n     *\/\n    async function chapter(tocUrl) {\n        var mheader={\n            ...header,\n            \"Referer\": baseurl\n        };\n\n        var htmlResult = await http.Get(tocUrl, mheader, true);\n\n        if(!htmlResult || !htmlResult.data){\n            flutterBridge.showToast(\"获取目录失败\");\n            return \"[]\";\n        }\n\n        var htmlContent = htmlResult.data;\n\n        flutterBridge.text(2, htmlContent);\n\n        var chapters = [];\n        var processedUrls = new Set();\n        var $tempContainer = parseHTMLSafely(removeHTMLTags(htmlContent));\n\n        \/\/ 已验证章节链接格式: \/read\/{书籍ID}\/{章节ID}.html\n        \/\/ 示例: \/read\/264234\/193501234.html\n        var chapterLinkMatcher = function(href) {\n            if (!href) return false;\n            \n            \/\/ 移除域名前缀\n            href = href.replace(\/^https?:\\\/\\\/[^\\\/]+\/, '');\n            \n            \/\/ 匹配: \/read\/数字\/数字.html\n            return \/^\\\/read\\\/\\d+\\\/\\d+\\.html$\/.test(href);\n        };\n\n        \/\/ ====== 天悦小说网章节提取(已验证有\"最新章节\"+\"全部章节\"两个区域)=====\n        \/\/ 真实结构:\n        \/\/ <div>最新章节<\/div>\n        \/\/ <a>第1483章 婚礼<\/a>  ← 要排除!\n        \/\/ ...\n        \/\/ <div>全部章节目录(共xxx章)<\/div>  ← ⭐ 从这里开始\n        \/\/ <a>第1章 xxx<\/a>\n        \/\/ <a>第2章 xxx<\/a>\n        \/\/ ...\n        \n        var $catalogHeader = null;\n        \n        \/\/ 查找\"全部章节目录\"标题\n        $tempContainer.find(\"dt, h2, h3, h4, div, span, strong\").each(function() {\n            var text = $(this).text().trim();\n            \n            if ((text.indexOf('全部章节') !== -1 ||\n                 text.indexOf('正文目录') !== -1 ||\n                 text.indexOf('章节目录') !== -1) &&\n                text.length < 100) {\n                \n                $catalogHeader = $(this);\n                flutterBridge.log(\"✅ 找到'全部章节目录': \" + text.substring(0, 80));\n                return false;\n            }\n        });\n\n        if ($catalogHeader && $catalogHeader.length > 0) {\n            \/\/ 使用标记法:找到标题后才开始提取\n            var startExtracting = false;\n            var $parentContainer = $catalogHeader.parent();\n            \n            \/\/ 遍历父容器的所有子元素\n            $parentContainer.children().each(function() {\n                var $child = $(this);\n                var tagName = this.tagName.toLowerCase();\n                \n                \/\/ 如果遇到目标标题 → 开始提取\n                if (!startExtracting) {\n                    if ($child[0] === $catalogHeader[0]) {\n                        startExtracting = true;\n                        flutterBridge.log(\"✅ 开始提取模式\");\n                    }\n                    return true;\n                }\n                \n                \/\/ 开始提取后,只处理<a>标签\n                if (tagName === 'a') {\n                    try{\n                        var chUrl = $child.attr('href');\n                        var chName = $child.text().trim();\n\n                        if(!chUrl || !chName || chName.length < 2){\n                            return true;\n                        }\n\n                        if(processedUrls.has(chUrl)){\n                            return true;\n                        }\n\n                        if(!chapterLinkMatcher(chUrl)){\n                            return true;\n                        }\n\n                        processedUrls.add(chUrl);\n                        chUrl = normalizeUrl(chUrl);\n\n                        chapters.push({\n                            \"name\": chName,\n                            \"chapterId\": chUrl,\n                            \"index\": chapters.length,\n                            \"isPay\": false,\n                            \"isVip\": false,\n                            \"isVolume\": false,\n                            \"tag\": \"\"\n                        });\n                    }catch(e){\n                        flutterBridge.log(\"解析章节出错: \" + e.message);\n                    }\n                }\n                \n                return true;\n            });\n\n            flutterBridge.log(\"📚 提取了 \" + chapters.length + \" 个章节\");\n        } else {\n            \/\/ 兜底方案:提取所有章节并用URL去重\n            flutterBridge.log(\"未找到'全部章节目录'标题,使用去重+排序模式\");\n\n            var allChapters = [];\n\n            $tempContainer.find(\"a\").each(function(index) {\n                try{\n                    var chUrl = $(this).attr('href');\n                    var chName = $(this).text().trim();\n\n                    if(!chUrl || !chName || chName.length < 2){\n                        return true;\n                    }\n\n                    if(processedUrls.has(chUrl)){\n                        return true;\n                    }\n\n                    if(!chapterLinkMatcher(chUrl)){\n                        return true;\n                    }\n\n                    processedUrls.add(chUrl);\n                    chUrl = normalizeUrl(chUrl);\n\n                    \/\/ 提取章节号用于排序\n                    var chapterNumMatch = chUrl.match(\/\\\/(\\d+)\\.html$\/);\n                    var chapterNum = chapterNumMatch ? parseInt(chapterNumMatch[1]) : 999999;\n\n                    allChapters.push({\n                        name: chName,\n                        chapterId: chUrl,\n                        num: chapterNum\n                    });\n                }catch(e){\n                    flutterBridge.log(\"解析章节出错: \" + e.message);\n                }\n            });\n\n            \/\/ 按章节号正序排列(第1章在最前)\n            allChapters.sort(function(a, b) { return a.num - b.num; });\n\n            allChapters.forEach(function(ch, idx) {\n                chapters.push({\n                    \"name\": ch.name,\n                    \"chapterId\": ch.chapterId,\n                    \"index\": idx,\n                    \"isPay\": false,\n                    \"isVip\": false,\n                    \"isVolume\": false,\n                    \"tag\": \"\"\n                });\n            });\n\n            flutterBridge.log(\"去重+排序模式找到 \" + chapters.length + \" 个章节\");\n        }\n\n        removeHTMLSafely($tempContainer);\n        flutterBridge.log(\"总共返回 \" + chapters.length + \" 个章节\");\n        return JSON.stringify(chapters);\n    }\n\n    \/*\n     * content(url) - 章节内容\n     * 已验证URL格式: \/read\/{书籍ID}\/{章节ID}.html\n     * 已确认存在分页: \/read\/{书籍ID}\/{章节ID}_{页码}.html\n     *\/\n    async function content(url) {\n        var mheader={\n            ...header,\n            \"Referer\": baseurl\n        };\n\n        var htmlResult = await http.Get(url, mheader, true);\n\n        if(!htmlResult || !htmlResult.data){\n            flutterBridge.showToast(\"获取内容失败\");\n            return \"\";\n        }\n\n        var htmlContent = htmlResult.data;\n\n        flutterBridge.text(3, htmlContent);\n\n        var allContent = [];  \/\/ 存储所有页面内容\n        var currentUrl = url; \/\/ 当前页面URL\n        var maxPages = 20;    \/\/ 最大页数限制\n        var pageCount = 0;\n\n        while (currentUrl && pageCount < maxPages) {\n            pageCount++;\n\n            \/\/ 解析当前页面\n            var $tempContainer = parseHTMLSafely(removeHTMLTags(htmlContent));\n\n            \/\/ 查找正文容器\n            var $contentElement = null;\n            \n            \/\/ 方法1: 查找正文容器(如果存在)\n            var candidates = $tempContainer.find(\"#content, .content, #BookText, #BookTextCon, .chapter-content, .text-content\");\n            if (candidates.length > 0) {\n                $contentElement = candidates.first();\n            }\n            \n            \/\/ 方法2: 如果没找到特定容器,收集所有文本节点(保留换行!)\n            if (!$contentElement || $contentElement.length === 0) {\n                var pageTexts = [];\n                \n                \/\/ 尝试从多个来源收集文本 - 保留每个元素的独立段落\n                $tempContainer.find(\"div, p\").each(function() {\n                    var $elem = $(this);\n                    var text = $elem.text().trim();\n                    \n                    \/\/ 过滤掉导航、按钮等无关内容\n                    if(text.length > 10 && \n                       text.indexOf('首页') === -1 && \n                       text.indexOf('目录') === -1 &&\n                       text.indexOf('上一章') === -1 &&\n                       text.indexOf('下一章') === -1 &&\n                       text.indexOf('加入书架') === -1 &&\n                       text.indexOf('推荐') === -1 &&\n                       text.indexOf('催更') === -1 &&\n                       !text.startsWith('http') &&\n                       text.indexOf('本小章还未完') === -1 &&\n                       \/\/ 过滤掉只包含数字或短标签的元素\n                       !$elem.is(\"a\") &&\n                       !$elem.hasClass(\"btn\") &&\n                       !$elem.hasClass(\"button\")) {\n                        \n                        \/\/ ✅ 关键:保留每个段落作为独立单元\n                        pageTexts.push(text);\n                    }\n                });\n                \n                if (pageTexts.length > 0) {\n                    \/\/ 用双换行连接每个段落,保留格式!\n                    var combinedText = pageTexts.join(\"\\n\\n\");\n                    allContent.push(combinedText);\n                    flutterBridge.log(\"获取第 \" + pageCount + \" 页(\" + pageTexts.length + \" 个段落),长度: \" + combinedText.length);\n                }\n            } else {\n                \/\/ 从找到的容器获取内容 - 保留段落结构!\n                var containerParagraphs = [];\n                \n                \/\/ 遍历容器内的所有直接子元素或段落\n                $contentElement.children().each(function() {\n                    var $child = $(this);\n                    var childTag = this.tagName.toLowerCase();\n                    \n                    \/\/ 只处理段落级元素:p, div, br等\n                    if (childTag === 'p' || childTag === 'div' || childTag === 'br') {\n                        var paraText = $child.text().trim();\n                        if(paraText.length > 5){\n                            containerParagraphs.push(paraText);\n                        }\n                    } else if (childTag === 'h2' || childTag === 'h3' || childTag === 'h4') {\n                        \/\/ 章节标题也保留\n                        var titleText = $child.text().trim();\n                        if(titleText){\n                            containerParagraphs.push(\"\\n\" + titleText + \"\\n\");  \/\/ 标题前后加空行\n                        }\n                    }\n                });\n                \n                \/\/ 如果没有子元素,尝试按<br>分割\n                if(containerParagraphs.length === 0){\n                    var rawHtml = $contentElement.html();\n                    \/\/ 用<br>或<\/p>分割成段落\n                    var parts = rawHtml.split(\/<br\\s*\\\/?>|<\\\/p>\/gi);\n                    for(var i=0; i<parts.length; i++){\n                        var partText = $('<div>').html(parts[i]).text().trim();\n                        if(partText.length > 5){\n                            containerParagraphs.push(partText);\n                        }\n                    }\n                }\n                \n                if (containerParagraphs.length > 0) {\n                    var pageContent = containerParagraphs.join(\"\\n\\n\");\n                    allContent.push(pageContent);\n                    flutterBridge.log(\"获取第 \" + pageCount + \" 页(\" + containerParagraphs.length + \" 个段落),长度: \" + pageContent.length);\n                }\n            }\n\n            \/\/ 查找下一页链接(已验证分页格式: _2.html, _3.html等)\n            var nextPageUrl = null;\n            \n            $tempContainer.find(\"a\").each(function() {\n                var text = $(this).text().trim();\n                var href = $(this).attr('href');\n                \n                \/\/ 匹配\"下一页\"按钮\n                if ((text === '下一页' || text === '下页' || text === '下一页(→)') && !nextPageUrl && href) {\n                    nextPageUrl = href;\n                    return false;  \/\/ 找到后停止\n                }\n            });\n\n            removeHTMLSafely($tempContainer);\n\n            if (!nextPageUrl) {\n                flutterBridge.log(\"没有更多页面,共 \" + pageCount + \" 页\");\n                break;\n            }\n\n            \/\/ 构建完整URL并获取下一页\n            if (nextPageUrl.startsWith('\/')) {\n                var pathParts = url.split('\/');\n                basePath = pathParts.slice(0, 3).join('\/');\n                currentUrl = basePath + nextPageUrl;\n            } else if (nextPageUrl.startsWith('http')) {\n                currentUrl = nextPageUrl;\n            } else {\n                var lastSlashIndex = url.lastIndexOf('\/');\n                currentUrl = url.substring(0, lastSlashIndex + 1) + nextPageUrl;\n            }\n\n            if (currentUrl === url && pageCount > 1) {\n                flutterBridge.log(\"检测到重复URL,停止翻页\");\n                break;\n            }\n\n            flutterBridge.log(\"正在获取第 \" + (pageCount + 1) + \" 页: \" + currentUrl);\n            \n            var nextResult = await http.Get(currentUrl, mheader, true);\n            if(!nextResult || !nextResult.data){\n                flutterBridge.log(\"获取第 \" + (pageCount + 1) + \" 页失败\");\n                break;\n            }\n            htmlContent = nextResult.data;\n        }\n\n        \/\/ 合并所有页面内容\n        var contenttxt = allContent.join(\"\\n\\n\");\n\n        \/\/ 清理内容\n        contenttxt = contenttxt.replace(\/\\n{3,}\/g, \"\\n\\n\");\n        contenttxt = contenttxt.trim();\n\n        \/\/ 限制最大长度\n        if(contenttxt.length > 50000) {\n            contenttxt = contenttxt.substring(0, 50000);\n        }\n\n        if(!contenttxt || contenttxt.length < 30){\n            flutterBridge.showToast(\"获取内容失败\");\n            return \"\";\n        }\n\n        return contenttxt;\n    }\n\n    \/*\n     * getfinds() - 发现\/分类\n     *\/\n    async function getfinds() {\n        var result = [];\n        var push = (title, url, type) => result.push({\n            title: title,\n            url: url,\n            type: type || 0\n        });\n\n        \/\/ ====== 基于网站真实数据的分类列表 ======\n        \/\/ 数据来源:网站JS变量 EXPLORE_META(已验证)\n        \/\/ URL格式: \/sort\/{分类ID}_{页码}\/\n\n        \/\/ 分类列表(按ID顺序)\n        push(\"玄幻\", \"\/sort\/1_{{page}}\/\", 0);\n        push(\"奇幻\", \"\/sort\/2_{{page}}\/\", 0);\n        push(\"武侠\", \"\/sort\/3_{{page}}\/\", 0);\n        push(\"都市\", \"\/sort\/4_{{page}}\/\", 0);\n        push(\"历史\", \"\/sort\/5_{{page}}\/\", 0);\n        push(\"军事\", \"\/sort\/6_{{page}}\/\", 0);\n        push(\"悬疑\", \"\/sort\/7_{{page}}\/\", 0);\n        push(\"游戏\", \"\/sort\/8_{{page}}\/\", 0);\n        push(\"科幻\", \"\/sort\/9_{{page}}\/\", 0);\n        push(\"体育\", \"\/sort\/10_{{page}}\/\", 0);\n        push(\"古言\", \"\/sort\/11_{{page}}\/\", 0);\n        push(\"现言\", \"\/sort\/12_{{page}}\/\", 0);\n        push(\"幻言\", \"\/sort\/13_{{page}}\/\", 0);\n        push(\"仙侠\", \"\/sort\/14_{{page}}\/\", 0);\n        push(\"青春\", \"\/sort\/15_{{page}}\/\", 0);\n        push(\"穿越\", \"\/sort\/16_{{page}}\/\", 0);\n        push(\"女生\", \"\/sort\/17_{{page}}\/\", 0);\n        push(\"其他\", \"\/sort\/18_{{page}}\/\", 0);\n\n        \/\/ 排行榜(已验证)\n        push(\"🔥 点击榜\", \"\/allvisit\/\", 0);      \/\/ 按点击量排行\n        push(\"⭐ 推荐榜\", \"\/allvote\/\", 0);       \/\/ 按推荐票数排行\n        push(\"💖 收藏榜\", \"\/goodnum\/\", 0);       \/\/ 按收藏数排行\n        push(\"📚 新书入库\", \"\/postdate\/\", 0);    \/\/ 按发布时间排行\n\n        return JSON.stringify(result);\n    }\n\n    \/*\n     * find(url, page) - 分类浏览\n     *\/\n    async function find(url, page) {\n        var u = url.replaceAll(\"{{page}}\", page);\n        if(!u.startsWith('http')){\n            u = baseurl + u;\n        }\n\n        flutterBridge.log(\"分类URL: \" + u);\n\n        var mheader={\n            ...header,\n            \"Referer\": baseurl\n        };\n\n        var htmlResult = await http.Get(u, mheader, true);\n\n        if(!htmlResult || !htmlResult.data){\n            flutterBridge.showToast(\"获取分类失败\");\n            return \"[]\";\n        }\n\n        var htmlContent = htmlResult.data;\n\n        flutterBridge.text(0, htmlContent);\n\n        var books = [];\n        var processedUrls = new Set();\n        var $tempContainer = parseHTMLSafely(removeHTMLTags(htmlContent));\n\n        \/\/ 已验证书籍链接格式: \/read\/{书籍ID}\/\n        var bookLinkMatcher = function(href) {\n            if (!href) return false;\n            return \/^\\\/read\\\/\\d+\\\/$\/.test(href);\n        };\n\n        \/\/ ====== 基于浏览器100%验证过的真实DOM结构 ======\n        \/\/ 真实结构(已通过浏览器验证):\n        \/\/\n        \/\/ <div id=\"alistbox\">                    ← 每本书的容器(注意:是id不是class)\n        \/\/   <div class=\"pic\">                     ← 封面区域\n        \/\/     <a href=\"\/read\/{书籍ID}\/\" title=\"《书名》全文阅读\">\n        \/\/       <img src=\"https:\/\/img.xtyxsw.org\/{ID}\/{IMG}.jpg\" alt=\"书名\">\n        \/\/     <\/a>\n        \/\/   <\/div>\n        \/\/   \n        \/\/   <div class=\"info\">                    ← 信息区域\n        \/\/     <div class=\"title\">                 ← 标题区\n        \/\/       <h2>《<a href=\"\/read\/{ID}\/\">纯书名<\/a>》<\/h2>\n        \/\/       <span>作者:<a href=\"\/author\/{作者}.html\">{作者}<\/a><\/span>\n        \/\/     <\/div>\n        \/\/     \n        \/\/     <div class=\"sys\">                  ← 最新章节\n        \/\/       <li>最新更新:<a href=\"\/read\/{ID}\/{章ID}.html\">第{N}章 标题<\/a><\/li>\n        \/\/     <\/div>\n        \/\/     \n        \/\/     <div class=\"intro\">                 ← 简介\n        \/\/       简介文本内容...\n        \/\/     <\/div>\n        \/\/     \n        \/\/     <div class=\"yuedu\">                 ← 操作按钮\n        \/\/       <a href=\"\/read\/{ID}\/\">开始阅读<\/a>\n        \/\/       <a onclick=\"mark({ID})\">加入书架<\/a>\n        \/\/     <\/div>\n        \/\/   <\/div>\n        \/\/ <\/div>\n\n        \/\/ 查找所有书籍容器(使用id选择器 #alistbox)\n        $tempContainer.find(\"#alistbox\").each(function() {\n            try{\n                var $item = $(this);\n                \n                \/\/ ========== 1. 书籍链接 ==========\n                var bookUrl = $item.find(\"a[href*='\/read\/']\").first().attr('href');\n                \n                if (!bookUrl || !bookLinkMatcher(bookUrl) || processedUrls.has(bookUrl)) { \n                    return true; \n                }\n                \n                processedUrls.add(bookUrl);\n                \n                \/\/ ========== 2. 书名(从.title h2 a提取 - 不含\"全文阅读\")==========\n                var bookName = $item.find(\".title h2 a\").first().text().trim();\n                \n                \/\/ 清理书名号《》\n                if(bookName){\n                    bookName = bookName.replace(\/[《》\"\"''【】\\(\\)]\/g, '').trim();\n                }\n                \n                if(!bookName || bookName.length < 2) return true;\n                \n                \/\/ ========== 3. 作者(从.title span a提取 - 已验证格式)==========\n                var author = $item.find(\".title span a\").first().text().trim();\n                \n                \/\/ ========== 4. 封面图(从.pic img提取)==========\n                var coverUrl = $item.find(\".pic img\").first().attr('src') || \"\";\n                \n                \/\/ ========== 5. 简介(从.intro提取)==========\n                var intro = $item.find(\".intro\").first().text().trim();\n                if(intro && intro.length > 200) {\n                    intro = intro.substring(0, 200);\n                }\n                \n                \/\/ ========== 6. 最新章节(从.sys li a提取 - 已验证格式)==========\n                var latestChapterTitle = $item.find(\".sys li a\").first().text().trim();\n                \n                flutterBridge.log(\"✅ 提取书籍: \" + bookName + \" | 作者: \" + author + \" | 最新: \" + latestChapterTitle);\n                \n                bookUrl = normalizeUrl(bookUrl);\n\n                var book={\n                    \"bookUrl\": bookUrl,\n                    \"name\": bookName,\n                    \"author\": author,\n                    \"kind\": \"\",\n                    \"coverUrl\": normalizeUrl(coverUrl),\n                    \"intro\": intro,\n                    \"tocUrl\": bookUrl,\n                    \"wordCount\": \"\",\n                    \"type\": 0,\n                    \"latestChapterTitle\": latestChapterTitle\n                };\n                books.push(book);\n                \n            }catch(e){\n                flutterBridge.log(\"解析#alistbox出错: \" + e.message);\n            }\n        });\n\n        \/\/ 备选方案:如果没找到#alistbox容器\n        if(books.length === 0) {\n            $tempContainer.find(\"a\").each(function() {\n                try{\n                    var bookUrl = $(this).attr('href');\n                    \n                    if (!bookUrl || processedUrls.has(bookUrl)) { return true; }\n                    if (!bookLinkMatcher(bookUrl)) { return true; }\n                    \n                    processedUrls.add(bookUrl);\n                    \n                    var bookName = $(this).attr('title') || $(this).text().trim();\n                    \n                    if(bookName && bookName.length > 1){\n                        bookUrl = normalizeUrl(bookUrl);\n\n                        var book={\n                            \"bookUrl\": bookUrl,\n                            \"name\": bookName,\n                            \"author\": \"\",\n                            \"kind\": \"\",\n                            \"coverUrl\": \"\",\n                            \"intro\": \"\",\n                            \"tocUrl\": bookUrl,\n                            \"wordCount\": \"\",\n                            \"type\": 0,\n                            \"latestChapterTitle\": \"\"\n                        };\n                        books.push(book);\n                    }\n                }catch(e){\n                    flutterBridge.log(\"解析书籍信息出错: \" + e.message);\n                }\n            });\n        }\n\n        removeHTMLSafely($tempContainer);\n        flutterBridge.log(\"找到 \" + books.length + \" 本书\");\n        return JSON.stringify(books);\n    }\n\n    async function getloginurl(){\n        return baseurl;\n    }\n\n<\/script>\n<\/html>",
    "login": false,
    "lastUpdateTime": "1778702914906"
}
广告