-
Notifications
You must be signed in to change notification settings - Fork 1
/
linpx.json
66 lines (66 loc) · 23 KB
/
linpx.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
[
{
"bookSourceComment": "由 NURUPO Studio 运营的 Linpx\n\n功能:搜索、发现、添加网址、订阅源添加小说",
"bookSourceGroup": "🔞 Pixiv",
"bookSourceName": "Linpx NURUPO",
"bookSourceType": 0,
"bookSourceUrl": "https://furrynovel.ink",
"customOrder": 1,
"enabled": true,
"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]",
"header": "{\"referer\":\"https://furrynovel.ink/\"}",
"lastUpdateTime": 1717261524984,
"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 u.urlCoverUrl = function (pxImgUrl) {\n return `https://pximg.furrynovel.ink/?url=${pxImgUrl}`\n }\n\n u.urlIllustUrl = function (illustId) {\n // 使用 pixiv.cat 获取插图\n // return `https://pixiv.cat/${illustId}.png` // 已墙不可用\n return `https://pixiv.re/${illustId}.png`\n // return `https://pixiv.nl/${illustId}.png`\n }\n\n util = u\n java.put(\"util\", objStringify(u))\n}\n\npublicFunc()\n\n// 获取请求的user id方便其他ajax请求构造\nlet uid = java.getResponse().headers().get(\"x-userid\")\nif (uid != null) {\n cache.put(\"pixiv:uid\", uid)\n}\njava.getStrResponse(null, null)",
"loginUrl": "",
"respondTime": 180000,
"ruleBookInfo": {
"author": "author",
"coverUrl": "cover_url",
"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\n(function (res) {\n let isHtml = res.startsWith(\"<!DOCTYPE html>\")\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 = util.urlCoverUrl(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:\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(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\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}]`, `<img src=\"${util.urlCoverUrl(res.images[key].origin)}\">`)\n })\n }\n\n // // 获取 [pixivimage:] 的图片链接\n let rex = /\\[pixivimage:(\\d+)]/gm\n let matched = content.match(RegExp(rex))\n if (matched) {\n for (let i in matched) {\n let illustId = matched[i].match(RegExp(\"\\\\d+\"))\n content = content.replace(`${matched[i]}`, `<img src=\"${util.urlIllustUrl(illustId)}\">`)\n }\n }\n\n\n\n // 替换 Pixiv 章节标记符号 [newpage] [chapter:]\n content = content.replace(`[newpage]`, ``)\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 // 章节编号\n // if ((chapter.includes(\"第\"))||(chapter.includes(\"章\"))){\n // // chapter = `${chapter}`\n // } else {\n // chapter = `第${Number(i)+1}节 ${chapter}`\n // }\n content = content.replace(`${matched[i]}`, `${\"<p><p/>\".repeat(3)}${chapter}<p><p/>`)\n }\n }\n\n // 替换 Pixiv 跳转页面标记符号 [[jump:]]\n matched = content.match(/\\[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(/\\[\\[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}`, `<a href=${urlLink}>${urlName}</a>`)\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(/\\[\\[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 // kana为中文,则替换回《书名号》\n var reg = new RegExp(\"[\\\\u4E00-\\\\u9FFF]+\",\"g\");\n if (reg.test(kana)) {\n content = content.replace(`${matchedText}`, `${kanji}《${kana}》`)\n } else{\n // 阅读不支持 <ruby> <rt> 注音\n // content = content.replace(`${matchedText}`, `<ruby>${kanji}<rt>${kana}</rt></ruby>`)\n content = content.replace(`${matchedText}`, `${kanji}(${kana})`)\n }\n }\n }\n\n return content\n})(result)",
"imageStyle": "DEFAULT",
"replaceRegex": "",
"sourceRegex": ""
},
"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 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 novel.coverUrl = util.urlCoverUrl(series.novels[0].coverUrl)\n\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 novel.coverUrl = util.urlCoverUrl(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:\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 urlSearchUsers(username) {\n return `https://api.furrynovel.ink/pixiv/search/user/${username}`\n}\n\nfunction urlSearchNovel(novelname) {\n return `https://api.furrynovel.ink/pixiv/search/novel/${novelname}`\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 novel.coverUrl = util.urlCoverUrl(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 novel.coverUrl = util.urlCoverUrl(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:\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(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
}
]