[ { "bookSourceComment": "🦊 Linpx 书源(单篇)(更新📆:2025/12/18)\n\n书源版本:253\n可用功能:✅搜索✅发现✅添加网址✅订阅源\n搜索小说:✅单篇❌系列✅作者✅标签✅链接\n发现小说:✅推荐作者✅最近更新\n添加网址:✅Linpx 链接✅Linpx 分享链接\n搜索网址:✅Linpx 链接✅Pixiv 原文链接\n订阅用法:点击订阅源打开小说,点击【添加到书架】按钮添加小说到书架\n温馨提示:⚠️Linpx 搜索暂时不支持系列小说名称,添加系列小说后,无法再次通过搜索获取并更新目录\n\n书源发布:Pixiv 书源频道 https://t.me/PixivSource\n兽人阅读频道 https://t.me/FurryReading\n项目地址:https://github.com/DowneyRem/PixivSource\n使用教程:https://downeyrem.github.io/PixivSource/Linpx\n\n规则订阅:Import 订阅源\nhttps://cdn.jsdelivr.net/gh/DowneyRem/PixivSource@main/import.json\nhttps://raw.githubusercontent.com/DowneyRem/PixivSource/main/import.json\n\n⚙️ 书源设置:\n书源管理 - 编辑书源 - 基本 - 变量说明 - 修改并保存", "bookSourceGroup": "🔞 Pixiv,🐲 Furry", "bookSourceName": "🦊 Linpx", "bookSourceType": 0, "bookSourceUrl": "https://furrynovel.ink", "bookUrlPattern": "(https?://)?(api\\.|www\\.)?(furrynovel\\.(ink|xyz))/(pn|pixiv/novel)/\\d+(/cache)?", "concurrentRate": "3/2000", "customButton": false, "customOrder": 3, "enabled": true, "enabledCookieJar": true, "enabledExplore": true, "eventListener": true, "exploreUrl": "@js:\nli = [\n {\"💯 推荐\": \"https://api.furrynovel.ink/fav/user/cache\"},\n {\"🆕 最新\": \"https://api.furrynovel.ink/pixiv/novels/recent/cache?page={{page}}\"},\n {\"🔄 随便\": \"https://furrynovel.ink\"},\n {\"🆙 更新\": \"https://cdn.jsdelivr.net/gh/DowneyRem/PixivSource@main/linpx.json\"},\n {\"📙 书源相关 📙\": \"\"},\n {\"🏠 主页\": \"https://downeyrem.github.io/PixivSource\"},\n {\"🔰 指南\": \"https://downeyrem.github.io/PixivSource/Linpx\"},\n {\"🐞 反馈\": \"https://github.com/DowneyRem/PixivSource/issues\"},\n {\"💰 打赏\": \"https://downeyrem.github.io/PixivSource/Sponsor\"},\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_flexShrink = 1\n item.style.layout_alignSelf = \"auto\"\n item.style.layout_wrapBefore = \"false\"\n if (item.url === \"\") {\n item.style.layout_flexBasisPercent = 1\n } else {\n item.style.layout_flexBasisPercent = -1\n }\n})\nJSON.stringify(li)", "header": "", "jsLib": "var cacheSaveSeconds = 7*24*60*60 // 长期缓存时间 7天\nvar cacheTempSeconds = 10*60*1000 // 短期缓存 10min\n\nfunction cacheGetAndSet(key, supplyFunc) {\n const {java, cache} = this\n let v = cache.get(key)\n // 缓存信息错误时,保存 10min 后重新请求\n if (v && JSON.parse(v).error === true) {\n if (new Date().getTime() >= JSON.parse(v).timestamp + cacheTempSeconds) {\n cache.delete(key)\n v = cache.get(key)\n }\n }\n // 无缓存信息时,进行请求\n if (v === undefined || v === null) {\n v = supplyFunc()\n v.timestamp = new Date().getTime()\n v = JSON.stringify(v)\n cache.put(key, v, cacheSaveSeconds)\n }\n return JSON.parse(v)\n}\n\nfunction putInCache(objectName, object, saveSeconds) {\n const {java, cache} = this\n if (object === undefined) object = null\n if (saveSeconds === undefined) saveSeconds = 0\n cache.put(objectName, JSON.stringify(object), saveSeconds)\n}\nfunction getFromCache(objectName) {\n const {java, cache} = this\n let object = cache.get(objectName)\n if (object === undefined) return null // 兼容源阅\n return JSON.parse(object)\n}\n\nfunction isHtmlString(str) {\n return str.startsWith(\"\")\n}\nfunction isJsonString(str) {\n try {\n if (typeof JSON.parse(str) === \"object\") {\n return true\n }\n } catch(e) {}\n return false\n}\n\nfunction getAjaxJson(url, forceUpdate) {\n const {java, cache} = this\n let v = cache.get(url)\n if (forceUpdate && v && new Date().getTime() >= JSON.parse(v).timestamp + cacheTempSeconds) cache.delete(url)\n return this.cacheGetAndSet(url, () => {\n return JSON.parse(java.ajax(url))\n })\n}\nfunction getAjaxAllJson(urls, forceUpdate) {\n const {java, cache} = this\n let v = cache.get(urls)\n if (forceUpdate && v && new Date().getTime() >= JSON.parse(v).timestamp + cacheTempSeconds) cache.delete(urls)\n return this.cacheGetAndSet(urls, () => {\n let result = java.ajaxAll(urls).map(resp => JSON.parse(resp.body()))\n cache.put(urls, JSON.stringify(result), cacheSaveSeconds)\n for (let i in urls) cache.put(urls[i], JSON.stringify(result[i]), cacheSaveSeconds)\n return result\n })\n}\nfunction getWebviewJson(url) {\n const {java, cache} = this\n return this.cacheGetAndSet(url, () => {\n let html = java.webView(null, url, null)\n return JSON.parse((html.match(new RegExp(\">\\\\[{.*?}]<\"))[0].replace(\">\", \"\").replace(\"<\", \"\")))\n })\n}\n\nfunction getWebViewUA() {\n const {java, cache} = this\n let userAgent = String(java.getWebViewUA())\n if (userAgent.includes(\"Windows NT 10.0; Win64; x64\")) {\n userAgent = \"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Mobile Safari/537.36\"\n }\n // java.log(`userAgent=${userAgent}`)\n cache.put(\"userAgent\", userAgent)\n return String(userAgent)\n}\nfunction startBrowser(url, title) {\n const {java} = this\n if (!title) title = url\n let msg = \"\"\n let headers = `{\"headers\": {\"User-Agent\":\"${this.getWebViewUA()}\"}}`\n if (url.includes(\"github.com\") || url.includes(\"github.io\")) {\n msg += \"\\n\\n即将打开 Github\\n请确认已开启代理/梯子/VPN等\"\n }\n this.sleepToast(msg, 0.01)\n java.startBrowser(`${url}, ${headers}`, title)\n}\n\nfunction urlNovelUrl(novelId) {\n return `https://furrynovel.ink/pixiv/novel/${novelId}/cache`\n}\nfunction urlNovelDetailed(novelId) {\n return `https://api.furrynovel.ink/pixiv/novel/${novelId}/cache`\n}\nfunction urlNovelsDetailed(nidList) {\n return `https://api.furrynovel.ink/pixiv/novels/cache?${nidList.map(v => \"ids[]=\" + v).join(\"&\")}`\n}\nfunction urlNovelComments(novelId) {\n return `https://api.furrynovel.ink/pixiv/novel/${novelId}/comments`\n}\n\nfunction urlSeriesUrl(seriesId) {\n return `https://www.pixiv.net/novel/series/${seriesId}`\n}\nfunction urlSeriesDetailed(seriesId) {\n return `https://api.furrynovel.ink/pixiv/series/${seriesId}/cache`\n}\n\nfunction urlUsersDetailed(uidList) {\n return `https://api.furrynovel.ink/pixiv/users/cache?${uidList.map(v => \"ids[]=\" + v).join(\"&\")}`\n}\n\nfunction urlSearchNovel(novelName, page) {\n return `https://api.furrynovel.ink/pixiv/search/novel/${novelName}/cache?page=${page}`\n}\nfunction urlSearchUsers(userName) {\n return `https://api.furrynovel.ink/pixiv/search/user/${userName}/cache`\n}\n\nfunction urlCoverUrl(pxImgUrl) {\n let url = `https://pximg.furrynovel.ink/?url=${pxImgUrl}&w=800`\n let headers = {\"Referer\": \"https://furrynovel.ink/\"}\n return `${url}, ${JSON.stringify({headers: headers})}`\n}\n\nfunction urlIllustUrl(illustId) {\n return `https://www.pixiv.net/artworks/${illustId}`\n}\nfunction urlIllustDetailed(illustId) {\n return `https://www.pixiv.net/ajax/illust/${illustId}?lang=zh`\n}\nfunction urlIP(url) {\n const {java, cache} = this\n url = url.replace(\"http://\", \"https://\").replace(\"www.pixiv.net\", \"210.140.139.155\")\n let headers = {\n \"User-Agent\": this.getWebViewUA(),\n \"X-Requested-With\": \"XMLHttpRequest\",\n \"Host\": \"www.pixiv.net\",\n \"Referer\": \"https://www.pixiv.net/\"\n }\n return `${url}, ${JSON.stringify({headers: headers})}`\n}\nfunction urlPixivCoverUrl(url) {\n const {java, cache} = this\n if (!url.trim()) return \"\"\n\n let headers = {\"Referer\": \"https://www.pixiv.net/\"}\n if (url.trim()) {\n if (url.includes(\"i.pximg.net\")) {\n url = url.replace(\"https://i.pximg.net\", \"https://210.140.139.133\")\n headers.Host = \"i.pximg.net\"\n } else {\n url = url.replace(\"https://s.pximg.net\", \"https://210.140.139.133\")\n headers.Host = \"s.pximg.net\"\n }\n }\n return `${url}, ${JSON.stringify({headers: headers})}`\n}\nfunction urlIllustOriginal(illustId, order) {\n const {java, cache} = this\n if (!order || order <= 1) order = 1\n let illustOriginal\n let url = this.urlIP(urlIllustDetailed(illustId))\n\n try {\n illustOriginal = this.cacheGetAndSet(url, () => {\n return JSON.parse(java.ajax(url))\n }).body.urls.original\n if (!illustOriginal) throw Error(\"e\")\n } catch (e) {\n let illustThumb = this.cacheGetAndSet(url, () => {\n return JSON.parse(java.ajax(url))\n }).body.userIllusts[illustId].url\n let date = illustThumb.match(\"\\\\d{4}\\\\/\\\\d{2}\\\\/\\\\d{2}\\\\/\\\\d{2}\\\\/\\\\d{2}\\\\/\\\\d{2}\")[0]\n illustOriginal =`https://i.pximg.net/img-original/img/${date}/${illustId}_p0.png`\n }\n // java.log(illustOriginal)\n return this.urlPixivCoverUrl(illustOriginal.replace(`_p0`, `_p${order - 1}`))\n // return this.urlCoverUrl(illustOriginal.replace(`_p0`, `_p${order - 1}`))\n}\n\nfunction dateFormat(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}\nfunction timeFormat(str) {\n let addZero = function (num) {\n return num < 10 ? '0' + num : num;\n }\n let time = new Date(str);\n let YY = time.getFullYear()\n let MM = addZero(time.getMonth() + 1)\n let DD = addZero(time.getDate())\n let hh = addZero(time.getHours())\n let mm = addZero(time.getMinutes())\n let ss = addZero(time.getSeconds())\n return `${YY}-${MM}-${DD} ${hh}:${mm}:${ss}`\n}\nfunction timeTextFormat(text) {\n if (text === undefined) {\n return \"\"\n }\n return `${text.slice(0, 10)} ${text.slice(11, 19)}`\n}\nfunction sleep(time) {\n let endTime = new Date().getTime() + time\n while(true) {\n if (new Date().getTime() > endTime){\n return;\n }\n }\n}\nfunction sleepToast(text, second) {\n const {java} = this\n java.log(text)\n // java.toast(text)\n java.longToast(text)\n if (second === undefined) second = 0.01\n this.sleep(1000*second)\n}\n\nfunction updateSource() {\n const {java, source} = this\n java.longToast(\"🆙 更新书源\\n\\nJsdelivr CDN 更新有延迟\\nGithub 更新需代理\")\n let onlineSource, comment, sourceName, sourceNameCapitalize, index = 0\n if (source.bookSourceUrl.includes(\"pixiv\")) sourceName = \"pixiv\"\n else if (source.bookSourceUrl.includes(\"furrynovel\")) sourceName = \"linpx\"\n sourceNameCapitalize = sourceName[0].toUpperCase() + sourceName.substring(1)\n\n if (source.bookSourceName.includes(\"备用\")) index = 1\n else if (source.bookSourceName.includes(\"漫画\")) index = 2\n if (source.bookSourceUrl.includes(\"furrynovel.com\")) {\n sourceNameCapitalize = \"FurryNovel\"\n index = 1\n }\n\n try {\n let updateUrl = `https://cdn.jsdelivr.net/gh/DowneyRem/PixivSource@main/${sourceName}.json`\n onlineSource = JSON.parse(java.get(updateUrl,{'User-Agent': 'Mozilla/5.0 (Linux; Android 14)','X-Requested-With': 'XMLHttpRequest'}).body())[index]\n } catch (e) {\n try {\n let updateUrl = `https://raw.githubusercontent.com/DowneyRem/PixivSource/main/${sourceName}.json`\n onlineSource = JSON.parse(java.get(updateUrl,{'User-Agent': 'Mozilla/5.0 (Linux; Android 14)','X-Requested-With': 'XMLHttpRequest'}).body())[index]\n } catch (e) {\n onlineSource = {lastUpdateTime: new Date().getTime(), bookSourceComment: source.bookSourceComment}\n }\n }\n comment = onlineSource.bookSourceComment.split(\"\\n\")\n // onlineSource = source\n // comment = source.bookSourceComment.split(\"\\n\")\n\n let htm = `\n\n\n\n \n \n 更新 ${source.bookSourceName} 书源\n \n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n
${source.bookSourceName} 书源 \n 🔰 使用指南\n || ❤️ 赞助开发\n
☁️ 远程版本:${onlineSource.bookSourceComment.split(\"\\n\")[2].replace(\"书源版本:\", \"\")}📆 更新:${timeFormat(onlineSource.lastUpdateTime)}
📥 本地版本:${source.bookSourceComment.split(\"\\n\")[2].replace(\"书源版本:\", \"\")}📆 更新:${timeFormat(source.lastUpdateTime)}
${comment.slice(3, 10).join(\"
\")}
${comment.slice(comment.length-2, comment.length).join(\"
\")}
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
更新 ${source.bookSourceName} 书源
\n\n`\n java.startBrowser(`data:text/html;charset=utf-8;base64, ${java.base64Encode(htm)}`, '更新书源')\n return []\n}", "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 book.bookUrl = novel.detailedUrl = urlNovelUrl(novel.id)\n if (!novel.seriesId) {\n book.tocUrl = novel.catalogUrl = urlNovelDetailed(novel.id)\n } else {\n book.tocUrl = novel.catalogUrl = urlSeriesDetailed(novel.seriesId)\n }\n return novel\n}\n\n(() => {\n return novelHandler(util.getNovelRes(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 replaceUploadedImage(res, content) {\n // 获取 [uploadedimage:] 的图片链接\n if (res.images !== undefined && res.images !== null) {\n Object.keys(res.images).forEach((key) => {\n // content = content.replace(`[uploadedimage:${key}]`, ``)\n content = content.replace(`[uploadedimage:${key}]`, ``)\n })\n }\n return content\n}\nfunction replacePixivImage(content) {\n // 获取 [pixivimage:] 的图片链接 [pixivimage:1234] [pixivimage:1234-1]\n let matched = content.match(RegExp(/\\[pixivimage:(\\d+)-?(\\d+)]/gm))\n if (matched) {\n matched.forEach(pixivimage => {\n let matched2, illustId, order = 0\n if (pixivimage.includes(\"-\")) {\n matched2 = pixivimage.match(RegExp(\"(\\\\d+)-(\\\\d+)\"))\n illustId = matched2[1]; order = matched2[2]\n } else {\n matched2 = pixivimage.match(RegExp(\"\\\\d+\"))\n illustId = matched2[0];\n }\n if (urlIllustOriginal(illustId, order)) {\n content = content.replace(`${pixivimage}`, ``)\n } else {\n content = content.replace(`${pixivimage}`, ``)\n }\n })\n }\n return content\n}\nfunction replaceNewPage(content) {\n // 替换 Pixiv 分页标记符号 [newpage]\n let 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 return content\n}\nfunction replaceChapter(content) {\n // 替换 Pixiv 章节标记符号 [chapter:]\n let 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 return content\n}\nfunction replaceJumpPage(content) {\n // 替换 Pixiv 跳转页面标记符号 [[jump:]]\n let 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 return content\n}\nfunction replaceJumpUrl(content) {\n // 替换 Pixiv 链接标记符号 [[jumpuri: > ]]\n let 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 // if (util.environment.IS_LEGADO) {\n // content = content.replace(`${matchedText}`, ` ${urlName}`)\n // } else {\n if (urlLink === urlName) {\n content = content.replace(`${matchedText}`, `${urlName}`)\n } else {\n content = content.replace(`${matchedText}`, `${urlName}: ${urlLink}`)\n }\n // }\n }\n }\n return content\n}\nfunction replaceRb(content) {\n // 替换 Pixiv 注音标记符号 [[rb: > ]]\n let 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_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 getContent(res) {\n let content = res.content\n if (util.SHOW_COMMENTS === true && res.desc !== \"\") {\n content = res.desc + \"\\n\" + \"——————————\\n\".repeat(2) + content\n }\n\n // 替换 Pixiv 标记符\n content = replaceUploadedImage(res, content)\n content = replacePixivImage(content)\n content = replaceNewPage(content)\n content = replaceChapter(content)\n content = replaceJumpPage(content)\n content = replaceJumpUrl(content)\n content = replaceRb(content)\n return content\n}\n\n(() => {\n return getContent(util.getNovelRes(result))\n})()", "imageStyle": "DEFAULT", "callBackJs": "// 恢复阅读搜索作者\nif (event === \"clickBookName\") {\n java.searchBook(book.name)\n}\n// 恢复阅读搜索作者\nif (event === \"clickAuthor\") {\n java.searchBook(book.author)\n}\n// 覆盖阅读默认分享\nif (event === \"clickShareBook\") {\n let text = `我正在看:【${book.author}】创作的《${book.name}》`\n if (!!book.durChapterTitle && String(book.name) !== String(book.durChapterTitle)) {\n text += `的 【${book.durChapterTitle}】`\n }\n text += `\\n\\n小说链接:\\n${book.bookUrl}\\n\\n分享自【开源阅读】Linpx书源。使用添加网址,快速添加本文`\n java.copyText(text)\n}\n\n// 开始书架刷新\nif (event === \"startShelfRefresh\") {\n source.putConcurrent(\"1/2000\")\n}\n// 结束书架刷新\nif (event === \"endShelfRefresh\") {\n source.putConcurrent(\"3/2000\")\n}" }, "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 * @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 = getWebviewJson(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 = getWebviewJson(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 handlerRegexNovels() {\n return () => {\n let result = java.webView(null, \"https://furrynovel.ink\", null)\n let name = result.match(RegExp('

(.*?)
'))[1]\n let resp = getAjaxJson(urlSearchNovel(name))\n if (resp.total !== undefined) {\n return util.formatNovels(util.handNovels(util.combineNovels(resp.novels)))\n }\n return []\n }\n}\n\nfunction handlerFactory() {\n if (baseUrl.includes(\"https://cdn.jsdelivr.net\")) {\n return () => {updateSource(); return []}\n }\n if (baseUrl.includes(\"/fav/user\")) {\n return handlerRecommendUsers()\n }\n if (baseUrl.includes(\"/pixiv/novels/recent\")) {\n return handlerFollowLatest()\n }\n if (baseUrl.includes(\"https://furrynovel.ink\")) {\n return handlerRegexNovels()\n }\n else {\n return () => {startBrowser(baseUrl, \"\"); return []}\n }\n}\n\n(() => {\n return handlerFactory()()\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// 存储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\nfunction getUser(username, exactMatch) {\n let resp = getAjaxJson(urlSearchUsers(String(username)))\n java.log(urlSearchUsers(String(username)))\n // java.log(JSON.stringify(resp))\n if (resp.error || resp.total === 0) {\n return []\n }\n\n // 去除无小说作者\n resp.users = resp.users.filter(user => user.novels.length >= 1)\n // java.log(JSON.stringify(resp))\n if (resp.total > resp.users.length) {\n java.log(`已经去除 ${resp.total - resp.users.length} 位无小说的作者`)\n }\n if (resp.users.length === 0) {\n return []\n }\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:${urlNovelsDetailed(list)}`)\n return getWebviewJson(urlNovelsDetailed(list))\n}\n\nfunction findUserNovels() {\n let novelList = []\n let username = String(java.get(\"key\"))\n let userArr = getUser(username, false)\n // 获取用户所有小说\n let uidList = userArr.filter(user => {\n return user.novels.length > 0\n }).map(user => user.id)\n // java.log(JSON.stringify(uidList))\n\n if (uidList.length > 0) {\n let list = getWebviewJson(urlUsersDetailed(uidList)) // 包含所有小说数据\n // java.log(JSON.stringify(list))\n let nidList = []\n // 从两层数组中提取novelsId\n list.forEach(user => {\n // 按id降序排序-相当于按时间降序排序\n user.novels.reverse().forEach(nid => nidList.push(nid))\n // series 数据写入缓存\n user.series.forEach(series => {\n series.novels = []\n putInCache(`LSeries${series.id}`, series)\n })\n })\n // java.log(JSON.stringify(nidList))\n getUserNovels(nidList).forEach(novel => {\n novelList.push(novel)\n })\n }\n\n // series 数据写入缓存\n novelList.forEach(novel =>{\n if (novel.seriesId) {\n let series = getFromCache(`LSeries${novel.seriesId}`)\n series.novels.push(novel)\n putInCache(`LSeries${novel.seriesId}`, series)\n }\n })\n return novelList.reverse() // 新小说前置\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 search(name, page=1) {\n let resp = getAjaxJson(urlSearchNovel(name, page))\n java.log(urlSearchNovel(name, page))\n if (resp.error === undefined && resp.total > 0) {\n return resp.novels\n } else {\n return []\n }\n}\n\nfunction getConvertNovels() {\n let novels = []\n let novelName = String(java.get(\"key\"))\n let name1 = String(java.s2t(novelName))\n let name2 = String(java.t2s(novelName))\n if (name1 !== novelName) novels = novels.concat(search(name1))\n if (name2 !== novelName) novels = novels.concat(search(name2))\n return novels\n}\n\n(() => {\n let novels = []\n let key = String(java.get(\"key\"))\n if (key.startsWith(\"@\")) {\n java.put(\"key\", key.slice(1))\n novels = novels.concat(findUserNovels())\n } else if (key.startsWith(\"#\")) {\n java.put(\"key\", key.slice(1))\n novels = novels.concat(getNovels())\n if (util.CONVERT_CHINESE) novels = novels.concat(getConvertNovels())\n } else {\n novels = novels.concat(getNovels())\n novels = novels.concat(findUserNovels())\n if (util.CONVERT_CHINESE) novels = novels.concat(getConvertNovels())\n }\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})()", "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 urlNovel(novelId) {\n if (util.SHOW_ORIGINAL_LINK) {\n return urlNovelUrl(novelId)\n } else {\n return urlNovelDetailed(novelId)\n }\n}\n\nfunction oneShotHandler(resp) {\n resp.textCount = resp.content.length\n resp.updateDate = timeTextFormat(resp.createDate)\n return [{\n title: resp.title.trim(),\n chapterUrl: urlNovel(resp.id),\n chapterInfo:`${resp.updateDate}  ${resp.textCount}字`\n }]\n}\n\nfunction seriesHandler(resp) {\n resp.novels.forEach(novel => {\n novel.title = novel.title.trim()\n novel.chapterUrl = urlNovel(novel.id)\n // novel.updateDate = String(novel.coverUrl.match(RegExp(\"\\\\d{4}/\\\\d{2}/\\\\d{2}\"))) //fake\n novel.detail = getAjaxJson(urlNovelDetailed(novel.id))\n novel.textCount = novel.detail.content.length\n novel.updateDate = timeTextFormat(novel.detail.createDate)\n novel.chapterInfo = `${novel.updateDate}  ${novel.textCount}字`\n delete novel.detail\n })\n return resp.novels\n}\n\nfunction seriesContentHandler(resp) {\n let novels = [], prevNovels = [], nextNovels = []\n while (resp.series.prev !== null && resp.series.prev !== undefined) {\n prevNovels.push(resp.series.prev)\n resp = getAjaxJson(urlNovelDetailed(resp.series.prev.id))\n }\n nextNovels.push({id: resp.id, order: resp.series.order, title: resp.title})\n while (resp.series.next !== null && resp.series.prev !== undefined) {\n nextNovels.push(resp.series.next)\n resp = getAjaxJson(urlNovelDetailed(resp.series.next.id))\n }\n novels = novels.concat(prevNovels.reverse())\n novels = novels.concat(nextNovels)\n novels.forEach(novel => {\n novel.title = novel.title.trim()\n novel.chapterUrl = urlNovel(novel.id)\n novel.detail = getAjaxJson(urlNovelDetailed(novel.id))\n novel.textCount = novel.detail.content.length\n novel.updateDate = timeTextFormat(novel.detail.createDate)\n novel.chapterInfo = `${novel.updateDate}  ${novel.textCount}字`\n delete novel.detail\n })\n // java.log(JSON.stringify(novels))\n return novels\n}\n\n(() => {\n let resp = util.getNovelResSeries(result)\n if (resp.novels !== undefined) {\n return seriesHandler(resp)\n } else if (resp.series) {\n return seriesContentHandler(resp)\n } else {\n return oneShotHandler(resp)\n }\n})()", "chapterName": "title", "chapterUrl": "chapterUrl", "updateTime": "chapterInfo" }, "searchUrl": "@js:\njava.put(\"key\", key)\njava.put(\"page\", page)\nif (key.startsWith(\"@\") || key.startsWith(\"@\")) {\n key = key.slice(1)\n java.log(`👤 搜索作者:${key}`)\n} else if (key.startsWith(\"#\") || key.startsWith(\"#\")) {\n key = key.slice(1)\n java.log(`#️⃣ 搜索标签:${key}`)\n} else {\n java.log(`🔍 搜索内容:${key}`)\n}\nurlSearchNovel(key, page)", "variableComment": "⚙️ 自定义书源设置:\n⚙️ 自定义设置:请在基本-变量说明处修改代码\n⚙️ 自定义设置:将 true 改为 false,或相反\n⚠️ 设置源变量【无法】更改书源自定义设置\n⚠️ 注意不要添加或删除尾随逗号\",\"\n以下内容为书源设置:\n{\n\"CONVERT_CHINESE\": true,\n\"MORE_INFORMATION\": false,\n\"SHOW_ORIGINAL_LINK\": true,\n\n\"REPLACE_TITLE_MARKS\": true,\n\"SHOW_CAPTIONS\": true,\n\"SHOW_COMMENTS\": true,\n\n\"DEBUG\": false\n}\n\n// CONVERT_CHINESE\n// 搜索:搜索时进行繁简转换\n// MORE_INFORMATION\n// 详情:书籍简介显示更多信息\n// SHOW_ORIGINAL_LINK\n// 目录:显示源链接,但会增加请求次数\n\n// REPLACE_TITLE_MARKS\n// 正文:注音内容为汉字时,替换为书名号\n// SHOW_CAPTIONS\n// 正文:章首显示小说描述\n// SHOW_COMMENTS\n// 正文:章尾显示小说评论\n\n// DEBUG\n// 调试模式\n\n", "weight": 0, "loginUrl": "", "loginUi": "", "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 = {}\n // 输出书源信息\n java.log(`${source.bookSourceComment.split(\"\\n\")[0]}`)\n java.log(`📌 ${source.bookSourceComment.split(\"\\n\")[2]}`)\n java.log(`📆 更新时间:${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.CONVERT_CHINESE = true // 搜索:搜索时进行繁简转换\n settings.MORE_INFORMATION = false // 详情:书籍简介显示更多信息\n settings.SHOW_ORIGINAL_LINK = true // 目录:显示原始链接,但会增加大量请求\n settings.REPLACE_TITLE_MARKS = true // 正文:注音内容为汉字时,替换为书名号\n // settings.SHOW_CAPTIONS = true // 正文:章首显示描述\n // settings.SHOW_COMMENTS = true // 正文:章尾显示评论\n settings.DEBUG = false // 全局:调试模式\n java.log(\"⚙️ 使用默认设置(无自定义设置 或 自定义设置有误)\")\n }\n u.CONVERT_CHINESE = settings.CONVERT_CHINESE\n u.MORE_INFORMATION = settings.MORE_INFORMATION\n u.SHOW_UPDATE_TIME = settings.SHOW_UPDATE_TIME\n u.SHOW_ORIGINAL_LINK = settings.SHOW_ORIGINAL_LINK\n u.REPLACE_TITLE_MARKS = settings.REPLACE_TITLE_MARKS\n // u.SHOW_CAPTIONS = settings.SHOW_CAPTIONS\n // u.SHOW_COMMENTS = settings.SHOW_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 u.debugFunc = (func) => {\n if (util.DEBUG) {\n func()\n }\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 // 获取 模拟系列数据\n u.getSeriesData = function (seriesId) {\n let series = getAjaxJson(urlSeriesDetailed(seriesId))\n if (series.error) series = getFromCache(`LSeries${seriesId}`)\n // java.log(JSON.stringify(series))\n return series\n }\n\n // 处理 novels 列表\n u.handNovels = function (novels) {\n novels.forEach(novel => {\n if (!novel.id) novel.id = novel._id\n // novel.title = novel.title\n // novel.userName = novel.userName\n // novel.tags = novel.tags\n if (novel.tags === undefined) {\n novel.tags = []\n }\n // 兼容详情页\n if (novel.content) {\n if (novel.series) {\n novel.seriesId = novel.series.id\n novel.seriesTitle = novel.series.title\n }\n novel.textCount = novel.length = novel.content.length\n }\n\n if (!novel.seriesId) {\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 = urlNovelDetailed(novel.id)\n } else {\n java.log(`正在获取系列小说:${novel.seriesId}`)\n // let series = getAjaxJson(urlSeriesDetailed(novel.seriesId))\n let series = this.getSeriesData(novel.seriesId)\n novel.id = series.novels[0].id\n novel.title = series.title\n if (series.tags) {\n novel.tags = novel.tags.concat(series.tags)\n }\n novel.tags.unshift(\"长篇\")\n novel.textCount = null // 无数据\n novel.createDate = null // 无数据\n novel.latestChapter = series.novels.reverse()[0].title\n novel.description = series.caption\n // 后端目前没有系列的 coverUrl 字段\n // novel.coverUrl = series.coverUrl\n novel.coverUrl = series.novels[0].coverUrl\n novel.detailedUrl = urlNovelDetailed(novel.id)\n\n let firstNovel = getAjaxJson(urlNovelDetailed(novel.id))\n if (firstNovel.error !== true) {\n novel.tags = novel.tags.concat(firstNovel.tags)\n novel.createDate = firstNovel.createDate\n if (novel.description === \"\") {\n novel.description = firstNovel.desc\n }\n }\n }\n })\n return novels\n }\n\n // 小说信息格式化\n u.formatNovels = function (novels) {\n novels.forEach(novel => {\n novel.title = novel.title.trim()\n if (!novel.userName.startsWith(\"@\")) novel.userName = `@${novel.userName}`\n novel.coverUrl = urlCoverUrl(novel.coverUrl)\n novel.createDate = dateFormat(novel.createDate)\n\n novel.tags2 = []\n for (let i in novel.tags) {\n let tag = novel.tags[i]\n if (tag.includes(\"/\")) {\n let tags = tag.split(\"/\")\n novel.tags2 = novel.tags2.concat(tags)\n } else {\n novel.tags2.push(tag)\n }\n }\n novel.tags = Array.from(new Set(novel.tags2))\n novel.tags = novel.tags.join(\",\")\n\n if (util.MORE_INFORMATION) {\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 let isJson = isJsonString(result)\n let isHtml = isHtmlString(result)\n if (!isJson && isHtml) {\n let id = baseUrl.match(new RegExp(\"\\\\d+\"))[0]\n let pattern = \"(https?://)?(www\\\\.)?pixiv\\\\.net/novel/(series/)?\\\\d+\"\n let isSeries = baseUrl.match(new RegExp(pattern))\n if (isSeries) {\n java.log(`系列ID:${id}`)\n res = getAjaxJson(urlSeriesDetailed(id))\n // res = this.getSeriesData(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 }\n if (isJson) {\n res = JSON.parse(result)\n }\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 = getAjaxJson(urlNovelDetailed(novelId))\n }\n if (res.error) {\n java.log(`无法从 Linpx 获取当前小说`)\n java.log(JSON.stringify(res))\n }\n return res\n }\n\n // 从网址获取id,尽可能返回系列 res,单篇小说返回小说 res\n u.getNovelResSeries = function (result) {\n let seriesId = 0, res = []\n let isJson = isJsonString(result)\n let isHtml = isHtmlString(result)\n if (!isJson && 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 = getAjaxJson(urlNovelDetailed(id))\n }\n }\n }\n if (isJson) {\n res = JSON.parse(result)\n }\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 = getAjaxJson(urlSeriesDetailed(seriesId))\n res = this.getSeriesData(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 util = u\n java.put(\"util\", objStringify(u))\n}\n\npublicFunc()\njava.getStrResponse(null, null)", "lastUpdateTime": 1765987200251 }, { "bookSourceComment": "🐯 兽人控小说站书源(更新📆:2025/12/18)\n\n书源版本:253\n可用功能:✅搜索✅发现✅添加网址✅订阅源\n搜索小说:✅单篇✅系列✅作者✅标签\n发现小说:✅热门小说✅最新小说✅随便来点\n添加网址:✅兽人控小说站链接\n订阅用法:点击订阅源打开小说/系列小说,点击【加入书架】按钮,添加小说到书架\n\n书源发布:Pixiv 书源频道 https://t.me/PixivSource\n兽人阅读频道 https://t.me/FurryReading\n项目地址:https://github.com/DowneyRem/PixivSource\n使用教程:https://downeyrem.github.io/PixivSource/FurryNovel\n\n规则订阅:Import 订阅源\nhttps://cdn.jsdelivr.net/gh/DowneyRem/PixivSource@main/import.json\nhttps://raw.githubusercontent.com/DowneyRem/PixivSource/main/import.json\n\n⚙️ 书源设置:\n书源管理 - 编辑书源 - 基本 - 变量说明 - 修改并保存\n\n🔎 筛选发现:\n发现 - 长按\"Pixiv\" - 编辑 - 右上角菜单 - 设置源变量\n设置源变量:输入想要搜索/筛选的标签,以空格间隔(或一行一个),保存\n发现 - 长按\"Pixiv\" - 刷新 - 查看他人收藏", "bookSourceGroup": "🔞 Pixiv,🐲 Furry", "bookSourceName": "🐯 兽人控小说站", "bookSourceType": 0, "bookSourceUrl": "https://furrynovel.com", "bookUrlPattern": "(https?://)?(www\\.|api\\.)?furrynovel\\.com(/api)?/(zh|en|ja)/novel/\\d+(/chapter/d+)?", "concurrentRate": "3/2000", "customButton": false, "customOrder": 4, "enabled": true, "enabledCookieJar": true, "enabledExplore": true, "eventListener": true, "exploreUrl": "@js:\nlet keyword = String(source.getVariable()).replace(\"#\", \"\")\nlet key = keyword.split(/[  ,,、\\n]/)\nif (key.includes(\"\")) {\n key.splice(key.indexOf(\"\"), 1)\n}\nif (key.length === 0) {\n sleepToast(\"可设置源变量,筛选发现 🔍 \")\n sleepToast('发现页 - 长按\"兽人控小说站\" - 编辑 - 右上角菜单 - 设置源变量')\n} else {\n sleepToast(`正在搜索:${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 {\"🆙 更新\": \"https://cdn.jsdelivr.net/gh/DowneyRem/PixivSource@main/linpx.json\"},\n {\"📙 书源相关 📙\": \"\"},\n {\"🏠 主页\": \"https://downeyrem.github.io/PixivSource\"},\n {\"🔰 指南\": \"https://downeyrem.github.io/PixivSource/FurryNovel\"},\n {\"🐞 反馈\": \"https://github.com/DowneyRem/PixivSource/issues\"},\n {\"💰 打赏\": \"https://downeyrem.github.io/PixivSource/Sponsor\"},\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_flexShrink = 1\n item.style.layout_alignSelf = \"auto\"\n item.style.layout_wrapBefore = \"false\"\n if (item.url === \"\") {\n item.style.layout_flexBasisPercent = 1\n } else {\n item.style.layout_flexBasisPercent = -1\n }\n})\nJSON.stringify(li)", "header": "", "jsLib": "var cacheSaveSeconds = 7*24*60*60 // 长期缓存时间 7天\nvar cacheTempSeconds = 10*60*1000 // 短期缓存 10min\n\nfunction cacheGetAndSet(key, supplyFunc) {\n const {java, cache} = this\n let v = cache.get(key)\n // 缓存信息错误时,保存 10min 后重新请求\n if (v && JSON.parse(v).code === 404) {\n if (new Date().getTime() >= JSON.parse(v).timestamp + cacheTempSeconds) {\n cache.delete(key)\n v = cache.get(key)\n }\n }\n // 无缓存信息时,进行请求\n if (v === undefined || v === null) {\n v = supplyFunc()\n v.timestamp = new Date().getTime()\n v = JSON.stringify(v)\n cache.put(key, v, cacheSaveSeconds)\n }\n return JSON.parse(v)\n}\n\nfunction getAjaxJson(url, forceUpdate) {\n const {java, cache} = this\n let v = cache.get(url)\n if (forceUpdate && v && new Date().getTime() >= JSON.parse(v).timestamp + cacheTempSeconds) cache.delete(url)\n return this.cacheGetAndSet(url, () => {\n return JSON.parse(java.ajax(url))\n })\n}\nfunction getAjaxAllJson(urls, forceUpdate) {\n const {java, cache} = this\n let v = cache.get(urls)\n if (forceUpdate && v && new Date().getTime() >= JSON.parse(v).timestamp + cacheTempSeconds) cache.delete(urls)\n return this.cacheGetAndSet(urls, () => {\n let result = java.ajaxAll(urls).map(resp => JSON.parse(resp.body()))\n cache.put(urls, JSON.stringify(result), cacheSaveSeconds)\n for (let i in urls) cache.put(urls[i], JSON.stringify(result[i]), cacheSaveSeconds)\n return result\n })\n}\n\nfunction getWebViewUA() {\n const {java, cache} = this\n let userAgent = String(java.getWebViewUA())\n if (userAgent.includes(\"Windows NT 10.0; Win64; x64\")) {\n userAgent = \"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Mobile Safari/537.36\"\n }\n // java.log(`userAgent=${userAgent}`)\n cache.put(\"userAgent\", userAgent)\n return String(userAgent)\n}\nfunction startBrowser(url, title) {\n const {java} = this\n if (!title) title = url\n let msg = \"\"\n let headers = `{\"headers\": {\"User-Agent\":\"${this.getWebViewUA()}\"}}`\n if (url.includes(\"github.com\") || url.includes(\"github.io\")) {\n msg += \"\\n\\n即将打开 Github\\n请确认已开启代理/梯子/VPN等\"\n }\n this.sleepToast(msg, 0.01)\n java.startBrowser(`${url}, ${headers}`, title)\n}\n\nfunction urlNovelUrl(novelId) {\n return `https://furrynovel.com/zh/novel/${novelId}`\n}\nfunction urlNovelDetail(novelId) {\n return `https://api.furrynovel.com/api/zh/novel/${novelId}`\n}\nfunction urlNovelsDetail(novelIds) {\n return `https://api.furrynovel.com/api/zh/novel?${novelIds.map(v => \"ids[]=\" + v).join(\"&\")}`\n}\nfunction urlNovelChapterUrl(novelId, chapterId) {\n return `https://furrynovel.com/zh/novel/${novelId}/chapter/${chapterId}`\n}\nfunction urlNovelChapterInfo(novelId) {\n return `https://api.furrynovel.com/api/zh/novel/${novelId}/chapter`\n}\nfunction urlNovelChapterDetail(novelId, chapterId) {\n return `https://api.furrynovel.com/api/zh/novel/${novelId}/chapter/${chapterId}`\n}\n\nfunction urlSearchNovel(name, page) {\n return `https://api.furrynovel.com/api/zh/novel?page=${page}&order_by=popular&keyword=${name}`\n}\n\nfunction urlLinpxNovelDetail(sourceId) {\n return `https://api.furrynovel.ink/pixiv/novel/${sourceId}/cache`\n}\nfunction urlLinpxCoverUrl(pxImgUrl) {\n if (!pxImgUrl.trim()) return \"\"\n let url = `https://pximg.furrynovel.ink/?url=${pxImgUrl}&w=800`\n let headers = {\"Referer\": \"https://furrynovel.ink/\"}\n return `${url}, ${JSON.stringify({headers: headers})}`\n}\n\nfunction urlIllustUrl(illustId) {\n return `https://www.pixiv.net/artworks/${illustId}`\n}\nfunction urlIllustDetailed(illustId) {\n return `https://www.pixiv.net/ajax/illust/${illustId}?lang=zh`\n}\nfunction urlIP(url) {\n const {java, cache} = this\n url = url.replace(\"http://\", \"https://\").replace(\"www.pixiv.net\", \"210.140.139.155\")\n let headers = {\n \"User-Agent\": this.getWebViewUA(),\n \"X-Requested-With\": \"XMLHttpRequest\",\n \"Host\": \"www.pixiv.net\",\n \"Referer\": \"https://www.pixiv.net/\"\n }\n return `${url}, ${JSON.stringify({headers: headers})}`\n}\nfunction urlPixivCoverUrl(url) {\n const {java, cache} = this\n if (!url.trim()) return \"\"\n\n let headers = {\"Referer\": \"https://www.pixiv.net/\"}\n if (url.trim()) {\n if (url.includes(\"i.pximg.net\")) {\n url = url.replace(\"https://i.pximg.net\", \"https://210.140.139.133\")\n headers.Host = \"i.pximg.net\"\n } else {\n url = url.replace(\"https://s.pximg.net\", \"https://210.140.139.133\")\n headers.Host = \"s.pximg.net\"\n }\n }\n return `${url}, ${JSON.stringify({headers: headers})}`\n}\nfunction urlIllustOriginal(illustId, order) {\n const {java, cache} = this\n if (!order || order <= 1) order = 1\n let illustOriginal\n let url = this.urlIP(urlIllustDetailed(illustId))\n\n try {\n illustOriginal = this.cacheGetAndSet(url, () => {\n return JSON.parse(java.ajax(url))\n }).body.urls.original\n if (!illustOriginal) throw Error(\"e\")\n } catch (e) {\n try{\n let illustThumb = this.cacheGetAndSet(url, () => {\n return JSON.parse(java.ajax(url))\n }).body.userIllusts[illustId].url\n let date = illustThumb.match(\"\\\\d{4}\\\\/\\\\d{2}\\\\/\\\\d{2}\\\\/\\\\d{2}\\\\/\\\\d{2}\\\\/\\\\d{2}\")[0]\n illustOriginal =`https://i.pximg.net/img-original/img/${date}/${illustId}_p0.png`\n } catch (e) {\n illustOriginal = \"\"\n }\n }\n // java.log(illustOriginal)\n // return this.urlPixivCoverUrl(illustOriginal.replace(`_p0`, `_p${order - 1}`))\n return this.urlLinpxCoverUrl(illustOriginal.replace(`_p0`, `_p${order - 1}`))\n}\n\nfunction urlSourceUrl(source, oneShot, sourceId) {\n if (source === \"bilibili\") {\n return `https://www.bilibili.com/read/readlist/rl${sourceId}/`\n }\n if (source === \"pixiv\" && oneShot === true) {\n return `https://www.pixiv.net/novel/show.php?id=${sourceId}`\n }\n if (source === \"pixiv\" && oneShot === false) {\n return `https://www.pixiv.net/novel/series/${sourceId}`\n }\n}\n\nfunction dateFormat(text) {\n return `${text.slice(0, 10)}`\n}\nfunction timeFormat(str) {\n let addZero = function (num) {\n return num < 10 ? '0' + num : num;\n }\n let time = new Date(str);\n let YY = time.getFullYear()\n let MM = addZero(time.getMonth() + 1)\n let DD = addZero(time.getDate())\n let hh = addZero(time.getHours())\n let mm = addZero(time.getMinutes())\n let ss = addZero(time.getSeconds())\n return `${YY}-${MM}-${DD} ${hh}:${mm}:${ss}`\n}\nfunction timeTextFormat(text) {\n return `${text.slice(0, 10)} ${text.slice(11, 19)}`\n}\nfunction sleep(time) {\n let endTime = new Date().getTime() + time\n while(true) {\n if (new Date().getTime() > endTime) {\n return;\n }\n }\n}\nfunction sleepToast(text, second) {\n const {java} = this\n java.log(text)\n // java.toast(text)\n java.longToast(text)\n if (second === undefined) second = 0.01\n this.sleep(1000*second)\n}\n\nfunction updateSource() {\n const {java, source} = this\n java.longToast(\"🆙 更新书源\\n\\nJsdelivr CDN 更新有延迟\\nGithub 更新需代理\")\n let onlineSource, comment, sourceName, sourceNameCapitalize, index = 0\n if (source.bookSourceUrl.includes(\"pixiv\")) sourceName = \"pixiv\"\n else if (source.bookSourceUrl.includes(\"furrynovel\")) sourceName = \"linpx\"\n sourceNameCapitalize = sourceName[0].toUpperCase() + sourceName.substring(1)\n\n if (source.bookSourceName.includes(\"备用\")) index = 1\n else if (source.bookSourceName.includes(\"漫画\")) index = 2\n if (source.bookSourceUrl.includes(\"furrynovel.com\")) {\n sourceNameCapitalize = \"FurryNovel\"\n index = 1\n }\n\n try {\n let updateUrl = `https://cdn.jsdelivr.net/gh/DowneyRem/PixivSource@main/${sourceName}.json`\n onlineSource = JSON.parse(java.get(updateUrl,{'User-Agent': 'Mozilla/5.0 (Linux; Android 14)','X-Requested-With': 'XMLHttpRequest'}).body())[index]\n } catch (e) {\n try {\n let updateUrl = `https://raw.githubusercontent.com/DowneyRem/PixivSource/main/${sourceName}.json`\n onlineSource = JSON.parse(java.get(updateUrl,{'User-Agent': 'Mozilla/5.0 (Linux; Android 14)','X-Requested-With': 'XMLHttpRequest'}).body())[index]\n } catch (e) {\n onlineSource = {lastUpdateTime: new Date().getTime(), bookSourceComment: source.bookSourceComment}\n }\n }\n comment = onlineSource.bookSourceComment.split(\"\\n\")\n // onlineSource = source\n // comment = source.bookSourceComment.split(\"\\n\")\n\n let htm = `\n\n\n\n \n \n 更新 ${source.bookSourceName} 书源\n \n\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n
${source.bookSourceName} 书源 \n 🔰 使用指南\n || ❤️ 赞助开发\n
☁️ 远程版本:${onlineSource.bookSourceComment.split(\"\\n\")[2].replace(\"书源版本:\", \"\")}📆 更新:${timeFormat(onlineSource.lastUpdateTime)}
📥 本地版本:${source.bookSourceComment.split(\"\\n\")[2].replace(\"书源版本:\", \"\")}📆 更新:${timeFormat(source.lastUpdateTime)}
${comment.slice(3, 8).join(\"
\")}
${comment.slice(comment.length-7, comment.length).join(\"
\")}
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
更新 ${source.bookSourceName} 书源
\n\n`\n java.startBrowser(`data:text/html;charset=utf-8;base64, ${java.base64Encode(htm)}`, '更新书源')\n return []\n}", "lastUpdateTime": 1765987200251, "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 = {}\n // 输出书源信息\n java.log(`${source.bookSourceComment.split(\"\\n\")[0]}`)\n java.log(`📌 ${source.bookSourceComment.split(\"\\n\")[2]}`)\n java.log(`📆 更新时间:${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.CONVERT_CHINESE = true // 搜索:搜索时进行繁简转换\n settings.MORE_INFORMATION = false // 详情:书籍简介显示更多信息\n // settings.SHOW_UPDATE_TIME = true // 目录:显示更新时间,但会增加少许请求\n settings.SHOW_ORIGINAL_LINK = true // 目录:显示原始链接,但会增加大量请求\n settings.REPLACE_TITLE_MARKS = true // 正文:注音内容为汉字时,替换为书名号\n // settings.SHOW_CAPTIONS = true // 正文:章首显示描述\n settings.SHOW_COMMENTS = true // 正文:章尾显示评论\n settings.DEBUG = false // 全局:调试模式\n java.log(\"⚙️ 使用默认设置(无自定义设置 或 自定义设置有误)\")\n }\n u.CONVERT_CHINESE = settings.CONVERT_CHINESE\n u.MORE_INFORMATION = settings.MORE_INFORMATION\n // u.SHOW_UPDATE_TIME = settings.SHOW_UPDATE_TIME\n u.SHOW_ORIGINAL_LINK = settings.SHOW_ORIGINAL_LINK\n u.REPLACE_TITLE_MARKS = settings.REPLACE_TITLE_MARKS\n // u.SHOW_CAPTIONS = settings.SHOW_CAPTIONS\n u.SHOW_COMMENTS = settings.SHOW_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 u.debugFunc = (func) => {\n if (util.DEBUG) {\n func()\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 if (novel.latest_chapters === undefined) {\n novel.latestChapter = null\n novel.detailedUrl = urlNovelDetail(novel.id)\n } else {\n novel.latestChapter = novel.latest_chapters[0].name\n novel.detailedUrl = urlNovelUrl(novel.id)\n }\n novel.description = novel.desc\n novel.coverUrl = novel.cover\n\n // novel.source = novel.source\n novel.oneShot = novel.ext_data.oneShot\n novel.sourceId = novel.source_id\n novel.sourceUrl = 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(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.trim()\n if (!novel.userName.startsWith(\"@\")) novel.userName = `@${novel.userName}`\n novel.tags2 = []\n for (let i in novel.tags) {\n let tag = novel.tags[i]\n if (tag.includes(\"/\")) {\n let tags = tag.split(\"/\")\n novel.tags2 = novel.tags2.concat(tags)\n } else {\n novel.tags2.push(tag)\n }\n }\n novel.tags = Array.from(new Set(novel.tags2))\n novel.tags = novel.tags.join(\",\")\n novel.createDate = dateFormat(novel.createDate)\n novel.updateDate = dateFormat(novel.updateDate)\n novel.syncDate = dateFormat(novel.syncDate)\n if (util.MORE_INFORMATION) {\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同步时间:${novel.syncDate}`\n }\n })\n return novels\n }\n\n u.getNovelRes = function (result, type) {\n let res = {data: []}, chapterId = 0\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\n if (isHtml && fnWebpage) {\n let novelId = baseUrl.match(new RegExp(\"\\\\d+\"))[0]\n if (type === \"detail\") {\n res = getAjaxJson(urlNovelDetail(novelId))\n } else if (type === \"catalog\") {\n res = getAjaxJson(urlNovelChapterInfo(novelId))\n } else if (type === \"content\") {\n try {\n chapterId = baseUrl.match(RegExp(/\\/(\\d+)\\/chapter\\/(\\d+)/))[2]\n } catch (e) {\n chapterId = getAjaxJson(urlNovelChapterInfo(novelId)).data[0].id\n } finally {\n res = getAjaxJson(urlNovelChapterDetail(novelId, chapterId))\n }\n }\n } else {\n res = JSON.parse(result)\n }\n if (res.data.length === 0) {\n java.log(`无法从 FurryNovel 获取当前小说`)\n java.log(JSON.stringify(res))\n }\n return res.data\n }\n\n util = u\n java.put(\"util\", objStringify(u))\n}\n\npublicFunc()\njava.getStrResponse(null, null)", "loginUi": "", "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 book.bookUrl = novel.detailedUrl = urlNovelUrl(novel.id)\n book.tocUrl = novel.catalogUrl = urlNovelChapterInfo(novel.id)\n return novel\n}\n\n(() => {\n return novelHandler(util.getNovelRes(result, \"detail\"))\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 replaceUploadedImage(res, content) {\n // 获取 [uploadedimage:] 的图片链接\n let hasEmbeddedImages = content.match(RegExp(/\\[uploadedimage:(\\d+)-?(\\d+)]/gm))\n if (hasEmbeddedImages) {\n resp = getAjaxJson(urlLinpxNovelDetail(res.source_id))\n Object.keys(resp.images).forEach((key) => {\n content = content.replace(`[uploadedimage:${key}]`, ``)\n })\n }\n return content\n}\nfunction replacePixivImage(content) {\n // 获取 [pixivimage:] 的图片链接 [pixivimage:1234] [pixivimage:1234-1]\n let matched = content.match(RegExp(/\\[pixivimage:(\\d+)-?(\\d+)]/gm))\n if (matched) {\n matched.forEach(pixivimage => {\n let matched2, illustId, order = 0\n if (pixivimage.includes(\"-\")) {\n matched2 = pixivimage.match(RegExp(\"(\\\\d+)-(\\\\d+)\"))\n illustId = matched2[1]; order = matched2[2]\n } else {\n matched2 = pixivimage.match(RegExp(\"\\\\d+\"))\n illustId = matched2[0];\n }\n if (urlIllustOriginal(illustId, order)) {\n content = content.replace(`${pixivimage}`, ``)\n } else {\n content = content.replace(`${pixivimage}`, ``)\n }\n })\n }\n return content\n}\nfunction replaceNewPage(content) {\n // 替换 Pixiv 分页标记符号 [newpage]\n let 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 return content\n}\nfunction replaceChapter(content) {\n // 替换 Pixiv 章节标记符号 [chapter:]\n let 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 return content\n}\nfunction replaceJumpPage(content) {\n // 替换 Pixiv 跳转页面标记符号 [[jump:]]\n let 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 return content\n}\nfunction replaceJumpUrl(content) {\n // 替换 Pixiv 链接标记符号 [[jumpuri: > ]]\n let 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 // if (util.environment.IS_LEGADO) {\n // content = content.replace(`${matchedText}`, ` ${urlName}`)\n // } else {\n if (urlLink === urlName) {\n content = content.replace(`${matchedText}`, `${urlName}`)\n } else {\n content = content.replace(`${matchedText}`, `${urlName}: ${urlLink}`)\n }\n // }\n }\n }\n return content\n}\nfunction replaceRb(content) {\n // 替换 Pixiv 注音标记符号 [[rb: > ]]\n let 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_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 getContent(res) {\n let content = res.content\n // 替换 Pixiv 标记符\n content = replaceUploadedImage(res, content)\n content = replacePixivImage(content)\n content = replaceNewPage(content)\n content = replaceChapter(content)\n content = replaceJumpPage(content)\n content = replaceJumpUrl(content)\n content = replaceRb(content)\n return content\n}\n\n(function () {\n return getContent(util.getNovelRes(result, \"content\"))\n})()", "imageStyle": "DEFAULT", "callBackJs": "// java.log(event)\n// java.log(typeof event)\n\n// 恢复阅读搜索作者\nif (event === \"clickBookName\") {\n java.searchBook(book.name)\n}\n// 恢复阅读搜索作者\nif (event === \"clickAuthor\") {\n java.searchBook(book.author)\n}\n// 覆盖阅读默认分享\nif (event === \"clickShareBook\") {\n let text = `我正在看:【${book.author}】创作的《${book.name}》`\n if (!!book.durChapterTitle && String(book.name) !== String(book.durChapterTitle)) {\n text += `的 【${book.durChapterTitle}】`\n }\n text += `\\n\\n小说链接:\\n${book.bookUrl}\\n\\n分享自【开源阅读】兽人控小说站书源。使用添加网址,快速添加本文`\n java.copyText(text)\n}\n\n// 开始书架刷新\nif (event === \"startShelfRefresh\") {\n source.putConcurrent(\"1/2000\")\n}\n// 结束书架刷新\nif (event === \"endShelfRefresh\") {\n source.putConcurrent(\"3/2000\")\n}" }, "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\nfunction handlerFactory() {\n if (baseUrl.includes(\"https://cdn.jsdelivr.net\")) {\n return updateSource()\n }\n if (baseUrl.includes(\"furrynovel.com\")) {\n return util.formatNovels(util.handNovels(util.getNovels()))\n }\n else {\n return startBrowser(baseUrl, \"\")\n }\n}\n\n(() => {\n return handlerFactory()\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 search(name, page=1) {\n let resp = getAjaxJson(urlSearchNovel(name, page))\n java.log(urlSearchNovel(name, page))\n if (resp.code === 200 && resp.count > 0) {\n return resp.data\n } else {\n return []\n }\n}\n\nfunction getConvertNovels() {\n let novels = []\n let novelName = String(java.get(\"key\"))\n let name1 = String(java.s2t(novelName))\n let name2 = String(java.t2s(novelName))\n if (name1 !== novelName) novels = novels.concat(search(name1))\n if (name2 !== novelName) novels = novels.concat(search(name2))\n return novels\n}\n\n(() => {\n let novels = []\n novels = novels.concat(util.getNovels())\n if (util.CONVERT_CHINESE) novels = novels.concat(getConvertNovels())\n novels = novels.sort((a, b) => a.source_id > b.source_id ? 1 : -1)\n // java.log(JSON.stringify(novels))\n // 返回空列表中止流程\n if (novels.length === 0) {\n return []\n }\n return util.formatNovels(util.handNovels(novels))\n})();", "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 urlNovelChapter(novelId, chapterId) {\n if (util.SHOW_ORIGINAL_LINK) {\n return urlNovelChapterUrl(novelId, chapterId)\n } else {\n return urlNovelChapterDetail(novelId, chapterId)\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.detailedUrl = urlNovelUrl(novel.novelId)\n novel.chapterName = novel.title = novel.name\n novel.chapterUrl = urlNovelChapter(novel.novelId, novel.chapterId)\n novel.chapterInfo = `${novel.created_at}  ${novel.text_count}字`\n })\n return novels\n}\n\n(function () {\n return novelHandler(util.getNovelRes(result, \"catalog\"))\n})()", "chapterName": "title", "chapterUrl": "chapterUrl", "updateTime": "chapterInfo" }, "searchUrl": "@js:\njava.put(\"key\", key)\njava.put(\"page\", page)\nif (key.startsWith(\"@\") || key.startsWith(\"@\")) {\n key = key.slice(1)\n java.log(`👤 搜索作者:${key}`)\n} else if (key.startsWith(\"#\") || key.startsWith(\"#\")) {\n key = key.slice(1)\n java.log(`#️⃣ 搜索标签:${key}`)\n} else {\n java.log(`🔍 搜索内容:${key}`)\n}\n\njava.put(\"key\", key)\nurlSearchNovel(key, page)", "variableComment": "🔎 筛选发现:\n发现 - 长按\"兽人小说站\" - 编辑 - 右上角菜单 - 设置源变量\n设置源变量:输入想要搜索/筛选的标签,以空格间隔(或一行一个),保存\n发现 - 长按\"兽人小说站\" - 刷新 - 查看他人收藏\n以下内容为源变量模板:\n中文 原创 纯爱\n\n\n⚙️ 自定义书源设置:\n⚙️ 自定义设置:请在基本-变量说明处修改代码\n⚙️ 自定义设置:将 true 改为 false,或相反\n⚠️ 设置源变量【无法】更改书源自定义设置\n⚠️ 注意不要添加或删除尾随逗号\",\"\n⚠️ 发现页需要长按\"兽人控小说站\",手动刷新\n以下内容为书源设置:\n{\n\"CONVERT_CHINESE\": true,\n\"MORE_INFORMATION\": false,\n\"SHOW_ORIGINAL_LINK\": true,\n\n\"REPLACE_TITLE_MARKS\": true,\n\"SHOW_CAPTIONS\": true,\n\"SHOW_COMMENTS\": true,\n\n\"FAST\": true,\n\"DEBUG\": false\n}\n\n// CONVERT_CHINESE\n// 搜索:搜索时进行繁简转换,但搜索会变慢\n// MORE_INFORMATION\n// 详情:书籍简介显示更多信息\n// SHOW_ORIGINAL_LINK\n// 目录:显示原始链接,但会增加大量请求\n\n// REPLACE_TITLE_MARKS\n// 正文:注音内容为汉字时,替换为书名号\n// SHOW_CAPTIONS\n// 正文:章首显示小说描述\n// SHOW_COMMENTS\n// 正文:章尾显示小说评论\n\n// DEBUG\n// 调试模式\n\n", "weight": 0 } ]