福书网
https://www.fushucun.com/
autobcb_admin (12020)05/08 05:20
该用户很懒,什么介绍也没有写!
{
"bookSourceUrl": "https:\/\/www.fushucun.com\/",
"bookSourceName": "福书网",
"enabledExplore": true,
"enabled": true,
"bookSourceGroup": "",
"author": "trae",
"help": false,
"html": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\">\n <title>福书网<\/title>\n<\/head>\n<body>\n\n<\/body>\n<script src=\"https:\/\/vc.jd.com\/web\/js\/jquery-3.1.1.min.js\"><\/script>\n<script>\n var isCookieJar=true;\n class FlutterJSBridge {\n constructor() {\n this.init();\n }\n\n init() {\n if (window.flutter_inappwebview) {\n this.isReady = true;\n this.CookieJar();\n } else {\n window.addEventListener('flutterInAppWebViewPlatformReady', () => {\n this.isReady = true;\n console.log('JSBridge初始化完成');\n this.CookieJar();\n });\n }\n }\n\n async CookieJar() {\n try {\n await window.flutter_inappwebview.callHandler('CookieJar', isCookieJar);\n } catch (error) {\n console.error('汇报完成准备失败:', error);\n }\n }\n\n async getbuildNumber() {\n try {\n return await window.flutter_inappwebview.callHandler('buildNumber');\n } catch (error) {\n return 0;\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 async htmlToText(str) {\n try {\n return await window.flutter_inappwebview.callHandler('htmlToText',str);\n } catch (error) {\n return \"\";\n }\n }\n \n async toTraditional(str) {\n try {\n return await window.flutter_inappwebview.callHandler('toTraditional',str);\n } catch (error) {\n return \"\";\n }\n }\n \n async toSimplified(str) {\n try {\n return await window.flutter_inappwebview.callHandler('toSimplified',str);\n } catch (error) {\n return \"\";\n }\n }\n\n async voice() {\n try {\n return await window.flutter_inappwebview.callHandler('voice');\n } catch (error) {\n return \"\";\n }\n }\n \n async getDeviceid() {\n try {\n return await window.flutter_inappwebview.callHandler('id');\n } catch (error) {\n return \"\";\n }\n }\n\n async getDevice() {\n try {\n return await window.flutter_inappwebview.callHandler('device');\n } catch (error) {\n return \"\";\n }\n }\n \n async getLoginUser() {\n try {\n return await window.flutter_inappwebview.callHandler('getLoginUser');\n } catch (error) {\n return \"\";\n }\n }\n\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 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 async showToast(str) {\n try {\n return await window.flutter_inappwebview.callHandler('showToast',str);\n } catch (error) {\n return false;\n }\n }\n \n async showLongToast(str) {\n try {\n return await window.flutter_inappwebview.callHandler('showLongToast',str);\n } catch (error) {\n return false;\n }\n }\n\n async getWebViewUA() {\n try {\n return await window.flutter_inappwebview.callHandler('getWebViewUA');\n } catch (error) {\n return \"\";\n }\n }\n\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 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 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 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 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 async webViewGetAjax(url,html,body,header,ajaxregex) {\n try {\n return await window.flutter_inappwebview.callHandler('webviewajax',url,html,body,header,ajaxregex);\n } catch (error) {\n return \"\";\n }\n }\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 async startBrowserWithShouldOverrideUrlLoading(url,title,header) {\n try {\n return await window.flutter_inappwebview.callHandler('startBrowserWithShouldOverrideUrlLoading',url,title,header);\n } catch (error) {\n return \"\";\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 async back() {\n try {\n return await window.flutter_inappwebview.callHandler('back');\n } catch (error) {\n return false;\n }\n }\n\n async utf8ToGbkUrlEncoded(str) {\n try {\n return await window.flutter_inappwebview.callHandler('utf8ToGbkUrlEncoded',str);\n } catch (error) {\n return \"\";\n }\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 async addbook(bookUrl) {\n try {\n return await window.flutter_inappwebview.callHandler('addbook',bookUrl);\n } catch (error) {\n return \"\";\n }\n }\n \n \n async getdurChapterIndex(bookUrl) {\n try {\n return await window.flutter_inappwebview.callHandler('getdurChapterIndex',bookUrl);\n } catch (error) {\n return 0;\n }\n }\n \n async base64encode(str) {\n try {\n return await window.flutter_inappwebview.callHandler('base64encode',str);\n } catch (error) {\n return \"\";\n }\n }\n \n async base64decode(str) {\n try {\n return await window.flutter_inappwebview.callHandler('base64decode',str);\n } catch (error) {\n return \"\";\n }\n }\n \n\n }\n\n class Http {\n constructor() {\n this.open = false;\n this.requestTimestamps = [];\n this.rateLimit = 5;\n this.rateLimitWindow = 1000;\n }\n\n async checkRateLimit() {\n if(!this.open) return;\n const now = Date.now();\n this.requestTimestamps = this.requestTimestamps.filter(timestamp => now - timestamp < this.rateLimitWindow);\n if (this.requestTimestamps.length >= this.rateLimit) {\n const oldestTimestamp = this.requestTimestamps[0];\n const waitTime = this.rateLimitWindow - (now - oldestTimestamp);\n await new Promise(resolve => setTimeout(resolve, waitTime));\n return this.checkRateLimit();\n }\n this.requestTimestamps.push(now);\n }\n\n async Get(url,headers,followRedirects) {\n try {\n await this.checkRateLimit();\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 await this.checkRateLimit();\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 await this.checkRateLimit();\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 async getLoginInfo(){\n return await this.get(\"LoginInfo\")\n }\n\n async putLoginInfo(info){\n return await this.set(\"LoginInfo\",info)\n }\n \n async getbookVariable(bookurl){\n return await this.get(bookurl)\n }\n \n async setbookVariable(bookurl,value){\n return await this.set(bookurl,value)\n }\n }\n\n class Cookie {\n constructor() {}\n\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 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 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 async setCookie(url,key,value) {\n try {\n return await window.flutter_inappwebview.callHandler('cookie.setcookie',url,key,value);\n } catch (error) {\n return null;\n }\n }\n\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 function parseHTMLSafely(htmlStr) {\n try {\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 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 function removeHTMLTags(htmlString) {\n let result = htmlString.replace(\/<script\\b[^<]*(?:(?!<\\\/script>)<[^<]*)*<\\\/script>\/gi, '');\n result = result.replace(\/<style\\b[^<]*(?:(?!<\\\/style>)<[^<]*)*<\\\/style>\/gi, '');\n return result;\n }\n\n<\/script>\n\n<script>\n const flutterBridge = new FlutterJSBridge();\n const cache = new Cache();\n const http = new Http();\n const cookie = new Cookie();\n var baseurl=\"https:\/\/www.fushucun.com\"\n var header={\n \"User-Agent\": \"Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/142.0.0.0 Safari\/537.36\",\n \"Referer\": baseurl,\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,en;q=0.8\"\n };\n\n \/\/ 智能容器定位工具函数\n function findBestContainer($htmlContainer, linkMatcher, minMatchCount) {\n var $bestContainer = null;\n var maxMatchCount = 0;\n var checkedParents = new Set();\n \n $htmlContainer.find(\"a\").each(function() {\n var href = $(this).attr('href');\n \n if (!linkMatcher(href)) {\n return true;\n }\n \n var $parent = $(this).parent();\n var depth = 0;\n \n while ($parent.length > 0 && depth < 10) {\n var parentKey = $parent[0].outerHTML.substring(0, Math.min(500, $parent[0].outerHTML.length));\n \n if (!checkedParents.has(parentKey)) {\n checkedParents.add(parentKey);\n \n var count = $parent.find(\"a\").filter(function() {\n return linkMatcher($(this).attr('href'));\n }).length;\n \n if (count > maxMatchCount && count >= minMatchCount) {\n maxMatchCount = count;\n $bestContainer = $parent;\n }\n }\n \n $parent = $parent.parent();\n depth++;\n }\n });\n \n return $bestContainer;\n }\n\n function normalizeUrl(url) {\n if (!url) return \"\";\n if (url.startsWith('http')) return url;\n if (url.startsWith('\/')) return baseurl + url;\n return baseurl + '\/' + url;\n }\n\n \/*\n * search(key, page) - 搜索功能\n * 福书网使用帝国CMS的POST搜索接口\n * 接口: \/e\/search\/index.php (POST)\n * 参数: keyboard, show, classid\n *\/\n async function search(key, page) {\n if(page > 1){\n return \"[]\";\n }\n\n var searchUrl = baseurl + \"\/e\/search\/index.php\";\n var body = \"keyboard=\" + await flutterBridge.utf8ToGbkUrlEncoded(key) + \"&show=title&tempid=1&classid=2,6&x=0&y=0\";\n\n flutterBridge.log(\"搜索URL: \" + searchUrl);\n flutterBridge.log(\"搜索参数: \" + body);\n\n \/\/ 使用 http.Post 发送搜索请求 (302重定向到结果页)\n \/\/ Header 必须与浏览器抓包一致\n var postHeader = {\n \"Connection\": \"keep-alive\",\n \"sec-ch-ua\": '\"Chromium\";v=\"147\", \"Not:A-Brand\";v=\"8\", \"Google Chrome\";v=\"147\"',\n \"sec-ch-ua-mobile\": \"?0\",\n \"sec-ch-ua-platform\": '\"macOS\"',\n \"Upgrade-Insecure-Requests\": \"1\",\n \"User-Agent\": \"Mozilla\/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit\/537.36 (KHTML, like Gecko) Chrome\/147.0.0.0 Safari\/537.36\",\n \"Origin\": baseurl,\n \"Accept\": \"text\/html,application\/xhtml+xml,application\/xml;q=0.9,image\/avif,image\/webp,*\/*;q=0.8\",\n \"Sec-Fetch-Site\": \"same-origin\",\n \"Sec-Fetch-Mode\": \"navigate\",\n \"Sec-Fetch-User\": \"?1\",\n \"Sec-Fetch-Dest\": \"document\",\n \"Referer\": baseurl,\n \"Cache-Control\":\"no-cache\",\n };\n var get = await http.Post(searchUrl, postHeader, body, \"application\/x-www-form-urlencoded\", true);\n \n \/\/ 获取返回的数据\n var htmlContent = get.data || \"\";\n\n\n if(!htmlContent){\n flutterBridge.showToast(\"搜索请求失败\");\n return \"[]\";\n }\n\n flutterBridge.text(0, htmlContent);\n\n var books = [];\n var processedUrls = new Set();\n var $tempContainer = parseHTMLSafely(removeHTMLTags(htmlContent));\n\n \/\/ 智能定位搜索结果容器 (article-list)\n var linkMatcher = function(href) {\n if (!href) return false;\n \/\/ 福书网的书籍链接格式: \/book\/数字.html\n return \/\\\/book\\\/\\d+\\.html\/.test(href);\n };\n \n var $resultContainer = findBestContainer($tempContainer, linkMatcher, 1);\n\n if (!$resultContainer) {\n $resultContainer = $tempContainer;\n }\n\n \/\/ 提取书籍信息\n $resultContainer.find(\"a\").each(function() {\n try{\n var bookUrl = $(this).attr('href');\n \n if (!bookUrl || processedUrls.has(bookUrl)) {\n return true;\n }\n \n if (!linkMatcher(bookUrl)) {\n return true;\n }\n \n processedUrls.add(bookUrl);\n \n \/\/ 提取文本,格式: 书名——作者\n var fullText = $(this).text().trim();\n \n \/\/ 解析书名、作者\n var bookName = fullText;\n var author = \"\";\n var kind = \"\";\n \n \/\/ 提取作者(——后面)\n var authorMatch = fullText.match(\/——(.+)$\/);\n if(authorMatch) {\n author = authorMatch[1].trim();\n bookName = fullText.substring(0, fullText.indexOf('——')).trim();\n }\n \n if(bookName && bookName.length > 1){\n bookUrl = normalizeUrl(bookUrl);\n\n var book={\n \"bookUrl\": bookUrl,\n \"name\": bookName,\n \"author\": author,\n \"kind\": kind,\n \"coverUrl\": \"\",\n \"intro\": \"\",\n \"tocUrl\": bookUrl,\n \"wordCount\": \"\",\n \"type\": 0,\n \"latestChapterTitle\": \"\"\n };\n books.push(book);\n }\n }catch(e){\n flutterBridge.log(\"解析书籍信息出错: \" + e.message);\n }\n });\n\n removeHTMLSafely($tempContainer);\n flutterBridge.log(\"找到 \" + books.length + \" 本书\");\n return JSON.stringify(books);\n }\n\n \/*\n * info(bookurl) - 书籍详情\n * 访问书籍页面提取基本信息\n *\/\n async function info(bookurl) {\n var mheader={\n ...header,\n \"Referer\": baseurl\n };\n\n var htmlContent = await flutterBridge.webview(bookurl, \"\", \"\", \"\", JSON.stringify(mheader));\n\n if(!htmlContent){\n flutterBridge.showToast(\"获取信息失败\");\n return JSON.stringify({});\n }\n\n flutterBridge.text(1, htmlContent);\n\n var $tempContainer = parseHTMLSafely(removeHTMLTags(htmlContent));\n\n \/\/ 从h1提取标题,格式: 书名(分类)——作者\n var h1Text = $tempContainer.find(\"h1\").first().text().trim();\n \n var name = \"\";\n var author = \"\";\n var kind = \"\";\n var intro = \"\";\n var latestChapterTitle = \"\";\n\n \/\/ 解析h1文本\n if(h1Text) {\n name = h1Text;\n \n \/\/ 提取分类\n var categoryMatch = h1Text.match(\/(([^)]+))\/);\n if(categoryMatch) {\n kind = categoryMatch[1];\n name = h1Text.substring(0, h1Text.indexOf('(')).trim();\n }\n \n \/\/ 提取作者\n var authorMatch = h1Text.match(\/——(.+)$\/);\n if(authorMatch) {\n author = authorMatch[1].trim();\n }\n }\n\n \/\/ 尝试从其他位置找作者\n if(!author) {\n $tempContainer.find(\"*\").each(function() {\n var text = $(this).text().trim();\n if(text.indexOf(\"作者\") !== -1 && !author) {\n var match = text.match(\/作者[::\\s]*(.+?)(?:$|[\\r\\n])\/);\n if(match) {\n author = match[1].trim();\n }\n }\n });\n }\n\n \/\/ 提取简介(查找\"文案:\"后面的内容,限制长度)\n var foundIntro = false;\n $tempContainer.find(\"*\").each(function() {\n if(foundIntro) return false;\n \n var $elem = $(this);\n var text = $elem.text().trim();\n \n \/\/ 查找包含\"文案\"的元素\n if(text.indexOf(\"文案\") !== -1 && text.indexOf(\"文案\") < 100) {\n var idx = text.indexOf(\"文案\");\n var potentialIntro = text.substring(idx + 2).replace(\/[::]\\s*\/, \"\").trim();\n \n \/\/ 简介应该在200字以内\n if(potentialIntro.length > 10 && potentialIntro.length < 500) {\n intro = potentialIntro;\n foundIntro = true;\n return false;\n }\n \n \/\/ 如果太长,可能是包含了正文,截取前面部分\n if(potentialIntro.length >= 500) {\n \/\/ 找到第一个句号或换行处截断\n var cutIdx = potentialIntro.indexOf('。');\n if(cutIdx > 20 && cutIdx < 300) {\n intro = potentialIntro.substring(0, cutIdx + 1).trim();\n foundIntro = true;\n return false;\n }\n }\n }\n });\n \n \/\/ 如果还没找到,尝试从特定位置提取(h1后面第一个大段文本)\n if(!intro && !foundIntro) {\n var h1Elem = $tempContainer.find(\"h1\").first();\n if(h1Elem.length > 0) {\n \/\/ 获取h1后面的兄弟元素或父元素的下一个子元素\n var nextElements = h1Elem.parent().find(\"p, div\").slice(0, 5);\n nextElements.each(function() {\n if(intro) return false;\n \n var text = $(this).text().trim();\n \/\/ 排除导航、按钮等\n if(text.length > 30 && text.length < 500 &&\n text.indexOf(\"首页\") === -1 &&\n text.indexOf(\"返回\") === -1 &&\n text.indexOf(\"加入收藏\") === -1 &&\n text.indexOf(\"来顶一下\") === -1) {\n intro = text;\n }\n });\n }\n }\n\n \/\/ 提取最新章节(从下拉菜单或分页信息)\n var pageInfo = $tempContainer.text();\n var pageMatch = pageInfo.match(\/(\\d+)\\s*\\\/\\s*(\\d+)\/);\n if(pageMatch) {\n latestChapterTitle = \"第\" + pageMatch[2] + \"章\";\n }\n\n var book={\n \"bookUrl\": bookurl,\n \"name\": name,\n \"author\": author,\n \"kind\": kind,\n \"coverUrl\": \"\",\n \"intro\": intro,\n \"tocUrl\": bookurl,\n \"wordCount\": \"\",\n \"type\": 0,\n \"latestChapterTitle\": latestChapterTitle\n };\n\n removeHTMLSafely($tempContainer);\n return JSON.stringify(book);\n }\n\n \/*\n * chapter(tocUrl) - 章节列表\n * 从下拉菜单或分页链接中提取章节\n *\/\n async function chapter(tocUrl) {\n var mheader={\n ...header,\n \"Referer\": baseurl\n };\n\n var htmlContent = await flutterBridge.webview(tocUrl, \"\", \"\", \"\", JSON.stringify(mheader));\n\n if(!htmlContent){\n flutterBridge.showToast(\"获取目录失败\");\n return \"[]\";\n }\n\n flutterBridge.text(2, htmlContent);\n\n var chapters = [];\n var $tempContainer = parseHTMLSafely(removeHTMLTags(htmlContent));\n\n \/\/ 方法1: 从下拉菜单提取章节\n var totalChapters = 0;\n \n \/\/ 查找分页信息获取总章节数\n var pageInfo = $tempContainer.text();\n var pageMatch = pageInfo.match(\/(\\d+)\\s*\\\/\\s*(\\d+)\/);\n if(pageMatch) {\n totalChapters = parseInt(pageMatch[2]);\n }\n\n \/\/ 如果找到了总章节数,生成章节列表\n if(totalChapters > 0) {\n for(var i = 1; i <= totalChapters; i++) {\n chapters.push({\n \"name\": \"第\" + i + \"章\",\n \"chapterId\": tocUrl + \"?\" + i,\n \"index\": i - 1,\n \"isPay\": false,\n \"isVip\": false,\n \"isVolume\": false,\n \"tag\": \"\"\n });\n }\n } else {\n \/\/ 方法2: 从下拉菜单选项提取\n $tempContainer.find(\"select option\").each(function(index) {\n if(index === 0) return; \/\/ 跳过第一个(通常是\"请选择\")\n \n var chapterName = $(this).text().trim();\n var chapterUrl = $(this).val() || (tocUrl + \"?\" + index);\n \n if(chapterName) {\n chapters.push({\n \"name\": chapterName,\n \"chapterId\": normalizeUrl(chapterUrl),\n \"index\": index - 1,\n \"isPay\": false,\n \"isVip\": false,\n \"isVolume\": false,\n \"tag\": \"\"\n });\n }\n });\n }\n\n removeHTMLSafely($tempContainer);\n flutterBridge.log(\"找到 \" + chapters.length + \" 个章节\");\n return JSON.stringify(chapters);\n }\n\n \/*\n * content(url) - 章节内容\n * 访问 ?N 参数获取对应章节内容\n *\/\n async function content(url) {\n var mheader={\n ...header,\n \"Referer\": baseurl\n };\n\n var htmlContent = await flutterBridge.webview(url, \"\", \"\", \"\", JSON.stringify(mheader));\n\n if(!htmlContent){\n flutterBridge.showToast(\"获取内容失败\");\n return \"\";\n }\n\n flutterBridge.text(3, htmlContent);\n\n var $tempContainer = parseHTMLSafely(removeHTMLTags(htmlContent));\n var contenttxt = \"\";\n\n \/\/ 查找正文内容区域\n \/\/ 福书网页面结构:正文在 <main> 或 <td class=\"main\"> 容器内\n \n \/\/ 方法1: 直接定位 main 容器\n var $mainContainer = $tempContainer.find(\".main\").first();\n \n if($mainContainer.length > 0) {\n \/\/ 从 main 中提取文本\n var mainText = $mainContainer.text().trim();\n \n if(mainText) {\n contenttxt = mainText;\n \n \/\/ 清理内容\n \/\/ 移除网站宣传语(包括后面的所有内容)\n contenttxt = contenttxt.replace(\/最最最全的耽美文库.*$\/s, \"\");\n contenttxt = contenttxt.replace(\/一秒钟记住我们的网址.*$\/s, \"\");\n contenttxt = contenttxt.replace(\/www\\.fushucun\\.com.*$\/s, \"\");\n \n \/\/ 移除元数据标签行\n contenttxt = contenttxt.replace(\/^(作品来源|作者|时间|文案)[^\\n]*\\n?\/gm, \"\");\n contenttxt = contenttxt.replace(\/^.*(主角|配角|其它|一句话简介|立意)[::][^\\n]*$\/gm, \"\");\n \n \/\/ 移除多余的空白行\n contenttxt = contenttxt.replace(\/\\n{3,}\/g, \"\\n\\n\");\n contenttxt = contenttxt.trim();\n \n \/\/ 限制最大长度\n if(contenttxt.length > 20000) {\n contenttxt = contenttxt.substring(0, 20000);\n }\n }\n }\n\n \/\/ 兜底方案\n if(!contenttxt || contenttxt.length < 50){\n flutterBridge.showToast(\"获取内容失败\");\n return \"\";\n }\n\n removeHTMLSafely($tempContainer);\n\n if(!contenttxt || contenttxt.length < 50){\n flutterBridge.showToast(\"获取内容失败\");\n return \"\";\n }\n\n return contenttxt;\n }\n\n \/*\n * getfinds() - 发现\/分类\n * 返回福书网的分类列表\n *\/\n async function getfinds() {\n var result = [];\n var push = (title, url, type) => result.push({\n title: title,\n url: url,\n type: type || 0\n });\n\n push(\"穿越重生\", \"\/chuanyuechongsheng\/\", 0);\n push(\"古代架空\", \"\/gudaixiaoshuo\/\", 0);\n push(\"GL百合\", \"\/baihexiaoshuo\/\", 0);\n push(\"玄幻网游\", \"\/xuanhuanwangyou\/\", 0);\n push(\"现代都市\", \"\/xiandaixiaoshuo\/\", 0);\n push(\"同人BL\", \"\/tongrenxiaoshuo\/\", 0);\n push(\"本站推荐\", \"\/best\/\", 0);\n\n return JSON.stringify(result);\n }\n\n \/*\n * find(url, page) - 分类浏览\n * 浏览某个分类的书籍列表\n *\/\n async function find(url, page) {\n var u = url.replaceAll(\"{{page}}\", page);\n if(page > 1) {\n u = u.replace(\/\\\/$\/, \"\") + \"\/index_\" + page + \".html\";\n }\n if(!u.startsWith('http')){\n u = baseurl + u;\n }\n\n flutterBridge.log(\"分类URL: \" + u);\n\n var mheader={\n ...header,\n \"Referer\": baseurl\n };\n\n var htmlContent = await flutterBridge.webview(u, \"\", \"\", \"\", JSON.stringify(mheader));\n\n if(!htmlContent){\n flutterBridge.showToast(\"获取分类失败\");\n return \"[]\";\n }\n\n flutterBridge.text(0, htmlContent);\n\n var books = [];\n var processedUrls = new Set();\n var $tempContainer = parseHTMLSafely(removeHTMLTags(htmlContent));\n\n \/\/ 智能定位书籍列表容器\n var linkMatcher = function(href) {\n if (!href) return false;\n \/\/ 福书网的书籍链接格式: \/book\/数字.html 或 \/年份\/数字.html\n return \/\\\/book\\\/\\d+\\.html\/.test(href) || \/\\d{4}\\\/\\d+\\.html\/.test(href);\n };\n \n var $resultContainer = findBestContainer($tempContainer, linkMatcher, 1);\n\n if (!$resultContainer) {\n $resultContainer = $tempContainer;\n }\n\n \/\/ 提取书籍(复用搜索逻辑)\n $resultContainer.find(\"a\").each(function() {\n try{\n var bookUrl = $(this).attr('href');\n \n if (!bookUrl || processedUrls.has(bookUrl)) {\n return true;\n }\n \n if (!linkMatcher(bookUrl)) {\n return true;\n }\n \n processedUrls.add(bookUrl);\n \n var fullText = $(this).text().trim();\n var bookName = fullText;\n var author = \"\";\n var kind = \"\";\n \n var categoryMatch = fullText.match(\/(([^)]+))\/);\n if(categoryMatch) {\n kind = categoryMatch[1];\n bookName = fullText.substring(0, fullText.indexOf('(')).trim();\n }\n \n var authorMatch = fullText.match(\/——(.+?)(?:\\s|$)\/);\n if(authorMatch) {\n author = authorMatch[1].trim();\n }\n \n var dateIndex = bookName.lastIndexOf(\"20\");\n if(dateIndex > 10 && dateIndex < bookName.length - 4) {\n bookName = bookName.substring(0, dateIndex).trim();\n }\n \n if(bookName && bookName.length > 1){\n bookUrl = normalizeUrl(bookUrl);\n\n var book={\n \"bookUrl\": bookUrl,\n \"name\": bookName,\n \"author\": author,\n \"kind\": kind,\n \"coverUrl\": \"\",\n \"intro\": \"\",\n \"tocUrl\": bookUrl,\n \"wordCount\": \"\",\n \"type\": 0,\n \"latestChapterTitle\": \"\"\n };\n books.push(book);\n }\n }catch(e){\n flutterBridge.log(\"解析书籍信息出错: \" + e.message);\n }\n });\n\n removeHTMLSafely($tempContainer);\n flutterBridge.log(\"找到 \" + books.length + \" 本书\");\n return JSON.stringify(books);\n }\n\n async function getloginurl(){\n return baseurl;\n }\n\n<\/script>\n<\/html>\n",
"login": true,
"lastUpdateTime": "1778188807032"
}