☁️云端聚合[优质源]
https://search.qingread.icu
linqingcia (8870)5天前
最后一次公开开放注册上限,后续永久不在对外开放注册。
{
"bookSourceComment": "11.23日更新:\n上线书单系统,可通过登录个人中心访问首页查看\n新增百度小说(可免费部分起点书,部分书籍收费)\n\n注:阅读请使用最新测试版\n1.所有源站每20秒最多15章,建议预下载调到0-3之间[白名单无请求限制]\n2.自定义源站示例:番茄小说,七猫小说 多个源站用英文,号分隔\n源站名参考 登录URL \/\/书源配置列表 v参数\n3.源站带有❇️图标表示只能指定或通过@搜索\n\n🍅番茄排序参数:\n❗填写保存后刷新发现页生效\n使用格式\n字数参数,状态参数,排序参数\n\n使用示例\n单个参数也可以,比如:完结\n· 10万,完结,热门\n10万字以下的完结热门作品\n· 100万以上,连载,新书\n100万字以上的连载新书\n· 50万以上,完结,评分\n50万字以上的完结高分作品\n\n字数参数(选一个)\n· 10万 - 10万字以下\n· 30万 - 30万字以下\n· 50万 - 50万字以下\n· 30万以上 - 30万字以上\n· 50万以上 - 50万字以上\n· 100万以上 - 100万字以上\n· 200万以上 - 200万字以上\n· 300万以上 - 300万字以上\n· 500万以上 - 500万字以上\n\n状态参数(选一个)\n· 完结 - 已完结\n· 连载 - 连载中\n· 半年完结 - 半年内完结\n· 3天更新 - 3天内更新\n· 7天更新 - 7天内更新\n· 1月更新 - 1个月内更新\n\n排序参数(选一个)\n· 热门 - 按热度排序\n· 新书 - 按新书排序\n· 评分 - 按评分排序\n· 字数 - 按字数排序\n\n4.搜索模式:\n书名@来源 多个来源用英文,号分隔\n示例: \n系统@番茄小说,多个源站使用英文,号分隔\n书名@类型 返回对应类型所有源站数据,可直接使用@方式搜索或者点击登陆切换模式\n示例: \n系统@小说 听书 漫画 短剧等\n默认书名排序\n指定作者优先:\n作者名@作者 作者名@作者,源站",
"bookSourceGroup": "聚合书源",
"bookSourceName": "☁️云端聚合[优质源]",
"bookSourceType": 0,
"bookSourceUrl": "https:\/\/search.qingread.icu",
"bookUrlPattern": "https?:\\\/\\\/(?:[a-zA-Z0-9-]+\\.)*qingread\\.icu\\\/details.*",
"customButton": false,
"customOrder": 0,
"enabled": true,
"enabledCookieJar": true,
"enabledExplore": true,
"eventListener": false,
"exploreUrl": "<js>\nlet config = (() => {\n try {\n let cfg = JSON.parse(source.getVariable())[0] || {};\n if (!cfg.host || !cfg.gender) {\n cfg = {gender: \"boy\", host: getServerHost()};\n source.setVariable(JSON.stringify([cfg]));\n java.toast(\"配置初始化完成\");\n }\n return cfg;\n } catch(e) {\n let cfg = {gender: \"boy\", host: getServerHost()};\n source.setVariable(JSON.stringify([cfg]));\n return cfg;\n }\n})();\n \nlet rawVariable = source.getVariable();\nlet 个人中心 = 1;\nlet obj = (title, url, type) => ({\n title: title,\n url: url,\n style: { layout_flexGrow: 1, layout_flexBasisPercent: type }\n});\n\nlet sort = [];\nlet push = (title, url, type) => sort.push(obj(title, url, type));\n\nlet currentType = \"\";\nlet currentSource = \"\";\ntry {\n let vArray = JSON.parse(rawVariable);\n if (vArray.length > 0) {\n let v = vArray[0];\n currentType = v.gender || \"\";\n currentSource = v.source || \"\";\n \n if (currentSource == \"推荐\") {\n currentType = \"recommend\";\n v.gender = \"recommend\";\n source.setVariable(JSON.stringify(vArray));\n } else if (currentType == \"recommend\") {\n currentType = \"boy\";\n v.gender = \"boy\";\n source.setVariable(JSON.stringify(vArray));\n }\n }\n} catch(e) {\n currentType = \"\";\n currentSource = \"\";\n}\n\nconst getGenderDisplayName = (gender) => {\n if (gender == \"boy\") return \"男频\";\n if (gender == \"girl\") return \"女频\";\n if (gender == \"recommend\") return \"推荐\";\n return \"\";\n};\n\nconst genderDisplay = getGenderDisplayName(currentType);\n\npush(`当前为【${currentSource || \"番茄小说\"}】${genderDisplay ? `【${genderDisplay}】` : ''}`, \"\", 1);\n\nconst excludedSources = [\"小说\", \"听书\", \"漫画\", \"视频\", \"短剧\", \"音频\",\"番茄小说\"];\n\nif (currentSource && currentSource !== \"推荐\" && !excludedSources.includes(currentSource)) {\n try {\n let apiUrl = `${getServerHost()}\/type_api?source=${currentSource}`;\n if (currentType) {\n apiUrl += `&gender=${currentType}`;\n }\n \n let response = JSON.parse(java.ajax(apiUrl));\n \n if (response.data && response.data.found) {\n response.data.found.forEach(item => {\n let finalUrl = \"\";\n if (item.url) {\n finalUrl = `${getServerHost()}\/type_api?source=${currentSource}&page={{page}}&url=${item.url}`;\n if (currentType) {\n finalUrl += `&gender=${currentType}`;\n }\n }\n sort.push({\n title: item.title,\n url: finalUrl,\n style: item.style || { layout_flexGrow: 1, layout_flexBasisPercent: 0.29 }\n });\n });\n } else {\n push(`【${currentSource}】${genderDisplay ? `【${genderDisplay}】` : ''}暂无数据`, \"\", 1);\n }\n } catch(e) {\n push(`【${currentSource}】${genderDisplay ? `【${genderDisplay}】` : ''}暂无数据`, \"\", 1);\n }\n JSON.stringify(sort);\n} else if (currentType == \"recommend\" && currentSource == \"推荐\") {\n \n let maleCategories = [];\n try {\n let maleResponse = JSON.parse(java.ajax(getServerHost() +\"\/api?action=subcategories&top_category_id=1\"));\n maleCategories = maleResponse.data;\n } catch(e) {\n push('男频分类加载失败,请检查网络', \"\", 1);\n }\n \n let femaleCategories = [];\n try {\n let femaleResponse = JSON.parse(java.ajax(getServerHost() +\"\/api?action=subcategories&top_category_id=2\"));\nfemaleCategories = femaleResponse.data;\n } catch(e) {\n push('女频分类加载失败,请检查网络', \"\", 1);\n }\n \n if (maleCategories && maleCategories.length > 0) {\n push('♂️ 男频推荐 ♂️', \"\", 1);\n maleCategories.forEach(cat => {\n push(cat.name, `${getServerHost()}\/api?action=books&sub_category_id=${cat.id}&limit=20&page={{page}}`, 0.29);\n });\n } else if (!maleCategories || maleCategories.length == 0) {\n push('男频分类暂无数据', \"\", 1);\n }\n \n if (femaleCategories && femaleCategories.length > 0) {\n push('♀️ 女频推荐 ♀️', \"\", 1);\n femaleCategories.forEach(cat => {\n push(cat.name, `${getServerHost()}\/api?action=books&sub_category_id=${cat.id}&limit=20&page={{page}}`, 0.29);\n });\n } else if (!femaleCategories || femaleCategories.length === 0) {\n push('女频分类暂无数据', \"\", 1);\n }\n \n JSON.stringify(sort);\n} else {\n let recommendUrl = getServerHost() + \"\/read_recommend?session=\" + getSessionId();\n push('个性推荐', recommendUrl, 0.29);\n push('巅峰榜单', 'https:\/\/fanqienovel.com\/api\/author\/misc\/top_book_list\/v1\/?limit=100&offset={{(page-1)*100}}', 0.29);\n push('出版榜单', 'https:\/\/fanqienovel.com\/api\/node\/publication\/list?page_index={{(page-1)*100}}&page_count=100', 0.29);\n push('爆更榜单', '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', 0.29);\n push('黑马榜单', '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', 0.29);\n push('热搜榜单', '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', 0.29);\t\n\n let fqCategoryArr = [];\n let showCategories = currentType == \"girl\" ? [0] : (currentType == \"boy\" ? [1] : [0, 1]);\n\n showCategories.forEach(i => {\n try {\n let response = JSON.parse(java.ajax(`${getServerHost()}\/type_style?new_category_tab=${i}`));\n let $ = response.data.category_tab_data;\n \n let tabName = $.tab_name || (i == 0 ? \"女频\" : \"男频\");\n fqCategoryArr.push(obj(`❤️${tabName}分类❤️`, null, 1));\n \n $.cell_data.forEach(c => {\n fqCategoryArr.push(obj(c.cell_name, null, 1));\n \n if (c.atom_data && Array.isArray(c.atom_data)) {\n c.atom_data.slice(1).forEach(a => {\n let d = a.category_data;\n if (d && d.name) {\n let cid = d.category_id;\n\n let gender, genre_type;\n if (i == 0) {\n gender = 0;\n genre_type = 0;\n } else if (i == 1) {\n gender = 1;\n genre_type = 0;\n } else {\n gender = 1;\n genre_type = 0;\n }\n \n fqCategoryArr.push({\n title: d.name,\n url: `${getServerHost()}\/type_style?category_id=${cid}&genre_type=${genre_type}&gender=${gender}&offset={{(page-1)*100}}&selected_items=${getSortKey()}`,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 0.29\n }\n });\n }\n });\n }\n });\n } catch (e) {\n fqCategoryArr.push(obj(\"分类加载失败,请重试\", null, 1));\n }\n });\n\n fqCategoryArr.forEach(item => {\n if (item.style && item.style.layout_flexBasisPercent) {\n push(item.title, item.url, item.style.layout_flexBasisPercent);\n } else {\n sort.push({\n title: item.title,\n url: item.url,\n style: {\n layout_flexGrow: 1,\n layout_flexBasisPercent: 0.29\n }\n });\n }\n});\n\ncategory = () => {\n category_url = \"https:\/\/novel.snssdk.com\/api\/novel\/channel\/homepage\/new_category\/page\/data\/v1\/?aid=13\";\n return JSON.parse(java.ajax(category_url)).data;\n}\n \njson = (data) => {\n boy = data.boy_category;\n girl = data.girl_category;\n publish = data.publish_category;\n json = [[\"男频\",\"gender=1\",boy],[\"女频\",\"gender=0\",girl],[\"出版\",\"genre_type=160\",publish]];\n return JSON.parse(JSON.stringify(json));\n}\t\n\nlet categoryData = json(category());\n\nif(currentType == \"girl\") {\n let [tit1, gender, categoryList] = categoryData[1];\n push('其它分类', null, 1);\n categoryList.forEach(($,index)=>{\n index++;\n title = $.category_name;\n cid = $.category_id;\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=${cid}&${gender}`;\n push(title, url, 0.29);\t\n });\n} else {\n let [tit1, gender, categoryList] = categoryData[0];\n push('其它分类', null, 1);\n categoryList.forEach(($,index)=>{\n index++;\n title = $.category_name;\n cid = $.category_id;\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=${cid}&${gender}`;\n push(title, url, 0.29);\t\n });\n}\n\nlet [tit1, gender, categoryList] = categoryData[2]; \npush('❤️'+tit1+'❤️', null, 1);\ncategoryList.forEach(($,index)=>{\n index++;\n title = $.category_name;\n cid = $.category_id;\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=${cid}&${gender}`;\n push(title, url, 0.29);\t\n});\n\n let hasValidSession = false;\n let username = \"\";\n\ntry {\n let uinfo = java.ajax(getServerHost() + \"\/book_user?session=\" + getSessionId());\n uinfo = JSON.parse(uinfo);\n \n if (uinfo && uinfo.data && uinfo.data.name) {\n username = uinfo.data.name;\n hasValidSession = true;\n }\n} catch (e) {\n}\n\nif (!hasValidSession) {\n java.toast(\"番茄登录已过期,请重新登录\");\n}\n\n let gro = [];\n let pushGro = (title, url, type) => gro.push(obj(title, url, type));\n\n let sArr = [];\n\n if (hasValidSession) {\n try {\n let book_shelf_info = JSON.parse(java.ajax(getServerHost() + \"\/book_shelf?session=\" + getSessionId()));\n\n if (book_shelf_info.code == 0) {\n 个人中心 = 1;\n \n let groups_bookids = { \"未分组\": [] };\n book_shelf_info.data.book_shelf_info.forEach(i => {\n if (!groups_bookids[i.group_name ? i.group_name : \"未分组\"]) \n groups_bookids[i.group_name] = [];\n groups_bookids[i.group_name ? i.group_name : \"未分组\"].push(i.book_id);\n });\n\n Object.keys(groups_bookids).forEach(k => {\n pushGro(k, \"https:\/\/fanqienovel.com\/fqbookshelf\/groupName\/\" + k, 0.4);\n });\n if (Object.keys(groups_bookids).length % 2 != 0) pushGro(\"占位\", \"\", 0.4);\n \n sArr.push(obj(username + '的个人中心', '', 1));\n sArr.push(obj(\"我的书架\", \"https:\/\/fanqienovel.com\/fqbookshelf\", 1));\n sArr = sArr.concat(gro);\n sArr.push(obj(\"阅读历史\", getServerHost() + \"\/read_history?session=\" + getSessionId() + \"&page={{page}}\", 1));\n } else {\n 个人中心 = 0;\n java.toast(\"获取书架信息失败,登录可能已过期\");\n }\n } catch (e) {\n 个人中心 = 0;\n java.toast(\"番茄登录过期,已隐藏个人中心\");\n }\n } else {\n 个人中心 = 0;\n java.toast(\"您还未登录番茄账号,无法同步数据\");\n }\n\n sort = sArr.concat(sort);\n JSON.stringify(sort);\n}\n<\/js>",
"header": "@js:\nJSON.stringify({\n\t\"User-Agent\":java.getWebViewUA(),\n\t\"X-Novel-Token\": \"QING_READ_2025\"\n\t})",
"jsLib": "function deviceType() {\n let { java } = this;\n \n try {\n java.deviceID(); \n return false; \n } catch (e) {\n try {\n java.androidId(); \n return true; \n } catch (e) {\n return false; \/\/ 都失败则默认返回false(安卓设备)\n }\n }\n}\n\nfunction isQRead() {\n let { java } = this;\n try {\n return java.qread && java.qread() == \"1\";\n } catch (e) {\n return false;\n }\n}\n\nfunction getServerHost() {\n\tlet { source } = this;\n try {\n const config = JSON.parse(source.getVariable());\n return config?.[0]?.host || source.bookSourceUrl;\n } catch(e) {\n return source.bookSourceUrl;\n }\n}\n\nfunction getSessionId() {\n const { cookie, source } = this;\n try {\n let cookieHeader = String(cookie.getCookie('fanqienovel.com'));\n let sessionId = cookieHeader.match(\/sessionid=([^;]+)\/)?.[1] || null;\n \n if (sessionId) {\n return sessionId;\n }\n } catch (e) {\n }\n \n try {\n let loginInfo = source.getLoginInfoMap() || {};\n return loginInfo['番茄登录Token'] || \"\";\n } catch (e) {\n return \"\";\n }\n}\n\nfunction splitArray(input, size) {\n let output = []\n for (let i = 0; i < input.length; i += size) {\n output.push(input.slice(i, i + size))\n }\n return output\n}\n\nfunction getCommentIconColor() {\n let { source } = this;\n let color = \"red\";\n let loginInfo = source.getLoginInfoMap();\n if (loginInfo && loginInfo['段评图标颜色'] && loginInfo['段评图标颜色'].startsWith('#')) {\n color = loginInfo['段评图标颜色'];\n }\n return color;\n}\n\nfunction wrapContentForIOS(content) {\n if (deviceType.call(this)) {\n return content;\n }\n \n let lines = content.split('\\n').map(line => {\n if (line.trim()) {\n return `<span rs-native>${line}<\/span>`;\n }\n return line;\n });\n \n return lines.join('\\n');\n}\n\nfunction buildCommentUrl(sourceType, bid, cid, para) {\n let host = getServerHost.call(this);\n let aidParam = (!deviceType.call(this) || isQRead.call(this)) ? \"&aid=web\" : \"\";\n \n switch (sourceType) {\n case 'jj':\n return host + '\/jj_comment?bid=' + bid + '&cid=' + cid + '¶=' + para + aidParam;\n case 'qm':\n return host + '\/qm_comment?item_id=' + cid + '&book_id=' + bid + '¶graph_id=' + para + aidParam;\n case 'fq':\n default:\n return host + '\/idea_comment?item_id=' + cid + '&book_id=' + bid + '¶=' + para + '&sessionid=' + getSessionId.call(this) + aidParam;\n }\n}\n\nfunction getCommentSourceName(sourceType) {\n switch (sourceType) {\n case 'jj': return '晋江';\n case 'qm': return '七猫';\n case 'fq':\n default: return '番茄';\n }\n}\n\nfunction startCommentBrowser(url, title) {\n if (isQRead.call(this)) {\n this.java.startBrowserDp(url, title);\n } else {\n this.java.startBrowser(url, title);\n }\n}\n\nfunction createCommentHtmlTag(displayText, url, sourceName) {\n const encodedUrl = url.replace(\/&\/g, '&');\n return '<comment count=\"' + displayText + '\" onPress=\"java.' + \n (isQRead.call(this) ? 'startBrowserDp' : 'startBrowser') + \n '(\\'' + encodedUrl + '\\',\\'' + sourceName + '段评\\')\" \/>';\n}\n\nfunction createSvg(number, color, bid, cid, para, sourceType = 'fq') {\n var displayText = number > 99 ? \"99+\" : number;\n var date = String(Date.now()).match(\/(\\d{6}$)\/)[1];\n var loginInfoMap = {};\n if (this.source && typeof this.source.getLoginInfoMap == 'function') {\n loginInfoMap = this.source.getLoginInfoMap() || {};\n }\n var bubbleStyle = String(loginInfoMap['段评气泡样式'] || '0');\n \n let isIOS = !deviceType.call(this);\n if (isIOS) {\n let commentUrl = buildCommentUrl.call(this, sourceType, bid, cid, para);\n let sourceName = getCommentSourceName(sourceType);\n return createCommentHtmlTag.call(this, displayText, commentUrl, sourceName);\n }\n \n var svg;\n if (bubbleStyle == '1') {\n svg = '<svg width=\"1000\" height=\"833\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">' +\n '<rect x=\"30\" y=\"20\" width=\"940\" height=\"793\" rx=\"180\" ry=\"180\" fill=\"none\" stroke=\"' + color + '\" stroke-width=\"40\" stroke-linejoin=\"round\"\/>' +\n '<text x=\"500\" y=\"444\" text-anchor=\"middle\" font-size=\"333\" fill=\"' + color + '\" dy=\"0.3em\" font-weight=\"700\" opacity=\"0.9\">' + displayText + '<\/text>' +\n '<\/svg>';\n } else if (bubbleStyle == '2') {\n svg = '<svg width=\"1000\" height=\"1000\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">' +\n '<circle cx=\"500\" cy=\"500\" r=\"460\" fill=\"none\" stroke=\"' + color + '\" stroke-width=\"40\" stroke-linejoin=\"round\"\/>' +\n '<text x=\"500\" y=\"500\" font-family=\"Arial, sans-serif\" text-anchor=\"middle\" font-size=\"360\" fill=\"' + color + '\" dy=\"0.3em\" font-weight=\"700\" opacity=\"0.9\">' + displayText + '<\/text>' +\n '<\/svg>';\n } else if (bubbleStyle == '3') {\n svg = '<svg width=\"160\" height=\"120\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\"><path d=\"M 55 10 L 120 10 Q 150 10 150 40 L 150 80 Q 150 110 120 110 L 55 110 Q 25 110 25 80 L 25 75 L 3 60 L 25 45 L 25 40 Q 25 10 55 10 Z\" fill=\"none\" stroke=\"' + color + '\" stroke-width=\"2\" stroke-linejoin=\"round\"\/><text x=\"87\" y=\"75\" font-family=\"Arial, sans-serif\" text-anchor=\"middle\" dominant-baseline=\"middle\" font-size=\"50\" font-weight=\"bold\" fill=\"' + color + '\">' + displayText + '<\/text><\/svg>';\n } else {\n svg = '<svg width=\"1000\" height=\"909\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\">' +\n '<path d=\"M80,80 h840 a60,60 0 0 1 60,60 v580 a60,60 0 0 1 -60,60 h-620 l-140,90 v-90 h-80 a60,60 0 0 1 -60,-60 v-580 a60,60 0 0 1 60,-60 z\" ' +\n 'fill=\"none\" stroke=\"' + color + '\" stroke-width=\"16\" stroke-linejoin=\"round\"\/>' +\n '<text x=\"500\" y=\"450\" text-anchor=\"middle\" ' + 'font-size=\"360\" fill=\"' + color + '\" dy=\"0.3em\">' + displayText + '<\/text>' +\n '<\/svg>';\n }\n \n var encodedSvg = this.java.base64Encode(svg);\n \n let jsFunction;\n switch (sourceType) {\n case 'jj':\n jsFunction = 'showJJCmt';\n break;\n case 'qm':\n jsFunction = 'showQmCmt';\n break;\n case 'fq':\n default:\n jsFunction = 'showCmt';\n break;\n }\n \n return 'data:image\/svg+xml;base64,' + encodedSvg + ',{\\'js\\':\\'' + jsFunction + '(\"' + bid + '\",\"' + cid + '\",\"' + para + '\",\"' + date + '\")\\',\\'style\\':\\'text\\'}';\n}\n\nfunction checkClickRateLimit(lastCallKey) {\n let { cache } = this;\n let lastCallTime = cache.getFromMemory(lastCallKey) || 0;\n let currentTime = Date.now();\n \n if (currentTime - lastCallTime < 1000) {\n return false;\n }\n cache.putMemory(lastCallKey, currentTime);\n return true;\n}\n\nfunction handleCommentClick(sourceType, bid, cid, para, date, cacheKey) {\n let { cache } = this;\n let mname = cacheKey;\n let load = (cache.getFromMemory(mname) ?? \"-\").split(\"-\");\n \n if (load[0] != \"1\" || load[1] != date) {\n cache.putMemory(mname, \"1-\" + date);\n return;\n }\n \n let lastCallKey = `last_call_${sourceType}_${bid}_${cid}_${para}`;\n if (!checkClickRateLimit.call(this, lastCallKey)) {\n return;\n }\n \n let commentUrl = buildCommentUrl.call(this, sourceType, bid, cid, para);\n let title = cache.getFromMemory(mname + \"-text\") ?? getCommentSourceName(sourceType) + \"段评\";\n \n startCommentBrowser.call(this, commentUrl, title);\n}\n\nfunction getComments(content, bid, cid, sourceType = 'fq', contentMd5 = null) {\n let { java, cache, source } = this;\n let host = getServerHost.call(this);\n try {\n let lines = content\n .replace(\/(<img[^>]*?>)\\n\/g, \"$1\")\n .split(\"\\n\")\n .filter(line => line.trim());\n \n if (sourceType == 'jj') {\n let apiUrl = `${host}\/proxy_jjidea?bid=${bid}&cid=${cid}&token=${JJtoken.call(this)}`;\n let response = java.ajax(apiUrl);\n let data = JSON.parse(response).data;\n \n data.forEach((item) => {\n let para = item.paragraph_id;\n let count = item.comment_total;\n \n let paraNum = parseInt(para);\n if (paraNum <= 0 || paraNum > lines.length) {\n return;\n }\n \n let adjustedPara = paraNum - 1;\n cache.putMemory(`jj-${bid}-${cid}-${para}-text`, lines[adjustedPara]);\n \n let color = getCommentIconColor.call(this);\n let commentTag = createSvg.call(this, count, color, bid, cid, para, sourceType);\n if (deviceType.call(this)) {\n lines[adjustedPara] += `<img src=\"${commentTag}\">`;\n } else {\n lines[adjustedPara] += commentTag;\n }\n });\n } else if (sourceType == 'qm') {\n if (!contentMd5) return wrapContentForIOS.call(this, content);\n \n let apiUrl = `${host}\/proxy_qmidea?action=paragraph_bubbles&book_id=${bid}&item_id=${cid}&content_md5=${contentMd5}`;\n let response = java.ajax(apiUrl);\n let data = JSON.parse(response).data;\n \n if (data && data.chapters && data.chapters.length > 0) {\n let chapterData = data.chapters[0];\n if (chapterData.bubbles) {\n chapterData.bubbles.forEach((bubble, bubbleIndex) => {\n let paraMd5 = bubble.p;\n let count = parseInt(bubble.c) || 0;\n \n if (count > 0 && bubbleIndex < lines.length) {\n let paraIndex = bubbleIndex;\n \n cache.putMemory(`qm-${bid}-${cid}-${paraMd5}-text`, lines[paraIndex]);\n \n let color = getCommentIconColor.call(this);\n let commentTag = createSvg.call(this, count, color, bid, cid, paraMd5, sourceType);\n if (deviceType.call(this)) {\n lines[paraIndex] += `<img src=\"${commentTag}\">`;\n } else {\n lines[paraIndex] += commentTag;\n }\n }\n });\n }\n }\n } else {\n let apiUrl = `${host}\/proxy_idea?item_id=${cid}`;\n let comments = java.ajax(apiUrl);\n let raw = JSON.parse(comments).data.data;\n\n Object.keys(raw).forEach((x) => {\n cache.putMemory(`fq-${bid}-${cid}-${x}-text`, lines[x]);\n \n let color = getCommentIconColor.call(this);\n let commentTag = createSvg.call(this, raw[x].count, color, bid, cid, x, sourceType);\n if (deviceType.call(this)) {\n lines[x] += `<img src=\"${commentTag}\">`;\n } else {\n lines[x] += commentTag;\n }\n });\n }\n \n let result = lines.join(\"\\n\");\n return wrapContentForIOS.call(this, result);\n } catch (e) {\n return wrapContentForIOS.call(this, content);\n }\n}\n\nfunction showJJCmt(bid, cid, para, date) {\n handleCommentClick.call(this, 'jj', bid, cid, para, date, `jj-${bid}-${cid}-${para}`);\n}\n\nfunction showCmt(bid, cid, para, date) {\n handleCommentClick.call(this, 'fq', bid, cid, para, date, `fq-${bid}-${cid}-${para}`);\n}\n\nfunction showQmCmt(bid, cid, para, date) {\n handleCommentClick.call(this, 'qm', bid, cid, para, date, `qm-${bid}-${cid}-${para}`);\n}\n\nfunction getSecretKey() {\n let { java, source } = this;\n const loginInfo = source.getLoginInfoMap();\n const key = loginInfo['密钥'];\n const keyStr = String(key || '');\n \n if (keyStr.length < 10) {\n java.toast(\"无效的密钥,请检查确认\");\n return \"\";\n }\n \n return java.base64Encode(keyStr);\n}\n\nfunction JJtoken() {\n\tlet { java, source } = this;\n const loginInfo = source.getLoginInfoMap();\n const key = loginInfo['晋江登录Token'] || '';\n if (!key) {\n java.toast(\"未填写晋江登录Token,无法查看付费章节段评\");\n }\n return java.base64Encode(key);\n}\n\nfunction getSortKey() {\n let { java, source } = this;\n const loginInfo = source.getLoginInfoMap();\n const sortKey = loginInfo['番茄排序'] || '';\n if (!sortKey) {\n java.toast(\"请先填写番茄排序,参数请看源注释\");\n }\n return sortKey;\n}\n\nfunction getBookIdFromUrl(baseUrl) {\n\tlet { java, source } = this;\n let finalUrl;\n let urlMatch = baseUrl.match(\/url=([^&]*)\/);\n \n if (urlMatch && urlMatch[1]) {\n finalUrl = java.base64Decode(urlMatch[1]);\n } else {\n let fqurl = JSON.parse(java.base64Decode(baseUrl.split(\",\")[1]));\n finalUrl = java.base64Decode(fqurl.url);\n }\n \n let bookIdMatch = finalUrl.match(\/book_id=(\\d{19})\/);\n return bookIdMatch ? bookIdMatch[1] : null;\n}\n\nfunction getBookData(bookId) {\n\tlet { java, source } = this;\n let newUrl = \"https:\/\/api5-normal-sinfonlineb.fqnovel.com\/reading\/bookapi\/multi-detail\/v\/?aid=1967&iid=1&version_code=999&book_id=\" + bookId;\n let response = java.ajax(newUrl);\n let responseData = JSON.parse(response);\n \n if (Array.isArray(responseData.data)) {\n return responseData.data[0];\n } else {\n return responseData.data;\n }\n}\n\nfunction parseAuthors(authorsData) {\n\tlet { java, source } = this;\n let authors = [];\n try {\n authors = authorsData ? JSON.parse(authorsData) : [];\n } catch(e) {\n authors = authorsData ? authorsData.replace(\/[\\[\\]\"]\/g,'').split(',') : [];\n }\n return authors.length > 0 ? (authors[0].AuthorName || authors[0] || \"\") : \"\";\n}\n\nfunction getToneInfo(bookId) {\n if (!bookId) return \"\";\n \n try {\n let tones = \"\\n===================\\n🎧AI音色编号信息: \";\n let info = JSON.parse(java.ajax(`https:\/\/reading.snssdk.com\/reading\/bookapi\/audio\/toneinfo\/?book_id=${bookId}&aid=1967`));\n for (let i of (info.data.tts_tones ? info.data.tts_tones : [])) {\n tones += `\\n${i.id} - ${i.title}${i.description ? \"(\" + i.description + \")\" : \"\"}`;\n }\n return tones;\n } catch (e) {\n return \"\";\n }\n}\n\nfunction syncReadingHistory(bookId) {\n let { java, source } = this;\n if (bookId) {\n var sessionid = getSessionId.call(this);\n if (sessionid && sessionid.trim() !== \"\") {\n var syncUrl = getServerHost.call(this) + \"\/update_history?book_id=\" + bookId + \"&sessionid=\" + sessionid;\n java.ajax(syncUrl);\n }\n }\n}\n\nfunction encode(data){\n\tconst { java } = this;\n\treturn java.createSymmetricCrypto(\"DES\/CBC\/PKCS5Padding\", String(\"KW8Dvm2N\"), String(\"1ae2c94b\")).encryptBase64(data)\n}",
"lastUpdateTime": "1764343298569",
"loginUi": "[\n {\n \"name\": \"👤用户中心\",\n \"type\": \"button\",\n \"action\": \"user_center()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 1\n }\n },{\n \"name\": \"🔑获取密钥\",\n \"type\": \"button\",\n \"action\": \"key()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🔄密钥查询\",\n \"type\": \"button\",\n \"action\": \"get_user()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"密钥\",\n \"type\": \"password\",\n \"action\": \"\"\n },{\n \"name\": \"🍅番茄账号登录\",\n \"type\": \"button\",\n \"action\": \"fq_login()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"❌退出番茄账号\",\n \"type\": \"button\",\n \"action\": \"logout()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"☁️聚合搜索\",\n \"type\": \"button\",\n \"action\": \"sou1()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"☕喝杯咖啡\",\n \"type\": \"button\",\n \"action\": \"coffee()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"自定义源站\",\n \"type\": \"text\"\n },{\n \"name\": \"听书AI音色编号\",\n \"type\": \"text\"\n },{\n \"name\": \"❤️段评开关\",\n \"type\": \"button\",\n \"action\": \"toggleParacomment()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🧭书源更新\",\n \"type\": \"button\",\n \"action\": \"version()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"💞找书\/推荐\",\n \"type\": \"button\",\n \"action\": \"tj()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"⚡TXT下载开关\",\n \"type\": \"button\",\n \"action\": \"toggleFqdown('fqdown')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🧸男 频🧸\",\n \"type\": \"button\",\n \"action\": \"boy()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.45\n }\n },{\n \"name\": \"🎀女 频🎀\",\n \"type\": \"button\",\n \"action\": \"girl()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.45\n }\n },{\n \"name\": \"🎚服务器切换\",\n \"type\": \"button\",\n \"action\": \"sethost()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🔄服务器检测\",\n \"type\": \"button\",\n \"action\": \"checkCurrentServer()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"↓ 切换\/查询当前模式 ↓\",\n \"type\": \"button\",\n \"action\": \"get_cx()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 1\n }\n },{\n \"name\": \"📖小说模式\",\n \"type\": \"button\",\n \"action\": \"type1()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🎧听书模式\",\n \"type\": \"button\",\n \"action\": \"type2()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🌈漫画模式\",\n \"type\": \"button\",\n \"action\": \"type3()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"▶️视频模式\",\n \"type\": \"button\",\n \"action\": \"type4()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🔄番茄阅读记录同步\",\n \"type\": \"button\",\n \"action\": \"toggleBookSync()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 1\n }\n },{\n \"name\": \"↓ 指 定 源 站 ↓\",\n \"type\": \"button\",\n \"action\": \"\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 1\n }\n },{\n \"name\": \"❇️书友推荐\",\n \"type\": \"button\",\n \"action\": \"recommend()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🍅番茄小说\",\n \"type\": \"button\",\n \"action\": \"sou2()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🍅番茄听书[AI音色]\",\n \"type\": \"button\",\n \"action\": \"sou3()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🎧番茄畅听\",\n \"type\": \"button\",\n \"action\": \"sou4()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🍅番茄漫画\",\n \"type\": \"button\",\n \"action\": \"sou5()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🍅番茄短剧\",\n \"type\": \"button\",\n \"action\": \"sou6()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"💫起点中文\",\n \"type\": \"button\",\n \"action\": \"sou7()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🐱七猫小说\",\n \"type\": \"button\",\n \"action\": \"sou8()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🔥101看书\",\n \"type\": \"button\",\n \"action\": \"sou9()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🌤️69书吧com\",\n \"type\": \"button\",\n \"action\": \"sou10()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🐧企鹅看书\",\n \"type\": \"button\",\n \"action\": \"sou11()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🍰淘小说\",\n \"type\": \"button\",\n \"action\": \"sou12()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🍒追书神器\",\n \"type\": \"button\",\n \"action\": \"sou13()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"📓晋江文学\",\n \"type\": \"button\",\n \"action\": \"sou14()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🔆酷我小说\",\n \"type\": \"button\",\n \"action\": \"sou15()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🦉猫眼看书\",\n \"type\": \"button\",\n \"action\": \"sou16()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🍋得间小说\",\n \"type\": \"button\",\n \"action\": \"sou17()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🔥半夏中文\",\n \"type\": \"button\",\n \"action\": \"sou18()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🔥爱下电子书\",\n \"type\": \"button\",\n \"action\": \"sou19()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🌾小米阅读\",\n \"type\": \"button\",\n \"action\": \"sou20()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🍉西瓜小说\",\n \"type\": \"button\",\n \"action\": \"sou21()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🐱七猫短剧\",\n \"type\": \"button\",\n \"action\": \"sou41()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🍭米读\",\n \"type\": \"button\",\n \"action\": \"sou22()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.2\n }\n },{\n \"name\": \"🎊疯读\",\n \"type\": \"button\",\n \"action\": \"sou23()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.2\n }\n },{\n \"name\": \"♟️书旗\",\n \"type\": \"button\",\n \"action\": \"sou24()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.2\n }\n },{\n \"name\": \"⭐中文看书\",\n \"type\": \"button\",\n \"action\": \"sou25()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🔥69书吧co\",\n \"type\": \"button\",\n \"action\": \"sou26()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🌸NT动漫\",\n \"type\": \"button\",\n \"action\": \"sou40()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"🌙笔阁趣文❇️\",\n \"type\": \"button\",\n \"action\": \"sou28()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"知乎盐选\",\n \"type\": \"button\",\n \"action\": \"sou29()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.25\n }\n },{\n \"name\": \"60看书\",\n \"type\": \"button\",\n \"action\": \"sou30()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.2\n }\n },{\n \"name\": \"🔥笔趣阁\",\n \"type\": \"button\",\n \"action\": \"sou31()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.25\n }\n },{\n \"name\": \"🍓满孟文学\",\n \"type\": \"button\",\n \"action\": \"sou32()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"⚖️圣武书屋\",\n \"type\": \"button\",\n \"action\": \"sou33()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"追光阅读\",\n \"type\": \"button\",\n \"action\": \"sou34()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.25\n }\n },{\n \"name\": \"大熊猫\",\n \"type\": \"button\",\n \"action\": \"sou35()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.25\n }\n },{\n \"name\": \"速读谷\",\n \"type\": \"button\",\n \"action\": \"sou37()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.25\n }\n },{\n \"name\": \"乐读\",\n \"type\": \"button\",\n \"action\": \"sou39()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"百度小说\",\n \"type\": \"button\",\n \"action\": \"sou27()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": 0.4\n }\n },{\n \"name\": \"段评图标颜色\",\n \"type\": \"text\"\n },{\n \"name\": \"段评气泡样式\",\n \"type\": \"text\"\n },{\n \"name\": \"番茄排序\",\n \"type\": \"text\"\n },{\n \"name\": \"番茄登录Token\",\n \"type\": \"text\"\n },{\n \t\"name\": \"晋江登录Token\",\n \t\"type\": \"password\"\n }\n]",
"loginUrl": "\/\/请勿修改版本号\nconst localVersion = \"4.96\";\n\nvar hosts = [\n \"https:\/\/search.qingread.icu\",\n \"http:\/\/154.64.253.94:8887\",\n \"https:\/\/read.search.qingread.icu\",\n \"http:\/\/searchs.qingread.icu:6789\"\n];\n\nvar sourceList = [\n {n:\"sou1\",v:null,m:\"☁️聚合搜索\"},\n {n:\"sou2\",v:\"番茄小说\",m:\"番茄小说\"},\n {n:\"sou3\",v:\"番茄听书\",m:\"番茄听书[AI音色]\"},\n {n:\"sou4\",v:\"番茄畅听\",m:\"番茄畅听\"},\n {n:\"sou5\",v:\"番茄漫画\",m:\"番茄漫画\"},\n {n:\"sou6\",v:\"番茄短剧\",m:\"番茄短剧\"},\n {n:\"sou7\",v:\"起点\",m:\"💫起点中文\"},\n {n:\"sou8\",v:\"七猫小说\",m:\"🐱七猫小说\"},\n {n:\"sou9\",v:\"101看书\",m:\"101看书\"},\n {n:\"sou10\",v:\"69书吧\",m:\"69书吧\"},\n {n:\"sou11\",v:\"企鹅看书\",m:\"🐧企鹅看书\"},\n {n:\"sou12\",v:\"淘小说\",m:\"🍰淘小说\"},\n {n:\"sou13\",v:\"追书神器\",m:\"🍒追书神器\"},\n {n:\"sou14\",v:\"晋江\",g:\"girl\",m:\"📓晋江文学\"},\n {n:\"sou15\",v:\"酷我\",m:\"🔆酷我小说\"},\n {n:\"sou16\",v:\"猫眼看书\",m:\"🦉猫眼看书\"},\n {n:\"sou17\",v:\"得间\",m:\"🍋得间小说\"},\n {n:\"sou18\",v:\"半夏\",m:\"🔥半夏中文\"},\n {n:\"sou19\",v:\"爱下电子书\",m:\"🔥爱下电子书\"},\n {n:\"sou20\",v:\"小米阅读\",m:\"🌾小米阅读\"},\n {n:\"sou21\",v:\"西瓜小说\",m:\"🍉西瓜小说\"},\n {n:\"sou22\",v:\"米读\",m:\"🍭米读\"},\n {n:\"sou23\",v:\"疯读\",m:\"🎊疯读\"},\n {n:\"sou24\",v:\"书旗\",m:\"♟️书旗\"},\n {n:\"sou25\",v:\"中文看书\",m:\"⭐中文看书\"},\n {n:\"sou26\",v:\"co\",m:\"🔥69书吧co\"},\n {n:\"sou27\",v:\"百度\",m:\"📖百度小说\"},\n {n:\"sou28\",v:\"笔阁趣文\",m:\"🌙笔阁趣文\"},\n {n:\"sou29\",v:\"知乎\",m:\"知乎盐选\"},\n {n:\"sou30\",v:\"60看书\",m:\"60看书\"},\n {n:\"sou31\",v:\"笔趣阁\",m:\"🔥笔趣阁\"},\n {n:\"sou32\",v:\"满孟\",m:\"🍓满孟文学\"},\n {n:\"sou33\",v:\"圣武书屋\",m:\"⚖️圣武书屋\"},\n {n:\"sou34\",v:\"追光阅读\",m:\"追光阅读\"},\n {n:\"sou35\",v:\"大熊猫文学\",m:\"大熊猫\"},\n {n:\"sou37\",v:\"速读谷\",m:\"速读谷\"},\n {n:\"sou39\",v:\"乐读\",m:\"🌸乐读小说\"},\n {n:\"sou40\",v:\"ntdm\",m:\"NT动漫\"},\n {n:\"sou41\",v:\"七猫短剧\",m:\"🐱七猫短剧\"},\n {n:\"type1\",v:\"小说\",m:\"小说模式\"},\n {n:\"type2\",v:\"听书\",m:\"听书模式\"},\n {n:\"type3\",v:\"漫画\",m:\"漫画模式\"},\n {n:\"type4\",v:\"视频\",m:\"视频模式\"}\n];\n\nfunction hasValidCustomSource() {\n const value = (source.getLoginInfoMap())['自定义源站'];\n return !(value == null || value == undefined || value == \"\" || String(value).trim() == \"\");\n}\n\nfunction showCustomSourceAlert() {\n const customSource = (source.getLoginInfoMap())['自定义源站'];\n java.toast(\n \"\\n⚠️ 自定义源站配置提醒 ⚠️\\n\" +\n \"──────────────────\\n\" +\n \"检测到已设置自定义源站: \\n✨\" +\n \"【\" + (customSource || \"\") + \"】✨\\n\" +\n \"──────────────────\\n\" +\n \"请前往「书源登录」清除配置\\n\" +\n \"才能使指定源站切换生效\"\n );\n}\n\nfunction updateConfig(key, value, message, gender) {\n if (key == \"source\" && hasValidCustomSource()) {\n showCustomSourceAlert();\n return;\n }\n \n try {\n var config = JSON.parse(source.getVariable());\n if (!config || !Array.isArray(config) || config.length == 0) {\n config = [{}];\n }\n config[0][key] = value;\n if (gender !== undefined) {\n config[0][\"gender\"] = gender;\n }\n source.setVariable(JSON.stringify(config));\n java.toast(message || \"设置已更新\");\n } catch(e) {\n var newConfig = {[key]: value};\n if (gender !== undefined) {\n newConfig[\"gender\"] = gender;\n }\n source.setVariable(JSON.stringify([newConfig]));\n java.toast(message || \"初始化设置\");\n }\n}\n\nfunction get_user() {\n try {\n var encodedKey = getSecretKey();\n if (!encodedKey) {\n java.toast(\"❌ 未获取到密钥,请先填写密钥\");\n return;\n }\n\n var timestamp = Math.floor(Date.now() \/ 1000);\n var apiUrl = getServerHost() + '\/query_key.php?key=' + encodedKey + '&t=' + timestamp;\n \n var response = java.ajax(apiUrl + \",\" + JSON.stringify({\n method: \"GET\",\n timeout: 10000\n }));\n \n var data = JSON.parse(response);\n \n if (data.success && data.data) {\n var userInfo = data.data;\n \n var statusDisplay = \"\";\n switch(userInfo.status) {\n case \"正常\":\n statusDisplay = \"✅ 正常\";\n break;\n case \"会员\":\n statusDisplay = \"🌟 会员\";\n break;\n case \"封禁\":\n statusDisplay = \"🚫 封禁\";\n break;\n default:\n statusDisplay = userInfo.status;\n }\n \n var keyValue = userInfo.api_key || \"\";\n var maskedKey = \"\";\n if (keyValue.length > 10) {\n maskedKey = keyValue.substring(0, 3) + \"****\" + keyValue.substring(keyValue.length - 4);\n } else {\n maskedKey = \"****\" + keyValue.substring(keyValue.length - 4);\n }\n \n var email = userInfo.email || \"未绑定\";\n var maskedEmail = \"未绑定\";\n if (email !== \"未绑定\" && email.includes(\"@\")) {\n var parts = email.split(\"@\");\n var username = parts[0];\n var domain = parts[1];\n if (username.length > 2) {\n maskedEmail = username.substring(0, 2) + \"****@\" + domain;\n } else {\n maskedEmail = username + \"****@\" + domain;\n }\n }\n \n var nickname = userInfo.nickname || \"未设置\";\n if (nickname === \"未设置\") {\n nickname = \"👤 \" + nickname;\n } else {\n nickname = \"👤 \" + nickname;\n }\n \n var displayContent = \n \"\\n────────────\\n\" +\n \"🔑 密 钥 数 据 🔑\\n\" +\n \"────────────\\n\" +\n \"🔤 密 钥\\n\" + maskedKey + \"\\n\" +\n \"────────────\\n\" +\n \"📧 绑定邮箱\\n\" + maskedEmail + \"\\n\" +\n \"────────────\\n\" +\n nickname + \"\\n\" +\n \"────────────\\n\" +\n \"📊 累计阅读\\n\" + userInfo.total_usage + \" 次\\n\" +\n \"────────────\\n\" +\n \"📈 今日阅读\\n\" + userInfo.today_usage + \" 次\\n\" +\n \"────────────\\n\" +\n \"🔄 密钥状态\\n\" + statusDisplay + \"\\n\" +\n \"────────────\\n\" +\n \"📅 注册时间\\n\" + userInfo.created_at + \"\\n\";\n \n if (userInfo.is_banned) {\n displayContent += \"────────────\\n\" +\n \"🚫 封禁信息\\n\" + userInfo.banned_info + \"\\n\";\n }\n \n if (userInfo.last_used !== \"从未使用\") {\n displayContent += \"────────────\\n\" +\n \"⏰ 最后使用\\n\" + userInfo.last_used + \"\\n\";\n }\n \n if (userInfo.is_member) {\n displayContent += \"────────────\\n\" +\n \"💎 会员状态\\n🌟 尊贵会员\\n\";\n }\n \n displayContent += \"────────────\\n\" +\n \"✨ 查询完成\";\n \n java.longToast(displayContent);\n \n } else {\n var errorMsg = data.message || \"未知错误\";\n \n java.longToast(\n \"\\n────────────\\n\" +\n \"❌ 密 钥 查 询 失 败 ❌\\n\" +\n \"────────────\\n\" +\n \"错误信息:\\n\" + errorMsg + \"\\n\" +\n \"────────────\\n\" +\n \"可能原因:\\n\" +\n \"• 密钥不存在\\n\" +\n \"• 密钥已过期\\n\" +\n \"• 密钥填写不完整\\n\" +\n \"• 网络连接问题\\n\" +\n \"────────────\\n\" +\n \"\\n💡 请检查密钥是否正确\"\n );\n }\n \n } catch (e) {\n java.longToast(\n \"\\n────────────\\n\" +\n \"❌ 密 钥 查 询 失 败 ❌\\n\" +\n \"────────────\\n\" +\n \"网络连接异常\\n\" +\n \"────────────\\n\" +\n \"请检查:\\n\" +\n \"• 网络连接是否正常\\n\" +\n \"• 服务器地址是否正确\\n\" +\n \"• 密钥格式是否正确\\n\" +\n \"────────────\\n\" +\n \"\\n🔧 建议重新填写密钥\"\n );\n }\n}\n\nfunction get_cx() {\n try {\n var configStr = source.getVariable();\n if (!configStr || configStr.trim() == \"\") {\n configStr = JSON.stringify([{host: hosts[0]}]);\n source.setVariable(configStr);\n }\n var config = JSON.parse(configStr);\n if (!config || !Array.isArray(config) || config.length == 0) {\n config = [{host: hosts[0]}]; \n source.setVariable(JSON.stringify(config)); \n }\n var currentHost = getServerHost();\n var currentSource = (!('source' in config[0]) || !config[0].source) ? \"☁️ 聚合搜索 ☁️\" : (sourceList.find(item => item.v == config[0].source)?.m || config[0].source);\n var currentGender = config[0].gender == \"boy\" ? \" 男🧸频 \" : (config[0].gender == \"girl\" ? \" 女🎀频 \" : \" 未设置 \");\n var currentTone = config[0].sz ? \"音色\" + data[parseInt(config[0].sz) || 1][0] : \"默认音色\";\n var customSource = (source.getLoginInfoMap())['自定义源站'] || \"未设置\"; \n var customSourceStatus = hasValidCustomSource() ? \"🔗 \" + customSource : \"当前未设置,示例:米读,书旗\";\n var keyStatus = (source.getLoginInfoMap())['密钥'] ? \"✅ 已填写\" : \"❌ 未填写\";\n var username = \"未登录\";\n \n try {\n var cookie_ = String(cookie.getKey(\"fanqienovel.com\", \"sessionid\")) || (source.getLoginInfoMap())['番茄登录Token'];\n if (cookie_) username = JSON.parse(java.ajax(\"https:\/\/fanqienovel.com\/api\/user\/info\/v2,\" + JSON.stringify({\n method: \"GET\", headers: { \"Cookie\": \"sessionid=\" + cookie_ }\n }))).data?.name || \"已登录(未获取到用户名)\";\n } catch (e) {}\n\n java.longToast(\n \"\\n───────────────\\n\" +\n \"☑️ 配 置 状 态 ☑️\\n\" +\n \"───────────────\\n\" +\n \"🔘 服 务 器 🔘\\n\" + currentHost + \"\\n\" +\n \"───────────────\\n\" +\n \"🌋 书 源 🌋\\n\" + currentSource + \"\\n\" +\n \"───────────────\\n\" +\n \"🏔️ 偏 好 🏔️\\n\" + currentGender + \"\\n\" +\n \"───────────────\\n\" +\n \"🎧 AI 音 色 🎧\\n\" + currentTone + \"\\n\" +\n \"───────────────\\n\" +\n \"❇️ 自 定 义 源 ❇️\\n\" + customSourceStatus + \"\\n\" +\n \"───────────────\\n\" +\n \"🔆 密 匙 状 态 🔆\\n\" + keyStatus + \"\\n\" +\n \"───────────────\\n\" +\n \"🍅 登 录 用 户 🍅\\n\" + username + \"\\n\" +\n \"───────────────\\n\" +\n \"\\n✨ 提示:点击按钮切换配置\"\n );\n } catch(e) {\n source.setVariable(JSON.stringify([{host: hosts[0]}]));\n java.toast(\"⚠️ 配置初始化完成,请重试\");\n }\n}\n\nthis.sethost = function() {\n try {\n var config = JSON.parse(source.getVariable());\n if (!config || !Array.isArray(config) || config.length == 0) {\n config = [{host: hosts[0]}];\n }\n \n var currentHost = config[0].host || hosts[0];\n var currentIndex = hosts.indexOf(currentHost);\n var newIndex = (currentIndex + 1) % hosts.length;\n var newHost = hosts[newIndex];\n config[0].host = newHost;\n source.setVariable(JSON.stringify(config));\n java.toast(\"\\n切换到:\\n\" + newHost + \"\");\n } catch(e) {\n source.setVariable(JSON.stringify([{host: hosts[0]}]));\n java.toast(\"\\n已初始化:\" + hosts[0]);\n }\n};\n\nfor (var i = 0; i < sourceList.length; i++) {\n (function() {\n var s = sourceList[i];\n this[s.n] = function() {\n if (hasValidCustomSource()) {\n showCustomSourceAlert();\n return;\n } if (s.g) {\n updateConfig(\"source\", s.v, \"\\n已切换\" + (s.v ? '到❇️' : '') + s.m +\"❇️\\n刷新发现页生效\", s.g);\n } else {\n updateConfig(\"source\", s.v, \"\\n已切换\" + (s.v ? '到❇️' : '') + s.m +\"❇️\\n刷新发现页生效\");\n }\n };\n }).call(this);\n}\n\nfunction Map(e) {\n if (e == \"source\" && hasValidCustomSource()) {\n showCustomSourceAlert();\n return (source.getLoginInfoMap())['自定义源站'];\n }\n var infomap = source.getLoginInfoMap();\n const value = infomap[e];\n if (!value) {\n java.longToast(\"需要填写密钥\");\n }\n return value;\n}\n\nfunction checkCurrentServer() {\n try {\n var currentHost = getServerHost();\n java.longToast(`\\n\\n♻️正在检测当前服务器:${currentHost}\\n请稍等~`);\n let date1 = new Date().getTime();\n let html = java.ajax(currentHost);\n let date2 = new Date().getTime();\n let t = date2 - date1;\n let c = String(html).indexOf('云端');\n let time = t \/ 1000 + 's';\n let logTime = '【' + currentHost + '】\\n┋┋\\n' + '解析时间:' + time;\n \n if (c == -1 || t > 5000) {\n java.longToast('\\n💔【访问失败提示】\\n' + '┏┅━┅━┅━┅━┅┅━┅━┅┓\\n┋┋\\n' + logTime + '\\n┋┋\\n♣️当前接口无法访问(可能被墙)♣️\\n┋┋\\n请切换其他接口\/切换网络环境\\n┋┋' + '\\n┗┅━┅━┅━┅━┅┅━┅━┅┛');\n } else if (t < 1000) {\n java.longToast('\\n💖【网络环境优良】\\n' + '┏┅━┅━┅━┅━┅┅━┅━┅┓\\n┋┋\\n' + logTime + '\\n┋┋\\n❤️延迟低,推荐使用此接口❤️\\n┋┋\\n网络环境优良,请继续保持状态\\n┋┋' + '\\n┗┅━┅━┅━┅━┅┅━┅━┅┛');\n } else if (t >= 1000 && t < 2000) {\n java.longToast('\\n💛【网络环境一般】\\n' + '┏┅━┅━┅━┅━┅┅━┅━┅┓\\n┋┋\\n' + logTime + '\\n┋┋\\n♦️延迟一般,勉强可使用♦️\\n┋┋\\n请切换其他接口或切换网络环境\\n┋┋' + '\\n┗┅━┅━┅━┅━┅┅━┅━┅┛');\n } else if (t >= 2000 && t < 5000) {\n java.longToast('\\n💔【网络环境堪忧】\\n' + '┏┅━┅━┅━┅━┅┅━┅━┅┓\\n┋┋\\n' + logTime + '\\n┋┋\\n♠延迟过高,不建议使用♠\\n┋┋\\n请切换其他接口或切换网络环境\\n┋┋' + '\\n┗┅━┅━┅━┅━┅┅━┅━┅┛');\n }\n } catch(e) {\n java.longToast('\\n❌【服务器检测失败】\\n' + '┏┅━┅━┅━┅━┅┅━┅━┅┓\\n┋┋\\n检测过程中发生错误\\n┋┋\\n请检查网络连接或服务器状态\\n┋┋' + '\\n┗┅━┅━┅━┅━┅┅━┅━┅┛');\n }\n}\n\nfunction fq_login() {\n try { \tjava.startBrowserAwait(\"https:\/\/fanqienovel.com\/\", \"登录\");\n } catch (e) {\n java.toast(e);\n }\n try {\n cookie.removeCookie(\"snssdk.com\");\n } catch (e) {}\n \n let cookieHeader = String(cookie.getCookie('fanqienovel.com'));\njava.toast(cookieHeader);\n \n const match = cookieHeader.match(\/sessionid=([^;]+)\/);\n java.toast(match);\n let sessionId = match ? match[1] : null;\n\n let cookieString = sessionId ? \"sessionid=\" + sessionId : source.getLoginInfoMap()['番茄登录Token'] || \"\";\n java.toast(cookieString);\n let user;\n try {\n user = JSON.parse(java.ajax(\"https:\/\/fanqienovel.com\/api\/user\/info\/v2,\" + JSON.stringify({\n method: \"GET\",\n headers: { \"Cookie\": cookieString }\n }))).data.name;\n } catch (e) { \n java.log(e); \n }\n \n if (!cookieString || cookieString == \"sessionid=\" || !user) {\n java.toast(\"未获取到登录凭据,登录失败\");\n return false;\n }\n \n java.toast(\"\\n\\n欢迎 \" + user + \"\\n登录成功!\");\n return true;\n}\n\nfunction login(){}\nfunction logout() {\n cookie.removeCookie(\"fanqienovel.com\");\n cookie.removeCookie(\"snssdk.com\");\n const manualToken = (source.getLoginInfoMap())['番茄登录Token'];\n \n const hasValidToken = manualToken && String(manualToken).trim().length > 0;\n \n if (hasValidToken) {\n java.toast(\"请手动移除填写的Token\");\n } else {\n java.toast(\"退出登录成功\");\n }\n}\n\nfunction toggleParacomment() {\n const key = \"fqparas\";\n let status = java.get(key) ?? \"off\";\n if (status == \"on\") {\n java.put(key, \"off\");\n java.toast(\"\\n🍅番茄,🌙晋江,🐱七猫\\n❌段评已关闭\");\n } else {\n java.put(key, \"on\");\n java.toast(\"\\n🍅番茄,🌙晋江,🐱七猫\\n✅段评已开启\");\n }\n}\n\nfunction toggleBookSync() {\n let status = java.get(\"book_sync\") ?? \"off\";\n if (status == \"off\") {\n java.put(\"book_sync\", \"on\");\n java.toast(\"\\n🍅番茄小说\\n✅已开启 - 将自动同步阅读记录\");\n } else {\n java.put(\"book_sync\", \"off\");\n java.toast(\"\\n🍅番茄小说\\n❌已关闭 - 停止同步阅读记录\");\n }\n}\n\nfunction toggleFqdown(a) {\n let status = java.get(a) ?? \"off\";\n if (status == \"off\") {\n java.put(a, \"on\");\n java.toast(\"\\n仅支持番茄小说\\n下载模式✔️已开启\");\n } else {\n java.put(a, \"off\");\n java.toast(\"\\n仅支持番茄小说\\n下载模式❌已关闭\");\n }\n}\n\nfunction key() { java.startBrowserAwait(getServerHost() + '\/key', \"获取密钥\"); }\nfunction user_center() { java.startBrowserAwait(getServerHost() + '\/user_login', \"用户中心\");}\nfunction coffee() { java.startBrowserAwait(getServerHost() + '\/coffee?key=' + getSecretKey(), \"☕\");}\nfunction tj() { java.startBrowserAwait(getServerHost() +'\/login?key=' + getSecretKey(), \"书籍推荐\");}\nfunction version() { java.startBrowserAwait(getServerHost() + '\/version?id=' + localVersion, \"版本检查\"); }\nfunction boy() { updateConfig(\"gender\", \"boy\", \"\\n已设置为\\n🧸男频🧸\\n刷新发现页生效\"); }\nfunction girl() { updateConfig(\"gender\", \"girl\", \"\\n已设置为\\n🎀女频🎀\\n刷新发现页生效\"); }\nfunction recommend() { updateConfig(\"gender\", \"recommend\", \"\\n已设置为\\n❇️网友推荐❇️\\n刷新发现页生效\");\n updateConfig(\"source\", \"推荐\"); }",
"respondTime": 180000,
"ruleBookInfo": {
"author": "$.author",
"canReName": "true",
"coverUrl": "$.cover",
"downloadUrls": "{{$.book_url##\\[|\\]}}\n@js:\neval(String(source.loginUrl));\nlet url = java.base64Decode(result);\nlet book_id = url.match(\/\\d{19}\/)?.[0];\njava.toast('\\n如果提示Unexpected webFileData,请等待几秒后点击阅读导入…\\n在线阅读关闭下载模式后刷新即可');\n\nif (book_id && \"{{$.source}}\".toLowerCase() == \"番茄小说\") {\n getServerHost() + `\/down?key=${getSecretKey()}&book_id=${book_id}`;\n}",
"init": "<js>\nif (String(baseUrl).startsWith(\"data:\")) {\n let res = JSON.parse(java.hexDecodeToString(result));\n let {source: source_name, url: book_url, name: title} = res;\n \n\/\/ 仅在番茄小说且开启下载模式时设置\nif (source_name == \"番茄小说\" && \n\t java.get(\"fqdown\") == \"on\") {\n book.type = 128;\n}\n\n let request = getServerHost() +`\/details?source=${source_name}&url=${book_url}&name=${title}`;\n result = java.ajax(request);\n}\nresult;\n\/\/java.log(result);\n<\/js>\n$.data",
"intro": " \n✨ 源站:{{$.source}}{{\"\\n\"+\"\"}}\n{{$.desc}}\n<js>\nconst tomatoSources = [\"番茄小说\", \"番茄听书\", \"番茄畅听\"];\n\nif (tomatoSources.includes(\"{{$.source}}\")) {\n try {\n let bookId = getBookIdFromUrl(baseUrl);\n if (bookId) {\n let bookData = getBookData(bookId);\n \n if (bookData && bookData.tomato_book_status !== '3') {\n \/\/ 检查同步开关状态,只有开启时才执行同步\n let syncStatus = java.get(\"book_sync\") ?? \"off\";\n if (syncStatus == \"on\") {\n syncReadingHistory(bookData.book_id);\n }\n \n let bookName = bookData.original_book_name || bookData.book_name || \"\";\n let aliasName = bookData.book_flight_alias_name || bookData.sub_title || \"\";\n let createTime = bookData.create_time ? bookData.create_time.split('T')[0] : \"\";\n let updateTime = bookData.last_chapter_update_time ? java.timeFormat(parseInt(bookData.last_chapter_update_time)*1000) : \"\";\n let authorName = parseAuthors(bookData.original_authors || bookData.roles);\n let tags = bookData.pure_category_tags || bookData.tags || \"\";\n let readCount = bookData.read_count || \"0\";\n let bookStatus = bookData.book_search_visible == 'false' ? \"下架\" : \"正常\";\n let abstract = bookData.book_abstract_v2 || bookData.abstract || \"\";\n let copyrightInfo = bookData.copyright_info || \"\";\n \n let output = ` \n✨ 源站:{{$.source}}{{\"\\n\"+\"\"}}\n📕 源名:${bookName}`;\n \n if (aliasName) output += `\\n📖 别名:${aliasName}`;\n \n output += `\\n✏️ 开坑:${createTime} \n🧭 更新:${updateTime}‎\n👤 作者:${authorName}\n🏷️ 标签:${tags}\n👁️ 在线:${readCount}人在读\n🔗 书籍状态:${bookStatus}`;\n \/\/ 显示同步状态\n output += `\\n🔄 阅读记录:${syncStatus == \"on\" ? \"✅已开启同步\" : \"❌已关闭同步\"}`;\n if ([\"番茄听书\"].includes(\"{{$.source}}\")) {\n output += getToneInfo(bookData.book_id);\n }\n \n output += `{{\"\\n\"+\"\"}}\n📜 简介:${abstract}{{\"\\n\"+\"\"}}`;\n \n if (copyrightInfo) {\n output += `\\n📍 ${copyrightInfo.split(',')[0]}。`;\n }\n \n result = output;\n }\n }\n } catch (e) {\n }\n}\nresult;\n<\/js>",
"kind": "$.tags",
"lastChapter": "$.latestChapterTitle",
"name": "$.title",
"tocUrl": "<js>\nlet catalog = {\n source: result.source,\n url: result.book_url,\n name: result.title,\n tab: result.tab || \"novel\"\n};\n\nvar tabTips = {audio: '听书', video: '视频', comic: '漫画'}; \nif (tabTips[catalog.tab]) {\n java.toast('当前为' + tabTips[catalog.tab] + '模式');\n}\n\nyunurl = java.base64Encode(JSON.stringify(catalog));\nresult = `data:catalogUrl;base64,${yunurl},{\"type\":\"qingci\"}`;\n<\/js>",
"wordCount": "$.wordCount"
},
"ruleContent": {
"content": "@js:eval(function(p,a,c,k,e,r){e=function(c){return(c<62?'':e(parseInt(c\/62)))+((c=c%62)>35?String.fromCharCode(c+29):c.toString(36))};if('0'.replace(0,e)==0){while(c--)r[e(c)]=k[c];k=[function(e){return r[e]||e}];e=function(){return'([3-7bcfh-mo-rt-wyzB-RTUW-Z]|1\\\\w)'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\\\b'+e(c)+'\\\\b','g'),k[c]);return p}('eval(String(R.loginUrl));q T=deviceType();3 h=4.7.j(\/h=(\\\\d+)\/)?.[1];3 k=4.7.j(\/k=(\\\\d+)\/)?.[1];3 r=4.7.j(\/r=(\\\\d+)\/)?.[1];3 t=4.7.j(\/t=(\\\\d+)\/)?.[1];3 w=G;3 y=G;5(\/七猫小说\/.c(4.7)){y=4.7.j(\/y=([a-f0-9]+)\/)?.[1]}5(\/七猫短剧\/.c(4.7)){l{q U=4.7.j(\/7=([^&]+)\/)?.[1];5(U){q 1c=b.W(U);w=1c.j(\/w=(\\\\d+)\/)?.[1]}}m(e){}}3 H=I()+baseUrl.split(\\',\\')[0].J(\/^https?:\\\\\/\\\\\/[^\\\\\/]+\/,\\'\\')+`&1d=${1e()}&i=10`;5(\/晋江\/.c(4.7)&&r&&t){H+=`&r=${r}&t=${t}`;3 X=JJtoken()||\"\";5(X){H+=`&token=${X}`}}3 u=z.K(b.Y(H));3 6=u.o.L;5((\/番茄短剧\/.c(4.7)&&h)||(\/七猫短剧\/.c(4.7)&&w)){l{q 1f=\/番茄短剧\/.c(4.7)?\\'h\\':\\'w\\';q 1g=\/番茄短剧\/.c(4.7)?h:w;q 1h=I()+`\/player?${1f}=${1g}&1d=${1e()}`;5(Z.1i==4.1j){b.11(1h,4.12||\\'视频播放\\');b.B(\\'视频加载中...\\')}6=`▶️刷新正文进入播放界面`}m(e){}}5(u.o.M==\"video\"&&Z.1i==4.1j&&!\/番茄短剧|七猫短剧\/.c(4.7)){3 1k=b.W(6.1l());b.11(1k,4.12||\\'视频播放\\');b.B(\\'视频加载中...\\');6=`▶️刷新正文进入播放界面`}5(\/^[A-Za-z0-9+\/=]+$\/.c(6)){5(T){with(p JavaImporter(13.b.1m.zip,13.b.io,13.android.1m)){3 14=1n.decode(6,1n.DEFAULT);l{3 15=p 1o(p 1p(p InflaterInputStream(p 1q(14)),\"1r-8\"));3 L=\"\";1s((N=15.1t())!=G)L+=N+\"\\\\n\";15.1u();6=L}m(e){l{3 16=p 1o(p 1p(p 1q(14),\"1r-8\"));3 17=\"\";1s((N=16.1t())!=G)17+=N+\"\\\\n\";16.1u();6=17}m(e2){}}}}O{l{6=b.W(6)}m(e){}}}5(4.7.1v(\\'P=\\')){3 18=4.7.j(\/P=([^&]*)\/)?.[1]||\\'\\';5(18&&k){l{3 19=z.K(b.Y(I()+`\/audio?P=${18}&item_ids=${k}`));5(19.o?.[0]?.1w)6=19.o[0].1w}m(e){}}}5(b.1x(\"fqparas\")==\"on\"&&b.1x(\"fqdown\")!=\"on\"){6=6.J(\/\\\\n+\/g,\"\\\\n\");5(\/晋江\/.c(4.7)&&r&&t){6=1a(6,r,t,\\'jj\\')}O 5(\/七猫小说\/.c(4.7)&&h&&k&&y){6=1a(6,h,k,\\'qm\\',y)}O 5(\/番茄小说\/.c(4.7)&&h&&k&&!4.7.1v(\\'P=\\')){6=1a(6,h,k,\\'fq\\')}}5(T){l{3 C=z.K(R.1z());3 Q=Date.Q();3 1A=2*60*60*1000;5(!C[0].1b||(Q-C[0].1b)>1A){C[0].1b=Q;R.setVariable(z.stringify(C));3 f=z.K(b.Y(I()+\"\/i?vid=sy\"));5(f.i){5(f.1C&&f.1C.toString().toLowerCase()==\"true\"&&f.i>D){b.11(i());6=\"请更新书源!下载页面已打开。\"}3 v=f.i>D?`\\\\n✨发现新版本V${f.i}(当前V${D})`+(f.v?`\\\\n${f.v}`:\\'\\'):f.i<D?`\\\\n⚠️版本异常\\\\n本地V${D}>云端V${f.i}\\\\n请检查书源配置`:\\'\\';5(v)b.B(v)}O 5(f.v){b.B(f.v)}}}m(e){b.B(\"⚠️ 版本检查失败: \"+e.message)}}3 M=u.o.M||\"\";3 E=u.o.E||\"\";5(M==\"novel\"&&E&&E.1l()!==\"\"){6+=\"\\\\n\"+E}5(\/晋江\/.c(4.7)&&u.o.1D){l{q 1E=Z.1z(\"custom\");3 F=\/关闭作话\/.c(1E)?\"\":u.o.1D;F=F.J(\/(?:.*(?:蟹蟹|扔了|感谢|投出|灌溉营养液|送的|谢谢).*?(?:手榴|月票|霸王票|小天使|火箭炮|深水鱼雷|浅水炸弹|地雷|营养液)([xX]\\\\d+)*.*|非常感谢大家对我的支持,我会继续努力的!|.*?瓶[;~]|\".*?\"(?:营养液|手榴弹|月票|霸王票|小天使|火箭炮|深水鱼雷|浅水炸弹|地雷)x\\\\d+|读者.*?(月石|营养液).*|地雷感谢:[\\\\s\\\\S]+)\/g,\\'\\');5(\/[\\\\u4e00-\\\\u9fa5]\/.c(F)){6+=`\\\\n【📢作者有话说】\\\\n${F}`}}m(e){}}6=6.J(4.12,\\'\\');',[],103,'|||let|chapter|if|result|url||||java|test|||versionData||book_id|version|match|item_id|try|catch||data|new|const|novelId||chapterId|response|msg|qm_id||content_md5|JSON||toast|config|localVersion|notice|saybody|null|contentUrl|getServerHost|replace|parse|content|tab|line|else|tone_id|now|source||isAndroid|urlParam||base64Decode|jjToken|ajax|book||startBrowser|title|Packages|bytes|reader|rawReader|rawContent|toneId|apiData|getComments|lastUpdateCheck|decodedUrl|key|getSecretKey|paramName|paramValue|videoUrl|durChapterIndex|index|normalVideoUrl|trim|util|Base64|BufferedReader|InputStreamReader|ByteArrayInputStream|UTF|while|readLine|close|includes|main_url|get||getVariable|fourHours||force|sayBody|bbb'.split('|'),0,{}))",
"imageStyle": "TEXT",
"payAction": "cid = baseUrl.match(\/chapterId=(\\d+)\/)[1];\nnid = baseUrl.match(\/novelId=(\\d+)\/)[1];\ntoken = java.base64Decode(JJtoken());\nenbody = String(Date.now())+\":\"+token+\":\"+nid;\nsign = encode(enbody)\nbody = \"versionCode=287&novelId=\"+nid+\"&chapters=\"+cid+\"&orderType=15&isautoBuy=0&autosubscribeStatus=0¬UseBackpoint=&sign=\"+sign;\nurl = \"https:\/\/android.jjwxc.net\/androidapi\/buy,\"\npost = {\n\t\"method\":\"POST\",\n\t\"body\":String(body)\n\t}\n a = java.ajax(url+JSON.stringify(post));\n \n if(\/购买成功\/.test(JSON.parse(a).message)){\n \tjava.longToast(JSON.parse(a).message);\n \tresult = true\n \t}else{\n \t\tmm = JSON.parse(a).message==\"\"?JSON.parse(a).popupdata.popupmessage+\"\\n❗️进晋江app进行一次验证后❗️才能在阅读app使用购买功能\":JSON.parse(a).message;\n \t\tjava.longToast(mm);\n \t\t}",
"replaceRegex": "##\\{\\!\\-\\- PGC_VOICE\\:.*\\-\\-\\}|\\(本章完\\)"
},
"ruleExplore": {
"author": "$.author",
"bookList": "<js>\nfunction getBookIdFull(url) {\n const {java} = this;\n let $ = JSON.parse(url).data;\n let arr = [];\n if ($.book_shelf_info && $.book_shelf_info.length > 0) {\n arr = $.book_shelf_info.map(item => item.book_id);\n } else if ($.data_list && $.data_list.length > 0) {\n arr = $.data_list.map(item => item.book_id_str);\n } else {\n java.toast(\"获取 book_id 失败,你可能需要登录!\");\n }\n return arr;\n}\n\nlet session = getSessionId()\n\ngetShelf = () => {\n let book_shelf_info = java.ajax(getServerHost() + \"\/book_shelf?session=\" + session)\n \n bid = getBookIdFull(book_shelf_info)\n \n let id_list = splitArray(bid, 200)\n let urls = []\n id_list.forEach(i => {\n urls.push(getServerHost() +\"\/detail?book_id=\" + i.join(\",\") + \"&sessionid=\" + session)\n })\n res = java.ajaxAll(urls)\n\n let resp = {book_info: []}\n res.forEach(r => {\n resp.book_info = resp.book_info.concat(JSON.parse(r.body()).data)\n })\n\n return resp\n}\n\nfunction getByGroupName(name) {\n let book_shelf_info = JSON.parse(java.ajax(getServerHost() +\"\/book_shelf?session=\" + session))\n \n let group_bookids = {\n \"未分组\": []\n }\n book_shelf_info.data.book_shelf_info.forEach(i => {\n if (!group_bookids[i.group_name ? i.group_name : \"未分组\"]) group_bookids[i.group_name] = []\n group_bookids[i.group_name ? i.group_name : \"未分组\"].push(i.book_id)\n })\n \n if (!group_bookids[decodeURIComponent(name)]) return {data: []}\n \n let book_ids = splitArray(group_bookids[decodeURIComponent(name)], 200)\n let urls = []\n\n book_ids.forEach(i => { \turls.push(\"https:\/\/api5-normal-sinfonlineb.fqnovel.com\/reading\/bookapi\/multi-detail\/v\/?aid=1967&iid=1&version_code=999&book_id=\" + i.join(\",\"))\n })\n \n res = java.ajaxAll(urls)\n\n let resp = {book_info: []}\n res.forEach(r => {\n resp.book_info = resp.book_info.concat(JSON.parse(r.body()).data)\n })\n\n return resp\n}\n\nfunction getByTabIndex(index) {\n let url = _mlsec.requestHeader(\n \"bookmall\/tab\",\n \"version_name=5.8.9.32\",\n null,\n \"sessionid=\" + session\n )\n let all = JSON.parse(java.ajax(url))\n let tab = all.data.tab_item[0].cell_data[index].cell_data\n if (!tab) tab = []\n let bookList = []\n for (let i of tab) {\n bookList = bookList.concat(i.book_data)\n }\n return { book_info: bookList }\n}\n\nfunction normalizeResponse(data) {\n if (data.book_info) return data.book_info;\n if (data.data && data.data.book_info) return data.data.book_info;\n if (data.data && Array.isArray(data.data)) return data.data;\n if (data.list) return data.list;\n if (data.book_list) return data.book_list;\n if (data.data && data.data.publication_list) return data.data.publication_list;\n if (data.result) return data.result;\n if (data.data && data.data.result) return data.data.result;\n if (data.data && data.data.cell_view && data.data.cell_view.book_data) {\n return data.data.cell_view.book_data;\n }\n if (data.data && data.data.records) return data.data.records;\n if (data.data && data.data.list) return data.data.list;\n return [];\n}\n\nif (baseUrl.endsWith(\"bookshelf\")) {\n result = getShelf(\"bookshelf\/info\")\n} else if(\/read_history\/.test(baseUrl)) {\n let pageMatch = baseUrl.match(\/page=(\\d+)\/);\n let page = pageMatch ? pageMatch[1] : '1';\n \n let history_response = java.ajax(getServerHost() + \"\/read_history?session=\" + session + \"&page=\" + page)\n let history_data = JSON.parse(history_response)\n \n if (history_data.data && history_data.data.data_list) {\n let book_ids = history_data.data.data_list.map(item => item.book_id_str)\n \n let id_list = splitArray(book_ids, 200)\n let urls = []\n id_list.forEach(i => {\n urls.push(\"https:\/\/api5-normal-sinfonlineb.fqnovel.com\/reading\/bookapi\/multi-detail\/v\/?aid=1967&iid=1&version_code=999&book_id=\" + i.join(\",\"))\n })\n \n let resp = {book_info: []}\n urls.forEach(url => {\n let detailResponse = java.ajax(url)\n let detailData = JSON.parse(detailResponse)\n if (detailData.data) {\n resp.book_info = resp.book_info.concat(detailData.data)\n }\n })\n \n result = resp\n } else {\n result = {book_info: []}\n }\n} else {\n let w = baseUrl.split(\"\/\")\n if (baseUrl.includes(\"groupName\")) {\n result = getByGroupName(w[w.length - 1])\n } else if (baseUrl.includes(\"tab\")) {\n result = getByTabIndex(parseInt(w[w.length - 1]))\n } else {\n result = JSON.parse(result)\n if (result.data && result.data.data) {\n result = {book_info: result.data.data}\n }\n }\n}\n\nlet normalizedData = normalizeResponse(result);\n\nJSON.stringify({data: normalizedData})\n<\/js>\n$.data[*]",
"bookUrl": "<js>\nlet tjurl = result.tjurl;\nlet source = result.source;\nlet book_url = result.book_url;\nlet title = result.title || result.book_name;\nlet book_id = result.book_id || result.series_id;\n\nlet detail = {\n source: source,\n url: book_url,\n name: title\n};\n\nif (tjurl && tjurl.trim() !== \"\") {\n let sourceMatch = tjurl.match(\/source=([^&]*)\/);\n let urlMatch = tjurl.match(\/url=([^&]*)\/);\n let nameMatch = tjurl.match(\/name=([^&]*)\/);\n \n detail.source = sourceMatch ? sourceMatch[1] : \"\";\n detail.url = urlMatch ? urlMatch[1] : java.base64Encode(tjurl.trim());\n detail.name = nameMatch ? nameMatch[1] : \"\";\n} \nelse if (book_id && \/^\\d{19}$\/.test(book_id)) {\n detail.url = java.base64Encode(`https:\/\/api5-normal-sinfonlineb.fqnovel.com\/reading\/bookapi\/multi-detail\/v\/?aid=1967&iid=1&version_code=999&book_id=${book_id}`);\n detail.source = \"番茄小说\";\n} else if (book_url) {\n detail.url = java.base64Encode(book_url);\n}\n\nlet yunurl = java.base64Encode(JSON.stringify(detail));\n`data:detailsUrl;base64,${yunurl},{\"type\":\"qingci\"}`;\n<\/js>",
"coverUrl": "$.thumb_url||$.audio_thumb_uri||$.thumbUri||$.cover",
"intro": "$.recommend_reason||$.abstract||$.desc",
"kind": "$.tags||$.book_type&&$.progress&&$.publisher&&$.category&&$.is_valid",
"lastChapter": "$.lastChapterTitle||$.last_chapter_title||$.latestChapterTitle",
"name": "$.book_name||$.bookName||$.title",
"wordCount": "$.word_number||$.WordsCount"
},
"ruleSearch": {
"author": "$.author",
"bookList": "$.data",
"bookUrl": "<js>\nlet source = result.source;\nlet book_url = result.book_url;\nlet title = result.title;\n\nlet detail = {\n source: source,\n url: book_url,\n name: title\n};\n\nyunurl = java.base64Encode(JSON.stringify(detail));\n`data:detailsUrl;base64,${yunurl},{\"type\":\"qingci\"}`;\n<\/js>",
"checkKeyWord": "我在精神病院学斩神@番茄小说",
"coverUrl": "$.cover",
"intro": "$.desc",
"kind": "$.tags",
"lastChapter": "{{$.source}}·{{$.latestChapterTitle}}",
"name": "$.title",
"wordCount": "$.wordCount"
},
"ruleToc": {
"chapterList": "<js>\nlet res = JSON.parse(java.hexDecodeToString(result));\nlet catalog = {\n source: res.source,\n url: res.url,\n name: res.name,\n tab: res.tab || \"novel\"\n};\n\nvar tabTips = {audio: '听书', video: '视频', comic: '漫画'}; \nif (tabTips[catalog.tab]) {\n java.toast('当前为' + tabTips[catalog.tab] + '模式');\n}\n\nlet op = {\n method: \"POST\",\n body: {\n source: catalog.source,\n url: res.url,\n name: res.name\n }\n};\n\nlet catalogUrl = getServerHost() +`\/catalog,${JSON.stringify(op)}`;\nlet response = java.ajax(catalogUrl);\n \/\/java.log(response);\n\nlet device = deviceType() ? 'android' : 'ios';\nif (device == 'android') {\n var typeMap = {audio:32, comic:64};\n book.type = catalog.tab in typeMap ? typeMap[catalog.tab] : 8;\n} else {\n var typeMap = {audio:1, comic:2, video:3};\n book.type = catalog.tab in typeMap ? typeMap[catalog.tab] : 0;\n}\n\nJSON.parse(response).data.map((x) => {\n const isFanqieApp = ['番茄小说', '番茄短剧', '番茄听书', '番茄畅听'].includes(catalog.source);\n const urlToMatch = x.url || catalog.url;\n const bookId = urlToMatch.match(\/book_id=(\\d{19})\\b\/)?.[1] || null;\n const itemId = urlToMatch.match(\/item_id=(\\d+)\/)?.[1] || null;\n \n let contentUrl = getServerHost() + `\/chapter?cid=${x.cid}&source=${catalog.source}&device=${device}`;\n \n if (!isFanqieApp) {\n contentUrl += `&url=${catalog.url}`;\n } else {\n contentUrl += `&book_id=${bookId}&item_id=${itemId}`;\n \n if (['番茄听书', '番茄畅听'].includes(catalog.source)) {\n const toneId = Number((source.getLoginInfoMap())['听书AI音色编号']) || 1;\n contentUrl += `&tone_id=${toneId}`;\n }\n }\n \n if (catalog.source == '七猫小说') {\n const qmBookId = x.url.match(\/book_id=(\\d+)\/)?.[1] || null;\n const qmChapterId = x.url.match(\/item_id=(\\d+)\/)?.[1] || null;\n \n contentUrl += `&${x.url}`;\n \n if (qmBookId && qmChapterId) {\n x.url = contentUrl + `,{\"type\":\"qingci\",\"js\":\"book ? result : '${getServerHost()}\/qm_comment?book_id=${qmBookId}&item_id=${qmChapterId}'\"}`;\n } else {\n x.url = contentUrl;\n }\n } else {\n let commentPart = '';\n \n if (catalog.source == '晋江') {\n const novelIdMatch = x.url.match(\/novelId=(\\d+)\/);\n const chapterIdMatch = x.url.match(\/chapterId=(\\d+)\/);\n if (novelIdMatch && chapterIdMatch) {\n const novelId = novelIdMatch[1];\n const chapterId = chapterIdMatch[1];\n commentPart = `,{\"type\":\"qingci\",\"js\":\"book ? result : '${getServerHost()}\/jj_comment?novelId=${novelId}&chapterId=${chapterId}'\"}`;\n }\n } else if (catalog.source == '番茄小说') {\n commentPart = `,{\"type\":\"qingci\",\"js\":\"book ? result : '${getServerHost()}\/comment?sessionid=${getSessionId()}&item_id=${itemId}&book_id=${bookId}'\"}`;\n }\n \n x.url = contentUrl + commentPart;\n }\n \n return x;\n});\n<\/js>",
"chapterName": "title",
"chapterUrl": "url",
"isVip": "isVip",
"updateTime": "tag"
},
"searchUrl": "<js>\nlet config = (() => {\n try {\n let cfg = JSON.parse(source.getVariable())[0] || {};\n if (!cfg.host || !cfg.gender) {\n cfg = {gender: \"boy\", host: getServerHost()};\n source.setVariable(JSON.stringify([cfg]));\n java.toast(\"配置初始化完成\");\n }\n return cfg;\n } catch(e) {\n let cfg = {gender: \"boy\", host: getServerHost()};\n source.setVariable(JSON.stringify([cfg]));\n return cfg;\n }\n})();\n\nvar sourceVal = '';\nvar customSource = '';\n\ntry {\n customSource = (source.getLoginInfoMap())['自定义源站'];\n} catch(e) {\n customSource = '';\n}\n\nif (customSource !== undefined && customSource !== null && String(customSource).trim() !== \"\") {\n sourceVal = String(customSource).trim();\n} else if (config.source) {\n sourceVal = config.source;\n}\n\nvar param = sourceVal ? '&source=' + encodeURIComponent(sourceVal) : '';\nvar searchUrl = getServerHost() + '\/search?page={{page}}&key={{key}}' + param;\n\nif (key && key.length == 19 && !Number.isNaN(parseInt(key))) {\n var u = getServerHost() +`\/detail?book_id=${key}`;\n var kdy = java.base64Encode(u);\n var response = JSON.parse(java.ajax(u));\n var nam = response.data.book_name || (response.data[0] && response.data[0].book_name) || '未知书名';\n result = getServerHost() + `\/details?url=${kdy}&source=番茄小说&name=${encodeURIComponent(nam)}`;\n} else {\n result = searchUrl;\n}\n<\/js>",
"weight": 0
}