天天看小说
https://cn.ttkan.co
autobcb_admin (12020)3天前
天天看小说 作者 星辰
{
"bookSourceUrl": "https:\/\/cn.ttkan.co",
"bookSourceName": "天天看小说",
"enabledExplore": true,
"enabled": true,
"bookSourceGroup": "雨落星辰",
"author": "星辰",
"help": true,
"html": "\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n <meta charset=\"UTF-8\">\n <title>天天看小说<\/title>\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:\/\/cn.ttkan.co\"\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 + \"\/i\/sor.aspx?key=\" + encodeURIComponent(key);\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 function parseBookList(html) {\n try {\n var $tempContainer = parseHTMLSafely(removeHTMLTags(html));\n var books = [];\n var bookUrls = new Set(); \/\/ 用于去重\n\n \/\/ 查找所有h3标题下的书籍链接\n $tempContainer.find(\"h3 a\").each(function () {\n var $link = $(this);\n var href = $link.attr('href');\n\n \/\/ 必须有href属性\n if (!href) {\n return true; \/\/ continue\n }\n\n \/\/ 过滤掉非书籍链接\n if (href.includes('\/i\/') || href.includes('\/fenlei\/') ||\n href.includes('\/paihang\/') || href.includes('\/wanjie\/') ||\n href.includes('\/zuixin\/') || href.includes('.html') ||\n href.includes('javascript:') || href.includes('\/zuozhe\/')) {\n return true; \/\/ continue\n }\n\n \/\/ 提取书籍ID,格式如 \/167\/ 或 https:\/\/www.sudugu.org\/167\/\n var match = href.match(\/\\\/(\\d+)\\\/?$\/);\n if (!match) {\n return true; \/\/ continue\n }\n\n var bookId = match[1];\n var bookUrl = \"\";\n if (href.startsWith('http')) {\n bookUrl = href;\n } else if (href.startsWith('\/')) {\n bookUrl = baseurl + href;\n } else {\n return true; \/\/ continue\n }\n\n \/\/ 确保URL以\/结尾\n if (!bookUrl.endsWith('\/')) {\n bookUrl += '\/';\n }\n\n \/\/ 去重\n if (bookUrls.has(bookUrl)) {\n return true; \/\/ continue\n }\n bookUrls.add(bookUrl);\n\n \/\/ 提取书名\n var name = $link.text().trim();\n if (!name || name.length < 2) {\n return true; \/\/ continue\n }\n\n \/\/ 查找父容器(.item或.itemtxt)获取更多信息\n var $container = $link.closest('.item, div');\n\n \/\/ 提取作者 - 查找包含\"作者:\"的链接\n var author = \"\";\n var $authorLink = $container.find(\"a[href*='\/zuozhe\/']\").first();\n if ($authorLink.length > 0) {\n var authorText = $authorLink.text().trim();\n \/\/ 移除\"作者:\"前缀\n author = authorText.replace(\/^作者[:\\s]*\/, '');\n }\n\n \/\/ 提取分类和状态 - 从span标签中提取\n var kind = \"\";\n var status = \"\";\n $container.find(\"span\").each(function () {\n var text = $(this).text().trim();\n if (text === '已完结' || text === '连载中') {\n status = text;\n } else if (text && text.length > 0 && text.length < 10 && !text.includes('更新')) {\n \/\/ 可能是分类\n if (!kind) {\n kind = text;\n }\n }\n });\n\n\n \/\/ 提取封面 - 在同一容器中查找img(优先查找.item容器)\n var coverUrl = \"\";\n var $itemContainer = $link.closest('.item');\n var $img = $itemContainer.length > 0 ? $itemContainer.find(\"img\").first() : $container.find(\"img\").first();\n if ($img.length > 0) {\n var src = $img.attr('src');\n if (src) {\n if (src.startsWith('http')) {\n coverUrl = src;\n } else if (src.startsWith('\/')) {\n coverUrl = baseurl + src;\n }\n }\n }\n\n\n \/\/ 提取最新章节 - 查找章节链接(包含.html的链接)\n var latestChapterTitle = \"\";\n var $chapterLinks = $container.find(\"a[href*='.html']\");\n if ($chapterLinks.length > 0) {\n \/\/ 取第一个章节链接(最新章节)\n latestChapterTitle = $chapterLinks.first().text().trim();\n }\n\n var book = {\n \"bookUrl\": bookUrl,\n \"name\": name,\n \"author\": author,\n \"kind\": kind,\n \"coverUrl\": coverUrl,\n \"intro\": \"\",\n \"tocUrl\": bookUrl,\n \"wordCount\": status, \/\/ 将状态信息放在wordCount字段\n \"type\": 0,\n \"latestChapterTitle\": latestChapterTitle\n };\n\n books.push(book);\n });\n\n removeHTMLSafely($tempContainer);\n return JSON.stringify(books);\n } catch (e) {\n flutterBridge.log(\"解析书籍列表出错:\" + e.message);\n return \"[]\";\n }\n }\n \/\/ 书籍详情函数\n \/\/ 书籍详情函数\nasync 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 \/\/ 核心:定位详情核心容器、封面专属容器(均用多class精准匹配,无全局干扰)\n var $infoWrap = $tempContainer.find(\".pure-u-xl-5-6.pure-u-lg-5-6.pure-u-md-2-3.pure-u-1-2\").first();\n var $coverWrap = $tempContainer.find(\".pure-u-xl-1-6.pure-u-lg-1-6.pure-u-md-1-3.pure-u-1-2\").first();\n \/\/ 初始化所有返回字段\n var name = \"\", author = \"\", coverUrl = \"\", kind = \"\", intro = \"\", wordCount = \"\", latestChapterTitle = \"\";\n\n \/\/ 1. 提取书名:容器内ul>li>h1 直接取文本(适配给定结构)\n if ($infoWrap.length > 0) {\n name = $infoWrap.find(\"ul li h1\").first().text().trim() || \"\";\n }\n \/\/ 备用方案:全局h1(防止容器class变化)\n if (!name) {\n name = $tempContainer.find(\"h1\").first().text().trim() || \"\";\n }\n\n \/\/ 2. 提取作者:匹配【作者:】span,取其后续兄弟a标签的文本\n if ($infoWrap.length > 0) {\n var $authorSpan = $infoWrap.find(\"ul li span:contains('作者:')\").first();\n if ($authorSpan.length > 0) {\n author = $authorSpan.next(\"a\").text().trim() || \"\";\n }\n }\n \/\/ 备用方案:全局匹配\n if (!author) {\n author = $tempContainer.find(\"span:contains('作者:')\").next(\"a\").text().trim() || \"\";\n }\n\n \/\/ 3. 提取分类:匹配【类别:】span,取其父li的文本并剔除前缀\n if ($infoWrap.length > 0) {\n var $kindSpan = $infoWrap.find(\"ul li span:contains('类别:')\").first();\n if ($kindSpan.length > 0) {\n kind = $kindSpan.parent(\"li\").text().trim().replace(\/^类别:\/, \"\").trim() || \"\";\n }\n }\n \/\/ 备用方案:全局匹配\n if (!kind) {\n kind = $tempContainer.find(\"span:contains('类别:')\").parent(\"li\").text().trim().replace(\/^类别:\/, \"\").trim() || \"\";\n }\n\n \/\/ 4. 提取封面:【精准匹配封面专属容器】(核心修改)\n \/\/ 优先从封面容器内取amp-img的src(结构里已是完整http地址,直接用)\n if ($coverWrap.length > 0) {\n var $coverAmpImg = $coverWrap.find(\"amp-img\").first();\n if ($coverAmpImg.length > 0) {\n coverUrl = $coverAmpImg.attr(\"src\") || \"\";\n }\n }\n \/\/ 备用兜底逻辑:容器匹配失败时,全局找amp-img→普通img,兼容各种情况\n if (!coverUrl) {\n var $ampImg = $tempContainer.find(\"amp-img\").first();\n if ($ampImg.length > 0) {\n coverUrl = $ampImg.attr(\"src\") || $ampImg.attr(\"data-src\") || \"\";\n } else {\n var $normalImg = $tempContainer.find(\"img\").first();\n if ($normalImg.length > 0) {\n coverUrl = $normalImg.attr(\"src\") || $normalImg.attr(\"data-src\") || \"\";\n }\n }\n \/\/ 兜底拼接baseurl(仅备用逻辑需要,精准匹配无需)\n if (coverUrl && !coverUrl.startsWith('http') && coverUrl.startsWith('\/')) {\n coverUrl = baseurl + coverUrl;\n }\n }\n \/\/ 最终去重空格,避免空值\/无效值\n coverUrl = coverUrl.trim();\n\n \/\/ 5. 提取简介:优先精准匹配.description容器,无结果则走原兜底逻辑\n var $descContainer = $tempContainer.find(\".description\").first();\n if ($descContainer.length > 0) {\n intro = $descContainer.text().trim().replace(\/\\s+\/g, \" \");\n } else {\n var $introContainer = $tempContainer.clone();\n $introContainer.find(\"#list, #pages, .pages, .bookmark, .pure-u-xl-5-6\").remove();\n $introContainer.find(\"p, div, section\").each(function () {\n var text = $(this).text().trim();\n if (text.length > 50 && text.length < 1000 &&\n !text.includes(\"最新章节\") && !text.includes(\"作者\") && !text.includes(\"更新时间\") &&\n !text.includes(\"第\") && !text.includes(\"章\") && !text.includes(\"收藏本书\")) {\n intro = text;\n return false;\n }\n });\n removeHTMLSafely($introContainer);\n }\n\n \/\/ 6. 提取字数:保留原通用逻辑(结构未展示,备用)\n $tempContainer.find(\"*\").each(function () {\n var text = $(this).clone().children().remove().end().text().trim();\n if (text.match(\/^[\\d.]+万字$\/)) {\n wordCount = text;\n return false;\n }\n });\n\n \/\/ 7. 提取最新章节:排除收藏的a标签,精准匹配章节链接\n var $chapterLinks = $tempContainer.find(\"a[href*='.html']\").not(\".anchor_bookmark\");\n if ($chapterLinks.length > 0) {\n latestChapterTitle = $chapterLinks.first().text().trim() || \"\";\n }\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\": wordCount,\n \"type\": 0,\n \"latestChapterTitle\": latestChapterTitle\n };\n\n \/\/ 销毁临时容器,避免内存占用\n removeHTMLSafely($tempContainer);\n \/\/ 返回JSON字符串,与原函数一致\n return JSON.stringify(book);\n } catch (e) {\n flutterBridge.log(\"获取详情出错:\" + e.message);\n return \"{}\";\n }\n}\n \/\/ 章节列表函数(支持分页)\n async function chapter(tocUrl) {\n try {\n var allChapters = [];\n var pageIndex = 0;\n\n while (true) {\n var pageUrl;\n if (pageIndex === 0) {\n pageUrl = tocUrl;\n } else {\n var baseUrl = tocUrl.endsWith('\/') ? tocUrl.slice(0, -1) : tocUrl;\n pageUrl = baseUrl + \"\/p-\" + (pageIndex + 1) + \".html\";\n }\n\n flutterBridge.log(\"正在获取第\" + (pageIndex + 1) + \"页: \" + pageUrl);\n\n var result = await getChapterPage(pageUrl);\n\n if (result.chapters.length > 0) {\n allChapters.push(...result.chapters);\n flutterBridge.log(\"第\" + (pageIndex + 1) + \"页获取到 \" + result.chapters.length + \" 章\");\n } else {\n flutterBridge.log(\"第\" + (pageIndex + 1) + \"页没有章节,停止获取\");\n break;\n }\n\n if (!result.hasNextPage) {\n flutterBridge.log(\"没有下一页,总共获取 \" + allChapters.length + \" 章\");\n break;\n }\n\n pageIndex++;\n if (pageIndex >= 100) {\n flutterBridge.log(\"目录页数超过100页,停止获取\");\n break;\n }\n }\n\n \/\/ 重新设置index\n for (var i = 0; i < allChapters.length; i++) {\n allChapters[i].index = i;\n }\n\n return JSON.stringify(allChapters);\n } catch (e) {\n flutterBridge.log(\"获取章节列表出错:\" + e.message);\n return \"[]\";\n }\n }\n\n \/\/ 获取单页章节\nasync function getChapterPage(pageUrl) {\n try {\n var get = await http.Get(pageUrl, JSON.stringify(header), true);\n flutterBridge.text(2, get.data);\n\n var $tempContainer = parseHTMLSafely(removeHTMLTags(get.data));\n var chapters = [];\n var hasNextPage = false;\n\n var novelId = \"\";\n var match = pageUrl.match(\/\\\/(\\d+)\\\/?\/);\n if (match) {\n novelId = match[1];\n }\n\n var $listContainer = $tempContainer.find(\"#list\");\n var $chapterLinks = $listContainer.length > 0 ? $listContainer.find(\"a[href*='.html']\") : $tempContainer.find(\"a[href*='.html']\");\n\n var index = 0;\n $chapterLinks.each(function () {\n var $link = $(this);\n var chapterName = $link.text().trim();\n var href = $link.attr('href');\n\n if (!chapterName || !href || chapterName.length < 2) {\n return true;\n }\n\n \/\/ 核心修复:添加「收藏本书」的过滤,同时保留原有所有排除条件\n if (chapterName.includes(\"下载\") || chapterName.includes(\"TXT\") || chapterName.includes(\"目录\") ||\n chapterName.includes(\"下一页\") || chapterName.includes(\"上一页\") || chapterName.includes(\"开始阅读\") ||\n chapterName.includes(\"最新章节\") || chapterName === \"全部\" || chapterName.includes(\"收藏本书\")) {\n return true;\n }\n\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 + \"\/\" + novelId + \"\/\" + href;\n }\n\n chapters.push({\n \"name\": chapterName,\n \"chapterId\": chapterId,\n \"index\": index,\n \"isPay\": false,\n \"isVip\": false,\n \"isVolume\": false,\n \"tag\": \"\"\n });\n index++;\n });\n\n $tempContainer.find(\"a\").each(function () {\n var text = $(this).text().trim();\n if (text === \"下一页\" || text.includes(\"下一页\")) {\n hasNextPage = true;\n return false;\n }\n });\n\n removeHTMLSafely($tempContainer);\n return { chapters: chapters, hasNextPage: hasNextPage };\n } catch (e) {\n flutterBridge.log(\"获取章节页出错:\" + e.message);\n return { chapters: [], hasNextPage: false };\n }\n}\n\n \/\/ 章节内容函数(支持分页章节)\n async function content(url) {\n try {\n var allContent = [];\n var currentUrl = url;\n var pageIndex = 1;\n var maxPages = 10; \/\/ 防止无限循环\n\n while (pageIndex <= maxPages) {\n var get = await http.Get(currentUrl, JSON.stringify(header), true);\n if (pageIndex === 1) {\n flutterBridge.text(3, get.data);\n }\n\n var $tempContainer = parseHTMLSafely(removeHTMLTags(get.data));\n\n var $content = $tempContainer.find(\".content\").first();\n\n if ($content.length > 0) {\n \/\/ 移除广告和无关元素\n $content.find(\"script, style, .ad, .adsbygoogle, .advertisement\").remove();\n\n \/\/ 提取段落内容\n var paragraphs = [];\n $content.find(\"p\").each(function () {\n var text = $(this).text().trim();\n if (text.length > 0) {\n paragraphs.push(text);\n }\n });\n\n \/\/ 如果没有p标签,直接获取文本\n if (paragraphs.length === 0) {\n var text = $content.text().trim();\n if (text.length > 0) {\n paragraphs.push(text);\n }\n }\n\n if (paragraphs.length > 0) {\n allContent.push(paragraphs.join('\\r\\n\\r\\n'));\n }\n } else {\n break;\n }\n\n \/\/ 检查是否有下一页\n var hasNextPage = false;\n $tempContainer.find(\"a\").each(function () {\n var text = $(this).text().trim();\n var href = $(this).attr('href');\n if ((text === \"下一页\" || text.includes(\"下一页\")) && href) {\n \/\/ 构建下一页URL\n if (href.startsWith('http')) {\n currentUrl = href;\n } else if (href.startsWith('\/')) {\n currentUrl = baseurl + href;\n } else {\n \/\/ 相对路径,需要基于当前URL构建\n var urlParts = currentUrl.split('\/');\n urlParts[urlParts.length - 1] = href;\n currentUrl = urlParts.join('\/');\n }\n hasNextPage = true;\n return false; \/\/ break\n }\n });\n\n removeHTMLSafely($tempContainer);\n\n if (!hasNextPage) {\n break;\n }\n\n pageIndex++;\n }\n\n \/\/ 合并所有分页内容\n var finalContent = allContent.join('\\r\\n\\r\\n');\n\n \/\/ 移除导航文本\n finalContent = finalContent.replace(\/上一章\/g, '');\n finalContent = finalContent.replace(\/目录\/g, '');\n finalContent = finalContent.replace(\/下一页\/g, '');\n finalContent = finalContent.replace(\/下一章\/g, '');\n finalContent = finalContent.replace(\/首页\/g, '');\n\n \/\/ 清理多余的空行\n finalContent = finalContent.replace(\/\\r\\n\\r\\n\\r\\n+\/g, '\\r\\n\\r\\n').trim();\n\n return finalContent;\n } catch (e) {\n flutterBridge.log(\"获取章节内容出错:\" + e.message);\n return \"\";\n }\n }\n\n \/\/ 发现页分类函数\n async function getfinds() {\n var sort = [\n { \"title\": \"连载\", \"url\": \"https:\/\/cn.ttkan.co\/api\/nq\/amp_novel_list?type=lianzai&filter=*&page={{page}}&limit=18&language=cn&__amp_source_origin=https%3A%2F%2Fcn.ttkan.co\", \"type\": 0 },\n { \"title\": \"玄幻\", \"url\": \"https:\/\/cn.ttkan.co\/api\/nq\/amp_novel_list?type=xuanhuan&filter=*&page={{page}}&limit=18&language=cn&__amp_source_origin=https%3A%2F%2Fcn.ttkan.co\", \"type\": 0 },\n { \"title\": \"都市\", \"url\": \"https:\/\/cn.ttkan.co\/api\/nq\/amp_novel_list?type=dushi&filter=*&page={{page}}&limit=18&language=cn&__amp_source_origin=https%3A%2F%2Fcn.ttkan.co\", \"type\": 0 },\n { \"title\": \"仙侠\", \"url\": \"https:\/\/cn.ttkan.co\/api\/nq\/amp_novel_list?type=wuxia&filter=*&page={{page}}&limit=18&language=cn&__amp_source_origin=https%3A%2F%2Fcn.ttkan.co\", \"type\": 0 },\n { \"title\": \"言情\", \"url\": \"https:\/\/cn.ttkan.co\/api\/nq\/amp_novel_list?type=yanqing&filter=*&page={{page}}&limit=18&language=cn&__amp_source_origin=https%3A%2F%2Fcn.ttkan.co\", \"type\": 0 },\n { \"title\": \"游戏\", \"url\": \"https:\/\/cn.ttkan.co\/api\/nq\/amp_novel_list?type=youxi&filter=*&page={{page}}&limit=18&language=cn&__amp_source_origin=https%3A%2F%2Fcn.ttkan.co\", \"type\": 0 },\n { \"title\": \"科幻\", \"url\": \"https:\/\/cn.ttkan.co\/api\/nq\/amp_novel_list?type=kehuan&filter=*&page={{page}}&limit=18&language=cn&__amp_source_origin=https%3A%2F%2Fcn.ttkan.co\", \"type\": 0 },\n { \"title\": \"悬疑\", \"url\": \"https:\/\/cn.ttkan.co\/api\/nq\/amp_novel_list?type=kongbu&filter=*&page={{page}}&limit=18&language=cn&__amp_source_origin=https%3A%2F%2Fcn.ttkan.co\", \"type\": 0 },\n { \"title\": \"灵异\", \"url\": \"https:\/\/cn.ttkan.co\/api\/nq\/amp_novel_list?type=lingyi&filter=*&page={{page}}&limit=18&language=cn&__amp_source_origin=https%3A%2F%2Fcn.ttkan.co\", \"type\": 0 },\n { \"title\": \"军事\", \"url\": \"https:\/\/cn.ttkan.co\/api\/nq\/amp_novel_list?type=lishi&filter=*&page={{page}}&limit=18&language=cn&__amp_source_origin=https%3A%2F%2Fcn.ttkan.co\", \"type\": 0 },\n { \"title\": \"现言\", \"url\": \"https:\/\/cn.ttkan.co\/api\/nq\/amp_novel_list?type=tongren&filter=*&page={{page}}&limit=18&language=cn&__amp_source_origin=https%3A%2F%2Fcn.ttkan.co\", \"type\": 0 },\n { \"title\": \"其它\", \"url\": \"https:\/\/cn.ttkan.co\/api\/nq\/amp_novel_list?type=qita&filter=*&page={{page}}&limit=18&language=cn&__amp_source_origin=https%3A%2F%2Fcn.ttkan.co\", \"type\": 0 }\n ];\n return JSON.stringify(sort);\n}\n\n\/\/ 发现页列表函数\n async function parsefindList(html) {\n var data = JSON.parse(html);\n var books = [];\n const bookUrlPrefix = \"https:\/\/cn.ttkan.co\/novel\/chapters\/\";\n const coverUrlPrefix = \"https:\/\/static.ttkan.co\/cover\/\";\n const coverUrlParams = \"?w=120&h=160&q=100\";\n\n data.items.forEach(b => {\n var book = {\n \"bookUrl\": bookUrlPrefix + b[\"novel_id\"],\n \"name\": b[\"name\"],\n \"author\": b[\"author\"],\n \"kind\": b[\"category\"],\n \"coverUrl\": coverUrlPrefix + b[\"topic_img\"] + coverUrlParams,\n \"intro\": b[\"description\"],\n \"tocUrl\": bookUrlPrefix + b[\"novel_id\"],\n \"wordCount\": b[\"wordCount\"],\n \"type\": 0,\n \"latestChapterTitle\": b[\"lastChapterTitle\"]\n }\n books.push(book);\n })\n return JSON.stringify(books);\n }\n\n \/\/ 发现页书籍列表函数\n async function find(url, page) {\n try {\n \/\/ APP传入的URL包含{{page}}占位符,需要替换为实际页码\n if (url.includes('{{page}}')) {\n url = url.replace('{{page}}', page);\n }\n\n var get = await http.Get(url, JSON.stringify(header), true);\n\n return parsefindList(get.data);\n } catch (e) {\n flutterBridge.log(\"获取发现页出错:\" + e.message);\n return \"[]\";\n }\n }\n\n \/\/帮助页面\n async function gethelp(){\n return \"网站本身搜索就不能用所以没有写!!!知豆不?\";\n }\n \/\/ 导出函数供APP调用\n window.search = search;\n window.info = info;\n window.chapter = chapter;\n window.content = content;\n window.getfinds = getfinds;\n window.find = find;\n\n<\/script>\n\n<\/html>",
"login": false,
"lastUpdateTime": "1770977875659"
}