🍅番茄小说纯阅版

http://23.141.172.241:9990

autobcb_admin (12020)03/01 00:50

番茄小说
二维码导入(APP尚未完成该功能)
{
    "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"
}
广告