斗破苍穹小说网
http://m.xdoupocangqiong.org/
Simwoodone (12535)09/20 12:48
该用户很懒,什么介绍也没有写!
{
"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"
}