斗破苍穹小说网

http://m.xdoupocangqiong.org/

Simwoodone (12535)09/20 12:48

该用户很懒,什么介绍也没有写!
二维码导入(APP尚未完成该功能)
{
    "bookSourceUrl": "http:\/\/m.xdoupocangqiong.org\/",
    "bookSourceName": "斗破苍穹小说网",
    "enabledExplore": true,
    "enabled": true,
    "bookSourceGroup": "",
    "author": "",
    "help": true,
    "html": "<!DOCTYPE html>\n<html lang=\"zh\" data-page-node-id=\"tXR4CGymoQm9YclaWc8ptS\">\n<head data-page-node-id=\"2OJao4JKOryvFqfALHcgvi\">\n<meta charset=\"UTF-8\" data-page-node-id=\"oDtsjqjAF8qUiC0Wpls9Ga\">\n<title>斗破苍穹小说网<\/title>\n<\/head>\n<body data-page-node-id=\"VHJs20OTJ1Q7jS6Ce7K9G7\"><\/body>\n<script src=\"https:\/\/vc.jd.com\/web\/js\/jquery-3.1.1.min.js\"><\/script>\n<script>\nvar isCookieJar = true; \/\/ 斗破苍穹小说网为纯静态 nginx 站,无需登录,保留 CookieJar 以兼容 App\n\n\/* ==========================================================================\n * FlutterJSBridge —— 所有原生能力必须通过 callHandler 调用\n * ========================================================================== *\/\nclass FlutterJSBridge {\n  constructor() { this.init(); }\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        this.CookieJar();\n      });\n    }\n  }\n\n  async CookieJar() {\n    try { await window.flutter_inappwebview.callHandler('CookieJar', isCookieJar); } catch (e) {}\n  }\n  async getbuildNumber() { try { return await window.flutter_inappwebview.callHandler('buildNumber'); } catch (e) { return 0; } }\n  async getversion() { try { return await window.flutter_inappwebview.callHandler('version'); } catch (e) { return \"0.0.0\"; } }\n  async getsourceurl() { try { return await window.flutter_inappwebview.callHandler('getsourceurl'); } catch (e) { return \"\"; } }\n  async getsearch() { try { return await window.flutter_inappwebview.callHandler('getsearch'); } catch (e) { return -1; } }\n  async getWidth() { try { return await window.flutter_inappwebview.callHandler('getWidth'); } catch (e) { return 0; } }\n  async getHeight() { try { return await window.flutter_inappwebview.callHandler('getHeight'); } catch (e) { return 0; } }\n  async htmlToText(str) { try { return await window.flutter_inappwebview.callHandler('htmlToText', str); } catch (e) { return \"\"; } }\n  async showvideoplay(url, title, key) { try { return await window.flutter_inappwebview.callHandler('showvideoplay', url, title, key); } catch (e) { return \"\"; } }\n  async refreshContent(bookurl) { try { return await window.flutter_inappwebview.callHandler('refreshContent', bookurl); } catch (e) { return \"\"; } }\n  async refreshExplore() { try { return await window.flutter_inappwebview.callHandler('refreshExplore'); } catch (e) { return \"\"; } }\n  async showsvg() { try { return await window.flutter_inappwebview.callHandler('showsvg'); } catch (e) { return \"\"; } }\n  async getsvg() { try { return await window.flutter_inappwebview.callHandler('getsvg'); } catch (e) { return []; } }\n  async addsvg(svg) { try { return await window.flutter_inappwebview.callHandler('addsvg', svg); } catch (e) { return []; } }\n  async setHtml(str) { try { return await window.flutter_inappwebview.callHandler('setHtml', str); } catch (e) { return \"\"; } }\n  async toTraditional(str) { try { return await window.flutter_inappwebview.callHandler('toTraditional', str); } catch (e) { return \"\"; } }\n  async toSimplified(str) { try { return await window.flutter_inappwebview.callHandler('toSimplified', str); } catch (e) { return \"\"; } }\n  async voice() { try { return await window.flutter_inappwebview.callHandler('voice'); } catch (e) { return \"\"; } }\n  async getDeviceid() { try { return await window.flutter_inappwebview.callHandler('id'); } catch (e) { return \"\"; } }\n  async getDevice() { try { return await window.flutter_inappwebview.callHandler('device'); } catch (e) { return \"\"; } }\n  async getLoginUser() { try { return await window.flutter_inappwebview.callHandler('getLoginUser'); } catch (e) { return \"\"; } }\n  async log(str) { try { return await window.flutter_inappwebview.callHandler('log', str); } catch (e) { return false; } }\n  async text(type, str) { try { return await window.flutter_inappwebview.callHandler('text', type, str); } catch (e) { return false; } }\n  async showToast(str) { try { return await window.flutter_inappwebview.callHandler('showToast', str); } catch (e) { return false; } }\n  async showLongToast(str) { try { return await window.flutter_inappwebview.callHandler('showLongToast', str); } catch (e) { return false; } }\n  async getWebViewUA() { try { return await window.flutter_inappwebview.callHandler('getWebViewUA'); } catch (e) { return \"\"; } }\n  async openurl(url) { try { return await window.flutter_inappwebview.callHandler('openurl', url, \"\"); } catch (e) { return false; } }\n  async openurlwithMimeType(url, mimeType) { try { return await window.flutter_inappwebview.callHandler('openurl', url, mimeType); } catch (e) { return false; } }\n  async webview(url, js, html, body, header) { try { return await window.flutter_inappwebview.callHandler('webview', url, js, html, body, header, \"\", \"\"); } catch (e) { return \"\"; } }\n  async webViewGetOverrideUrl(url, js, html, body, header, overrideUrlRegex) { try { return await window.flutter_inappwebview.callHandler('webview', url, js, html, body, header, overrideUrlRegex, \"\"); } catch (e) { return \"\"; } }\n  async webViewGetSource(url, js, html, body, header, urlregex) { try { return await window.flutter_inappwebview.callHandler('webview', url, js, html, body, header, \"\", urlregex); } catch (e) { return \"\"; } }\n  async webViewGetAjax(url, html, body, header, ajaxregex) { try { return await window.flutter_inappwebview.callHandler('webviewajax', url, html, body, header, ajaxregex); } catch (e) { return \"\"; } }\n  async startBrowser(url, title, header) { try { return await window.flutter_inappwebview.callHandler('startBrowser', url, title, header); } catch (e) { return \"\"; } }\n  async startBrowserWithShouldOverrideUrlLoading(url, title, header) { try { return await window.flutter_inappwebview.callHandler('startBrowserWithShouldOverrideUrlLoading', url, title, header); } catch (e) { return \"\"; } }\n  async startBrowserDp(url, title) { try { return await window.flutter_inappwebview.callHandler('startBrowserDp', url, title); } catch (e) { return \"\"; } }\n  async startBrowserDpWithShouldOverrideUrlLoading(url, title) { try { return await window.flutter_inappwebview.callHandler('startBrowserDpWithShouldOverrideUrlLoading', url, title); } catch (e) { return \"\"; } }\n  async startBrowserDpwithHtml(url, html, title) { try { return await window.flutter_inappwebview.callHandler('startBrowserDpwithHtml', url, html, title); } catch (e) { return \"\"; } }\n  async back() { try { return await window.flutter_inappwebview.callHandler('back'); } catch (e) { return false; } }\n  async utf8ToGbkUrlEncoded(str) { try { return await window.flutter_inappwebview.callHandler('utf8ToGbkUrlEncoded', str); } catch (e) { return \"\"; } }\n  async getVerificationCode(str, header) { try { return await window.flutter_inappwebview.callHandler('getVerificationCode', str, header); } catch (e) { return \"\"; } }\n  async addbook(bookUrl) { try { return await window.flutter_inappwebview.callHandler('addbook', bookUrl); } catch (e) { return \"\"; } }\n  async searchbook(key) { try { return await window.flutter_inappwebview.callHandler('searchbook', key); } catch (e) { return \"\"; } }\n  async getdurChapterIndex(bookUrl) { try { return await window.flutter_inappwebview.callHandler('getdurChapterIndex', bookUrl); } catch (e) { return 0; } }\n  async getReadBook() { try { return await window.flutter_inappwebview.callHandler('getReadBook'); } catch (e) { return null; } }\n  async base64encode(str) { try { return await window.flutter_inappwebview.callHandler('base64encode', str); } catch (e) { return \"\"; } }\n  async base64decode(str) { try { return await window.flutter_inappwebview.callHandler('base64decode', str); } catch (e) { return \"\"; } }\n}\n\n\/* ==========================================================================\n * Http —— 原生网络请求\n * ========================================================================== *\/\nclass Http {\n  constructor() {\n    this.open = false;\n    this.requestTimestamps = [];\n    this.rateLimit = 5;\n    this.rateLimitWindow = 1000;\n  }\n  async checkRateLimit() {\n    if (!this.open) return;\n    const now = Date.now();\n    this.requestTimestamps = this.requestTimestamps.filter(t => now - t < this.rateLimitWindow);\n    if (this.requestTimestamps.length >= this.rateLimit) {\n      const waitTime = this.rateLimitWindow - (now - this.requestTimestamps[0]);\n      await new Promise(resolve => setTimeout(resolve, waitTime));\n      return this.checkRateLimit();\n    }\n    this.requestTimestamps.push(now);\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 (e) { return null; }\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 (e) { return null; }\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 (e) { return null; }\n  }\n}\n\n\/* ==========================================================================\n * Cache \/ Cookie\n * ========================================================================== *\/\nclass Cache {\n  constructor() {}\n  async get(key) { try { return await window.flutter_inappwebview.callHandler('cache.get', key); } catch (e) { return null; } }\n  async set(key, value) { try { return await window.flutter_inappwebview.callHandler('cache.set', key, value); } catch (e) { return null; } }\n  async remove(key) { try { return await window.flutter_inappwebview.callHandler('cache.remove', key); } catch (e) { return null; } }\n  async allget(key) { try { return await window.flutter_inappwebview.callHandler('cache.allget', key); } catch (e) { return null; } }\n  async allset(key, value) { try { return await window.flutter_inappwebview.callHandler('cache.allset', key, value); } catch (e) { return null; } }\n  async allremove(key) { try { return await window.flutter_inappwebview.callHandler('cache.allremove', key); } catch (e) { 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  async putLyric(bookUrl, index, lyric) { try { return await window.flutter_inappwebview.callHandler('putLyric', bookUrl, index, lyric); } catch (e) { return null; } }\n}\n\nclass Cookie {\n  constructor() {}\n  async get(url) { try { return await window.flutter_inappwebview.callHandler('cookie.get', url); } catch (e) { return null; } }\n  async remove(url) { try { return await window.flutter_inappwebview.callHandler('cookie.remove', url); } catch (e) { return null; } }\n  async set(url, value) { try { return await window.flutter_inappwebview.callHandler('cookie.set', url, value); } catch (e) { return null; } }\n  async setCookie(url, key, value) { try { return await window.flutter_inappwebview.callHandler('cookie.setcookie', url, key, value); } catch (e) { return null; } }\n  async getCookie(url, value) { try { return await window.flutter_inappwebview.callHandler('cookie.getCookie', url, value); } catch (e) { return null; } }\n}\n\n\/* ==========================================================================\n * 辅助函数\n * ========================================================================== *\/\nfunction parseHTMLSafely(htmlStr) {\n  try {\n    var tempDiv = document.createElement('div');\n    tempDiv.innerHTML = htmlStr;\n    return $(tempDiv);\n  } catch (e) {\n    return $('<div>');\n  }\n}\nfunction removeHTMLSafely(tempContainer) {\n  try {\n    if (tempContainer && tempContainer[0]) {\n      tempContainer[0].innerHTML = '';\n      if (tempContainer[0].parentNode) tempContainer[0].parentNode.removeChild(tempContainer[0]);\n    }\n  } catch (e) {}\n}\nfunction removeHTMLTags(htmlString) {\n  var r = String(htmlString || '').replace(\/<script\\b[^<]*(?:(?!<\\\/script>)<[^<]*)*<\\\/script>\/gi, '');\n  return r.replace(\/<style\\b[^<]*(?:(?!<\\\/style>)<[^<]*)*<\\\/style>\/gi, '');\n}\n<\/script>\n\n<!-- ======================= 业务逻辑 ======================= -->\n<script>\nconst flutterBridge = new FlutterJSBridge();\nconst cache = new Cache();\nconst http = new Http();\nconst cookie = new Cookie();\n\nconst BASE = \"http:\/\/m.xdoupocangqiong.org\";\nconst BOOK_TYPE = 0; \/\/ 0=小说\n\nvar reqHeaders = {\n  \"User-Agent\": \"Mozilla\/5.0 (Linux; Android 13; Pixel 7) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/120.0.0.0 Mobile Safari\/537.36\",\n  \"Accept\": \"text\/html,application\/xhtml+xml,application\/xml;q=0.9,image\/avif,image\/webp,*\/*;q=0.8\",\n  \"Accept-Language\": \"zh-CN,zh;q=0.9\",\n  \"Referer\": BASE + \"\/\"\n};\n\n\/* ---------- 基础工具 ---------- *\/\n\n\/\/ 相对地址补全为绝对地址\nfunction abs(u) {\n  if (!u) return \"\";\n  u = String(u).trim();\n  if (\/^(https?:)?\\\/\\\/\/i.test(u)) return u.indexOf('\/\/') === 0 ? 'http:' + u : u;\n  if (u.charAt(0) === '\/') return BASE + u;\n  return BASE + '\/' + u;\n}\n\n\/\/ 列表字段用的紧凑文本\nfunction clean(s) {\n  return (s == null ? \"\" : String(s)).replace(\/\\s+\/g, \" \").trim();\n}\n\n\/\/ 正文段落用(保留段落内部原貌,仅去首尾与换行)\nfunction cleanPara(s) {\n  return (s == null ? \"\" : String(s)).replace(\/[\\r\\n\\t]+\/g, \"\").replace(\/^[\\s\\u3000]+|[\\s\\u3000]+$\/g, \"\");\n}\n\n\/\/ 站点占位封面 public\/image\/nocover.jpg 会被替换成空串,交给 App 显示默认封面\nfunction realCover(u) {\n  u = abs(u);\n  if (!u) return \"\";\n  if (\/nocover|no_cover|default\\.jpg|blank\\.\/i.test(u)) return \"\";\n  return u;\n}\n\n\/\/ 统一取页面 HTML;失败返回 \"\"\nasync function fetchHtml(url) {\n  if (!url) return \"\";\n  var r = null;\n  try { r = await http.Get(url, JSON.stringify(reqHeaders), true); } catch (e) { r = null; }\n  if (!r) return \"\";\n  if (typeof r === 'string') return r;\n  if (r.statusCode && r.statusCode !== 200) return \"\";\n  return r.data || \"\";\n}\n\n\/\/ 书籍列表解析(搜索页 \/ 分类页 \/ 排行页共用)\n\/\/ 站点结构:ul.pt-card > li > .pt-cover(img) + .pt-novel(.pt-name\/.pt-author\/.pt-info\/.pt-desc)\nfunction parseBookList(html) {\n  var books = [];\n  if (!html) return books;\n  var $t = parseHTMLSafely(removeHTMLTags(html));\n  var seen = {};\n\n  $t.find(\"ul.pt-card > li\").each(function () {\n    try {\n      var $li = $(this);\n      var $a = $li.find(\".pt-name a\").first();\n      if (!$a.length) $a = $li.find(\"a[href]\").first();\n      var href = $a.attr(\"href\") || \"\";\n      if (!\/^\\\/\\d+\\\/?$\/.test(href)) return;\n      if (seen[href]) return;\n\n      var name = clean($a.attr(\"title\") || $a.text());\n      if (!name) return;\n      seen[href] = 1;\n\n      \/\/ 作者:搜索页在 .pt-author,分类\/排行页在 .pt-info a.0\n      var author = clean($li.find(\".pt-author\").first().find(\"a\").first().text());\n      if (!author) author = clean($li.find(\".pt-info a\").eq(0).text());\n\n      \/\/ 分类:分类\/排行页 .pt-info a.1\n      var kind = clean($li.find(\".pt-info a\").eq(1).text());\n      if (kind === author) kind = \"\";\n\n      var cover = realCover($li.find(\".pt-cover img\").first().attr(\"src\"));\n      var latest = clean($li.find(\".pt-author.ptm-text-cut a\").first().text());\n      var intro = clean($li.find(\".pt-desc\").first().text());\n\n      books.push({\n        bookUrl: abs(href),\n        name: name,\n        author: author,\n        kind: kind,\n        coverUrl: cover,\n        intro: intro,\n        tocUrl: abs(href),\n        wordCount: \"\",\n        type: BOOK_TYPE,\n        latestChapterTitle: latest\n      });\n    } catch (e) {}\n  });\n\n  removeHTMLSafely($t);\n  return books;\n}\n\n\/* ==========================================================================\n * 1. 搜索\n * 站点搜索:GET \/search.html?searchkey={key}(结果无分页)\n * ========================================================================== *\/\nasync function search(key, page, env) {\n  if (page && page > 1) return \"[]\";\n  if (!key) return \"[]\";\n\n  var url = BASE + \"\/search.html?searchkey=\" + encodeURIComponent(key);\n  var html = await fetchHtml(url);\n  if (!html) return \"[]\";\n\n  var books = parseBookList(html);\n  return JSON.stringify(books);\n}\n\n\/* ==========================================================================\n * 2. 详情\n * 站点详情页:\/1\/  (h2.pt-name、.pt-info、.baseinfo img、.intro)\n * ========================================================================== *\/\nasync function info(bookurl) {\n  if (!bookurl) return JSON.stringify({});\n  var html = await fetchHtml(bookurl);\n  if (!html) return JSON.stringify({});\n\n  var $t = parseHTMLSafely(removeHTMLTags(html));\n\n  var $nameA = $t.find(\".pt-name a\").first();\n  var name = clean($nameA.attr(\"title\") || $nameA.text());\n\n  \/\/ .pt-info 依次为「作者: xxx \/ 分类: xxx \/ 更新: xxx」\n  var author = \"\", kind = \"\", updateTime = \"\";\n  $t.find(\".pt-info\").each(function () {\n    var $d = $(this);\n    var t = clean($d.text());\n    var val = clean($d.find(\"a\").first().text()) || t.replace(\/^[^::]*[::]\\s*\/, \"\");\n    if (!author && t.indexOf(\"作者\") !== -1) author = val;\n    else if (!kind && t.indexOf(\"分类\") !== -1) kind = val;\n    else if (!updateTime && t.indexOf(\"更新\") !== -1) updateTime = val;\n  });\n\n  var cover = realCover($t.find(\".baseinfo img\").first().attr(\"src\"));\n\n  \/\/ 简介:.intro 为主简介,.intro.ann 是公告,需排除\n  var $intro = $t.find(\".intro\").not(\".ann\").first();\n  if (!$intro.length) $intro = $t.find(\".intro\").first();\n  var intro = clean($intro.text());\n  if (!intro) intro = clean($t.find(\".pt-desc\").first().text());\n\n  \/\/ 最新章节(首页「最近章节」卡片)\n  var latest = clean($t.find(\".pt-dir li a\").first().text());\n  if (!latest) latest = clean($t.find(\".ptm-list-view li a\").first().text());\n\n  \/\/ 目录地址:把书籍地址规范化为 \/{id}\/list\/\n  var bu = String(bookurl).split(\"?\")[0].split(\"#\")[0];\n  if (bu.charAt(bu.length - 1) !== '\/') bu += '\/';\n  var tocUrl = \/\\\/(list|toc)\\\/$\/.test(bu) ? bu : bu + \"list\/\";\n\n  removeHTMLSafely($t);\n\n  var book = {\n    bookUrl: String(bookurl),\n    name: name,\n    author: author,\n    kind: kind,\n    coverUrl: cover,\n    intro: intro,\n    tocUrl: tocUrl,\n    wordCount: \"\",\n    type: BOOK_TYPE,\n    latestChapterTitle: latest\n  };\n  return JSON.stringify(book);\n}\n\n\/* ==========================================================================\n * 3. 目录\n * 站点目录:\/{id}\/list\/?page=N&sort=asc  每页 50 章,末页不足 50\n * 注意:页码越界时服务端会钳制回末页,必须按 href 去重\n * ========================================================================== *\/\n\n\/\/ 把 tocUrl \/ bookUrl 规范成不含 query 的目录基址,如 http:\/\/...\/1\/list\nfunction listBase(tocUrl, bookurl) {\n  var t = String(tocUrl || bookurl || \"\").split(\"?\")[0].split(\"#\")[0];\n  if (!t) return \"\";\n  if (t.charAt(t.length - 1) === '\/') t = t.substring(0, t.length - 1);\n  if (\/\\\/(list|toc)$\/.test(t)) return t;\n\n  var b = String(bookurl || t).split(\"?\")[0].split(\"#\")[0];\n  if (b.charAt(b.length - 1) === '\/') b = b.substring(0, b.length - 1);\n  if (\/\\\/(list|toc)$\/.test(b)) return b;\n  if (\/\\\/\\d+$\/.test(b)) return b + \"\/list\";\n  return t;\n}\n\n\/\/ 解析一页目录,新增章节追加到 chapters,返回本页新增数量\nfunction collectChapters(html, chapters, seen) {\n  if (!html) return 0;\n  var $t = parseHTMLSafely(removeHTMLTags(html));\n  var got = 0;\n  $t.find(\"ul.ptm-list-view li a\").each(function () {\n    try {\n      var $a = $(this);\n      var href = $a.attr(\"href\") || \"\";\n      if (!\/^\\\/\\d+\\\/t?\\d+\\.html$\/.test(href)) return;\n      if (seen[href]) return;\n      var nm = clean($a.attr(\"title\") || $a.text()).replace(\/^\\d+\\.\\s*\/, \"\");\n      if (!nm) return;\n      seen[href] = 1;\n      chapters.push({ name: nm, chapterId: abs(href), url: abs(href) });\n      got++;\n    } catch (e) {}\n  });\n  removeHTMLSafely($t);\n  return got;\n}\n\nasync function chapter(tocUrl, bookurl) {\n  var base = listBase(tocUrl, bookurl);\n  if (!base) return \"[]\";\n\n  var first = await fetchHtml(base + \"\/?page=1&sort=asc\");\n  if (!first) return \"[]\";\n\n  var chapters = [], seen = {};\n  var got = collectChapters(first, chapters, seen);\n  if (!chapters.length) return \"[]\";\n\n  \/\/ 从「请选择章节」页码列表中推断总页数,避免盲目翻页\n  var maxPage = 60;\n  var $t = parseHTMLSafely(removeHTMLTags(first));\n  var maxFromSel = 0;\n  $t.find(\".pt-dir-sel li a\").each(function () {\n    var m = String($(this).attr(\"href\") || \"\").match(\/[?&]page=(\\d+)\/);\n    if (m) { var n = parseInt(m[1], 10); if (n > maxFromSel) maxFromSel = n; }\n  });\n  removeHTMLSafely($t);\n  if (maxFromSel > 1) maxPage = Math.min(maxFromSel, 200);\n  else if (got < 50) maxPage = 1;\n\n  \/\/ 剩余页并发抓取(每批 4 个),按页序追加\n  var page = 2;\n  while (page <= maxPage) {\n    var batch = [];\n    for (var k = 0; k < 4 && page + k <= maxPage; k++) batch.push(page + k);\n\n    var results = await Promise.all(batch.map(function (p) {\n      return fetchHtml(base + \"\/?page=\" + p + \"&sort=asc\");\n    }));\n\n    var batchNew = 0, stop = false;\n    for (var i = 0; i < results.length; i++) {\n      var n = collectChapters(results[i], chapters, seen);\n      batchNew += n;\n      \/\/ 末页不足 50 章说明已到目录末尾\n      if (n < 50) { stop = true; break; }\n    }\n    if (stop || batchNew === 0) break;\n    page += batch.length;\n  }\n\n  for (var j = 0; j < chapters.length; j++) {\n    chapters[j].index = j;\n    chapters[j].isPay = false;\n    chapters[j].isVip = false;\n    chapters[j].isVolume = false;\n    chapters[j].tag = \"\";\n  }\n\n  flutterBridge.log(\"目录章节数: \" + chapters.length);\n  return JSON.stringify(chapters);\n}\n\n\/* ==========================================================================\n * 4. 正文\n * 站点正文:\/{id}\/{cid}.html,长章分页为 \/{id}\/{cid}_2.html、_3.html…\n * 终止条件:页码 N\/N 已到末页,或下一页不再是 _数字.html(即跨章)\n * ========================================================================== *\/\n\n\/\/ 标题归一化:去空白\/控制字符、去括号附加语,用于识别「章节标题行」\nfunction normTitle(s) {\n  return String(s == null ? \"\" : s)\n    .replace(\/[\\s\\u3000\\u0000-\\u001f\\u007f]+\/g, '')\n    .replace(\/[((【\\[][^))】\\]]*[))】\\]]\/g, '')\n    .replace(\/[·・::\\-—_]\/g, '');\n}\n\n\/\/ 「章节标题行」形态:第X章\/节\/回 + 极短后缀,且不含正文标点\n\/\/ 站点会在正文开头重复插入标题行(有时章号与页面 h1 不一致),需按形态兜底清除\nvar TITLE_LINE_RE = \/^第[0-9零一二三四五六七八九十百千万两]+[章节回](?:[\\s\\u3000]*[^\\s“”\"',,。…、」』]){0,30}$\/;\n\n\/\/ 正文区内的站点冗余\/广告短句(仅对短句生效,避免误伤正文)\nvar CONTENT_AD_RE = \/(推荐全文字|手打站|无弹窗|请记住本站|一秒记住|最新章节请|手机用户请|章节错误|内容严重缺失|求收藏|求推荐票|点击下一页|本站域名|更多精彩|txt下载|全集下载|第一版主|www\\.|https?:)\/i;\n\nasync function content(url, bookurl) {\n  if (!url) return \"\";\n\n  var parts = [];\n  var next = abs(url);\n  var guard = 0;\n\n  while (next && guard < 30) {\n    guard++;\n    var html = await fetchHtml(next);\n    if (!html) break;\n\n    var $t = parseHTMLSafely(removeHTMLTags(html));\n    var $c = $t.find(\"#articlecon\").first();\n    if (!$c.length) { removeHTMLSafely($t); break; }\n\n    \/\/ 分页标记「(第1\/3页)」\n    var cur = 0, total = 0;\n    var marker = cleanPara($c.text()).match(\/第\\s*(\\d+)\\s*\\\/\\s*(\\d+)\\s*页\/);\n    if (marker) { cur = parseInt(marker[1], 10); total = parseInt(marker[2], 10); }\n\n    \/\/ 站点自身的「下一页」(最后一页会指向下一章)\n    var rawNext = $t.find(\"#pb_next\").first().attr(\"href\") || \"\";\n\n    \/\/ 章节标题(用于剔除正文区里重复出现的标题行)\n    var titleKey = normTitle($t.find(\"h1.title\").first().text());\n\n    $c.find(\"#center_tip\").remove();\n    $c.find(\"script, style, ins, iframe\").remove();\n\n    \/\/ 去掉被字符包裹或独立成段的分页标记\n    function stripPageMark(s) {\n      return cleanPara(String(s)\n        .replace(\/[((]\\s*第\\s*\\d+\\s*\\\/\\s*\\d+\\s*页\\s*[))]\/g, '')\n        .replace(\/第\\s*\\d+\\s*\\\/\\s*\\d+\\s*页\/g, ''));\n    }\n\n    var text = \"\";\n    var started = false;   \/\/ 正文是否已开始(标题行只在开头出现)\n    $c.find(\"p\").each(function () {\n      var s = stripPageMark($(this).text());\n      if (!s) return;\n      if (titleKey && normTitle(s) === titleKey) return;          \/\/ 与页面标题一致的标题行\n      if (s.length <= 30 && CONTENT_AD_RE.test(s)) return;        \/\/ 站点广告短句\n      if (s.indexOf(\"最新网址\") !== -1) return;                    \/\/ 站点横幅\n      if (s.indexOf(\"本章未完\") !== -1) return;                    \/\/ 翻页提示\n      if (\/^\\(?(?本章[未已]\/.test(s)) return;\n      if (s.indexOf(\"斗破苍穹小说网\") !== -1 && s.length < 20) return;\n      if (!started && s.length <= 45 && TITLE_LINE_RE.test(s)) return; \/\/ 开头的重复标题行\n      if (s.length > 45) started = true;\n      text += s + \"\\r\\n\\r\\n\";\n    });\n\n    \/\/ 兜底:无 <p> 结构时直接取整段文本\n    if (!text) {\n      var raw = cleanPara($c.text());\n      raw = raw.replace(\/最新网址[::]\\s*\\S+\/g, '')\n               .replace(\/[((]\\s*第\\s*\\d+\\s*\\\/\\s*\\d+\\s*页\\s*[))]\/g, '')\n               .replace(\/第\\s*\\d+\\s*\\\/\\s*\\d+\\s*页\/g, '')\n               .replace(\/(?本章未完[^))]*[))]?\/g, '')\n               .replace(\/\\(本章未[完已][^)]*\\)\/g, '');\n      text = cleanPara(raw);\n    }\n\n    if (text) parts.push(text.trim());\n    removeHTMLSafely($t);\n\n    \/\/ 是否继续翻页\n    if (!rawNext || rawNext === next || rawNext === url) break;\n    if (!\/_\\d+\\.html$\/.test(rawNext)) break;        \/\/ 下一页不是分页,而是下一章\n    if (total > 0 && cur >= total) break;           \/\/ 已到本章末页\n    next = abs(rawNext);\n  }\n\n  return parts.join(\"\\r\\n\\r\\n\").replace(\/\\r\\n{3,}\/g, \"\\r\\n\\r\\n\").trim();\n}\n\n\/* ==========================================================================\n * 5. 发现\n * 分类:\/sort\/{slug}.html 与 \/sort\/{slug}\/{page}.html\n * 排行:\/top\/(周\/月\/总人气为页内 tab,一次取回)\n * 全本:\/over\/{page}.html\n * ========================================================================== *\/\nasync function getfinds() {\n  var R = [];\n  function H(t) { R.push({ title: t, url: \"\", width: 3 }); }\n  function I(t, u) { R.push({ title: t, url: u, type: 0, width: 0 }); }\n\n  H(\"📚 分类\");\n  [\n    [\"玄幻\", \"xuanhuan\"], [\"奇幻\", \"qihuan\"], [\"武侠\", \"wuxia\"], [\"仙侠\", \"xianxia\"],\n    [\"都市\", \"dushi\"], [\"历史\", \"lishi\"], [\"军事\", \"junshi\"], [\"游戏\", \"youxi\"],\n    [\"竞技\", \"jingji\"], [\"科幻\", \"kehuan\"], [\"灵异\", \"lingyi\"], [\"同人\", \"tongren\"],\n    [\"女生\", \"nvsheng\"], [\"其他\", \"qita\"]\n  ].forEach(function (c) { I(c[0], BASE + \"\/sort\/\" + c[1] + \"\/{{page}}.html\"); });\n\n  H(\"🏆 榜单\");\n  I(\"人气排行\", BASE + \"\/top\/\");\n  I(\"全本小说\", BASE + \"\/over\/{{page}}.html\");\n\n  return JSON.stringify(R);\n}\n\nasync function find(url, page) {\n  var u = String(url || \"\").replace(\/\\{\\{page\\}\\}\/g, String(page || 1));\n  if (!u) return \"[]\";\n  if (!\/^https?:\/i.test(u)) u = abs(u);\n\n  var html = await fetchHtml(u);\n  if (!html) return \"[]\";\n  return JSON.stringify(parseBookList(html));\n}\n\n\/* ==========================================================================\n * 6. 其它可选函数\n * ========================================================================== *\/\nasync function filter() { return \"\"; }\nasync function findfilter(url) { return \"\"; }\nasync function getloginurl() { return BASE + \"\/search\/\"; }\nasync function login() {}\nasync function pay(bookurl, url) {}\nasync function imagedecrypt(url, image) { return []; }\nasync function shouldOverrideUrlLoading(url) { return true; }\n\nasync function gethelp() {\n  return \"@html:<p><b>斗破苍穹小说网<\/b>(m.xdoupocangqiong.org)<\/p>\"\n    + \"<p>书源:搜索 \/ 详情 \/ 目录 \/ 正文 \/ 分类发现 全部可用。<\/p>\"\n    + \"<p>站点使用 HTTP 明文访问,请确保网络环境未强制 HTTPS。<\/p>\"\n    + \"<p>目录共 39 页 × 50 章,首次加载目录需要约几秒。<\/p>\";\n}\n\nasync function callbackjs(event, book) { return false; }\n<\/script>\n<\/html>\n",
    "login": false,
    "customButton": false,
    "eventListener": false,
    "lastUpdateTime": "1789879737040"
}
广告