🍅番茄小说纯阅版
http://23.141.172.241:9990
autobcb_admin (12020)03/01 00:50
番茄小说
{
"bookSourceUrl": "http:\/\/23.141.172.241:9990",
"bookSourceName": "🍅番茄小说纯阅版",
"enabledExplore": true,
"enabled": true,
"bookSourceGroup": "🍅番茄",
"author": "清词",
"help": false,
"html": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <title>🍅番茄小说纯阅版<\/title>\n<\/head>\n<body>\n\n<\/body>\n<!-- 没用到jq请去掉-->\n<script src=\"https:\/\/vc.jd.com\/web\/js\/jquery-3.1.1.min.js\"><\/script>\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 \/\/将简体字转成繁体字\n async toTraditional(str) {\n try {\n return await window.flutter_inappwebview.callHandler('toTraditional',str);\n } catch (error) {\n return \"\";\n }\n }\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 \/\/播放朗读引擎仅tts源生效\n async voice() {\n try {\n return await window.flutter_inappwebview.callHandler('voice');\n } catch (error) {\n return \"\";\n }\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 \/\/获取轻悦时光登录用户名,没登录返回为空\n async getLoginUser() {\n try {\n return await window.flutter_inappwebview.callHandler('getLoginUser');\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 \/\/webview 里禁止使用,webview请使用js获取ua (navigator.userAgent)\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 * 使用webView拦截 ajax\n * ajaxregex 为正则表达式,通过 ajax 匹配 path\n * 匹配成功返回 ajax 的结果 失败返回 html\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\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 * 启动前台 webview 并对每次打开的 url 进行拦截\n * @param url 网址\n * @param title 标题\n * @param header 请求的header头,此参数必须是json字符串\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 \/\/专门为段评设置的半屏显示,不返回任何东西\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 \/\/提交内容bookUrl,我会调用书源 info 函数来获取这本书的信息\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 \/\/获取书本当前阅读章节index\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 \/\/utf8 字符串转base64\n async base64encode(str) {\n try {\n return await window.flutter_inappwebview.callHandler('base64encode',str);\n } catch (error) {\n return \"\";\n }\n }\n \n \/\/base64 转utf8字符串\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\n \/\/webview下isCookieJar必定true 会自动处理cookie\n \/\/以下提交的url,headers,body 都必须为字符串,headers必须为json字符串\n \/\/当followRedirects 为 false 时不处理重定向,当为 true 时会自动处理重定向 ,如不明白用途直接用 true 最佳\n \/\/ 以下所有参数除当followRedirects外均为 String\n \/\/ 如果需要使用http2协议 请在url 前添加 http2:\/\/ ,例如 http2:\/\/baidu.com\n \/\/ 如果https一直被盾拦截 ,可以使用https2协议\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 \/\/设置单独一个cookie\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 \/\/通过 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\n<script>\n const flutterBridge = new FlutterJSBridge();\n const cache = new Cache();\n const http = new Http();\n const cookie = new Cookie();\n \n var header = {\n \"User-Agent\": \"Mozilla\/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit\/537.36\",\n \"X-Novel-Token\": \"QING_READ_2025\"\n };\n \n var baseUrl = \"http:\/\/38.76.199.231:3000\";\n var gender = \"boy\";\n \n async function init() {\n await flutterBridge.CookieJar();\n }\n init();\n \n async function getSessionId() {\n try {\n const sessionCookie = await cookie.getCookie(\"https:\/\/fanqienovel.com\", \"sessionid\");\n return sessionCookie || \"\";\n } catch (e) {\n return \"\";\n }\n }\n \n function replaceCover(u) {\n if (!u) return \"\";\n if (u.startsWith(\"https:\/\/\")) u = u.substring(8);\n else if (u.startsWith(\"http:\/\/\")) u = u.substring(7);\n let uArr = u.split(\"\/\");\n uArr[0] = \"https:\/\/p6-novel.byteimg.com\/origin\";\n let uArr2 = [];\n uArr.forEach((x) => {\n if (!x.includes(\"?\") && !x.includes(\"~\")) uArr2.push(x);\n else uArr2.push(x.split(\"~\")[0]);\n });\n return uArr2.join(\"\/\");\n }\n \n \/\/ ========== 搜索函数 ==========\n async function search(key, page) {\n try {\n let searchKey = key.trim();\n \n if (\/^\\d{19}$\/.test(searchKey)) {\n const detailUrl = `https:\/\/api5-normal-sinfonlineb.fqnovel.com\/reading\/bookapi\/multi-detail\/v\/?aid=1967&iid=1&version_code=999&book_id=${searchKey}`;\n const get = await http.Get(detailUrl, header, true);\n const response = JSON.parse(get.data);\n const books = [];\n \n if (response.data) {\n const data = Array.isArray(response.data) ? response.data[0] : response.data;\n books.push(await processBookData(data));\n }\n return JSON.stringify(books);\n }\n \n const searchUrl = `https:\/\/novel.snssdk.com\/api\/novel\/channel\/homepage\/search\/search\/v1\/?q=${encodeURIComponent(searchKey)}&offset=${(page-1)*10}&enterfrom_aid&enter_from=inner_search&device_platform=android&aid=1967`;\n const get = await http.Get(searchUrl, header, true);\n const response = JSON.parse(get.data);\n const books = [];\n \n if (response.data && response.data.ret_data) {\n for (let item of response.data.ret_data) {\n if (item.genre === \"0\") {\n books.push(await processBookData(item));\n }\n }\n }\n return JSON.stringify(books);\n } catch (error) {\n return \"[]\";\n }\n}\n \n async function processBookData(data) {\n const bookId = data.book_id || data.series_id;\n const bookUrl = `https:\/\/api5-normal-sinfonlineb.fqnovel.com\/reading\/bookapi\/multi-detail\/v\/?aid=1967&iid=1&version_code=999&book_id=${bookId}`;\n \n return {\n bookUrl: bookUrl,\n name: data.book_name || data.title || \"未知书名\",\n author: data.author || \"未知作者\",\n kind: formatKindText(data),\n coverUrl: replaceCover(data.thumb_url || data.cover || \"\"),\n intro: data.abstract || data.book_abstract_v2 || \"暂无简介\",\n tocUrl: \"\",\n wordCount: data.word_number || \"\",\n type: 0,\n latestChapterTitle: data.last_chapter_title || \"\"\n };\n }\n \n function formatKindText(data) {\n let result = [];\n if (data.gender === 0) result.push(\"女生\");\n else if (data.gender === 1) result.push(\"男生\");\n else if (data.gender === 2) result.push(\"出版\");\n \n if (data.category) result.push(data.category);\n if (data.sub_title) result.push(data.sub_title);\n \n if (data.creation_status !== undefined) {\n const statusText = data.creation_status === 0 ? \"连载\" : \n data.creation_status === 1 ? \"完结\" :\n data.creation_status === 4 ? \"断更\" : \"未知\";\n result.push(statusText);\n }\n \n if (data.score) result.push(`${data.score}分`);\n \n if (data.isbn) {\n return result.join(\",\").replace(\/男生|女生\/, \"出版\");\n }\n \n return result.join(\",\");\n}\n \n \/\/ ========== 获取书籍详情 ==========\n async function info(bookurl) {\n try {\n const bookIdMatch = bookurl.match(\/book_id=(\\d{19})\/);\n if (!bookIdMatch) {\n return JSON.stringify({\n bookUrl: bookurl,\n name: \"未知书名\",\n author: \"未知作者\",\n kind: \"\",\n coverUrl: \"\",\n intro: \"获取详情失败\",\n tocUrl: bookurl,\n wordCount: \"\",\n type: 0,\n latestChapterTitle: \"\"\n });\n }\n \n const apiUrl = `http:\/\/38.76.199.231:7777\/detail?book_id=${bookIdMatch[1]}`;\n const apiResponse = await http.Get(apiUrl, header, true);\n const apiData = JSON.parse(apiResponse.data);\n const data = Array.isArray(apiData.data) ? apiData.data[0] : apiData.data;\n \n if (!data) {\n return JSON.stringify({\n bookUrl: bookurl,\n name: \"未知书名\",\n author: \"未知作者\",\n kind: \"\",\n coverUrl: \"\",\n intro: \"获取详情失败\",\n tocUrl: bookurl,\n wordCount: \"\",\n type: 0,\n latestChapterTitle: \"\"\n });\n }\n \n let intro = \"\\n\";\n intro += `📕 源名:${data.original_book_name || data.book_name || \"未知\"}\\n`;\n if (data.book_flight_alias_name) intro += `📖 别名:${data.book_flight_alias_name}\\n`;\n if (data.create_time) {\n const datePart = data.create_time.split('T')[0];\n if (datePart) intro += `✏️ 开坑:${datePart}\\n`;\n }\n if (data.tags) intro += `🏷️ 标签:${data.tags}\\n`;\n \n let authorName = data.author || \"未知作者\";\n if (data.roles) {\n try {\n const roles = typeof data.roles === 'string' ? JSON.parse(data.roles) : data.roles;\n if (Array.isArray(roles) && roles.length > 0) {\n authorName = roles[0].AuthorName || roles[0] || authorName;\n }\n } catch (e) {}\n }\n intro += `👤 主角:${authorName}\\n`;\n if (data.read_count) intro += `👁️ 在线:${data.read_count}人在读\\n`;\n \n const bookStatus = data.book_search_visible == 'true' ? '正常' : (data.tomato_book_status == '3' ? '下架' : '小黑屋');\n intro += `🔗 书籍状态:${bookStatus}\\n\\n`;\n if (data.abstract) intro += `📜 简介:${data.abstract}\\n\\n`;\n if (data.copyright_info) {\n const firstPart = data.copyright_info.split(',')[0];\n if (firstPart) intro += `📍 ${firstPart}。\\n`;\n }\n \n const book = {\n bookUrl: bookurl,\n name: data.book_name || \"未知书名\",\n author: authorName,\n kind: formatBookKind(data),\n coverUrl: replaceCover(data.thumb_url || \"\"),\n intro: intro,\n tocUrl: `https:\/\/fanqienovel.com\/api\/reader\/directory\/detail?bookId=${data.book_id}`,\n wordCount: data.word_number || \"\",\n type: 0,\n latestChapterTitle: data.last_chapter_title || \"\"\n };\n return JSON.stringify(book);\n } catch (error) {\n return \"{}\";\n }\n}\n\nfunction formatBookKind(data) {\n let result = [];\n if (data.gender === 0) result.push(\"女生\");\n else if (data.gender === 1) result.push(\"男生\");\n else if (data.gender === 2) result.push(\"出版\");\n \n if (data.category) result.push(data.category);\n \n if (data.creation_status !== undefined) {\n const statusText = data.creation_status === 0 ? \"连载\" : \n data.creation_status === 1 ? \"完结\" :\n data.creation_status === 4 ? \"断更\" : \"未知\";\n result.push(statusText);\n }\n \n if (data.score) result.push(`${data.score}分`);\n \n if (data.isbn) {\n return result.join(\",\").replace(\/男生|女生\/, \"出版\");\n }\n \n return result.join(\",\");\n}\n \nasync function chapter(tocUrl, bookurl) {\n try {\n const get = await http.Get(tocUrl, header, true);\n const data = JSON.parse(get.data);\n const chapters = [];\n \n if (data.data && data.data.chapterListWithVolume) {\n const chapterList = data.data.chapterListWithVolume;\n let v_Index = [];\n \n for (let volumeIndex = 0; volumeIndex < chapterList.length; volumeIndex++) {\n let volumeChapters = chapterList[volumeIndex];\n if (volumeChapters.length === 0) continue;\n \n let volumeName = volumeChapters[0].volume_name;\n v_Index.push(chapters.length);\n \n chapters.push({\n name: volumeName,\n chapterId: \"\",\n index: chapters.length,\n isPay: false,\n isVip: false,\n isVolume: true,\n tag: \"\"\n });\n \n for (let chapterItem of volumeChapters) {\n let timestamp = parseInt(chapterItem.firstPassTime) * 1000;\n let chapterTime = new Date(timestamp).toLocaleString();\n let chapterInfo = [volumeName, chapterTime].join(\" | \").replace(\/第.卷:默认 \\|\/g, \"\").trim();\n \n let chapterId = \"\";\n if (chapterItem.itemId) {\n chapterId = \"data:chapter;base64,\" + btoa(unescape(encodeURIComponent(JSON.stringify({\n item_id: chapterItem.itemId,\n title: chapterItem.title || \"未知章节\"\n }))));\n }\n \n chapters.push({\n name: chapterItem.title || \"未知章节\",\n chapterId: chapterId,\n index: chapters.length,\n isPay: false,\n isVip: false,\n isVolume: false,\n tag: chapterInfo\n });\n }\n }\n \n let len = v_Index.length;\n if (len < 2) {\n for (; len > 0; len--) {\n chapters.splice(v_Index[len - 1], 1);\n }\n } else {\n for (let i = 0; i < len; i++) {\n let currentVolumeIndex = v_Index[i];\n let nextVolumeIndex = v_Index[i + 1];\n if (nextVolumeIndex === undefined) nextVolumeIndex = chapters.length;\n let chapterCount = nextVolumeIndex - currentVolumeIndex - 1;\n chapters[currentVolumeIndex][\"tag\"] = `共 ${chapterCount} 章`;\n }\n }\n }\n \n return JSON.stringify(chapters);\n } catch (error) {\n return \"[]\";\n }\n}\n\nasync function content(url, bookurl) {\n try {\n if (!url.startsWith('data:chapter;base64,')) {\n return \"章节格式错误\";\n }\n \n const base64Part = url.split('base64,')[1];\n const decodedStr = decodeURIComponent(escape(atob(base64Part)));\n const chapterData = JSON.parse(decodedStr);\n \n if (!chapterData.item_id) {\n return \"章节ID错误\";\n }\n \n let bookId = \"\";\n if (bookurl && bookurl.includes(\"book_id=\")) {\n const bookIdMatch = bookurl.match(\/book_id=(\\d{19})\/);\n if (bookIdMatch) {\n bookId = bookIdMatch[1];\n }\n }\n \n let contentUrl = `${baseUrl}\/content?item_id=${chapterData.item_id}`;\n if (bookId) {\n contentUrl += `&book_id=${bookId}`;\n }\n \n const get = await http.Get(contentUrl, header, true);\n const response = JSON.parse(get.data);\n \n \/\/ 检查是否是限流错误\n if (response && response.error && response.message) {\n return response.message;\n }\n \n if (Array.isArray(response) && response.length > 0) {\n const chapter = response[0];\n \n if (chapter.content) {\n return chapter.content;\n }\n }\n \n return \"内容加载失败\";\n } catch (error) {\n return \"内容加载失败\";\n }\n}\n \n async function getfinds() {\n try {\n const sessionId = await getSessionId();\n const finds = [];\n \n if (sessionId) {\n try {\n const book_shelf_url = 'https:\/\/fanqienovel.com\/reading\/bookapi\/bookshelf\/info\/v:version\/?aid=1967&iid=0&version_code=57700&update_version_code=57700';\n const book_shelf_header = {\n ...header,\n \"Cookie\": `sessionid=${sessionId}`\n };\n const book_shelf_response = await http.Get(book_shelf_url, book_shelf_header, true);\n const book_shelf_info = JSON.parse(book_shelf_response.data);\n \n if (book_shelf_info.code == 0) {\n const userinfo_url = \"https:\/\/fanqienovel.com\/api\/user\/info\/v2\";\n const userinfo_response = await http.Get(userinfo_url, book_shelf_header, true);\n const userinfo = JSON.parse(userinfo_response.data);\n const username = userinfo.data.name;\n \n finds.push({\n title: `${username} 的个人中心`,\n url: \"\",\n type: 0,\n width: 3\n });\n finds.push({\n title: \"我的书架\",\n url: \"https:\/\/fanqienovel.com\/fqbookshelf\",\n type: 0,\n width: 1\n });\n finds.push({\n title: \"个性推荐\",\n url: `${baseUrl}\/read_recommend?session=${sessionId}`,\n type: 0,\n width: 1\n });\n \n let groups_bookids = {};\n book_shelf_info.data.book_shelf_info.forEach(i => {\n let groupName = i.group_name ? i.group_name : \"未分组\";\n if (!groups_bookids[groupName]) groups_bookids[groupName] = [];\n groups_bookids[groupName].push(i.book_id);\n });\n \n for (let k in groups_bookids) {\n if (groups_bookids[k].length > 0) {\n finds.push({\n title: `${k} (${groups_bookids[k].length})`,\n url: `${baseUrl}\/detail?book_id=${groups_bookids[k].join(\",\")}`,\n type: 0,\n width: 0\n });\n }\n }\n \n finds.push({\n title: \"阅读历史\",\n url: `https:\/\/fanqienovel.com\/reading\/bookapi\/read_history\/list\/v\/?aid=1967&app_name=novelapp&channel=0&iid=1&offset={{(page-1)*100}}&os_version=12&version_code=59732`,\n type: 0,\n width: 3\n });\n }\n } catch (e) {}\n }\n \n finds.push({\n title: \"巅峰榜单\",\n url: \"https:\/\/fanqienovel.com\/api\/author\/misc\/top_book_list\/v1\/?limit=100&offset={{(page-1)*100}}\",\n type: 0,\n width: 0\n });\n finds.push({\n title: \"出版榜单\",\n url: \"https:\/\/fanqienovel.com\/api\/node\/publication\/list?page_index={{(page-1)*100}}&page_count=100\",\n type: 0,\n width: 0\n });\n finds.push({\n title: \"爆更榜单\",\n url: \"https:\/\/api-lf.fanqiesdk.com\/api\/novel\/channel\/homepage\/rank\/rank_list\/v2\/?aid=13&limit=50&offset={{(page-1)*100}}&side_type=15&type=1\",\n type: 0,\n width: 0\n });\n finds.push({\n title: \"黑马榜单\",\n url: \"https:\/\/api-lf.fanqiesdk.com\/api\/novel\/channel\/homepage\/rank\/rank_list\/v2\/?aid=13&limit=50&offset={{(page-1)*100}}&side_type=13&type=1\",\n type: 0,\n width: 1\n });\n finds.push({\n title: \"热搜榜单\",\n url: \"https:\/\/api-lf.fanqiesdk.com\/api\/novel\/channel\/homepage\/rank\/rank_list\/v2\/?aid=13&limit=50&offset={{(page-1)*100}}&side_type=12&type=1\",\n type: 0,\n width: 1\n });\n \n let categoryIdsToLoad = gender == \"girl\" ? [0, 2] : [1, 2];\n let categoryNameMap = {\"0\": \"女频\", \"1\": \"男频\", \"2\": \"出版\"};\n \n for (let i of categoryIdsToLoad) {\n try {\n const typeUrl = `${baseUrl}\/type_style?new_category_tab=${i}`;\n const typeResponse = await http.Get(typeUrl, header, true);\n const typeData = JSON.parse(typeResponse.data);\n \n if (typeData.data && typeData.data.category_tab_data) {\n let tabData = typeData.data.category_tab_data;\n let tabName = tabData.tab_name || categoryNameMap[i] || \"未知分类\";\n \n finds.push({\n title: `${tabName}分类`,\n url: \"\",\n type: 0,\n width: 3\n });\n \n if (tabData.cell_data) {\n for (let cell of tabData.cell_data) {\n finds.push({\n title: cell.cell_name,\n url: \"\",\n type: 0,\n width: 3\n });\n \n if (cell.atom_data && Array.isArray(cell.atom_data)) {\n for (let atom of cell.atom_data) {\n if (atom && atom.category_data) {\n let category = atom.category_data;\n if (category && category.name) {\n let cid = category.category_id;\n let genderParam = i;\n let genre_type = i == 2 ? 160 : 0;\n \n finds.push({\n title: category.name,\n url: `${baseUrl}\/type_style?category_id=${cid}&offset={{(page-1)*10}}&gender=${genderParam}&genre_type=${genre_type}&selected_items=`,\n type: 0,\n width: 0.29\n });\n }\n }\n }\n }\n }\n }\n }\n } catch (e) {\n finds.push({\n title: `${categoryNameMap[i]}分类加载失败`,\n url: \"\",\n type: 0,\n width: 3\n });\n }\n }\n \n try {\n const category_url = \"https:\/\/novel.snssdk.com\/api\/novel\/channel\/homepage\/new_category\/page\/data\/v1\/?aid=13\";\n const category_response = await http.Get(category_url, header, true);\n const category_data = JSON.parse(category_response.data).data;\n let boy = category_data.boy_category;\n let girl = category_data.girl_category;\n let publish = category_data.publish_category;\n let categoryData = [[\"男频\",\"gender=1\",boy],[\"女频\",\"gender=0\",girl],[\"出版\",\"genre_type=160\",publish]];\n \n if (gender == \"girl\") {\n let [tit1, genderParam, category] = categoryData[1];\n finds.push({\n title: \"更多分类\",\n url: \"\",\n type: 0,\n width: 3\n });\n for (let cat of category) {\n finds.push({\n title: cat.category_name,\n url: `https:\/\/novel.snssdk.com\/api\/novel\/channel\/homepage\/new_category\/book_list\/v1\/?aid=1967&app_name=news_article&app_version=9.7.3&channel=tengxun_tt&creation_status=9&device_platform=android&enter_from=novel_category&novel_host&novel_version&version_code=973&version_name=9.7.3&word_count=9&os=android&device_type=ProjectTitan&os_api=29&os_version=10&offset={{(page-1)*100}}&limit=100&category_id=${cat.category_id}&${genderParam}`,\n type: 0,\n width: 0.29\n });\n }\n } else {\n let [tit1, genderParam, category] = categoryData[0];\n finds.push({\n title: \"更多分类\",\n url: \"\",\n type: 0,\n width: 3\n });\n for (let cat of category) {\n finds.push({\n title: cat.category_name,\n url: `https:\/\/novel.snssdk.com\/api\/novel\/channel\/homepage\/new_category\/book_list\/v1\/?aid=1967&app_name=news_article&app_version=9.7.3&channel=tengxun_tt&creation_status=9&device_platform=android&enter_from=novel_category&novel_host&novel_version&version_code=973&version_name=9.7.3&word_count=9&os=android&device_type=ProjectTitan&os_api=29&os_version=10&offset={{(page-1)*100}}&limit=100&category_id=${cat.category_id}&${genderParam}`,\n type: 0,\n width: 0.29\n });\n }\n }\n \n let [tit1, genderParam, category] = categoryData[2];\n finds.push({\n title: tit1,\n url: \"\",\n type: 0,\n width: 3\n });\n for (let cat of category) {\n finds.push({\n title: cat.category_name,\n url: `https:\/\/novel.snssdk.com\/api\/novel\/channel\/homepage\/new_category\/book_list\/v1\/?aid=1967&app_name=news_article&app_version=9.7.3&channel=tengxun_tt&creation_status=9&device_platform=android&enter_from=novel_category&novel_host&novel_version&version_code=973&version_name=9.7.3&word_count=9&os=android&device_type=ProjectTitan&os_api=29&os_version=10&offset={{(page-1)*100}}&limit=100&category_id=${cat.category_id}&${genderParam}`,\n type: 0,\n width: 0.29\n });\n }\n } catch (e) {}\n \n return JSON.stringify(finds);\n } catch (error) {\n return JSON.stringify([{\n title: \"发现页加载失败\",\n url: \"\",\n type: 0,\n width: 3\n }]);\n }\n}\n \n \/\/ ========== 发现页内容 ==========\n async function find(url, page) {\n try {\n url = url.replace(\"{{page}}\", page);\n url = url.replace(\"{{(page-1)*100}}\", (page - 1) * 100);\n url = url.replace(\"{{(page-1)*10}}\", (page - 1) * 10);\n const get = await http.Get(url, header, true);\n const books = [];\n \n if (url.includes(\"type_style\")) {\n const response = JSON.parse(get.data);\n if (response.data && response.data.book_info) {\n for (const item of response.data.book_info) books.push(await processBookData(item));\n } else if (response.data && response.data.data) {\n for (const item of response.data.data) books.push(await processBookData(item));\n }\n } else if (url.includes(\"novel.snssdk.com\") && url.includes(\"new_category\/book_list\")) {\n const response = JSON.parse(get.data);\n if (response.data && response.data.data) {\n for (const item of response.data.data) books.push(await processBookData(item));\n }\n } else if (url.includes(baseUrl) && url.includes(\"detail?book_id=\")) {\n const response = JSON.parse(get.data);\n if (response.data) {\n const dataList = Array.isArray(response.data) ? response.data : [response.data];\n for (const item of dataList) books.push(await processBookData(item));\n }\n } else if (url.includes(\"fanqienovel.com\/fqbookshelf\/groupName\/\")) {\n const sessionId = await getSessionId();\n if (!sessionId) return \"[]\";\n const groupName = decodeURIComponent(url.split(\"groupName\/\")[1]);\n const book_shelf_url = 'https:\/\/fanqienovel.com\/reading\/bookapi\/bookshelf\/info\/v:version\/?aid=1967&iid=0&version_code=57700&update_version_code=57700';\n const book_shelf_header = { ...header, \"Cookie\": `sessionid=${sessionId}` };\n const book_shelf_response = await http.Get(book_shelf_url, book_shelf_header, true);\n const book_shelf_info = JSON.parse(book_shelf_response.data);\n if (book_shelf_info.code == 0) {\n let group_bookids = [];\n book_shelf_info.data.book_shelf_info.forEach(i => {\n let gName = i.group_name ? i.group_name : \"未分组\";\n if (gName === groupName) group_bookids.push(i.book_id);\n });\n if (group_bookids.length > 0) {\n const detailUrl = `${baseUrl}\/detail?book_id=${group_bookids.join(\",\")}`;\n const detailResponse = await http.Get(detailUrl, header, true);\n const detailData = JSON.parse(detailResponse.data);\n const dataList = Array.isArray(detailData.data) ? detailData.data : [detailData.data];\n for (const bookData of dataList) books.push(await processBookData(bookData));\n }\n }\n } else if (url.includes(\"fanqienovel.com\/fqbookshelf\") || url.includes(\"read_recommend\") || url.includes(\"read_history\")) {\n const sessionId = await getSessionId();\n if (!sessionId) return \"[]\";\n if (url.includes(\"read_recommend\")) {\n const response = JSON.parse(get.data);\n if (response.data && response.data.cell_view && response.data.cell_view.book_data) {\n for (const item of response.data.cell_view.book_data) books.push(await processBookData(item));\n }\n } else if (url.includes(\"read_history\")) {\n const response = JSON.parse(get.data);\n if (response.data && response.data.data_list) {\n for (const item of response.data.data_list) {\n const bookId = item.book_id_str || item.book_id;\n if (bookId) {\n const detailUrl = `https:\/\/api5-normal-sinfonlineb.fqnovel.com\/reading\/bookapi\/multi-detail\/v\/?aid=1967&iid=1&version_code=999&book_id=${bookId}`;\n const detailResponse = await http.Get(detailUrl, header, true);\n const detailData = JSON.parse(detailResponse.data);\n const bookData = Array.isArray(detailData.data) ? detailData.data[0] : detailData.data;\n if (bookData) books.push(await processBookData(bookData));\n }\n }\n }\n }\n } else {\n const response = JSON.parse(get.data);\n let bookList = [];\n if (response.book_list) bookList = response.book_list;\n else if (response.data && response.data.publication_list) bookList = response.data.publication_list;\n else if (response.data && response.data.result) bookList = response.data.result;\n else if (response.data && Array.isArray(response.data)) bookList = response.data;\n for (const item of bookList) books.push(await processBookData(item));\n }\n return JSON.stringify(books);\n } catch (error) {\n return \"[]\";\n }\n }\n \n async function getloginurl() {\n const sessionId = await getSessionId();\n const logins = [\n {\n \"name\": \"服务器地址\",\n \"type\": \"text\"\n },\n {\n \"name\": \"手动登录Token\",\n \"type\": \"password\"\n }\n ];\n \n if (sessionId) {\n logins.push({\n \"name\": \"退出番茄账号\",\n \"type\": \"button\",\n \"action\": \"logoutFanqie()\"\n });\n } else {\n logins.push({\n \"name\": \"登录番茄账号\",\n \"type\": \"button\",\n \"action\": \"loginFanqie()\"\n });\n }\n \n logins.push({\n \"name\": \"切换服务器\",\n \"type\": \"button\",\n \"action\": \"switchHost()\"\n });\n \n logins.push({\n \"name\": \"男频\",\n \"type\": \"button\",\n \"action\": \"setGender('boy')\"\n });\n \n logins.push({\n \"name\": \"女频\",\n \"type\": \"button\",\n \"action\": \"setGender('girl')\"\n });\n \n return JSON.stringify(logins);\n}\n\nasync function login() {\n const info = await cache.getLoginInfo();\n try {\n const loginInfo = JSON.parse(info);\n if (loginInfo[\"服务器地址\"]) {\n baseUrl = loginInfo[\"服务器地址\"].trim();\n flutterBridge.showToast(\"服务器地址已保存\");\n }\n if (loginInfo[\"手动登录Token\"]) {\n await cookie.setCookie(\"https:\/\/fanqienovel.com\", \"sessionid\", loginInfo[\"手动登录Token\"]);\n flutterBridge.showToast(\"Token已保存\");\n }\n } catch (e) {\n flutterBridge.showToast(\"获取设置失败:\" + e.message);\n }\n}\n\nasync function loginFanqie() {\n await flutterBridge.startBrowser(\"https:\/\/fanqienovel.com\/\", \"登录\");\n}\n\nasync function logoutFanqie() {\n try {\n await cookie.remove(\"fanqienovel.com\");\n await cookie.remove(\"snssdk.com\");\n flutterBridge.showToast(\"已退出番茄账号\");\n } catch (e) {\n flutterBridge.showToast(\"退出失败: \" + e.message);\n }\n}\n\nasync function switchHost() {\n const hosts = [\n \"http:\/\/38.76.199.231:3000\"\n ];\n \n let currentIndex = hosts.indexOf(baseUrl);\n if (currentIndex === -1) currentIndex = 0;\n \n const nextIndex = (currentIndex + 1) % hosts.length;\n baseUrl = hosts[nextIndex];\n \n flutterBridge.showToast(`已切换到: ${baseUrl}`);\n}\n\nasync function setGender(g) {\n gender = g;\n flutterBridge.showToast(`已设置为${g === 'boy' ? '男频' : '女频'}`);\n}\n\nasync function pay(bookurl, url) {\n return \"\";\n}\n<\/script>\n\n<\/html>",
"login": true,
"lastUpdateTime": "1772297440093"
}