[ { "bookSourceComment": "由 NURUPO Studio 运营的 Linpx\n\n功能:搜索、发现、添加网址、订阅源添加小说", "bookSourceGroup": "🔞 Pixiv", "bookSourceName": "Linpx NURUPO", "bookSourceType": 0, "bookSourceUrl": "https://furrynovel.ink", "customOrder": 1, "enabled": false, "enabledCookieJar": false, "enabledExplore": true, "exploreUrl": "[\n {\n \"title\": \"推荐作者\",\n \"url\": \"https://api.furrynovel.ink/fav/user\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\":0.3\n }\n },\n {\n \"title\": \"最新小说\",\n \"url\": \"https://api.furrynovel.ink/pixiv/novels/recent?page={{page}}\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\":0.3\n }\n }\n]", "lastUpdateTime": 1712318032222, "respondTime": 180000, "ruleBookInfo": { "author": "author", "coverUrl": "cover_url", "init": "@js:\n(function (res) {\n let isHtml = res.startsWith(\"\")\n if (isHtml) {\n let matchResult = baseUrl.match(new RegExp(\"pn|pixiv/novel\"))\n if (matchResult == null) {\n return []\n }\n let id = baseUrl.match(new RegExp(\"\\\\d+\"))[0]\n if (baseUrl.includes(\"/cache\")) {\n res = JSON.parse(java.ajax(`https://api.furrynovel.ink/pixiv/novel/${id}/cache`))\n // 不获取缓存系列\n res.series = null\n } else {\n res = JSON.parse(java.ajax(`https://api.furrynovel.ink/pixiv/novel/${id}`))\n }\n } else {\n res = JSON.parse(res)\n if (res.total === 0) {\n return []\n }\n }\n\n let prop = {}\n //为了兼顾导入书架直接走详情页逻辑\n //这里不能直接用book.xxx 来复用搜索页处理结果\n prop['author'] = res.userName\n prop['count'] = book.wordCount\n prop['desc'] = res.desc\n prop['cover_url'] = `https://api.furrynovel.ink/proxy/pximg?url=${res.coverUrl}`\n\n if (res.series === undefined || res.series === null) {\n prop['name'] = res.title\n if (baseUrl.includes(\"/cache\")) {\n prop['catalog'] = `https://api.furrynovel.ink/pixiv/novel/${res.id}/cache`\n } else {\n prop['catalog'] = `https://api.furrynovel.ink/pixiv/novel/${res.id}`\n }\n res.tags.unshift('单本')\n } else {\n prop['name'] = res.series.title\n res.tags.unshift('长篇')\n prop['catalog'] = `https://api.furrynovel.ink/pixiv/series/${res.series.id}`\n }\n prop['classes'] = res.tags.join(\",\")\n return prop\n})(result)\n", "intro": "desc", "kind": "classes", "lastChapter": "latest_chapter", "name": "name", "tocUrl": "catalog", "wordCount": "count" }, "ruleContent": { "content": "@js:\n(function (res) {\n res = JSON.parse(res)\n let content = res.content\n if (res.series !== null && res.desc !== undefined && res.desc !== \"\") {\n content = res.desc + \"\\n\" + \"——————————\\n\".repeat(2) + content\n }\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 return content\n})(result)", "imageStyle": "FULL", "replaceRegex": "", "sourceRegex": "" }, "ruleExplore": { "author": "userName", "bookList": "@js:\n\nfunction urlSearchUsers(username) {\n return `https://api.furrynovel.ink/pixiv/search/user/${username}`\n}\n\nfunction urlSeries(seriesId) {\n return `https://api.furrynovel.ink/pixiv/series/${seriesId}`\n}\n\nfunction urlNovelsDetailed(nidList) {\n return `https://api.furrynovel.ink/pixiv/novels?${nidList.map(v => \"ids=\" + v).join(\"&\")}`\n}\n\nfunction urlUserDetailed(uidList) {\n return `https://api.furrynovel.ink/pixiv/users?${uidList.map(v => \"ids=\" + v).join(\"&\")}`\n}\n\nfunction getAjaxJson(url) {\n return cacheGetAndSet(url, () => {\n // java.log(`url:${url}`)\n return JSON.parse(java.ajax(url))\n })\n}\n\nfunction getWebviewJson(url) {\n return cacheGetAndSet(url, () => {\n java.log(`url:${url}`)\n let html = java.webView(null, url, null)\n // java.log(`返回的html:${html}`)\n return JSON.parse((html.match(new RegExp(\">\\\\[\\\\{.*?}]<\"))[0].replace(\">\", \"\").replace(\"<\", \"\")))\n })\n}\n\nfunction 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\n// 存储seriesID\nvar seriesSet = new Set();\n\n// 将多个长篇小说解析为一本书\nfunction combineNovels(novels) {\n return novels.filter(novel => {\n //单本直接解析为一本书\n //需要判断是否为null\n if (novel.seriesId === undefined || novel.seriesId === null) {\n return true\n }\n\n //集合中没有该系列解析为一本书\n if (!seriesSet.has(novel.seriesId)) {\n seriesSet.add(novel.seriesId)\n return true\n }\n\n return false\n })\n}\n\n// 将小说的封面规则与详情地址替换\nfunction handlerNovels(novels) {\n novels.forEach(novel => {\n novel.detailedUrl = `https://api.furrynovel.ink/pixiv/novel/${novel.id}`\n if (novel.seriesId !== undefined && novel.seriesId !== null) {\n novel.title = novel.seriesTitle\n novel.length = null\n\n java.log(`正在获取系列小说:${novel.seriesId}`)\n let series = getAjaxJson(urlSeries(novel.seriesId))\n // 后端目前没有系列的coverUrl字段\n // novel.coverUrl = `https://api.furrynovel.ink/proxy/pximg?url=${series.imageUrl}`\n novel.coverUrl = `https://api.furrynovel.ink/proxy/pximg?url=${series.novels[0].coverUrl}`\n if (series.caption === \"\") {\n let firstNovels = getAjaxJson(urlNovelsDetailed([series.novels[0].id]))\n if (firstNovels.length > 0) {\n novel.desc = firstNovels[0].desc\n } else {\n novel.desc = \"该小说可能部分章节因为权限或者被删除无法查看\"\n }\n } else {\n novel.desc = series.caption\n }\n\n //如果没有标签 取第一章的tag\n if (series.tags.length === 0) {\n // 系列至少会有一章\n novel.tags = series.novels[0].tags\n } else {\n novel.tags = series.tags\n }\n\n if (novel.tags === undefined) {\n novel.tags = []\n }\n novel.tags.unshift(\"长篇\")\n\n\n } else {\n novel.tags.unshift(\"单本\")\n novel.coverUrl = `https://api.furrynovel.ink/proxy/pximg?url=${novel.coverUrl}`\n }\n\n novel.tags = novel.tags.join(\",\")\n })\n return novels\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 novelList = []\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\n // java.log(`查询的用户Ids:${userIds}`)\n\n let usersInfo = getWebviewJson(urlUserDetailed(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 handlerNovels(combineNovels(novelList))\n }\n}\n\nfunction handlerFollowLatest() {\n return () => {\n let resp = JSON.parse(result)\n return handlerNovels(combineNovels(resp))\n }\n}\n\nfunction handlerFactory() {\n if (baseUrl.indexOf(\"/fav/user\") !== -1) {\n return handlerRecommendUsers()\n }\n\n if (baseUrl.indexOf(\"/pixiv/novels/recent\") !== -1) {\n return handlerFollowLatest()\n }\n\n}\n\n(() => {\n return handlerFactory()()\n})()\n", "bookUrl": "detailedUrl", "coverUrl": "coverUrl", "intro": "desc", "kind": "tags", "lastChapter": "", "name": "title", "wordCount": "length" }, "ruleReview": {}, "ruleSearch": { "author": "userName", "bookList": "@js:\n\nfunction urlSearchUsers(username) {\n return `https://api.furrynovel.ink/pixiv/search/user/${username}`\n}\n\nfunction urlUserDetailed(uidList) {\n return `https://api.furrynovel.ink/pixiv/users?${uidList.map(v => \"ids[]=\" + v).join(\"&\")}`\n}\n\nfunction urlNovelsDetailed(nidList) {\n return `https://api.furrynovel.ink/pixiv/novels?${nidList.map(v => \"ids[]=\" + v).join(\"&\")}`\n}\n\nfunction urlSeries(seriesId) {\n return `https://api.furrynovel.ink/pixiv/series/${seriesId}`\n}\n\nfunction 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\n\nfunction getUser(username, exactMatch) {\n // 修复传入object的bug\n username = String(username)\n let resp = getAjaxJson(urlSearchUsers(username))\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 getAjaxJson(url) {\n return cacheGetAndSet(url, () => {\n return JSON.parse(java.ajax(url))\n })\n}\n\nfunction getWebviewJson(url) {\n return 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// 包含所有小说数据\nfunction getUserDetailedList(uidList) {\n // java.log(`UIDLIST:${JSON.stringify(uidList)}`)\n return getWebviewJson(urlUserDetailed(uidList))\n}\n\nfunction getNovels(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\n // java.log(`NIDURL:${urlNovelsDetailed(list)}`)\n return getWebviewJson(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 combineNovels(novels) {\n return novels.filter(novel => {\n //单本直接解析为一本书\n //需要判断是否为null\n if (novel.seriesId === undefined || novel.seriesId === null) {\n return true\n }\n\n //集合中没有该系列解析为一本书\n if (!seriesSet.has(novel.seriesId)) {\n seriesSet.add(novel.seriesId)\n return true\n }\n\n return false\n })\n}\n\n// 将小说的封面规则与详情地址替换\nfunction formatNovels(novels) {\n novels.forEach(novel => {\n novel.detailedUrl = `https://api.furrynovel.ink/pixiv/novel/${novel.id}`\n if (novel.seriesId !== undefined && novel.seriesId !== null) {\n novel.title = novel.seriesTitle\n novel.length = null\n\n let series = getAjaxJson(urlSeries(novel.seriesId))\n // 后端目前没有系列的coverUrl字段\n // novel.coverUrl = `https://api.furrynovel.ink/proxy/pximg?url=${series.imageUrl}`\n novel.coverUrl = `https://api.furrynovel.ink/proxy/pximg?url=${series.novels[0].coverUrl}`\n if (series.caption === \"\") {\n let firstNovels = getAjaxJson(urlNovelsDetailed([series.novels[0].id]))\n if (firstNovels.length > 0) {\n novel.desc = firstNovels[0].desc\n } else {\n novel.desc = \"该小说可能部分章节因为权限或者被删除无法查看\"\n }\n } else {\n novel.desc = series.caption\n }\n\n //如果没有标签 取第一章的tag\n if (series.tags.length === 0) {\n // 系列至少会有一章\n novel.tags = series.novels[0].tags\n } else {\n novel.tags = series.tags\n }\n\n if (novel.tags === undefined) {\n novel.tags = []\n }\n novel.tags.unshift(\"长篇\")\n\n\n } else {\n if (novel.tags === undefined) {\n novel.tags = []\n }\n novel.tags.unshift(\"单本\")\n novel.coverUrl = `https://api.furrynovel.ink/proxy/pximg?url=${novel.coverUrl}`\n }\n\n novel.tags = novel.tags.join(\",\")\n })\n return novels\n}\n\nfunction findUserNovels(username) {\n let novelList = []\n // 查询用户\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 = getUserDetailedList(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 getNovels(nidList).forEach(novel => {\n novelList.push(novel)\n })\n }\n return novelList\n}\n\n(function (res) {\n res = JSON.parse(res)\n let novels = []\n findUserNovels(java.get(\"key\")).forEach(v => {\n novels.push(v)\n })\n novels = novels.concat(res.novels)\n // 返回空列表中止流程\n if (novels.length === 0) {\n return []\n }\n return formatNovels(combineNovels(novels))\n}(result))", "bookUrl": "detailedUrl", "coverUrl": "coverUrl", "intro": "desc", "kind": "tags", "name": "title", "wordCount": "length" }, "ruleToc": { "chapterList": "@js:\n(function (res) {\n res = JSON.parse(res)\n let cache = function () {\n if (baseUrl.includes(\"/cache\")) {\n return \"/cache\"\n }\n return \"\"\n }()\n\n if (res.novels !== undefined) {\n res.novels.forEach(v => {\n v['url'] = `https://api.furrynovel.ink/pixiv/novel/${v.id}${cache}`\n })\n return res.novels\n }\n return [{\n id: res.id,\n title: res.title\n }]\n})(result)\n", "chapterName": "title", "chapterUrl": "url" }, "searchUrl": "@js:\njava.put(\"page\",page);java.put(\"key\",key);\n`https://api.furrynovel.ink/pixiv/search/novel/${encodeURI(key)}?page=${page}`;", "variableComment": "", "weight": 0 } ]