[ { "bookSourceComment": "Linpx 书源(单篇)(更新📆:2025-02-15)\n\n可用功能:✅搜索✅发现✅添加网址✅订阅源 \n搜索小说:✅单篇❌系列✅作者✅标签✅链接 \n发现小说:✅推荐作者✅最近更新 \n添加网址:✅Linpx 链接✅Linpx 分享链接 \n搜索网址:✅Linpx 链接✅Pixiv 原文链接 \n订阅用法:点击订阅源打开小说,点击【添加到书架】按钮添加小说到书架 \n温馨提示:⚠️Linpx 搜索暂时不支持系列小说名称,添加系列小说后,无法再次通过搜索获取并更新目录\n\n书源发布:兽人阅读频道 https://t.me/FurryReading \n项目地址:https://github.com/windyhusky/PixivSource \n使用教程:https://github.com/windyhusky/PixivSource/blob/main/doc/Linpx.md\n\n规则订阅:Import 订阅源\nhttps://cdn.jsdelivr.net/gh/windyhusky/PixivSource@main/import.json\nhttps://raw.githubusercontent.com/windyhusky/PixivSource/main/import.json\n\n⚙️ 书源设置:\n书源管理 - 编辑书源 - 基本 - 变量说明 - 修改并保存", "bookSourceGroup": "🔞 Pixiv", "bookSourceName": "Linpx", "bookSourceType": 0, "bookSourceUrl": "https://furrynovel.ink", "bookUrlPattern": ".*(https?://)?(api\\.|www\\.)?(furrynovel\\.(ink|xyz))/(pn|pixiv/novel)/\\d+(/cache)?.*", "customOrder": 2, "enabled": true, "enabledCookieJar": false, "enabledExplore": true, "exploreUrl": "@js:\nli = [\n {\"推荐作者\": \"https://api.furrynovel.ink/fav/user/cache\"},\n {\"最新小说\": \"https://api.furrynovel.ink/pixiv/novels/recent/cache?page={{page}}\"}\n]\n\n// 格式化发现地址\nli.forEach(item => {\n item.title = Object.keys(item)[0]\n item.url = Object.values(item)[0]\n delete item[Object.keys(item)[0]]\n item.style = {}\n item.style.layout_flexGrow = 1\n item.style.layout_flexBasisPercent = 0.15\n})\nJSON.stringify(li)", "header": "{\"referer\":\"https://furrynovel.ink/\"}", "lastUpdateTime": 1739548800251, "loginCheckJs": "var util = {}\n\nfunction objStringify(obj) {\n return JSON.stringify(obj, (n, v) => {\n if (typeof v == \"function\")\n return v.toString();\n return v;\n });\n}\n\nfunction publicFunc() {\n let u = {}, settings = {}\n java.log(String(source.bookSourceComment).split(\"\\n\")[0]) // 输出书源信息\n java.log(`手动更新时间:${java.timeFormat(source.lastUpdateTime)}`) // 输出书源信息\n settings = JSON.parse(String(source.variableComment).match(RegExp(/{([\\s\\S]*?)}/gm)))\n if (settings !== null) {\n java.log(\"⚙️ 使用自定义设置\")\n } else {\n settings = {}\n settings.SHOW_ORIGINAL_NOVEL_LINK = true\n settings.REPLACE_BOOK_TITLE_MARKS = true\n settings.MORE_INFO_IN_DESCRIPTION = false\n settings.SHOW_NOVEL_CAPTIONS = true\n settings.SHOW_NOVEL_COMMENTS = true\n settings.DEBUG = false\n java.log(\"⚙️ 使用默认设置(无自定义设置 或 自定义设置有误)\")\n }\n u.SHOW_ORIGINAL_NOVEL_LINK = settings.SHOW_ORIGINAL_NOVEL_LINK // 目录处显示小说源链接,但会增加请求次数\n u.REPLACE_BOOK_TITLE_MARKS = settings.REPLACE_BOOK_TITLE_MARKS // 注音内容为汉字时,替换为书名号\n u.MORE_INFO_IN_DESCRIPTION = settings.MORE_INFO_IN_DESCRIPTION // 书籍简介显示更多信息\n u.SHOW_NOVEL_CAPTIONS = settings.SHOW_NOVEL_CAPTIONS // 书籍简介显示更多信息\n u.SHOW_NOVEL_COMMENTS = settings.SHOW_NOVEL_COMMENTS // 书籍简介显示更多信息\n u.DEBUG = settings.DEBUG // 调试模式\n\n if (u.DEBUG === true) {\n java.log(JSON.stringify(settings, null, 4))\n java.log(`DEBUG = ${u.DEBUG}`)\n }\n\n\n u.debugFunc = (func) => {\n if (util.DEBUG) {\n func()\n }\n }\n u.cacheGetAndSet = (key, supplyFunc) => {\n let v = cache.get(key)\n if (v === undefined || v === null) {\n v = JSON.stringify(supplyFunc())\n // 缓存10分钟\n cache.put(key, v, 600)\n }\n return JSON.parse(v)\n }\n u.getAjaxJson = (url) => {\n return util.cacheGetAndSet(url, () => {\n return JSON.parse(java.ajax(url))\n })\n }\n u.getWebviewJson = (url) => {\n return util.cacheGetAndSet(url, () => {\n let html = java.webView(null, url, null)\n return JSON.parse((html.match(new RegExp(\">\\\\[{.*?}]<\"))[0].replace(\">\", \"\").replace(\"<\", \"\")))\n })\n }\n\n u.urlNovelUrl = (novelId) => {\n return `https://furrynovel.ink/pixiv/novel/${novelId}/cache`\n }\n u.urlNovelDetailed = (novelId) => {\n return `https://api.furrynovel.ink/pixiv/novel/${novelId}/cache`\n }\n u.urlNovelsDetailed = (nidList) => {\n return `https://api.furrynovel.ink/pixiv/novels/cache?${nidList.map(v => \"ids[]=\" + v).join(\"&\")}`\n }\n u.urlNovelComments = (novelId) => {\n return `https://api.furrynovel.ink/pixiv/novel/${novelId}/comments`\n }\n u.urlNovel = (novelId) => {\n if (util.SHOW_ORIGINAL_NOVEL_LINK) {\n return util.urlNovelUrl(novelId)\n } else {\n return util.urlNovelDetailed(novelId)\n }\n }\n\n // u.urlSeriesUrl = function (id) {\n // return `https://furrynovel.ink/pixiv/series/${id}/cache`\n // }\n u.urlSeriesUrl = (seriesId) => {\n return `https://www.pixiv.net/novel/series/${seriesId}`\n }\n u.urlSeriesDetailed = (seriesId) => {\n return `https://api.furrynovel.ink/pixiv/series/${seriesId}/cache`\n }\n u.urlSeries = (seriesId) => {\n if (util.SHOW_ORIGINAL_NOVEL_LINK) {\n return util.urlSeriesUrl(seriesId)\n } else {\n return util.urlSeriesDetailed(seriesId)\n }\n }\n\n u.urlUserUrl = (userId) => {\n return `https://furrynovel.ink/pixiv/user/${userId}/cache`\n }\n u.urlUserDetailed = (userId) => {\n return `https://api.furrynovel.ink/pixiv/user/${userId}/cache`\n }\n u.urlUsersDetailed = (uidList) => {\n return `https://api.furrynovel.ink/pixiv/users/cache?${uidList.map(v => \"ids[]=\" + v).join(\"&\")}`\n }\n\n u.urlSearchNovel = (novelName) => {\n return `https://api.furrynovel.ink/pixiv/search/novel/${novelName}/cache`\n }\n u.urlSearchUsers = (userName) => {\n return `https://api.furrynovel.ink/pixiv/search/user/${userName}/cache`\n }\n\n u.urlCoverUrl = (pxImgUrl) => {\n return `https://pximg.furrynovel.ink/?url=${pxImgUrl}&w=800`\n }\n // u.urlIllustUrl = function (illustId) {\n // return `https://www.pixiv.net/artworks/${illustId}`\n // }\n u.urlIllustOriginal = (illustId, order) => {\n // 使用 pixiv.cat 获取插图\n let illustOriginal = `https://pixiv.re/${illustId}.png`\n // let illustOriginal = `https://pixiv.nl/${illustId}.png`\n if (order >= 1) {\n illustOriginal = `https://pixiv.re/${illustId}-${order}.png`\n // illustOriginal = `https://pixiv.nl/${illustId}-${order}.png`\n }\n return illustOriginal\n }\n\n // 将多个长篇小说解析为一本书\n u.combineNovels = function(novels) {\n return novels.filter(novel => {\n // 单本直接解析为一本书,需要判断是否为 null\n if (novel.seriesId === undefined || novel.seriesId === null) {\n return true\n }\n //集合中没有该系列解析为一本书\n if (!seriesSet.has(novel.seriesId)) {\n seriesSet.add(novel.seriesId)\n return true\n }\n return false\n })\n }\n\n // 处理 novels 列表\n u.handNovels = function (novels){\n novels.forEach(novel => {\n if (novel.tags === undefined) {\n novel.tags = []\n }\n // novel.id = novel.id\n // novel.title = novel.title\n // novel.userName = novel.userName\n if (novel.seriesId === undefined || novel.seriesId === null) {\n // novel.tags = novel.tags\n novel.tags.unshift(\"单本\")\n novel.textCount = novel.length\n novel.latestChapter = novel.title\n novel.description = novel.desc\n // novel.coverUrl = novel.coverUrl\n novel.detailedUrl = util.urlNovel(novel.id)\n\n } else {\n java.log(`正在获取系列小说:${novel.seriesId}`)\n let series = util.getAjaxJson(util.urlSeriesDetailed(novel.seriesId))\n novel.id = series.novels[0].id\n novel.title = novel.seriesTitle\n novel.tags = series.tags\n // novel.tags = novel.tags.concat(series.tags)\n novel.tags = novel.tags.concat(series.novels[0].tags)\n novel.tags.unshift(\"长篇\")\n novel.tags = Array.from(new Set(novel.tags))\n novel.textCount = null // 无数据\n novel.createDate = null // 无数据\n novel.latestChapter = series.novels[series.novels.length-1].title\n novel.description = series.caption\n // 后端目前没有系列的 coverUrl 字段\n // novel.coverUrl = series.coverUrl\n novel.coverUrl = series.novels[0].coverUrl\n novel.detailedUrl = util.urlNovel(novel.id)\n // novel.detailedUrl = util.urlSeries(novel.id)\n\n if (series.caption === \"\") {\n let firstNovel = util.getAjaxJson(util.urlNovelDetailed(novel.id))\n novel.createDate = firstNovel.createDate\n if (firstNovel.length > 0) {\n novel.description = firstNovel[0].desc\n } else {\n novel.description = \"该小说可能部分章节因为权限或者被删除无法查看\"\n }\n }\n }\n })\n return novels\n }\n\n // 小说信息格式化\n u.formatNovels = function (novels) {\n novels.forEach(novel => {\n novel.title = novel.title.replace(RegExp(/^\\s+|\\s+$/g), \"\")\n novel.tags = novel.tags.join(\",\")\n novel.coverUrl = util.urlCoverUrl(novel.coverUrl)\n // novel.readingTime = `${novel.readingTime / 60} 分钟`\n novel.createDate = util.dateFormat(novel.createDate);\n // novel.updateDate = this.dateFormat(novel.updateDate);\n if (util.MORE_INFO_IN_DESCRIPTION) {\n novel.description = `\\n书名:${novel.title}\\n作者:${novel.userName}\\n标签:${novel.tags}\\n上传:${novel.createDate}\\n简介:${novel.description}`\n } else {\n novel.description = `\\n${novel.description}\\n上传时间:${novel.createDate}`\n }\n })\n return novels\n }\n\n // 从网址获取id,返回单篇小说 res,系列返回首篇小说 res\n u.getNovelRes = function (result) {\n let novelId = 0, res = {}\n // 兼容搜索直接输入链接\n pattern = \"(https?://)?(api\\\\.|www\\\\.)?((furrynovel\\\\.(ink|xyz))|pixiv\\\\.net)(/ajax)?/(pn|(pixiv/)?novel)/(show\\\\.php\\\\?id=|series/)?\\\\d+(/cache)?\"\n // pattern = String(bookSourceUrl).replace(\".*\", \"\")\n if (RegExp(pattern).test(result) && !(result.startsWith(\"\"))) {\n baseUrl = result.match(RegExp(pattern))[0]\n result = \"\"\n java.log(`匹配链接:${baseUrl}`)\n }\n\n let isHtml = result.startsWith(\"\")\n if (isHtml) {\n let id = baseUrl.match(new RegExp(\"\\\\d+\"))[0]\n let pattern = \"(https?://)?(www\\\\.)?pixiv\\\\.net(/ajax)?/novel/(series/)?\\\\d+\"\n let isSeries = baseUrl.match(new RegExp(pattern))\n if (isSeries) {\n java.log(`系列ID:${id}`)\n res = util.getAjaxJson(util.urlSeriesDetailed(id))\n } else {\n let pattern = \"((furrynovel\\\\.(ink|xyz))|pixiv\\\\.net)/(pn|(pixiv/)?novel)/(show\\\\.php\\\\?id=)?\\\\d+\"\n let isNovel = baseUrl.match(new RegExp(pattern))\n if (isNovel) {\n novelId = id\n }\n }\n } else {\n res = JSON.parse(result)\n }\n if (res.total !== undefined && res.total !== null) {\n novelId = res.novels[0].id\n }\n if (novelId) {\n java.log(`匹配小说ID:${novelId}`)\n res = util.getAjaxJson(util.urlNovelDetailed(novelId))\n }\n if (res.error) {\n java.log(`无法从 Linpx 获取当前小说`)\n java.log(JSON.stringify(res))\n return []\n }\n return res\n }\n\n // 从网址获取id,尽可能返回系列 res,单篇小说返回小说 res\n u.getNovelResSeries = function (result) {\n let seriesId = 0, res = {}\n let isHtml = result.startsWith(\"\")\n if (isHtml) {\n let id = baseUrl.match(new RegExp(\"\\\\d+\"))[0]\n let pattern = \"(https?://)?(www\\\\.)?pixiv\\\\.net(/ajax)?/novel/(series/)?\\\\d+\"\n let isSeries = baseUrl.match(new RegExp(pattern))\n if (isSeries) {\n seriesId = id\n } else {\n let pattern = \"((furrynovel\\\\.(ink|xyz))|pixiv\\\\.net)/(pn|(pixiv/)?novel)/(show\\\\.php\\\\?id=)?\\\\d+\"\n let isNovel = baseUrl.match(new RegExp(pattern))\n if (isNovel) {\n java.log(`匹配小说ID:${id}`)\n res = util.getAjaxJson(util.urlNovelDetailed(id))\n }\n }\n } else {\n res = JSON.parse(result)\n }\n if (res.series !== undefined && res.series !== null) {\n seriesId = res.series.id\n }\n if (seriesId) {\n java.log(`系列ID:${seriesId}`)\n res = util.getAjaxJson(util.urlSeriesDetailed(seriesId))\n }\n if (res.error) {\n java.log(`无法从 Linpx 获取当前小说`)\n java.log(JSON.stringify(res))\n return []\n }\n return res\n }\n\n u.dateFormat = function (str) {\n let addZero = function (num) {\n return num < 10 ? '0' + num : num;\n }\n let time = new Date(str);\n let Y = time.getFullYear() + \"年\";\n let M = addZero(time.getMonth() + 1) + \"月\";\n let D = addZero(time.getDate()) + \"日\";\n return Y + M + D;\n }\n u.timeTextFormat = function (text) {\n if (text === undefined) {\n return \"\"\n }\n return `${text.slice(0, 10)} ${text.slice(11, 19)}`\n }\n\n util = u\n java.put(\"util\", objStringify(u))\n}\n\npublicFunc()\njava.getStrResponse(null, null)", "loginUrl": "", "respondTime": 180000, "ruleBookInfo": { "author": "userName", "canReName": "true", "coverUrl": "coverUrl", "init": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction novelHandler(res){\n let info = {}\n info.novelId = res.id\n info.title = res.title.replace(RegExp(/^\\s+|\\s+$/g), \"\")\n info.userName = res.userName\n info.tags = res.tags\n info.textCount = res.length\n info.description = res.desc\n info.coverUrl = util.urlCoverUrl(res.coverUrl)\n // info.catalogUrl = util.urlNovelDetailed(res.id)\n info.createDate = util.timeTextFormat(res.createDate)\n\n if (res.series === undefined || res.series === null) {\n info.title = info.latestChapter = info.title\n info.tags.unshift('单本')\n info.detailedUrl = util.urlNovelUrl(info.novelId)\n info.catalogUrl = util.urlNovelDetailed(info.novelId)\n } else {\n info.seriesId = res.series.id\n info.title = res.series.title.replace(RegExp(/^\\s+|\\s+$/g), \"\")\n info.tags.unshift('长篇')\n info.detailedUrl = util.urlSeriesUrl(info.seriesId)\n info.catalogUrl = util.urlSeriesDetailed(info.seriesId)\n\n let res2 = util.getAjaxJson(util.urlSeriesDetailed(info.seriesId))\n info.tags = info.tags.concat(res2.tags) //合并系列 tags\n info.tags = Array.from(new Set(info.tags))\n info.description = `${res2.caption}\\n当前章节简介:\\n${info.description}`\n }\n info.tags = info.tags.join(\",\")\n if (util.MORE_INFO_IN_DESCRIPTION) {\n info.description = `\\n书名:${info.title}\\n作者:${info.userName}\\n标签:${info.tags}\\n上传:${info.createDate}\\n简介:${info.description}`\n } else {\n info.description = `\\n${info.description}\\n上传时间:${info.createDate}`\n }\n return info\n}\n\n(function (res) {\n // 为了兼顾导入书架直接走详情页逻辑,不能直接用 book.xxx 来复用搜索页处理结果\n res = util.getNovelRes(result) // 系列数据过少,暂不分流\n try {\n return novelHandler(res)\n }catch (e) {\n java.log(e)\n java.log(`受 Linpx 的限制,无法获取当前小说的数据`)\n }\n})(result)\n", "intro": "description", "kind": "tags", "lastChapter": "latestChapter", "name": "title", "tocUrl": "catalogUrl", "wordCount": "textCount" }, "ruleContent": { "content": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction getContent(res) {\n let content = res.content\n if (util.SHOW_NOVEL_COMMENTS === true && res.desc !== \"\") {\n content = res.desc + \"\\n\" + \"——————————\\n\".repeat(2) + content\n }\n\n // 获取 [uploadedimage:] 的图片链接\n // 将存在的 pixiv 图片链接替换为可访问的直连\n if (res.images !== undefined && res.images !== null) {\n Object.keys(res.images).forEach((key) => {\n content = content.replace(`[uploadedimage:${key}]`, ``)\n })\n }\n\n // 获取 [pixivimage:] 的图片链接 [pixivimage:1234] [pixivimage:1234-1]\n let matched = content.match(RegExp(/\\[pixivimage:(\\d+)-?(\\d+)]/gm))\n if (matched) {\n for (let i in matched) {\n let illustId, order\n let matched2 = matched[i].match(RegExp(\"(\\\\d+)-?(\\\\d+)\"))\n let temp = matched2[0].split(\"-\")\n illustId = temp[0]\n if (temp.length >= 2) {\n order = temp[1]\n }\n content = content.replace(`${matched[i]}`, ``)\n }\n }\n\n // 替换 Pixiv 分页标记符号 [newpage]\n matched = content.match(RegExp(/[  ]*\\[newpage][  ]*/gm))\n if (matched) {\n for (let i in matched) {\n java.log(matched[i])\n content = content.replace(`${matched[i]}`, `${\"

\".repeat(3)}`)\n }\n }\n\n // 替换 Pixiv 章节标记符号 [chapter:]\n matched = content.match(RegExp(/\\[chapter:(.*?)]/gm))\n if (matched) {\n for (let i in matched) {\n let matched2 = matched[i].match(/\\[chapter:(.*?)]/m)\n let chapter = matched2[1].trim()\n content = content.replace(`${matched[i]}`, `${chapter}

`)\n }\n }\n\n // 替换 Pixiv 跳转页面标记符号 [[jump:]]\n matched = content.match(RegExp(/\\[jump:(\\d+)]/gm))\n if (matched) {\n for (let i in matched) {\n let page = matched[i].match(/\\d+/)\n content = content.replace(`${matched[i]}`, `\\n\\n跳转至第${page}节`)\n }\n }\n\n // 替换 Pixiv 链接标记符号 [[jumpuri: > ]]\n matched = content.match(RegExp(/\\[\\[jumpuri:(.*?)>(.*?)]]/gm))\n if (matched) {\n for (let i in matched) {\n let matched2 = matched[i].match(/\\[\\[jumpuri:(.*?)>(.*?)]]/m)\n let matchedText = matched2[0]\n let urlName = matched2[1].trim()\n let urlLink = matched2[2].trim()\n // 阅读不支持超链接\n // content = content.replace(`${matchedText}`, `${urlName}`)\n if (urlLink === urlName) {\n content = content.replace(`${matchedText}`, `${urlName}`)\n } else {\n content = content.replace(`${matchedText}`, `${urlName}: ${urlLink}`)\n }\n }\n }\n\n // 替换 Pixiv 注音标记符号 [[rb: > ]]\n matched = content.match(RegExp(/\\[\\[rb:(.*?)>(.*?)]]/gm))\n if (matched) {\n for (let i in matched) {\n let matched2 = matched[i].match(/\\[\\[rb:(.*?)>(.*?)]]/m)\n let matchedText = matched2[0]\n let kanji = matched2[1].trim()\n let kana = matched2[2].trim()\n\n if (!util.REPLACE_BOOK_TITLE_MARKS) {\n // 默认替换成(括号)\n content = content.replace(`${matchedText}`, `${kanji}(${kana})`)\n } else {\n let reg = RegExp(\"[\\\\u4E00-\\\\u9FFF]+\", \"g\");\n if (reg.test(kana)) {\n // kana为中文,则替换回《书名号》\n content = content.replace(`${matchedText}`, `${kanji}《${kana}》`)\n } else {\n // 阅读不支持 注音\n // content = content.replace(`${matchedText}`, `${kanji}${kana}`)\n content = content.replace(`${matchedText}`, `${kanji}(${kana})`)\n }\n }\n }\n }\n return content\n}\n\n(function () {\n return getContent(util.getNovelRes(result))\n})()", "imageStyle": "SINGLE", "replaceRegex": "", "sourceRegex": "" }, "ruleExplore": { "author": "userName", "bookList": "@js:\nvar util = objParse(String(java.get(\"util\")))\nvar seriesSet = new Set(); // 存储seriesID\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\n\n/**\n * @params arr 传入的源数组\n * @params length 需要获取的元素的个数\n */\nfunction randomChoseArrayItem(arr, length) {\n let copyArr = JSON.parse(JSON.stringify(arr))\n let newArr = [];\n for (let i = 0; i < length; i++) {\n let index = Math.floor(Math.random() * copyArr.length);\n let item = copyArr[index];\n newArr.push(item)\n copyArr.splice(index, 1)\n }\n return newArr.reverse()\n}\n\n\nfunction handlerRecommendUsers() {\n const MAX_FETCH_USER_NUMBER = 2;\n\n return () => {\n let userIds = JSON.parse(result).map(i => i.id)\n // java.log(`用户id个数:${userIds.length}`)\n if (userIds.length > MAX_FETCH_USER_NUMBER) {\n userIds = randomChoseArrayItem(userIds, MAX_FETCH_USER_NUMBER);\n }\n // java.log(`查询的用户Ids:${userIds}`)\n let usersInfo = util.getWebviewJson(util.urlUsersDetailed(userIds))\n // java.log(`返回的${JSON.stringify(usersInfo)}`)\n let queryNovelIds = []\n // java.log(`${JSON.stringify(usersInfo)}`)\n usersInfo.filter(user => user.novels && user.novels.length > 0)\n .map(user => user.novels)\n // 将list展平[1,2,3]变为1,2,3 添加到novelList中\n .forEach(novels => {\n novels.forEach(novel => {\n queryNovelIds.push(novel)\n })\n })\n // 暂时限制最大获取数量\n if (queryNovelIds.length > 10) {\n queryNovelIds = randomChoseArrayItem(queryNovelIds, 10)\n }\n novelList = util.getWebviewJson(util.urlNovelsDetailed(queryNovelIds))\n return util.formatNovels(util.handNovels(util.combineNovels(novelList)))\n }\n}\n\nfunction handlerFollowLatest() {\n return () => {\n let resp = JSON.parse(result)\n return util.formatNovels(util.handNovels(util.combineNovels(resp)))\n }\n}\n\nfunction handlerFactory() {\n if (baseUrl.indexOf(\"/fav/user\") !== -1) {\n return handlerRecommendUsers()\n }\n if (baseUrl.indexOf(\"/pixiv/novels/recent\") !== -1) {\n return handlerFollowLatest()\n }\n}\n\n(() => {\n return handlerFactory()()\n})()\n", "bookUrl": "detailedUrl", "coverUrl": "coverUrl", "intro": "description", "kind": "tags", "lastChapter": "latestChapter", "name": "title", "wordCount": "textCount" }, "ruleSearch": { "author": "userName", "bookList": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction getUser(username, exactMatch) {\n // let resp = util.getAjaxJson(util.urlSearchUsers(String(username)))\n let resp = java.ajax(util.urlSearchUsers(String(username))) // 兼容搜索链接\n if (resp.startsWith(\"\") || JSON.parse(result).error) {\n return []\n }\n resp = JSON.parse(resp)\n if (resp.users.length === 0) {\n return []\n }\n if (!exactMatch) {\n return resp.users\n }\n // 只返回用户名完全一样的用户\n return resp.users.filter(user => {\n return user.name === username\n })\n}\n\nfunction getUserNovels(nidList) {\n let page = Number(java.get(\"page\"))\n // 分页\n let list = nidList.slice((page - 1) * 20, page * 20)\n if (list.length === 0) {\n return []\n }\n // java.log(`NIDURL:${util.urlNovelsDetailed(list)}`)\n return util.getWebviewJson(util.urlNovelsDetailed(list))\n}\n\n// 存储seriesID\nvar first = true;\nvar seriesSet = {\n keywords: \"Linpx:Search\",\n has: (value) => {\n let page = Number(java.get(\"page\"))\n if (page === 1 && first) {\n first = false\n cache.deleteMemory(this.keywords)\n return false\n }\n\n let v = cache.getFromMemory(this.keywords)\n if (v === undefined || v === null) {\n return false\n }\n let set = new Set(JSON.parse(v))\n return set.has(value)\n },\n\n add: (value) => {\n let v = cache.getFromMemory(this.keywords)\n if (v === undefined || v === null) {\n cache.putMemory(this.keywords, JSON.stringify([value]))\n\n } else {\n let arr = JSON.parse(v)\n if (typeof arr === \"string\") {\n arr = Array(arr)\n }\n arr.push(value)\n cache.putMemory(this.keywords, JSON.stringify(arr))\n }\n },\n};\n\n\nfunction findUserNovels() {\n let novelList = []\n let username = String(java.get(\"key\"))\n let userArr = getUser(username, true)\n // 获取用户所有小说\n let uidList = userArr.filter(user => {\n return user.novels.length > 0\n }).map(user => user.id)\n\n if (uidList.length > 0) {\n let list = util.getWebviewJson(util.urlUsersDetailed(uidList)) // 包含所有小说数据\n let nidList = []\n // 从两层数组中提取novelsId\n list.forEach(user => {\n user.novels\n // 按id降序排序-相当于按时间降序排序\n .reverse()\n .forEach(nid => nidList.push(nid))\n })\n getUserNovels(nidList).forEach(novel => {\n novelList.push(novel)\n })\n }\n return novelList\n}\n\nfunction getNovels(){\n if (result.startsWith(\"\") || JSON.parse(result).error) {\n return []\n } else {\n return JSON.parse(result).novels\n }\n}\n\nfunction getLinkNovels() {\n try {\n return util.getNovelRes(String(java.get(\"key\")))\n } catch (e) {\n return []\n }\n}\n\n(function () {\n let novels = []\n novels = novels.concat(getNovels())\n novels = novels.concat(getLinkNovels())\n novels = novels.concat(findUserNovels())\n // java.log(JSON.stringify(novels))\n // 返回空列表中止流程\n if (novels.length === 0) {\n return []\n }\n return util.formatNovels(util.handNovels(util.combineNovels(novels)))\n}(result))", "bookUrl": "detailedUrl", "checkKeyWord": "测试页面", "coverUrl": "coverUrl", "intro": "description", "kind": "tags", "lastChapter": "latestChapter", "name": "title", "wordCount": "textCount" }, "ruleToc": { "chapterList": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction oneShotHandler(res) {\n res.textCount = res.content.length\n res.updateDate = util.timeTextFormat(res.createDate)\n return [{\n title: res.title.replace(RegExp(/^\\s+|\\s+$/g), \"\"),\n chapterUrl: util.urlNovel(res.id),\n chapterInfo:`${res.updateDate}  ${res.textCount}字`\n }]\n}\n\nfunction seriesHandler(res) {\n res.novels.forEach(v => {\n v.title = v.title.replace(RegExp(/^\\s+|\\s+$/g), \"\").replace(RegExp(/(|)|-/g), \"\")\n v.chapterUrl = util.urlNovel(v.id)\n // v.updateDate = String(v.coverUrl.match(RegExp(\"\\\\d{4}/\\\\d{2}/\\\\d{2}\"))) //fake\n v.detail = util.getAjaxJson(util.urlNovelDetailed(v.id))\n try{\n v.textCount = v.detail.content.length\n v.updateDate = util.timeTextFormat(v.detail.createDate)\n v.chapterInfo = `${v.updateDate}  ${v.textCount}字`\n } catch (e) {}\n util.debugFunc(() => {\n java.log(`${v.title}`)\n })\n })\n return res.novels\n}\n\n(function (res) {\n res = util.getNovelResSeries(result)\n if (res.novels !== undefined) {\n return seriesHandler(res)\n } else {\n return oneShotHandler(res)\n }\n})(result)\n", "chapterName": "title", "chapterUrl": "chapterUrl", "updateTime": "chapterInfo" }, "searchUrl": "@js:\njava.put(\"page\",page);java.put(\"key\",key);\n`https://api.furrynovel.ink/pixiv/search/novel/${encodeURI(key)}/cache?page=${page}`;", "variableComment": "⚙️ 自定义书源设置:\n⚙️ 自定义设置:请在基本-变量说明处修改代码\n⚙️ 自定义设置:将 true 改为 false,或相反\n⚠️ 设置源变量【无法】更改书源自定义设置\n⚠️ 注意不要添加或删除尾随逗号\",\"\n以下内容为书源设置:\n{\n\"MORE_INFO_IN_DESCRIPTION\": false,\n\"SHOW_ORIGINAL_NOVEL_LINK\": true,\n\"REPLACE_BOOK_TITLE_MARKS\": true,\n\"SHOW_NOVEL_CAPTIONS\": true,\n\"SHOW_NOVEL_COMMENTS\": true,\n\"DEBUG\": false\n}\n\n// MORE_INFO_IN_DESCRIPTION\n// 详情:书籍简介显示更多信息\n// SHOW_ORIGINAL_NOVEL_LINK\n// 目录:显示源链接,但会增加请求次数\n// REPLACE_BOOK_TITLE_MARKS\n// 正文:注音内容为汉字时,替换为书名号\n// SHOW_NOVEL_CAPTIONS\n// 正文:章首显示小说描述\n// SHOW_NOVEL_COMMENTS\n// 正文:章尾显示小说评论\n// DEBUG\n// 调试模式\n\n", "weight": 0 }, { "bookSourceComment": "兽人控小说站书源(更新📆:2025-02-15)\n\n可用功能:✅搜索✅发现✅添加网址✅订阅源 \n搜索小说:✅单篇✅系列✅作者✅标签 \n发现小说:✅热门小说✅最新小说✅随便来点 \n添加网址:✅兽人控小说站链接 \n订阅用法:点击订阅源打开小说/系列小说,点击【加入书架】按钮,添加小说到书架\n\n书源发布:兽人阅读频道 https://t.me/FurryReading \n项目地址:https://github.com/windyhusky/PixivSource \n使用教程:https://github.com/windyhusky/PixivSource/blob/main/doc/FurryNovel.md\n\n规则订阅:Import 订阅源\nhttps://cdn.jsdelivr.net/gh/windyhusky/PixivSource@main/import.json\nhttps://raw.githubusercontent.com/windyhusky/PixivSource/main/import.json\n\n⚙️ 书源设置:\n书源管理 - 编辑书源 - 基本 - 变量说明 - 修改并保存\n\n🔎 筛选发现:\n发现 - 长按\"Pixiv\" - 编辑 - 右上角菜单 - 设置源变量 \n设置源变量:输入想要搜索/筛选的标签,以空格间隔(或一行一个),保存\n发现 - 长按\"Pixiv\" - 刷新 - 查看他人收藏", "bookSourceGroup": "🔞 Pixiv", "bookSourceName": "兽人控小说站", "bookSourceType": 0, "bookSourceUrl": "https://furrynovel.com", "bookUrlPattern": "(https?://)?(www\\.|api\\.)?furrynovel\\.com(/api)?/(zh|en|ja)/novel/\\d+(/chapter/d+)?", "customOrder": 3, "enabled": true, "enabledCookieJar": false, "enabledExplore": true, "exploreUrl": "@js:\nlet key = []\nlet keyword = String(source.getVariable()).replace(\"#\", \"\")\nif (keyword.includes(\"\\n\")) {\n keyword = keyword.replace(RegExp(/\\s+/g), \"\\n\")\n key = keyword.split(\"\\n\")\n}\nif (keyword.includes(\" \")) {\n keyword = keyword.replace(RegExp(/\\s+/g), \" \")\n key = keyword.split(\" \")\n}\nif (key.length === 0){\n java.longToast(\"可设置源变量,筛选发现内容\")\n} else {\n java.longToast(`正在搜索:${key.join(\"、\")}`)\n}\n\nlet li = [\n {\"热门小说\": `https://api.furrynovel.com/api/novel?page={{page}}&order_by=popular&${key.map(v => \"tags[]=\" + v).join(\"&\")}`},\n {\"最新小说\": `https://api.furrynovel.com/api/novel?page={{page}}&order_by=latest&${key.map(v => \"tags[]=\" + v).join(\"&\")}`},\n {\"随便来点\": `https://api.furrynovel.com/api/novel?page={{page}}&order_by=random&${key.map(v => \"tags[]=\" + v).join(\"&\")}`}\n]\n\n// 格式化发现地址\nli.forEach(item => {\n item.title = Object.keys(item)[0]\n item.url = Object.values(item)[0]\n delete item[Object.keys(item)[0]]\n item.style = {}\n item.style.layout_flexGrow = 1\n item.style.layout_flexBasisPercent = 0.15\n})\nJSON.stringify(li)", "header": "{\"referer\": \"https://furrynovel.com/\"}", "lastUpdateTime": 1739548800251, "loginCheckJs": "var util = {}\n\nfunction objStringify(obj) {\n return JSON.stringify(obj, (n, v) => {\n if (typeof v == \"function\")\n return v.toString();\n return v;\n });\n}\n\nfunction publicFunc() {\n let u = {}, settings = {}\n java.log(String(source.bookSourceComment).split(\"\\n\")[0]) // 输出书源信息\n java.log(`手动更新时间:${java.timeFormat(source.lastUpdateTime)}`) // 输出书源信息\n settings = JSON.parse(String(source.variableComment).match(RegExp(/{([\\s\\S]*?)}/gm)))\n if (settings !== null) {\n java.log(\"⚙️ 使用自定义设置\")\n } else {\n settings = {}\n settings.SHOW_ORIGINAL_NOVEL_LINK = true\n settings.REPLACE_BOOK_TITLE_MARKS = true\n settings.MORE_INFO_IN_DESCRIPTION = false\n settings.DEBUG = false\n java.log(\"⚙️ 使用默认设置(无自定义设置 或 自定义设置有误)\")\n }\n u.SHOW_ORIGINAL_NOVEL_LINK = settings.SHOW_ORIGINAL_NOVEL_LINK // 目录处显示小说源链接,但会增加请求次数\n u.REPLACE_BOOK_TITLE_MARKS = settings.REPLACE_BOOK_TITLE_MARKS // 注音内容为汉字时,替换为书名号\n u.MORE_INFO_IN_DESCRIPTION = settings.MORE_INFO_IN_DESCRIPTION // 书籍简介显示更多信息\n u.DEBUG = settings.DEBUG // 调试模式\n\n if (u.DEBUG === true) {\n java.log(JSON.stringify(settings, null, 4))\n java.log(`DEBUG = ${u.DEBUG}`)\n }\n\n\n u.debugFunc = (func) => {\n if (util.DEBUG) {\n func()\n }\n }\n u.cacheGetAndSet = (key, supplyFunc) => {\n let v = cache.get(key)\n if (v === undefined || v === null) {\n v = JSON.stringify(supplyFunc())\n // 缓存10分钟\n cache.put(key, v, 600)\n }\n return JSON.parse(v)\n }\n u.getAjaxJson = (url) => {\n return util.cacheGetAndSet(url, () => {\n return JSON.parse(java.ajax(url))\n })\n }\n u.getWebviewJson = (url) => {\n return util.cacheGetAndSet(url, () => {\n let html = java.webView(null, url, null)\n return JSON.parse((html.match(new RegExp(\">\\\\[{.*?}]<\"))[0].replace(\">\", \"\").replace(\"<\", \"\")))\n })\n }\n\n u.urlNovelUrl = (novelId) => {\n return `https://furrynovel.com/zh/novel/${novelId}`\n }\n u.urlNovelDetail = (novelId) => {\n return `https://api.furrynovel.com/api/zh/novel/${novelId}`\n }\n u.urlNovelsDetail = (novelIds) => {\n return `https://api.furrynovel.com/api/zh/novel?${novelIds.map(v => \"ids[]=\" + v).join(\"&\")}`\n }\n u.urlNovelChapterUrl = (novelId, chapterId) => {\n return `https://furrynovel.com/zh/novel/${novelId}/chapter/${chapterId}`\n }\n u.urlNovelChapterInfo = (novelId) => {\n return `https://api.furrynovel.com/api/zh/novel/${novelId}/chapter`\n }\n u.urlNovelChapterDetail = (novelId, chapterId) => {\n return `https://api.furrynovel.com/api/zh/novel/${novelId}/chapter/${chapterId}`\n }\n u.urlNovelChapter = (novelId, chapterId) => {\n if (util.SHOW_ORIGINAL_NOVEL_LINK) {\n return util.urlNovelChapterUrl(novelId, chapterId)\n } else {\n return util.urlNovelChapterDetail(novelId, chapterId)\n }\n }\n u.urlCoverUrl = (pxImgUrl) => {\n return `https://img.furrynovel.com/?url=${pxImgUrl}`\n }\n\n u.urlLinpxNovelUrl = (novelId) => {\n return `https://furrynovel.ink/pixiv/novel/${novelId}/cache`\n }\n u.urlLinpxNovelDetail = (novelId) => {\n return `https://api.furrynovel.ink/pixiv/novel/${novelId}/cache`\n }\n u.urlLinpxCoverUrl = (pxImgUrl) => {\n return `https://pximg.furrynovel.ink/?url=${pxImgUrl}&w=800`\n }\n u.urlIllustOriginal = (illustId, order) => {\n // 使用 pixiv.cat 获取插图\n let illustOriginal = `https://pixiv.re/${illustId}.png`\n // let illustOriginal = `https://pixiv.nl/${illustId}.png`\n if (order >= 1) {\n illustOriginal = `https://pixiv.re/${illustId}-${order}.png`\n // illustOriginal = `https://pixiv.nl/${illustId}-${order}.png`\n }\n return illustOriginal\n }\n u.urlSourceUrl = (source, oneShot, id) => {\n if (source === \"bilibili\") {\n return `https://www.bilibili.com/read/readlist/rl${id}/`\n }\n if (source === \"pixiv\" && oneShot === true) {\n return `https://www.pixiv.net/novel/show.php?id=${id}`\n }\n if (source === \"pixiv\" && oneShot === false) {\n return `https://www.pixiv.net/novel/series/${id}`\n }\n }\n\n u.getNovels = function () {\n if (JSON.parse(result).code === 200 && JSON.parse(result).count > 0){\n return JSON.parse(result).data\n } else {\n return []\n }\n }\n\n u.handNovels = function (novels) {\n novels.forEach(novel =>{\n // novel.id = novel.id\n novel.title = novel.name\n // novel.tags = novel.tags\n novel.userName = novel.author.name\n // novel.userId = novel.author.id\n novel.textCount = null\n try {\n novel.latestChapter = novel.latest_chapters[0].name}\n catch (e) {\n novel.latestChapter = null\n }\n novel.description = novel.desc\n novel.coverUrl = novel.cover\n novel.detailedUrl = util.urlNovelDetail(novel.id)\n\n // novel.source = novel.source\n novel.oneShot = novel.ext_data.oneShot\n novel.sourceId = novel.source_id\n novel.sourceUrl = util.urlSourceUrl(novel.source, novel.oneShot, novel.sourceId)\n\n novel.createDate = novel.created_at\n novel.updateDate = novel.updated_at\n novel.syncDate = novel.fetched_at\n // novel.status = novel.status\n // if (novel.status !== \"publish\"){ // suspend\n // java.log(util.urlNovelUrl(novel.id))\n // java.log(novel.sourceUrl)\n // }\n })\n return novels\n }\n\n u.formatNovels = function (novels) {\n novels.forEach(novel => {\n novel.title = novel.title.replace(RegExp(/^\\s+|\\s+$/g), \"\")\n novel.tags = novel.tags.join(\",\")\n novel.createDate = util.dateFormat(novel.createDate)\n novel.updateDate = util.dateFormat(novel.updateDate)\n novel.syncDate = util.dateFormat(novel.syncDate)\n if (util.MORE_INFO_IN_DESCRIPTION) {\n novel.description = `\\n书名:${novel.title}\\n作者:${novel.userName}\\n标签:${novel.tags}\\n上传:${novel.createDate}\\n更新:${novel.updateDate}\\n同步:${novel.syncDate}\\n简介:${novel.description}`\n } else {\n novel.description = `\\n${novel.description}\\n上传时间:${novel.createDate}\\n更新时间:${novel.updateDate}\\n\n 同步时间:${novel.syncDate}`\n }\n })\n return novels\n }\n\n u.dateFormat = function(text) {\n return `${text.slice(0, 10)}`\n }\n u.timeTextFormat = function(text) {\n return `${text.slice(0, 10)} ${text.slice(11, 19)}`\n }\n\n util = u\n java.put(\"util\", objStringify(u))\n}\n\npublicFunc()\njava.getStrResponse(null, null)", "loginUrl": "", "respondTime": 180000, "ruleBookInfo": { "author": "userName", "canReName": "true", "coverUrl": "coverUrl", "init": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction novelHandler(novel) {\n novel = util.formatNovels(util.handNovels([novel]))[0]\n novel.detailedUrl = util.urlNovelUrl(novel.id)\n novel.catalogUrl = util.urlNovelChapterInfo(novel.id)\n return novel\n}\n\nfunction getNovelRes(result){\n let res = {}\n let isHtml = result.startsWith(\"\")\n let pattern = \"(https?://)?(www\\\\.)?furrynovel\\\\.com/(zh|en|ja)/novel/\\\\d+(/chapter/d+)?\"\n let fnWebpage = baseUrl.match(new RegExp(pattern))\n if (isHtml && fnWebpage) {\n let novelId = baseUrl.match(new RegExp(\"\\\\d+\"))[0]\n res = util.getAjaxJson(util.urlNovelDetail(novelId))\n } else {\n res = JSON.parse(result)\n }\n if (res.data.length === 0) {\n java.log(`无法从 FurryNovel.com 获取当前小说`)\n java.log(JSON.stringify(res))\n }\n return res.data\n}\n\n(() => {\n try {\n return novelHandler(getNovelRes(result))\n } catch (e) {\n java.log(e)\n java.log(`受 FurryNovel.com 限制,无法获取当前小说数据`)\n }\n})();", "intro": "description", "kind": "tags", "lastChapter": "latestChapter", "name": "title", "tocUrl": "catalogUrl", "wordCount": "textCount" }, "ruleContent": { "content": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction getContent(res) {\n let content = res.content\n // 获取 [uploadedimage:] 的图片链接\n let hasEmbeddedImages = content.match(RegExp(/\\[uploadedimage:(\\d+)-?(\\d+)]/gm))\n if (hasEmbeddedImages) {\n resp = JSON.parse(java.ajax(util.urlLinpxNovelDetail(res.source_id)))\n Object.keys(resp.images).forEach((key) => {\n content = content.replace(`[uploadedimage:${key}]`, ``)\n })\n }\n\n // 获取 [pixivimage:] 的图片链接 [pixivimage:1234] [pixivimage:1234-1]\n let matched = content.match(RegExp(/\\[pixivimage:(\\d+)-?(\\d+)]/gm))\n if (matched) {\n for (let i in matched) {\n let illustId, order\n let matched2 = matched[i].match(RegExp(\"(\\\\d+)-?(\\\\d+)\"))\n let temp = matched2[0].split(\"-\")\n illustId = temp[0]\n if (temp.length >= 2) {\n order = temp[1]\n }\n content = content.replace(`${matched[i]}`, ``)\n }\n }\n\n // 替换 Pixiv 分页标记符号 [newpage]\n matched = content.match(RegExp(/[  ]*\\[newpage][  ]*/gm))\n if (matched) {\n for (let i in matched) {\n content = content.replace(`${matched[i]}`, `${\"

\".repeat(3)}`)\n }\n }\n\n // 替换 Pixiv 章节标记符号 [chapter:]\n matched = content.match(RegExp(/\\[chapter:(.*?)]/gm))\n if (matched) {\n for (let i in matched) {\n let matched2 = matched[i].match(/\\[chapter:(.*?)]/m)\n let chapter = matched2[1].trim()\n content = content.replace(`${matched[i]}`, `${chapter}

`)\n }\n }\n\n // 替换 Pixiv 跳转页面标记符号 [[jump:]]\n matched = content.match(RegExp(/\\[jump:(\\d+)]/gm))\n if (matched) {\n for (let i in matched) {\n let page = matched[i].match(/\\d+/)\n content = content.replace(`${matched[i]}`, `\\n\\n跳转至第${page}节`)\n }\n }\n\n // 替换 Pixiv 链接标记符号 [[jumpuri: > ]]\n matched = content.match(RegExp(/\\[\\[jumpuri:(.*?)>(.*?)]]/gm))\n if (matched) {\n for (let i in matched) {\n let matched2 = matched[i].match(/\\[\\[jumpuri:(.*?)>(.*?)]]/m)\n let matchedText = matched2[0]\n let urlName = matched2[1].trim()\n let urlLink = matched2[2].trim()\n // 阅读不支持超链接\n //content = content.replace(`${matchedText}`, `${urlName}`)\n if (urlLink === urlName) {\n content = content.replace(`${matchedText}`, `${urlName}`)\n } else {\n content = content.replace(`${matchedText}`, `${urlName}: ${urlLink}`)\n }\n }\n }\n\n // 替换 Pixiv 注音标记符号 [[rb: > ]]\n matched = content.match(RegExp(/\\[\\[rb:(.*?)>(.*?)]]/gm))\n if (matched) {\n for (let i in matched) {\n let matched2 = matched[i].match(/\\[\\[rb:(.*?)>(.*?)]]/m)\n let matchedText = matched2[0]\n let kanji = matched2[1].trim()\n let kana = matched2[2].trim()\n\n if (!util.REPLACE_BOOK_TITLE_MARKS) {\n // 默认替换成(括号)\n content = content.replace(`${matchedText}`, `${kanji}(${kana})`)\n } else {\n let reg = RegExp(\"[\\\\u4E00-\\\\u9FFF]+\", \"g\");\n if (reg.test(kana)) {\n // kana为中文,则替换回《书名号》\n content = content.replace(`${matchedText}`, `${kanji}《${kana}》`)\n } else {\n // 阅读不支持 注音\n // content = content.replace(`${matchedText}`, `${kanji}${kana}`)\n content = content.replace(`${matchedText}`, `${kanji}(${kana})`)\n }\n }\n }\n }\n return content\n}\n\nfunction getNovelRes(result){\n let chapterId = 0, res = {}\n let isHtml = result.startsWith(\"\")\n let pattern = \"(https?://)?(www\\\\.)?furrynovel\\\\.com/(zh|en|ja)/novel/\\\\d+(/chapter/\\\\d+)?\"\n let fnWebpage = baseUrl.match(new RegExp(pattern))\n if (isHtml && fnWebpage) {\n let novelId = baseUrl.match(RegExp(/\\/(\\d+)\\/chapter/))[1]\n try {\n chapterId = baseUrl.match(RegExp(/\\/(\\d+)\\/chapter\\/(\\d+)/))[2]\n } catch (e) {\n chapterId = util.getAjaxJson(util.urlNovelChapterInfo(novelId)).data[0].id\n } finally {\n res = util.getAjaxJson(util.urlNovelChapterDetail(novelId, chapterId))\n }\n } else {\n res = JSON.parse(result)\n }\n if (res.data.length === 0) {\n java.log(`无法从 FurryNovel.com 获取当前小说`)\n java.log(JSON.stringify(res))\n }\n return res.data\n}\n\n(function () {\n return getContent(getNovelRes(result))\n})()", "imageStyle": "SINGLE" }, "ruleExplore": { "author": "userName", "bookList": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\n(() => {\n return util.formatNovels(util.handNovels(util.getNovels()))\n})();", "bookUrl": "detailedUrl", "coverUrl": "coverUrl", "intro": "description", "kind": "tags", "lastChapter": "latestChapter", "name": "title", "wordCount": "textCount" }, "ruleSearch": { "author": "userName", "bookList": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\n(() => {\n return util.formatNovels(util.handNovels(util.getNovels()))\n})();", "bookUrl": "detailedUrl", "coverUrl": "coverUrl", "intro": "description", "kind": "tags", "lastChapter": "latestChapter", "name": "title", "wordCount": "textCount" }, "ruleToc": { "chapterList": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction novelHandler(novels) {\n novels.forEach(novel => {\n novel.chapterId = novel.id\n novel.novelId = baseUrl.match(RegExp(/\\d+/))[0]\n novel.chapterName = novel.title = novel.name\n novel.chapterUrl = util.urlNovelChapter(novel.novelId, novel.chapterId)\n novel.chapterInfo = `${novel.created_at}  ${novel.text_count}字`\n })\n return novels\n}\n\nfunction getNovelRes(result){\n let res = {}\n let isHtml = result.startsWith(\"\")\n let pattern = \"(https?://)?(www\\\\.)?furrynovel\\\\.com/(zh|en|ja)/novel/\\\\d+(/chapter/d+)?\"\n let fnWebpage = baseUrl.match(new RegExp(pattern))\n if (isHtml && fnWebpage) {\n let novelId = baseUrl.match(new RegExp(\"\\\\d+\"))[0]\n // res = util.getAjaxJson(util.urlNovelDetail(novelId))\n res = util.getAjaxJson(util.urlNovelChapterInfo(novelId))\n } else {\n res = JSON.parse(result)\n }\n if (res.data.length === 0) {\n java.log(`无法从 FurryNovel.com 获取当前小说`)\n java.log(JSON.stringify(res))\n }\n return res.data\n}\n\n(function () {\n return novelHandler(getNovelRes(result))\n})()", "chapterName": "title", "chapterUrl": "chapterUrl", "updateTime": "chapterInfo" }, "searchUrl": "@js:\njava.put(\"page\",page);java.put(\"key\",key);\n`https://api.furrynovel.com/api/zh/novel?page=${page}&order_by=popular&keyword=${encodeURI(key)}`;", "variableComment": "🔎 筛选发现:\n发现 - 长按\"兽人小说站\" - 编辑 - 右上角菜单 - 设置源变量\n设置源变量:输入想要搜索/筛选的标签,以空格间隔(或一行一个),保存\n发现 - 长按\"兽人小说站\" - 刷新 - 查看他人收藏\n以下内容为源变量模板:\n中文 原创 纯爱\n\n\n⚙️ 自定义书源设置:\n⚙️ 自定义设置:请在基本-变量说明处修改代码\n⚙️ 自定义设置:将 true 改为 false,或相反\n⚠️ 设置源变量【无法】更改书源自定义设置\n⚠️ 注意不要添加或删除尾随逗号\",\"\n⚠️ 发现页需要长按\"兽人控小说站\",手动刷新\n以下内容为书源设置:\n{\n\"MORE_INFO_IN_DESCRIPTION\": false,\n\"SHOW_ORIGINAL_NOVEL_LINK\": true,\n\"REPLACE_BOOK_TITLE_MARKS\": true,\n\"SHOW_NOVEL_COMMENTS\": true,\n\"DEBUG\": false\n}\n\n// MORE_INFO_IN_DESCRIPTION\n// 详情:书籍简介显示更多信息\n// SHOW_ORIGINAL_NOVEL_LINK\n// 目录:显示源链接,但会增加请求次数\n// REPLACE_BOOK_TITLE_MARKS\n// 正文:注音内容为汉字时,替换为书名号\n// SHOW_NOVEL_COMMENTS\n// 正文:章尾显示小说评论\n// DEBUG\n// 调试模式\n\n", "weight": 0 } ]