八叉书库
https://bcshuku.com
autobcb_admin (12020)01/07 00:58
大灰狼制作后修复了目录
{
"bookSourceUrl": "https:\/\/bcshuku.com",
"bookSourceName": "八叉书库",
"enabledExplore": true,
"enabled": true,
"bookSourceGroup": "18",
"author": "大灰狼修复版",
"help": false,
"html": "\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"UTF-8\">\n <title>八叉书库<\/title>\n<\/head>\n\n<body>\n\n<\/body>\n<script src=\"https:\/\/vc.jd.com\/web\/js\/jquery-3.1.1.min.js\"><\/script>\n<!--如果要引入外部 js 必须在书源代码的上面-->\n<script>\n var isCookieJar = true;\/\/ 不需要CookieJar请修改此处\n class FlutterJSBridge {\n constructor() {\n this.init(); \/\/前台webview 里必须删除这行\n }\n\n init() {\n if (window.flutter_inappwebview) {\n this.isReady = true;\n this.CookieJar();\n } else {\n window.addEventListener('flutterInAppWebViewPlatformReady', () => {\n this.isReady = true;\n console.log('JSBridge初始化完成');\n this.CookieJar();\n });\n }\n }\n\n \/\/通知原生页面初始化完成,仅在书源和tts生效,webview请勿使用,只有通知加载成功后才允许运行,否则会一直等待加载成功\n async CookieJar() {\n try {\n await window.flutter_inappwebview.callHandler('CookieJar', isCookieJar);\n } catch (error) {\n console.error('汇报完成准备失败:', error);\n }\n }\n\n \/\/获取应用编译版本\n async getbuildNumber() {\n try {\n return await window.flutter_inappwebview.callHandler('buildNumber');\n } catch (error) {\n return 0;\n }\n }\n\n \/\/获取应用版本\n async getversion() {\n try {\n return await window.flutter_inappwebview.callHandler('version');\n } catch (error) {\n return \"0.0.0\";\n }\n }\n\n \/\/获取设备唯一id\n async getDeviceid() {\n try {\n return await window.flutter_inappwebview.callHandler('id');\n } catch (error) {\n return \"\";\n }\n }\n\n \/\/获取设备平台 此处返回 windows、macos、ios、ohos、android\n async getDevice() {\n try {\n return await window.flutter_inappwebview.callHandler('device');\n } catch (error) {\n return \"\";\n }\n }\n\n \/\/输出日志,前台webview请勿使用\n \/\/str 为 String\n async log(str) {\n try {\n return await window.flutter_inappwebview.callHandler('log', str);\n } catch (error) {\n return false;\n }\n }\n\n \/\/书源调试时可输出 html 代码到前台\n \/\/type 0 搜索源码 , 1详情源码 ,2目录源码 ,3正文源码\n \/\/str 为 String\n \/\/type 为int\n async text(type, str) {\n try {\n return await window.flutter_inappwebview.callHandler('text', type, str);\n } catch (error) {\n return false;\n }\n }\n\n \/\/toast弹窗\n \/\/str 为 String\n async showToast(str) {\n try {\n return await window.flutter_inappwebview.callHandler('showToast', str);\n } catch (error) {\n return false;\n }\n }\n\n \/\/获取默认ua\n async getWebViewUA() {\n try {\n return await window.flutter_inappwebview.callHandler('getWebViewUA');\n } catch (error) {\n return \"\";\n }\n }\n\n \/\/通过url打开外部应用\n \/\/url 为 String\n async openurl(url) {\n try {\n return await window.flutter_inappwebview.callHandler('openurl', url, \"\");\n } catch (error) {\n return false;\n }\n }\n\n \/\/通过url打开外部应用并附带mimeType\n \/\/url 为 String\n \/\/mimeType 为 String\n async openurlwithMimeType(url, mimeType) {\n try {\n return await window.flutter_inappwebview.callHandler('openurl', url, mimeType);\n } catch (error) {\n return false;\n }\n }\n\n \/**\n * 使用webView访问网络\n * @param html 直接用webView载入的html, 如果html为空直接访问url\n * @param url html内如果有相对路径的资源不传入url访问不了\n * @param js 用来取返回值的js语句, 没有就返回整个源代码\n * @param body 当参数不为空的时候,会以post请求,此时请务必在 header 中带上content-type\n * @param header 请求的header头,此参数必须是json字符串\n * @return 返回js获取的内容\n *\/\n async webview(url, js, html, body, header) {\n try {\n return await window.flutter_inappwebview.callHandler('webview', url, js, html, body, header, \"\", \"\");\n } catch (error) {\n return \"\";\n }\n }\n\n \/**\n * overrideUrlRegex 为正则表达式\n * 使用方法和上面的一样\n * 但返回的内容为正则到的内容,如果无法正则到则返回 js 获取的内容,如果 js 为空则返回页面 html\n *\/\n async webViewGetOverrideUrl(url, js, html, body, header, overrideUrlRegex) {\n try {\n return await window.flutter_inappwebview.callHandler('webview', url, js, html, body, header, overrideUrlRegex, \"\");\n } catch (error) {\n return \"\";\n }\n }\n\n \/**\n * 使用webView获取资源url\n * urlregex 为正则表达式\n * 使用方法和上面的一样\n * 但返回的内容为正则到的内容,如果无法正则到则返回 js 获取的内容,如果 js 为空则返回页面 html\n *\/\n async webViewGetSource(url, js, html, body, header, urlregex) {\n try {\n return await window.flutter_inappwebview.callHandler('webview', url, js, html, body, header, \"\", urlregex);\n } catch (error) {\n return \"\";\n }\n }\n\n\n \/**\n * 启动前台 webview 访问链接并获取结束时的 html,可用于手工过盾\n * @param url 网址\n * @param title 标题\n * @param header 请求的header头,此参数必须是json字符串\n * @return 返回网页的内容\n *\/\n async startBrowser(url, title, header) {\n try {\n return await window.flutter_inappwebview.callHandler('startBrowser', url, title, header);\n } catch (error) {\n return \"\";\n }\n }\n\n \/\/专门为段评设置的半屏显示,不返回任何东西\n async startBrowserDp(url, title) {\n try {\n return await window.flutter_inappwebview.callHandler('startBrowserDp', url, title);\n } catch (error) {\n return \"\";\n }\n }\n\n \/\/仅前台webview可以使用,返回按钮,返回上一个页面\n async back() {\n try {\n return await window.flutter_inappwebview.callHandler('back');\n } catch (error) {\n return false;\n }\n }\n\n \/\/将 utf8字符串转到 gbk 并 url 编码\n async utf8ToGbkUrlEncoded(str) {\n try {\n return await window.flutter_inappwebview.callHandler('utf8ToGbkUrlEncoded', str);\n } catch (error) {\n return \"\";\n }\n }\n\n \/*\n * @param str为图片链接\n * @param header 请求的header头,此参数必须是json字符串\n * 此函数是让用户输入图片中的验证码,当链接为空则直接让用户输入验证码\n *\/\n async getVerificationCode(str, header) {\n try {\n return await window.flutter_inappwebview.callHandler('getVerificationCode', str, header);\n } catch (error) {\n return \"\";\n }\n }\n\n \/\/提交内容书本信息 json 后的字符串\n async addbook(book) {\n try {\n return await window.flutter_inappwebview.callHandler('addbook', book);\n } catch (error) {\n return \"\";\n }\n }\n\n }\n\n \/\/webview请勿使用\n \/\/以下提交的url,headers,body 都必须为字符串,headers必须为json字符串\n \/\/当followRedirects 为 false 时不处理重定向,当为 true 时会自动处理重定向 ,如不明白用途直接用 true 最佳\n \/\/ 以下所有参数除当followRedirects外均为 String\n class Http {\n constructor() { }\n\n \/*\n * 通用返回字段\n * method post get 或者 head\n * body 请求返回后的字节的 base64\n * headers map<String,List<String>> 可通过headers[\"\"]来或者\n * statusCode 状态码\n * statusMessage\n * data 返回后的字节 格式化后的内容\n *\/\n async Get(url, headers, followRedirects) {\n try {\n return await window.flutter_inappwebview.callHandler('http', \"get\", url, \"\", JSON.stringify(headers), followRedirects, \"\");\n } catch (error) {\n return null;\n }\n }\n\n async Head(url, headers, followRedirects) {\n try {\n return await window.flutter_inappwebview.callHandler('http', \"head\", url, \"\", JSON.stringify(headers), followRedirects, \"\");\n } catch (error) {\n return null;\n }\n }\n\n\n async Post(url, headers, body, contenttype, followRedirects) {\n try {\n return await window.flutter_inappwebview.callHandler('http', \"post\", url, body, JSON.stringify(headers), followRedirects, contenttype);\n } catch (error) {\n return null;\n }\n }\n }\n\n class Cache {\n constructor() { }\n async get(key) {\n try {\n return await window.flutter_inappwebview.callHandler('cache.get', key);\n } catch (error) {\n return null;\n }\n }\n\n async set(key, value) {\n try {\n return await window.flutter_inappwebview.callHandler('cache.set', key, value);\n } catch (error) {\n return null;\n }\n }\n\n async remove(key) {\n try {\n return await window.flutter_inappwebview.callHandler('cache.remove', key);\n } catch (error) {\n return null;\n }\n }\n\n \/\/如果登录为弹窗格式的,里面输入框输入的内容可以通过这个函数获取,默认返回的json格式或者为空,需要自行转换\n async getLoginInfo() {\n return await this.get(\"LoginInfo\")\n }\n\n \/\/将修改后的弹窗输入内容报错 ,必须 JSON.stringify,不然会出错\n async putLoginInfo(info) {\n return await this.set(\"LoginInfo\", info)\n }\n\n \/\/获取书本变量\n async getbookVariable(bookurl) {\n return await this.get(bookurl)\n }\n\n \/\/写入书本变量\n async setbookVariable(bookurl, value) {\n return await this.set(bookurl, value)\n }\n }\n\n class Cookie {\n constructor() { }\n\n \/\/通过url获取当前url的所有cookie\n async get(url) {\n try {\n return await window.flutter_inappwebview.callHandler('cookie.get', url);\n } catch (error) {\n return null;\n }\n }\n\n \/\/通过url删除当前url的所有cookie\n async remove(url) {\n try {\n return await window.flutter_inappwebview.callHandler('cookie.remove', url);\n } catch (error) {\n return null;\n }\n }\n\n\n \/\/通过url保存当前url的所有cookie\n async set(url, value) {\n try {\n return await window.flutter_inappwebview.callHandler('cookie.set', url, value);\n } catch (error) {\n return null;\n }\n }\n\n \/\/通过 url 获取单个 cookie 的值\n async getCookie(url, value) {\n try {\n return await window.flutter_inappwebview.callHandler('cookie.getCookie', url, value);\n } catch (error) {\n return null;\n }\n }\n }\n\n \/\/安全的创建一个 div 解析 html\n function parseHTMLSafely(htmlStr) {\n try {\n \/\/ 在函数作用域内创建独立的临时容器\n \/\/ 每个调用创建新的jQuery对象,互不影响\n var tempDiv = document.createElement('div');\n tempDiv.innerHTML = htmlStr;\n return $(tempDiv);\n } catch (e) {\n flutterBridge.log(\"HTML解析错误:\" + e.message);\n return $('<div>');\n }\n }\n\n \/\/parseHTMLSafely 创建的用完后必须删除\n function removeHTMLSafely(tempContainer) {\n try {\n tempContainer.innerHTML = '';\n if (tempContainer.parentNode) {\n tempContainer.parentNode.removeChild(tempContainer);\n }\n } catch (e) {\n flutterBridge.log(\"HTML移除失败:\" + e.message);\n }\n }\n\n \/\/移除 css js,创建parseHTMLSafely前如果用不上 cssjs 建议移除\n function removeHTMLTags(htmlString) {\n \/\/ 移除script标签\n let result = htmlString.replace(\/<script\\b[^<]*(?:(?!<\\\/script>)<[^<]*)*<\\\/script>\/gi, '');\n \/\/ 移除style标签\n result = result.replace(\/<style\\b[^<]*(?:(?!<\\\/style>)<[^<]*)*<\\\/style>\/gi, '');\n return result;\n }\n\n<\/script>\n<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.8xsk.info\"\n var header = { \"User-Agent\": \"Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/131.0.0.0 Safari\/537.36\" };\n\n \/\/ 搜索函数\n async function search(key, page) {\n if (page > 1) {\n return \"[]\";\n }\n\n try {\n var url = baseurl + \"\/e\/search\/index.php?keyboard=\" + encodeURIComponent(key) + \"&show=title,writer,byr&searchget=1\";\n var myheader = {\n ...header,\n \"Referer\": baseurl\n };\n\n var get = await http.Get(url, JSON.stringify(myheader), true);\n flutterBridge.text(0, get.data);\n\n return parseBookList(get.data);\n } catch (e) {\n flutterBridge.log(\"搜索出错:\" + e.message);\n return \"[]\";\n }\n }\n\n \/\/ 书籍详情函数\n async function info(bookurl) {\n try {\n var get = await http.Get(bookurl, JSON.stringify(header), true);\n flutterBridge.text(1, get.data);\n\n var $tempContainer = parseHTMLSafely(removeHTMLTags(get.data));\n\n \/\/ 提取书名\n var name = $tempContainer.find(\"h1\").first().text().trim();\n\n \/\/ 提取作者\n var author = \"\";\n \/\/ 优先使用 itemprop='author' 属性\n var $authorLink = $tempContainer.find(\"a[itemprop='author'], .info-chitiet a[href*='show=writer']\").first();\n if ($authorLink.length > 0) {\n \/\/ 直接获取文本\n author = $authorLink.text().trim();\n\n \/\/ 如果文本为空,尝试从URL解析\n if (!author) {\n var href = $authorLink.attr('href');\n if (href) {\n var match = href.match(\/keyboard=([^&]+)\/); \/\/ Corrected regex\n if (match) {\n author = decodeURIComponent(match[1]);\n }\n }\n }\n }\n\n \/\/ 提取封面\n var coverUrl = \"\";\n \/\/ 优先使用.col-md-3 img选择器(详情页结构)\n var imgElem = $tempContainer.find(\".col-md-3 img, .img-responsive, img.img-book\").first();\n if (imgElem.length > 0) {\n var src = imgElem.attr('src');\n if (src) {\n if (src.startsWith('http')) {\n coverUrl = src;\n } else {\n coverUrl = baseurl + src;\n }\n }\n }\n\n \/\/ 提取分类\n var kind = \"\";\n \/\/ 优先使用 itemprop='genre' 属性\n var $categoryLink = $tempContainer.find(\"a[itemprop='genre'], .info-chitiet a[href*='\/booklist']\").first();\n if ($categoryLink.length > 0) {\n var categoryText = $categoryLink.text().trim();\n \/\/ 过滤\"未知\"\n if (categoryText && categoryText !== \"未知\") {\n kind = \"分类:\" + categoryText; \/\/ 添加\"分类:\"前缀\n }\n }\n\n \/\/ 提取简介\n var intro = \"\";\n \/\/ 优先使用 .desc-text 选择器\n var $descText = $tempContainer.find(\".desc-text\").first();\n if ($descText.length > 0) {\n intro = $descText.text().trim();\n \/\/ 移除开头的\"小说介绍\"字样\n intro = intro.replace(\/^小说介绍\\s*\/, \"\").trim();\n }\n\n \/\/ 备用方法1: 查找包含\"小说介绍\"的元素\n if (!intro) {\n var $introContainer = $tempContainer.find(\"*\").filter(function () {\n return $(this).text().trim() === \"小说介绍\" || $(this).text().includes(\"小说介绍\");\n }).first();\n\n if ($introContainer.length > 0) {\n var parentText = $introContainer.parent().text();\n intro = parentText.replace(\/小说介绍\\s*\/g, \"\").trim();\n }\n }\n\n \/\/ 备用方法2: 查找较长的 p 标签\n if (!intro) {\n $tempContainer.find(\"p\").each(function () {\n var text = $(this).text().trim();\n if (text.length > 50 && !text.includes(\"最新章节\") && !text.includes(\"作者\")) {\n intro = text;\n return false;\n }\n });\n }\n\n \/\/ 清理简介:移除\"未知\"等无意义的占位符\n if (intro) {\n intro = intro.replace(\/^未知\\s*$\/g, \"\").trim(); \/\/ 如果整个简介就是\"未知\",清空\n intro = intro.replace(\/^\\s*未知\\s*$\/gm, \"\").trim(); \/\/ 移除只包含\"未知\"的行\n }\n\n \/\/ 提取观看人数 - 根据glyphicon-eye-open图标定位\n var viewCount = \"\";\n\n \/\/ 方法1: 通过glyphicon-eye-open图标查找\n var $eyeIcon = $tempContainer.find(\".glyphicon-eye-open\").first();\n if ($eyeIcon.length > 0) {\n \/\/ 图标在h3中,观看人数在h3后面的span中\n var $h3 = $eyeIcon.closest('h3');\n if ($h3.length > 0) {\n var $viewElem = $h3.next('span');\n if ($viewElem.length > 0) {\n var viewText = $viewElem.text().trim();\n if (viewText && \/^\\d+$\/.test(viewText)) {\n viewCount = \"观看:\" + viewText;\n }\n }\n }\n }\n\n \/\/ 方法2: 如果方法1失败,尝试查找纯数字p标签(兼容旧版HTML)\n if (!viewCount) {\n var $infoParagraphs = $tempContainer.find(\".info-chitiet p\");\n $infoParagraphs.each(function () {\n var $p = $(this);\n \/\/ 跳过有图标的p标签\n if ($p.find(\".glyphicon\").length > 0) {\n return true; \/\/ continue\n }\n\n \/\/ 获取纯文本\n var text = $p.text().trim();\n \/\/ 检查是否是纯数字(观看人数)\n if (\/^\\d+$\/.test(text)) {\n viewCount = \"观看:\" + text;\n return false; \/\/ break\n }\n });\n }\n\n \/\/ 提取更新日期 - 从连载状态后面提取\n var updateDate = \"\";\n var $statusElem = $tempContainer.find(\".info-chitiet span.text-primary\").first();\n if ($statusElem.length > 0) {\n var statusText = $statusElem.text().trim();\n \/\/ 提取日期部分,格式如 \"连载中 2025-12-23\" 或 \"完结 2025-12-23\"\n var dateMatch = statusText.match(\/(\\d{4}[-\\\/]\\d{1,2}[-\\\/]\\d{1,2})\/);\n if (dateMatch) {\n updateDate = dateMatch[1];\n }\n }\n\n \/\/ 组合观看人数和更新日期\n var wordCountInfo = viewCount;\n if (updateDate) {\n if (wordCountInfo) {\n wordCountInfo += \" \" + updateDate;\n } else {\n wordCountInfo = updateDate;\n }\n }\n\n \/\/ 提取最新章节\n var latestChapterTitle = \"\";\n var latestLink = $tempContainer.find(\"a[href*='\/chapter']\").last();\n if (latestLink.length > 0) {\n latestChapterTitle = latestLink.text().trim();\n }\n\n var book = {\n \"bookUrl\": bookurl,\n \"name\": name,\n \"author\": author,\n \"kind\": kind,\n \"coverUrl\": coverUrl,\n \"intro\": intro,\n \"tocUrl\": bookurl,\n \"wordCount\": wordCountInfo, \/\/ 使用wordCount字段存储观看人数和更新日期\n \"type\": 0,\n \"latestChapterTitle\": latestChapterTitle,\n };\n\n removeHTMLSafely($tempContainer);\n return JSON.stringify(book);\n } catch (e) {\n flutterBridge.log(\"获取详情出错:\" + e.message);\n return \"{}\";\n }\n }\n\n \/\/ 章节列表函数\n async function chapter(tocUrl) {\n var i=0\n var chapters = [];\n var last=\"\"\n while (true){\n if(i === 0){\n var c= await chapter2(tocUrl)\n last=c.last;\n if(c.chapters.length > 0){\n chapters.push(...c.chapters);\n }else{\n break;\n }\n }else{\n var c= await chapter2(tocUrl+\"?p=\"+(i+1))\n if(c.chapters.length > 0){\n chapters.push(...c.chapters);\n }else{\n break;\n }\n }\n if(last == \"\"){\n break;\n }\n if((tocUrl+\"?p=\"+(i+1)).includes(last)){\n break;\n }\n i++;\n\n }\n return JSON.stringify(chapters);\n }\n\n async function chapter2(tocUrl) {\n var last=\"\"\n var chapters = [];\n try {\n var get = await http.Get(tocUrl, JSON.stringify(header), true);\n flutterBridge.text(2, get.data);\n for(var i=2 ;i< 100;i++){\n if(get.data.includes(\"?p=\"+i)){\n last=\"?p=\"+i;\n }\n }\n\n var $tempContainer = parseHTMLSafely(removeHTMLTags(get.data));\n\n var index = 0;\n\n \/\/ 提取书籍ID\n var novelId = \"\";\n var match = tocUrl.match(\/\\\/novel(\\d+)\\\/\/);\n if (match) {\n novelId = match[1];\n }\n\n \/\/ 查找章节列表\n \/\/ 网站有两个章节区域:\"最新章节\"(倒序)和\"完整章节列表\"(正序)\n \/\/ 我们只需要完整的章节列表\n\n \/\/ 方法1: 使用主内容区域的最后一个章节列表\n var $mainContent = $tempContainer.find(\".col-md-8, .col-xs-12\").first();\n var $chapterLists = $mainContent.find(\"ul.list-chapter\");\n\n \/\/ 如果有多个列表,取最后一个(完整列表)\n var $fullChapterList = $chapterLists.length > 1 ? $chapterLists.last() : $chapterLists.first();\n\n if ($fullChapterList.length > 0) {\n $fullChapterList.find(\"li a\").each(function () {\n var $link = $(this);\n var chapterName = $link.text().trim();\n var href = $link.attr('href');\n\n if (chapterName && href && href.includes('chapter')) {\n var chapterId = \"\";\n if (href.startsWith('http')) {\n chapterId = href;\n } else if (href.startsWith('\/')) {\n chapterId = baseurl + href;\n } else {\n chapterId = baseurl + \"\/novel\" + novelId + \"\/\" + href;\n }\n\n var chapter = {\n \"name\": chapterName,\n \"chapterId\": chapterId,\n \"index\": index,\n \"isPay\": false,\n \"isVip\": false,\n \"isVolume\": false,\n \"tag\": \"\"\n };\n chapters.push(chapter);\n index++;\n }\n });\n } else {\n \/\/ 备用方法: 如果没有找到 ul.list-chapter,使用通用选择器\n flutterBridge.log(\"未找到标准章节列表,使用备用方法\");\n $tempContainer.find(\"a[href*='chapter']\").each(function () {\n var $link = $(this);\n var chapterName = $link.text().trim();\n var href = $link.attr('href');\n\n if (chapterName && href) {\n var chapterId = \"\";\n if (href.startsWith('http')) {\n chapterId = href;\n } else if (href.startsWith('\/')) {\n chapterId = baseurl + href;\n } else {\n chapterId = baseurl + \"\/novel\" + novelId + \"\/\" + href;\n }\n\n var chapter = {\n \"name\": chapterName,\n \"chapterId\": chapterId,\n \"index\": index,\n \"isPay\": false,\n \"isVip\": false,\n \"isVolume\": false,\n \"tag\": \"\"\n };\n chapters.push(chapter);\n index++;\n }\n });\n }\n\n removeHTMLSafely($tempContainer);\n return {chapters,last};\n } catch (e) {\n flutterBridge.log(\"获取章节列表出错:\" + e.message);\n return {chapters,last};\n }\n }\n\n\n \/\/ 章节内容函数\n async function content(url) {\n try {\n \/\/ 使用webview来处理JavaScript动态加载的内容\n \/\/ 等待内容加载完成后再提取\n var js = `\n (function() {\n \/\/ 等待内容加载完成\n var checkInterval = setInterval(function() {\n var contentDiv = document.querySelector('#chapter-content, .chapter-content');\n if(contentDiv && contentDiv.innerText.length > 50 && !contentDiv.innerText.includes('正在加载中')) {\n clearInterval(checkInterval);\n }\n }, 100);\n\n \/\/ 最多等待5秒\n setTimeout(function() {\n clearInterval(checkInterval);\n }, 5000);\n\n \/\/ 返回内容\n setTimeout(function() {\n var contentDiv = document.querySelector('#chapter-content, .chapter-content');\n if(contentDiv) {\n return contentDiv.innerHTML;\n }\n return '';\n }, 2000);\n })();\n `;\n\n \/\/ 使用webview获取动态内容,使用innerText直接获取文本\n \/\/ 网站使用JavaScript动态加载内容,需要等待加载完成\n var jsCode = `\n (function() {\n var container = document.querySelector('#chapter-content, .chapter-content');\n if (!container) return '';\n var text = container.innerText || container.textContent || '';\n \/\/ 过滤占位文本\n if (text.includes('精彩内容正在加载中') || text.includes('正在加载中') || text.length < 50) {\n return '';\n }\n return text;\n })()\n `;\n\n var result = await flutterBridge.webview(url, jsCode, \"\", \"\", JSON.stringify(header));\n\n flutterBridge.text(3, result);\n\n \/\/ 检查是否成功获取内容\n if (!result || result.length < 50 || result.includes('精彩内容正在加载中')) {\n return \"\";\n }\n\n \/\/ 清理内容\n result = result.trim();\n \/\/ 规范化换行符\n result = result.replace(\/\\r\\n\/g, \"\\n\").replace(\/\\r\/g, \"\\n\");\n\n return result;\n } catch (e) {\n return \"\";\n }\n }\n\n \/\/ 发现页分类函数\n async function getfinds() {\n var sort = [\n {\n title: \"长篇\",\n url: baseurl + \"\/booklist1\/{{page}}\/\",\n type: 0,\n },\n {\n title: \"综合\",\n url: baseurl + \"\/booklist2\/{{page}}\/\",\n type: 0,\n },\n {\n title: \"武侠\",\n url: baseurl + \"\/booklist3\/{{page}}\/\",\n type: 0,\n },\n {\n title: \"历史\",\n url: baseurl + \"\/booklist4\/{{page}}\/\",\n type: 0,\n },\n {\n title: \"都市\",\n url: baseurl + \"\/booklist5\/{{page}}\/\",\n type: 0,\n },\n {\n title: \"玄幻\",\n url: baseurl + \"\/booklist6\/{{page}}\/\",\n type: 0,\n },\n {\n title: \"女生\",\n url: baseurl + \"\/booklist7\/{{page}}\/\",\n type: 0,\n },\n {\n title: \"其他\",\n url: baseurl + \"\/booklist8\/{{page}}\/\",\n type: 0,\n },\n {\n title: \"现代\",\n url: baseurl + \"\/booklist9\/{{page}}\/\",\n type: 0,\n },\n {\n title: \"完本小说\",\n url: baseurl + \"\/completed\/{{page}}\/\",\n type: 0,\n },\n {\n title: \"热门排行\",\n url: baseurl + \"\/popular\/{{page}}\/\",\n type: 0,\n },\n ];\n return JSON.stringify(sort);\n }\n\n \/\/ 发现页书籍列表函数\n async function find(url, page) {\n try {\n var targetUrl;\n\n \/\/ 判断URL类型\n var isCompleted = url.includes(\"\/completed\");\n var isPopular = url.includes(\"\/popular\");\n var isBooklist = url.includes(\"\/booklist\");\n\n \/\/ 处理{{page}}占位符并生成正确的分页URL\n if (url.includes(\"{{page}}\")) {\n \/\/ URL包含占位符,需要根据类型替换为正确的格式\n if (isCompleted || isPopular) {\n \/\/ 完本小说和热门排行的分页格式\n \/\/ 第1页: \/completed\/\n \/\/ 第2页: \/completed\/index_2.html\n if (page === 1) {\n \/\/ 第1页:直接移除{{page}}部分\n targetUrl = url.replace(\"{{page}}\/\", \"\").replace(\"\/{{page}}\", \"\");\n } else {\n \/\/ 第N页:将{{page}}\/替换为 index_{page}.html\n targetUrl = url.replace(\"{{page}}\/\", \"index_\" + page + \".html\");\n }\n } else if (isBooklist) {\n \/\/ 普通分类的分页格式\n \/\/ 第1页: \/booklist1\/\n \/\/ 第2页: \/booklist1\/1.html\n if (page === 1) {\n \/\/ 第1页:直接移除{{page}}部分\n targetUrl = url.replace(\"{{page}}\/\", \"\").replace(\"\/{{page}}\", \"\");\n } else {\n \/\/ 第N页:替换为 {page-1}.html\n targetUrl = url.replace(\"{{page}}\/\", (page - 1) + \".html\");\n }\n } else {\n \/\/ 其他情况:直接替换为页码\n targetUrl = url.replace(\"{{page}}\", page.toString());\n }\n } else {\n \/\/ URL不包含占位符(兼容旧格式)\n if (isCompleted || isPopular) {\n if (page === 1) {\n targetUrl = url.replace(\/\\\/index(_\\d+)?\\.html$\/, '') + \"\/\";\n } else {\n targetUrl = url.replace(\/\\\/index(_\\d+)?\\.html$\/, '').replace(\/\\\/$\/, '') + \"\/index_\" + page + \".html\";\n }\n } else if (isBooklist) {\n if (page === 1) {\n targetUrl = url.replace(\/\\\/\\d+\\.html$\/, '') + \"\/\";\n } else {\n targetUrl = url.replace(\/\\\/\\d+\\.html$\/, '').replace(\/\\\/$\/, '') + \"\/\" + (page - 1) + \".html\";\n }\n } else {\n targetUrl = url;\n }\n }\n\n \/\/ 发送请求\n var get = await http.Get(targetUrl, JSON.stringify(header), true);\n\n if (!get || !get.data) {\n flutterBridge.log(\"❌ HTTP请求失败或返回空数据\");\n return \"[]\";\n }\n\n flutterBridge.text(0, get.data);\n\n \/\/ 解析书籍列表\n var result = parseBookList(get.data);\n\n return result;\n } catch (e) {\n return \"[]\";\n }\n }\n\n \/\/ 解析书籍列表的通用函数\n function parseBookList(html) {\n try {\n var $tempContainer = parseHTMLSafely(removeHTMLTags(html));\n var books = [];\n\n \/\/ 查找所有书籍卡片 - 使用正确的选择器 .home-truyendecu\n var $bookCards = $tempContainer.find(\".home-truyendecu\");\n\n if ($bookCards.length === 0) {\n removeHTMLSafely($tempContainer);\n return \"[]\";\n }\n\n $bookCards.each(function (index) {\n try {\n var $item = $(this);\n\n \/\/ 提取书名和链接 - 从 .caption h3 或 h3[itemprop=\"name\"]\n var $titleLink = $item.find(\".caption a[itemprop='url']\").first();\n if ($titleLink.length === 0) {\n $titleLink = $item.find(\".caption h3 a\").first();\n }\n if ($titleLink.length === 0) {\n return; \/\/ continue\n }\n\n var name = $titleLink.find(\"h3[itemprop='name']\").text().trim();\n if (!name) {\n name = $titleLink.text().trim();\n }\n if (!name) {\n name = $item.find(\".caption h3\").text().trim();\n }\n\n if (!name) {\n return; \/\/ continue\n }\n\n var bookUrl = $titleLink.attr('href');\n if (!bookUrl) {\n return; \/\/ continue\n }\n\n \/\/ 规范化bookUrl\n if (!bookUrl.startsWith('http')) {\n if (bookUrl.startsWith('\/')) {\n bookUrl = baseurl + bookUrl;\n } else {\n bookUrl = baseurl + \"\/\" + bookUrl;\n }\n }\n\n \/\/ 提取封面 - 从 .each_truyen img\n var coverUrl = \"\";\n var $img = $item.find(\".each_truyen img\").first();\n if ($img.length > 0) {\n var src = $img.attr('src') || $img.attr('data-src');\n if (src) {\n if (src.startsWith('http')) {\n coverUrl = src;\n } else if (src.startsWith('\/')) {\n coverUrl = baseurl + src;\n } else {\n coverUrl = src; \/\/ 可能已经是完整URL\n }\n }\n }\n\n \/\/ 提取作者 - 列表页的作者在 .chi-tiet.chuyen-muc a 中(无href属性)\n var author = \"\";\n var $authorElem = $item.find(\".chi-tiet.chuyen-muc a\").first();\n if ($authorElem.length > 0) {\n var authorText = $authorElem.text().trim();\n \/\/ 过滤日期格式和纯数字(浏览量)\n if (authorText &&\n !\/^\\d{4}[-\\\/]\\d{1,2}[-\\\/]\\d{1,2}\/.test(authorText) && \/\/ 不是日期\n !\/^\\d+$\/.test(authorText) && \/\/ 不是纯数字\n authorText !== \"未知\") { \/\/ 不是\"未知\"\n author = \"作者:\" + authorText; \/\/ 添加\"作者:\"前缀\n }\n }\n\n \/\/ 如果没找到,尝试其他位置\n if (!author) {\n var $chiTiet = $item.find(\".chi-tiet\").first();\n if ($chiTiet.length > 0) {\n var text = $chiTiet.text().trim();\n if (text &&\n !\/^\\d{4}[-\\\/]\/.test(text) &&\n !\/^\\d+$\/.test(text) &&\n text !== \"未知\") {\n author = \"作者:\" + text; \/\/ 添加\"作者:\"前缀\n }\n }\n }\n\n \/\/ 提取分类 - 列表页不显示\n var kind = \"\";\n\n \/\/ 提取简介 - 列表页不显示\n var intro = \"\";\n\n \/\/ 提取最新章节\n var latestChapterTitle = \"\";\n var $latestChapter = $item.find(\".chi-tiet.tt-status a, .tt-status a\").first();\n if ($latestChapter.length > 0) {\n latestChapterTitle = $latestChapter.text().trim();\n }\n\n var book = {\n \"bookUrl\": bookUrl,\n \"name\": name,\n \"author\": author,\n \"kind\": kind,\n \"coverUrl\": coverUrl,\n \"intro\": intro,\n \"tocUrl\": bookUrl,\n \"wordCount\": \"\",\n \"type\": 0,\n \"latestChapterTitle\": latestChapterTitle,\n };\n books.push(book);\n } catch (e) {\n \/\/ 跳过解析失败的书籍\n }\n });\n\n removeHTMLSafely($tempContainer);\n return JSON.stringify(books);\n } catch (e) {\n return \"[]\";\n }\n }\n\n \/\/ 登录相关函数(可选)\n async function getloginurl() {\n return \"https:\/\/www.8xsk.info\/\";\n }\n\n async function login() {\n\n }\n\n \/\/ 导出函数供APP调用\n window.search = search;\n window.info = info;\n window.toc = toc;\n window.content = content;\n window.getfinds = getfinds;\n window.find = find;\n window.getloginurl = getloginurl;\n window.login = login;\n\n<\/script>\n\n<\/html>",
"login": true,
"lastUpdateTime": "1767718736970"
}