[ { "bookSourceComment": "Pixiv 书源(更新时间:2024-06-17)\n\n可用功能:✅搜索✅发现✅添加链接✅订阅源\n搜索小说:✅小说名称✅作者名称✅小说标签\n发现小说:✅关注作者✅追更列表✅推荐✅收藏\n添加网址:✅Pixiv小说链接✅Pixiv系列小说链接\n订阅用法:点击订阅源打开小说/系列小说,【刷新】,点击【加入书架】按钮添加小说到书架\n\n书源发布:兽人阅读频道 https://t.me/FurryReading\n项目地址:https://github.com/windyhusky/PixivSource\n规则订阅:https://cdn.jsdelivr.net/gh/windyhusky/PixivSource@main/pixiv.json\nhttps://raw.githubusercontent.com/windyhusky/PixivSource/main/pixiv.json", "bookSourceGroup": "🔞 Pixiv", "bookSourceName": "Pixiv", "bookSourceType": 0, "bookSourceUrl": "https://www.pixiv.net", "bookUrlPattern": "(https?://)?(www.)?pixiv.net/(ajax/|)novel/.*", "customOrder": 0, "enabled": true, "enabledCookieJar": false, "enabledExplore": true, "exploreUrl": "[\n {\n \"title\": \"关注作者\",\n \"url\": \"https://www.pixiv.net/ajax/follow_latest/novel?p={{page}}&mode=all&lang=zh\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\":0.3\n }\n },\n {\n \"title\": \"追更列表\",\n \"url\": \"https://www.pixiv.net/ajax/watch_list/novel?p={{page}}&new=1&lang=zh\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\":0.3\n }\n },\n {\n \"title\": \"推荐小说\",\n \"url\": \"https://www.pixiv.net/ajax/top/novel?mode=all&lang=zh\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\":0.3\n }\n },\n {\n \"title\": \"收藏小说\",\n \"url\": \"https://www.pixiv.net/ajax/user/{{cache.get(\\\"pixiv:uid\\\")}}/novels/bookmarks?tag=&offset={{(page-1)*24}}&limit=24&rest=show&lang=zh\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\":0.3\n }\n }\n]", "header": "{\"referer\":\"https://www.pixiv.net\"}", "lastUpdateTime": 1718385310822, "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.cacheGetAndSet = (key, supplyFunc) => {\n let v = cache.get(key)\n if (v === undefined || v === null) {\n v = JSON.stringify(supplyFunc())\n // 缓存10分钟\n cache.put(key, v, 600)\n }\n return JSON.parse(v)\n }\n u.getAjaxJson = (url) => {\n return util.cacheGetAndSet(url, () => {\n return JSON.parse(java.ajax(url))\n })\n }\n u.getWebviewJson = (url, parseFunc) => {\n return util.cacheGetAndSet(url, () => {\n let html = java.webView(null, url, null)\n return JSON.parse(parseFunc(html))\n })\n }\n u.debugFunc = (func) => {\n if (String(source.getVariable()) === \"debug\") {\n func()\n }\n }\n\n u.urlNovelDetailed = (nid) => {\n return `https://www.pixiv.net/ajax/novel/${nid}`\n }\n u.urlSeries = (seriesId) => {\n return `https://www.pixiv.net/ajax/novel/series/${seriesId}?lang=zh`\n }\n u.urlSeriesNovels = (seriesId, limit, offset) => {\n if (limit > 30) {\n limit = 30\n }\n\n if (limit < 10) {\n limit = 10\n }\n\n return `https://www.pixiv.net/ajax/novel/series_content/${seriesId}?limit=${limit}&last_order=${offset}&order_by=asc&lang=zh`\n }\n u.searchNovel = (novelName, page) =>{\n return `https://www.pixiv.net/ajax/search/novels/${encodeURI(novelName)}?word=${encodeURI(novelName)}&order=date_d&mode=all&p=${page}&s_mode=s_tag&lang=zh`\n }\n // 完全匹配用户名\n u.urlSearchUser = (username) => {\n return `https://www.pixiv.net/search_user.php?s_mode=s_usr&nick=${encodeURI(username)}&nick_mf=1`\n }\n u.urlUserAllWorks = (uid) => {\n return `https://www.pixiv.net/ajax/user/${uid}/profile/all?lang=zh`\n }\n u.urlUserNovels = (uid, nidList) => {\n return `https://www.pixiv.net/ajax/user/${uid}/novels?${nidList.map(v => \"ids[]=\" + v).join(\"&\")}`\n }\n\n u.urlIllustDetailed = (illustId) => {\n return `https://www.pixiv.net/ajax/illust/${illustId}?lang=zh`\n }\n u.urlSeriesIllusts = (seriesId) => {\n return `https://www.pixiv.net/ajax/series/${seriesId}?p=1&lang=zh`\n }\n u.urlCoverUrl = (url) => {\n return `${url},{\"headers\": {\"Referer\":\"https://www.pixiv.net/\"}}`\n }\n\n u.formatNovels = function (novels) {\n novels.forEach(novel => {\n novel.detailedUrl = util.urlNovelDetailed(novel.id)\n const time = this.dateFormat(novel.updateDate);\n novel.tags = novel.tags.join(\",\")\n novel.coverUrl = util.urlCoverUrl(novel.url)\n novel.description += `\\n更新时间:${time}`\n })\n return novels\n }\n\n u.dateFormat = function (str) {\n let addZero = function (num) {\n return num < 10 ? '0' + num : num;\n }\n\n let time = new Date(str);\n let Y = time.getFullYear() + \"年\";\n let M = addZero(time.getMonth() + 1) + \"月\";\n let D = addZero(time.getDate()) + \"日\";\n return Y + M + D;\n }\n\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": "https://accounts.pixiv.net/login", "respondTime": 180000, "ruleBookInfo": { "author": "author", "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\n(function (res) {\n // 获取网址id,请求并解析数据\n let isHtml = result.startsWith(\"\")\n if (isHtml) {\n var novelId = 0\n let isSeries = baseUrl.match(new RegExp(\"pixiv.net/(ajax/|)novel/series\"))\n if (isSeries) {\n let seriesId = baseUrl.match(new RegExp(\"\\\\d+\"))[0]\n java.log(`系列ID:${seriesId}`)\n // 获取系列第一篇小说的 id\n let url = util.urlSeriesNovels(seriesId, 30, 0)\n res = util.cacheGetAndSet(url, () => {\n return JSON.parse(java.ajax(url))\n })\n novelId = res.body.thumbnails.novel[0].id\n java.log(`小说ID:${novelId}`)\n res = util.getAjaxJson(util.urlNovelDetailed(novelId)).body\n // java.log(JSON.stringify(res))\n } else {\n let isNovel = baseUrl.match(new RegExp(\"pixiv.net/(ajax/|)novel\"))\n if (isNovel) {\n novelId = baseUrl.match(new RegExp(\"\\\\d+\"))[0]\n java.log(`小说ID:${novelId}`)\n res = util.getAjaxJson(util.urlNovelDetailed(novelId)).body\n // java.log(JSON.stringify(res))\n } else {\n return []\n }\n }\n } else {\n // 从搜索直接获取 json\n res = JSON.parse(result).body\n if (res.total === 0) {\n return []\n }\n }\n\n let info = {}\n info.author = res.userName\n info.name = res.title\n info.tags = res.userNovels[`${res.id}`].tags\n info.wordCount = res.wordCount\n info.latestChapter = null\n info.desc = res.description\n info.coverUrl = res.coverUrl\n info.catalogUrl = util.urlNovelDetailed(res.id)\n\n if (res.seriesNavData === undefined || res.seriesNavData === null) {\n info.name = res.title\n info.catalog = util.urlNovelDetailed(res.id)\n info.tags.unshift('单本')\n } else {\n info.name = res.seriesNavData.title\n info.catalog = util.urlSeries(res.seriesNavData.id)\n info.tags.unshift('长篇')\n }\n info.tags = info.tags.join(\",\")\n\n return info\n})();", "intro": "desc", "kind": "tags", "name": "name", "tocUrl": "catalogUrl", "wordCount": "wordCount" }, "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(() => {\n let res = JSON.parse(result).body\n let content = res.content\n // 在正文内部添加小说描述\n if (res.seriesNavData !== undefined && res.seriesNavData !== null && res.description !== \"\") {\n content = res.description + \"\\n\" + \"——————————\\n\".repeat(2) + content\n }\n\n // 获取 [uploadedimage:] 的图片链接\n let hasEmbeddedImages = res.textEmbeddedImages !== undefined && res.textEmbeddedImages !== null\n if (hasEmbeddedImages) {\n Object.keys(res.textEmbeddedImages).forEach((key) => {\n // 不再使用 linpx 服务加载图片\n // content = content.replace(`[uploadedimage:${key}]`, ``)\n content = content.replace(`[uploadedimage:${key}]`, ``)\n\n })\n }\n\n // // 获取 [pixivimage:] 的图片链接\n let matched = content.match(RegExp(/\\[pixivimage:(\\d+)]/gm))\n if (matched) {\n for (let i in matched) {\n let illustId = matched[i].match(RegExp(\"\\\\d+\"))\n let res2 = util.getAjaxJson(util.urlIllustDetailed(illustId)).body\n let illustOriginal = res2.urls.original\n content = content.replace(`${matched[i]}`, ``)\n }\n }\n\n\n // 替换 Pixiv 分页标记符号 [newpage]\n matched = content.match(RegExp(/[  ]*\\[newpage][  ]*/gm))\n if (matched) {\n for (let i in matched){\n java.log(matched[i])\n content = content.replace(`${matched[i]}`, `${\"

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

\".repeat(3)}${chapter}

`)\n content = content.replace(`${matched[i]}`, `${chapter}

`)\n }\n }\n\n // 替换 Pixiv 跳转页面标记符号 [[jump:]]\n matched = content.match(RegExp(/\\[jump:(\\d+)]/gm))\n if (matched) {\n for (let i in matched) {\n let page = matched[i].match(/\\d+/)\n content = content.replace(`${matched[i]}`, `\\n\\n跳转至第${page}节`)\n }\n }\n\n // 替换 Pixiv 链接标记符号 [[jumpuri: > ]]\n matched = content.match(RegExp(/\\[\\[jumpuri:(.*?)>(.*?)]]/gm))\n if (matched) {\n for (let i in matched) {\n let matched2 = matched[i].match(/\\[\\[jumpuri:(.*?)>(.*?)]]/m)\n let matchedText = matched2[0]\n let urlName = matched2[1].trim()\n let urlLink = matched2[2].trim()\n // 阅读不支持超链接\n //content = content.replace(`${matchedText}`, `${urlName}`)\n if (urlLink === urlName) {\n content = content.replace(`${matchedText}`, `${urlName}`)\n } else {\n content = content.replace(`${matchedText}`, `${urlName}: ${urlLink}`)\n }\n }\n }\n\n // 替换 Pixiv 注音标记符号 [[rb: > ]]\n matched = content.match(RegExp(/\\[\\[rb:(.*?)>(.*?)]]/gm))\n if (matched) {\n for (let i in matched) {\n let matched2 = matched[i].match(/\\[\\[rb:(.*?)>(.*?)]]/m)\n let matchedText = matched2[0]\n let kanji = matched2[1].trim()\n let kana = matched2[2].trim()\n // kana为中文,则替换回《书名号》\n var reg = new RegExp(\"[\\\\u4E00-\\\\u9FFF]+\",\"g\");\n if (reg.test(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", "imageStyle": "DEFAULT", "nextContentUrl": "", "title": "" }, "ruleExplore": { "author": "userName", "bookList": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\n\n// 存储seriesID 有BUG无法处理翻页\nvar seriesSet = new Set();\n// 将多个长篇小说解析为一本书\nfunction combineNovels(novels) {\n return novels.filter(novel => {\n //单本直接解析为一本书\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\nfunction handNovels(novels) {\n novels.forEach(novel => {\n if (novel.tags === undefined || novel.tags === null) {\n novel.tags = []\n }\n\n if (novel.seriesId === undefined || novel.seriesId === null) {\n novel.tags.unshift(\"单本\")\n } else {\n let userAllWorks = util.getAjaxJson(util.urlUserAllWorks(novel.userId)).body\n for (let series of userAllWorks.novelSeries) {\n if (series.id === novel.seriesId) {\n // let series = util.getAjaxJson(util.urlSeries(novel.seriesId)).body\n novel.textCount = series.publishedTotalCharacterCount\n novel.url = series.cover.urls[\"480mw\"]\n novel.title = series.title\n novel.tags = series.tags\n novel.description = series.caption\n\n try{\n // 发送请求获取第一章 获取标签与简介\n if (novel.tags.length === 0 || novel.description === \"\") {\n let firstNovel = util.getAjaxJson(util.urlNovelDetailed(series.firstNovelId)).body\n if (novel.tags.length === 0) {\n novel.tags = firstNovel.tags.tags.map(item => item.tag)\n }\n\n if (novel.description === \"\") {\n novel.description = firstNovel.description\n }\n }\n novel.tags.unshift(\"长篇\")\n break\n } catch (e) {\n java.log(e)\n }\n }\n }\n }\n })\n return novels\n}\n\nfunction handlerFactory() {\n let cookie = String(java.getCookie(\"https://www.pixiv.net/\", null))\n if (cookie === null || cookie === undefined || cookie === \"\") {\n return handlerNoLogin()\n }\n if (baseUrl.indexOf(\"/bookmark\") !== -1) {\n return handlerBookMarks()\n }\n\n if (baseUrl.indexOf(\"/top\") !== -1) {\n return handlerRecommend()\n }\n\n if (baseUrl.indexOf(\"/following\") !== -1) {\n return handlerFollowing()\n }\n\n if (baseUrl.indexOf(\"/follow_latest\") !== -1) {\n return handlerFollowLatest()\n }\n\n if (baseUrl.indexOf(\"/watch_list\") !== -1) {\n return handlerWatchList()\n }\n}\n\nfunction handlerFollowing() {\n return () => {\n let novelList = []\n JSON.parse(result).body.users\n .filter(user => user.novels.length > 0)\n .map(user => user.novels)\n .forEach(novels => {\n return novels.forEach(novel => {\n novelList.push(novel)\n })\n })\n return util.formatNovels(handNovels(novelList))\n }\n}\n\n// 推荐小说\nfunction handlerRecommend() {\n return () => {\n let res = JSON.parse(result)\n const recommend = res.body.page.recommend\n const novels = res.body.thumbnails.novel\n let nidSet = new Set(recommend.ids)\n // java.log(nidSet.size)\n let list = novels.filter(novel => nidSet.has(String(novel.id)))\n // java.log(`过滤结果:${JSON.stringify(list)}`)\n return util.formatNovels(handNovels(combineNovels(list)))\n }\n}\n\nfunction handlerNoLogin() {\n return () => {\n java.longToast(\"此功能需要在书源登录后才能使用\")\n return []\n }\n}\n\n// 收藏小说\nfunction handlerBookMarks() {\n return () => {\n let resp = JSON.parse(result).body.works\n if (resp === undefined || resp.length === 0) {\n //流程无法本环节中止 只能交给下一流程处理\n return []\n }\n\n return util.formatNovels(handNovels(resp))\n }\n}\n\n//关注作者,近期小说\nfunction handlerFollowLatest() {\n return () => {\n let resp = JSON.parse(result)\n return util.formatNovels(handNovels(combineNovels(resp.body.thumbnails.novel)))\n }\n}\n\n// 追更列表\nfunction handlerWatchList(){\n return () => {\n let resp = JSON.parse(result)\n let novels = []\n let seriesList = resp.body.thumbnails.novelSeries\n for (let i in seriesList) {\n let novelId = seriesList[i].latestEpisodeId // 使用最后一篇小说,重新请求并合并小说\n novels.push(util.getAjaxJson(util.urlNovelDetailed(novelId)).body.userNovels[`${novelId}`])\n }\n return util.formatNovels(handNovels(combineNovels(novels)))\n }\n}\n\n(() => {\n return handlerFactory()()\n})()", "bookUrl": "detailedUrl", "coverUrl": "coverUrl", "intro": "description", "kind": "tags", "lastChapter": "latestChapter", "name": "title", "wordCount": "textCount" }, "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\n//不可使用 base 内的 util.getAjaxJson() 替换\nfunction getAjaxJson(url) {\n return util.cacheGetAndSet(url, () => {\n return JSON.parse(java.ajax(url))\n })\n}\n\nfunction getWebviewJson(url, parseFunc) {\n return util.cacheGetAndSet(url, () => {\n let html = java.webView(null, url, null)\n return JSON.parse(parseFunc(html))\n })\n}\n\nvar first = true;\n// 存储seriesID·\nvar seriesSet = {\n keywords: \"Pixiv: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 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//处理novels列表\n//查询作者\nfunction handNovels(novels) {\n novels.forEach(novel => {\n if (novel.tags === undefined || novel.tags === null) {\n novel.tags = []\n }\n if (novel.seriesId === undefined || novel.seriesId === null) {\n novel.tags.unshift(\"单本\")\n } else {\n let userAllWorks = getAjaxJson(util.urlUserAllWorks(novel.userId)).body\n for (let series of userAllWorks.novelSeries) {\n if (series.id === novel.seriesId) {\n // let series = getAjaxJson(util.urlSeries(novel.seriesId)).body\n novel.textCount = series.publishedTotalCharacterCount\n novel.url = series.cover.urls[\"480mw\"]\n novel.title = series.title\n novel.tags = series.tags\n novel.description = series.caption\n\n // 发送请求获取第一章 获取标签与简介\n if (novel.tags.length === 0 || novel.description === \"\") {\n let firstNovel = getAjaxJson(util.urlNovelDetailed(series.firstNovelId)).body\n if (novel.tags.length === 0) {\n novel.tags = firstNovel.tags.tags.map(item => item.tag)\n }\n\n if (novel.description === \"\") {\n novel.description = firstNovel.description\n }\n }\n\n novel.tags.unshift(\"长篇\")\n break\n }\n }\n }\n })\n util.debugFunc(() => {\n java.log(`处理小说完成`)\n })\n return novels\n}\n\nfunction isLogin() {\n let cookie = String(java.getCookie(\"https://www.pixiv.net/\", null))\n return typeof cookie === \"string\" && cookie !== \"\"\n}\n\nfunction getUserNovels(username) {\n if (!isLogin()) {\n return []\n }\n\n let html = java.ajax(util.urlSearchUser(username))\n // java.log(html)\n // 仅匹配有投稿作品的用户\n let match = html.match(new RegExp(\"/users/\\\\d+/novels\"))\n if (match === null || match.length === 0) {\n return []\n }\n\n let regNumber = new RegExp(\"\\\\d+\")\n let uidList = match.map(v => {\n return v.match(regNumber)[0]\n })\n\n // 仅限3个作者\n if (uidList.length >= 3) {\n uidList.length = 3\n }\n\n let novels = []\n let page = Number(java.get(\"page\"))\n\n uidList.forEach(id => {\n let r = getAjaxJson(util.urlUserAllWorks(id))\n let novelsId = Object.keys(r.body.novels).reverse().slice((page - 1) * 20, page * 20)\n let url = util.urlUserNovels(id, novelsId)\n util.debugFunc(() => {\n java.log(`发送获取作者小说的Ajax请求:${url}`)\n })\n let userNovels = getWebviewJson(url, html => {\n return (html.match(new RegExp(\">\\\\{.*?}<\"))[0].replace(\">\", \"\").replace(\"<\", \"\"))\n }).body\n // 获取对应的小说 该序列是按照id排序\n // 反转以按照更新时间排序\n novels = novels.concat(Object.values(userNovels).reverse())\n })\n\n\n util.debugFunc(() => {\n java.log(`获取用户搜索小说结束`)\n })\n return novels\n}\n\n(() => {\n //作者 TAG 书名都要支持\n let resp = JSON.parse(result);\n let novelsList = getUserNovels(String(java.get(\"key\")))\n novelsList = novelsList.concat(resp.body.novel.data)\n return util.formatNovels(handNovels(combineNovels(novelsList)))\n})();", "bookUrl": "detailedUrl", "coverUrl": "coverUrl", "intro": "description", "kind": "tags", "lastChapter": "latestChapter", "name": "title", "wordCount": "textCount" }, "ruleToc": { "chapterList": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction seriesHandler(res) {\n const limit = 30\n let returnList = [];\n let seriesID = res.seriesNavData.seriesId\n let allChaptersCount = (() => {\n let result = util.cacheGetAndSet(util.urlSeries(seriesID), () => {\n return util.getAjaxJson(util.urlSeries(seriesID))\n }).body.total\n util.debugFunc(() => {\n java.log(`本目录一共有:${result} 章节`);\n })\n return result;\n })();\n\n //发送请求获得相应数量的目录列表\n function sendAjaxForGetChapters(lastIndex) {\n let url = util.urlSeriesNovels(seriesID, limit, lastIndex)\n res = util.cacheGetAndSet(url, () => {\n return util.getAjaxJson(url)\n })\n res = res.body.page.seriesContents\n res.forEach(v => {\n v.chapterUrl = util.urlNovelDetailed(v.id)\n })\n return res;\n }\n\n //逻辑控制者 也就是使用上面定义的两个函数来做对应功能\n //要爬取的总次数\n let max = (allChaptersCount / limit) + 1\n for (let i = 0; i < max; i++) {\n //java.log(\"i的值:\"+i)\n let list = sendAjaxForGetChapters(i * limit);\n //取出每个值\n returnList = returnList.concat(list)\n }\n return returnList\n}\n\nfunction aloneHandler() {\n return [{title: book.name, chapterUrl: baseUrl}]\n}\n\n(() => {\n let res = JSON.parse(result).body\n if (res.seriesNavData === null || res.seriesNavData === undefined) {\n return aloneHandler()\n }\n return seriesHandler(res)\n})()", "chapterName": "title", "chapterUrl": "chapterUrl" }, "searchUrl": "@js:\njava.put(\"page\",page);java.put(\"key\",key);\n`https://www.pixiv.net/ajax/search/novels/${encodeURI(key)}?word=${encodeURI(key)}&order=date_d&mode=all&p=${page}&s_mode=s_tag&lang=zh`;", "variableComment": "", "weight": 0 }, { "bookSourceComment": "Pixiv 系列小说书源(更新时间:2024-06-20)\n\n可用功能:✅搜索系列小说✅添加链接✅订阅源\n搜索小说:✅系列小说名称✅作者名称✅小说标签\n添加网址:✅Pixiv小说链接✅Pixiv系列小说链接\n订阅用法:点击订阅源打开小说/系列小说,【刷新】,点击【加入书架】按钮添加小说到书架\n\n书源发布:兽人阅读频道 https://t.me/FurryReading\n项目地址:https://github.com/windyhusky/PixivSource\n规则订阅:https://cdn.jsdelivr.net/gh/windyhusky/PixivSource@main/pixiv.json\nhttps://raw.githubusercontent.com/windyhusky/PixivSource/main/pixiv.json", "bookSourceGroup": "🔞 Pixiv", "bookSourceName": "Pixiv 系列小说", "bookSourceType": 0, "bookSourceUrl": "https://www.pixiv.net/novel", "bookUrlPattern": "(https?://)?(www.)?pixiv.net/(ajax/|)novel/.*", "customOrder": 1, "enabled": true, "enabledCookieJar": true, "enabledExplore": false, "header": "{\"referer\":\"https://www.pixiv.net\"}", "lastUpdateTime": 1718612243949, "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.cacheGetAndSet = (key, supplyFunc) => {\n let v = cache.get(key)\n if (v === undefined || v === null) {\n v = JSON.stringify(supplyFunc())\n // 缓存10分钟\n cache.put(key, v, 600)\n }\n return JSON.parse(v)\n }\n u.getAjaxJson = (url) => {\n return util.cacheGetAndSet(url, () => {\n return JSON.parse(java.ajax(url))\n })\n }\n u.getWebviewJson = (url, parseFunc) => {\n return util.cacheGetAndSet(url, () => {\n let html = java.webView(null, url, null)\n return JSON.parse(parseFunc(html))\n })\n }\n u.debugFunc = (func) => {\n if (String(source.getVariable()) === \"debug\") {\n func()\n }\n }\n\n u.urlNovelDetailed = (nid) => {\n return `https://www.pixiv.net/ajax/novel/${nid}`\n }\n u.urlSeries = (seriesId) => {\n return `https://www.pixiv.net/ajax/novel/series/${seriesId}?lang=zh`\n }\n u.urlSeriesNovels = (seriesId, limit, offset) => {\n if (limit > 30) {\n limit = 30\n }\n\n if (limit < 10) {\n limit = 10\n }\n\n return `https://www.pixiv.net/ajax/novel/series_content/${seriesId}?limit=${limit}&last_order=${offset}&order_by=asc&lang=zh`\n }\n u.searchNovel = (novelName, page) =>{\n return `https://www.pixiv.net/ajax/search/novels/${encodeURI(novelName)}?word=${encodeURI(novelName)}&order=date_d&mode=all&p=${page}&s_mode=s_tag&lang=zh`\n }\n // 完全匹配用户名\n u.urlSearchUser = (username) => {\n return `https://www.pixiv.net/search_user.php?s_mode=s_usr&nick=${encodeURI(username)}&nick_mf=1`\n }\n u.urlUserAllWorks = (uid) => {\n return `https://www.pixiv.net/ajax/user/${uid}/profile/all?lang=zh`\n }\n u.urlUserNovels = (uid, nidList) => {\n return `https://www.pixiv.net/ajax/user/${uid}/novels?${nidList.map(v => \"ids[]=\" + v).join(\"&\")}`\n }\n\n u.urlIllustDetailed = (illustId) => {\n return `https://www.pixiv.net/ajax/illust/${illustId}?lang=zh`\n }\n u.urlSeriesIllusts = (seriesId) => {\n return `https://www.pixiv.net/ajax/series/${seriesId}?p=1&lang=zh`\n }\n u.urlCoverUrl = (url) => {\n return `${url},{\"headers\": {\"Referer\":\"https://www.pixiv.net/\"}}`\n }\n\n u.formatNovels = function (novels) {\n novels.forEach(novel => {\n novel.detailedUrl = util.urlNovelDetailed(novel.id)\n novel.name = novel.title\n novel.author = novel.userName\n novel.tags = novel.tags.join(\",\")\n novel.textCount = novel.wordCount\n novel.description = novel.caption\n // const time = this.dateFormat(novel.updateDate);\n const time = this.dateFormat(novel.updateDateTime);\n novel.description += `\\n更新时间:${time}`\n novel.coverUrl = util.urlCoverUrl(novel.url)\n })\n return novels\n }\n\n u.dateFormat = function (str) {\n let addZero = function (num) {\n return num < 10 ? '0' + num : num;\n }\n\n let time = new Date(str);\n let Y = time.getFullYear() + \"年\";\n let M = addZero(time.getMonth() + 1) + \"月\";\n let D = addZero(time.getDate()) + \"日\";\n return Y + M + D;\n }\n\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": "https://accounts.pixiv.net/login", "respondTime": 180000, "ruleBookInfo": { "author": "author", "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\n(function (res) {\n // 获取网址id,请求并解析数据\n let isHtml = result.startsWith(\"\")\n if (isHtml) {\n var novelId = 0\n let isSeries = baseUrl.match(new RegExp(\"pixiv.net/(ajax/|)novel/series\"))\n if (isSeries) {\n let seriesId = baseUrl.match(new RegExp(\"\\\\d+\"))[0]\n java.log(`系列ID:${seriesId}`)\n // 获取系列第一篇小说的 id\n let url = util.urlSeriesNovels(seriesId, 30, 0)\n res = util.cacheGetAndSet(url, () => {\n return JSON.parse(java.ajax(url))\n })\n novelId = res.body.thumbnails.novel[0].id\n java.log(`首篇小说ID:${novelId}`)\n res = util.getAjaxJson(util.urlNovelDetailed(novelId)).body\n // java.log(JSON.stringify(res))\n } else {\n let isNovel = baseUrl.match(new RegExp(\"pixiv.net/(ajax/|)novel\"))\n if (isNovel) {\n novelId = baseUrl.match(new RegExp(\"\\\\d+\"))[0]\n java.log(`匹配小说ID:${novelId}`)\n res = util.getAjaxJson(util.urlNovelDetailed(novelId)).body\n // java.log(JSON.stringify(res))\n } else {\n return []\n }\n }\n } else {\n // 从搜索直接获取 json\n res = JSON.parse(result).body\n if (res.total === 0) {\n return []\n }\n }\n // java.log(JSON.stringify(res))\n let info = {}\n info.author = res.userName\n info.name = res.title\n info.tags = res.userNovels[`${res.id}`].tags\n info.wordCount = res.wordCount\n info.latestChapter = null\n info.desc = res.description\n info.coverUrl = res.coverUrl\n info.catalogUrl = util.urlNovelDetailed(res.id)\n\n if (res.seriesNavData === undefined || res.seriesNavData === null) {\n info.name = res.title\n info.catalog = util.urlNovelDetailed(res.id)\n info.tags.unshift('单本')\n } else {\n info.name = res.seriesNavData.title\n info.catalog = util.urlSeries(res.seriesNavData.id)\n info.tags.unshift('长篇')\n }\n info.tags = info.tags.join(\",\")\n\n return info\n})();", "intro": "desc", "kind": "tags", "name": "name", "tocUrl": "catalogUrl", "wordCount": "wordCount" }, "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(() => {\n let res = JSON.parse(result).body\n let content = res.content\n // 在正文内部添加小说描述\n if (res.seriesNavData !== undefined && res.seriesNavData !== null && res.description !== \"\") {\n content = res.description + \"\\n\" + \"——————————\\n\".repeat(2) + content\n }\n\n // 获取 [uploadedimage:] 的图片链接\n let hasEmbeddedImages = res.textEmbeddedImages !== undefined && res.textEmbeddedImages !== null\n if (hasEmbeddedImages) {\n Object.keys(res.textEmbeddedImages).forEach((key) => {\n // 不再使用 linpx 服务加载图片\n // content = content.replace(`[uploadedimage:${key}]`, ``)\n content = content.replace(`[uploadedimage:${key}]`, ``)\n\n })\n }\n\n // // 获取 [pixivimage:] 的图片链接\n let matched = content.match(RegExp(/\\[pixivimage:(\\d+)]/gm))\n if (matched) {\n for (let i in matched) {\n let illustId = matched[i].match(RegExp(\"\\\\d+\"))\n let res2 = util.getAjaxJson(util.urlIllustDetailed(illustId)).body\n let illustOriginal = res2.urls.original\n content = content.replace(`${matched[i]}`, ``)\n }\n }\n\n\n // 替换 Pixiv 分页标记符号 [newpage]\n matched = content.match(RegExp(/[  ]*\\[newpage][  ]*/gm))\n if (matched) {\n for (let i in matched){\n java.log(matched[i])\n content = content.replace(`${matched[i]}`, `${\"

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

\".repeat(3)}${chapter}

`)\n content = content.replace(`${matched[i]}`, `${chapter}

`)\n }\n }\n\n // 替换 Pixiv 跳转页面标记符号 [[jump:]]\n matched = content.match(RegExp(/\\[jump:(\\d+)]/gm))\n if (matched) {\n for (let i in matched) {\n let page = matched[i].match(/\\d+/)\n content = content.replace(`${matched[i]}`, `\\n\\n跳转至第${page}节`)\n }\n }\n\n // 替换 Pixiv 链接标记符号 [[jumpuri: > ]]\n matched = content.match(RegExp(/\\[\\[jumpuri:(.*?)>(.*?)]]/gm))\n if (matched) {\n for (let i in matched) {\n let matched2 = matched[i].match(/\\[\\[jumpuri:(.*?)>(.*?)]]/m)\n let matchedText = matched2[0]\n let urlName = matched2[1].trim()\n let urlLink = matched2[2].trim()\n // 阅读不支持超链接\n //content = content.replace(`${matchedText}`, `${urlName}`)\n if (urlLink === urlName) {\n content = content.replace(`${matchedText}`, `${urlName}`)\n } else {\n content = content.replace(`${matchedText}`, `${urlName}: ${urlLink}`)\n }\n }\n }\n\n // 替换 Pixiv 注音标记符号 [[rb: > ]]\n matched = content.match(RegExp(/\\[\\[rb:(.*?)>(.*?)]]/gm))\n if (matched) {\n for (let i in matched) {\n let matched2 = matched[i].match(/\\[\\[rb:(.*?)>(.*?)]]/m)\n let matchedText = matched2[0]\n let kanji = matched2[1].trim()\n let kana = matched2[2].trim()\n // kana为中文,则替换回《书名号》\n var reg = new RegExp(\"[\\\\u4E00-\\\\u9FFF]+\",\"g\");\n if (reg.test(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", "imageStyle": "DEFAULT" }, "ruleExplore": {}, "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//不可使用 base 内的 util.getAjaxJson() 替换\nfunction getAjaxJson(url) {\n return util.cacheGetAndSet(url, () => {\n return JSON.parse(java.ajax(url))\n })\n}\n\nfunction getWebviewJson(url, parseFunc) {\n return util.cacheGetAndSet(url, () => {\n let html = java.webView(null, url, null)\n return JSON.parse(parseFunc(html))\n })\n}\n\nvar first = true;\n// 存储seriesID·\nvar seriesSet = {\n keywords: \"Pixiv: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 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//处理novels列表\n//查询作者\nfunction handNovels(novels) {\n novels.forEach(novel => {\n if (novel.tags === undefined || novel.tags === null) {\n novel.tags = []\n }\n // 最大化使用搜索活得的数据\n novel.textCount = novel.publishedWordCount\n novel.url = novel.cover.urls[\"480mw\"]\n novel.description = novel.caption\n // novel.tags = novel.tags\n\n if (novel.isOneshot !== false) {\n // novel.isOneshot === true 单篇小说\n novel.seriesId = undefined\n novel.id = novel.novelId\n novel.name = novel.latestChapter = novel.title\n novel.tags.unshift(\"单本\")\n\n } else {\n // novel.isOneshot === false 则为 series 系列小说\n novel.seriesId = novel.id\n // novel.id = novel.latestEpisodeId //\n novel.name = novel.title\n novel.textCount = novel.publishedWordCount\n\n let userAllWorks = getAjaxJson(util.urlUserAllWorks(novel.userId)).body\n for (let series of userAllWorks.novelSeries) {\n if (series.id === novel.seriesId) {\n let series = getAjaxJson(util.urlSeries(novel.seriesId)).body\n novel.id = series.firstNovelId\n // novel.textCount = series.publishedTotalCharacterCount\n // novel.url = series.cover.urls[\"480mw\"]\n // novel.title = series.title\n // novel.tags = series.tags\n // novel.description = series.caption\n\n // 发送请求获取第一章 获取标签与简介\n if (novel.tags.length === 0 || novel.description === \"\") {\n let firstNovel = getAjaxJson(util.urlNovelDetailed(series.firstNovelId)).body\n if (novel.tags.length === 0) {\n novel.tags = firstNovel.tags.tags.map(item => item.tag)\n }\n\n if (novel.description === \"\") {\n novel.description = firstNovel.description\n }\n }\n\n novel.tags.unshift(\"长篇\")\n break\n }\n }\n }\n })\n util.debugFunc(() => {\n java.log(`处理小说完成`)\n })\n return novels\n}\n\nfunction isLogin() {\n let cookie = String(java.getCookie(\"https://www.pixiv.net/\", null))\n return typeof cookie === \"string\" && cookie !== \"\"\n}\n\nfunction getUserNovels(username) {\n if (!isLogin()) {\n return []\n }\n\n let html = java.ajax(util.urlSearchUser(username))\n // java.log(html)\n // 仅匹配有投稿作品的用户\n let match = html.match(new RegExp(\"/users/\\\\d+/novels\"))\n if (match === null || match.length === 0) {\n return []\n }\n\n let regNumber = new RegExp(\"\\\\d+\")\n let uidList = match.map(v => {\n return v.match(regNumber)[0]\n })\n\n // 仅限3个作者\n if (uidList.length >= 3) {\n uidList.length = 3\n }\n\n let novels = []\n let page = Number(java.get(\"page\"))\n\n uidList.forEach(id => {\n let r = getAjaxJson(util.urlUserAllWorks(id))\n let novelsId = Object.keys(r.body.novels).reverse().slice((page - 1) * 20, page * 20)\n let url = util.urlUserNovels(id, novelsId)\n util.debugFunc(() => {\n java.log(`发送获取作者小说的Ajax请求:${url}`)\n })\n let userNovels = getWebviewJson(url, html => {\n return (html.match(new RegExp(\">\\\\{.*?}<\"))[0].replace(\">\", \"\").replace(\"<\", \"\"))\n }).body\n // 获取对应的小说 该序列是按照id排序\n // 反转以按照更新时间排序\n novels = novels.concat(Object.values(userNovels).reverse())\n })\n\n\n util.debugFunc(() => {\n java.log(`获取用户搜索小说结束`)\n })\n return novels\n}\n\n(() => {\n //作者 TAG 书名都要支持\n let resp = JSON.parse(result);\n let novelsList = getUserNovels(String(java.get(\"key\")))\n novelsList = novelsList.concat(resp.body.novel.data)\n // return util.formatNovels(handNovels(combineNovels(novelsList)))\n return util.formatNovels(handNovels(novelsList))\n})();", "bookUrl": "detailedUrl", "coverUrl": "coverUrl", "intro": "description", "kind": "tags", "lastChapter": "latestChapter", "name": "title", "wordCount": "textCount" }, "ruleToc": { "chapterList": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction seriesHandler(res) {\n const limit = 30\n let returnList = [];\n let seriesID = res.seriesNavData.seriesId\n let allChaptersCount = (() => {\n let result = util.cacheGetAndSet(util.urlSeries(seriesID), () => {\n return util.getAjaxJson(util.urlSeries(seriesID))\n }).body.total\n util.debugFunc(() => {\n java.log(`本目录一共有:${result} 章节`);\n })\n return result;\n })();\n\n //发送请求获得相应数量的目录列表\n function sendAjaxForGetChapters(lastIndex) {\n let url = util.urlSeriesNovels(seriesID, limit, lastIndex)\n res = util.cacheGetAndSet(url, () => {\n return util.getAjaxJson(url)\n })\n res = res.body.page.seriesContents\n res.forEach(v => {\n v.chapterUrl = util.urlNovelDetailed(v.id)\n })\n return res;\n }\n\n //逻辑控制者 也就是使用上面定义的两个函数来做对应功能\n //要爬取的总次数\n let max = (allChaptersCount / limit) + 1\n for (let i = 0; i < max; i++) {\n //java.log(\"i的值:\"+i)\n let list = sendAjaxForGetChapters(i * limit);\n //取出每个值\n returnList = returnList.concat(list)\n }\n return returnList\n}\n\nfunction aloneHandler() {\n return [{title: book.name, chapterUrl: baseUrl}]\n}\n\n(() => {\n let res = JSON.parse(result).body\n if (res.seriesNavData === null || res.seriesNavData === undefined) {\n return aloneHandler()\n }\n return seriesHandler(res)\n})()", "chapterName": "title", "chapterUrl": "chapterUrl" }, "searchUrl": "@js:\njava.put(\"page\",page);java.put(\"key\",key);\n`https://www.pixiv.net/ajax/search/novels/${encodeURI(key)}?word=${encodeURI(key)}&order=date_d&mode=all&p=${page}&s_mode=s_tag&gs=1&lang=zh`;", "weight": 0 } ]