[ { "bookSourceComment": "🅿️ Pixiv 小说(更新📆:2026/09/21)\n\n书源版本:284\n使用说明:📌阅读 Sigma 3.26.0216 版本可用\n可用功能:✅搜索✅发现✅添加网址✅订阅源\n搜索小说:✅单篇✅系列✅标签✅作者\n发现小说:✅关注✅追更✅推荐✅发现\n发现小说:✅收藏✅书签✅首页✅排行\n添加网址:✅小说✅系列✅作者\n订阅用法:点击订阅源打开小说/系列小说,【刷新】,点击【加入书架】按钮,添加到书架\n\n书源发布:Pixiv 书源频道 https://t.me/PixivSource\n项目地址:https://github.com/DowneyRem/PixivSource\n使用教程:https://pixivsource.pages.dev/Pixiv\n\n⚙️ 书源设置:\n1️⃣ 书架 - 阅读界面 - Pixiv 小说 - 登录\n2️⃣ 我的 - 书源管理 - Pixiv 小说 - 登录\n点击【👀 书源设置】【👀 发现设置】显示相关设置按钮\n\n▶️ 互动功能、自定义功能:\n1️⃣ 小说书架 - 阅读界面 - Pixiv 小说 - 登录\n\n💬 发送评论:\n1️⃣ 文本框内 输入内容,点击【✅ 发送评论】\n\n🚫 屏蔽标签/描述(本地):\n1️⃣ 点击【👀 查看屏蔽】,切换至屏蔽列表\n2️⃣ 文本框内 输入内容,点击【🚫 加入屏蔽】\n\n📌 喜欢标签(本地):\n1️⃣ 文本框内 输入标签,点击【📌 喜欢标签】\n\n❤️ 关注 他人收藏(本地):\n1️⃣ 文本框内 输入作者ID,点击【❤️ 他人收藏】", "bookSourceGroup": "🔞 Pixiv", "bookSourceName": "🅿️ Pixiv 小说", "bookSourceType": 0, "bookSourceUrl": "https://www.pixiv.net/novel", "bookUrlPattern": "(https?://)?(www\\.)?pixiv\\.net(/ajax)?/(novel/(show\\.php\\?id=|series/)?|users?/)\\d+.*", "concurrentRate": "30/5000", "customButton": true, "customOrder": 0, "enabled": true, "enabledCookieJar": true, "enabledExplore": true, "eventListener": true, "exploreUrl": "@js:\nlet settings = getFromCacheObject(\"pixivSettings\")\nif (!settings) settings = setDefaultSettings()\nlet li =[]\n\nlet adultDefault = [\n {\"⭐️ 关注\": \"https://www.pixiv.net/ajax/follow_latest/novel?p={{page}}&mode=r18&lang=zh\"},\n {\"📃 追更\": \"https://www.pixiv.net/ajax/watch_list/novel?p={{page}}&new=1&lang=zh\"},\n {\"💯 推荐\": \"https://www.pixiv.net/ajax/top/novel?mode=r18&lang=zh\"},\n {\"🔍 发现\": \"https://www.pixiv.net/ajax/novel/discovery?mode=r18&lang=zh\"},\n {\"❤️ 收藏\": \"https://www.pixiv.net/ajax/user/{{cache.get(\\\"pixivUid\\\")}}/novels/bookmarks?tag=&offset={{(page-1)*24}}&limit=24&rest=show&lang=zh\"},\n {\"㊙️ 收藏\": \"https://www.pixiv.net/ajax/user/{{cache.get(\\\"pixivUid\\\")}}/novels/bookmarks?tag=&offset={{(page-1)*24}}&limit=24&rest=hide&lang=zh\"},\n {\"🏷️ 书签\": \"https://www.pixiv.net/novel/marker_all.php?p={{page}}\"},\n {\"🏠 首页\": \"https://www.pixiv.net/ajax/street/for_you?lang=zh\"},\n]\n\nlet normalDefault = [\n {\"✅ 常规 小说 推荐 ✅\": \"\"},\n {\"⭐️ 关注\": \"https://www.pixiv.net/ajax/follow_latest/novel?p={{page}}&mode=all&lang=zh\"},\n {\"💯 推荐\": \"https://www.pixiv.net/ajax/top/novel?mode=all&lang=zh\"},\n {\"🔍 发现\": \"https://www.pixiv.net/ajax/novel/discovery?mode=safe&lang=zh\"},\n {\"🆙 更新\": \"https://cdn.jsdelivr.net/gh/DowneyRem/PixivSource@main/pixiv.json\"},\n]\n\nlet adultNew = [\n {\"🆕 最新 企划 约稿 💰\": \"\"},\n {\"🆕 最新\": \"https://www.pixiv.net/ajax/novel/new?lastId=0&limit=20&r18=true&lang=zh\"},\n {\"📑 企划\": \"https://www.pixiv.net/ajax/user_event/portal/novels?mode=r18&p={{page}}&lang=zh\"},\n {\"💰 约稿\": \"https://www.pixiv.net/ajax/commission/page/request/complete/novels?mode=r18&p={{page}}&lang=zh\"},\n {\"🔍 发现\": \"https://www.pixiv.net/ajax/novel/discovery?mode=all&lang=zh\"},\n]\n\nlet generalNew = [\n {\"✅ 最新 企划 约稿 ✅\": \"\"},\n {\"最新\": \"https://www.pixiv.net/ajax/novel/new?lastId=0&limit=20&r18=false&lang=zh\"},\n {\"企划\": \"https://www.pixiv.net/ajax/user_event/portal/novels?mode=all&p={{page}}&lang=zh\"},\n {\"约稿\": \"https://www.pixiv.net/ajax/commission/page/request/complete/novels?mode=all&p={{page}}&lang=zh\"},\n {\"编辑\": \"https://www.pixiv.net/ajax/novel/editors_picks?limit=30&lang=zh\"},\n]\n\nlet adultRank = [\n {\"👑 排行榜单 👑\": \"\"},\n {\"今日\": \"https://www.pixiv.net/ajax/ranking/novel?mode=daily_r18&content=novel&p={{page}}\"},\n {\"本周\": \"https://www.pixiv.net/ajax/ranking/novel?mode=weekly_r18&content=novel&p={{page}}\"},\n {\"R18G\": \"https://www.pixiv.net/ajax/ranking/novel?mode=r18g&content=novel&p={{page}}\"},\n {\"男性\": \"https://www.pixiv.net/ajax/ranking/novel?mode=male_r18&content=novel&p={{page}}\"},\n {\"女性\": \"https://www.pixiv.net/ajax/ranking/novel?mode=female_r18&content=novel&p={{page}}\"}\n]\n\nlet generalRank = [\n {\"🏆 排行榜单 🏆\": \"\"},\n {\"今日\": \"https://www.pixiv.net/ajax/ranking/novel?mode=daily&content=novel&p={{page}}\"},\n {\"本周\": \"https://www.pixiv.net/ajax/ranking/novel?mode=weekly&content=novel&p={{page}}\"},\n {\"本月\": \"https://www.pixiv.net/ajax/ranking/novel?mode=monthly&content=novel&p={{page}}\"},\n {\"男性\": \"https://www.pixiv.net/ajax/ranking/novel?mode=male&content=novel&p={{page}}\"},\n {\"女性\": \"https://www.pixiv.net/ajax/ranking/novel?mode=female&content=novel&p={{page}}\"},\n {\"新人\": \"https://www.pixiv.net/ajax/ranking/novel?mode=rookie&content=novel&p={{page}}\"},\n {\"原创\": \"https://www.pixiv.net/ajax/ranking/novel?mode=weekly_original&content=novel&p={{page}}\"},\n {\"AI生成\": \"https://www.pixiv.net/ajax/ranking/novel?mode=weekly_ai&content=novel&p={{page}}\"}\n]\n\nlet adultGenre = [\n {\"🔥 原创热门 🔥\": \"\"},\n {\"男性\": \"https://www.pixiv.net/ajax/genre/novel/male?mode=r18&lang=zh\"},\n {\"女性\": \"https://www.pixiv.net/ajax/genre/novel/female?mode=r18&lang=zh\"},\n {\"恋爱\": \"https://www.pixiv.net/ajax/genre/novel/romance?mode=r18&lang=zh\"},\n {\"异世界奇幻\": \"https://www.pixiv.net/ajax/genre/novel/isekai_fantasy?mode=r18&lang=zh\"},\n {\"现代奇幻\": \"https://www.pixiv.net/ajax/genre/novel/contemporary_fantasy?mode=r18&lang=zh\"},\n {\"悬疑\": \"https://www.pixiv.net/ajax/genre/novel/mystery?mode=r18&lang=zh\"},\n {\"恐怖\": \"https://www.pixiv.net/ajax/genre/novel/horror?mode=r18&lang=zh\"},\n {\"科幻\": \"https://www.pixiv.net/ajax/genre/novel/sci-fi?mode=r18&lang=zh\"},\n {\"文学\": \"https://www.pixiv.net/ajax/genre/novel/literature?mode=r18&lang=zh\"},\n {\"情感\": \"https://www.pixiv.net/ajax/genre/novel/drama?mode=r18&lang=zh\"},\n {\"历史\": \"https://www.pixiv.net/ajax/genre/novel/historical_pieces?mode=r18&lang=zh\"},\n {\"耽美\": \"https://www.pixiv.net/ajax/genre/novel/bl?mode=r18&lang=zh\"},\n {\"百合\": \"https://www.pixiv.net/ajax/genre/novel/yuri?mode=r18&lang=zh\"},\n {\"散文·诗歌\": \"https://www.pixiv.net/ajax/genre/novel/poetry?mode=r18&lang=zh\"},\n {\"随笔·纪实\": \"https://www.pixiv.net/ajax/genre/novel/non-fiction?mode=r18&lang=zh\"},\n {\"剧本\": \"https://www.pixiv.net/ajax/genre/novel/screenplays?mode=r18&lang=zh\"},\n {\"评论\": \"https://www.pixiv.net/ajax/genre/novel/reviews?mode=r18&lang=zh\"},\n {\"其他\": \"https://www.pixiv.net/ajax/genre/novel/other?mode=r18&lang=zh\"}\n]\n\nlet generalGenre = [\n {\"❤️‍🔥 原创热门 ❤️‍🔥\": \"\"},\n {\"综合\": \"https://www.pixiv.net/ajax/genre/novel/all?mode=safe&lang=zh\"},\n {\"恋爱\": \"https://www.pixiv.net/ajax/genre/novel/romance?mode=safe&lang=zh\"},\n {\"异世界奇幻\": \"https://www.pixiv.net/ajax/genre/novel/isekai_fantasy?mode=safe&lang=zh\"},\n {\"现代奇幻\": \"https://www.pixiv.net/ajax/genre/novel/contemporary_fantasy?mode=safe&lang=zh\"},\n {\"悬疑\": \"https://www.pixiv.net/ajax/genre/novel/mystery?mode=safe&lang=zh\"},\n {\"恐怖\": \"https://www.pixiv.net/ajax/genre/novel/horror?mode=safe&lang=zh\"},\n {\"科幻\": \"https://www.pixiv.net/ajax/genre/novel/sci-fi?mode=safe&lang=zh\"},\n {\"文学\": \"https://www.pixiv.net/ajax/genre/novel/literature?mode=safe&lang=zh\"},\n {\"情感\": \"https://www.pixiv.net/ajax/genre/novel/drama?mode=safe&lang=zh\"},\n {\"历史\": \"https://www.pixiv.net/ajax/genre/novel/historical_pieces?mode=safe&lang=zh\"},\n {\"耽美\": \"https://www.pixiv.net/ajax/genre/novel/bl?mode=safe&lang=zh\"},\n {\"百合\": \"https://www.pixiv.net/ajax/genre/novel/yuri?mode=safe&lang=zh\"},\n {\"散文·诗歌\": \"https://www.pixiv.net/ajax/genre/novel/poetry?mode=safe&lang=zh\"},\n {\"随笔·纪实\": \"https://www.pixiv.net/ajax/genre/novel/non-fiction?mode=safe&lang=zh\"},\n {\"剧本\": \"https://www.pixiv.net/ajax/genre/novel/screenplays?mode=safe&lang=zh\"},\n {\"评论\": \"https://www.pixiv.net/ajax/genre/novel/reviews?mode=safe&lang=zh\"},\n {\"其他\": \"https://www.pixiv.net/ajax/genre/novel/other?mode=safe&lang=zh\"}\n]\n\nlet about = [\n {\"📘 书源相关 📘\": \"\"},\n {\"🏠 主页\": \"https://pixivsource.pages.dev\"},\n {\"🔰 功能\": \"https://pixivsource.pages.dev/Pixiv\"},\n {\"🐞 反馈\": \"https://github.com/DowneyRem/PixivSource/issues\"},\n {\"💰 打赏\": \"https://pixivsource.pages.dev/Sponsor\"},\n]\n\n// 收藏标签\nlet likeTagLinks = [{\"📌 喜欢标签 📌\":\"\"}]\nlet likeTags = getFromCacheObject(\"likeTags\")\nif (likeTags && likeTags.length >= 1) {\n likeTags.forEach(tag => {\n let tagLink = {}\n tagLink[tag] = `${urlSearchNovel(tag, \"{{page}}\")}`\n likeTagLinks.push(tagLink)\n })\n}\n\n// 他人收藏\nlet othersBookmarks = [{\"❤️ 他人收藏 ❤️\": \"\"}]\nlet likeAuthors = getFromCacheMap(\"likeAuthorsMap\")\nif (!likeAuthors) likeAuthors = getFromCacheMap(\"likeAuthors\")\nif (likeAuthors.size > 0) {\n likeAuthors.forEach((authorName, authorId) => {\n let bookmark = {}\n bookmark[authorName] = urlUserBookmarks(authorId)\n othersBookmarks.push(bookmark)\n })\n}\n\n// 兽人小说\nlet furrynovels = [{\"🦊 兽人小说 🦊\": \"\"}]\nif (settings.SHOW_FURRY) {\n let authorsListLinpx = getAjaxJson(urlLinpxAuthors())\n const randomAuthors = authorsListLinpx\n .sort(() => Math.random() - 0.5)\n .slice(0, 5)\n const randomObjects = randomAuthors.map(author => ({\n [author.name]: urlUserAllWorks(author.id)\n }))\n furrynovels = furrynovels.concat(randomObjects)\n}\n\n\n// 添加发现\nif (settings.SHOW_ADULT) li = li.concat(adultDefault)\nif (settings.SHOW_GENERAL) li = li.concat(normalDefault)\nif (settings.SHOW_NEW_ADULT) li = li.concat(adultNew)\nif (settings.SHOW_NEW_GENERAL) li = li.concat(generalNew)\n\nif (settings.SHOW_RANK_ADULT)li = li.concat(adultRank)\nif (settings.SHOW_RANK_GENERAL) li = li.concat(generalRank)\nif (settings.SHOW_GENRE_ADULT) li = li.concat(adultGenre)\nif (settings.SHOW_GENRE_GENERAL) li = li.concat(generalGenre)\n\nif (likeTags && likeTags.length >= 1) li = li.concat(likeTagLinks)\nif (likeAuthors && likeAuthors.size > 0) li = li.concat(othersBookmarks)\nif (settings.SHOW_FURRY) li = li.concat(furrynovels)\nif (settings.SHOW_ABOUT) li = li.concat(about)\nsleepToast('功能手册\\n\\n发现 - 书源相关 - \"🔰 功能\" ')\n\n// 添加格式\nli.forEach(item => {\n item.title = Object.keys(item)[0]\n item.url = Object.values(item)[0]\n if (item.url.includes(\"https://www.pixiv.net\")) item.url = urlIP(item.url)\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})\n\nJSON.stringify(li)", "header": "{\"Referer\":\"https://www.pixiv.net\"}", "jsLib": "var checkTimes = 0\nvar cacheSaveSeconds = 30*24*60*60 // 长期缓存 30 天\nvar cacheTempSeconds = 10*60*1000 // 冷却时间 10 分钟\n\nfunction cacheGetAndSet(key, supplyFunc, requestUpdate) {\n const {java, cache} = this\n let timestamp = 0\n let v = this.getFromCacheObject(key)\n if (Array.isArray(v)) {\n try {\n timestamp = v[0].timestamp\n } catch (e) {\n timestamp = 0\n }\n } else if (v) {\n timestamp = v.timestamp\n }\n\n const isExpired = v && (new Date().getTime() >= timestamp + cacheTempSeconds)\n const isError = v && (v.error === true) && isExpired\n requestUpdate = requestUpdate || isExpired\n\n if (!v || requestUpdate || isError) {\n v = supplyFunc()\n let now = new Date().getTime()\n // getAjaxJson getWebviewJson 时间戳写入对象本身\n if (!Array.isArray(v)) {\n v = Object.assign({timestamp: now}, v)\n }\n // else {\n // // getAjaxAllJson 时间戳写入第一个元素(读取时 v[0].timestamp)// 不重复写入\n // if (v.length > 0) v[0] = Object.assign({timestamp: now}, v[0])\n // }\n this.putInCacheObject(key, v, cacheSaveSeconds)\n }\n return v\n}\n\nfunction putInCache(name, object, saveSeconds) {\n const {java, cache} = this\n if (saveSeconds === undefined) saveSeconds = 0\n if (object) {\n cache.put(name, object, saveSeconds)\n }\n}\nfunction getFromCache(name) {\n const {java, cache} = this\n let object = cache.get(name)\n if (object === undefined) return null // 兼容源阅\n return object\n}\n\nfunction normalizeUrl(url) {\n if (!url.startsWith(\"https://210.140\")) return url\n return url.replace(\"210.140.139.155\", \"www.pixiv.net\")\n .replace(\"210.140.139.133\", \"i.pximg.net\")\n .split(\",\")[0]\n}\nfunction putInCacheObject(objectName, object, saveSeconds) {\n const {java, cache} = this\n if (object === undefined) object = null\n if (saveSeconds === undefined) saveSeconds = 0\n if (objectName === \"pixivSettings\") {\n this._settings = object\n }\n cache.put(this.normalizeUrl(objectName), JSON.stringify(object), saveSeconds)\n}\nfunction getFromCacheObject(objectName) {\n const {java, cache} = this\n if (objectName === \"pixivSettings\" && this._settings) {\n return this._settings\n }\n let object = cache.get(this.normalizeUrl(objectName))\n if (object === undefined) return null // 兼容源阅,避免 parse 报错\n return JSON.parse(object)\n}\n\nfunction putInCacheMap(mapName, mapObject, saveSeconds) {\n const {java, cache} = this\n if (saveSeconds === undefined) saveSeconds = 0\n let orderedArray = []\n mapObject.forEach((value, key) => {\n const item = {}\n item[key] = value\n orderedArray.push(item)\n })\n\n // let orderedArray = Array.from(mapObject, ([key, value]) => ({ [key]: value }))\n // [{'key1': 'value1'}, {'key2': 'value2'}]\n cache.put(mapName, JSON.stringify(orderedArray), saveSeconds)\n}\nfunction getFromCacheMap(mapName) {\n const {java, cache} = this\n let cached = cache.get(mapName)\n let newMap = new Map()\n if (cached === null || cached === undefined) {\n return newMap\n }\n\n let parsedData\n try {\n parsedData = JSON.parse(cached)\n } catch (e) {\n return newMap\n }\n\n if (Array.isArray(parsedData)) {\n parsedData.forEach(item => {\n for (let key in item) {\n newMap.set(key, item[key])\n }\n })\n } else {\n for (let key in parsedData) {\n newMap.set(key, parsedData[key])\n }\n }\n return newMap\n}\n\nfunction isHtmlString(str) {\n return str.startsWith(\"\") || str.startsWith(\"\")\n}\nfunction isJsonString(str) {\n try {\n let result = JSON.parse(str)\n return typeof result === \"object\" && result !== null\n } catch(e) {\n return false\n }\n}\nfunction isFunctionString(str) {\n return typeof str == \"string\" && (\n str.trim().startsWith(\"function\") ||\n RegExp(/^\\s*(\\([^)]*\\)|[A-Za-z_$][\\w$]*)\\s*=>/).test(str)\n )\n}\n\nfunction isLogin() {\n const {java, cache} = this\n return !!this.getFromCache(\"pixivCsrfToken\")\n}\nfunction checkLogin() {\n const {java, cache} = this\n return !JSON.stringify(java.ajax(urlSelfInfo())).error\n}\n\nfunction getAjaxJson(url, requestUpdate) {\n const {java, cache} = this\n return this.cacheGetAndSet(url, () => {\n return JSON.parse(java.ajax(url))\n }, requestUpdate)\n}\nfunction getAjaxAllJson(urls, requestUpdate) {\n const {java, cache} = this\n let batchKey = JSON.stringify(urls)\n return this.cacheGetAndSet(batchKey, () => {\n let results = []\n let now = new Date().getTime()\n let responses = java.ajaxAll(urls)\n for (let i in urls) {\n let data = JSON.parse(responses[i].body())\n data = Object.assign({timestamp: now}, data)\n results.push(data)\n this.putInCacheObject(urls[i], data, cacheSaveSeconds)\n }\n return results\n }, requestUpdate)\n}\n\nfunction getWebViewUA() {\n const {java, cache} = this\n let userAgent = this.getFromCache(\"userAgent\")\n if (userAgent) return String(userAgent)\n\n userAgent = String(java.getWebViewUA())\n // java.log(`userAgent=${userAgent}`)\n this.putInCache(\"userAgent\", userAgent, cacheSaveSeconds/4)\n return String(userAgent)\n}\nfunction startBrowser(url, title) {\n const {java, cache} = this\n if (!title) title = url\n let msg = \"\"\n let headers = {}\n headers[\"User-Agent\"] = this.getWebViewUA()\n\n if (url.includes(\"https://www.pixiv.net\")) {\n if (url.includes(\"settings\")) msg += \"⚙️ 账号设置\"\n else msg += \"⤴️ 分享小说\"\n msg += \"\\n\\n即将打开 Pixiv\\n请确认已开启代理/梯子/VPN等\"\n } else if (url.includes(\"github.com\") || url.includes(\"github.io\")) {\n if (url.includes(\"issues\")) msg += \"🐞 反馈问题\"\n msg += \"\\n\\n即将打开 Github\\n请确认已开启代理/梯子/VPN等\"\n }\n this.sleepToast(msg, 0.01)\n java.startBrowser(`${url}, ${JSON.stringify({headers: headers})}`, title)\n}\n\n// 直连功能参考自 洛娅橙的阅读仓库\n// https://github.com/Luoyacheng/yuedu\n// 其直连功能参考自 PixEz Flutter\n// https://github.com/Notsfsssf/pixez-flutter\nfunction urlIP(url) {\n const {java, cache} = this\n if (!this._settings) {\n this._settings = this.getFromCacheObject(\"pixivSettings\") || this.setDefaultSettings()\n }\n if (this._settings.IPDirect) {\n url = url.replace(\"http://\", \"https://\").replace(\"www.pixiv.net\", \"210.140.139.155\")\n let headers = {\n \"User-Agent\": this.getFromCache(\"userAgent\"),\n \"X-Requested-With\": \"XMLHttpRequest\",\n \"Host\": \"www.pixiv.net\",\n \"Referer\": \"https://www.pixiv.net/\",\n \"X-csrf-token\": this.getFromCache(\"pixivCsrfToken\") || \"\",\n \"Cookie\": this.getFromCache(\"pixivCookie\") || \"\"\n }\n return `${url}, ${JSON.stringify({headers: headers})}`\n }\n return url\n}\n\nfunction urlSelfInfo() {\n return `https://www.pixiv.net/ajax/settings/self`\n}\n\nfunction urlNovelUrl(novelId) {\n return `https://www.pixiv.net/novel/show.php?id=${novelId}`\n}\nfunction urlNovelDetailed(novelId) {\n return `https://www.pixiv.net/ajax/novel/${novelId}`\n}\nfunction urlNovelsDetailed(userId, nidList) {\n return `https://www.pixiv.net/ajax/user/${userId}/novels?${nidList.map(v => `ids[]=${v}`).join(\"&\")}`\n}\nfunction urlNovelBookmarkData(novelId) {\n return `https://www.pixiv.net/ajax/novel/${novelId}/bookmarkData`\n}\nfunction urlNovelComments(novelId, offset, limit) {\n return `https://www.pixiv.net/ajax/novels/comments/roots?novel_id=${novelId}&offset=${offset}&limit=${limit}&lang=zh`\n}\nfunction urlNovelCommentsReply(commentId, page) {\n return `https://www.pixiv.net/ajax/novels/comments/replies?comment_id=${commentId}&page=${page}&lang=zh`\n}\nfunction urlNovelsRecommendInit(novelId, limit) {\n if (limit === undefined) limit = 9\n return `https://www.pixiv.net/ajax/novel/${novelId}/recommend/init?limit=${limit}&lang=zh`\n}\nfunction urlNovelsRecommendDetailed(nidList) {\n if (nidList.length >= 9) nidList.length = 9\n return `https://www.pixiv.net/ajax/novel/recommend/novels?${nidList.map(v => `novelIds[]=${v}`).join(\"&\")}`\n}\n\nfunction urlSeriesUrl(seriesId) {\n return `https://www.pixiv.net/novel/series/${seriesId}`\n}\nfunction urlSeriesDetailed(seriesId) {\n return `https://www.pixiv.net/ajax/novel/series/${seriesId}?lang=zh`\n}\nfunction urlSeriesNovelsTitles(seriesId) {\n return `https://www.pixiv.net/ajax/novel/series/${seriesId}/content_titles`\n}\nfunction urlSeriesNovels(seriesId, limit, offset) {\n if (limit > 30) limit = 30\n if (limit < 10) limit = 10\n return `https://www.pixiv.net/ajax/novel/series_content/${seriesId}?limit=${limit}&last_order=${offset}&order_by=asc&lang=zh`\n}\n\nfunction urlUserUrl(userID) {\n return `https://www.pixiv.net/users/${userID}/novels`\n}\nfunction urlUserDetailed(userID) {\n return `https://www.pixiv.net/ajax/user/${userID}`\n}\nfunction urlUserWorkLatest(userID) {\n return `https://www.pixiv.net/ajax/user/${userID}/works/latest`\n}\nfunction urlUserAllWorks(userId) {\n return `https://www.pixiv.net/ajax/user/${userId}/profile/all?lang=zh`\n}\nfunction urlUserBookmarks(userId) {\n return `https://www.pixiv.net/ajax/user/${userId}/novels/bookmarks?tag=&offset={{(page-1)*30}}&limit=30&rest=show&lang=zh`\n}\n\nfunction urlSearchNovelFull(novelName, series, 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&gs=${Number(series)}&lang=zh`\n}\nfunction urlSearchNovel(novelName, page) {\n return urlSearchNovelFull(novelName, false, page)\n}\nfunction urlSearchSeries(novelName, page) {\n return urlSearchNovelFull(novelName, true, page)\n}\nfunction urlSearchUser(userName, page, full) {\n if (!page) page = 1\n const fullUrl = full ? \"s_usr_full\": \"s_usr\"\n return `https://www.pixiv.net/ajax/search/users?nick=${userName}&s_mode=${fullUrl}&i=1&p=${page}&lang=zh`\n}\n\nfunction urlLinpxAuthors() {\n return \"https://api.linpx.ink/fav/user/cache\"\n}\n\n// 直连功能参考自 洛娅橙的阅读仓库\n// https://github.com/Luoyacheng/yuedu\n// 其直连功能参考自 PixEz Flutter\n// https://github.com/Notsfsssf/pixez-flutter\nfunction urlCoverUrl(url) {\n const {java, cache} = this\n if (url && !url.trim()) return \"\"\n\n let headers = {\"Referer\": \"https://www.pixiv.net/\"}\n if (!this._settings) {\n this._settings = this.getFromCacheObject(\"pixivSettings\") || this.setDefaultSettings()\n }\n if (this._settings.IPDirect && 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 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 urlIllustOriginal(illustId, order) {\n const {java, cache} = this\n if (!order || order <= 1) order = 1\n let illustOriginal = \"\"\n\n let resp = this.getAjaxJson(this.urlIP(urlIllustDetailed(illustId)))\n try {\n illustOriginal = resp.body.urls.original\n } catch (e) {\n try {\n let illustThumb = resp.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-master/img/${date}/${illustId}_p0_master1200.jpg`\n } catch (e) {}\n }\n\n if (illustOriginal.split(\",\")[0] === \"\") return \"\"\n return this.urlCoverUrl(illustOriginal.replace(`_p0`, `_p${order - 1}`))\n}\nfunction urlEmojiUrl(emojiId) {\n return `https://s.pximg.net/common/images/emoji/${emojiId}.png, {'style': 'text'}`\n}\nfunction urlStampUrl(stampId) {\n return `https://s.pximg.net/common/images/stamp/generated-stamps/${stampId}_s.jpg, {'style': 'text'}`\n}\n\nfunction urlMessageThreadLatest(max) {\n if (max === undefined || max <= 5) max = 5\n return `https://www.pixiv.net/rpc/index.php?mode=latest_message_threads2&num=${max}&lang=zh`\n}\nfunction urlMessageThreadContents(threadId, max) {\n return `https://www.pixiv.net/rpc/index.php?mode=message_thread_contents&thread_id=${threadId}&num=${max}`\n}\nfunction urlMessageThreadDetail(threadId) {\n return `https://www.pixiv.net/rpc/index.php?mode=message_thread&thread_id=${threadId}`\n}\nfunction urlNotification() {\n return `https://www.pixiv.net/ajax/notification?lang=zh`\n}\n\nfunction addZero(num) {\n return String(num).padStart(2, '0')\n}\nfunction dateFormat(str) {\n let time = new Date(str);\n let Y = time.getFullYear() + \"年\";\n let M = this.addZero(time.getMonth() + 1) + \"月\";\n let D = this.addZero(time.getDate()) + \"日\";\n return Y + M + D;\n}\nfunction timeFormat(str) {\n let time = new Date(str);\n let YY = time.getFullYear()\n let MM = this.addZero(time.getMonth() + 1)\n let DD = this.addZero(time.getDate())\n let hh = this.addZero(time.getHours())\n let mm = this.addZero(time.getMinutes())\n let ss = this.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}\n\nfunction sleep(seconds) {\n return Packages.java.lang.Thread.sleep(1000*seconds)\n}\nfunction sleepToast(text, seconds) {\n let {java} = this\n java.log(text)\n java.longToast(text)\n if (seconds === undefined) {seconds = 0.01}\n this.sleep(seconds)\n}\n\nfunction setDefaultSettings() {\n const {java, cache} = this\n let settings = {}\n settings.SEARCH_AUTHOR = false // 搜索:默认不搜索作者名称\n settings.CONVERT_CHINESE = true // 搜索:搜索时进行繁简转换\n settings.MORE_INFORMATION = false // 详情:书籍简介显示更多信息\n\n settings.ADD_CHAPTER_INDEX = false // 目录:添加章节编号\n settings.SHOW_UPDATE_TIME = true // 目录:显示更新时间,但会增加少许请求\n settings.SHOW_ORIGINAL_LINK = true // 目录:显示原始链接,但会增加大量请求\n\n settings.SHOW_CAPTIONS = true // 正文:章首显示描述\n settings.SHOW_PICTURES = true // 正文:正文显示图片,但会增加少许请求\n settings.SHOW_QUESTION = true // 正文:正文显示投票\n settings.SHOW_COMMENTS = true // 正文:章尾显示评论,但会增加大量请求\n settings.REPLACE_TITLE_MARKS = true // 正文:注音内容为汉字时,替换为书名号\n\n settings.AUTO_LIKE_NOVELS = false // 全局:添加书架:自动收藏/追更\n settings.AUTO_DISLIKE_NOVELS = false// 全局:删除书籍:自动取消收藏/追更\n settings.HIDE_LIKE_NOVELS = false // 全局:搜索/发现:隐藏已收藏小说\n settings.HIDE_WATCHED_SERIES = false// 全局:搜索/发现:隐藏已追更系列\n\n settings.COMBINE_NOVELS = true // 全局:整合系列\n settings.IPDirect = false // 全局:直连模式\n settings.FAST = false // 全局:快速模式\n settings.DEBUG = false // 全局:调试模式\n\n settings.SHOW_ADULT = true // 发现:显示 默认发现\n settings.SHOW_GENERAL = true // 发现:显示 常规小说\n settings.SHOW_NEW_ADULT = true // 发现:显示 最新企划约稿 R18\n settings.SHOW_NEW_GENERAL = false // 发现:显示 最新企划约稿 常规\n\n settings.SHOW_RANK_ADULT = true // 发现:显示 排行榜单 R18\n settings.SHOW_RANK_GENERAL = false // 发现:显示 排行榜单 常规\n settings.SHOW_GENRE_ADULT = false // 发现:显示 原创热门 R18\n settings.SHOW_GENRE_GENERAL = false // 发现:显示 原创热门 常规\n\n settings.SHOW_ABOUT = true // 发现:显示 书源相关\n settings.SHOW_FURRY = false // 发现:显示 兽人小说推荐作者\n\n settings.SHOW_SETTINGS = false // 设置:显示 书源设置\n settings.SHOW_DISCOVER = false // 设置:显示 发现设置\n settings.SHOW_SETTINGS2 = false // 设置:显示 书源设置\n settings.SHOW_DISCOVER2 = false // 设置:显示 发现设置\n this.putInCacheObject(\"pixivSettings\", settings)\n return settings\n}\nfunction checkSettings(settings) {\n const {java, cache} = this\n if (!settings) settings = this.getFromCacheObject(\"pixivSettings\")\n if (!settings) settings = this.setDefaultSettings()\n if (settings.IPDirect) {\n settings.SHOW_ORIGINAL_LINK = false // 目录:显示章节源链接\n }\n\n if (settings.FAST) {\n settings.CONVERT_CHINESE = false // 搜索:繁简通搜\n settings.SHOW_ORIGINAL_LINK = false // 目录:显示章节源链接\n settings.SHOW_UPDATE_TIME = false // 目录:显示章节更新时间\n\n settings.SHOW_PICTURES = false // 正文:显示图片\n settings.SHOW_QUESTION = false // 正文:正文显示投票\n settings.SHOW_COMMENTS = false // 正文:显示评论\n }\n\n // 新设置初始化\n if (settings.SHOW_PICTURES === undefined) settings.SHOW_PICTURES = true\n if (settings.SHOW_QUESTION === undefined) settings.SHOW_QUESTION = true\n if (settings.COMBINE_NOVELS === undefined) settings.COMBINE_NOVELS = true\n if (settings.SHOW_ADULT === undefined) settings.SHOW_ADULT = true\n if (settings.SHOW_ABOUT === undefined) settings.SHOW_ABOUT = true\n\n this.putInCacheObject(\"pixivSettings\", settings)\n return settings\n}\n\nfunction updateSource() {\n try {\n this.updateSourceHtml()\n } catch(e) {\n this.updateSourceLink()\n }\n}\n\nfunction updateSourceLink(){\n const {java, source} = this\n let sourceName\n if (source.bookSourceUrl.includes(\"pixiv\")) sourceName = \"pixiv\"\n else if (source.bookSourceUrl.includes(\"furrynovel\")) sourceName = \"linpx\"\n\n try {\n java.openUrl(`legado://import/importonline?src=https://cdn.jsdelivr.net/gh/DowneyRem/PixivSource@main/${sourceName}.json`)\n } catch (e) {\n java.openUrl(`legado://import/importonline?src=https://raw.githubusercontent.com/DowneyRem/PixivSource/main/${sourceName}.json`)\n }\n}\n\nfunction updateSourceHtml() {\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, {\n \"User-Agent\": this.getFromCache(\"userAgent\"),\n 'X-Requested-With': 'XMLHttpRequest'\n }).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,{\n \"User-Agent\": this.getFromCache(\"userAgent\"),\n 'X-Requested-With': 'XMLHttpRequest'\n }).body())[index]\n } catch (e) {\n onlineSource = {\n lastUpdateTime: new Date().getTime(),\n bookSourceComment: source.bookSourceComment\n }\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-20, 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}\n\nfunction profile(funcName, func) {\n return function() {\n var start = Packages.java.lang.System.currentTimeMillis()\n try {\n return func.apply(this, arguments)\n } finally {\n const {java, cache} = this\n var end = Packages.java.lang.System.currentTimeMillis()\n var duration = end - start\n if (duration >= 5) {\n java.log(\"⏱️ [\" + funcName + \"] 耗时: \" + duration + \"ms\")\n }\n }\n }\n}\n\n// cacheGetAndSet = profile(\"cacheGetAndSet\", cacheGetAndSet)\n// getAjaxJson = profile(\"getAjaxJson\", getAjaxJson)\n// getAjaxAllJson = profile(\"getAjaxAllJson\", getAjaxAllJson)\n// getWebviewJson = profile(\"getWebviewJson\", getWebviewJson)\n//\n// putInCacheObject = profile(\"putInCacheObject\", putInCacheObject)\n// getFromCacheObject = profile(\"getFromCacheObject\", getFromCacheObject)\n// putInCache = profile(\"putInCache\", putInCache)\n// getFromCache = profile(\"getFromCache\", getFromCache)\n// putInCacheMap= profile(\"putInCacheMap\", putInCacheMap)\n// getFromCacheMap = profile(\"getFromCacheMap\", getFromCacheMap)\n//\n// urlIP = profile(\"urlIP\", urlIP)\n// urlCoverUrl = profile(\"urlCoverUrl\", urlCoverUrl)\n// urlIllustOriginal = profile(\"urlIllustOriginal\", urlIllustOriginal)\n//\n// urlSearchNovel = profile(\"urlSearchNovel\", urlSearchNovel)\n// urlSearchUser = profile(\"urlSearchUser\", urlSearchUser)\n//\n// checkSettings = profile(\"checkSettings\", checkSettings)\n// getWebViewUA = profile(\"getWebViewUA\", getWebViewUA)", "lastUpdateTime": 1789920000251, "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\n// 检测 轻悦时光,多平台\n// 不可用 java.getResponse()\n// java.getUserAgent() java.getWebViewUA() 目前返回内容相同\nfunction isQYSG() {\n return java.getUserAgent() === java.getWebViewUA()\n}\n\n// 检测 源阅\n// 可用 java.ajax() 不可用 java.webview() java.ajaxAll()\n// 可用 java.getCookie() cache.put() cache.get() 默认值为 undefined\n// 可用 java.startBrowser() 不可用 java.startBrowserAwaitAwait\n// 可用 source.bookSourceName source.getVariable() source.setVariable()等\n// java.getUserAgent() java.getWebViewUA() 目前返回内容相同\nfunction isSourceRead() {\n return java.getUserAgent() === java.getWebViewUA() && String(java.getWebViewUA()).includes(\"Win\")\n}\n\n// 正式版 不支持在 JSlib 的函数直接设置默认参数\n// 正式版 不支持 a?.b 的写法\n// 检测 阅读 正式版 与 Beta 版本\nfunction isLegadoOfficial() {\n let isLegadoOfficialStatus\n try {\n eval('({})?.value')\n isLegadoOfficialStatus = false\n } catch (e) {\n isLegadoOfficialStatus = true\n }\n return isLegadoOfficialStatus\n}\n\n// 检测 阅读 Beta 版本 与 Sigma 版本\n// Sigma 版本新增函数\n// java.ajaxTestAll()\n// java.openVideoPlayer(url: String, title: String, float: Boolean)\n// cookie.setWebCookie(url,cookie)\n// source.refreshExplore()\n// source.refreshJSLib()\nfunction isLegadoSigma() {\n return typeof java.ajaxTestAll === \"function\"\n}\n\nfunction publicFunc() {\n let u = {}\n u.init = () => {\n u.settings = getFromCacheObject(\"pixivSettings\")\n if (!u.settings) u.settings = setDefaultSettings()\n u.settings = checkSettings()\n putInCacheObject(\"pixivSettings\", u.settings)\n\n u.environment = getFromCacheObject(\"pixivEnvironment\")\n if (!u.environment) u.checkEnvironment()\n }\n\n u.checkEnvironment = () => {\n u.environment = {}\n u.environment.IS_SOURCEREAD = isSourceRead()\n u.environment.IS_QYSG = isQYSG()\n u.environment.IS_LEGADO_SIGMA = isLegadoSigma()\n u.environment.IS_LEGADO_OFFICIAL = isLegadoOfficial()\n u.environment.IS_LEGADO = u.environment.IS_LEGADO_SIGMA || u.environment.IS_LEGADO_OFFICIAL\n putInCacheObject(\"pixivEnvironment\", u.environment)\n }\n\n u.logSource = () => {\n java.log(`${source.bookSourceComment.split(\"\\n\")[0]}`)\n java.log(`📌 ${source.bookSourceComment.split(\"\\n\")[2]}`)\n java.log(`📆 更新时间:${java.timeFormat(source.lastUpdateTime)}`)\n }\n\n u.logEnvironment = () => {\n if (u.environment.IS_SOURCEREAD) {\n java.log(\"▶️ 当前软件:🍎 源阅 SourceRead\")\n } else if (u.environment.IS_QYSG) {\n java.log(\"▶️ 当前软件:🍎 轻悦时光 QYSG\")\n } else if (u.environment.IS_LEGADO_SIGMA) {\n java.log(\"▶️ 当前软件:🤖 阅读 Sigma\")\n } else if (u.environment.IS_LEGADO_OFFICIAL && !source.bookSourceComment.includes(\"备用\")) {\n java.log(\"▶️ 当前软件:🤖 阅读 正式版\")\n sleepToast(\"\\n⚠️当前软件为:阅读【正式版】\\n【正式版】已年久失修,不推荐继续使用\\n\\n为了更好的使用体验,请使用:\\n阅读【Sigma】\\n\\n即将为您打开下载界面,请在浏览器内打开并下载\")\n sleep(3); startBrowser(\"https://pixivsource.pages.dev/Download\", \"下载阅读 Sigma\")\n }\n }\n\n u.logSettings = () => {\n if (u.settings.IPDirect) java.log(\"✈️ 直连模式:✅ 已开启\")\n if (u.settings.FAST) java.log(\"⏩ 快速模式:✅ 已开启\")\n if (u.settings.DEBUG) java.log(\"🐞 调试模式:✅ 已开启\")\n }\n\n u.checkPixiv = () => {\n let logs = []\n let checkThings = [\"pixivCsrfToken\", \"pixivCookie\", \"pixivUid\"]\n checkThings.forEach(item => {\n let text = `${getFromCache(item)? \"✅\" : \"❌\"} ${item}`\n logs.push(text)\n java.log(text)\n })\n if (logs.join(\"\").split(\"❌\").length >= 2) {\n sleepToast(\"⚠️ 登录状态\\n 当前登录信息不完整,请重新登录\\n或使用【备份恢复】恢复登录状态\")\n }\n }\n\n u.debugFunc = (func) => {\n if (util.settings.DEBUG === true) {\n func()\n }\n }\n\n u.checkStatus = function(status) {\n if (status === true) return \"✅ 已\"\n else if (status === false) return \"❌ 未\"\n else if (status === undefined) return \"🈚️ 无数据:\"\n }\n\n u.login = function() {\n let resp = java.startBrowserAwait(`https://accounts.pixiv.net/login,\n {\"headers\": {\"User-Agent\": ${getWebViewUA()}}}`, '登录账号', false)\n if (resp.code() === 200) {\n this.getCsrfToken(); this.getCookie()\n } else {\n java.log(resp.code()); sleepToast(\"⚠️ 登录失败\")\n }\n }\n\n u.logout = function() {\n this.removeCookie()\n java.startBrowser(\"https://www.pixiv.net/logout.php\", \"退出账号\")\n this.removeCookie()\n sleepToast(`✅ 已退出当前账号\\n\\n退出后请点击右上角的 ✔️ 退出\\n\\n登录请点击【登录账号】进行登录`)\n }\n\n u.getCookie = function() {\n let pixivCookie = String(java.getCookie(\"https://www.pixiv.net/\", null))\n if (isLogin()) putInCache(\"pixivCookie\", pixivCookie, 60*60) // 缓存1h\n }\n\n u.removeCookie = function() {\n cookie.removeCookie('https://www.pixiv.net')\n cookie.removeCookie('https://accounts.pixiv.net')\n cookie.removeCookie('https://accounts.google.com')\n cookie.removeCookie('https://api.weibo.com')\n cache.delete(\"pixivUid\")\n cache.delete(\"pixivCookie\")\n cache.delete(\"pixivCsrfToken\")\n cache.delete(\"PixivHeaders\")\n }\n\n // 获取 Csrf Token,以便进行收藏等请求\n // 获取方法来自脚本 Pixiv Previewer\n // https://github.com/Ocrosoft/PixivPreviewer\n // https://greasyfork.org/zh-CN/scripts/30766-pixiv-previewer/code\n u.getCsrfToken = function() {\n let pixivCsrfToken = getFromCache(\"pixivCsrfToken\")\n if (!pixivCsrfToken) {\n let html = java.ajax(\"https://www.pixiv.net/\")\n try {\n pixivCsrfToken = html.match(/token\\\\\":\\\\\"([a-z0-9]{32})/)[1]\n putInCache(\"pixivCsrfToken\", pixivCsrfToken) // 与登录设备有关,无法存储 nul\n } catch (e) {\n pixivCsrfToken = null\n cache.delete(\"pixivCsrfToken\") // 与登录设备有关,无法存储 nul\n // sleepToast(\"⚠️ 未登录账号(pixivCsrfToken)\")\n }\n java.log(`pixivCsrfToken:\\n${pixivCsrfToken}`)\n }\n return pixivCsrfToken\n }\n\n // 将多个长篇小说解析为一本书\n u.combineNovels = function(novels) {\n if (!util.settings.COMBINE_NOVELS) return novels\n return novels.filter(novel => {\n // 单本直接解析为一本书\n if (!novel.seriesId) {\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.authorFilter = function(novels) {\n let blockAuthorList = getFromCacheObject(\"blockAuthorList\")\n if (Array.isArray(blockAuthorList)) {\n java.log(`🚫 屏蔽作者ID:${JSON.stringify(blockAuthorList)}`)\n let blockAuthorSet = new Set(blockAuthorList.map(id => String(id)))\n novels = novels.filter(novel => !blockAuthorSet.has(String(novel.userId)))\n }\n return novels\n }\n\n\n // 过滤收藏与追更\n u.novelFilter = function(novels) {\n let novels1 = [], novels2 = []\n let likeNovels = getFromCacheObject(\"likeNovels\")\n let watchedSeries = getFromCacheObject(\"watchedSeries\")\n let novels0 = novels.map(novel => novel.id)\n\n java.log(`${util.checkStatus(!util.settings.HIDE_LIKE_NOVELS)}显示收藏小说`)\n if (util.settings.HIDE_LIKE_NOVELS) {\n novels = novels.filter(novel => !likeNovels.includes(Number(novel.id)))\n novels1 = novels.map(novel => novel.id)\n java.log(`⏬ 过滤收藏:过滤前${novels0.length};过滤后${novels1.length}`)\n }\n\n java.log(`${util.checkStatus(!util.settings.HIDE_WATCHED_SERIES)}显示追更系列`)\n if (util.settings.HIDE_WATCHED_SERIES) {\n novels = novels.filter(novel => !watchedSeries.includes(Number(novel.seriesId)))\n novels2 = novels.map(novel => novel.id)\n if (novels1.length >= 1) novels0 = novels1\n java.log(`⏬ 过滤追更:过滤前${novels0.length};过滤后${novels2.length}`)\n }\n\n let novels3 = novels.map(novel => novel.id)\n if (novels0.length >= 1 && novels3.length === 0) {\n let msg = `⏬ 过滤小说\\n⚠️ 过滤后无结果\\n\\n请根据需要\\n`\n if (util.settings.HIDE_LIKE_NOVELS) msg += \"开启显示收藏小说\\n\"\n if (util.settings.HIDE_WATCHED_SERIES) msg += \"开启显示追更系列\"\n sleepToast(msg, 1)\n }\n return novels\n }\n\n // 过滤描述与标签(屏蔽标签/屏蔽描述)\n u.novelFilter2 = function(novels) {\n const length = novels.length\n let novels0 = novels.map(novel => novel.id)\n let blockCaption = getFromCacheObject(\"blockCaption\")\n if (!blockCaption) blockCaption = getFromCacheObject(\"captionBlockWords\")\n if (blockCaption) {\n // 仅保留没有任何屏蔽词的小说\n // novels = novels.filter(novel => {\n // return !blockCaption.some(item => {\n // if (novel.description !== undefined) return novel.description.includes(item)\n // })\n // })\n novels = novels.filter(novel => !blockCaption.some(item => novel.description.includes(item)))\n let novels2 = novels.map(novel => novel.id)\n java.log(`🚫 屏蔽描述:${blockCaption.join(\"\\n\")}`)\n java.log(`🚫 屏蔽描述:过滤前${novels0.length};过滤后${novels2.length}`)\n }\n\n let blockTags = getFromCacheObject(\"blockTags\")\n if (!blockTags) blockTags = getFromCacheObject(\"tagsBlockWords\")\n if (blockTags) {\n // 仅保留没有任何屏蔽词的小说\n // novels = novels.filter(novel => {\n // return !blockTags.some(item => {\n // if (novel.tags !== undefined) return novel.tags.includes(item)\n // })\n // })\n novels = novels.filter(novel => !blockTags.some(item => novel.tagsList.includes(item)))\n let novels2 = novels.map(novel => novel.id)\n java.log(`🚫 屏蔽标签:${blockTags.join(\"、\")}`)\n java.log(`🚫 屏蔽标签:过滤前${novels0.length};过滤后${novels2.length}`)\n }\n return novels\n }\n\n // 收藏小说/追更系列 写入缓存\n u.saveNovels = function(listInCacheName, list) {\n let listInCache = getFromCacheObject(listInCacheName)\n if (!listInCache) listInCache = []\n\n listInCache = listInCache.concat(list)\n listInCache = Array.from(new Set(listInCache))\n putInCacheObject(listInCacheName, listInCache)\n\n if (listInCacheName === \"likeNovels\") listInCacheName = \"❤️ 收藏小说ID\"\n else if (listInCacheName === \"watchedSeries\") listInCacheName = \"📃 追更系列ID\"\n util.debugFunc(() => {\n java.log(`${listInCacheName}:${JSON.stringify(listInCache)}`)\n })\n }\n\n u.saveAuthors = function(authors) {\n let pixivAuthors = getFromCacheObject(\"pixivAuthors\")\n if (!pixivAuthors) pixivAuthors = {}\n\n pixivAuthors = Object.assign(pixivAuthors, authors)\n putInCacheObject(\"pixivAuthors\", pixivAuthors)\n }\n\n // 处理 novels 列表\n u.handNovels = function(novels, isDetail) {\n if (!isDetail) isDetail = false\n let likeNovels = [], watchedSeries = [], authors = {}\n novels = util.authorFilter(novels)\n novels.forEach(novel => {\n // novel.id = novel.id\n // novel.title = novel.title\n // novel.userName = novel.userName\n // novel.userId = novel.userId\n // novel.tags = novel.tags\n authors[novel.userName] = novel.userId // 加入缓存,便于搜索作者\n if (novel.tags === undefined || novel.tags === null) {\n novel.tags = []\n }\n // 搜索单篇\n if (novel.isOneshot === undefined) {\n // novel.seriesId = novel.seriesId\n // novel.seriesTitle = novel.seriesTitle\n // novel.textCount = novel.textCount\n // novel.description = novel.description\n novel.coverUrl = novel.url\n // novel.createDate = novel.createDate\n // novel.updateDate = novel.updateDate\n }\n\n // 搜索系列\n if (novel.isOneshot !== undefined) {\n if (novel.isOneshot === true) {\n novel.seriesId = undefined\n novel.seriesTitle = undefined\n novel.id = novel.novelId // 获取真正的 novelId\n // novel.title = novel.title\n } else {\n novel.seriesId = novel.id\n novel.seriesTitle = novel.title\n novel.id = novel.novelId = novel.latestEpisodeId // 获取真正的 novelId\n // novel.isWatched = novel.isWatched // 搜索系列可获取\n }\n novel.textCount = novel.textLength\n novel.description = novel.caption\n novel.coverUrl = novel.cover.urls[\"480mw\"]\n novel.createDate = novel.createDateTime\n novel.updateDate = novel.updateDateTime\n }\n\n // 单篇正文详情页\n if (novel.content) {\n novel.novelId = novel.id\n novel.tags = novel.tags.tags.map(item => item.tag)\n novel.textCount = novel.userNovels[`${novel.id}`].textCount\n // novel.latestChapter = novel.title\n // novel.description = novel.description\n novel.coverUrl = novel.userNovels[`${novel.id}`].url\n // novel.createDate = novel.createDate\n novel.updateDate = novel.uploadDate\n\n if (novel.seriesNavData) {\n novel.seriesId = novel.seriesNavData.seriesId\n novel.seriesTitle = novel.seriesNavData.title\n }\n }\n\n // 系列详情\n if (novel.firstNovelId) {\n novel.seriesId = novel.id\n novel.id = novel.novelId = novel.firstNovelId\n novel.seriesTitle = novel.title\n novel.coverUrl = novel.cover.urls[\"480mw\"]\n // novel.isWatched = novel.isWatched // 搜索系列可获取\n }\n\n // 发现:排行榜\n if (novel.rank) {\n // novel.id = novel.id\n // novel.title = novel.title\n novel.userName = novel.user_name\n novel.userId = novel.user_id\n novel.tags = novel.tag_a\n // novel.language = novel.language\n novel.seriesId = novel.series_id\n novel.seriesTitle = novel.series_title || \"\"\n novel.textCount = novel.character_count\n novel.description = novel.comment\n novel.coverUrl = novel.url\n let date = novel.create_date.split(\" \")\n novel.createDate = novel.updateDate = `${date[0]}T${date[1]}:00+09:00`\n novel.isBookmark = novel.is_bookmarked\n }\n\n // 单篇加更多信息,搜索、详情\n if (!novel.seriesId || !util.settings.COMBINE_NOVELS) {\n novel.tags.unshift(\"单本\")\n novel.latestChapter = novel.title\n novel.detailedUrl = urlIP(urlNovelDetailed(novel.id))\n novel.total = 1\n if (novel.bookmarkData) {\n novel.isBookmark = true\n putInCache(`collect${novel.id}`, novel.bookmarkData.id)\n likeNovels.push(Number(novel.id))\n } else {\n novel.isBookmark = false\n }\n }\n\n // 系列添加更多信息,搜索\n if (novel.seriesId && !isDetail && util.settings.COMBINE_NOVELS) {\n novel.title = novel.seriesTitle\n novel.tags.unshift(\"长篇\")\n novel.detailedUrl = urlIP(urlSeriesDetailed(novel.seriesId))\n }\n\n // 系列添加更多信息,详情\n if (novel.seriesId && isDetail && util.settings.COMBINE_NOVELS) {\n let series = getAjaxJson(urlIP(urlSeriesDetailed(novel.seriesId))).body\n novel.id = series.firstNovelId\n novel.title = series.title\n novel.tags = novel.tags.concat(series.tags)\n novel.tags.unshift(\"长篇\")\n novel.textCount = series.publishedTotalCharacterCount\n novel.description = series.caption\n novel.coverUrl = series.cover.urls[\"480mw\"]\n novel.detailedUrl = urlIP(urlSeriesDetailed(novel.seriesId))\n novel.createDate = series.createDate\n novel.updateDate = series.updateDate\n novel.total = series.publishedContentCount\n novel.isWatched = series.isWatched\n if (novel.isWatched === true) {\n watchedSeries.push(Number(novel.seriesId))\n }\n\n // 防止系列首篇无权限获取\n // 发送请求获取第一章 获取标签与简介\n let firstNovel = {}\n try {\n firstNovel = getAjaxJson(urlIP(urlSeriesNovels(novel.seriesId, 30, 0))).body.thumbnails.novel[0]\n novel.id = novel.firstNovelId = firstNovel.id\n novel.tags = novel.tags.concat(firstNovel.tags)\n } catch (e) { // 防止系列首篇无权限获取\n firstNovel = {}\n firstNovel.description = \"\"\n }\n if (novel.description === \"\") {\n novel.description = firstNovel.description\n }\n }\n })\n // 收藏小说/追更系列 写入缓存\n util.saveNovels(\"likeNovels\", likeNovels)\n util.saveNovels(\"watchedSeries\", watchedSeries)\n util.saveAuthors(authors)\n util.debugFunc(() => {\n java.log(`处理小说完成`)\n })\n return novels\n }\n\n // 小说信息格式化\n u.formatNovels = function(novels) {\n novels = util.novelFilter(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.readingTime = `${novel.readingTime / 60} 分钟`\n novel.createDate = dateFormat(novel.createDate)\n novel.updateDate = dateFormat(novel.updateDate)\n\n novel.tagsList = []\n for (let i in novel.tags) {\n let tag = novel.tags[i]\n if (tag.includes(\"/\")) {\n let tags = tag.split(\"/\")\n novel.tagsList = novel.tagsList.concat(tags)\n } else {\n novel.tagsList.push(tag)\n }\n }\n novel.tags = Array.from(new Set(novel.tagsList))\n novel.tags = novel.tags.map(item => `#${item}`)\n novel.tags = novel.tags.join(\",\")\n\n let collectMsg\n if (novel.seriesId) {\n collectMsg = `📃 追更:${util.checkStatus(novel.isWatched)}追更系列`\n } else {\n collectMsg = `❤️ 收藏:${util.checkStatus(novel.isBookmark)}加入收藏`\n }\n\n if (util.settings.MORE_INFORMATION) {\n novel.description = `\\n🅿️ 登录:${util.checkStatus(isLogin())}登录账号\n ${collectMsg}\\n📖 书名:${novel.title}\\n👤 作者:${novel.userName}\n #️ 标签:${novel.tags}\\n⬆️ 上传:${novel.createDate}\n 🔄 更新:${novel.updateDate}\\n📄 简介:${novel.description}`\n } else {\n novel.description = `\\n🅿️ 登录:${util.checkStatus(isLogin())}登录账号\n ${collectMsg}\\n⬆️ 上传:${novel.createDate}\\n🔄 更新:${novel.updateDate}\n 📄 简介:${novel.description}`\n }\n })\n novels = util.novelFilter2(novels)\n return novels\n }\n\n u.getUserNovelsById = function(resp) {\n let novels = [], seriesIds = []\n // 获取系列小说,与 util.handNovels 系列详情兼容\n if (resp.body.novelSeries.length >= 1) {\n resp.body.novelSeries.forEach(novel =>{\n seriesIds.push(novel.id)\n novel.textCount = novel.publishedTotalCharacterCount\n novel.description = novel.caption\n })\n novels = novels.concat(resp.body.novelSeries)\n }\n\n // 获取所有系列内部的小说 ID\n let seriesNovelIds = []\n if (util.environment.IS_LEGADO) {\n let seriesUrls = seriesIds.map(seriesId => urlIP(urlSeriesNovelsTitles(seriesId)))\n // let resp = getAjaxAllJson(seriesUrls).map(resp => resp.body)\n // seriesNovelIds = resp.flat().map(item => item.id)\n seriesNovelIds = getAjaxAllJson(seriesUrls).flatMap(resp => resp.body.map(item => item.id))\n }\n\n if (util.environment.IS_SOURCEREAD) {\n seriesIds.forEach(seriesId => {\n let novels = getAjaxJson(urlIP(urlSeriesNovelsTitles(seriesId))).body\n seriesNovelIds.push.apply(seriesNovelIds, novels.map(novel => novel.id))\n })\n }\n // java.log(`有系列的小说ID:${JSON.stringify(seriesNovelIds)}`)\n // java.log(JSON.stringify(seriesNovelIds.length))\n\n // 获取单篇小说,默认过滤系列小说的 novelId,否则与系列重复\n let novelIds = Object.keys(resp.body.novels)\n novelIds = novelIds.filter(novelId => (!seriesNovelIds.includes(novelId)))\n // java.log(`真单篇的小说ID:${JSON.stringify(novelIds)}`)\n // java.log(JSON.stringify(novelIds.length))\n\n if (novelIds) {\n let singles = getAjaxJson(urlIP(urlNovelsDetailed(getFromCache(\"pixivUid\"), novelIds))).body\n novels = novels.concat(Object.values(singles).reverse())\n }\n return novels\n }\n\n\n // 正文,目录,详情,搜索:从网址获取id,返回单篇小说 res,系列返回首篇小说 res\n // pixiv 默认分享信息中有#号,不会被识别成链接,无法使用添加网址\n u.getNovelRespFirst = function(result) {\n let novelId = 0, res = {\"body\": {}}\n let isJson = isJsonString(result)\n let isHtml = isHtmlString(result)\n\n if (!isJson && isHtml) {\n let id = baseUrl.match(new RegExp(\"\\\\d+\"))[0]\n let pattern = \"(https?://)?(www\\\\.)?pixiv\\\\.net(/ajax)?/users?/\\\\d+\"\n let isAuthor = baseUrl.match(new RegExp(pattern))\n if (isAuthor) {\n java.log(`作者ID:${id}`)\n novelId = Object.keys(getAjaxJson(urlIP(urlUserWorkLatest(id))).body.novels).reverse()[0]\n }\n\n 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 novelId = getAjaxJson(urlIP(urlSeriesNovels(id, 30, 0))).body.thumbnails.novel[0].id\n } else {\n let pattern = \"(https?://)?(www\\\\.)?pixiv\\\\.net/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 (novelId) {\n java.log(`匹配小说ID:${novelId}`)\n res = getAjaxJson(urlIP(urlNovelDetailed(novelId)))\n }\n if (res.error === true) {\n java.log(`无法从 Pixiv 获取当前小说`)\n java.log(JSON.stringify(res))\n }\n return res\n }\n\n u.init(); u.logSource(); u.logEnvironment(); u.logSettings()\n if (u.settings.DEBUG) u.checkPixiv()\n util = u; java.put(\"util\", objStringify(u))\n}\n\nfunction checkMessageThread(checkTimes) {\n if (checkTimes === undefined) {\n checkTimes = Number(getFromCache(\"checkTimes\"))\n }\n if (checkTimes === 0 && isLogin()) {\n let latestMsg = getAjaxJson(urlIP(urlMessageThreadLatest(5)))\n if (latestMsg.error === true) {\n java.log(JSON.stringify(latestMsg))\n } else if (latestMsg.body.total >= 1) {\n let msg = latestMsg.body.message_threads.filter(item => item.thread_name === \"pixiv事務局\")[0]\n if (msg && new Date().getTime()- 1000*msg.modified_at <= 3*24*60*60*1000) { // 3天内进行提示\n sleepToast(`您于 ${timeFormat(1000*msg.modified_at)} 触发 Pixiv 【过度访问】,请修改密码并重新登录。\\n如已修改请忽略`, 3)\n sleepToast(`${msg.latest_content}`, 5)\n java.startBrowser(\"https://accounts.pixiv.net/password/change\",'修改密码')\n }\n }\n }\n putInCache(\"checkTimes\", checkTimes + 1, 4*60*60) // 缓存4h,每4h提醒一次\n // putInCache(\"checkTimes\", checkTimes + 1, 60) // 测试用,缓存60s,每分钟提醒一次\n // java.log(checkTimes + 1)\n}\n\n// 获取请求的user id方便其他ajax请求构造\nfunction getPixivUid() {\n // cache.delete(\"pixivUid\")\n let pixivUid = getFromCache(\"pixivUid\")\n let pixivCookie = getFromCache(\"pixivCookie\")\n if (!pixivUid && pixivCookie) {\n pixivUid = pixivCookie.match(new RegExp(\"\\\\d+\"))[0]\n java.log(`pixivUid: ${pixivUid}`)\n putInCache(\"pixivUid\", pixivUid)\n }\n return pixivUid\n}\n\nfunction getHeaders() {\n let headers = {\n \"accept\": \"application/json\",\n \"accept-encoding\": \"gzip, deflate, br, zstd\",\n \"accept-language\": \"zh-CN\",\n // \"content-type\": \"application/json; charset=utf-8\",\n // \"content-type\": \"application/x-www-form-urlencoded; charset=utf-8\",\n \"origin\": \"https://www.pixiv.net\",\n \"Referer\": \"https://www.pixiv.net/\",\n // \"sec-ch-ua\": `\"Not/A)Brand\";v=\"8\", \"Chromium\";v=\"132\", \"Google Chrome\";v=\"132\"`,\n // \"sec-ch-ua-mobile\": \"?0\",\n // \"sec-ch-ua-platform\": \"Windows\",\n // \"sec-fetch-dest\": \"empty\",\n // \"sec-fetch-mode\": \"cors\",\n // \"sec-fetch-site\": \"same-origin\",\n \"user-agent\": getFromCache(\"userAgent\") || \"\",\n \"x-csrf-token\": getFromCache(\"pixivCsrfToken\") || \"\",\n \"Cookie\": getFromCache(\"pixivCookie\") || \"\"\n }\n putInCacheObject(\"pixivHeaders\", headers)\n return headers\n}\n\npublicFunc()\nif (result.code() === 200) {\n getPixivUid(); getWebViewUA(); util.getCookie(); util.getCsrfToken(); getHeaders()\n if (!util.settings.FAST) checkMessageThread() // 检测过度访问\n}\njava.getStrResponse(null, null)", "loginUi": "@js:\nlet pixivSettings = getFromCacheObject(\"pixivSettings\")\nif (!pixivSettings) pixivSettings = setDefaultSettings()\nlet pixivEnvironment = getFromCacheObject(\"pixivEnvironment\")\nlet number = { 1:\"1️⃣\", 2:\"2️⃣\", 3:\"3️⃣\", 4:\"4️⃣\", 5:\"5️⃣\" }\nconst BOOK = typeof book !== 'undefined' && !!book\n\nlet source = [\n {\"🅿️ 登录账号\": \"login()\" },\n {\"⚙️ 账号设置\": \"startPixivSettings()\" },\n {\"🔙 退出账号\": \"logout()\" },\n\n {\"🆙 更新书源\": \"updateSource()\" },\n {\"🔰 功能手册\": \"startGithubReadme()\" },\n {\"🐞 反馈问题\": \"startGithubIssue()\" },\n]\nlet settingsBase = []\nif (BOOK) {\n settingsBase = [\n {\"👀 书源设置\": `editSettings('SHOW_SETTINGS2')` },\n {\"👀 发现设置\": `editSettings('SHOW_DISCOVER2')` },\n {\"🔰 功能手册\": \"startGithubReadme()\" },\n {\"🔢 章节编号\": \"editSettings('ADD_CHAPTER_INDEX')\" },\n {\"❤️ 自动收藏\": \"editSettings('AUTO_LIKE_NOVELS')\" },\n {\"🐞 调试模式\": \"editSettings('DEBUG')\" },\n ]\n} else {\n settingsBase = [\n {\"🚫 ✈️ 直连模式\": \"editSettings('IPDirect')\" },\n {\"🚫 ⏩ 快速模式\": \"editSettings('FAST')\" },\n {\"🚫 🐞 调试模式\": \"editSettings('DEBUG')\" },\n {\"⚙️ 当前设置\": \"showSettings()\" },\n {\"👀 书源设置\": `editSettings('SHOW_SETTINGS')` },\n {\"👀 发现设置\": `editSettings('SHOW_DISCOVER')` },\n ]\n}\n\nlet novel = [\n {\"章节名称\": \"text\" },\n {\"❤️ ㊙️ 收藏本章\": \"novelBookmarkAdd()\" },\n {\"📃 🚫 追更系列\": \"seriesWatchFactory()\" },\n {\"❤️ 收藏系列\": \"novelsBookmarkAdd()\" },\n\n {\"🖤 取消收藏\": \"novelsBookmarkDelete()\" },\n {\"⭐️ ⚫️ 关注作者\": \"userFollowFactory()\"},\n {\"🚫 ⭕️ 屏蔽作者\": \"userBlock()\"},\n]\n\nif (pixivSettings.SHOW_QUESTION && BOOK) {\n let getNovelId = () => {\n try { return chapter.url.match(/novel\\/(\\d+)/)[1] } catch(e) {}\n try { return chapter.url.match(/\\d+/)[0] } catch(e) {}\n return 0\n }\n let getNovelData = (novelId) => {\n try { return getFromCacheObject(urlNovelDetailed(novelId)).body } catch(e) {}\n try { return getAjaxJson(urlNovelDetailed(novelId)).body } catch(e) {}\n return {}\n }\n\n let pixivUid = String(getFromCache(\"pixivUid\"))\n let novelData = getNovelData(getNovelId())\n let showChoices = novelData.pollData && pixivUid !== novelData.userId && !novelData.pollData.selectedValue\n if (pixivSettings.DEBUG) showChoices = !!novelData.pollData\n if (showChoices) {\n let choices = [{\"问卷调查\": \"text\"}]\n\n novelData.pollData.choices.forEach((choice, i) => {\n let emoji = number[i + 1]\n let key = `${emoji} 投票选项`\n let value = `novelPollAnswer(${i + 1})`\n choices.push({ [key] : value })\n })\n novel = novel.concat(choices)\n }\n}\n\nlet comment = [\n {\"文本框\": \"text\" },\n {\"✅ 发送评论\": \"novelCommentAdd()\" },\n {\"🗑️ 删除评论\": \"novelCommentDelete()\" },\n {\"🔄 刷新本章\": \"cleanCache()\" },\n\n {\"🚫 添加屏蔽\": \"blockAddFactory()\" },\n {\"⭕️ 删除屏蔽\": \"blockDeleteFactory()\" },\n {\"👀 查看屏蔽\": \"blockShowFactory()\" },\n\n {\"📌 喜欢标签\": \"likeTagsAdd()\" },\n {\"🗑️ 删除标签\": \"likeTagsDelete()\" },\n {\"👀 查看标签\": \"likeTagsShow()\" },\n\n {\"❤️ 他人收藏\": \"likeAuthorsAdd()\" },\n {\"🖤 取消收藏\": \"likeAuthorsDelete()\" },\n {\"👀 查看收藏\": \"likeAuthorsShow()\" },\n]\n\nlet settings = [\n {\"书源设置\": \"text\" },\n {\"💾 备份恢复\": \"backupRestore()\" },\n {\"🔧 默认设置\": \"setDefaultSettingsLoginUrl()\" },\n {\"📚 🚫 整合系列\": \"editSettings('COMBINE_NOVELS')\" },\n\n {\"🚫 👤 搜索作者\": \"editSettings('SEARCH_AUTHOR')\" },\n {\"🀄 🚫 繁简通搜\": \"editSettings('CONVERT_CHINESE')\" },\n {\"🚫 📖 更多简介\": \"editSettings('MORE_INFORMATION')\" },\n\n {\"🚫 🔢 章节编号\": \"editSettings('ADD_CHAPTER_INDEX')\" },\n {\"📅 🚫 更新时间\": \"editSettings('SHOW_UPDATE_TIME')\" },\n {\"🔗 🚫 原始链接\": \"editSettings('SHOW_ORIGINAL_LINK')\" },\n\n {\"📄 🚫 显示描述\": \"editSettings('SHOW_CAPTIONS')\" },\n {\"🖼️ 🚫 显示插图\": \"editSettings('SHOW_PICTURES')\" },\n {\"✅ 🚫 显示投票\": \"editSettings('SHOW_QUESTION')\" },\n\n {\"💬 🚫 显示评论\": \"editSettings('SHOW_COMMENTS')\" },\n {\"📚 🚫 恢复《》\": \"editSettings('REPLACE_TITLE_MARKS')\" },\n {\"❤️ 🚫 自动收藏\": \"editSettings('AUTO_LIKE_NOVELS')\" },\n\n {\"🚫 🖤 自动取消\": \"editSettings('AUTO_DISLIKE_NOVELS')\" },\n {\"🚫 ❤️ 隐藏收藏\": \"editSettings('HIDE_LIKE_NOVELS')\" },\n {\"🚫 📃 隐藏追更\": \"editSettings('HIDE_WATCHED_SERIES')\" },\n\n // {\"📚 🚫 整合系列\": \"editSettings('COMBINE_NOVELS')\" },\n // {\"🚫 ⏩ 快速模式\": \"editSettings('FAST')\" },\n // {\"🚫 🐞 调试模式\": \"editSettings('DEBUG')\" },\n // {\"🚫 ✈️ 直连模式\": \"editSettings('IPDirect')\" },\n\n // {\"⏳ 图片解析\": \"editSettings('PIC_SOURCE')\" },\n // {\"🔗 图片链接\": \"editSettings('PIC_LINK')\" },\n // {\"↔️ 图片大小\": \"editSettings('PIC_SIZE')\" },\n]\n\nlet discoverSettings = [\n {\"发现设置\": \"text\" },\n {\"🔍 当前发现\": \"showSettingsDiscover()\" },\n {\"🔞 默认发现\": \"editSettings('SHOW_ADULT')\" },\n {\"🆗 常规小说\": \"editSettings('SHOW_GENERAL')\" },\n\n {\"🔞 最新企划\": \"editSettings('SHOW_NEW_ADULT')\" },\n {\"🆗 最新企划\": \"editSettings('SHOW_NEW_GENERAL')\" },\n {\"🔞 排行榜单\": \"editSettings('SHOW_RANK_ADULT')\" },\n\n {\"🆗 排行榜单\": \"editSettings('SHOW_RANK_GENERAL')\" },\n {\"🔞 原创热门\": \"editSettings('SHOW_GENRE_ADULT')\" },\n {\"🆗 原创热门\": \"editSettings('SHOW_GENRE_GENERAL')\" },\n\n {\"📚 书源相关\": \"editSettings('SHOW_ABOUT')\" },\n {\"🦊 兽人小说\": \"editSettings('SHOW_FURRY')\" },\n {\"🐺 兽人作者\": \"updatePixivAuthors()\" },\n]\n\nlet li = []\nif (BOOK) {\n li = settingsBase.concat(novel).concat(comment)\n if (pixivSettings.SHOW_SETTINGS2) li = li.concat(settings)\n if (pixivSettings.SHOW_DISCOVER2) li = li.concat(discoverSettings)\n} else {\n li = source.concat(settingsBase)\n if (pixivSettings.SHOW_SETTINGS) li = li.concat(settings)\n if (pixivSettings.SHOW_DISCOVER) li = li.concat(discoverSettings)\n}\n\n// 处理按钮\nli.forEach(item => {\n item.name = Object.keys(item)[0]\n if (!pixivEnvironment?.IS_LEGADO_SIGMA) {\n item.name = item.name.replace(/🚫 |⚫️ |㊙️ /g, \"\")\n }\n let list = item.name.split(\" \")\n if (list.length === 1 ) {\n item.type = \"text\"\n } else if (list.length === 2) {\n item.type = \"button\"\n item.action = Object.values(item)[0]\n } else {\n item.name = list[list.length - 1]\n item.type = \"toggle\"\n item.default = `${list[0]} `\n list.length = list.length - 1\n item.chars = list.map(char => `${char} `)\n item.action = Object.values(item)[0]\n }\n delete item[Object.keys(item)[0]]\n // 添加格式\n if (item.type === \"button\" || item.type === \"toggle\") {\n item.style = {}\n item.style.layout_flexGrow = 1\n item.style.layout_flexBasisPercent = -1\n }\n})\n\nJSON.stringify(li)", "loginUrl": "function login() {\n sleepToast(\"🔄 正在检测登陆状态,请稍候\")\n if (isLogin()) {\n sleepToast(\"️🅿️ 登录账号\\n✅ 已经登录过账号了\\n\\n可以点击【🔙 退出账号】来切换账号\")\n return false\n }\n\n let resp = java.startBrowserAwait(`https://accounts.pixiv.net/login,\n {\"headers\": {\"User-Agent\": ${getWebViewUA()}}}`, '登录账号', false)\n if (resp.code() === 200) {\n getCsrfToken(); getCookie()\n return true\n } else {\n java.log(resp.code()); sleepToast(\"🅿️ 登录账号\\n\\n⚠️ 登录失败\")\n return false\n }\n}\n\nfunction logout() {\n removeCookie()\n java.startBrowser(\"https://www.pixiv.net/logout.php\", \"退出账号\")\n removeCookie(); removeLikeDataCache()\n sleepToast(`✅ 已退出当前账号\\n\\n退出后请点击右上角的 ✔️ 退出\\n\\n登录请点击【登录账号】进行登录`)\n}\n\nfunction removeCookie() {\n cookie.removeCookie('https://www.pixiv.net')\n cookie.removeCookie('https://accounts.pixiv.net')\n cookie.removeCookie('https://accounts.google.com')\n cookie.removeCookie('https://api.weibo.com')\n cache.delete(\"pixivUid\")\n cache.delete(\"pixivCookie\")\n cache.delete(\"pixivCsrfToken\")\n cache.delete(\"PixivHeaders\")\n}\n\nfunction removeCacheList(listName) {\n let list = getFromCacheObject(listName)\n list.forEach(item => cache.delete(`collect${item}`))\n if (listName !== \"blockAuthorList\") cache.delete(listName)\n}\n\nfunction removeLikeDataCache() {\n // 删除 likeNovels 与 watchedSeries\n removeCacheList(\"likeNovels\")\n removeCacheList(\"watchedSeries\")\n}\n\nfunction getCookie() {\n let pixivCookie = String(java.getCookie(\"https://www.pixiv.net/\", null))\n if (!isLogin()) return \"\"\n putInCache(\"pixivCookie\", pixivCookie, 60*60)\n return pixivCookie\n}\n\n// 获取 Csrf Token,以便进行收藏等请求\n// 获取方法来自脚本 Pixiv Previewer\n// https://github.com/Ocrosoft/PixivPreviewer\n// https://greasyfork.org/zh-CN/scripts/30766-pixiv-previewer/code\nfunction getCsrfToken() {\n let pixivCsrfToken = getFromCache(\"pixivCsrfToken\")\n if (!pixivCsrfToken) {\n let html = java.ajax(\"https://www.pixiv.net/\")\n try {\n pixivCsrfToken = html.match(/token\\\\\":\\\\\"([a-z0-9]{32})/)[1]\n putInCache(\"pixivCsrfToken\", pixivCsrfToken) // 与登录设备有关,无法存储 nul\n } catch (e) {\n pixivCsrfToken = null\n cache.delete(\"pixivCsrfToken\") // 与登录设备有关,无法存储 nul\n // sleepToast(\"⚠️ 未登录账号(pixivCsrfToken)\")\n }\n java.log(`pixivCsrfToken:\\n${pixivCsrfToken}`)\n }\n return pixivCsrfToken\n}\n\nfunction getNovelId(seriesId) {\n if (chapter) {\n try {\n return chapter.url.match(/novel\\/(\\d+)/)[1]\n } catch (e) {\n return chapter.url.match(/\\d+/)[0]\n }\n }\n\n if (!book.bookUrl.includes(\"series\")) {\n return book.bookUrl.match(/\\d+/)[0]\n } else {\n seriesId = book.bookUrl.match(/\\d+/)[0]\n }\n\n if (seriesId) {\n let novelIds = getFromCacheObject(`novelIds${seriesId}`)\n if (novelIds) {\n return getFromCacheObject(`novelIds${seriesId}`)[book.durChapterIndex]\n } else {\n return getAjaxJson(urlIP(urlSeriesNovelsTitles(seriesId)), true).body[book.durChapterIndex].id\n }\n }\n}\n\nfunction getNovel() {\n let environment = getFromCacheObject(\"pixivEnvironment\")\n if (environment.IS_LEGADO_SIGMA) {\n try {\n let novel = {}\n if (book.bookUrl.includes(\"series\")) {\n novel.seriesId = book.bookUrl.match(/\\d+/)[0]\n novel.seriesTitle = book.name\n novel.id = getNovelId(novel.seriesId)\n novel.title = book.durChapterTitle\n } else {\n novel.seriesId = 0\n novel.seriesTitle = \"\"\n novel.id = book.bookUrl.match(/\\d+/)[0]\n novel.title = book.name\n }\n novel.author = novel.userName = book.author.replace(\"@\", \"\")\n let resp = getAjaxJson(urlIP(urlNovelDetailed(novel.id))).body\n novel.userId = resp.userId\n novel.question = resp?.pollData?.question || \"\"\n // java.log(JSON.stringify(novel))\n return novel\n } catch (e) {\n // 无法阻止后续函数在日志中报错\n return sleepToast(\"🔰 功能提示\\n\\n⚠️ 请在【小说正文】使用该功能\")\n }\n } else { // 兼容用\n return getFromCacheObject(\"novel\")\n }\n}\n\nfunction getPostBody(url, body, headers) {\n if (headers === undefined) headers = getFromCacheObject(\"pixivHeaders\")\n // if (headers === undefined) headers = getFromCacheObject(\"headers\")\n if (isJsonString(body)) {\n headers[\"content-type\"] = \"application/json; charset=utf-8\"\n } else if (typeof body === \"string\") {\n headers[\"content-type\"] = \"application/x-www-form-urlencoded; charset=utf-8\"\n }\n\n let settings = getFromCacheObject(\"pixivSettings\")\n if (settings.IPDirect) {\n url = url.replace(\"http://\", \"https://\").replace(\"www.pixiv.net\", \"210.140.139.155\")\n headers[\"Host\"] = \"www.pixiv.net\"\n }\n try {\n java.log(`getPostBody(${url}, ${body}, ${headers})`)\n // java.log(`getPostBody(${url}, ${body}, ${JSON.stringify(headers)})`)\n return JSON.parse(java.post(url, body, headers).body())\n } catch (e) {\n e = String(e)\n // sleepToast(e)\n // sleepToast(JSON.stringify(headers))\n if (e.includes(\"400\")) sleepToast(`📤 getPostBody\\n\\n⚠️ 缺少 headers`, 1)\n // else if (e.includes(\"401\")) sleepToast(`📤 getPostBody\\n\\n⚠️ 缺少 cookie 或 cookie 过期`, 1)\n // else if (e.includes(\"403\")) sleepToast(`📤 getPostBody\\n\\n⚠️ 缺少 cookie 或 cookie 过期`, 1)\n else if (e.includes(\"404\")) sleepToast(`📤 getPostBody\\n\\n⚠️ 404 缺少 pixivCsrfToken `, 1)\n else if (e.includes(\"422\")) sleepToast(`📤 getPostBody\\n\\n⚠️ 请求信息有误`, 1)\n return {error: true, errMsg:e}\n }\n}\n\nfunction novelBookmarkAdd() {\n let restrict = 0\n let novel = getNovel()\n let novelObj = getAjaxJson(urlNovelDetailed(novel.id), true)\n if (novelObj.body.bookmarkData && novelObj.body.bookmarkData.private === false) restrict = 1\n let resp = getPostBody(\n \"https://www.pixiv.net/ajax/novels/bookmarks/add\",\n JSON.stringify({\"novel_id\": novel.id, \"restrict\": restrict, \"comment\":\"\", \"tags\":[]})\n )\n if (resp.error === true) {\n sleepToast(`❤️ 收藏小说\\n\\n⚠️ 收藏【${novel.title}】失败`)\n shareFactory(\"novel\")\n } else {\n putInCacheObject(`collect${novel.id}`, resp.body)\n let likeNovels = getFromCacheObject(\"likeNovels\")\n likeNovels.push(Number(novel.id))\n putInCacheObject(\"likeNovels\", likeNovels)\n\n let novelObj = getAjaxJson(urlNovelDetailed(novel.id))\n novelObj.body.isBookmark = true\n putInCacheObject(urlNovelDetailed(novel.id), novelObj, cacheSaveSeconds)\n }\n\n if (restrict === 1) {\n sleepToast(`㊙️ 私密收藏\\n\\n✅ 已私密收藏\\n${novel.title}`)\n } else {\n sleepToast(`❤️ 公开收藏\\n\\n✅ 已公开收藏\\n${novel.title}`)\n }\n}\n\nfunction getNovelBookmarkId(novelId) {\n let bookmarkId = getFromCacheObject(`collect${novelId}`)\n if (bookmarkId === null) {\n try {\n bookmarkId = getAjaxJson(urlNovelBookmarkData(novelId), true).body.bookmarkData.id\n } catch (e) {\n bookmarkId = 0\n }\n }\n return bookmarkId\n}\n\nfunction novelBookmarkDelete() {\n let novel = getNovel()\n let bookmarkId = getNovelBookmarkId(novel.id)\n if (bookmarkId === 0) return sleepToast(`🖤 取消收藏\\n\\n✅ 已经取消收藏\\n${novel.title}`)\n\n let resp = getPostBody(\n \"https://www.pixiv.net/ajax/novels/bookmarks/delete\",\n `del=1&book_id=${bookmarkId}`\n )\n if (resp.error === true) {\n sleepToast(`🖤 取消收藏\\n\\n⚠️ 取消收藏失败\\n${novel.title}`)\n shareFactory(\"novel\")\n } else {\n cache.delete(`collect${novel.id}`)\n sleepToast(`🖤 取消收藏\\n\\n✅ 已经取消收藏\\n${novel.title}`)\n\n let likeNovels = getFromCacheObject(\"likeNovels\")\n likeNovels = likeNovels.filter(item => item !== Number(novel.id))\n putInCacheObject(\"likeNovels\", likeNovels)\n\n let novelObj = getAjaxJson(urlNovelDetailed(novel.id))\n novelObj.body.isBookmark = false\n putInCacheObject(urlNovelDetailed(novel.id), novelObj, cacheSaveSeconds)\n }\n}\n\nfunction novelsBookmarkDelete() {\n let novel = getNovel()\n if (!isLongClick) {\n if (!novel.seriesId) sleepToast(`🖤 取消收藏\\n\\n正在取消收藏【本章】`)\n else sleepToast(`🖤 取消收藏\\n\\n正在取消收藏【本章】\\n长按可取消收藏【整个系列】`)\n return novelBookmarkDelete(0)\n }\n if (isLongClick && !novel.seriesId) {\n return (`🖤 取消收藏系列\\n\\n⚠️ 【${novel.title}】非系列小说`)\n }\n sleepToast(`🖤 取消收藏系列\\n\\n🔄 正在取消收藏系列,请稍后……`, 2)\n\n let bookmarkIds = []\n let novelIds = getFromCacheObject(`novelIds${novel.seriesId}`)\n novelIds.forEach(novelId => {\n let bookmarkId = getNovelBookmarkId(novelId)\n if (bookmarkId) bookmarkIds.push(getNovelBookmarkId(novelId))\n })\n let resp = getPostBody(\n \"https://www.pixiv.net/ajax/novels/bookmarks/remove\",\n JSON.stringify({\"bookmarkIds\": bookmarkIds})\n )\n if (resp.error === true) {\n sleepToast(`🖤 取消收藏系列\\n\\n⚠️ 取消收藏【${novel.seriesTitle}】的篇目失败`, 2)\n shareFactory(\"series\")\n } else {\n sleepToast(`🖤 取消收藏系列\\n\\n✅ 已取消收藏【${novel.seriesTitle}】的全部篇目`)\n novelIds.forEach(novelId => {cache.delete(`collect${novelId}`)})\n\n let likeNovels = getFromCacheObject(\"likeNovels\")\n likeNovels = likeNovels.filter(item => !novelIds.includes(Number(item)))\n putInCacheObject(\"likeNovels\", likeNovels)\n\n novelIds.forEach(novelId => {\n let novelObj = getAjaxJson(urlNovelDetailed(novelId))\n novelObj.body.isBookmark = false\n putInCacheObject(urlNovelDetailed(novelId), novelObj, cacheSaveSeconds)\n })\n }\n}\n\nfunction novelsBookmarkAdd() {\n let novel = getNovel()\n if (!novel.seriesId) {\n sleepToast(`❤️ 收藏系列\\n\\n⚠️ 【${novel.title}】非系列小说,现已收藏本篇小说`)\n return novelBookmarkAdd(0)\n } else {\n sleepToast(`❤️ 收藏系列\\n\\n🔄 正在收藏系列【${novel.seriesTitle}】,请稍后……`, 2)\n }\n\n let novelIds = getFromCacheObject(`novelIds${novel.seriesId}`)\n let likeNovels = getFromCacheObject(\"likeNovels\")\n if (likeNovels === null) likeNovels = []\n novelIds.forEach(novelId => {\n if (likeNovels && !likeNovels.includes(Number(novelId))) {\n sleep(0.5 * 1000 * Math.random())\n let resp = getPostBody(\n \"https://www.pixiv.net/ajax/novels/bookmarks/add\",\n JSON.stringify({\"novel_id\": novelId, \"restrict\": 0, \"comment\": \"\", \"tags\": []})\n )\n\n if (resp.error === true) {\n sleepToast(`❤️ 收藏系列\\n\\n⚠️ 收藏【${novelId}】失败`)\n shareFactory(\"series\")\n } else if (resp.body === null) {\n // sleepToast(`❤️ 收藏小说\\n\\n✅ 已经收藏【${novel.title}】了`)\n } else {\n putInCacheObject(`collect${novelId}`, resp.body)\n likeNovels.push(Number(novelId))\n\n let novelObj = getAjaxJson(urlNovelDetailed(novelId))\n novelObj.body.isBookmark = true\n putInCacheObject(urlNovelDetailed(novelId), novelObj, cacheSaveSeconds)\n }\n }\n })\n putInCacheObject(\"likeNovels\", likeNovels)\n sleepToast(`❤️ 收藏系列\\n\\n✅ 已经收藏【${novel.seriesTitle}】全部章节`)\n}\n\nfunction novelMarker(page) {\n if (page === undefined) page = 1\n let novel = getNovel()\n let lastMarker = getFromCacheObject(`marker${novel.id}`)\n if (lastMarker === true) page = 0\n\n let resp = getPostBody(\n \"https://www.pixiv.net/novel/rpc_marker.php\",\n `mode=save&i_id=${novel.id}&u_id=${getFromCacheObject(\"pixivUid\")}&page=${page}`\n )\n java.log(`mode=save&i_id=${novel.id}&u_id=${getFromCacheObject(\"pixivUid\")}&page=${page}`)\n if (resp.error === true) {\n sleepToast(\"🏷️ 添加书签\\n\\n⚠️ 操作失败\", 1)\n shareFactory(\"novel\")\n } else if (lastMarker === true) {\n putInCache(`marker${novel.id}`, false)\n sleepToast(`🏷️ 添加书签\\n\\n✅ 已删除书签`)\n } else {\n putInCache(`marker${novel.id}`, true)\n sleepToast(`🏷️ 添加书签\\n\\n✅ 已加入书签`)\n }\n}\n\nfunction seriesWatch() {\n let novel = getNovel()\n let resp = getPostBody(\n `https://www.pixiv.net/ajax/novel/series/${novel.seriesId}/watch`,\n \"{}\"\n )\n if (resp.error === true) {\n sleepToast(`📃 追更系列\\n\\n⚠️ 追更【${novel.seriesTitle}】失败`, 1)\n shareFactory(\"series\")\n } else {\n putInCache(`watch${novel.seriesId}`, true)\n sleepToast(`📃 追更系列\\n\\n✅ 已追更【${novel.seriesTitle}】`)\n\n let watchedSeries = getFromCacheObject(\"watchedSeries\")\n watchedSeries.push(Number(novel.seriesId))\n putInCacheObject(\"watchedSeries\", watchedSeries)\n\n let novelObj = getAjaxJson(urlSeriesDetailed(novel.seriesId))\n novelObj.body.isWatched = true\n putInCacheObject(urlSeriesDetailed(novel.seriesId), novelObj, cacheSaveSeconds)\n }\n}\n\nfunction seriesUnWatch() {\n let novel = getNovel()\n let resp = getPostBody(\n `https://www.pixiv.net/ajax/novel/series/${novel.seriesId}/unwatch`,\n \"{}\"\n )\n if (resp.error === true) {\n sleepToast(`📃 追更系列\\n\\n⚠️ 取消追更【${novel.seriesTitle}】失败`, 1)\n shareFactory(\"series\")\n } else {\n cache.delete(`watch${novel.seriesId}`)\n sleepToast(`📃 追更系列\\n\\n✅ 已取消追更【${novel.seriesTitle}】`)\n\n let watchedSeries = getFromCacheObject(\"watchedSeries\")\n watchedSeries = watchedSeries.filter(item => item !== Number(novel.seriesId))\n putInCacheObject(\"watchedSeries\", watchedSeries)\n\n let novelObj = getAjaxJson(urlSeriesDetailed(novel.seriesId))\n novelObj.body.isWatched = false\n putInCacheObject(urlSeriesDetailed(novel.seriesId), novelObj, cacheSaveSeconds)\n }\n}\n\nfunction seriesWatchFactory(code) {\n if (code === undefined) code = 1\n let novel = getNovel()\n if (!novel.seriesId) {\n return sleepToast(`📃 追更系列\\n\\n⚠️ 【${novel.title}】非系列小说,无法加入追更列表`)\n }\n\n let lastStatus = getFromCacheObject(`watch${novel.seriesId}`)\n if (lastStatus === true) code = 0\n if (code === 0) seriesUnWatch()\n else if (code === 1) seriesWatch()\n}\n\nfunction userFollow(restrict) {\n if (restrict === undefined) restrict = 0\n let novel = getNovel()\n let resp = getPostBody(\n \"https://www.pixiv.net/bookmark_add.php\",\n `mode=add&type=user&user_id=${novel.userId}&tag=\"\"&restrict=${restrict}&format=json`\n )\n if (resp.error) {\n sleepToast(`⭐️ 关注作者\\n\\n⚠️ 关注【${novel.userName}】失败\\n已打开浏览器,请(上滑)手动关注`, 1)\n java.startBrowserAwait(`${urlUserUrl(novel.userId)},\n {\"User-Agent\": ${getWebViewUA()}}}`, `关注${novel.userName}`, false)\n }\n sleepToast(`⭐️ 关注作者\\n\\n✅ 已关注【${novel.userName}】`)\n}\n\nfunction userUnFollow() {\n let novel = getNovel()\n let resp = getPostBody(\n \"https://www.pixiv.net/rpc_group_setting.php\",\n `mode=del&type=bookuser&id=${novel.userId}`\n )\n if (resp.error === true) {\n sleepToast(`⭐️ 关注作者\\n\\n⚠️ 取消关注【${novel.userName}】失败`, 1)\n shareFactory(\"author\")\n } else {\n sleepToast(`⭐️ 关注作者\\n\\n✅ 已取消关注【${novel.userName}】`)\n }\n}\n\nfunction userFollowFactory() {\n let novel = getNovel()\n let lastStatus = getAjaxJson(urlUserDetailed(novel.userId), true).body.isFollowed\n if (lastStatus) userUnFollow()\n else userFollow()\n}\n\nfunction userBlock() {\n let authors = getFromCacheObject(\"blockAuthorList\")\n if (!authors) authors = []\n let authorsMap = getFromCacheMap(\"blockAuthorMap\")\n if (!authorsMap || authorsMap.size === 0) {\n authorsMap = new Map()\n authors.forEach(author => {\n authorsMap.set(author, getAjaxJson(urlUserDetailed(author)).body.name)\n })\n }\n\n let novel = getNovel()\n if (authorsMap.has(String(novel.userId))) {\n authorsMap.delete(String(novel.userId))\n sleepToast(`🚫 屏蔽作者\\n\\n✅ 已取消屏蔽【${novel.userName}】\\n现已恢复显示其小说`)\n } else if (!!novel.userId) {\n authorsMap.set(String(novel.userId), novel.userName)\n sleepToast(`🚫 屏蔽作者\\n\\n✅ 本地已屏蔽【${novel.userName}】\\n今后不再显示其小说`)\n }\n\n authors = Array.from(authorsMap.keys())\n putInCacheObject(\"blockAuthorList\", authors)\n putInCacheMap(\"blockAuthorMap\", authorsMap)\n // source.setVariable(authors.toString())\n // sleepToast(JSON.stringify(authors))\n}\n\n// 拆分长评论\nfunction splitComments(text) {\n if (!text) return []\n let limit = 140\n\n // 1. 预留序号空间(例如 \" (10/10)\" 占 8 个字符,预留 10 个以防万一)\n const reservedSpace = 10\n const safeLimit = limit - reservedSpace\n\n // 2. 核心拆分逻辑\n let chars = Array.from(text.trim())\n let tempSegments = []\n\n const strongPunc = /[。!?…\\uff0e\\uff01\\uff1f!?.…]/ // 强断句标点\n const weakPunc = /[\\uff0c\\uff1b,;]/ // 弱断句标点\n\n while (chars.length > 0) {\n if (chars.length <= safeLimit) {\n tempSegments.push(chars.join('').trim())\n break\n }\n\n let chunk = chars.slice(0, safeLimit)\n let splitIndex = -1\n\n // 优先级 1: 换行符\n for (let i = chunk.length - 1; i >= 0; i--) {\n if (chunk[i] === '\\n') {\n splitIndex = i\n break\n }\n }\n\n // 优先级 2: 强标点(。!?等)\n if (splitIndex === -1) {\n for (let i = chunk.length - 1; i >= 0; i--) {\n if (strongPunc.test(chunk[i])) {\n splitIndex = i\n break\n }\n }\n }\n\n // 优先级 3: 弱标点(仅在没找到强标点时使用逗号)\n if (splitIndex === -1) {\n for (let i = chunk.length - 1; i >= 0; i--) {\n if (weakPunc.test(chunk[i])) {\n splitIndex = i\n break\n }\n }\n }\n\n // 兜底: 硬截断\n if (splitIndex === -1) {\n splitIndex = safeLimit - 1\n }\n\n // 截取并清理\n let segment = chars.slice(0, splitIndex + 1).join('').trim();\n if (segment) tempSegments.push(segment)\n\n // 移除已处理字符并跳过开头的空白\n chars = chars.slice(splitIndex + 1);\n while (chars.length > 0 && (chars[0] === '\\n' || chars[0] === ' ')) {\n chars.shift()\n }\n }\n\n // 3. 注入序号\n const total = tempSegments.length;\n if (total <= 1) return tempSegments;\n return tempSegments.map((content, i) => `${content} (${i + 1}/${total})`).reverse();\n}\n\nfunction novelCommentAdd() {\n let resp, novel = getNovel()\n let userId = getFromCacheObject(\"pixivUid\")\n let comment = String(result.get(\"文本框\")).trim()\n if (comment === \"\") {\n return sleepToast(`✅ 发送评论\\n⚠️ 请在【文本框】内输入评论\\n\\n输入【评论内容;评论ID】可回复该条评论,如【非常喜欢;123456】`)\n }\n\n let comments = splitComments(comment)\n if (comments.length >= 2) sleepToast(\"✅ 发送评论\\n\\n正在拆分长评论,即将逐条发送\")\n comments.forEach(comment => {\n sleep(0.5 * Math.random())\n let matched = comment.match(RegExp(/(;|;\\s*)\\d{8,}/))\n if (matched) {\n let commentId = comment.match(new RegExp(/;(\\d{8,})/))[1]\n comment = comment.replace(new RegExp(`(;|;\\s*)${commentId}`), \"\")\n resp = getPostBody(\n \"https://www.pixiv.net/novel/rpc/post_comment.php\",\n `type=comment&novel_id=${novel.id}&author_user_id=${userId}&comment=${encodeURI(comment)}&parent_id=${commentId}`)\n } else {\n resp = getPostBody(\n \"https://www.pixiv.net/novel/rpc/post_comment.php\",\n `type=comment&novel_id=${novel.id}&author_user_id=${userId}&comment=${encodeURI(comment)}`\n )\n }\n\n if (resp.error === true) {\n sleepToast(\"✅ 发送评论\\n\\n⚠️ 评论失败\", 1)\n shareFactory(\"novel\")\n } else {\n sleepToast(`✅ 发送评论\\n\\n✅ 已在【${novel.title}】发布评论:\\n${comment}`, 1)\n }\n })\n try {java.refreshContent()} catch(err) {}\n if (comments.length >= 2) sleepToast(\"✅ 发送评论\\n\\n✅ 长评论已发送完毕\", 1)\n}\n\nfunction getNovelCommentID(novelId, commentText) {\n let list = [], uid = String(getFromCacheObject(\"pixivUid\"))\n let resp = getAjaxJson(urlNovelComments(novelId, 0, 50), true)\n resp.body.comments.forEach(comment => {\n if (comment.userId === uid && comment.comment === commentText) list.push(comment.id)\n\n if (comment.hasReplies === true) {\n let resp = getAjaxJson(urlNovelCommentsReply(comment.id, 1), true)\n resp.body.comments.forEach(comment => {\n if (comment.userId === uid && comment.comment === commentText) list.push(comment.id)\n })\n }\n })\n // java.log(JSON.stringify(list))\n return list\n}\n\nfunction novelCommentDelete() {\n let commentIDs, novel = getNovel()\n let comment = String(result.get(\"文本框\")).trim()\n if (comment === \"\") {\n return sleepToast(`🗑 删除评论\\n⚠️ 请在【文本框】内输入需要删除的【评论ID】,以分号间隔\\n或输入需要删除的【评论内容】\\n\\n如:【123;456;789】\\n或【模拟评论内容】`)\n }\n\n if (RegExp(/[;;]/).test(comment)) {\n commentIDs = comment.split(/[;;]/)\n .map(item => item.trim()) // 去除每个元素前后的空格\n .filter(item => item !== \"\") // 过滤掉因为末尾分号产生的空项\n } else if (RegExp(/\\d{8,}/).test(comment)) {\n let matched = comment.match(/\\d{8,}/g)\n commentIDs = Array.from(matched || [])\n } else {\n commentIDs = getNovelCommentID(novel.id, comment)\n // java.log(JSON.stringify(commentIDs))\n if (commentIDs.length === 0) {\n return sleepToast(`🗑 删除评论\\n\\n⚠️ 未能找到这条评论\\n请检查是否有错别字或标点符号是否一致`)\n }\n }\n\n commentIDs.forEach(commentID =>{\n sleep(0.5 * 1000 * Math.random())\n let resp = getPostBody(\n \"https://www.pixiv.net/novel/rpc_delete_comment.php\",\n `i_id=${novel.id}&del_id=${commentID}`\n )\n // java.log(JSON.stringify(resp))\n if (resp.error === true) {\n sleepToast(\"🗑 删除评论\\n\\n⚠️ 评论删除失败\", 1)\n shareFactory(\"novel\")\n } else {\n let isCommentText = !RegExp(/[;;]/).test(comment) && !RegExp(/\\d{8,}/).test(comment)\n let toastComment = isCommentText ? comment : commentID\n sleepToast(`🗑 删除评论\\n\\n✅ 已在【${novel.title}】删除评论:\\n${toastComment}`, 1)\n }\n })\n try {java.refreshContent()} catch(err) {}\n if (comments.length >= 2) sleepToast(\"🗑 删除评论\\n\\n✅ 评论已删除完毕\", 1)\n}\n\nfunction novelPollAnswer(choiceId) {\n let novel = getNovel()\n let resp = getPostBody(\n `https://www.pixiv.net/ajax/novel/${novel.id}/poll/answer`,\n JSON.stringify({\"choice_id\": choiceId})\n )\n // 200 成功,401 未登录,403 重复投票,400 选项超过范围\n if (resp.error === true) {\n if (resp.errMsg.includes(\"401\")) {\n sleepToast(`📃 小说投票\\n\\n⚠️ 请先登录,再投票哦`, 3)\n } else if (resp.errMsg.includes(\"403\")) {\n cleanCache(0)\n sleepToast(`📃 小说投票\\n\\n✅ 你已经投过票了`, 3)\n } else {\n sleepToast(`📃 小说投票\\n\\n⚠️ 投票失败`, 3)\n shareFactory(\"novel\")\n }\n } else {\n cleanCache(0)\n sleepToast(`📃 小说投票\\n\\n✅ 投票成功`)\n }\n}\n\nlet blockType = {\n \"Caption\": \"📃 简介屏蔽列表\",\n \"Tags\": \"#️ 标签屏蔽列表\",\n \"Authors\": \"👤 作者屏蔽列表\"\n}\n\nfunction printAuthorMap(map) {\n let text = \"\"\n map.forEach((value, key) => {\n text += `@${value} ${key}\\n`\n })\n return text.trim()\n}\n\nfunction blockShowFactory() {\n let keys = Object.keys(blockType)\n let key = getFromCacheObject(\"blockType\")\n\n // 切换屏蔽列表\n let currentIndex = keys.indexOf(key)\n let nextIndex = (currentIndex + 1) % keys.length\n key = keys[nextIndex]\n putInCacheObject(\"blockType\", key)\n\n if (key === \"Authors\") {\n let words = printAuthorMap(getFromCacheMap(\"blockAuthorMap\"))\n if (!words) words = \"\"\n sleepToast(`👀 查看屏蔽\\n${blockType[key]}\\n\\n${words}`, 2)\n } else {\n let words = getFromCacheObject(`block${key}`)\n // if (!words) words = getFromCacheObject(`${key.toLowerCase()}BlockWords`)\n if (!words) words = []\n sleepToast(`👀 查看屏蔽\\n${blockType[key]}\\n\\n${words.join(\"\\n\")}`, 2)\n putInCacheObject(`block${key}`, words)\n }\n}\n\nfunction blockWordAdd() {\n let method = getFromCacheObject(\"blockType\")\n let blockTypeName = blockType[method]\n let blockWords = getFromCacheObject(`block${method}`)\n // if (!blockWords) getFromCacheObject(`${method.toLowerCase()}BlockWords`)\n if (!blockWords) blockWords = []\n\n let word = String(result.get(\"文本框\")).trim()\n if (word === \"\") {\n sleepToast(`🚫 添加屏蔽\\n${blockTypeName}\\n\\n⚠️ 请在【文本框】内输入屏蔽词`)\n } else if (blockWords.includes(word)) {\n sleepToast(`🚫 添加屏蔽\\n${blockTypeName}\\n\\n✅ 【${word}】已经加入屏蔽列表了`)\n } else {\n blockWords.push(word)\n putInCacheObject(`block${method}`, blockWords)\n sleepToast(`🚫 添加屏蔽\\n${blockTypeName}\\n\\n✅ 已将【${word}】加入屏蔽列表中`)\n }\n}\n\nfunction blockWordDelete() {\n let method = getFromCacheObject(\"blockType\")\n let blockTypeName = blockType[method]\n let blockWords = getFromCacheObject(`block${method}`)\n // if (!blockWords) getFromCacheObject(`${method.toLowerCase()}BlockWords`)\n if (!blockWords) blockWords = []\n\n let word = String(result.get(\"文本框\")).trim()\n if (word === \"\") {\n sleepToast(`⭕️ 删除屏蔽\\n${blockTypeName}\\n\\n⚠️ 请在【文本框】内输入屏蔽词`)\n } else if (!blockWords.includes(word)) {\n sleepToast(`⭕️ 删除屏蔽\\n${blockTypeName}\\n\\n⚠️ 【${word}】不在屏蔽列表\\n请检查是否有错别字或标点符号是否一致`)\n } else {\n blockWords = blockWords.filter(item => item !== word)\n putInCacheObject(`block${method}`, blockWords)\n sleepToast(`⭕️ 删除屏蔽\\n${blockTypeName}\\n\\n✅ 已删除屏蔽词【${word}】`)\n }\n}\n\nfunction blockAuthorAdd() {\n let method = getFromCacheObject(\"blockType\")\n let blockTypeName = blockType[method]\n let blockAuthors = getFromCacheMap(`blockAuthorMap`)\n\n let word = String(result.get(\"文本框\")).trim()\n if (word === \"\") {\n sleepToast(`🚫 添加屏蔽\\n${blockTypeName}\\n\\n⚠️ 请在【文本框】内输入【作者ID】\\n或使用上方 🚫 屏蔽作者`)\n } else if (blockAuthors.has(word)) {\n let text = `${blockAuthors.get(word)} ${word}`\n sleepToast(`🚫 添加屏蔽\\n${blockTypeName}\\n\\n✅ 【${text}】已经加入屏蔽列表了`)\n }\n // 输入纯数字,添加对应ID的作者\n else if (!isNaN(word)) {\n let user = getAjaxJson(urlUserDetailed(word)).body\n blockAuthors.set(user.userId, user.name)\n let text = `@${user.name} ${user.userId}`\n sleepToast(`🚫 添加屏蔽\\n${blockTypeName}\\n\\n✅ 已将【${text}】加入屏蔽列表中`)\n }\n else if (word) {\n sleepToast(`🚫 添加屏蔽\\n${blockTypeName}\\n\\n⚠️ 输入【用户ID】可屏蔽该作者`)\n }\n putInCacheMap(`blockAuthorMap`, blockAuthors)\n}\n\nfunction blockAuthorDelete() {\n let method = getFromCacheObject(\"blockType\")\n let blockTypeName = blockType[method]\n let blockAuthors = getFromCacheMap(`blockAuthorMap`)\n\n let word = String(result.get(\"文本框\")).trim()\n if (word === \"\") {\n sleepToast(`⭕️ 删除屏蔽\\n${blockTypeName}\\n\\n⚠️ 请在【文本框】内输入【作者ID】\\n或使用上方 🚫 屏蔽作者`)\n }\n // 输入纯数字,删除对应ID的作者\n else if (!isNaN(word) && blockAuthors.has(word)) {\n let text = `@${blockAuthors.get(word)} ${word}`\n blockAuthors.delete(word)\n sleepToast(`⭕️ 删除屏蔽\\n${blockTypeName}\\n\\n✅ 已删除【${text}】`)\n }\n //作者名称\n else if (Array.from(blockAuthors.values()).includes(word)) {\n let index = Array.from(blockAuthors.values()).indexOf(word)\n let key = Array.from(blockAuthors.keys())[index]\n let text = `@${blockAuthors.get(key)} ${key}`\n blockAuthors.delete(key)\n sleepToast(`⭕️ 删除屏蔽\\n${blockTypeName}\\n\\n✅ 已删除【${text}】`)\n }\n else if (word) {\n sleepToast(`⭕️ 删除屏蔽\\n${blockTypeName}\\n\\n⚠️ 输入【用户ID】可屏蔽该作者`)\n }\n putInCacheMap(`blockAuthorMap`, blockAuthors)\n}\n\nfunction blockAddFactory() {\n if (getFromCacheObject(\"blockType\") === \"Authors\") return blockAuthorAdd()\n else return blockWordAdd()\n}\n\nfunction blockDeleteFactory() {\n if (getFromCacheObject(\"blockType\") === \"Authors\") return blockAuthorDelete()\n else return blockWordDelete()\n}\n\n\nfunction likeTagsShow() {\n let likeTags = getFromCacheObject(`likeTags`)\n if (likeTags === null) likeTags = []\n sleepToast(`👀 查看标签\\n📌 喜欢标签\\n\\n${likeTags.join(\"、\")}`, 5)\n}\n\nfunction likeTagsAdd() {\n let likeTags = getFromCacheObject(`likeTags`)\n if (likeTags === null) likeTags = []\n\n let word = String(result.get(\"文本框\")).trim()\n if (word === \"\") {\n sleepToast(`📌 喜欢标签\\n📌 添加标签\\n\\n⚠️ 请在【文本框】内直接输入标签内容`)\n } else if (word.startsWith(\"@\") || word.startsWith(\"@\")) {\n sleepToast(`📌 喜欢标签\\n📌 添加标签\\n\\n⚠️ 仅支持添加【标签】\\n不支持添加 @作者名称`)\n } else if (word.startsWith(\"#\") || word.startsWith(\"#\")) {\n sleepToast(`📌 喜欢标签\\n📌 添加标签\\n\\n⚠️ 仅支持添加【标签】\\n不支持添加 #标签名称`)\n } else if (likeTags.includes(word)) {\n sleepToast(`📌 喜欢标签\\n📌 添加标签\\n\\n✅ 【${word}】已经加入喜欢标签了\\n请于发现页刷新后查看`)\n } else {\n likeTags.push(word)\n putInCacheObject(`likeTags`, likeTags)\n sleepToast(`📌 喜欢标签\\n📌 添加标签\\n\\n✅ 已将【${word}】加入喜欢标签了`)\n try {source.refreshExplore()} catch (e) {}\n }\n}\n\nfunction likeTagsDelete() {\n let likeTags = getFromCacheObject(`likeTags`)\n if (likeTags === null) likeTags = []\n\n let word = String(result.get(\"文本框\")).trim()\n if (word === \"\") {\n sleepToast(`📌 喜欢标签\\n🗑 删除标签\\n\\n⚠️ 请在【文本框】内直接输入标签内容`)\n } else if (!likeTags.includes(word)) {\n sleepToast(`📌 喜欢标签\\n🗑 删除标签\\n\\n⚠️ 【${word}】不在喜欢标签\\n请检查是否有错别字`)\n } else {\n likeTags = likeTags.filter(item => item !== word)\n putInCacheObject(`likeTags`, likeTags)\n sleepToast(`📌 喜欢标签\\n🗑 删除标签\\n\\n✅ 已删除该标签【${word}】`)\n try {source.refreshExplore()} catch (e) {}\n }\n}\n\n\nfunction likeAuthorsShow() {\n let likeAuthors = getFromCacheMap(\"likeAuthorsMap\")\n if (likeAuthors.size === 0) likeAuthors = getFromCacheMap(\"likeAuthors\")\n\n let text = printAuthorMap(likeAuthors)\n sleepToast(`👀 查看收藏\\n❤️ 他人收藏\\n\\n${text.trim()}`, 2)\n putInCacheMap(`likeAuthorsMap`, likeAuthors)\n}\n\nfunction likeAuthorsAdd() {\n let likeAuthors = getFromCacheMap(\"likeAuthorsMap\")\n if (likeAuthors.size === 0) likeAuthors = getFromCacheMap(\"likeAuthors\")\n \n let word = String(result.get(\"文本框\")).trim()\n if (word.startsWith(\"@\") || word.startsWith(\"@\")) {\n return sleepToast(`❤️ 他人收藏\\n❤️ 添加收藏\\n\\n⚠️ 仅支持通过【作者ID】关注\\n不支持添加 @作者名称`)\n } else if (word.startsWith(\"#\") || word.startsWith(\"#\")) {\n return sleepToast(`❤️ 他人收藏\\n❤️ 添加收藏\\n\\n⚠️ 仅支持通过【作者ID】关注\\n不支持添加 #标签名称`)\n } else if (likeAuthors.has(word)) {\n let text = `${likeAuthors.get(word)} ${word}`\n sleepToast(`❤️ 他人收藏\\n❤️ 添加收藏\\n\\n✅ 【${text}】已经加入收藏列表了,请于发现页查看`)\n }\n\n // 无输入内容,添加当前小说的作者\n if (word === \"\") {\n let novel = getNovel()\n likeAuthors.set(String(novel.userId), novel.userName)\n let text = `@${novel.userName} ${novel.userId}`\n sleepToast(`❤️ 他人收藏\\n❤️ 添加收藏\\n\\n✅ 已将【${text}】加入他人收藏列表了,请于发现页查看\\n\\n📌 【文本框】内输入【用户ID】可关注其他用户的收藏`)\n }\n // 输入纯数字,添加对应ID的作者\n else if (!isNaN(word)) {\n let user = getAjaxJson(urlUserDetailed(word)).body\n likeAuthors.set(user.userId, user.name)\n let text = `@${user.name} ${user.userId}`\n sleepToast(`❤️ 他人收藏\\n️ 添加收藏\\n\\n✅ 已将【${text}】加入他人收藏列表了,请于发现页查看`)\n }\n\n else if (word) {\n sleepToast(`❤️ 他人收藏\\n❤️ 添加收藏\\n\\n📌 【文本框】内输入【用户ID】可关注其他用户的收藏`)\n }\n putInCacheMap(`likeAuthorsMap`, likeAuthors)\n try {source.refreshExplore()} catch (e) {}\n}\n\nfunction likeAuthorsDelete() {\n let likeAuthors = getFromCacheMap(\"likeAuthorsMap\")\n if (likeAuthors.size === 0) likeAuthors = getFromCacheMap(\"likeAuthors\")\n\n let word = String(result.get(\"文本框\")).trim()\n if (word.startsWith(\"@\") || word.startsWith(\"@\")) {\n return sleepToast(`❤️ 他人收藏\\n🖤 取消收藏\\n\\n⚠️ 仅支持通过【作者ID/作者名称】取关\\n不支持输入 @作者名称`)\n } else if (word.startsWith(\"#\") || word.startsWith(\"#\")) {\n return sleepToast(`❤️ 他人收藏\\n🖤 取消收藏\\n\\n⚠️ 仅支持通过【作者ID/作者名称】取关\\n不支持输入 #标签名称`)\n }\n\n if (word === \"\") {\n let novel = getNovel()\n likeAuthors.delete(novel.userId)\n let text = `@${novel.userName} ${novel.userId}`\n sleepToast(`❤️ 他人收藏\\n🖤 取消收藏\\n\\n✅ 已将【${text}】移出他人收藏列表了\\n\\n📌 【文本框】内输入【用户ID】可取消关注其他用户的收藏`)\n\n // 输入纯数字,删除对应ID的作者\n } else if (!isNaN(word) && likeAuthors.has(word)) {\n let text = `@${likeAuthors.get(word)} ${word}`\n likeAuthors.delete(word)\n sleepToast(`❤️ 他人收藏\\n🖤 取消收藏\\n\\n✅ 已取关【${text}】`)\n\n //作者名称\n } else if (Array.from(likeAuthors.values()).includes(word)) {\n let index = Array.from(likeAuthors.values()).indexOf(word)\n let key = Array.from(likeAuthors.keys())[index]\n let text = `@${likeAuthors.get(key)} ${key}`\n likeAuthors.delete(key)\n sleepToast(`❤️ 他人收藏\\n🖤 取消收藏\\n\\n✅ 已取关【${text}】`)\n }\n else if (word) {\n sleepToast(`❤️ 他人收藏\\n🖤 取消收藏\\n\\n📌 【文本框】内输入【用户ID】可取关其他用户的收藏`)\n }\n putInCacheMap(`likeAuthorsMap`, likeAuthors)\n try {source.refreshExplore()} catch (e) {}\n}\n\nfunction shareFactory(type) {\n let novel = getNovel()\n if (novel === undefined || novel === null) return sleepToast(\"⚠️ 请在小说阅读页面,使用本功能\")\n if (type.includes(\"author\")) {\n startBrowser(urlUserUrl(novel.userId), novel.userName)\n }\n else if (type.includes(\"novel\") || (!novel.seriesId)) {\n startBrowser(urlNovelUrl(novel.id), novel.title)\n }\n else if (type.includes(\"series\") && novel.seriesId) {\n startBrowser(urlSeriesUrl(novel.seriesId), novel.seriesTitle)\n }\n}\n\n// 用【备份恢复】恢复登录状态后,打开浏览器不清空原有登录状态\nfunction startPixivSettings() {\n let headers = getFromCacheObject(\"pixivHeaders\")\n headers[\"user-agent\"] = getFromCache(\"userAgent\") || \"\"\n headers[\"x-csrf-token\"] = getFromCache(\"pixivCsrfToken\") || \"\"\n headers[\"user-agent\"] = getFromCache(\"pixivCookie\") || \"\"\n\n java.startBrowser(`https://www.pixiv.net/settings/viewing, ${JSON.stringify({headers: headers})}`, \"账号设置\")\n}\nfunction startGithubReadme() {\n startBrowser(\"https://pixivsource.pages.dev/Pixiv\", \"使用指南\")\n}\nfunction startGithubIssue() {\n startBrowser(\"https://github.com/DowneyRem/PixivSource/issues\", \"反馈问题\")\n}\n\nfunction checkStatus(status) {\n if (eval(String(status)) === true) return \"❤️\"\n else return \"🖤\"\n}\n\nfunction readMeSearch() {\n return sleepToast(`🔍 搜索说明\\n\n 标签之间需要以【空格】间隔\n ➖ 排除标签:#标签1 -标签2\n 👤 作者专搜:@搜索作者名称\n #️ 标签专搜:#标签1 标签2 \n ⏬ 字数筛选1:#标签1 标签2 字数3k5\n ⏬ 字数筛选2:@作者的名称 字数3w5`.replace(\" \",\"\"), 5)\n}\n\nconst serachSettingsName = {\n \"SEARCH_AUTHOR\": \"🔍 搜索作者\",\n \"CONVERT_CHINESE\": \"🀄️ 繁简通搜\",\n}\nconst detailSettingsName = {\n \"MORE_INFORMATION\": \"📖 更多简介\",\n}\nconst catalogSettingsName = {\n \"ADD_CHAPTER_INDEX\": \"🔢 章节编号\",\n \"SHOW_UPDATE_TIME\": \"📅 更新时间\",\n \"SHOW_ORIGINAL_LINK\": \"🔗 原始链接\",\n}\nconst contentSettingsName = {\n \"SHOW_CAPTIONS\": \"📄 显示描述\",\n \"SHOW_PICTURES\": \"🖼️ 显示图片\",\n \"SHOW_QUESTION\": \"✅ 显示投票\",\n\n \"SHOW_COMMENTS\": \"💬 显示评论\",\n \"REPLACE_TITLE_MARKS\": \"📚 恢复《》\",\n}\nconst otherSettingsName = {\n \"AUTO_LIKE_NOVELS\": \"❤️ 自动收藏\",\n \"AUTO_DISLIKE_NOVELS\": \"🖤 自动取消\",\n\n \"HIDE_LIKE_NOVELS\": \"❤️ 隐藏收藏\",\n \"HIDE_WATCHED_SERIES\": \"📃 隐藏追更\",\n\n \"COMBINE_NOVELS\": \"📚 整合系列\",\n}\nconst modeSettingsName = {\n \"FAST\": \"⏩ 快速模式\",\n \"DEBUG\": \"🐞 调试模式\",\n \"IPDirect\": \"✈️ 直连模式\",\n}\nconst pictureSettingsName = {\n \"PIC_SOURCE\": \"⏳ 图片解析\",\n \"PIC_LINK\": \"🔗 图片链接\",\n \"PIC_SIZE\": \"↔️ 图片大小\",\n}\nconst novelSettingsName = Object.assign({},\n serachSettingsName,\n detailSettingsName,\n catalogSettingsName,\n contentSettingsName,\n otherSettingsName,\n modeSettingsName,\n // pictureSettingsName\n)\nconst fastSettingsName = {\n \"CONVERT_CHINESE\": \"🀄️ 繁简通搜\",\n \"SHOW_UPDATE_TIME\": \"📅 更新时间\",\n \"SHOW_ORIGINAL_LINK\": \"🔗 原始链接\",\n\n \"SHOW_PICTURES\": \"🖼️ 显示图片\",\n \"SHOW_QUESTION\": \"✅ 显示投票\",\n \"SHOW_COMMENTS\": \"💬 显示评论\",\n}\nconst IPDirectSettingsName = {\n \"SHOW_ORIGINAL_LINK\": \"🔗 原始链接\",\n}\nconst discoverSettingsName ={\n \"SHOW_ADULT\": \"🔞 默认发现\",\n \"SHOW_GENERAL\": \"🆗 常规小说\",\n \"SHOW_NEW_ADULT\": \"🔞 最新企划\",\n \"SHOW_NEW_GENERAL\": \"🆗 最新企划\",\n\n \"SHOW_RANK_ADULT\": \"🔞 排行榜单\",\n \"SHOW_RANK_GENERAL\": \"🆗 排行榜单\",\n \"SHOW_GENRE_ADULT\": \"🔞 原创热门\",\n \"SHOW_GENRE_GENERAL\": \"🆗 原创热门\",\n\n \"SHOW_ABOUT\": \"📚 书源相关\",\n \"SHOW_FURRY\": \"🦊 兽人小说\",\n}\nconst menuSettingsName = {\n \"SHOW_DISCOVER\": \"⚙️ 发现设置\\n(书源编辑界面)\",\n \"SHOW_SETTINGS\": \"⚙️ 书源设置\\n(书源编辑界面)\",\n \"SHOW_DISCOVER2\": \"⚙️ 发现设置\\n(小说阅读界面)\",\n \"SHOW_SETTINGS2\": \"⚙️ 书源设置\\n(小说阅读界面)\",\n}\nconst settingsName = Object.assign({},\n novelSettingsName,\n fastSettingsName,\n IPDirectSettingsName,\n discoverSettingsName,\n menuSettingsName\n)\n\nfunction statusMsg(status) {\n if (status === true) return \"✅ 已开启\"\n else if (status === false) return \"🚫 已关闭\"\n else if (status === undefined) return \"🈚️ 未设置\"\n}\n\n// 检测快速模式修改的4个设置\nfunction getSettingStatus(mode) {\n if (mode === undefined) mode = \"\"\n let keys = [], msgList = []\n let settings = getFromCacheObject(\"pixivSettings\")\n if (mode === \"FAST\") {\n keys = Object.keys(fastSettingsName)\n } else if (mode === \"IPDirect\") {\n keys = Object.keys(IPDirectSettingsName)\n } else if (mode.includes(\"DISCOVER\")) {\n keys = Object.keys(discoverSettingsName)\n } else {\n keys = Object.keys(novelSettingsName)\n }\n for (let i = 0; i < keys.length; i++) {\n msgList.push(`${statusMsg(settings[keys[i]])} ${settingsName[keys[i]]}`)\n if ((i+1) % 6 === 0) msgList.push(\"\\n\")\n }\n return msgList.join(\"\\n\").trim()\n}\n\nfunction showSettings() {\n sleepToast(`\\n⚙️ 当前设置\\n\\n${getSettingStatus()}`)\n}\nfunction showSettingsDiscover() {\n sleepToast(`\\n⚙️ 当前发现设置\\n\\n${getSettingStatus(\"DISCOVER\")}`)\n}\n\nfunction setDefaultSettingsLoginUrl() {\n setDefaultSettings()\n sleepToast(`\\n✅ 已恢复 🔧 默认设置\\n\\n${getSettingStatus()}`)\n refreshSettings(\"DEFAULT\")\n}\n\nfunction editSettings(settingName) {\n let msg, status\n let settings = getFromCacheObject(\"pixivSettings\")\n if (!settings) settings = setDefaultSettings()\n if (!!settings[settingName]) {\n status = settings[settingName] = !settings[settingName]\n } else {\n status = settings[settingName] = true\n }\n putInCacheObject(\"pixivSettings\", settings)\n\n if (settingName === \"FAST\") {\n checkSettings(settings)\n msg = `\\n\\n${statusMsg(status)} ${settingsName[settingName]}\\n\\n${getSettingStatus(settingName)}`\n\n } else if (settingName === \"IPDirect\") {\n if (settings.IPDirect && !isLogin()) {\n settings.IPDirect = false\n checkSettings(settings)\n msg = \"✈️ 直连模式\\n\\n✈️ 直连模式 需登录账号\\n当前未登录账号,现已关闭直连模式\"\n } else {\n checkSettings(settings)\n msg = `\\n\\n${statusMsg(status)} ${settingsName[settingName]}\\n\\n${getSettingStatus(settingName)}`\n }\n } else {\n msg = `\\n\\n${statusMsg(status)} ${settingsName[settingName]}`\n }\n\n sleepToast(msg)\n refreshSettings(settingName)\n}\n\nfunction refreshSettings(settingName) {\n if (settingName === \"DEFAULT\") {\n try { source.refreshExplore() } catch (e) {}\n try { java.refreshBookInfo() } catch(e) {}\n try { java.refreshBookToc() } catch(e) {}\n try { java.refreshContent() } catch(e) {}\n\n } else if (settingName in modeSettingsName) {\n try { java.reLoginView() } catch (e) {}\n try { source.refreshExplore() } catch (e) {}\n try { java.refreshBookToc() } catch(e) {}\n try { java.refreshContent() } catch(e) {}\n\n } else if (settingName in menuSettingsName) {\n try { java.reLoginView() } catch (e) {}\n\n } else if (settingName in discoverSettingsName) {\n try { source.refreshExplore() } catch (e) {}\n\n } else if (settingName in detailSettingsName) {\n try { java.refreshBookInfo() } catch(e) {}\n\n } else if (settingName in catalogSettingsName) {\n try { java.refreshBookToc() } catch(e) {}\n\n } else if (settingName in contentSettingsName) {\n try { java.refreshContent() } catch(e) {}\n\n } else if (settingName in pictureSettingsName) {\n try { java.refreshContent() } catch(e) {}\n }\n}\n\n\nfunction backupRestore() {\n let variable = String(result.get(\"书源设置\") || \"\").trim()\n if (!variable) variable = String(source.getVariable() || \"\").trim()\n\n if (variable === \"\") {\n let data = backupData()\n try {\n java.copyText(data)\n java.upLoginData({\"书源设置\": data})\n sleepToast(\"\\n💾 备份数据\\n\\n✅ 已导出书源数据 到剪贴板\")\n } catch(e) {\n try {\n source.putVariable(data)\n sleepToast(\"\\n💾 备份恢复\\n\\n✅ 已导出书源数据至 源变量\")\n } catch(e) {\n sleepToast(\"\\n💾 备份恢复\\n\\n⚠️ 书源导出数据失败\")\n }\n }\n }\n\n else if (isJsonString(variable)) {\n restoreData(JSON.parse(variable))\n sleepToast(\"\\n💾 恢复数据\\n\\n✅ 已导入书源数据\")\n try { java.upLoginData({\"书源设置\": \"\"})} catch(e) {}\n try { source.putVariable(\"\") } catch(e) {}\n } else {\n sleepToast(\"\\n💾 备份恢复\\n\\n⚠️ 输入数据出错,请检查数据格式(JSON)\")\n }\n}\n\nfunction backupData() {\n let data = {}\n // 账号相关\n data.pixivUid = getFromCache(\"pixivUid\")\n data.pixivCookie = getCookie()\n data.pixivCsrfToken = getCsrfToken()\n\n // 书源设置\n data.pixivSettings = getFromCacheObject(\"pixivSettings\")\n data.blockCaption = getFromCacheObject(\"blockCaption\")\n data.blockTags = getFromCacheObject(\"blockTags\")\n data.likeTags = getFromCacheObject(\"likeTags\")\n\n // 书源设置 Map\n data.blockAuthorMap = Object.fromEntries(getFromCacheMap(\"blockAuthorMap\"))\n data.likeAuthorsMap = Object.fromEntries(getFromCacheMap(\"likeAuthorsMap\"))\n return JSON.stringify(data, null, 4)\n}\n\nfunction restoreData(data) {\n // 账号相关\n let pixivCookie = stripCfCookies(data?.pixivCookie)\n if (pixivCookie) {\n removeCookie()\n putInCache(\"pixivUid\", data?.pixivUid)\n putInCache(\"pixivCookie\", pixivCookie, 60*60)\n putInCache(\"pixivCsrfToken\", data?.pixivCsrfToken)\n cookie.setCookie(\"https://www.pixiv.net\", pixivCookie)\n cookie.setCookie(\"https://accounts.pixiv.net\", pixivCookie)\n try {\n cookie.setWebCookie(\"https://www.pixiv.net\", pixivCookie)\n cookie.setWebCookie(\"https://accounts.pixiv.net\", pixivCookie)\n } catch (e) {}\n }\n\n // 书源设置\n putInCacheObject(\"pixivSettings\", data?.pixivSettings)\n putInCacheObject(\"blockCaption\", data?.blockCaption)\n putInCacheObject(\"blockTags\", data?.blockTags)\n putInCacheObject(\"likeTags\", data?.likeTags)\n\n // 书源设置 Map\n putInCacheMap(\"blockAuthorMap\", new Map(Object.entries(data?.blockAuthorMap)))\n putInCacheMap(\"likeAuthorsMap\", new Map(Object.entries(data?.likeAuthorsMap)))\n try {source.refreshExplore()} catch (e) {}\n}\n\nfunction stripCfCookies(cookieStr) {\n return cookieStr\n .replace(/;?\\s*cf_clearance=[^;]*/g, '')\n .replace(/;?\\s*__cf_bm=[^;]*/g, '')\n .replace(/^;\\s*/, '') // 防止开头多余的分号\n .trim()\n}\n\nfunction cleanCache(toast) {\n let novel = getNovel()\n cache.delete(`${urlNovelUrl(novel.id)}`)\n cache.delete(`${urlNovelDetailed(novel.id)}`)\n try {java.refreshContent()} catch(err) {}\n if (toast) sleepToast(`🔄 刷新本章\\n\\n若正文未更新,请手动刷新`, 5)\n}\n\nfunction getFurryAuthors() {\n cache.delete(urlLinpxAuthors()) // 删除缓存实时请求数据\n let furryAuthorsMap = {}\n let authorsListLinpx = getAjaxJson(urlLinpxAuthors())\n authorsListLinpx.forEach(author => {\n furryAuthorsMap[author.name] = author.id\n })\n\n // let authorsMapFurryReading = getAjaxJson(\"\")\n // furryAuthorsMap = Object.assign(furryAuthorsMap, authorsMapFurryReading)\n putInCacheObject(\"furryAuthors\", furryAuthorsMap)\n return furryAuthorsMap\n}\n\nfunction updatePixivAuthors() {\n let furryAuthors = getFurryAuthors()\n let pixivAuthors = getFromCacheObject(\"pixivAuthors\")\n if (!pixivAuthors) pixivAuthors = {}\n pixivAuthors = Object.assign(pixivAuthors, furryAuthors)\n putInCacheObject(\"pixivAuthors\", pixivAuthors, cacheSaveSeconds)\n sleepToast(\"\\n🐺 兽人作者搜索优化 \\n\\n ✅ 已导入 Linpx 推荐作者\", 1)\n return pixivAuthors\n}", "respondTime": 180000, "ruleBookInfo": { "init": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (isFunctionString(v)) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction novelHandler(novel){\n novel = util.formatNovels(util.handNovels([novel], true))[0]\n if (novel.seriesId && util.settings.COMBINE_NOVELS) {\n book.bookUrl = novel.detailedUrl = urlSeriesUrl(novel.seriesId);\n book.tocUrl = novel.catalogUrl = urlIP(urlSeriesDetailed(novel.seriesId));\n } else {\n book.bookUrl = novel.detailedUrl = urlNovelUrl(novel.id);\n book.tocUrl = novel.catalogUrl = urlIP(urlNovelDetailed(novel.id));\n }\n return novel\n}\n\n(() => {\n return novelHandler(util.getNovelRespFirst(result).body)\n})()", "name": "title", "author": "userName", "kind": "tags", "wordCount": "textCount", "lastChapter": "latestChapter", "intro": "description", "coverUrl": "coverUrl", "tocUrl": "catalogUrl", "canReName": "true" }, "ruleContent": { "content": "@js:\nvar util = objParse(String(java.get(\"util\")))\nlet emoji = {\n \"normal\": 101, \"surprise\": 102, \"series\": 103, \"heaven\": 104, \"happy\": 105,\n \"excited\": 106, \"sing\": 107, \"cry\": 108, \"normal2\": 201, \"shame2\": 202,\n \"love2\": 203, \"interesting2\": 204, \"blush2\": 205, \"fire2\": 206, \"angry2\": 207,\n \"shine2\": 208, \"panic2\": 209, \"normal3\": 301, \"satisfaction3\": 302, \"surprise3\": 303,\n \"smile3\": 304, \"shock3\": 305, \"gaze3\": 306, \"wink3\": 307, \"happy3\": 308,\n \"excited3\": 309, \"love3\": 310, \"normal4\": 401, \"surprise4\": 402, \"series4\": 403,\n \"love4\": 404, \"shine4\": 405, \"sweet4\": 406, \"shame4\": 407, \"sleep4\": 408,\n \"heart\": 501, \"teardrop\": 502, \"star\": 503\n}\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (isFunctionString(v)) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction getNovelInfo(resp) {\n if (resp && resp.error === true) return\n\n let novel = resp.body\n novel.id = Number(novel.id)\n if (novel.bookmarkData) {\n novel.isBookmark = true\n putInCache(`collect${novel.id}`, novel.bookmarkData.id)\n util.saveNovels(\"likeNovels\", [Number(novel.id)])\n } else {\n novel.isBookmark = false\n }\n\n if (novel.seriesNavData) {\n novel.seriesId = Number(novel.seriesNavData.seriesId)\n novel.seriesTitle = novel.seriesNavData.title\n novel.isWatched = novel.seriesNavData.isWatched\n util.saveNovels(\"watchedSeries\", [Number(novel.seriesId)])\n } else {\n novel.seriesId = null\n novel.seriesTitle = \"\"\n novel.isWatched = false\n }\n\n // 系列 + 阅读,使用当前章节名称\n if (novel.seriesId && util.environment.IS_LEGADO) {\n novel.title = book.durChapterTitle\n try{\n let novelIds = getFromCacheObject(`novelIds${novel.seriesId}`)\n novel.id = novelIds[book.durChapterIndex]\n } catch(e) {\n let novelIds = getAjaxJson(urlIP(urlSeriesNovelsTitles(novel.seriesId)), true).body\n novel.id = novelIds[book.durChapterIndex].id\n }\n }\n\n // 添加投票信息\n if (novel.pollData) {\n novel.question = novel.pollData.question\n } else {\n novel.question = \"\"\n }\n\n // 登录界面显示信息\n novel[\"章节名称\"] = novel.title\n novel[\"问卷调查\"] = novel.question || \"\"\n source.putLoginInfo(JSON.stringify(novel))\n\n // 写入缓存\n resp.body = novel\n putInCacheObject(\"novel\", resp)\n putInCacheObject(urlNovelDetailed(novel.id), resp)\n}\n\n// 在正文内部添加小说描述\nfunction getCaptions(resp, content) {\n if (util.settings.SHOW_CAPTIONS && resp.description !== \"\") {\n content = resp.description + \"\\n\" + \"——————————\\n\".repeat(2) + content\n }\n return content\n}\n\n// 获取 [uploadedimage:] 的图片链接\nfunction replaceUploadedImage(resp, content) {\n if (resp.textEmbeddedImages) {\n let images = resp.textEmbeddedImages\n Object.keys(images).forEach((key) => {\n if (util.settings.SHOW_PICTURES) {\n let url = urlCoverUrl(images[key].urls.original)\n content = content.replace(`[uploadedimage:${key}]`, ``)\n } else {\n content = content.replace(`[uploadedimage:${key}]`, '')\n }\n })\n }\n return content\n}\n\n// 获取 [pixivimage:] 的图片链接 [pixivimage:1234] [pixivimage:1234-1]\nfunction replacePixivImage(content) {\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) && util.settings.SHOW_PICTURES) {\n content = content.replace(`${pixivimage}`, ``)\n } else {\n content = content.replace(`${pixivimage}`, ``)\n }\n })\n }\n return content\n}\n\n// 替换 Pixiv 分页标记符号 [newpage]\nfunction replaceNewPage(content) {\n if (!util.environment.IS_LEGADO_SIGMA) {\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 }\n return content\n}\n\n// 替换 Pixiv 章节标记符号 [chapter:]\nfunction replaceChapter(content) {\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 // 替换 Pixiv 分页标记符号 [newpage]\n if (util.environment.IS_LEGADO_SIGMA) {\n content = content.replace(`${matched[i]}`, `

${chapter}

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

​

`)\n }\n }\n }\n return content\n}\n\n// 替换 Pixiv 跳转页面标记符号 [[jump:]]\nfunction replaceJumpPage(content) {\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}\n\n// 替换 Pixiv 链接标记符号 [[jumpuri: > ]]\nfunction replaceJumpUrl(content) {\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_SIGMA) {\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}\n\n// 替换 Pixiv 注音标记符号 [[rb: > ]]\nfunction replaceRb(content) {\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.settings.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\n// 添加投票信息\nfunction getPollData(resp, content) {\n if (!util.settings.SHOW_QUESTION || !resp.pollData) return content\n\n // resp.pollData.selectedValue = 0\n if (resp.pollData) {\n let text = \"\"\n let showText = util.settings.DEBUG || String(getFromCache(\"pixivUid\")) !== resp.userId\n if (!resp.pollData.selectedValue && showText) {\n text = \"\\n\\n(⚠️当前未投,点击【Pixiv 小说】,打开登录界面,点击投票选项,即可参与投票)\"\n }\n let poll = `📃 问卷调查(✅${resp.pollData.total}已投)\n ${text}\\n\\n🔠 投票:\\n${resp.pollData.question}\\n`\n\n resp.pollData.choices.forEach((choice, i) => {\n if ( i+1 === Number(resp.pollData.selectedValue)) {\n poll += `✅选项${choice.id}:${choice.text}(✅${choice.count})\\n`\n } else {\n poll += `☑️选项${choice.id}:${choice.text}(✅${choice.count})\\n`\n }\n })\n content += \"\\n\" + \"——————————\\n\".repeat(2) + poll\n }\n return content\n}\n\nfunction processComment(item) {\n let text = item.comment || \"\"\n if (text === \"\" && item.stampId) {\n return ``\n }\n return text.replace(/\\(([^)]+)\\)/g, (match, key) => {\n if (emoji.hasOwnProperty(key)) {\n return ``\n }\n return match\n })\n}\n\nfunction formatComment(item, replyToName = null) {\n const content = processComment(item)\n const isMyComment = item.userId === String(getFromCache(\"pixivUid\"))\n const commentId = isMyComment ? `(${item.id})` : \"\"\n const name = replyToName ? `@${item.userName}(⤴️@${replyToName})` : `@${item.userName}`\n return `${name}:${content}(${item.commentDate})${commentId}\\n`\n}\n\nfunction getComment(resp, content) {\n if (!util.settings.SHOW_COMMENTS || resp.commentCount === 0) return content\n\n const limit = 50\n let comments = [], commentUrls = [];\n let maxPage = Math.ceil(resp.commentCount / limit)\n if (maxPage >= 2 && util.environment.IS_LEGADO) {\n for (let i = 0; i < maxPage; i++) {\n commentUrls.push(urlIP(urlNovelComments(resp.id, i * limit, limit)))\n }\n comments = getAjaxAllJson(commentUrls).map(resp => resp.body.comments).flat()\n } else {\n for (let i = 0; i < maxPage; i++) {\n let result = getAjaxJson(urlIP(urlNovelComments(resp.id, i * limit, limit)), true)\n if (result && !result.error && result.body && result.body.comments) {\n comments = comments.concat(result.body.comments)\n }\n }\n }\n\n let commentText = `💬 评论(共计${comments.length}条):\\n`\n comments.forEach(comment => {\n commentText += formatComment(comment)\n if (comment.hasReplies) {\n let resp = getAjaxJson(urlIP(urlNovelCommentsReply(comment.id, 1)), true)\n if (resp && !resp.error && resp.body && resp.body.comments) {\n resp.body.comments.reverse().forEach(reply => {\n commentText += formatComment(reply, reply.replyToUserName)\n })\n }\n commentText += \"——————————\\n\"\n }\n })\n return content + \"\\n\" + \"——————————\\n\".repeat(2) + commentText\n}\n\nfunction getContent(resp) {\n getNovelInfo(resp) // 放入信息以便登陆界面使用\n let content = String(resp.body.content)\n // let content = \"undefined\"\n if (content.includes(\"undefined\")) {\n return checkContent()\n }\n\n content = getCaptions(resp.body, content)\n content = replaceUploadedImage(resp.body, 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 content = getPollData(resp.body, content)\n content = getComment(resp.body, content)\n return content\n}\n\nfunction checkContent() {\n let latestMsg = getAjaxJson(urlMessageThreadLatest(5))\n if (latestMsg.error === true) {\n java.log(JSON.stringify(latestMsg))\n\n } else if (latestMsg.body.total >= 1) {\n let msg = latestMsg.body.message_threads.filter(item => item.thread_name === \"pixiv事務局\")[0]\n if (msg === undefined) {\n sleepToast(`您于 ${java.timeFormat(new Date().getTime())} 触发 Pixiv 【请求限制】,建议稍候/重新登录再继续`, 3)\n // java.startBrowser(\"https://www.pixiv.net\", '退出登录')\n // java.startBrowser(\"https://www.pixiv.net/logout.php\",'退出登录') // 不清除 WebView 缓存无法重新登录\n\n } else if (new Date().getTime()- 1000*msg.modified_at <= 3*24*60*60*1000) { // 3*24h内提醒\n sleepToast(`您于 ${java.timeFormat(1000*msg.modified_at)} 触发 Pixiv 【过度访问】,请修改密码并重新登录`, 3)\n sleepToast(`${msg.latest_content}`, 5)\n java.startBrowser(\"https://accounts.pixiv.net/password/change\",'修改密码')\n }\n }\n}\n\n(() => {\n return getContent(util.getNovelRespFirst(result))\n})()", "imageStyle": "FULL", "callBackJs": "function getNovelId(seriesId) {\n if (chapter) {\n try {\n return chapter.url.match(/novel\\/(\\d+)/)[1]\n } catch (e) {\n return chapter.url.match(/\\d+/)[0]\n }\n }\n\n if (!book.bookUrl.includes(\"series\")) {\n return book.bookUrl.match(/\\d+/)[0]\n } else {\n seriesId = book.bookUrl.match(/\\d+/)[0]\n }\n\n if (seriesId) {\n let novelIds = getFromCacheObject(`novelIds${seriesId}`)\n if (novelIds) {\n return getFromCacheObject(`novelIds${seriesId}`)[book.durChapterIndex]\n } else {\n return getAjaxJson(urlIP(urlSeriesNovelsTitles(seriesId)), true).body[book.durChapterIndex].id\n }\n }\n}\n\nfunction getNovel() {\n let novel = {}\n if (book.bookUrl.includes(\"series\")) {\n novel.seriesId = book.bookUrl.match(/\\d+/)[0]\n novel.seriesTitle = book.name\n novel.id = getNovelId(novel.seriesId)\n novel.title = book.durChapterTitle\n } else {\n novel.seriesId = 0\n novel.seriesTitle = \"\"\n novel.id = book.bookUrl.match(/\\d+/)[0]\n novel.title = book.name\n }\n novel.author = novel.userName = book.author.replace(\"@\", \"\")\n let resp = getAjaxJson(urlIP(urlNovelDetailed(novel.id))).body\n novel.userId = resp.userId\n novel.question = resp?.pollData?.question || \"\"\n // java.log(JSON.stringify(novel))\n return novel\n}\n\nfunction getPostBody(url, body, headers) {\n if (headers === undefined) headers = getFromCacheObject(\"pixivHeaders\")\n if (headers === undefined) headers = getFromCacheObject(\"headers\")\n if (isJsonString(body)) {\n headers[\"content-type\"] = \"application/json; charset=utf-8\"\n } else if (typeof body === \"string\") {\n headers[\"content-type\"] = \"application/x-www-form-urlencoded; charset=utf-8\"\n }\n\n let settings = getFromCacheObject(\"pixivSettings\")\n if (settings.IPDirect) {\n url = url.replace(\"http://\", \"https://\").replace(\"www.pixiv.net\", \"210.140.139.155\")\n headers[\"Host\"] = \"www.pixiv.net\"\n }\n try {\n java.log(`getPostBody(${url}, ${body}, ${headers})`)\n // java.log(`getPostBody(${url}, ${body}, ${JSON.stringify(headers)})`)\n return JSON.parse(java.post(url, body, headers).body())\n } catch (e) {\n e = String(e)\n // sleepToast(e)\n // sleepToast(JSON.stringify(headers))\n if (e.includes(\"400\")) sleepToast(`📤 getPostBody\\n\\n⚠️ 缺少 headers`, 1)\n // else if (e.includes(\"401\")) sleepToast(`📤 getPostBody\\n\\n⚠️ 缺少 cookie 或 cookie 过期`, 1)\n // else if (e.includes(\"403\")) sleepToast(`📤 getPostBody\\n\\n⚠️ 缺少 cookie 或 cookie 过期`, 1)\n else if (e.includes(\"404\")) sleepToast(`📤 getPostBody\\n\\n⚠️ 404 缺少 pixivCsrfToken `, 1)\n else if (e.includes(\"422\")) sleepToast(`📤 getPostBody\\n\\n⚠️ 请求信息有误`, 1)\n return {error: true, errMsg:e}\n }\n}\n\nfunction novelBookmarkAdd() {\n let restrict = 0\n let novel = getNovel()\n let novelObj = getAjaxJson(urlNovelDetailed(novel.id), true)\n if (novelObj.body.bookmarkData && novelObj.body.bookmarkData.private === false) restrict = 1\n let resp = getPostBody(\n \"https://www.pixiv.net/ajax/novels/bookmarks/add\",\n JSON.stringify({\"novel_id\": novel.id, \"restrict\": restrict, \"comment\":\"\", \"tags\":[]})\n )\n if (resp.error === true) {\n sleepToast(`❤️ 收藏小说\\n\\n⚠️ 收藏【${novel.title}】失败`)\n // shareFactory(\"novel\")\n } else {\n putInCacheObject(`collect${novel.id}`, resp.body)\n let likeNovels = getFromCacheObject(\"likeNovels\")\n likeNovels.push(Number(novel.id))\n putInCacheObject(\"likeNovels\", likeNovels)\n\n let novelObj = getAjaxJson(urlNovelDetailed(novel.id))\n novelObj.body.isBookmark = true\n putInCacheObject(urlNovelDetailed(novel.id), novelObj, cacheSaveSeconds)\n }\n\n if (restrict === 1) {\n sleepToast(`㊙️ 私密收藏\\n\\n✅ 已私密收藏\\n${novel.title}`)\n } else {\n sleepToast(`❤️ 公开收藏\\n\\n✅ 已公开收藏\\n${novel.title}`)\n }\n}\n\nfunction getNovelBookmarkId(novelId) {\n let bookmarkId = getFromCacheObject(`collect${novelId}`)\n if (bookmarkId === null) {\n try {\n bookmarkId = getAjaxJson(urlNovelBookmarkData(novelId), true).body.bookmarkData.id\n } catch (e) {\n bookmarkId = 0\n }\n }\n return bookmarkId\n}\n\nfunction novelBookmarkDelete() {\n let novel = getNovel()\n let bookmarkId = getNovelBookmarkId(novel.id)\n if (bookmarkId === 0) return sleepToast(`🖤 取消收藏\\n\\n✅ 已经取消收藏\\n${novel.title}`)\n\n let resp = getPostBody(\n \"https://www.pixiv.net/ajax/novels/bookmarks/delete\",\n `del=1&book_id=${bookmarkId}`\n )\n if (resp.error === true) {\n sleepToast(`🖤 取消收藏\\n\\n⚠️ 取消收藏失败\\n${novel.title}`)\n // shareFactory(\"novel\")\n } else {\n cache.delete(`collect${novel.id}`)\n sleepToast(`🖤 取消收藏\\n\\n✅ 已经取消收藏\\n${novel.title}`)\n\n let likeNovels = getFromCacheObject(\"likeNovels\")\n likeNovels = likeNovels.filter(item => item !== Number(novel.id))\n putInCacheObject(\"likeNovels\", likeNovels)\n\n let novelObj = getAjaxJson(urlNovelDetailed(novel.id))\n novelObj.body.isBookmark = false\n putInCacheObject(urlNovelDetailed(novel.id), novelObj, cacheSaveSeconds)\n }\n}\n\nfunction seriesWatch() {\n let novel = getNovel()\n let resp = getPostBody(\n `https://www.pixiv.net/ajax/novel/series/${novel.seriesId}/watch`,\n \"{}\"\n )\n if (resp.error === true) {\n sleepToast(`📃 追更系列\\n\\n⚠️ 追更【${novel.seriesTitle}】失败`, 1)\n // shareFactory(\"series\")\n } else {\n putInCache(`watch${novel.seriesId}`, true)\n sleepToast(`📃 追更系列\\n\\n✅ 已追更【${novel.seriesTitle}】`)\n\n let watchedSeries = getFromCacheObject(\"watchedSeries\")\n watchedSeries.push(Number(novel.seriesId))\n putInCacheObject(\"watchedSeries\", watchedSeries)\n\n let novelObj = getAjaxJson(urlSeriesDetailed(novel.seriesId))\n novelObj.body.isWatched = true\n putInCacheObject(urlSeriesDetailed(novel.seriesId), novelObj, cacheSaveSeconds)\n }\n}\n\nfunction seriesUnWatch() {\n let novel = getNovel()\n let resp = getPostBody(\n `https://www.pixiv.net/ajax/novel/series/${novel.seriesId}/unwatch`,\n \"{}\"\n )\n if (resp.error === true) {\n sleepToast(`📃 追更系列\\n\\n⚠️ 取消追更【${novel.seriesTitle}】失败`, 1)\n // shareFactory(\"series\")\n } else {\n cache.delete(`watch${novel.seriesId}`)\n sleepToast(`📃 追更系列\\n\\n✅ 已取消追更【${novel.seriesTitle}】`)\n\n let watchedSeries = getFromCacheObject(\"watchedSeries\")\n watchedSeries = watchedSeries.filter(item => item !== Number(novel.seriesId))\n putInCacheObject(\"watchedSeries\", watchedSeries)\n\n let novelObj = getAjaxJson(urlSeriesDetailed(novel.seriesId))\n novelObj.body.isWatched = false\n putInCacheObject(urlSeriesDetailed(novel.seriesId), novelObj, cacheSaveSeconds)\n }\n}\n\nfunction shareBook() {\n let text = `我正在看:【${book.author.replace(\"@\", \"\")}】创作的《${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分享自【开源阅读】Pixiv书源。使用添加网址,快速添加本文`\n java.copyText(text)\n return true\n}\n\nfunction clearCache() {\n let novel = getNovel()\n if (novel.id) {\n cache.delete(`${urlNovelUrl(novel.id)}`)\n cache.delete(`${urlNovelDetailed(novel.id)}`)\n cache.delete(`${urlSearchNovel(novel.title, 1)}`)\n }\n if (novel.seriesId) {\n cache.delete(`${urlSeriesUrl(novel.seriesId)}`)\n cache.delete(`${urlSeriesDetailed(novel.seriesId)}`)\n cache.delete(`${urlSearchSeries(novel.seriesTitle, 1)}`)\n\n let novelIds = getFromCacheObject(`novelIds${novel.seriesId}`)\n if (!novelIds) novelIds = getAjaxJson(urlIP(urlSeriesNovelsTitles(seriesId)), true).body.map(item => item.id)\n if (novelIds && novelIds.length > 0) {\n novelIds.forEach(novelId => {\n cache.delete(`${urlNovelUrl(novelId)}`)\n cache.delete(`${urlNovelDetailed(novelId)}`)\n })\n }\n }\n return true\n}\n\nfunction copyBookUrl() {\n java.copyText(book.bookUrl)\n return true\n}\nfunction copyTocUrl() {\n java.copyText(book.tocUrl)\n return true\n}\n\n// 添加书籍、删除书籍\nfunction addBookShelf() {\n let settings = getFromCacheObject(\"pixivSettings\")\n if (!settings.AUTO_LIKE_NOVELS) return\n\n let novel = getNovel()\n if (novel.seriesId) seriesWatch()\n else if (novel.id) novelBookmarkAdd()\n}\nfunction delBookShelf() {\n let settings = getFromCacheObject(\"pixivSettings\")\n if (!settings.AUTO_DISLIKE_NOVELS) return\n\n let novel = getNovel()\n if (novel.seriesId) seriesUnWatch()\n else if (novel.id) novelBookmarkDelete()\n}\n\n// 保存阅读,更新登录界面的章节名称\nfunction saveRead() {\n let novel = getNovel()\n novelData = {}\n novelData[\"章节名称\"] = book.durChapterTitle\n // novelData[\"章节名称\"] = novel.title\n novelData[\"问卷调查\"] = novel.question\n source.putLoginInfo(JSON.stringify(novelData))\n}\n\nfunction startShelfRefresh() {\n source.putConcurrent(\"18/30000\")\n}\nfunction endShelfRefresh() {\n source.putConcurrent(\"25/5000\")\n}\n\nfunction customButton(){\n java.open(\"login\")\n}\nfunction longCustomButton(){\n java.open(\"login\")\n}\n\nfunction clickBookName() {\n java.open(\"search\", null, book.name)\n return true\n}\nfunction longClickBookName() {\n let novel = getNovel()\n startBrowser(urlNovelUrl(novel.id), novel.title)\n return true\n}\n\nfunction clickAuthor() {\n java.open(\"search\", null, book.author)\n return true\n}\nfunction longClickAuthor() {\n let novel = getNovel()\n startBrowser(urlUserUrl(novel.userId), novel.userName)\n return true\n}\n\n// function clickBookLabel() {\n// java.open(\"search\", null, book.tags)\n// return true\n// }\n// function longClickBookLabel() {\n// startBrowser(urlSearchNovelWeb(book.tags,1), book.tag)\n// return true\n// }\n\nfunction callBackFactory(event) {\n switch (event) {\n case \"clickBookName\":\n return clickBookName()\n case \"longClickBookName\":\n return longClickBookName()\n\n case \"clickAuthor\":\n return clickAuthor()\n case \"longClickAuthor\":\n return longClickAuthor()\n\n // case \"clickBookLabel\":\n // return clickBookLabel()\n // case \"longClickBookLabel\":\n // return longClickBookLabel()\n\n case \"clickCustomButton\":\n return customButton()\n case \"longClickCustomButton\":\n return longCustomButton()\n\n case \"clickShareBook\":\n return shareBook()\n case \"clickClearCache\":\n return clearCache()\n\n case \"clickCopyBookUrl\":\n return copyBookUrl()\n case \"clickCopyTocUrl\":\n return copyTocUrl()\n\n // 下面的事件无法被回调结果消费\n case \"addBookShelf\":\n return addBookShelf()\n case \"delBookShelf\":\n return delBookShelf()\n\n case \"saveRead\":\n return saveRead()\n case \"startRead\":\n return saveRead()\n // case \"endRead\":\n // return endRead()\n\n case \"startShelfRefresh\":\n return startShelfRefresh()\n case \"endShelfRefresh\":\n return endShelfRefresh()\n }\n}\n\n(() => {\n return callBackFactory(event)\n})()" }, "ruleExplore": { "author": "userName", "bookList": "@js:\nvar util = objParse(String(java.get(\"util\")))\nvar seriesSet = new Set(); // 存储seriesID 有BUG无法处理翻页\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (isFunctionString(v)) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction handlerFactory() {\n if (baseUrl.includes(\"https://cdn.jsdelivr.net\")) {\n return () => {updateSource(); return []}\n }\n if (baseUrl.includes(\"https://github.com\")) {\n return () => {startBrowser(baseUrl, \"\"); return []}\n }\n if (baseUrl.includes(\"https://pixivsource.pages.dev\")) {\n return () => {startBrowser(baseUrl, \"\"); return []}\n }\n if (!isLogin() && !util.settings.DEBUG) {\n return handlerNoLogin()\n }\n\n if (baseUrl.includes(\"/bookmark\")) {\n return handlerBookMarks()\n }\n if (baseUrl.includes(\"/top\")) {\n return handlerRecommend()\n }\n if (baseUrl.includes(\"/follow_latest\")) {\n return handlerFollowLatest()\n }\n if (baseUrl.includes(\"/watch_list\")) {\n return handlerWatchList()\n }\n if (baseUrl.includes(\"/genre\")) {\n return handlerWatchList()\n }\n if (baseUrl.includes(\"/discovery\")) {\n return handlerDiscovery()\n }\n if (baseUrl.includes(\"/new\")) {\n return handlerDiscovery()\n }\n if (baseUrl.includes(\"/commission/\")) {\n return handlerFollowLatest()\n }\n if (baseUrl.includes(\"/user_event/portal\")) {\n return handlerFollowLatest()\n }\n if (baseUrl.includes(\"/editors_picks\")) {\n return handlerFollowLatest()\n }\n if (baseUrl.includes(\"/ranking\")) {\n return handlerRanking()\n }\n if (baseUrl.includes(\"/search/novels\")) {\n return handlerSearch()\n }\n if (baseUrl.includes(\"/street/for_you\")) {\n return handlerHome()\n }\n if (baseUrl.includes(\"/profile/all\")) {\n return handlerUserNovels()\n }\n // 正则匹配网址内容\n if (baseUrl.includes(\"/marker_all\")) {\n return handlerRegex()\n }\n}\n\nfunction handlerNoLogin() {\n return () => {\n sleepToast(\"⭐️ 发现小说\\n\\n⚠️ 当前未登录账号\\n请登录 Pixiv 账号\", 1.5)\n util.removeCookie(); util.login()\n sleepToast(\"⭐️ 发现小说\\n\\n登录成功后,请重新进入发现\", 2)\n return []\n }\n}\n\n// 推荐小说\nfunction handlerRecommend() {\n return () => {\n let resp = JSON.parse(result)\n const recommend = resp.body.page.recommend\n const novels = resp.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(util.handNovels(util.combineNovels(list)))\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 return util.formatNovels(util.handNovels(resp))\n }\n}\n\n//关注作者,小说委托,小说企划,编辑部推荐\nfunction handlerFollowLatest() {\n return () => {\n let resp = JSON.parse(result)\n return util.formatNovels(util.handNovels(util.combineNovels(resp.body.thumbnails.novel)))\n }\n}\n\n//推荐小说,最近小说\nfunction handlerDiscovery() {\n return () => {\n let resp = JSON.parse(result)\n return util.formatNovels(util.handNovels(util.combineNovels(resp.body.novels)))\n }\n}\n\n// 搜索标签\nfunction handlerSearch() {\n return () => {\n let resp = JSON.parse(result)\n return util.formatNovels(util.handNovels(util.combineNovels(resp.body.novel.data)))\n }\n}\n\n// 追更列表,热门分类\nfunction handlerWatchList() {\n return () => {\n let resp = JSON.parse(result)\n return util.formatNovels(util.handNovels(resp.body.thumbnails.novelSeries))\n }\n}\n\n// 排行榜\nfunction handlerRanking() {\n return () => {\n try {\n let resp = JSON.parse(result)\n return util.formatNovels(util.handNovels(resp.body.display_a.rank_a))\n } catch (e) {\n return []\n }\n }\n}\n\n// 首页\nfunction handlerHome() {\n return () => {\n let resp = JSON.parse(result)\n let novels = resp.body.contents\n .filter(item => item.kind === \"novel\")\n .flatMap(item => {\n return item.thumbnails.map(novel => {\n novel.tags = novel.tags.map(tag => tag.name)\n return novel\n })\n })\n // novels.forEach(novel => novel.tags = novel.tags.map(tag => tag.name))\n // java.log(JSON.stringify(novels))\n return util.formatNovels(util.handNovels(util.combineNovels(novels)))\n }\n}\n\n// 作者小说\nfunction handlerUserNovels() {\n return () => {\n let resp = JSON.parse(result)\n return util.formatNovels(util.handNovels(util.getUserNovelsById(resp)))\n }\n}\n\n// 书签,顺序相同\nfunction handlerRegex() {\n return () => {\n let novelIds = []\n let matched = result.match(RegExp(/\\/novel\\/show\\.php\\?id=\\d{5,}/gm))\n for (let i in matched) {\n let novelId = matched[i].match(RegExp(/\\d{5,}/))[0]\n if (novelIds.indexOf(novelId) === -1) {\n novelIds.push(novelId)\n }\n }\n\n let novels = getAjaxJson(urlIP(urlNovelsDetailed(getFromCache(\"pixivUid\"), novelIds))).body\n return util.formatNovels(util.handNovels(util.combineNovels(Object.values(novels).reverse())))\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 (isFunctionString(v)) {\n return eval(`(${v})`)\n }\n return v;\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\nfunction getUserIdCache() {\n let userId\n let userName = String(java.get(\"keyword\"))\n //cache.delete(\"pixivAuthors\")\n let pixivAuthors = getFromCacheObject(\"pixivAuthors\")\n // java.log(JSON.stringify(pixivAuthors))\n if (pixivAuthors) userId = pixivAuthors[userName]\n if (userId) {\n java.log(`👤 缓存作者ID:${userId}`)\n return [userId]\n }\n}\n\n// 模糊搜索作者\nfunction getUserNovelsByName() {\n let userName = String(java.get(\"keyword\"))\n let page = Number(java.get(\"page\"))\n\n // cache.delete(urlIP(urlSearchUser(userName, page, full)))\n let resp = getAjaxJson(urlIP(urlSearchUser(userName, page))).body\n let novels = resp.thumbnails.novel\n let userIds = resp.page.userIds\n\n java.log(`👤 获取作者ID:${JSON.stringify(userIds)}`)\n if (userIds.length === 1) {\n let pixivAuthors = getFromCacheObject(\"pixivAuthors\")\n pixivAuthors[userName] = userIds[0]\n putInCacheObject(\"pixivAuthors\", pixivAuthors)\n }\n if (userIds.length === 0) sleepToast(`\\n暂无名为【${userName}】的作者发布过小说\\n请尝试其他关键词`)\n return [userIds, novels]\n}\n\nfunction getUserNovelsById(uid) {\n return util.getUserNovelsById(getAjaxJson(urlIP(urlUserAllWorks(uid)), true))\n}\n\nfunction getUserNovels() {\n let novels = []\n let uidList = getUserIdCache()\n if (!uidList) [uidList, novels] = getUserNovelsByName()\n if (uidList.length === 1 ) novels = getUserNovelsById(uidList[0])\n return novels\n}\n\nfunction search(name, type, page) {\n let resp = {}\n if (type.includes(\"novel\")) {\n resp = getAjaxJson(urlIP(urlSearchNovel(name, page)))\n java.log(urlIP(urlSearchNovel(name, page)))\n }\n if (type.includes(\"series\")) {\n resp = getAjaxJson(urlIP(urlSearchSeries(name, page)))\n java.log(urlIP(urlSearchSeries(name, page)))\n }\n if (resp.error === true || resp.total === 0) {\n return {\"data\": [], \"total\":0, \"lastPage\": 0}\n }\n return resp.body.novel\n}\n\nfunction getSeries() {\n let resp = JSON.parse(result)\n if (resp.error === true) {\n return []\n }\n let name = String(java.get(\"keyword\"))\n let page = Number(java.get(\"page\"))\n java.log(urlIP(urlSearchSeries(name, page)))\n putInCacheObject(urlIP(urlSearchSeries(name, page)), resp, cacheSaveSeconds) // 加入缓存\n return resp.body.novel.data\n}\n\nfunction getNovels() {\n let name = String(java.get(\"keyword\"))\n let page = Number(java.get(\"page\"))\n let resp = search(name, \"novel\", page)\n return util.combineNovels(resp.data)\n}\n\nfunction getConvertNovels() {\n let novels = []\n let name = String(java.get(\"keyword\"))\n let page = Number(java.get(\"page\"))\n let name1 = String(java.s2t(name))\n let name2 = String(java.t2s(name))\n if (name1 !== name) novels = novels.concat(search(name1, \"novel\", page).data)\n if (name2 !== name) novels = novels.concat(search(name2, \"novel\", page).data)\n novels = util.combineNovels(novels)\n if (util.settings.COMBINE_NOVELS) return novels\n\n if (name1 !== name ) novels = novels.concat(search(name1, \"series\", page).data)\n if (name2 !== name) novels = novels.concat(search(name2, \"series\", page).data)\n return novels\n}\n\nfunction novelFilter(novels) {\n let textCount = 0, tags = []\n let limitedTextCount = String(java.get(\"limitedTextCount\")).replace(\"字数\", \"\").replace(\"字數\", \"\")\n // limitedTextCount = `3w 3k 3w5 3k5`.[0]\n if (limitedTextCount.includes(\"w\") || limitedTextCount.includes(\"W\")) {\n let num = limitedTextCount.toLowerCase().split(\"w\")\n textCount = 10000 * num[0] + 1000 * num[1]\n } else if (limitedTextCount.includes(\"k\") || limitedTextCount.includes(\"K\")) {\n let num = limitedTextCount.toLowerCase().split(\"k\")\n textCount = 1000 * num[0] + 100 * num[1]\n }\n\n let novels0 = novels.map(novel => novel.id)\n if (textCount >= 1) {\n novels = novels.filter(novel => novel.textCount >= textCount)\n let novels1 = novels.map(novel => novel.id)\n java.log(`🔢 字数限制:${limitedTextCount}`)\n java.log(`⏬ 字数限制:过滤前${novels0.length};过滤后${novels1.length}`)\n }\n\n let inputTags = String(java.get(\"inputTags\")).split(\" \")\n for (let i in inputTags) {\n let tag = inputTags[i].trim()\n if (tag !== \"\") tags.push(`${tag}`)\n }\n\n if (tags.length >= 1) {\n // 仅保留含有所有标签的小说\n // novels = novels.filter(novel => {\n // // java.log(`${JSON.stringify(novel.tags)}\\n${tags.every(item => novel.tags.includes(item))}`)\n // return tags.every(item => novel.tags.includes(item))\n // })\n novels = novels.filter(novel => tags.every(item => novel.tags.includes(item)))\n let novels2 = novels.map(novel => novel.id)\n java.log(`#️⃣ 过滤标签:${tags.join(\"、\")}`)\n java.log(`#️⃣ 过滤标签:过滤前${novels0.length};过滤后${novels2.length}`)\n }\n\n let inputAuthor = String(java.get(\"inputAuthor\")).trim()\n if (inputAuthor) {\n // novels = novels.filter(novel => {\n // java.log(`${novel.userName}-${novel.userName.includes(inputAuthor)}`)\n // return novel.userName.includes(inputAuthor)\n // })\n novels = novels.filter(novel => novel.userName.includes(inputAuthor))\n let novels2 = novels.map(novel => novel.id)\n java.log(`👤 过滤作者:${inputAuthor.join(\"、\")}`)\n java.log(`👤 过滤作者:过滤前${novels0.length};过滤后${novels2.length}`)\n }\n return novels\n}\n\nfunction handlerFactory() {\n let novels = []\n let keyword = String(java.get(\"keyword\"))\n if (keyword.startsWith(\"#\")) {\n java.put(\"keyword\", keyword.slice(1))\n novels = novels.concat(getNovels())\n if (util.settings.COMBINE_NOVELS) novels = novels.concat(getSeries())\n }\n\n // 登录检测\n else if (!isLogin() && !util.settings.DEBUG) {\n sleepToast(\"🔍 搜索小说\\n\\n⚠️ 当前未登录账号\\n请登录 Pixiv 账号\", 1.5)\n util.removeCookie(); util.login()\n sleepToast(\"🔍 搜索小说\\n\\n登录成功后,请重新搜索\", 2)\n return novels\n }\n\n else if (keyword.startsWith(\"@\") ) {\n java.put(\"keyword\", keyword.slice(1))\n novels = novels.concat(getUserNovels())\n\n } else if (keyword.startsWith(\"$\") || util.settings.SEARCH_AUTHOR) {\n if (keyword.startsWith(\"$\")) {\n keyword = keyword.slice(1)\n java.put(\"keyword\", keyword)\n }\n sleepToast(`\\n\\n👤 模糊搜索作者:${keyword}`)\n novels = novels.concat(getUserIdOnline()[1])\n\n } else {\n novels = novels.concat(getNovels())\n if (util.settings.COMBINE_NOVELS) novels = novels.concat(getSeries())\n if (util.settings.CONVERT_CHINESE) novels = novels.concat(getConvertNovels())\n }\n // java.log(JSON.stringify(novels))\n return novelFilter(util.formatNovels(util.handNovels(novels)))\n}\n\n(() => {\n return handlerFactory()\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 (isFunctionString(v)) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction urlNovel(novelId){\n if (util.settings.SHOW_ORIGINAL_LINK) {\n return urlNovelUrl(novelId)\n } else {\n return urlNovelDetailed(novelId)\n }\n}\n\nfunction oneShotHandler(res) {\n res.textCount = res.userNovels[`${res.id}`].textCount\n res.createDate = timeTextFormat(res.createDate)\n return [{\n title: res.title.trim(),\n chapterUrl: urlIP(urlNovel(res.id)),\n chapterInfo: `${res.createDate}  ${res.textCount}字`\n }]\n}\n\nfunction seriesHandler(res) {\n const limit = 30\n let returnList = [], novelIds = []\n let seriesID = res.id, allChaptersCount = res.total\n util.debugFunc(() => {\n java.log(`本系列 ${seriesID} 一共有${allChaptersCount}章`)\n })\n\n //发送请求获得相应数量的目录列表\n function sendAjaxForGetChapters(lastIndex) {\n let resp = getAjaxJson(urlIP(urlSeriesNovels(seriesID, limit, lastIndex)), true)\n let novels = resp.body.thumbnails.novel\n // let novels = resp.body.page.seriesContents\n\n novels.forEach((v, i) => {\n novelIds.push(v.id)\n v.chapterUrl = urlIP(urlNovel(v.id))\n v.title = v.title.trim()\n if (util.settings.ADD_CHAPTER_INDEX) v.title = \"第\"+ (i+1) +\"章 \" + v.title\n if (v.updateDate && v.textCount) {\n v.updateDate = timeTextFormat(v.createDate)\n v.chapterInfo = `${v.updateDate}  ${v.textCount}字`\n }\n\n util.debugFunc(() => {\n java.log(`${v.title}`)\n })\n })\n return novels\n }\n\n if (!util.settings.SHOW_UPDATE_TIME) {\n returnList = getAjaxJson(urlIP(urlSeriesNovelsTitles(seriesID)), true).body\n returnList.forEach((v, i) => {\n novelIds.push(v.id)\n v.chapterUrl = urlIP(urlNovel(v.id))\n v.title = v.title.trim()\n if (util.settings.ADD_CHAPTER_INDEX) v.title = \"第\"+ (i+1) +\"章 \" + v.title\n\n util.debugFunc(() => {\n java.log(`${v.title}`)\n })\n })\n } else {\n //逻辑控制者 也就是使用上面定义的两个函数来做对应功能\n //要爬取的总次数\n let max = Math.ceil(allChaptersCount / limit)\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 }\n putInCacheObject(`novelIds${seriesID}`, novelIds, cacheSaveSeconds)\n // java.log(JSON.stringify(returnList))\n return returnList\n}\n\n(() => {\n let res = util.getNovelRespFirst(result).body\n if ((res.firstNovelId || res.seriesNavData) && util.settings.COMBINE_NOVELS) {\n return seriesHandler(res)\n } else {\n return oneShotHandler(res)\n }\n})()", "chapterName": "title", "chapterUrl": "chapterUrl", "updateTime": "chapterInfo" }, "searchUrl": "@js:\njava.put(\"key\", key)\njava.put(\"page\", page)\nlet keyword = key.split(\" \")\nlet limitedTextCount\nif (key.includes(\"字数\") || key.includes(\"字數\") ) {\n limitedTextCount = keyword.pop()\n keyword = keyword.join(\" \")\n} else {\n limitedTextCount = \"\"\n keyword = key\n}\njava.put(\"keyword\", keyword)\njava.put(\"limitedTextCount\", limitedTextCount)\n\nif (keyword.startsWith(\"@\") || keyword.startsWith(\"@\")) {\n keyword = keyword.slice(1)\n if (keyword.includes(\"#\") || keyword.includes(\"#\")) {\n let author = keyword.split(\" \")[0]\n let tags = keyword.replace(author, \"\").trim().slice(1)\n java.put(\"keyword\", `@${author}`)\n java.put(\"inputTags\", tags)\n java.log(`👤 搜索作者:${author} #️⃣ 过滤标签:${tags.replace(\" \", \"、\")}`)\n } else {\n java.put(\"keyword\", `@${keyword}`)\n java.log(`👤 搜索作者:${keyword}`)\n }\n\n} else if (keyword.startsWith(\"$\") || keyword.startsWith(\"$\")) {\n keyword = keyword.slice(1)\n // java.log(`👤 粗略搜索作者:${keyword}`)\n java.put(\"keyword\", `$${keyword}`)\n\n} else if (keyword.startsWith(\"#\") || keyword.startsWith(\"#\")) {\n keyword = keyword.slice(1)\n java.log(`#️⃣ 搜索标签:${keyword}`)\n java.put(\"keyword\", `#${keyword}`)\n\n} else {\n java.log(`🔍 搜索内容:${keyword}`)\n}\nurlIP(urlSearchSeries(keyword, page))", "variableComment": "⚙️ 书源设置:\n1️⃣ 书架 - 阅读界面 - Pixiv 小说 - 登录\n2️⃣ 我的 - 书源管理 - Pixiv 小说 - 登录\n点击【👀 书源设置】【👀 发现设置】显示相关设置按钮\n\n▶️ 自定功能:\n小说书架 - 阅读界面 - Pixiv 小说 - 登录\n\n💬 发送评论:\n1️⃣ 文本框内 输入内容,点击【✅ 发送评论】\n\n🚫 屏蔽标签/描述(本地):\n1️⃣ 点击【👀 查看屏蔽】,切换至屏蔽列表\n2️⃣ 文本框内 输入内容,点击【🚫 加入屏蔽】\n\n📌 喜欢标签(本地):\n1️⃣ 文本框内 输入标签,点击【📌 喜欢标签】\n\n❤️ 关注 他人收藏(本地):\n1️⃣ 文本框内 输入作者ID,点击【❤️ 他人收藏】\n\n", "weight": 0 }, { "bookSourceComment": "🅿️ Pixiv 小说备用(更新📆:2026/09/21)\n\n书源版本:284\n使用说明:📌阅读 正式版 3.25 可用\n可用功能:✅搜索✅发现✅添加网址✅订阅源\n搜索小说:✅单篇✅系列✅标签✅作者\n发现小说:✅关注✅追更✅推荐✅发现\n发现小说:✅收藏✅书签✅首页✅排行\n添加网址:✅小说✅系列✅作者\n订阅用法:点击订阅源打开小说/系列小说,【刷新】,点击【加入书架】按钮,添加到书架\n\n书源发布:Pixiv 书源频道 https://t.me/PixivSource\n项目地址:https://github.com/DowneyRem/PixivSource\n使用教程:https://pixivsource.pages.dev/Pixiv\n\n⚙️ 书源设置:\n1️⃣ 书架 - 阅读界面 - Pixiv 小说 - 登录\n2️⃣ 我的 - 书源管理 - Pixiv 小说 - 登录\n点击【👀 书源设置】【👀 发现设置】显示相关设置按钮", "bookSourceGroup": "🔞 Pixiv", "bookSourceName": "🅿️ Pixiv 小说备用", "bookSourceType": 0, "bookSourceUrl": "https://www.pixiv.net", "bookUrlPattern": "(https?://)?(www\\.)?pixiv\\.net(/ajax)?/(novel/(show\\.php\\?id=|series/)?|users?/)\\d+.*", "concurrentRate": "30/5000", "customButton": true, "customOrder": 1, "enabled": false, "enabledCookieJar": true, "enabledExplore": false, "eventListener": true, "exploreUrl": "@js:\nlet settings = getFromCacheObject(\"pixivSettings\")\nif (!settings) settings = setDefaultSettings()\nlet li =[]\n\nlet adultDefault = [\n {\"⭐️ 关注\": \"https://www.pixiv.net/ajax/follow_latest/novel?p={{page}}&mode=r18&lang=zh\"},\n {\"📃 追更\": \"https://www.pixiv.net/ajax/watch_list/novel?p={{page}}&new=1&lang=zh\"},\n {\"💯 推荐\": \"https://www.pixiv.net/ajax/top/novel?mode=r18&lang=zh\"},\n {\"🔍 发现\": \"https://www.pixiv.net/ajax/novel/discovery?mode=r18&lang=zh\"},\n {\"❤️ 收藏\": \"https://www.pixiv.net/ajax/user/{{cache.get(\\\"pixivUid\\\")}}/novels/bookmarks?tag=&offset={{(page-1)*24}}&limit=24&rest=show&lang=zh\"},\n {\"㊙️ 收藏\": \"https://www.pixiv.net/ajax/user/{{cache.get(\\\"pixivUid\\\")}}/novels/bookmarks?tag=&offset={{(page-1)*24}}&limit=24&rest=hide&lang=zh\"},\n {\"🏷️ 书签\": \"https://www.pixiv.net/novel/marker_all.php?p={{page}}\"},\n {\"🏠 首页\": \"https://www.pixiv.net/ajax/street/for_you?lang=zh\"},\n]\n\nlet normalDefault = [\n {\"✅ 常规 小说 推荐 ✅\": \"\"},\n {\"⭐️ 关注\": \"https://www.pixiv.net/ajax/follow_latest/novel?p={{page}}&mode=all&lang=zh\"},\n {\"💯 推荐\": \"https://www.pixiv.net/ajax/top/novel?mode=all&lang=zh\"},\n {\"🔍 发现\": \"https://www.pixiv.net/ajax/novel/discovery?mode=safe&lang=zh\"},\n {\"🆙 更新\": \"https://cdn.jsdelivr.net/gh/DowneyRem/PixivSource@main/pixiv.json\"},\n]\n\nlet adultNew = [\n {\"🆕 最新 企划 约稿 💰\": \"\"},\n {\"🆕 最新\": \"https://www.pixiv.net/ajax/novel/new?lastId=0&limit=20&r18=true&lang=zh\"},\n {\"📑 企划\": \"https://www.pixiv.net/ajax/user_event/portal/novels?mode=r18&p={{page}}&lang=zh\"},\n {\"💰 约稿\": \"https://www.pixiv.net/ajax/commission/page/request/complete/novels?mode=r18&p={{page}}&lang=zh\"},\n {\"🔍 发现\": \"https://www.pixiv.net/ajax/novel/discovery?mode=all&lang=zh\"},\n]\n\nlet generalNew = [\n {\"✅ 最新 企划 约稿 ✅\": \"\"},\n {\"最新\": \"https://www.pixiv.net/ajax/novel/new?lastId=0&limit=20&r18=false&lang=zh\"},\n {\"企划\": \"https://www.pixiv.net/ajax/user_event/portal/novels?mode=all&p={{page}}&lang=zh\"},\n {\"约稿\": \"https://www.pixiv.net/ajax/commission/page/request/complete/novels?mode=all&p={{page}}&lang=zh\"},\n {\"编辑\": \"https://www.pixiv.net/ajax/novel/editors_picks?limit=30&lang=zh\"},\n]\n\nlet adultRank = [\n {\"👑 排行榜单 👑\": \"\"},\n {\"今日\": \"https://www.pixiv.net/ajax/ranking/novel?mode=daily_r18&content=novel&p={{page}}\"},\n {\"本周\": \"https://www.pixiv.net/ajax/ranking/novel?mode=weekly_r18&content=novel&p={{page}}\"},\n {\"R18G\": \"https://www.pixiv.net/ajax/ranking/novel?mode=r18g&content=novel&p={{page}}\"},\n {\"男性\": \"https://www.pixiv.net/ajax/ranking/novel?mode=male_r18&content=novel&p={{page}}\"},\n {\"女性\": \"https://www.pixiv.net/ajax/ranking/novel?mode=female_r18&content=novel&p={{page}}\"}\n]\n\nlet generalRank = [\n {\"🏆 排行榜单 🏆\": \"\"},\n {\"今日\": \"https://www.pixiv.net/ajax/ranking/novel?mode=daily&content=novel&p={{page}}\"},\n {\"本周\": \"https://www.pixiv.net/ajax/ranking/novel?mode=weekly&content=novel&p={{page}}\"},\n {\"本月\": \"https://www.pixiv.net/ajax/ranking/novel?mode=monthly&content=novel&p={{page}}\"},\n {\"男性\": \"https://www.pixiv.net/ajax/ranking/novel?mode=male&content=novel&p={{page}}\"},\n {\"女性\": \"https://www.pixiv.net/ajax/ranking/novel?mode=female&content=novel&p={{page}}\"},\n {\"新人\": \"https://www.pixiv.net/ajax/ranking/novel?mode=rookie&content=novel&p={{page}}\"},\n {\"原创\": \"https://www.pixiv.net/ajax/ranking/novel?mode=weekly_original&content=novel&p={{page}}\"},\n {\"AI生成\": \"https://www.pixiv.net/ajax/ranking/novel?mode=weekly_ai&content=novel&p={{page}}\"}\n]\n\nlet adultGenre = [\n {\"🔥 原创热门 🔥\": \"\"},\n {\"男性\": \"https://www.pixiv.net/ajax/genre/novel/male?mode=r18&lang=zh\"},\n {\"女性\": \"https://www.pixiv.net/ajax/genre/novel/female?mode=r18&lang=zh\"},\n {\"恋爱\": \"https://www.pixiv.net/ajax/genre/novel/romance?mode=r18&lang=zh\"},\n {\"异世界奇幻\": \"https://www.pixiv.net/ajax/genre/novel/isekai_fantasy?mode=r18&lang=zh\"},\n {\"现代奇幻\": \"https://www.pixiv.net/ajax/genre/novel/contemporary_fantasy?mode=r18&lang=zh\"},\n {\"悬疑\": \"https://www.pixiv.net/ajax/genre/novel/mystery?mode=r18&lang=zh\"},\n {\"恐怖\": \"https://www.pixiv.net/ajax/genre/novel/horror?mode=r18&lang=zh\"},\n {\"科幻\": \"https://www.pixiv.net/ajax/genre/novel/sci-fi?mode=r18&lang=zh\"},\n {\"文学\": \"https://www.pixiv.net/ajax/genre/novel/literature?mode=r18&lang=zh\"},\n {\"情感\": \"https://www.pixiv.net/ajax/genre/novel/drama?mode=r18&lang=zh\"},\n {\"历史\": \"https://www.pixiv.net/ajax/genre/novel/historical_pieces?mode=r18&lang=zh\"},\n {\"耽美\": \"https://www.pixiv.net/ajax/genre/novel/bl?mode=r18&lang=zh\"},\n {\"百合\": \"https://www.pixiv.net/ajax/genre/novel/yuri?mode=r18&lang=zh\"},\n {\"散文·诗歌\": \"https://www.pixiv.net/ajax/genre/novel/poetry?mode=r18&lang=zh\"},\n {\"随笔·纪实\": \"https://www.pixiv.net/ajax/genre/novel/non-fiction?mode=r18&lang=zh\"},\n {\"剧本\": \"https://www.pixiv.net/ajax/genre/novel/screenplays?mode=r18&lang=zh\"},\n {\"评论\": \"https://www.pixiv.net/ajax/genre/novel/reviews?mode=r18&lang=zh\"},\n {\"其他\": \"https://www.pixiv.net/ajax/genre/novel/other?mode=r18&lang=zh\"}\n]\n\nlet generalGenre = [\n {\"❤️‍🔥 原创热门 ❤️‍🔥\": \"\"},\n {\"综合\": \"https://www.pixiv.net/ajax/genre/novel/all?mode=safe&lang=zh\"},\n {\"恋爱\": \"https://www.pixiv.net/ajax/genre/novel/romance?mode=safe&lang=zh\"},\n {\"异世界奇幻\": \"https://www.pixiv.net/ajax/genre/novel/isekai_fantasy?mode=safe&lang=zh\"},\n {\"现代奇幻\": \"https://www.pixiv.net/ajax/genre/novel/contemporary_fantasy?mode=safe&lang=zh\"},\n {\"悬疑\": \"https://www.pixiv.net/ajax/genre/novel/mystery?mode=safe&lang=zh\"},\n {\"恐怖\": \"https://www.pixiv.net/ajax/genre/novel/horror?mode=safe&lang=zh\"},\n {\"科幻\": \"https://www.pixiv.net/ajax/genre/novel/sci-fi?mode=safe&lang=zh\"},\n {\"文学\": \"https://www.pixiv.net/ajax/genre/novel/literature?mode=safe&lang=zh\"},\n {\"情感\": \"https://www.pixiv.net/ajax/genre/novel/drama?mode=safe&lang=zh\"},\n {\"历史\": \"https://www.pixiv.net/ajax/genre/novel/historical_pieces?mode=safe&lang=zh\"},\n {\"耽美\": \"https://www.pixiv.net/ajax/genre/novel/bl?mode=safe&lang=zh\"},\n {\"百合\": \"https://www.pixiv.net/ajax/genre/novel/yuri?mode=safe&lang=zh\"},\n {\"散文·诗歌\": \"https://www.pixiv.net/ajax/genre/novel/poetry?mode=safe&lang=zh\"},\n {\"随笔·纪实\": \"https://www.pixiv.net/ajax/genre/novel/non-fiction?mode=safe&lang=zh\"},\n {\"剧本\": \"https://www.pixiv.net/ajax/genre/novel/screenplays?mode=safe&lang=zh\"},\n {\"评论\": \"https://www.pixiv.net/ajax/genre/novel/reviews?mode=safe&lang=zh\"},\n {\"其他\": \"https://www.pixiv.net/ajax/genre/novel/other?mode=safe&lang=zh\"}\n]\n\nlet about = [\n {\"📘 书源相关 📘\": \"\"},\n {\"🏠 主页\": \"https://pixivsource.pages.dev\"},\n {\"🔰 功能\": \"https://pixivsource.pages.dev/Pixiv\"},\n {\"🐞 反馈\": \"https://github.com/DowneyRem/PixivSource/issues\"},\n {\"💰 打赏\": \"https://pixivsource.pages.dev/Sponsor\"},\n]\n\n// 收藏标签\nlet likeTagLinks = [{\"📌 喜欢标签 📌\":\"\"}]\nlet likeTags = getFromCacheObject(\"likeTags\")\nif (likeTags && likeTags.length >= 1) {\n likeTags.forEach(tag => {\n let tagLink = {}\n tagLink[tag] = `${urlSearchNovel(tag, \"{{page}}\")}`\n likeTagLinks.push(tagLink)\n })\n}\n\n// 他人收藏\nlet othersBookmarks = [{\"❤️ 他人收藏 ❤️\": \"\"}]\nlet likeAuthors = getFromCacheMap(\"likeAuthorsMap\")\nif (!likeAuthors) likeAuthors = getFromCacheMap(\"likeAuthors\")\nif (likeAuthors.size > 0) {\n likeAuthors.forEach((authorName, authorId) => {\n let bookmark = {}\n bookmark[authorName] = urlUserBookmarks(authorId)\n othersBookmarks.push(bookmark)\n })\n}\n\n// 兽人小说\nlet furrynovels = [{\"🦊 兽人小说 🦊\": \"\"}]\nif (settings.SHOW_FURRY) {\n let authorsListLinpx = getAjaxJson(urlLinpxAuthors())\n const randomAuthors = authorsListLinpx\n .sort(() => Math.random() - 0.5)\n .slice(0, 5)\n const randomObjects = randomAuthors.map(author => ({\n [author.name]: urlUserAllWorks(author.id)\n }))\n furrynovels = furrynovels.concat(randomObjects)\n}\n\n\n// 添加发现\nif (settings.SHOW_ADULT) li = li.concat(adultDefault)\nif (settings.SHOW_GENERAL) li = li.concat(normalDefault)\nif (settings.SHOW_NEW_ADULT) li = li.concat(adultNew)\nif (settings.SHOW_NEW_GENERAL) li = li.concat(generalNew)\n\nif (settings.SHOW_RANK_ADULT)li = li.concat(adultRank)\nif (settings.SHOW_RANK_GENERAL) li = li.concat(generalRank)\nif (settings.SHOW_GENRE_ADULT) li = li.concat(adultGenre)\nif (settings.SHOW_GENRE_GENERAL) li = li.concat(generalGenre)\n\nif (likeTags && likeTags.length >= 1) li = li.concat(likeTagLinks)\nif (likeAuthors && likeAuthors.size > 0) li = li.concat(othersBookmarks)\nif (settings.SHOW_FURRY) li = li.concat(furrynovels)\nif (settings.SHOW_ABOUT) li = li.concat(about)\nsleepToast('功能手册\\n\\n发现 - 书源相关 - \"🔰 功能\" ')\n\n// 添加格式\nli.forEach(item => {\n item.title = Object.keys(item)[0]\n item.url = Object.values(item)[0]\n if (item.url.includes(\"https://www.pixiv.net\")) item.url = urlIP(item.url)\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})\n\nJSON.stringify(li)", "header": "{\"Referer\":\"https://www.pixiv.net\"}", "jsLib": "var cacheSaveSeconds = 30*24*60*60 // 长期缓存 30 天\nvar cacheTempSeconds = 10*60*1000 // 冷却时间 10 分钟\n\nfunction cacheGetAndSet(key, supplyFunc, requestUpdate) {\n const {java, cache} = this\n let timestamp = 0\n let v = this.getFromCacheObject(key)\n if (Array.isArray(v)) {\n try {\n timestamp = v[0].timestamp\n } catch (e) {\n timestamp = 0\n }\n } else if (v) {\n timestamp = v.timestamp\n }\n\n const isExpired = v && (new Date().getTime() >= timestamp + cacheTempSeconds)\n const isError = v && (v.error === true) && isExpired\n requestUpdate = requestUpdate || isExpired\n\n if (!v || requestUpdate || isError) {\n v = supplyFunc()\n let now = new Date().getTime()\n // getAjaxJson getWebviewJson 时间戳写入对象本身\n if (!Array.isArray(v)) {\n v = Object.assign({timestamp: now}, v)\n }\n // else {\n // // getAjaxAllJson 时间戳写入第一个元素(读取时 v[0].timestamp)// 不重复写入\n // if (v.length > 0) v[0] = Object.assign({timestamp: now}, v[0])\n // }\n this.putInCacheObject(key, v, cacheSaveSeconds)\n }\n return v\n}\n\nfunction putInCache(name, object, saveSeconds) {\n const {java, cache} = this\n if (saveSeconds === undefined) saveSeconds = 0\n if (object) {\n cache.put(name, object, saveSeconds)\n }\n}\nfunction getFromCache(name) {\n const {java, cache} = this\n let object = cache.get(name)\n if (object === undefined) return null // 兼容源阅\n return object\n}\n\nfunction normalizeUrl(url) {\n if (!url.startsWith(\"https://210.140\")) return url\n return url.replace(\"210.140.139.155\", \"www.pixiv.net\")\n .replace(\"210.140.139.133\", \"i.pximg.net\")\n .split(\",\")[0]\n}\nfunction putInCacheObject(objectName, object, saveSeconds) {\n const {java, cache} = this\n if (object === undefined) object = null\n if (saveSeconds === undefined) saveSeconds = 0\n // if (objectName === \"pixivSettings\") {\n // this._settings = object\n // }\n cache.put(this.normalizeUrl(objectName), JSON.stringify(object), saveSeconds)\n}\nfunction getFromCacheObject(objectName) {\n const {java, cache} = this\n // if (objectName === \"pixivSettings\" && this._settings) {\n // return this._settings\n // }\n let object = cache.get(this.normalizeUrl(objectName))\n if (object === undefined) return null // 兼容源阅,避免 parse 报错\n return JSON.parse(object)\n}\n\nfunction putInCacheMap(mapName, mapObject, saveSeconds) {\n const {java, cache} = this\n if (saveSeconds === undefined) saveSeconds = 0\n let orderedArray = []\n mapObject.forEach((value, key) => {\n const item = {}\n item[key] = value\n orderedArray.push(item)\n })\n\n // let orderedArray = Array.from(mapObject, ([key, value]) => ({ [key]: value }))\n // [{'key1': 'value1'}, {'key2': 'value2'}]\n cache.put(mapName, JSON.stringify(orderedArray), saveSeconds)\n}\nfunction getFromCacheMap(mapName) {\n const {java, cache} = this\n let cached = cache.get(mapName)\n let newMap = new Map()\n if (cached === null || cached === undefined) {\n return newMap\n }\n\n let parsedData\n try {\n parsedData = JSON.parse(cached)\n } catch (e) {\n return newMap\n }\n\n if (Array.isArray(parsedData)) {\n parsedData.forEach(item => {\n for (let key in item) {\n newMap.set(key, item[key])\n }\n })\n } else {\n for (let key in parsedData) {\n newMap.set(key, parsedData[key])\n }\n }\n return newMap\n}\n\nfunction isHtmlString(str) {\n return str.startsWith(\"\") || str.startsWith(\"\")\n}\nfunction isJsonString(str) {\n try {\n let result = JSON.parse(str)\n return typeof result === \"object\" && result !== null\n } catch(e) {\n return false\n }\n}\nfunction isFunctionString(str) {\n return typeof str == \"string\" && (\n str.trim().startsWith(\"function\") ||\n RegExp(/^\\s*(\\([^)]*\\)|[A-Za-z_$][\\w$]*)\\s*=>/).test(str)\n )\n}\n\nfunction isLogin() {\n const {java, cache} = this\n return !!this.getFromCache(\"pixivCsrfToken\")\n}\nfunction checkLogin() {\n const {java, cache} = this\n return !JSON.stringify(java.ajax(urlSelfInfo())).error\n}\n\nfunction getAjaxJson(url, requestUpdate) {\n const {java, cache} = this\n return this.cacheGetAndSet(url, () => {\n return JSON.parse(java.ajax(url))\n }, requestUpdate)\n}\nfunction getAjaxAllJson(urls, requestUpdate) {\n const {java, cache} = this\n let batchKey = JSON.stringify(urls)\n return this.cacheGetAndSet(batchKey, () => {\n let results = []\n let now = new Date().getTime()\n let responses = java.ajaxAll(urls)\n for (let i in urls) {\n let data = JSON.parse(responses[i].body())\n data = Object.assign({timestamp: now}, data)\n results.push(data)\n this.putInCacheObject(urls[i], data, cacheSaveSeconds)\n }\n return results\n }, requestUpdate)\n}\n\nfunction getWebViewUA() {\n const {java, cache} = this\n let userAgent = this.getFromCache(\"userAgent\")\n if (userAgent) return String(userAgent)\n\n userAgent = String(java.getWebViewUA())\n // java.log(`userAgent=${userAgent}`)\n this.putInCache(\"userAgent\", userAgent, cacheSaveSeconds/4)\n return String(userAgent)\n}\nfunction startBrowser(url, title) {\n const {java, cache} = this\n if (!title) title = url\n let msg = \"\"\n let headers = {}\n headers[\"User-Agent\"] = this.getWebViewUA()\n\n if (url.includes(\"https://www.pixiv.net\")) {\n if (url.includes(\"settings\")) msg += \"⚙️ 账号设置\"\n else msg += \"⤴️ 分享小说\"\n msg += \"\\n\\n即将打开 Pixiv\\n请确认已开启代理/梯子/VPN等\"\n } else if (url.includes(\"github.com\") || url.includes(\"github.io\")) {\n if (url.includes(\"issues\")) msg += \"🐞 反馈问题\"\n msg += \"\\n\\n即将打开 Github\\n请确认已开启代理/梯子/VPN等\"\n }\n this.sleepToast(msg, 0.01)\n java.startBrowser(`${url}, ${JSON.stringify({headers: headers})}`, title)\n}\n\n// 直连功能参考自 洛娅橙的阅读仓库\n// https://github.com/Luoyacheng/yuedu\n// 其直连功能参考自 PixEz Flutter\n// https://github.com/Notsfsssf/pixez-flutter\nfunction urlIP(url) {\n const {java, cache} = this\n let settings = this.getFromCacheObject(\"pixivSettings\")\n if (!settings) settings = this.setDefaultSettings()\n if (settings.IPDirect) {\n url = url.replace(\"http://\", \"https://\").replace(\"www.pixiv.net\", \"210.140.139.155\")\n let headers = {\n \"User-Agent\": this.getFromCache(\"userAgent\"),\n \"X-Requested-With\": \"XMLHttpRequest\",\n \"Host\": \"www.pixiv.net\",\n \"Referer\": \"https://www.pixiv.net/\",\n \"X-csrf-token\": this.getFromCache(\"pixivCsrfToken\") || \"\",\n \"Cookie\": this.getFromCache(\"pixivCookie\") || \"\"\n }\n return `${url}, ${JSON.stringify({headers: headers})}`\n }\n return url\n}\n\nfunction urlSelfInfo() {\n return `https://www.pixiv.net/ajax/settings/self`\n}\n\nfunction urlNovelUrl(novelId) {\n return `https://www.pixiv.net/novel/show.php?id=${novelId}`\n}\nfunction urlNovelDetailed(novelId) {\n return `https://www.pixiv.net/ajax/novel/${novelId}`\n}\nfunction urlNovelsDetailed(userId, nidList) {\n return `https://www.pixiv.net/ajax/user/${userId}/novels?${nidList.map(v => `ids[]=${v}`).join(\"&\")}`\n}\nfunction urlNovelBookmarkData(novelId) {\n return `https://www.pixiv.net/ajax/novel/${novelId}/bookmarkData`\n}\nfunction urlNovelComments(novelId, offset, limit) {\n return `https://www.pixiv.net/ajax/novels/comments/roots?novel_id=${novelId}&offset=${offset}&limit=${limit}&lang=zh`\n}\nfunction urlNovelCommentsReply(commentId, page) {\n return `https://www.pixiv.net/ajax/novels/comments/replies?comment_id=${commentId}&page=${page}&lang=zh`\n}\nfunction urlNovelsRecommendInit(novelId, limit) {\n if (limit === undefined) limit = 9\n return `https://www.pixiv.net/ajax/novel/${novelId}/recommend/init?limit=${limit}&lang=zh`\n}\nfunction urlNovelsRecommendDetailed(nidList) {\n if (nidList.length >= 9) nidList.length = 9\n return `https://www.pixiv.net/ajax/novel/recommend/novels?${nidList.map(v => `novelIds[]=${v}`).join(\"&\")}`\n}\n\nfunction urlSeriesUrl(seriesId) {\n return `https://www.pixiv.net/novel/series/${seriesId}`\n}\nfunction urlSeriesDetailed(seriesId) {\n return `https://www.pixiv.net/ajax/novel/series/${seriesId}?lang=zh`\n}\nfunction urlSeriesNovelsTitles(seriesId) {\n return `https://www.pixiv.net/ajax/novel/series/${seriesId}/content_titles`\n}\nfunction urlSeriesNovels(seriesId, limit, offset) {\n if (limit > 30) limit = 30\n if (limit < 10) limit = 10\n return `https://www.pixiv.net/ajax/novel/series_content/${seriesId}?limit=${limit}&last_order=${offset}&order_by=asc&lang=zh`\n}\n\nfunction urlUserUrl(userID) {\n return `https://www.pixiv.net/users/${userID}/novels`\n}\nfunction urlUserDetailed(userID) {\n return `https://www.pixiv.net/ajax/user/${userID}`\n}\nfunction urlUserWorkLatest(userID) {\n return `https://www.pixiv.net/ajax/user/${userID}/works/latest`\n}\nfunction urlUserAllWorks(userId) {\n return `https://www.pixiv.net/ajax/user/${userId}/profile/all?lang=zh`\n}\nfunction urlUserBookmarks(userId) {\n return `https://www.pixiv.net/ajax/user/${userId}/novels/bookmarks?tag=&offset={{(page-1)*30}}&limit=30&rest=show&lang=zh`\n}\n\nfunction urlSearchNovelFull(novelName, series, 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&gs=${Number(series)}&lang=zh`\n}\nfunction urlSearchNovel(novelName, page) {\n return urlSearchNovelFull(novelName, false, page)\n}\nfunction urlSearchSeries(novelName, page) {\n return urlSearchNovelFull(novelName, true, page)\n}\nfunction urlSearchUser(userName, page, full) {\n if (!page) page = 1\n const fullUrl = full ? \"s_usr_full\": \"s_usr\"\n return `https://www.pixiv.net/ajax/search/users?nick=${userName}&s_mode=${fullUrl}&i=1&p=${page}&lang=zh`\n}\n\nfunction urlLinpxAuthors() {\n return \"https://api.linpx.ink/fav/user/cache\"\n}\n\n// 直连功能参考自 洛娅橙的阅读仓库\n// https://github.com/Luoyacheng/yuedu\n// 其直连功能参考自 PixEz Flutter\n// https://github.com/Notsfsssf/pixez-flutter\nfunction urlCoverUrl(url) {\n const {java, cache} = this\n if (url && !url.trim()) return \"\"\n\n let headers = {\"Referer\": \"https://www.pixiv.net/\"}\n if (!this._settings) {\n this._settings = this.getFromCacheObject(\"pixivSettings\") || this.setDefaultSettings()\n }\n if (this._settings.IPDirect && 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 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 urlIllustOriginal(illustId, order) {\n const {java, cache} = this\n if (!order || order <= 1) order = 1\n let illustOriginal = \"\"\n\n let resp = this.getAjaxJson(this.urlIP(urlIllustDetailed(illustId)))\n try {\n illustOriginal = resp.body.urls.original\n } catch (e) {\n try {\n let illustThumb = resp.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-master/img/${date}/${illustId}_p0_master1200.jpg`\n } catch (e) {}\n }\n\n if (illustOriginal.split(\",\")[0] === \"\") return \"\"\n return this.urlCoverUrl(illustOriginal.replace(`_p0`, `_p${order - 1}`))\n}\nfunction urlEmojiUrl(emojiId) {\n return `https://s.pximg.net/common/images/emoji/${emojiId}.png, {'style': 'text'}`\n}\nfunction urlStampUrl(stampId) {\n return `https://s.pximg.net/common/images/stamp/generated-stamps/${stampId}_s.jpg, {'style': 'text'}`\n}\n\nfunction urlMessageThreadLatest(max) {\n if (max === undefined || max <= 5) max = 5\n return `https://www.pixiv.net/rpc/index.php?mode=latest_message_threads2&num=${max}&lang=zh`\n}\nfunction urlMessageThreadContents(threadId, max) {\n return `https://www.pixiv.net/rpc/index.php?mode=message_thread_contents&thread_id=${threadId}&num=${max}`\n}\nfunction urlMessageThreadDetail(threadId) {\n return `https://www.pixiv.net/rpc/index.php?mode=message_thread&thread_id=${threadId}`\n}\nfunction urlNotification() {\n return `https://www.pixiv.net/ajax/notification?lang=zh`\n}\n\nfunction addZero(num) {\n return String(num).padStart(2, '0')\n}\nfunction dateFormat(str) {\n let time = new Date(str);\n let Y = time.getFullYear() + \"年\";\n let M = this.addZero(time.getMonth() + 1) + \"月\";\n let D = this.addZero(time.getDate()) + \"日\";\n return Y + M + D;\n}\nfunction timeFormat(str) {\n let time = new Date(str);\n let YY = time.getFullYear()\n let MM = this.addZero(time.getMonth() + 1)\n let DD = this.addZero(time.getDate())\n let hh = this.addZero(time.getHours())\n let mm = this.addZero(time.getMinutes())\n let ss = this.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}\n\nfunction sleep(seconds) {\n return Packages.java.lang.Thread.sleep(1000*seconds)\n}\nfunction sleepToast(text, seconds) {\n let {java} = this\n java.log(text)\n java.longToast(text)\n if (seconds === undefined) {seconds = 0.01}\n this.sleep(seconds)\n}\n\nfunction setDefaultSettings() {\n const {java, cache} = this\n let settings = {}\n settings.SEARCH_AUTHOR = false // 搜索:默认不搜索作者名称\n settings.CONVERT_CHINESE = true // 搜索:搜索时进行繁简转换\n settings.MORE_INFORMATION = false // 详情:书籍简介显示更多信息\n\n settings.ADD_CHAPTER_INDEX = false // 目录:添加章节编号\n settings.SHOW_UPDATE_TIME = true // 目录:显示更新时间,但会增加少许请求\n settings.SHOW_ORIGINAL_LINK = true // 目录:显示原始链接,但会增加大量请求\n\n settings.SHOW_CAPTIONS = true // 正文:章首显示描述\n settings.SHOW_PICTURES = true // 正文:正文显示图片,但会增加少许请求\n settings.SHOW_QUESTION = true // 正文:正文显示投票\n settings.SHOW_COMMENTS = true // 正文:章尾显示评论,但会增加大量请求\n settings.REPLACE_TITLE_MARKS = true // 正文:注音内容为汉字时,替换为书名号\n\n settings.AUTO_LIKE_NOVELS = false // 全局:添加书架:自动收藏/追更\n settings.AUTO_DISLIKE_NOVELS = false// 全局:删除书籍:自动取消收藏/追更\n settings.HIDE_LIKE_NOVELS = false // 全局:搜索/发现:隐藏已收藏小说\n settings.HIDE_WATCHED_SERIES = false// 全局:搜索/发现:隐藏已追更系列\n\n settings.COMBINE_NOVELS = true // 全局:整合系列\n settings.IPDirect = false // 全局:直连模式\n settings.FAST = false // 全局:快速模式\n settings.DEBUG = false // 全局:调试模式\n\n settings.SHOW_ADULT = true // 发现:显示 默认发现\n settings.SHOW_GENERAL = true // 发现:显示 常规小说\n settings.SHOW_NEW_ADULT = true // 发现:显示 最新企划约稿 R18\n settings.SHOW_NEW_GENERAL = false // 发现:显示 最新企划约稿 常规\n\n settings.SHOW_RANK_ADULT = true // 发现:显示 排行榜单 R18\n settings.SHOW_RANK_GENERAL = false // 发现:显示 排行榜单 常规\n settings.SHOW_GENRE_ADULT = false // 发现:显示 原创热门 R18\n settings.SHOW_GENRE_GENERAL = false // 发现:显示 原创热门 常规\n\n settings.SHOW_ABOUT = true // 发现:显示 书源相关\n settings.SHOW_FURRY = false // 发现:显示 兽人小说推荐作者\n\n settings.SHOW_SETTINGS = false // 设置:显示 书源设置\n settings.SHOW_DISCOVER = false // 设置:显示 发现设置\n settings.SHOW_SETTINGS2 = false // 设置:显示 书源设置\n settings.SHOW_DISCOVER2 = false // 设置:显示 发现设置\n this.putInCacheObject(\"pixivSettings\", settings)\n return settings\n}\nfunction checkSettings(settings) {\n const {java, cache} = this\n if (!settings) settings = this.getFromCacheObject(\"pixivSettings\")\n if (!settings) settings = this.setDefaultSettings()\n if (settings.IPDirect) {\n settings.SHOW_ORIGINAL_LINK = false // 目录:显示章节源链接\n }\n\n if (settings.FAST) {\n settings.CONVERT_CHINESE = false // 搜索:繁简通搜\n settings.SHOW_ORIGINAL_LINK = false // 目录:显示章节源链接\n settings.SHOW_UPDATE_TIME = false // 目录:显示章节更新时间\n\n settings.SHOW_PICTURES = false // 正文:显示图片\n settings.SHOW_QUESTION = false // 正文:正文显示投票\n settings.SHOW_COMMENTS = false // 正文:显示评论\n }\n\n // 新设置初始化\n if (settings.SHOW_PICTURES === undefined) settings.SHOW_PICTURES = true\n if (settings.SHOW_QUESTION === undefined) settings.SHOW_QUESTION = true\n if (settings.COMBINE_NOVELS === undefined) settings.COMBINE_NOVELS = true\n if (settings.SHOW_ADULT === undefined) settings.SHOW_ADULT = true\n if (settings.SHOW_ABOUT === undefined) settings.SHOW_ABOUT = true\n\n this.putInCacheObject(\"pixivSettings\", settings)\n return settings\n}\n\nfunction updateSource() {\n try {\n this.updateSourceHtml()\n } catch(e) {\n this.updateSourceLink()\n }\n}\n\nfunction updateSourceLink(){\n const {java, source} = this\n let sourceName\n if (source.bookSourceUrl.includes(\"pixiv\")) sourceName = \"pixiv\"\n else if (source.bookSourceUrl.includes(\"furrynovel\")) sourceName = \"linpx\"\n\n try {\n java.openUrl(`legado://import/importonline?src=https://cdn.jsdelivr.net/gh/DowneyRem/PixivSource@main/${sourceName}.json`)\n } catch (e) {\n java.openUrl(`legado://import/importonline?src=https://raw.githubusercontent.com/DowneyRem/PixivSource/main/${sourceName}.json`)\n }\n}\n\nfunction updateSourceHtml() {\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, {\n \"User-Agent\": this.getFromCache(\"userAgent\"),\n 'X-Requested-With': 'XMLHttpRequest'\n }).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,{\n \"User-Agent\": this.getFromCache(\"userAgent\"),\n 'X-Requested-With': 'XMLHttpRequest'\n }).body())[index]\n } catch (e) {\n onlineSource = {\n lastUpdateTime: new Date().getTime(),\n bookSourceComment: source.bookSourceComment\n }\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-20, 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": 1789920000251, "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\n// 检测 轻悦时光,多平台\n// 不可用 java.getResponse()\n// java.getUserAgent() java.getWebViewUA() 目前返回内容相同\nfunction isQYSG() {\n return java.getUserAgent() === java.getWebViewUA()\n}\n\n// 检测 源阅\n// 可用 java.ajax() 不可用 java.webview() java.ajaxAll()\n// 可用 java.getCookie() cache.put() cache.get() 默认值为 undefined\n// 可用 java.startBrowser() 不可用 java.startBrowserAwaitAwait\n// 可用 source.bookSourceName source.getVariable() source.setVariable()等\n// java.getUserAgent() java.getWebViewUA() 目前返回内容相同\nfunction isSourceRead() {\n return java.getUserAgent() === java.getWebViewUA() && String(java.getWebViewUA()).includes(\"Win\")\n}\n\n// 正式版 不支持在 JSlib 的函数直接设置默认参数\n// 正式版 不支持 a?.b 的写法\n// 检测 阅读 正式版 与 Beta 版本\nfunction isLegadoOfficial() {\n let isLegadoOfficialStatus\n try {\n eval('({})?.value')\n isLegadoOfficialStatus = false\n } catch (e) {\n isLegadoOfficialStatus = true\n }\n return isLegadoOfficialStatus\n}\n\n// 检测 阅读 Beta 版本 与 Sigma 版本\n// Sigma 版本新增函数\n// java.ajaxTestAll()\n// java.openVideoPlayer(url: String, title: String, float: Boolean)\n// cookie.setWebCookie(url,cookie)\n// source.refreshExplore()\n// source.refreshJSLib()\nfunction isLegadoSigma() {\n return typeof java.ajaxTestAll === \"function\"\n}\n\nfunction publicFunc() {\n let u = {}\n u.init = () => {\n u.settings = getFromCacheObject(\"pixivSettings\")\n if (!u.settings) u.settings = setDefaultSettings()\n u.settings = checkSettings()\n putInCacheObject(\"pixivSettings\", u.settings)\n\n u.environment = getFromCacheObject(\"pixivEnvironment\")\n if (!u.environment) u.checkEnvironment()\n }\n\n u.checkEnvironment = () => {\n u.environment = {}\n u.environment.IS_SOURCEREAD = isSourceRead()\n u.environment.IS_QYSG = isQYSG()\n u.environment.IS_LEGADO_SIGMA = isLegadoSigma()\n u.environment.IS_LEGADO_OFFICIAL = isLegadoOfficial()\n u.environment.IS_LEGADO = u.environment.IS_LEGADO_SIGMA || u.environment.IS_LEGADO_OFFICIAL\n putInCacheObject(\"pixivEnvironment\", u.environment)\n }\n\n u.logSource = () => {\n java.log(`${source.bookSourceComment.split(\"\\n\")[0]}`)\n java.log(`📌 ${source.bookSourceComment.split(\"\\n\")[2]}`)\n java.log(`📆 更新时间:${java.timeFormat(source.lastUpdateTime)}`)\n }\n\n u.logEnvironment = () => {\n if (u.environment.IS_SOURCEREAD) {\n java.log(\"▶️ 当前软件:🍎 源阅 SourceRead\")\n } else if (u.environment.IS_QYSG) {\n java.log(\"▶️ 当前软件:🍎 轻悦时光 QYSG\")\n } else if (u.environment.IS_LEGADO_SIGMA) {\n java.log(\"▶️ 当前软件:🤖 阅读 Sigma\")\n } else if (u.environment.IS_LEGADO_OFFICIAL && !source.bookSourceComment.includes(\"备用\")) {\n java.log(\"▶️ 当前软件:🤖 阅读 正式版\")\n sleepToast(\"\\n⚠️当前软件为:阅读【正式版】\\n【正式版】已年久失修,不推荐继续使用\\n\\n为了更好的使用体验,请使用:\\n阅读【Sigma】\\n\\n即将为您打开下载界面,请在浏览器内打开并下载\")\n sleep(3); startBrowser(\"https://pixivsource.pages.dev/Download\", \"下载阅读 Sigma\")\n }\n }\n\n u.logSettings = () => {\n if (u.settings.IPDirect) java.log(\"✈️ 直连模式:✅ 已开启\")\n if (u.settings.FAST) java.log(\"⏩ 快速模式:✅ 已开启\")\n if (u.settings.DEBUG) java.log(\"🐞 调试模式:✅ 已开启\")\n }\n\n u.checkPixiv = () => {\n let logs = []\n let checkThings = [\"pixivCsrfToken\", \"pixivCookie\", \"pixivUid\"]\n checkThings.forEach(item => {\n let text = `${getFromCache(item)? \"✅\" : \"❌\"} ${item}`\n logs.push(text)\n java.log(text)\n })\n if (logs.join(\"\").split(\"❌\").length >= 2) {\n sleepToast(\"⚠️ 登录状态\\n 当前登录信息不完整,请重新登录\\n或使用【备份恢复】恢复登录状态\")\n }\n }\n\n u.debugFunc = (func) => {\n if (util.settings.DEBUG === true) {\n func()\n }\n }\n\n u.checkStatus = function(status) {\n if (status === true) return \"✅ 已\"\n else if (status === false) return \"❌ 未\"\n else if (status === undefined) return \"🈚️ 无数据:\"\n }\n\n u.login = function() {\n let resp = java.startBrowserAwait(`https://accounts.pixiv.net/login,\n {\"headers\": {\"User-Agent\": ${getWebViewUA()}}}`, '登录账号', false)\n if (resp.code() === 200) {\n this.getCsrfToken(); this.getCookie()\n } else {\n java.log(resp.code()); sleepToast(\"⚠️ 登录失败\")\n }\n }\n\n u.logout = function() {\n this.removeCookie()\n java.startBrowser(\"https://www.pixiv.net/logout.php\", \"退出账号\")\n this.removeCookie()\n sleepToast(`✅ 已退出当前账号\\n\\n退出后请点击右上角的 ✔️ 退出\\n\\n登录请点击【登录账号】进行登录`)\n }\n\n u.getCookie = function() {\n let pixivCookie = String(java.getCookie(\"https://www.pixiv.net/\", null))\n if (isLogin()) putInCache(\"pixivCookie\", pixivCookie, 60*60) // 缓存1h\n }\n\n u.removeCookie = function() {\n cookie.removeCookie('https://www.pixiv.net')\n cookie.removeCookie('https://accounts.pixiv.net')\n cookie.removeCookie('https://accounts.google.com')\n cookie.removeCookie('https://api.weibo.com')\n cache.delete(\"pixivUid\")\n cache.delete(\"pixivCookie\")\n cache.delete(\"pixivCsrfToken\")\n cache.delete(\"PixivHeaders\")\n }\n\n // 获取 Csrf Token,以便进行收藏等请求\n // 获取方法来自脚本 Pixiv Previewer\n // https://github.com/Ocrosoft/PixivPreviewer\n // https://greasyfork.org/zh-CN/scripts/30766-pixiv-previewer/code\n u.getCsrfToken = function() {\n let pixivCsrfToken = getFromCache(\"pixivCsrfToken\")\n if (!pixivCsrfToken) {\n let html = java.ajax(\"https://www.pixiv.net/\")\n try {\n pixivCsrfToken = html.match(/token\\\\\":\\\\\"([a-z0-9]{32})/)[1]\n putInCache(\"pixivCsrfToken\", pixivCsrfToken) // 与登录设备有关,无法存储 nul\n } catch (e) {\n pixivCsrfToken = null\n cache.delete(\"pixivCsrfToken\") // 与登录设备有关,无法存储 nul\n // sleepToast(\"⚠️ 未登录账号(pixivCsrfToken)\")\n }\n java.log(`pixivCsrfToken:\\n${pixivCsrfToken}`)\n }\n return pixivCsrfToken\n }\n\n // 将多个长篇小说解析为一本书\n u.combineNovels = function(novels) {\n if (!util.settings.COMBINE_NOVELS) return novels\n return novels.filter(novel => {\n // 单本直接解析为一本书\n if (!novel.seriesId) {\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.authorFilter = function(novels) {\n let blockAuthorList = getFromCacheObject(\"blockAuthorList\")\n if (Array.isArray(blockAuthorList)) {\n java.log(`🚫 屏蔽作者ID:${JSON.stringify(blockAuthorList)}`)\n let blockAuthorSet = new Set(blockAuthorList.map(id => String(id)))\n novels = novels.filter(novel => !blockAuthorSet.has(String(novel.userId)))\n }\n return novels\n }\n\n\n // 过滤收藏与追更\n u.novelFilter = function(novels) {\n let novels1 = [], novels2 = []\n let likeNovels = getFromCacheObject(\"likeNovels\")\n let watchedSeries = getFromCacheObject(\"watchedSeries\")\n let novels0 = novels.map(novel => novel.id)\n\n java.log(`${util.checkStatus(!util.settings.HIDE_LIKE_NOVELS)}显示收藏小说`)\n if (util.settings.HIDE_LIKE_NOVELS) {\n novels = novels.filter(novel => !likeNovels.includes(Number(novel.id)))\n novels1 = novels.map(novel => novel.id)\n java.log(`⏬ 过滤收藏:过滤前${novels0.length};过滤后${novels1.length}`)\n }\n\n java.log(`${util.checkStatus(!util.settings.HIDE_WATCHED_SERIES)}显示追更系列`)\n if (util.settings.HIDE_WATCHED_SERIES) {\n novels = novels.filter(novel => !watchedSeries.includes(Number(novel.seriesId)))\n novels2 = novels.map(novel => novel.id)\n if (novels1.length >= 1) novels0 = novels1\n java.log(`⏬ 过滤追更:过滤前${novels0.length};过滤后${novels2.length}`)\n }\n\n let novels3 = novels.map(novel => novel.id)\n if (novels0.length >= 1 && novels3.length === 0) {\n let msg = `⏬ 过滤小说\\n⚠️ 过滤后无结果\\n\\n请根据需要\\n`\n if (util.settings.HIDE_LIKE_NOVELS) msg += \"开启显示收藏小说\\n\"\n if (util.settings.HIDE_WATCHED_SERIES) msg += \"开启显示追更系列\"\n sleepToast(msg, 1)\n }\n return novels\n }\n\n // 过滤描述与标签(屏蔽标签/屏蔽描述)\n u.novelFilter2 = function(novels) {\n const length = novels.length\n let novels0 = novels.map(novel => novel.id)\n let blockCaption = getFromCacheObject(\"blockCaption\")\n if (!blockCaption) blockCaption = getFromCacheObject(\"captionBlockWords\")\n if (blockCaption) {\n // 仅保留没有任何屏蔽词的小说\n // novels = novels.filter(novel => {\n // return !blockCaption.some(item => {\n // if (novel.description !== undefined) return novel.description.includes(item)\n // })\n // })\n novels = novels.filter(novel => !blockCaption.some(item => novel.description.includes(item)))\n let novels2 = novels.map(novel => novel.id)\n java.log(`🚫 屏蔽描述:${blockCaption.join(\"\\n\")}`)\n java.log(`🚫 屏蔽描述:过滤前${novels0.length};过滤后${novels2.length}`)\n }\n\n let blockTags = getFromCacheObject(\"blockTags\")\n if (!blockTags) blockTags = getFromCacheObject(\"tagsBlockWords\")\n if (blockTags) {\n // 仅保留没有任何屏蔽词的小说\n // novels = novels.filter(novel => {\n // return !blockTags.some(item => {\n // if (novel.tags !== undefined) return novel.tags.includes(item)\n // })\n // })\n novels = novels.filter(novel => !blockTags.some(item => novel.tagsList.includes(item)))\n let novels2 = novels.map(novel => novel.id)\n java.log(`🚫 屏蔽标签:${blockTags.join(\"、\")}`)\n java.log(`🚫 屏蔽标签:过滤前${novels0.length};过滤后${novels2.length}`)\n }\n return novels\n }\n\n // 收藏小说/追更系列 写入缓存\n u.saveNovels = function(listInCacheName, list) {\n let listInCache = getFromCacheObject(listInCacheName)\n if (!listInCache) listInCache = []\n\n listInCache = listInCache.concat(list)\n listInCache = Array.from(new Set(listInCache))\n putInCacheObject(listInCacheName, listInCache)\n\n if (listInCacheName === \"likeNovels\") listInCacheName = \"❤️ 收藏小说ID\"\n else if (listInCacheName === \"watchedSeries\") listInCacheName = \"📃 追更系列ID\"\n util.debugFunc(() => {\n java.log(`${listInCacheName}:${JSON.stringify(listInCache)}`)\n })\n }\n\n u.saveAuthors = function(authors) {\n let pixivAuthors = getFromCacheObject(\"pixivAuthors\")\n if (!pixivAuthors) pixivAuthors = {}\n\n pixivAuthors = Object.assign(pixivAuthors, authors)\n putInCacheObject(\"pixivAuthors\", pixivAuthors)\n }\n\n // 处理 novels 列表\n u.handNovels = function(novels, isDetail) {\n if (!isDetail) isDetail = false\n let likeNovels = [], watchedSeries = [], authors = {}\n novels = util.authorFilter(novels)\n novels.forEach(novel => {\n // novel.id = novel.id\n // novel.title = novel.title\n // novel.userName = novel.userName\n // novel.userId = novel.userId\n // novel.tags = novel.tags\n authors[novel.userName] = novel.userId // 加入缓存,便于搜索作者\n if (novel.tags === undefined || novel.tags === null) {\n novel.tags = []\n }\n // 搜索单篇\n if (novel.isOneshot === undefined) {\n // novel.seriesId = novel.seriesId\n // novel.seriesTitle = novel.seriesTitle\n // novel.textCount = novel.textCount\n // novel.description = novel.description\n novel.coverUrl = novel.url\n // novel.createDate = novel.createDate\n // novel.updateDate = novel.updateDate\n }\n\n // 搜索系列\n if (novel.isOneshot !== undefined) {\n if (novel.isOneshot === true) {\n novel.seriesId = undefined\n novel.seriesTitle = undefined\n novel.id = novel.novelId // 获取真正的 novelId\n // novel.title = novel.title\n } else {\n novel.seriesId = novel.id\n novel.seriesTitle = novel.title\n novel.id = novel.novelId = novel.latestEpisodeId // 获取真正的 novelId\n // novel.isWatched = novel.isWatched // 搜索系列可获取\n }\n novel.textCount = novel.textLength\n novel.description = novel.caption\n novel.coverUrl = novel.cover.urls[\"480mw\"]\n novel.createDate = novel.createDateTime\n novel.updateDate = novel.updateDateTime\n }\n\n // 单篇正文详情页\n if (novel.content) {\n novel.novelId = novel.id\n novel.tags = novel.tags.tags.map(item => item.tag)\n novel.textCount = novel.userNovels[`${novel.id}`].textCount\n // novel.latestChapter = novel.title\n // novel.description = novel.description\n novel.coverUrl = novel.userNovels[`${novel.id}`].url\n // novel.createDate = novel.createDate\n novel.updateDate = novel.uploadDate\n\n if (novel.seriesNavData) {\n novel.seriesId = novel.seriesNavData.seriesId\n novel.seriesTitle = novel.seriesNavData.title\n }\n }\n\n // 系列详情\n if (novel.firstNovelId) {\n novel.seriesId = novel.id\n novel.id = novel.novelId = novel.firstNovelId\n novel.seriesTitle = novel.title\n novel.coverUrl = novel.cover.urls[\"480mw\"]\n // novel.isWatched = novel.isWatched // 搜索系列可获取\n }\n\n // 发现:排行榜\n if (novel.rank) {\n // novel.id = novel.id\n // novel.title = novel.title\n novel.userName = novel.user_name\n novel.userId = novel.user_id\n novel.tags = novel.tag_a\n // novel.language = novel.language\n novel.seriesId = novel.series_id\n novel.seriesTitle = novel.series_title || \"\"\n novel.textCount = novel.character_count\n novel.description = novel.comment\n novel.coverUrl = novel.url\n let date = novel.create_date.split(\" \")\n novel.createDate = novel.updateDate = `${date[0]}T${date[1]}:00+09:00`\n novel.isBookmark = novel.is_bookmarked\n }\n\n // 单篇加更多信息,搜索、详情\n if (!novel.seriesId || !util.settings.COMBINE_NOVELS) {\n novel.tags.unshift(\"单本\")\n novel.latestChapter = novel.title\n novel.detailedUrl = urlIP(urlNovelDetailed(novel.id))\n novel.total = 1\n if (novel.bookmarkData) {\n novel.isBookmark = true\n putInCache(`collect${novel.id}`, novel.bookmarkData.id)\n likeNovels.push(Number(novel.id))\n } else {\n novel.isBookmark = false\n }\n }\n\n // 系列添加更多信息,搜索\n if (novel.seriesId && !isDetail && util.settings.COMBINE_NOVELS) {\n novel.title = novel.seriesTitle\n novel.tags.unshift(\"长篇\")\n novel.detailedUrl = urlIP(urlSeriesDetailed(novel.seriesId))\n }\n\n // 系列添加更多信息,详情\n if (novel.seriesId && isDetail && util.settings.COMBINE_NOVELS) {\n let series = getAjaxJson(urlIP(urlSeriesDetailed(novel.seriesId))).body\n novel.id = series.firstNovelId\n novel.title = series.title\n novel.tags = novel.tags.concat(series.tags)\n novel.tags.unshift(\"长篇\")\n novel.textCount = series.publishedTotalCharacterCount\n novel.description = series.caption\n novel.coverUrl = series.cover.urls[\"480mw\"]\n novel.detailedUrl = urlIP(urlSeriesDetailed(novel.seriesId))\n novel.createDate = series.createDate\n novel.updateDate = series.updateDate\n novel.total = series.publishedContentCount\n novel.isWatched = series.isWatched\n if (novel.isWatched === true) {\n watchedSeries.push(Number(novel.seriesId))\n }\n\n // 防止系列首篇无权限获取\n // 发送请求获取第一章 获取标签与简介\n let firstNovel = {}\n try {\n firstNovel = getAjaxJson(urlIP(urlSeriesNovels(novel.seriesId, 30, 0))).body.thumbnails.novel[0]\n novel.id = novel.firstNovelId = firstNovel.id\n novel.tags = novel.tags.concat(firstNovel.tags)\n } catch (e) { // 防止系列首篇无权限获取\n firstNovel = {}\n firstNovel.description = \"\"\n }\n if (novel.description === \"\") {\n novel.description = firstNovel.description\n }\n }\n })\n // 收藏小说/追更系列 写入缓存\n util.saveNovels(\"likeNovels\", likeNovels)\n util.saveNovels(\"watchedSeries\", watchedSeries)\n util.saveAuthors(authors)\n util.debugFunc(() => {\n java.log(`处理小说完成`)\n })\n return novels\n }\n\n // 小说信息格式化\n u.formatNovels = function(novels) {\n novels = util.novelFilter(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.readingTime = `${novel.readingTime / 60} 分钟`\n novel.createDate = dateFormat(novel.createDate)\n novel.updateDate = dateFormat(novel.updateDate)\n\n novel.tagsList = []\n for (let i in novel.tags) {\n let tag = novel.tags[i]\n if (tag.includes(\"/\")) {\n let tags = tag.split(\"/\")\n novel.tagsList = novel.tagsList.concat(tags)\n } else {\n novel.tagsList.push(tag)\n }\n }\n novel.tags = Array.from(new Set(novel.tagsList))\n novel.tags = novel.tags.map(item => `#${item}`)\n novel.tags = novel.tags.join(\",\")\n\n let collectMsg\n if (novel.seriesId) {\n collectMsg = `📃 追更:${util.checkStatus(novel.isWatched)}追更系列`\n } else {\n collectMsg = `❤️ 收藏:${util.checkStatus(novel.isBookmark)}加入收藏`\n }\n\n if (util.settings.MORE_INFORMATION) {\n novel.description = `\\n🅿️ 登录:${util.checkStatus(isLogin())}登录账号\n ${collectMsg}\\n📖 书名:${novel.title}\\n👤 作者:${novel.userName}\n #️ 标签:${novel.tags}\\n⬆️ 上传:${novel.createDate}\n 🔄 更新:${novel.updateDate}\\n📄 简介:${novel.description}`\n } else {\n novel.description = `\\n🅿️ 登录:${util.checkStatus(isLogin())}登录账号\n ${collectMsg}\\n⬆️ 上传:${novel.createDate}\\n🔄 更新:${novel.updateDate}\n 📄 简介:${novel.description}`\n }\n })\n novels = util.novelFilter2(novels)\n return novels\n }\n\n u.getUserNovelsById = function(resp) {\n let novels = [], seriesIds = []\n // 获取系列小说,与 util.handNovels 系列详情兼容\n if (resp.body.novelSeries.length >= 1) {\n resp.body.novelSeries.forEach(novel =>{\n seriesIds.push(novel.id)\n novel.textCount = novel.publishedTotalCharacterCount\n novel.description = novel.caption\n })\n novels = novels.concat(resp.body.novelSeries)\n }\n\n // 获取所有系列内部的小说 ID\n let seriesNovelIds = []\n if (util.environment.IS_LEGADO) {\n let seriesUrls = seriesIds.map(seriesId => urlIP(urlSeriesNovelsTitles(seriesId)))\n // let resp = getAjaxAllJson(seriesUrls).map(resp => resp.body)\n // seriesNovelIds = resp.flat().map(item => item.id)\n seriesNovelIds = getAjaxAllJson(seriesUrls).flatMap(resp => resp.body.map(item => item.id))\n }\n\n if (util.environment.IS_SOURCEREAD) {\n seriesIds.forEach(seriesId => {\n let novels = getAjaxJson(urlIP(urlSeriesNovelsTitles(seriesId))).body\n seriesNovelIds.push.apply(seriesNovelIds, novels.map(novel => novel.id))\n })\n }\n // java.log(`有系列的小说ID:${JSON.stringify(seriesNovelIds)}`)\n // java.log(JSON.stringify(seriesNovelIds.length))\n\n // 获取单篇小说,默认过滤系列小说的 novelId,否则与系列重复\n let novelIds = Object.keys(resp.body.novels)\n novelIds = novelIds.filter(novelId => (!seriesNovelIds.includes(novelId)))\n // java.log(`真单篇的小说ID:${JSON.stringify(novelIds)}`)\n // java.log(JSON.stringify(novelIds.length))\n\n if (novelIds) {\n let singles = getAjaxJson(urlIP(urlNovelsDetailed(getFromCache(\"pixivUid\"), novelIds))).body\n novels = novels.concat(Object.values(singles).reverse())\n }\n return novels\n }\n\n\n // 正文,目录,详情,搜索:从网址获取id,返回单篇小说 res,系列返回首篇小说 res\n // pixiv 默认分享信息中有#号,不会被识别成链接,无法使用添加网址\n u.getNovelRespFirst = function(result) {\n let novelId = 0, res = {\"body\": {}}\n let isJson = isJsonString(result)\n let isHtml = isHtmlString(result)\n\n if (!isJson && isHtml) {\n let id = baseUrl.match(new RegExp(\"\\\\d+\"))[0]\n let pattern = \"(https?://)?(www\\\\.)?pixiv\\\\.net(/ajax)?/users?/\\\\d+\"\n let isAuthor = baseUrl.match(new RegExp(pattern))\n if (isAuthor) {\n java.log(`作者ID:${id}`)\n novelId = Object.keys(getAjaxJson(urlIP(urlUserWorkLatest(id))).body.novels).reverse()[0]\n }\n\n 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 novelId = getAjaxJson(urlIP(urlSeriesNovels(id, 30, 0))).body.thumbnails.novel[0].id\n } else {\n let pattern = \"(https?://)?(www\\\\.)?pixiv\\\\.net/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 (novelId) {\n java.log(`匹配小说ID:${novelId}`)\n res = getAjaxJson(urlIP(urlNovelDetailed(novelId)))\n }\n if (res.error === true) {\n java.log(`无法从 Pixiv 获取当前小说`)\n java.log(JSON.stringify(res))\n }\n return res\n }\n\n u.init(); u.logSource(); u.logEnvironment(); u.logSettings()\n if (u.settings.DEBUG) u.checkPixiv()\n util = u; java.put(\"util\", objStringify(u))\n}\n\nfunction checkMessageThread(checkTimes) {\n if (checkTimes === undefined) {\n checkTimes = Number(getFromCache(\"checkTimes\"))\n }\n if (checkTimes === 0 && isLogin()) {\n let latestMsg = getAjaxJson(urlIP(urlMessageThreadLatest(5)))\n if (latestMsg.error === true) {\n java.log(JSON.stringify(latestMsg))\n } else if (latestMsg.body.total >= 1) {\n let msg = latestMsg.body.message_threads.filter(item => item.thread_name === \"pixiv事務局\")[0]\n if (msg && new Date().getTime()- 1000*msg.modified_at <= 3*24*60*60*1000) { // 3天内进行提示\n sleepToast(`您于 ${timeFormat(1000*msg.modified_at)} 触发 Pixiv 【过度访问】,请修改密码并重新登录。\\n如已修改请忽略`, 3)\n sleepToast(`${msg.latest_content}`, 5)\n java.startBrowser(\"https://accounts.pixiv.net/password/change\",'修改密码')\n }\n }\n }\n putInCache(\"checkTimes\", checkTimes + 1, 4*60*60) // 缓存4h,每4h提醒一次\n // putInCache(\"checkTimes\", checkTimes + 1, 60) // 测试用,缓存60s,每分钟提醒一次\n // java.log(checkTimes + 1)\n}\n\n// 获取请求的user id方便其他ajax请求构造\nfunction getPixivUid() {\n // cache.delete(\"pixivUid\")\n let pixivUid = getFromCache(\"pixivUid\")\n let pixivCookie = getFromCache(\"pixivCookie\")\n if (!pixivUid && pixivCookie) {\n pixivUid = pixivCookie.match(new RegExp(\"\\\\d+\"))[0]\n java.log(`pixivUid: ${pixivUid}`)\n putInCache(\"pixivUid\", pixivUid)\n }\n return pixivUid\n}\n\nfunction getHeaders() {\n let headers = {\n \"accept\": \"application/json\",\n \"accept-encoding\": \"gzip, deflate, br, zstd\",\n \"accept-language\": \"zh-CN\",\n // \"content-type\": \"application/json; charset=utf-8\",\n // \"content-type\": \"application/x-www-form-urlencoded; charset=utf-8\",\n \"origin\": \"https://www.pixiv.net\",\n \"Referer\": \"https://www.pixiv.net/\",\n // \"sec-ch-ua\": `\"Not/A)Brand\";v=\"8\", \"Chromium\";v=\"132\", \"Google Chrome\";v=\"132\"`,\n // \"sec-ch-ua-mobile\": \"?0\",\n // \"sec-ch-ua-platform\": \"Windows\",\n // \"sec-fetch-dest\": \"empty\",\n // \"sec-fetch-mode\": \"cors\",\n // \"sec-fetch-site\": \"same-origin\",\n \"user-agent\": getFromCache(\"userAgent\") || \"\",\n \"x-csrf-token\": getFromCache(\"pixivCsrfToken\") || \"\",\n \"Cookie\": getFromCache(\"pixivCookie\") || \"\"\n }\n putInCacheObject(\"pixivHeaders\", headers)\n return headers\n}\n\npublicFunc()\nif (result.code() === 200) {\n getPixivUid(); getWebViewUA(); util.getCookie(); util.getCsrfToken(); getHeaders()\n if (!util.settings.FAST) checkMessageThread() // 检测过度访问\n}\njava.getStrResponse(null, null)", "loginUi": "[\n {\n \"name\": \"🅿️ 登录账号\",\n \"type\": \"button\",\n \"action\": \"login()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"⚙️ 账号设置\",\n \"type\": \"button\",\n \"action\": \"startPixivSettings()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🔙 退出账号\",\n \"type\": \"button\",\n \"action\": \"logout()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🆙 更新书源\",\n \"type\": \"button\",\n \"action\": \"updateSource()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🔰 功能手册\",\n \"type\": \"button\",\n \"action\": \"startGithubReadme()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"✈️ 直连模式\",\n \"type\": \"button\",\n \"action\": \"editSettings('IPDirect')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"章节名称\",\n \"type\": \"text\"\n },\n {\n \"name\": \"❤️ 收藏本章\",\n \"type\": \"button\",\n \"action\": \"novelBookmarkFactory(1)\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"📃 追更系列\",\n \"type\": \"button\",\n \"action\": \"seriesWatchFactory()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"❤️ 收藏系列\",\n \"type\": \"button\",\n \"action\": \"novelsBookmarkAdd()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🖤 取消收藏\",\n \"type\": \"button\",\n \"action\": \"novelsBookmarkDelete()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"⭐️ 关注作者\",\n \"type\": \"button\",\n \"action\": \"userFollowFactory()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🚫 屏蔽作者\",\n \"type\": \"button\",\n \"action\": \"userBlock()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n\n {\n \"name\": \"文本框\",\n \"type\": \"text\"\n },\n {\n \"name\": \"✅ 发送评论\",\n \"type\": \"button\",\n \"action\": \"novelCommentAdd()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🗑️ 删除评论\",\n \"type\": \"button\",\n \"action\": \"novelCommentDelete()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🔄 刷新本章\",\n \"type\": \"button\",\n \"action\": \"cleanCache()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n\n {\n \"name\": \"🚫 添加屏蔽\",\n \"type\": \"button\",\n \"action\": \"blockAddFactory()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"⭕️ 删除屏蔽\",\n \"type\": \"button\",\n \"action\": \"blockDeleteFactory()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"👀 查看屏蔽\",\n \"type\": \"button\",\n \"action\": \"blockShowFactory()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n\n {\n \"name\": \"📌 喜欢标签\",\n \"type\": \"button\",\n \"action\": \"likeTagsAdd()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🗑️ 删除标签\",\n \"type\": \"button\",\n \"action\": \"likeTagsDelete()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"👀 查看标签\",\n \"type\": \"button\",\n \"action\": \"likeTagsShow()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n\n {\n \"name\": \"❤️ 他人收藏\",\n \"type\": \"button\",\n \"action\": \"likeAuthorsAdd()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🖤 取消收藏\",\n \"type\": \"button\",\n \"action\": \"likeAuthorsDelete()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"👀 查看收藏\",\n \"type\": \"button\",\n \"action\": \"likeAuthorsShow()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n\n {\n \"name\": \"书源设置\",\n \"type\": \"text\"\n },\n {\n \"name\": \"💾 备份恢复\",\n \"type\": \"button\",\n \"action\": \"backupRestore()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"⚙️ 当前设置\",\n \"type\": \"button\",\n \"action\": \"showSettings()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🔧 默认设置\",\n \"type\": \"button\",\n \"action\": \"setDefaultSettingsLoginUrl()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"👤 搜索作者\",\n \"type\": \"button\",\n \"action\": \"editSettings('SEARCH_AUTHOR')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🀄 繁简通搜\",\n \"type\": \"button\",\n \"action\": \"editSettings('CONVERT_CHINESE')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"📖 更多简介\",\n \"type\": \"button\",\n \"action\": \"editSettings('MORE_INFORMATION')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🔢 章节编号\",\n \"type\": \"button\",\n \"action\": \"editSettings('ADD_CHAPTER_INDEX')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"📅 更新时间\",\n \"type\": \"button\",\n \"action\": \"editSettings('SHOW_UPDATE_TIME')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🔗 原始链接\",\n \"type\": \"button\",\n \"action\": \"editSettings('SHOW_ORIGINAL_LINK')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"📄 显示描述\",\n \"type\": \"button\",\n \"action\": \"editSettings('SHOW_CAPTIONS')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🖼️ 显示插图\",\n \"type\": \"button\",\n \"action\": \"editSettings('SHOW_PICTURES')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"✅ 显示投票\",\n \"type\": \"button\",\n \"action\": \"editSettings('SHOW_QUESTION')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"💬 显示评论\",\n \"type\": \"button\",\n \"action\": \"editSettings('SHOW_COMMENTS')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"📚 恢复《》\",\n \"type\": \"button\",\n \"action\": \"editSettings('REPLACE_TITLE_MARKS')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"❤️ 自动收藏\",\n \"type\": \"button\",\n \"action\": \"editSettings('AUTO_LIKE_NOVELS')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🖤 自动取消\",\n \"type\": \"button\",\n \"action\": \"editSettings('AUTO_DISLIKE_NOVELS')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"❤️ 隐藏收藏\",\n \"type\": \"button\",\n \"action\": \"editSettings('HIDE_LIKE_NOVELS')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"📃 隐藏追更\",\n \"type\": \"button\",\n \"action\": \"editSettings('HIDE_WATCHED_SERIES')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"📚 整合系列\",\n \"type\": \"button\",\n \"action\": \"editSettings('COMBINE_NOVELS')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"⏩ 快速模式\",\n \"type\": \"button\",\n \"action\": \"editSettings('FAST')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🐞 调试模式\",\n \"type\": \"button\",\n \"action\": \"editSettings('DEBUG')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n// {\n// \"name\": \"✈️ 直连模式\",\n// \"type\": \"button\",\n// \"action\": \"editSettings('IPDirect')\",\n// \"style\": {\n// \"layout_flexGrow\": 1,\n// \"layout_flexBasisPercent\": -1\n// }\n// },\n//\n// {\n// \"name\": \"⏳ 图片解析\",\n// \"type\": \"button\",\n// \"action\": \"editSettings('PIC_SOURCE')\",\n// \"style\": {\n// \"layout_flexGrow\": 1,\n// \"layout_flexBasisPercent\": -1\n// }\n// },\n// {\n// \"name\": \"🔗 图片链接\",\n// \"type\": \"button\",\n// \"action\": \"editSettings('PIC_LINK')\",\n// \"style\": {\n// \"layout_flexGrow\": 1,\n// \"layout_flexBasisPercent\": -1\n// }\n// },\n// {\n// \"name\": \"↔️ 图片大小\",\n// \"type\": \"button\",\n// \"action\": \"editSettings('PIC_SIZE')\",\n// \"style\": {\n// \"layout_flexGrow\": 1,\n// \"layout_flexBasisPercent\": -1\n// }\n// },\n\n {\n \"name\": \"发现设置\",\n \"type\": \"text\"\n },\n {\n \"name\": \"🔍 当前发现\",\n \"type\": \"button\",\n \"action\": \"showSettingsDiscover()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🔞 默认发现\",\n \"type\": \"button\",\n \"action\": \"editSettings('SHOW_ADULT')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🆗 常规小说\",\n \"type\": \"button\",\n \"action\": \"editSettings('SHOW_GENERAL')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🔞 最新企划\",\n \"type\": \"button\",\n \"action\": \"editSettings('SHOW_NEW_ADULT')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🆗 最新企划\",\n \"type\": \"button\",\n \"action\": \"editSettings('SHOW_NEW_GENERAL')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🔞 排行榜单\",\n \"type\": \"button\",\n \"action\": \"editSettings('SHOW_RANK_ADULT')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🆗 排行榜单\",\n \"type\": \"button\",\n \"action\": \"editSettings('SHOW_RANK_GENERAL')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🔞 原创热门\",\n \"type\": \"button\",\n \"action\": \"editSettings('SHOW_GENRE_ADULT')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🆗 原创热门\",\n \"type\": \"button\",\n \"action\": \"editSettings('SHOW_GENRE_GENERAL')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"📚 书源相关\",\n \"type\": \"button\",\n \"action\": \"editSettings('SHOW_ABOUT')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🦊 兽人小说\",\n \"type\": \"button\",\n \"action\": \"editSettings('SHOW_FURRY')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"🐺 兽人作者\",\n \"type\": \"button\",\n \"action\": \"updatePixivAuthors()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n }\n]", "loginUrl": "function login() {\n let resp = java.startBrowserAwait(`https://accounts.pixiv.net/login,\n {\"headers\": {\"User-Agent\": ${getWebViewUA()}}}`, '登录账号', false)\n if (resp.code() === 200) {\n getCsrfToken(); getCookie()\n return true\n } else {\n java.log(resp.code()); sleepToast(\"🅿️ 登录账号\\n\\n⚠️ 登录失败\")\n return false\n }\n}\n\nfunction logout() {\n removeCookie()\n java.startBrowser(\"https://www.pixiv.net/logout.php\", \"退出账号\")\n removeCookie(); removeLikeDataCache()\n sleepToast(`✅ 已退出当前账号\\n\\n退出后请点击右上角的 ✔️ 退出\\n\\n登录请点击【登录账号】进行登录`)\n}\n\nfunction removeCookie() {\n cookie.removeCookie('https://www.pixiv.net')\n cookie.removeCookie('https://accounts.pixiv.net')\n cookie.removeCookie('https://accounts.google.com')\n cookie.removeCookie('https://api.weibo.com')\n cache.delete(\"pixivUid\")\n cache.delete(\"pixivCookie\")\n cache.delete(\"pixivCsrfToken\")\n cache.delete(\"PixivHeaders\")\n}\n\nfunction removeCacheList(listName) {\n let list = getFromCacheObject(listName)\n list.forEach(item => cache.delete(`collect${item}`))\n if (listName !== \"blockAuthorList\") cache.delete(listName)\n}\n\nfunction removeLikeDataCache() {\n // 删除 likeNovels 与 watchedSeries\n removeCacheList(\"likeNovels\")\n removeCacheList(\"watchedSeries\")\n}\n\nfunction getCookie() {\n let pixivCookie = String(java.getCookie(\"https://www.pixiv.net/\", null))\n if (!isLogin()) return \"\"\n putInCache(\"pixivCookie\", pixivCookie, 60*60)\n return pixivCookie\n}\n\n// 获取 Csrf Token,以便进行收藏等请求\n// 获取方法来自脚本 Pixiv Previewer\n// https://github.com/Ocrosoft/PixivPreviewer\n// https://greasyfork.org/zh-CN/scripts/30766-pixiv-previewer/code\nfunction getCsrfToken() {\n let pixivCsrfToken = getFromCache(\"pixivCsrfToken\")\n if (!pixivCsrfToken) {\n let html = java.ajax(\"https://www.pixiv.net/\")\n try {\n pixivCsrfToken = html.match(/token\\\\\":\\\\\"([a-z0-9]{32})/)[1]\n putInCache(\"pixivCsrfToken\", pixivCsrfToken) // 与登录设备有关,无法存储 nul\n } catch (e) {\n pixivCsrfToken = null\n cache.delete(\"pixivCsrfToken\") // 与登录设备有关,无法存储 nul\n // sleepToast(\"⚠️ 未登录账号(pixivCsrfToken)\")\n }\n java.log(`pixivCsrfToken:\\n${pixivCsrfToken}`)\n }\n return pixivCsrfToken\n}\n\nfunction getNovelId(seriesId) {\n if (chapter) {\n try {\n return chapter.url.match(/novel\\/(\\d+)/)[1]\n } catch (e) {\n return chapter.url.match(/\\d+/)[0]\n }\n }\n\n if (!book.bookUrl.includes(\"series\")) {\n return book.bookUrl.match(/\\d+/)[0]\n } else {\n seriesId = book.bookUrl.match(/\\d+/)[0]\n }\n\n if (seriesId) {\n let novelIds = getFromCacheObject(`novelIds${seriesId}`)\n if (novelIds) {\n return getFromCacheObject(`novelIds${seriesId}`)[book.durChapterIndex]\n } else {\n return getAjaxJson(urlIP(urlSeriesNovelsTitles(seriesId)), true).body[book.durChapterIndex].id\n }\n }\n}\n\nfunction getNovel() {\n let environment = getFromCacheObject(\"pixivEnvironment\")\n if (environment.IS_LEGADO_SIGMA) {\n try {\n let novel = {}\n if (book.bookUrl.includes(\"series\")) {\n novel.seriesId = book.bookUrl.match(/\\d+/)[0]\n novel.seriesTitle = book.name\n novel.id = getNovelId(novel.seriesId)\n novel.title = book.durChapterTitle\n } else {\n novel.seriesId = 0\n novel.seriesTitle = \"\"\n novel.id = book.bookUrl.match(/\\d+/)[0]\n novel.title = book.name\n }\n novel.author = novel.userName = book.author.replace(\"@\", \"\")\n let resp = getAjaxJson(urlIP(urlNovelDetailed(novel.id))).body\n novel.userId = resp.userId\n novel.question = resp?.pollData?.question || \"\"\n // java.log(JSON.stringify(novel))\n return novel\n } catch (e) {\n // 无法阻止后续函数在日志中报错\n return sleepToast(\"🔰 功能提示\\n\\n⚠️ 请在【小说正文】使用该功能\")\n }\n } else { // 兼容用\n return getFromCacheObject(\"novel\")\n }\n}\n\nfunction getPostBody(url, body, headers) {\n if (headers === undefined) headers = getFromCacheObject(\"pixivHeaders\")\n // if (headers === undefined) headers = getFromCacheObject(\"headers\")\n if (isJsonString(body)) {\n headers[\"content-type\"] = \"application/json; charset=utf-8\"\n } else if (typeof body === \"string\") {\n headers[\"content-type\"] = \"application/x-www-form-urlencoded; charset=utf-8\"\n }\n\n let settings = getFromCacheObject(\"pixivSettings\")\n if (settings.IPDirect) {\n url = url.replace(\"http://\", \"https://\").replace(\"www.pixiv.net\", \"210.140.139.155\")\n headers[\"Host\"] = \"www.pixiv.net\"\n }\n try {\n java.log(`getPostBody(${url}, ${body}, ${headers})`)\n // java.log(`getPostBody(${url}, ${body}, ${JSON.stringify(headers)})`)\n return JSON.parse(java.post(url, body, headers).body())\n } catch (e) {\n e = String(e)\n // sleepToast(e)\n // sleepToast(JSON.stringify(headers))\n if (e.includes(\"400\")) sleepToast(`📤 getPostBody\\n\\n⚠️ 缺少 headers`, 1)\n // else if (e.includes(\"401\")) sleepToast(`📤 getPostBody\\n\\n⚠️ 缺少 cookie 或 cookie 过期`, 1)\n // else if (e.includes(\"403\")) sleepToast(`📤 getPostBody\\n\\n⚠️ 缺少 cookie 或 cookie 过期`, 1)\n else if (e.includes(\"404\")) sleepToast(`📤 getPostBody\\n\\n⚠️ 404 缺少 pixivCsrfToken `, 1)\n else if (e.includes(\"422\")) sleepToast(`📤 getPostBody\\n\\n⚠️ 请求信息有误`, 1)\n return {error: true, errMsg:e}\n }\n}\n\nfunction novelBookmarkAdd() {\n let restrict = 0\n let novel = getNovel()\n let novelObj = getAjaxJson(urlNovelDetailed(novel.id), true)\n if (novelObj.body.bookmarkData && novelObj.body.bookmarkData.private === false) restrict = 1\n let resp = getPostBody(\n \"https://www.pixiv.net/ajax/novels/bookmarks/add\",\n JSON.stringify({\"novel_id\": novel.id, \"restrict\": restrict, \"comment\":\"\", \"tags\":[]})\n )\n if (resp.error === true) {\n sleepToast(`❤️ 收藏小说\\n\\n⚠️ 收藏【${novel.title}】失败`)\n shareFactory(\"novel\")\n } else {\n putInCacheObject(`collect${novel.id}`, resp.body)\n let likeNovels = getFromCacheObject(\"likeNovels\")\n likeNovels.push(Number(novel.id))\n putInCacheObject(\"likeNovels\", likeNovels)\n\n let novelObj = getAjaxJson(urlNovelDetailed(novel.id))\n novelObj.body.isBookmark = true\n putInCacheObject(urlNovelDetailed(novel.id), novelObj, cacheSaveSeconds)\n }\n\n if (restrict === 1) {\n sleepToast(`㊙️ 私密收藏\\n\\n✅ 已私密收藏\\n${novel.title}`)\n } else {\n sleepToast(`❤️ 公开收藏\\n\\n✅ 已公开收藏\\n${novel.title}`)\n }\n}\n\nfunction getNovelBookmarkId(novelId) {\n let bookmarkId = getFromCacheObject(`collect${novelId}`)\n if (bookmarkId === null) {\n try {\n bookmarkId = getAjaxJson(urlNovelBookmarkData(novelId), true).body.bookmarkData.id\n } catch (e) {\n bookmarkId = 0\n }\n }\n return bookmarkId\n}\n\nfunction novelBookmarkDelete() {\n let novel = getNovel()\n let bookmarkId = getNovelBookmarkId(novel.id)\n if (bookmarkId === 0) return sleepToast(`🖤 取消收藏\\n\\n✅ 已经取消收藏\\n${novel.title}`)\n\n let resp = getPostBody(\n \"https://www.pixiv.net/ajax/novels/bookmarks/delete\",\n `del=1&book_id=${bookmarkId}`\n )\n if (resp.error === true) {\n sleepToast(`🖤 取消收藏\\n\\n⚠️ 取消收藏失败\\n${novel.title}`)\n shareFactory(\"novel\")\n } else {\n cache.delete(`collect${novel.id}`)\n sleepToast(`🖤 取消收藏\\n\\n✅ 已经取消收藏\\n${novel.title}`)\n\n let likeNovels = getFromCacheObject(\"likeNovels\")\n likeNovels = likeNovels.filter(item => item !== Number(novel.id))\n putInCacheObject(\"likeNovels\", likeNovels)\n\n let novelObj = getAjaxJson(urlNovelDetailed(novel.id))\n novelObj.body.isBookmark = false\n putInCacheObject(urlNovelDetailed(novel.id), novelObj, cacheSaveSeconds)\n }\n}\n\nfunction novelsBookmarkDelete() {\n let novel = getNovel()\n if (!isLongClick) {\n if (!novel.seriesId) sleepToast(`🖤 取消收藏\\n\\n正在取消收藏【本章】`)\n else sleepToast(`🖤 取消收藏\\n\\n正在取消收藏【本章】\\n长按可取消收藏【整个系列】`)\n return novelBookmarkDelete(0)\n }\n if (isLongClick && !novel.seriesId) {\n return (`🖤 取消收藏系列\\n\\n⚠️ 【${novel.title}】非系列小说`)\n }\n sleepToast(`🖤 取消收藏系列\\n\\n🔄 正在取消收藏系列,请稍后……`, 2)\n\n let bookmarkIds = []\n let novelIds = getFromCacheObject(`novelIds${novel.seriesId}`)\n novelIds.forEach(novelId => {\n let bookmarkId = getNovelBookmarkId(novelId)\n if (bookmarkId) bookmarkIds.push(getNovelBookmarkId(novelId))\n })\n let resp = getPostBody(\n \"https://www.pixiv.net/ajax/novels/bookmarks/remove\",\n JSON.stringify({\"bookmarkIds\": bookmarkIds})\n )\n if (resp.error === true) {\n sleepToast(`🖤 取消收藏系列\\n\\n⚠️ 取消收藏【${novel.seriesTitle}】的篇目失败`, 2)\n shareFactory(\"series\")\n } else {\n sleepToast(`🖤 取消收藏系列\\n\\n✅ 已取消收藏【${novel.seriesTitle}】的全部篇目`)\n novelIds.forEach(novelId => {cache.delete(`collect${novelId}`)})\n\n let likeNovels = getFromCacheObject(\"likeNovels\")\n likeNovels = likeNovels.filter(item => !novelIds.includes(Number(item)))\n putInCacheObject(\"likeNovels\", likeNovels)\n\n novelIds.forEach(novelId => {\n let novelObj = getAjaxJson(urlNovelDetailed(novelId))\n novelObj.body.isBookmark = false\n putInCacheObject(urlNovelDetailed(novelId), novelObj, cacheSaveSeconds)\n })\n }\n}\n\nfunction novelsBookmarkAdd() {\n let novel = getNovel()\n if (!novel.seriesId) {\n sleepToast(`❤️ 收藏系列\\n\\n⚠️ 【${novel.title}】非系列小说,现已收藏本篇小说`)\n return novelBookmarkAdd(0)\n } else {\n sleepToast(`❤️ 收藏系列\\n\\n🔄 正在收藏系列【${novel.seriesTitle}】,请稍后……`, 2)\n }\n\n let novelIds = getFromCacheObject(`novelIds${novel.seriesId}`)\n let likeNovels = getFromCacheObject(\"likeNovels\")\n if (likeNovels === null) likeNovels = []\n novelIds.forEach(novelId => {\n if (likeNovels && !likeNovels.includes(Number(novelId))) {\n sleep(0.5 * 1000 * Math.random())\n let resp = getPostBody(\n \"https://www.pixiv.net/ajax/novels/bookmarks/add\",\n JSON.stringify({\"novel_id\": novelId, \"restrict\": 0, \"comment\": \"\", \"tags\": []})\n )\n\n if (resp.error === true) {\n sleepToast(`❤️ 收藏系列\\n\\n⚠️ 收藏【${novelId}】失败`)\n shareFactory(\"series\")\n } else if (resp.body === null) {\n // sleepToast(`❤️ 收藏小说\\n\\n✅ 已经收藏【${novel.title}】了`)\n } else {\n putInCacheObject(`collect${novelId}`, resp.body)\n likeNovels.push(Number(novelId))\n\n let novelObj = getAjaxJson(urlNovelDetailed(novelId))\n novelObj.body.isBookmark = true\n putInCacheObject(urlNovelDetailed(novelId), novelObj, cacheSaveSeconds)\n }\n }\n })\n putInCacheObject(\"likeNovels\", likeNovels)\n sleepToast(`❤️ 收藏系列\\n\\n✅ 已经收藏【${novel.seriesTitle}】全部章节`)\n}\n\nfunction novelMarker(page) {\n if (page === undefined) page = 1\n let novel = getNovel()\n let lastMarker = getFromCacheObject(`marker${novel.id}`)\n if (lastMarker === true) page = 0\n\n let resp = getPostBody(\n \"https://www.pixiv.net/novel/rpc_marker.php\",\n `mode=save&i_id=${novel.id}&u_id=${getFromCacheObject(\"pixivUid\")}&page=${page}`\n )\n java.log(`mode=save&i_id=${novel.id}&u_id=${getFromCacheObject(\"pixivUid\")}&page=${page}`)\n if (resp.error === true) {\n sleepToast(\"🏷️ 添加书签\\n\\n⚠️ 操作失败\", 1)\n shareFactory(\"novel\")\n } else if (lastMarker === true) {\n putInCache(`marker${novel.id}`, false)\n sleepToast(`🏷️ 添加书签\\n\\n✅ 已删除书签`)\n } else {\n putInCache(`marker${novel.id}`, true)\n sleepToast(`🏷️ 添加书签\\n\\n✅ 已加入书签`)\n }\n}\n\nfunction seriesWatch() {\n let novel = getNovel()\n let resp = getPostBody(\n `https://www.pixiv.net/ajax/novel/series/${novel.seriesId}/watch`,\n \"{}\"\n )\n if (resp.error === true) {\n sleepToast(`📃 追更系列\\n\\n⚠️ 追更【${novel.seriesTitle}】失败`, 1)\n shareFactory(\"series\")\n } else {\n putInCache(`watch${novel.seriesId}`, true)\n sleepToast(`📃 追更系列\\n\\n✅ 已追更【${novel.seriesTitle}】`)\n\n let watchedSeries = getFromCacheObject(\"watchedSeries\")\n watchedSeries.push(Number(novel.seriesId))\n putInCacheObject(\"watchedSeries\", watchedSeries)\n\n let novelObj = getAjaxJson(urlSeriesDetailed(novel.seriesId))\n novelObj.body.isWatched = true\n putInCacheObject(urlSeriesDetailed(novel.seriesId), novelObj, cacheSaveSeconds)\n }\n}\n\nfunction seriesUnWatch() {\n let novel = getNovel()\n let resp = getPostBody(\n `https://www.pixiv.net/ajax/novel/series/${novel.seriesId}/unwatch`,\n \"{}\"\n )\n if (resp.error === true) {\n sleepToast(`📃 追更系列\\n\\n⚠️ 取消追更【${novel.seriesTitle}】失败`, 1)\n shareFactory(\"series\")\n } else {\n cache.delete(`watch${novel.seriesId}`)\n sleepToast(`📃 追更系列\\n\\n✅ 已取消追更【${novel.seriesTitle}】`)\n\n let watchedSeries = getFromCacheObject(\"watchedSeries\")\n watchedSeries = watchedSeries.filter(item => item !== Number(novel.seriesId))\n putInCacheObject(\"watchedSeries\", watchedSeries)\n\n let novelObj = getAjaxJson(urlSeriesDetailed(novel.seriesId))\n novelObj.body.isWatched = false\n putInCacheObject(urlSeriesDetailed(novel.seriesId), novelObj, cacheSaveSeconds)\n }\n}\n\nfunction seriesWatchFactory(code) {\n if (code === undefined) code = 1\n let novel = getNovel()\n if (!novel.seriesId) {\n return sleepToast(`📃 追更系列\\n\\n⚠️ 【${novel.title}】非系列小说,无法加入追更列表`)\n }\n\n let lastStatus = getFromCacheObject(`watch${novel.seriesId}`)\n if (lastStatus === true) code = 0\n if (code === 0) seriesUnWatch()\n else if (code === 1) seriesWatch()\n}\n\nfunction userFollow(restrict) {\n if (restrict === undefined) restrict = 0\n let novel = getNovel()\n let resp = getPostBody(\n \"https://www.pixiv.net/bookmark_add.php\",\n `mode=add&type=user&user_id=${novel.userId}&tag=\"\"&restrict=${restrict}&format=json`\n )\n if (resp.error) {\n sleepToast(`⭐️ 关注作者\\n\\n⚠️ 关注【${novel.userName}】失败\\n已打开浏览器,请(上滑)手动关注`, 1)\n java.startBrowserAwait(`${urlUserUrl(novel.userId)},\n {\"User-Agent\": ${getWebViewUA()}}}`, `关注${novel.userName}`, false)\n }\n sleepToast(`⭐️ 关注作者\\n\\n✅ 已关注【${novel.userName}】`)\n}\n\nfunction userUnFollow() {\n let novel = getNovel()\n let resp = getPostBody(\n \"https://www.pixiv.net/rpc_group_setting.php\",\n `mode=del&type=bookuser&id=${novel.userId}`\n )\n if (resp.error === true) {\n sleepToast(`⭐️ 关注作者\\n\\n⚠️ 取消关注【${novel.userName}】失败`, 1)\n shareFactory(\"author\")\n } else {\n sleepToast(`⭐️ 关注作者\\n\\n✅ 已取消关注【${novel.userName}】`)\n }\n}\n\nfunction userFollowFactory() {\n let novel = getNovel()\n let lastStatus = getAjaxJson(urlUserDetailed(novel.userId), true).body.isFollowed\n if (lastStatus) userUnFollow()\n else userFollow()\n}\n\nfunction userBlock() {\n let authors = getFromCacheObject(\"blockAuthorList\")\n if (!authors) authors = []\n let authorsMap = getFromCacheMap(\"blockAuthorMap\")\n if (!authorsMap || authorsMap.size === 0) {\n authorsMap = new Map()\n authors.forEach(author => {\n authorsMap.set(author, getAjaxJson(urlUserDetailed(author)).body.name)\n })\n }\n\n let novel = getNovel()\n if (authorsMap.has(String(novel.userId))) {\n authorsMap.delete(String(novel.userId))\n sleepToast(`🚫 屏蔽作者\\n\\n✅ 已取消屏蔽【${novel.userName}】\\n现已恢复显示其小说`)\n } else if (!!novel.userId) {\n authorsMap.set(String(novel.userId), novel.userName)\n sleepToast(`🚫 屏蔽作者\\n\\n✅ 本地已屏蔽【${novel.userName}】\\n今后不再显示其小说`)\n }\n\n authors = Array.from(authorsMap.keys())\n putInCacheObject(\"blockAuthorList\", authors)\n putInCacheMap(\"blockAuthorMap\", authorsMap)\n // source.setVariable(authors.toString())\n // sleepToast(JSON.stringify(authors))\n}\n\n// 拆分长评论\nfunction splitComments(text) {\n if (!text) return []\n let limit = 140\n\n // 1. 预留序号空间(例如 \" (10/10)\" 占 8 个字符,预留 10 个以防万一)\n const reservedSpace = 10\n const safeLimit = limit - reservedSpace\n\n // 2. 核心拆分逻辑\n let chars = Array.from(text.trim())\n let tempSegments = []\n\n const strongPunc = /[。!?…\\uff0e\\uff01\\uff1f!?.…]/ // 强断句标点\n const weakPunc = /[\\uff0c\\uff1b,;]/ // 弱断句标点\n\n while (chars.length > 0) {\n if (chars.length <= safeLimit) {\n tempSegments.push(chars.join('').trim())\n break\n }\n\n let chunk = chars.slice(0, safeLimit)\n let splitIndex = -1\n\n // 优先级 1: 换行符\n for (let i = chunk.length - 1; i >= 0; i--) {\n if (chunk[i] === '\\n') {\n splitIndex = i\n break\n }\n }\n\n // 优先级 2: 强标点(。!?等)\n if (splitIndex === -1) {\n for (let i = chunk.length - 1; i >= 0; i--) {\n if (strongPunc.test(chunk[i])) {\n splitIndex = i\n break\n }\n }\n }\n\n // 优先级 3: 弱标点(仅在没找到强标点时使用逗号)\n if (splitIndex === -1) {\n for (let i = chunk.length - 1; i >= 0; i--) {\n if (weakPunc.test(chunk[i])) {\n splitIndex = i\n break\n }\n }\n }\n\n // 兜底: 硬截断\n if (splitIndex === -1) {\n splitIndex = safeLimit - 1\n }\n\n // 截取并清理\n let segment = chars.slice(0, splitIndex + 1).join('').trim();\n if (segment) tempSegments.push(segment)\n\n // 移除已处理字符并跳过开头的空白\n chars = chars.slice(splitIndex + 1);\n while (chars.length > 0 && (chars[0] === '\\n' || chars[0] === ' ')) {\n chars.shift()\n }\n }\n\n // 3. 注入序号\n const total = tempSegments.length;\n if (total <= 1) return tempSegments;\n return tempSegments.map((content, i) => `${content} (${i + 1}/${total})`).reverse();\n}\n\nfunction novelCommentAdd() {\n let resp, novel = getNovel()\n let userId = getFromCacheObject(\"pixivUid\")\n let comment = String(result.get(\"文本框\")).trim()\n if (comment === \"\") {\n return sleepToast(`✅ 发送评论\\n⚠️ 请在【文本框】内输入评论\\n\\n输入【评论内容;评论ID】可回复该条评论,如【非常喜欢;123456】`)\n }\n\n let comments = splitComments(comment)\n if (comments.length >= 2) sleepToast(\"✅ 发送评论\\n\\n正在拆分长评论,即将逐条发送\")\n comments.forEach(comment => {\n sleep(0.5 * Math.random())\n let matched = comment.match(RegExp(/(;|;\\s*)\\d{8,}/))\n if (matched) {\n let commentId = comment.match(new RegExp(/;(\\d{8,})/))[1]\n comment = comment.replace(new RegExp(`(;|;\\s*)${commentId}`), \"\")\n resp = getPostBody(\n \"https://www.pixiv.net/novel/rpc/post_comment.php\",\n `type=comment&novel_id=${novel.id}&author_user_id=${userId}&comment=${encodeURI(comment)}&parent_id=${commentId}`)\n } else {\n resp = getPostBody(\n \"https://www.pixiv.net/novel/rpc/post_comment.php\",\n `type=comment&novel_id=${novel.id}&author_user_id=${userId}&comment=${encodeURI(comment)}`\n )\n }\n\n if (resp.error === true) {\n sleepToast(\"✅ 发送评论\\n\\n⚠️ 评论失败\", 1)\n shareFactory(\"novel\")\n } else {\n sleepToast(`✅ 发送评论\\n\\n✅ 已在【${novel.title}】发布评论:\\n${comment}`, 1)\n }\n })\n try {java.refreshContent()} catch(err) {}\n if (comments.length >= 2) sleepToast(\"✅ 发送评论\\n\\n✅ 长评论已发送完毕\", 1)\n}\n\nfunction getNovelCommentID(novelId, commentText) {\n let list = [], uid = String(getFromCacheObject(\"pixivUid\"))\n let resp = getAjaxJson(urlNovelComments(novelId, 0, 50), true)\n resp.body.comments.forEach(comment => {\n if (comment.userId === uid && comment.comment === commentText) list.push(comment.id)\n\n if (comment.hasReplies === true) {\n let resp = getAjaxJson(urlNovelCommentsReply(comment.id, 1), true)\n resp.body.comments.forEach(comment => {\n if (comment.userId === uid && comment.comment === commentText) list.push(comment.id)\n })\n }\n })\n // java.log(JSON.stringify(list))\n return list\n}\n\nfunction novelCommentDelete() {\n let commentIDs, novel = getNovel()\n let comment = String(result.get(\"文本框\")).trim()\n if (comment === \"\") {\n return sleepToast(`🗑 删除评论\\n⚠️ 请在【文本框】内输入需要删除的【评论ID】,以分号间隔\\n或输入需要删除的【评论内容】\\n\\n如:【123;456;789】\\n或【模拟评论内容】`)\n }\n\n if (RegExp(/[;;]/).test(comment)) {\n commentIDs = comment.split(/[;;]/)\n .map(item => item.trim()) // 去除每个元素前后的空格\n .filter(item => item !== \"\") // 过滤掉因为末尾分号产生的空项\n } else if (RegExp(/\\d{8,}/).test(comment)) {\n let matched = comment.match(/\\d{8,}/g)\n commentIDs = Array.from(matched || [])\n } else {\n commentIDs = getNovelCommentID(novel.id, comment)\n // java.log(JSON.stringify(commentIDs))\n if (commentIDs.length === 0) {\n return sleepToast(`🗑 删除评论\\n\\n⚠️ 未能找到这条评论\\n请检查是否有错别字或标点符号是否一致`)\n }\n }\n\n commentIDs.forEach(commentID =>{\n sleep(0.5 * 1000 * Math.random())\n let resp = getPostBody(\n \"https://www.pixiv.net/novel/rpc_delete_comment.php\",\n `i_id=${novel.id}&del_id=${commentID}`\n )\n // java.log(JSON.stringify(resp))\n if (resp.error === true) {\n sleepToast(\"🗑 删除评论\\n\\n⚠️ 评论删除失败\", 1)\n shareFactory(\"novel\")\n } else {\n let isCommentText = !RegExp(/[;;]/).test(comment) && !RegExp(/\\d{8,}/).test(comment)\n let toastComment = isCommentText ? comment : commentID\n sleepToast(`🗑 删除评论\\n\\n✅ 已在【${novel.title}】删除评论:\\n${toastComment}`, 1)\n }\n })\n try {java.refreshContent()} catch(err) {}\n if (comments.length >= 2) sleepToast(\"🗑 删除评论\\n\\n✅ 评论已删除完毕\", 1)\n}\n\nfunction novelPollAnswer(choiceId) {\n let novel = getNovel()\n let resp = getPostBody(\n `https://www.pixiv.net/ajax/novel/${novel.id}/poll/answer`,\n JSON.stringify({\"choice_id\": choiceId})\n )\n // 200 成功,401 未登录,403 重复投票,400 选项超过范围\n if (resp.error === true) {\n if (resp.errMsg.includes(\"401\")) {\n sleepToast(`📃 小说投票\\n\\n⚠️ 请先登录,再投票哦`, 3)\n } else if (resp.errMsg.includes(\"403\")) {\n cleanCache(0)\n sleepToast(`📃 小说投票\\n\\n✅ 你已经投过票了`, 3)\n } else {\n sleepToast(`📃 小说投票\\n\\n⚠️ 投票失败`, 3)\n shareFactory(\"novel\")\n }\n } else {\n cleanCache(0)\n sleepToast(`📃 小说投票\\n\\n✅ 投票成功`)\n }\n}\n\nlet blockType = {\n \"Caption\": \"📃 简介屏蔽列表\",\n \"Tags\": \"#️ 标签屏蔽列表\",\n \"Authors\": \"👤 作者屏蔽列表\"\n}\n\nfunction printAuthorMap(map) {\n let text = \"\"\n map.forEach((value, key) => {\n text += `@${value} ${key}\\n`\n })\n return text.trim()\n}\n\nfunction blockShowFactory() {\n let keys = Object.keys(blockType)\n let key = getFromCacheObject(\"blockType\")\n\n // 切换屏蔽列表\n let currentIndex = keys.indexOf(key)\n let nextIndex = (currentIndex + 1) % keys.length\n key = keys[nextIndex]\n putInCacheObject(\"blockType\", key)\n\n if (key === \"Authors\") {\n let words = printAuthorMap(getFromCacheMap(\"blockAuthorMap\"))\n if (!words) words = \"\"\n sleepToast(`👀 查看屏蔽\\n${blockType[key]}\\n\\n${words}`, 2)\n } else {\n let words = getFromCacheObject(`block${key}`)\n // if (!words) words = getFromCacheObject(`${key.toLowerCase()}BlockWords`)\n if (!words) words = []\n sleepToast(`👀 查看屏蔽\\n${blockType[key]}\\n\\n${words.join(\"\\n\")}`, 2)\n putInCacheObject(`block${key}`, words)\n }\n}\n\nfunction blockWordAdd() {\n let method = getFromCacheObject(\"blockType\")\n let blockTypeName = blockType[method]\n let blockWords = getFromCacheObject(`block${method}`)\n // if (!blockWords) getFromCacheObject(`${method.toLowerCase()}BlockWords`)\n if (!blockWords) blockWords = []\n\n let word = String(result.get(\"文本框\")).trim()\n if (word === \"\") {\n sleepToast(`🚫 添加屏蔽\\n${blockTypeName}\\n\\n⚠️ 请在【文本框】内输入屏蔽词`)\n } else if (blockWords.includes(word)) {\n sleepToast(`🚫 添加屏蔽\\n${blockTypeName}\\n\\n✅ 【${word}】已经加入屏蔽列表了`)\n } else {\n blockWords.push(word)\n putInCacheObject(`block${method}`, blockWords)\n sleepToast(`🚫 添加屏蔽\\n${blockTypeName}\\n\\n✅ 已将【${word}】加入屏蔽列表中`)\n }\n}\n\nfunction blockWordDelete() {\n let method = getFromCacheObject(\"blockType\")\n let blockTypeName = blockType[method]\n let blockWords = getFromCacheObject(`block${method}`)\n // if (!blockWords) getFromCacheObject(`${method.toLowerCase()}BlockWords`)\n if (!blockWords) blockWords = []\n\n let word = String(result.get(\"文本框\")).trim()\n if (word === \"\") {\n sleepToast(`⭕️ 删除屏蔽\\n${blockTypeName}\\n\\n⚠️ 请在【文本框】内输入屏蔽词`)\n } else if (!blockWords.includes(word)) {\n sleepToast(`⭕️ 删除屏蔽\\n${blockTypeName}\\n\\n⚠️ 【${word}】不在屏蔽列表\\n请检查是否有错别字或标点符号是否一致`)\n } else {\n blockWords = blockWords.filter(item => item !== word)\n putInCacheObject(`block${method}`, blockWords)\n sleepToast(`⭕️ 删除屏蔽\\n${blockTypeName}\\n\\n✅ 已删除屏蔽词【${word}】`)\n }\n}\n\nfunction blockAuthorAdd() {\n let method = getFromCacheObject(\"blockType\")\n let blockTypeName = blockType[method]\n let blockAuthors = getFromCacheMap(`blockAuthorMap`)\n\n let word = String(result.get(\"文本框\")).trim()\n if (word === \"\") {\n sleepToast(`🚫 添加屏蔽\\n${blockTypeName}\\n\\n⚠️ 请在【文本框】内输入【作者ID】\\n或使用上方 🚫 屏蔽作者`)\n } else if (blockAuthors.has(word)) {\n let text = `${blockAuthors.get(word)} ${word}`\n sleepToast(`🚫 添加屏蔽\\n${blockTypeName}\\n\\n✅ 【${text}】已经加入屏蔽列表了`)\n }\n // 输入纯数字,添加对应ID的作者\n else if (!isNaN(word)) {\n let user = getAjaxJson(urlUserDetailed(word)).body\n blockAuthors.set(user.userId, user.name)\n let text = `@${user.name} ${user.userId}`\n sleepToast(`🚫 添加屏蔽\\n${blockTypeName}\\n\\n✅ 已将【${text}】加入屏蔽列表中`)\n }\n else if (word) {\n sleepToast(`🚫 添加屏蔽\\n${blockTypeName}\\n\\n⚠️ 输入【用户ID】可屏蔽该作者`)\n }\n putInCacheMap(`blockAuthorMap`, blockAuthors)\n}\n\nfunction blockAuthorDelete() {\n let method = getFromCacheObject(\"blockType\")\n let blockTypeName = blockType[method]\n let blockAuthors = getFromCacheMap(`blockAuthorMap`)\n\n let word = String(result.get(\"文本框\")).trim()\n if (word === \"\") {\n sleepToast(`⭕️ 删除屏蔽\\n${blockTypeName}\\n\\n⚠️ 请在【文本框】内输入【作者ID】\\n或使用上方 🚫 屏蔽作者`)\n }\n // 输入纯数字,删除对应ID的作者\n else if (!isNaN(word) && blockAuthors.has(word)) {\n let text = `@${blockAuthors.get(word)} ${word}`\n blockAuthors.delete(word)\n sleepToast(`⭕️ 删除屏蔽\\n${blockTypeName}\\n\\n✅ 已删除【${text}】`)\n }\n //作者名称\n else if (Array.from(blockAuthors.values()).includes(word)) {\n let index = Array.from(blockAuthors.values()).indexOf(word)\n let key = Array.from(blockAuthors.keys())[index]\n let text = `@${blockAuthors.get(key)} ${key}`\n blockAuthors.delete(key)\n sleepToast(`⭕️ 删除屏蔽\\n${blockTypeName}\\n\\n✅ 已删除【${text}】`)\n }\n else if (word) {\n sleepToast(`⭕️ 删除屏蔽\\n${blockTypeName}\\n\\n⚠️ 输入【用户ID】可屏蔽该作者`)\n }\n putInCacheMap(`blockAuthorMap`, blockAuthors)\n}\n\nfunction blockAddFactory() {\n if (getFromCacheObject(\"blockType\") === \"Authors\") return blockAuthorAdd()\n else return blockWordAdd()\n}\n\nfunction blockDeleteFactory() {\n if (getFromCacheObject(\"blockType\") === \"Authors\") return blockAuthorDelete()\n else return blockWordDelete()\n}\n\n\nfunction likeTagsShow() {\n let likeTags = getFromCacheObject(`likeTags`)\n if (likeTags === null) likeTags = []\n sleepToast(`👀 查看标签\\n📌 喜欢标签\\n\\n${likeTags.join(\"、\")}`, 5)\n}\n\nfunction likeTagsAdd() {\n let likeTags = getFromCacheObject(`likeTags`)\n if (likeTags === null) likeTags = []\n\n let word = String(result.get(\"文本框\")).trim()\n if (word === \"\") {\n sleepToast(`📌 喜欢标签\\n📌 添加标签\\n\\n⚠️ 请在【文本框】内直接输入标签内容`)\n } else if (word.startsWith(\"@\") || word.startsWith(\"@\")) {\n sleepToast(`📌 喜欢标签\\n📌 添加标签\\n\\n⚠️ 仅支持添加【标签】\\n不支持添加 @作者名称`)\n } else if (word.startsWith(\"#\") || word.startsWith(\"#\")) {\n sleepToast(`📌 喜欢标签\\n📌 添加标签\\n\\n⚠️ 仅支持添加【标签】\\n不支持添加 #标签名称`)\n } else if (likeTags.includes(word)) {\n sleepToast(`📌 喜欢标签\\n📌 添加标签\\n\\n✅ 【${word}】已经加入喜欢标签了\\n请于发现页刷新后查看`)\n } else {\n likeTags.push(word)\n putInCacheObject(`likeTags`, likeTags)\n sleepToast(`📌 喜欢标签\\n📌 添加标签\\n\\n✅ 已将【${word}】加入喜欢标签了`)\n try {source.refreshExplore()} catch (e) {}\n }\n}\n\nfunction likeTagsDelete() {\n let likeTags = getFromCacheObject(`likeTags`)\n if (likeTags === null) likeTags = []\n\n let word = String(result.get(\"文本框\")).trim()\n if (word === \"\") {\n sleepToast(`📌 喜欢标签\\n🗑 删除标签\\n\\n⚠️ 请在【文本框】内直接输入标签内容`)\n } else if (!likeTags.includes(word)) {\n sleepToast(`📌 喜欢标签\\n🗑 删除标签\\n\\n⚠️ 【${word}】不在喜欢标签\\n请检查是否有错别字`)\n } else {\n likeTags = likeTags.filter(item => item !== word)\n putInCacheObject(`likeTags`, likeTags)\n sleepToast(`📌 喜欢标签\\n🗑 删除标签\\n\\n✅ 已删除该标签【${word}】`)\n try {source.refreshExplore()} catch (e) {}\n }\n}\n\n\nfunction likeAuthorsShow() {\n let likeAuthors = getFromCacheMap(\"likeAuthorsMap\")\n if (likeAuthors.size === 0) likeAuthors = getFromCacheMap(\"likeAuthors\")\n\n let text = printAuthorMap(likeAuthors)\n sleepToast(`👀 查看收藏\\n❤️ 他人收藏\\n\\n${text.trim()}`, 2)\n putInCacheMap(`likeAuthorsMap`, likeAuthors)\n}\n\nfunction likeAuthorsAdd() {\n let likeAuthors = getFromCacheMap(\"likeAuthorsMap\")\n if (likeAuthors.size === 0) likeAuthors = getFromCacheMap(\"likeAuthors\")\n \n let word = String(result.get(\"文本框\")).trim()\n if (word.startsWith(\"@\") || word.startsWith(\"@\")) {\n return sleepToast(`❤️ 他人收藏\\n❤️ 添加收藏\\n\\n⚠️ 仅支持通过【作者ID】关注\\n不支持添加 @作者名称`)\n } else if (word.startsWith(\"#\") || word.startsWith(\"#\")) {\n return sleepToast(`❤️ 他人收藏\\n❤️ 添加收藏\\n\\n⚠️ 仅支持通过【作者ID】关注\\n不支持添加 #标签名称`)\n } else if (likeAuthors.has(word)) {\n let text = `${likeAuthors.get(word)} ${word}`\n sleepToast(`❤️ 他人收藏\\n❤️ 添加收藏\\n\\n✅ 【${text}】已经加入收藏列表了,请于发现页查看`)\n }\n\n // 无输入内容,添加当前小说的作者\n if (word === \"\") {\n let novel = getNovel()\n likeAuthors.set(String(novel.userId), novel.userName)\n let text = `@${novel.userName} ${novel.userId}`\n sleepToast(`❤️ 他人收藏\\n❤️ 添加收藏\\n\\n✅ 已将【${text}】加入他人收藏列表了,请于发现页查看\\n\\n📌 【文本框】内输入【用户ID】可关注其他用户的收藏`)\n }\n // 输入纯数字,添加对应ID的作者\n else if (!isNaN(word)) {\n let user = getAjaxJson(urlUserDetailed(word)).body\n likeAuthors.set(user.userId, user.name)\n let text = `@${user.name} ${user.userId}`\n sleepToast(`❤️ 他人收藏\\n️ 添加收藏\\n\\n✅ 已将【${text}】加入他人收藏列表了,请于发现页查看`)\n }\n\n else if (word) {\n sleepToast(`❤️ 他人收藏\\n❤️ 添加收藏\\n\\n📌 【文本框】内输入【用户ID】可关注其他用户的收藏`)\n }\n putInCacheMap(`likeAuthorsMap`, likeAuthors)\n try {source.refreshExplore()} catch (e) {}\n}\n\nfunction likeAuthorsDelete() {\n let likeAuthors = getFromCacheMap(\"likeAuthorsMap\")\n if (likeAuthors.size === 0) likeAuthors = getFromCacheMap(\"likeAuthors\")\n\n let word = String(result.get(\"文本框\")).trim()\n if (word.startsWith(\"@\") || word.startsWith(\"@\")) {\n return sleepToast(`❤️ 他人收藏\\n🖤 取消收藏\\n\\n⚠️ 仅支持通过【作者ID/作者名称】取关\\n不支持输入 @作者名称`)\n } else if (word.startsWith(\"#\") || word.startsWith(\"#\")) {\n return sleepToast(`❤️ 他人收藏\\n🖤 取消收藏\\n\\n⚠️ 仅支持通过【作者ID/作者名称】取关\\n不支持输入 #标签名称`)\n }\n\n if (word === \"\") {\n let novel = getNovel()\n likeAuthors.delete(novel.userId)\n let text = `@${novel.userName} ${novel.userId}`\n sleepToast(`❤️ 他人收藏\\n🖤 取消收藏\\n\\n✅ 已将【${text}】移出他人收藏列表了\\n\\n📌 【文本框】内输入【用户ID】可取消关注其他用户的收藏`)\n\n // 输入纯数字,删除对应ID的作者\n } else if (!isNaN(word) && likeAuthors.has(word)) {\n let text = `@${likeAuthors.get(word)} ${word}`\n likeAuthors.delete(word)\n sleepToast(`❤️ 他人收藏\\n🖤 取消收藏\\n\\n✅ 已取关【${text}】`)\n\n //作者名称\n } else if (Array.from(likeAuthors.values()).includes(word)) {\n let index = Array.from(likeAuthors.values()).indexOf(word)\n let key = Array.from(likeAuthors.keys())[index]\n let text = `@${likeAuthors.get(key)} ${key}`\n likeAuthors.delete(key)\n sleepToast(`❤️ 他人收藏\\n🖤 取消收藏\\n\\n✅ 已取关【${text}】`)\n }\n else if (word) {\n sleepToast(`❤️ 他人收藏\\n🖤 取消收藏\\n\\n📌 【文本框】内输入【用户ID】可取关其他用户的收藏`)\n }\n putInCacheMap(`likeAuthorsMap`, likeAuthors)\n try {source.refreshExplore()} catch (e) {}\n}\n\nfunction shareFactory(type) {\n let novel = getNovel()\n if (novel === undefined || novel === null) return sleepToast(\"⚠️ 请在小说阅读页面,使用本功能\")\n if (type.includes(\"author\")) {\n startBrowser(urlUserUrl(novel.userId), novel.userName)\n }\n else if (type.includes(\"novel\") || (!novel.seriesId)) {\n startBrowser(urlNovelUrl(novel.id), novel.title)\n }\n else if (type.includes(\"series\") && novel.seriesId) {\n startBrowser(urlSeriesUrl(novel.seriesId), novel.seriesTitle)\n }\n}\n\n// 用【备份恢复】恢复登录状态后,打开浏览器不清空原有登录状态\nfunction startPixivSettings() {\n let headers = getFromCacheObject(\"pixivHeaders\")\n headers[\"user-agent\"] = getFromCache(\"userAgent\") || \"\"\n headers[\"x-csrf-token\"] = getFromCache(\"pixivCsrfToken\") || \"\"\n headers[\"user-agent\"] = getFromCache(\"pixivCookie\") || \"\"\n\n java.startBrowser(`https://www.pixiv.net/settings/viewing, ${JSON.stringify({headers: headers})}`, \"账号设置\")\n}\nfunction startGithubReadme() {\n startBrowser(\"https://pixivsource.pages.dev/Pixiv\", \"使用指南\")\n}\nfunction startGithubIssue() {\n startBrowser(\"https://github.com/DowneyRem/PixivSource/issues\", \"反馈问题\")\n}\n\nfunction checkStatus(status) {\n if (eval(String(status)) === true) return \"❤️\"\n else return \"🖤\"\n}\n\nfunction readMeSearch() {\n return sleepToast(`🔍 搜索说明\\n\n 标签之间需要以【空格】间隔\n ➖ 排除标签:#标签1 -标签2\n 👤 作者专搜:@搜索作者名称\n #️ 标签专搜:#标签1 标签2 \n ⏬ 字数筛选1:#标签1 标签2 字数3k5\n ⏬ 字数筛选2:@作者的名称 字数3w5`.replace(\" \",\"\"), 5)\n}\n\nconst serachSettingsName = {\n \"SEARCH_AUTHOR\": \"🔍 搜索作者\",\n \"CONVERT_CHINESE\": \"🀄️ 繁简通搜\",\n}\nconst detailSettingsName = {\n \"MORE_INFORMATION\": \"📖 更多简介\",\n}\nconst catalogSettingsName = {\n \"ADD_CHAPTER_INDEX\": \"🔢 章节编号\",\n \"SHOW_UPDATE_TIME\": \"📅 更新时间\",\n \"SHOW_ORIGINAL_LINK\": \"🔗 原始链接\",\n}\nconst contentSettingsName = {\n \"SHOW_CAPTIONS\": \"📄 显示描述\",\n \"SHOW_PICTURES\": \"🖼️ 显示图片\",\n \"SHOW_QUESTION\": \"✅ 显示投票\",\n\n \"SHOW_COMMENTS\": \"💬 显示评论\",\n \"REPLACE_TITLE_MARKS\": \"📚 恢复《》\",\n}\nconst otherSettingsName = {\n \"AUTO_LIKE_NOVELS\": \"❤️ 自动收藏\",\n \"AUTO_DISLIKE_NOVELS\": \"🖤 自动取消\",\n\n \"HIDE_LIKE_NOVELS\": \"❤️ 隐藏收藏\",\n \"HIDE_WATCHED_SERIES\": \"📃 隐藏追更\",\n\n \"COMBINE_NOVELS\": \"📚 整合系列\",\n}\nconst modeSettingsName = {\n \"FAST\": \"⏩ 快速模式\",\n \"DEBUG\": \"🐞 调试模式\",\n \"IPDirect\": \"✈️ 直连模式\",\n}\nconst pictureSettingsName = {\n \"PIC_SOURCE\": \"⏳ 图片解析\",\n \"PIC_LINK\": \"🔗 图片链接\",\n \"PIC_SIZE\": \"↔️ 图片大小\",\n}\nconst novelSettingsName = Object.assign({},\n serachSettingsName,\n detailSettingsName,\n catalogSettingsName,\n contentSettingsName,\n otherSettingsName,\n modeSettingsName,\n // pictureSettingsName\n)\nconst fastSettingsName = {\n \"CONVERT_CHINESE\": \"🀄️ 繁简通搜\",\n \"SHOW_UPDATE_TIME\": \"📅 更新时间\",\n \"SHOW_ORIGINAL_LINK\": \"🔗 原始链接\",\n\n \"SHOW_PICTURES\": \"🖼️ 显示图片\",\n \"SHOW_QUESTION\": \"✅ 显示投票\",\n \"SHOW_COMMENTS\": \"💬 显示评论\",\n}\nconst IPDirectSettingsName = {\n \"SHOW_ORIGINAL_LINK\": \"🔗 原始链接\",\n}\nconst discoverSettingsName ={\n \"SHOW_ADULT\": \"🔞 默认发现\",\n \"SHOW_GENERAL\": \"🆗 常规小说\",\n \"SHOW_NEW_ADULT\": \"🔞 最新企划\",\n \"SHOW_NEW_GENERAL\": \"🆗 最新企划\",\n\n \"SHOW_RANK_ADULT\": \"🔞 排行榜单\",\n \"SHOW_RANK_GENERAL\": \"🆗 排行榜单\",\n \"SHOW_GENRE_ADULT\": \"🔞 原创热门\",\n \"SHOW_GENRE_GENERAL\": \"🆗 原创热门\",\n\n \"SHOW_ABOUT\": \"📚 书源相关\",\n \"SHOW_FURRY\": \"🦊 兽人小说\",\n}\nconst menuSettingsName = {\n \"SHOW_DISCOVER\": \"⚙️ 发现设置\\n(书源编辑界面)\",\n \"SHOW_SETTINGS\": \"⚙️ 书源设置\\n(书源编辑界面)\",\n \"SHOW_DISCOVER2\": \"⚙️ 发现设置\\n(小说阅读界面)\",\n \"SHOW_SETTINGS2\": \"⚙️ 书源设置\\n(小说阅读界面)\",\n}\nconst settingsName = Object.assign({},\n novelSettingsName,\n fastSettingsName,\n IPDirectSettingsName,\n discoverSettingsName,\n menuSettingsName\n)\n\nfunction statusMsg(status) {\n if (status === true) return \"✅ 已开启\"\n else if (status === false) return \"🚫 已关闭\"\n else if (status === undefined) return \"🈚️ 未设置\"\n}\n\n// 检测快速模式修改的4个设置\nfunction getSettingStatus(mode) {\n if (mode === undefined) mode = \"\"\n let keys = [], msgList = []\n let settings = getFromCacheObject(\"pixivSettings\")\n if (mode === \"FAST\") {\n keys = Object.keys(fastSettingsName)\n } else if (mode === \"IPDirect\") {\n keys = Object.keys(IPDirectSettingsName)\n } else if (mode.includes(\"DISCOVER\")) {\n keys = Object.keys(discoverSettingsName)\n } else {\n keys = Object.keys(novelSettingsName)\n }\n for (let i = 0; i < keys.length; i++) {\n msgList.push(`${statusMsg(settings[keys[i]])} ${settingsName[keys[i]]}`)\n if ((i+1) % 6 === 0) msgList.push(\"\\n\")\n }\n return msgList.join(\"\\n\").trim()\n}\n\nfunction showSettings() {\n sleepToast(`\\n⚙️ 当前设置\\n\\n${getSettingStatus()}`)\n}\nfunction showSettingsDiscover() {\n sleepToast(`\\n⚙️ 当前发现设置\\n\\n${getSettingStatus(\"DISCOVER\")}`)\n}\n\nfunction setDefaultSettingsLoginUrl() {\n setDefaultSettings()\n sleepToast(`\\n✅ 已恢复 🔧 默认设置\\n\\n${getSettingStatus()}`)\n refreshSettings(\"DEFAULT\")\n}\n\nfunction editSettings(settingName) {\n let msg, status\n let settings = getFromCacheObject(\"pixivSettings\")\n if (!settings) settings = setDefaultSettings()\n if (!!settings[settingName]) {\n status = settings[settingName] = !settings[settingName]\n } else {\n status = settings[settingName] = true\n }\n putInCacheObject(\"pixivSettings\", settings)\n\n if (settingName === \"FAST\") {\n checkSettings(settings)\n msg = `\\n\\n${statusMsg(status)} ${settingsName[settingName]}\\n\\n${getSettingStatus(settingName)}`\n\n } else if (settingName === \"IPDirect\") {\n if (settings.IPDirect && !isLogin()) {\n settings.IPDirect = false\n checkSettings(settings)\n msg = \"✈️ 直连模式\\n\\n✈️ 直连模式 需登录账号\\n当前未登录账号,现已关闭直连模式\"\n } else {\n checkSettings(settings)\n msg = `\\n\\n${statusMsg(status)} ${settingsName[settingName]}\\n\\n${getSettingStatus(settingName)}`\n }\n } else {\n msg = `\\n\\n${statusMsg(status)} ${settingsName[settingName]}`\n }\n\n sleepToast(msg)\n refreshSettings(settingName)\n}\n\nfunction refreshSettings(settingName) {\n if (settingName === \"DEFAULT\") {\n try { source.refreshExplore() } catch (e) {}\n try { java.refreshBookInfo() } catch(e) {}\n try { java.refreshBookToc() } catch(e) {}\n try { java.refreshContent() } catch(e) {}\n\n } else if (settingName in modeSettingsName) {\n try { java.reLoginView() } catch (e) {}\n try { source.refreshExplore() } catch (e) {}\n try { java.refreshBookToc() } catch(e) {}\n try { java.refreshContent() } catch(e) {}\n\n } else if (settingName in menuSettingsName) {\n try { java.reLoginView() } catch (e) {}\n\n } else if (settingName in discoverSettingsName) {\n try { source.refreshExplore() } catch (e) {}\n\n } else if (settingName in detailSettingsName) {\n try { java.refreshBookInfo() } catch(e) {}\n\n } else if (settingName in catalogSettingsName) {\n try { java.refreshBookToc() } catch(e) {}\n\n } else if (settingName in contentSettingsName) {\n try { java.refreshContent() } catch(e) {}\n\n } else if (settingName in pictureSettingsName) {\n try { java.refreshContent() } catch(e) {}\n }\n}\n\n\nfunction backupRestore() {\n let variable = String(result.get(\"书源设置\") || \"\").trim()\n if (!variable) variable = String(source.getVariable() || \"\").trim()\n\n if (variable === \"\") {\n let data = backupData()\n try {\n java.copyText(data)\n java.upLoginData({\"书源设置\": data})\n sleepToast(\"\\n💾 备份数据\\n\\n✅ 已导出书源数据 到剪贴板\")\n } catch(e) {\n try {\n source.putVariable(data)\n sleepToast(\"\\n💾 备份恢复\\n\\n✅ 已导出书源数据至 源变量\")\n } catch(e) {\n sleepToast(\"\\n💾 备份恢复\\n\\n⚠️ 书源导出数据失败\")\n }\n }\n }\n\n else if (isJsonString(variable)) {\n restoreData(JSON.parse(variable))\n sleepToast(\"\\n💾 恢复数据\\n\\n✅ 已导入书源数据\")\n try { java.upLoginData({\"书源设置\": \"\"})} catch(e) {}\n try { source.putVariable(\"\") } catch(e) {}\n } else {\n sleepToast(\"\\n💾 备份恢复\\n\\n⚠️ 输入数据出错,请检查数据格式(JSON)\")\n }\n}\n\nfunction backupData() {\n let data = {}\n // 账号相关\n data.pixivUid = getFromCache(\"pixivUid\")\n data.pixivCookie = getCookie()\n data.pixivCsrfToken = getCsrfToken()\n\n // 书源设置\n data.pixivSettings = getFromCacheObject(\"pixivSettings\")\n data.blockCaption = getFromCacheObject(\"blockCaption\")\n data.blockTags = getFromCacheObject(\"blockTags\")\n data.likeTags = getFromCacheObject(\"likeTags\")\n\n // 书源设置 Map\n data.blockAuthorMap = Object.fromEntries(getFromCacheMap(\"blockAuthorMap\"))\n data.likeAuthorsMap = Object.fromEntries(getFromCacheMap(\"likeAuthorsMap\"))\n return JSON.stringify(data, null, 4)\n}\n\nfunction restoreData(data) {\n // 账号相关\n let pixivCookie = stripCfCookies(data?.pixivCookie)\n if (pixivCookie) {\n removeCookie()\n putInCache(\"pixivUid\", data?.pixivUid)\n putInCache(\"pixivCookie\", pixivCookie, 60*60)\n putInCache(\"pixivCsrfToken\", data?.pixivCsrfToken)\n cookie.setCookie(\"https://www.pixiv.net\", pixivCookie)\n cookie.setCookie(\"https://accounts.pixiv.net\", pixivCookie)\n try {\n cookie.setWebCookie(\"https://www.pixiv.net\", pixivCookie)\n cookie.setWebCookie(\"https://accounts.pixiv.net\", pixivCookie)\n } catch (e) {}\n }\n\n // 书源设置\n putInCacheObject(\"pixivSettings\", data?.pixivSettings)\n putInCacheObject(\"blockCaption\", data?.blockCaption)\n putInCacheObject(\"blockTags\", data?.blockTags)\n putInCacheObject(\"likeTags\", data?.likeTags)\n\n // 书源设置 Map\n putInCacheMap(\"blockAuthorMap\", new Map(Object.entries(data?.blockAuthorMap)))\n putInCacheMap(\"likeAuthorsMap\", new Map(Object.entries(data?.likeAuthorsMap)))\n try {source.refreshExplore()} catch (e) {}\n}\n\nfunction stripCfCookies(cookieStr) {\n return cookieStr\n .replace(/;?\\s*cf_clearance=[^;]*/g, '')\n .replace(/;?\\s*__cf_bm=[^;]*/g, '')\n .replace(/^;\\s*/, '') // 防止开头多余的分号\n .trim()\n}\n\nfunction cleanCache(toast) {\n let novel = getNovel()\n cache.delete(`${urlNovelUrl(novel.id)}`)\n cache.delete(`${urlNovelDetailed(novel.id)}`)\n try {java.refreshContent()} catch(err) {}\n if (toast) sleepToast(`🔄 刷新本章\\n\\n若正文未更新,请手动刷新`, 5)\n}\n\nfunction getFurryAuthors() {\n cache.delete(urlLinpxAuthors()) // 删除缓存实时请求数据\n let furryAuthorsMap = {}\n let authorsListLinpx = getAjaxJson(urlLinpxAuthors())\n authorsListLinpx.forEach(author => {\n furryAuthorsMap[author.name] = author.id\n })\n\n // let authorsMapFurryReading = getAjaxJson(\"\")\n // furryAuthorsMap = Object.assign(furryAuthorsMap, authorsMapFurryReading)\n putInCacheObject(\"furryAuthors\", furryAuthorsMap)\n return furryAuthorsMap\n}\n\nfunction updatePixivAuthors() {\n let furryAuthors = getFurryAuthors()\n let pixivAuthors = getFromCacheObject(\"pixivAuthors\")\n if (!pixivAuthors) pixivAuthors = {}\n pixivAuthors = Object.assign(pixivAuthors, furryAuthors)\n putInCacheObject(\"pixivAuthors\", pixivAuthors, cacheSaveSeconds)\n sleepToast(\"\\n🐺 兽人作者搜索优化 \\n\\n ✅ 已导入 Linpx 推荐作者\", 1)\n return pixivAuthors\n}", "respondTime": 180000, "ruleBookInfo": { "init": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (isFunctionString(v)) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction novelHandler(novel){\n novel = util.formatNovels(util.handNovels([novel], true))[0]\n if (novel.seriesId && util.settings.COMBINE_NOVELS) {\n book.bookUrl = novel.detailedUrl = urlSeriesUrl(novel.seriesId);\n book.tocUrl = novel.catalogUrl = urlIP(urlSeriesDetailed(novel.seriesId));\n } else {\n book.bookUrl = novel.detailedUrl = urlNovelUrl(novel.id);\n book.tocUrl = novel.catalogUrl = urlIP(urlNovelDetailed(novel.id));\n }\n return novel\n}\n\n(() => {\n return novelHandler(util.getNovelRespFirst(result).body)\n})()", "name": "title", "author": "userName", "kind": "tags", "wordCount": "textCount", "lastChapter": "latestChapter", "intro": "description", "coverUrl": "coverUrl", "tocUrl": "catalogUrl", "canReName": "true" }, "ruleContent": { "content": "@js:\nvar util = objParse(String(java.get(\"util\")))\nlet emoji = {\n \"normal\": 101, \"surprise\": 102, \"series\": 103, \"heaven\": 104, \"happy\": 105,\n \"excited\": 106, \"sing\": 107, \"cry\": 108, \"normal2\": 201, \"shame2\": 202,\n \"love2\": 203, \"interesting2\": 204, \"blush2\": 205, \"fire2\": 206, \"angry2\": 207,\n \"shine2\": 208, \"panic2\": 209, \"normal3\": 301, \"satisfaction3\": 302, \"surprise3\": 303,\n \"smile3\": 304, \"shock3\": 305, \"gaze3\": 306, \"wink3\": 307, \"happy3\": 308,\n \"excited3\": 309, \"love3\": 310, \"normal4\": 401, \"surprise4\": 402, \"series4\": 403,\n \"love4\": 404, \"shine4\": 405, \"sweet4\": 406, \"shame4\": 407, \"sleep4\": 408,\n \"heart\": 501, \"teardrop\": 502, \"star\": 503\n}\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (isFunctionString(v)) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction getNovelInfo(resp) {\n if (resp && resp.error === true) return\n\n let novel = resp.body\n novel.id = Number(novel.id)\n if (novel.bookmarkData) {\n novel.isBookmark = true\n putInCache(`collect${novel.id}`, novel.bookmarkData.id)\n util.saveNovels(\"likeNovels\", [Number(novel.id)])\n } else {\n novel.isBookmark = false\n }\n\n if (novel.seriesNavData) {\n novel.seriesId = Number(novel.seriesNavData.seriesId)\n novel.seriesTitle = novel.seriesNavData.title\n novel.isWatched = novel.seriesNavData.isWatched\n util.saveNovels(\"watchedSeries\", [Number(novel.seriesId)])\n } else {\n novel.seriesId = null\n novel.seriesTitle = \"\"\n novel.isWatched = false\n }\n\n // 系列 + 阅读,使用当前章节名称\n if (novel.seriesId && util.environment.IS_LEGADO) {\n novel.title = book.durChapterTitle\n try{\n let novelIds = getFromCacheObject(`novelIds${novel.seriesId}`)\n novel.id = novelIds[book.durChapterIndex]\n } catch(e) {\n let novelIds = getAjaxJson(urlIP(urlSeriesNovelsTitles(novel.seriesId)), true).body\n novel.id = novelIds[book.durChapterIndex].id\n }\n }\n\n // 添加投票信息\n if (novel.pollData) {\n novel.question = novel.pollData.question\n } else {\n novel.question = \"\"\n }\n\n // 登录界面显示信息\n novel[\"章节名称\"] = novel.title\n novel[\"问卷调查\"] = novel.question || \"\"\n source.putLoginInfo(JSON.stringify(novel))\n\n // 写入缓存\n resp.body = novel\n putInCacheObject(\"novel\", resp)\n putInCacheObject(urlNovelDetailed(novel.id), resp)\n}\n\n// 在正文内部添加小说描述\nfunction getCaptions(resp, content) {\n if (util.settings.SHOW_CAPTIONS && resp.description !== \"\") {\n content = resp.description + \"\\n\" + \"——————————\\n\".repeat(2) + content\n }\n return content\n}\n\n// 获取 [uploadedimage:] 的图片链接\nfunction replaceUploadedImage(resp, content) {\n if (resp.textEmbeddedImages) {\n let images = resp.textEmbeddedImages\n Object.keys(images).forEach((key) => {\n if (util.settings.SHOW_PICTURES) {\n let url = urlCoverUrl(images[key].urls.original)\n content = content.replace(`[uploadedimage:${key}]`, ``)\n } else {\n content = content.replace(`[uploadedimage:${key}]`, '')\n }\n })\n }\n return content\n}\n\n// 获取 [pixivimage:] 的图片链接 [pixivimage:1234] [pixivimage:1234-1]\nfunction replacePixivImage(content) {\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) && util.settings.SHOW_PICTURES) {\n content = content.replace(`${pixivimage}`, ``)\n } else {\n content = content.replace(`${pixivimage}`, ``)\n }\n })\n }\n return content\n}\n\n// 替换 Pixiv 分页标记符号 [newpage]\nfunction replaceNewPage(content) {\n if (!util.environment.IS_LEGADO_SIGMA) {\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 }\n return content\n}\n\n// 替换 Pixiv 章节标记符号 [chapter:]\nfunction replaceChapter(content) {\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 // 替换 Pixiv 分页标记符号 [newpage]\n if (util.environment.IS_LEGADO_SIGMA) {\n content = content.replace(`${matched[i]}`, `

${chapter}

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

​

`)\n }\n }\n }\n return content\n}\n\n// 替换 Pixiv 跳转页面标记符号 [[jump:]]\nfunction replaceJumpPage(content) {\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}\n\n// 替换 Pixiv 链接标记符号 [[jumpuri: > ]]\nfunction replaceJumpUrl(content) {\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_SIGMA) {\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}\n\n// 替换 Pixiv 注音标记符号 [[rb: > ]]\nfunction replaceRb(content) {\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.settings.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\n// 添加投票信息\nfunction getPollData(resp, content) {\n if (!util.settings.SHOW_QUESTION || !resp.pollData) return content\n\n // resp.pollData.selectedValue = 0\n if (resp.pollData) {\n let text = \"\"\n let showText = util.settings.DEBUG || String(getFromCache(\"pixivUid\")) !== resp.userId\n if (!resp.pollData.selectedValue && showText) {\n text = \"\\n\\n(⚠️当前未投,点击【Pixiv 小说】,打开登录界面,点击投票选项,即可参与投票)\"\n }\n let poll = `📃 问卷调查(✅${resp.pollData.total}已投)\n ${text}\\n\\n🔠 投票:\\n${resp.pollData.question}\\n`\n\n resp.pollData.choices.forEach((choice, i) => {\n if ( i+1 === Number(resp.pollData.selectedValue)) {\n poll += `✅选项${choice.id}:${choice.text}(✅${choice.count})\\n`\n } else {\n poll += `☑️选项${choice.id}:${choice.text}(✅${choice.count})\\n`\n }\n })\n content += \"\\n\" + \"——————————\\n\".repeat(2) + poll\n }\n return content\n}\n\nfunction processComment(item) {\n let text = item.comment || \"\"\n if (text === \"\" && item.stampId) {\n return ``\n }\n return text.replace(/\\(([^)]+)\\)/g, (match, key) => {\n if (emoji.hasOwnProperty(key)) {\n return ``\n }\n return match\n })\n}\n\nfunction formatComment(item, replyToName = null) {\n const content = processComment(item)\n const isMyComment = item.userId === String(getFromCache(\"pixivUid\"))\n const commentId = isMyComment ? `(${item.id})` : \"\"\n const name = replyToName ? `@${item.userName}(⤴️@${replyToName})` : `@${item.userName}`\n return `${name}:${content}(${item.commentDate})${commentId}\\n`\n}\n\nfunction getComment(resp, content) {\n if (!util.settings.SHOW_COMMENTS || resp.commentCount === 0) return content\n\n const limit = 50\n let comments = [], commentUrls = [];\n let maxPage = Math.ceil(resp.commentCount / limit)\n if (maxPage >= 2 && util.environment.IS_LEGADO) {\n for (let i = 0; i < maxPage; i++) {\n commentUrls.push(urlIP(urlNovelComments(resp.id, i * limit, limit)))\n }\n comments = getAjaxAllJson(commentUrls).map(resp => resp.body.comments).flat()\n } else {\n for (let i = 0; i < maxPage; i++) {\n let result = getAjaxJson(urlIP(urlNovelComments(resp.id, i * limit, limit)), true)\n if (result && !result.error && result.body && result.body.comments) {\n comments = comments.concat(result.body.comments)\n }\n }\n }\n\n let commentText = `💬 评论(共计${comments.length}条):\\n`\n comments.forEach(comment => {\n commentText += formatComment(comment)\n if (comment.hasReplies) {\n let resp = getAjaxJson(urlIP(urlNovelCommentsReply(comment.id, 1)), true)\n if (resp && !resp.error && resp.body && resp.body.comments) {\n resp.body.comments.reverse().forEach(reply => {\n commentText += formatComment(reply, reply.replyToUserName)\n })\n }\n commentText += \"——————————\\n\"\n }\n })\n return content + \"\\n\" + \"——————————\\n\".repeat(2) + commentText\n}\n\nfunction getContent(resp) {\n getNovelInfo(resp) // 放入信息以便登陆界面使用\n let content = String(resp.body.content)\n // let content = \"undefined\"\n if (content.includes(\"undefined\")) {\n return checkContent()\n }\n\n content = getCaptions(resp.body, content)\n content = replaceUploadedImage(resp.body, 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 content = getPollData(resp.body, content)\n content = getComment(resp.body, content)\n return content\n}\n\nfunction checkContent() {\n let latestMsg = getAjaxJson(urlMessageThreadLatest(5))\n if (latestMsg.error === true) {\n java.log(JSON.stringify(latestMsg))\n\n } else if (latestMsg.body.total >= 1) {\n let msg = latestMsg.body.message_threads.filter(item => item.thread_name === \"pixiv事務局\")[0]\n if (msg === undefined) {\n sleepToast(`您于 ${java.timeFormat(new Date().getTime())} 触发 Pixiv 【请求限制】,建议稍候/重新登录再继续`, 3)\n // java.startBrowser(\"https://www.pixiv.net\", '退出登录')\n // java.startBrowser(\"https://www.pixiv.net/logout.php\",'退出登录') // 不清除 WebView 缓存无法重新登录\n\n } else if (new Date().getTime()- 1000*msg.modified_at <= 3*24*60*60*1000) { // 3*24h内提醒\n sleepToast(`您于 ${java.timeFormat(1000*msg.modified_at)} 触发 Pixiv 【过度访问】,请修改密码并重新登录`, 3)\n sleepToast(`${msg.latest_content}`, 5)\n java.startBrowser(\"https://accounts.pixiv.net/password/change\",'修改密码')\n }\n }\n}\n\n(() => {\n return getContent(util.getNovelRespFirst(result))\n})()", "imageStyle": "FULL", "callBackJs": "function getNovelId(seriesId) {\n if (chapter) {\n try {\n return chapter.url.match(/novel\\/(\\d+)/)[1]\n } catch (e) {\n return chapter.url.match(/\\d+/)[0]\n }\n }\n\n if (!book.bookUrl.includes(\"series\")) {\n return book.bookUrl.match(/\\d+/)[0]\n } else {\n seriesId = book.bookUrl.match(/\\d+/)[0]\n }\n\n if (seriesId) {\n let novelIds = getFromCacheObject(`novelIds${seriesId}`)\n if (novelIds) {\n return getFromCacheObject(`novelIds${seriesId}`)[book.durChapterIndex]\n } else {\n return getAjaxJson(urlIP(urlSeriesNovelsTitles(seriesId)), true).body[book.durChapterIndex].id\n }\n }\n}\n\nfunction getNovel() {\n let novel = {}\n if (book.bookUrl.includes(\"series\")) {\n novel.seriesId = book.bookUrl.match(/\\d+/)[0]\n novel.seriesTitle = book.name\n novel.id = getNovelId(novel.seriesId)\n novel.title = book.durChapterTitle\n } else {\n novel.seriesId = 0\n novel.seriesTitle = \"\"\n novel.id = book.bookUrl.match(/\\d+/)[0]\n novel.title = book.name\n }\n novel.author = novel.userName = book.author.replace(\"@\", \"\")\n let resp = getAjaxJson(urlIP(urlNovelDetailed(novel.id))).body\n novel.userId = resp.userId\n novel.question = resp?.pollData?.question || \"\"\n // java.log(JSON.stringify(novel))\n return novel\n}\n\nfunction getPostBody(url, body, headers) {\n if (headers === undefined) headers = getFromCacheObject(\"pixivHeaders\")\n if (headers === undefined) headers = getFromCacheObject(\"headers\")\n if (isJsonString(body)) {\n headers[\"content-type\"] = \"application/json; charset=utf-8\"\n } else if (typeof body === \"string\") {\n headers[\"content-type\"] = \"application/x-www-form-urlencoded; charset=utf-8\"\n }\n\n let settings = getFromCacheObject(\"pixivSettings\")\n if (settings.IPDirect) {\n url = url.replace(\"http://\", \"https://\").replace(\"www.pixiv.net\", \"210.140.139.155\")\n headers[\"Host\"] = \"www.pixiv.net\"\n }\n try {\n java.log(`getPostBody(${url}, ${body}, ${headers})`)\n // java.log(`getPostBody(${url}, ${body}, ${JSON.stringify(headers)})`)\n return JSON.parse(java.post(url, body, headers).body())\n } catch (e) {\n e = String(e)\n // sleepToast(e)\n // sleepToast(JSON.stringify(headers))\n if (e.includes(\"400\")) sleepToast(`📤 getPostBody\\n\\n⚠️ 缺少 headers`, 1)\n // else if (e.includes(\"401\")) sleepToast(`📤 getPostBody\\n\\n⚠️ 缺少 cookie 或 cookie 过期`, 1)\n // else if (e.includes(\"403\")) sleepToast(`📤 getPostBody\\n\\n⚠️ 缺少 cookie 或 cookie 过期`, 1)\n else if (e.includes(\"404\")) sleepToast(`📤 getPostBody\\n\\n⚠️ 404 缺少 pixivCsrfToken `, 1)\n else if (e.includes(\"422\")) sleepToast(`📤 getPostBody\\n\\n⚠️ 请求信息有误`, 1)\n return {error: true, errMsg:e}\n }\n}\n\nfunction novelBookmarkAdd() {\n let restrict = 0\n let novel = getNovel()\n let novelObj = getAjaxJson(urlNovelDetailed(novel.id), true)\n if (novelObj.body.bookmarkData && novelObj.body.bookmarkData.private === false) restrict = 1\n let resp = getPostBody(\n \"https://www.pixiv.net/ajax/novels/bookmarks/add\",\n JSON.stringify({\"novel_id\": novel.id, \"restrict\": restrict, \"comment\":\"\", \"tags\":[]})\n )\n if (resp.error === true) {\n sleepToast(`❤️ 收藏小说\\n\\n⚠️ 收藏【${novel.title}】失败`)\n // shareFactory(\"novel\")\n } else {\n putInCacheObject(`collect${novel.id}`, resp.body)\n let likeNovels = getFromCacheObject(\"likeNovels\")\n likeNovels.push(Number(novel.id))\n putInCacheObject(\"likeNovels\", likeNovels)\n\n let novelObj = getAjaxJson(urlNovelDetailed(novel.id))\n novelObj.body.isBookmark = true\n putInCacheObject(urlNovelDetailed(novel.id), novelObj, cacheSaveSeconds)\n }\n\n if (restrict === 1) {\n sleepToast(`㊙️ 私密收藏\\n\\n✅ 已私密收藏\\n${novel.title}`)\n } else {\n sleepToast(`❤️ 公开收藏\\n\\n✅ 已公开收藏\\n${novel.title}`)\n }\n}\n\nfunction getNovelBookmarkId(novelId) {\n let bookmarkId = getFromCacheObject(`collect${novelId}`)\n if (bookmarkId === null) {\n try {\n bookmarkId = getAjaxJson(urlNovelBookmarkData(novelId), true).body.bookmarkData.id\n } catch (e) {\n bookmarkId = 0\n }\n }\n return bookmarkId\n}\n\nfunction novelBookmarkDelete() {\n let novel = getNovel()\n let bookmarkId = getNovelBookmarkId(novel.id)\n if (bookmarkId === 0) return sleepToast(`🖤 取消收藏\\n\\n✅ 已经取消收藏\\n${novel.title}`)\n\n let resp = getPostBody(\n \"https://www.pixiv.net/ajax/novels/bookmarks/delete\",\n `del=1&book_id=${bookmarkId}`\n )\n if (resp.error === true) {\n sleepToast(`🖤 取消收藏\\n\\n⚠️ 取消收藏失败\\n${novel.title}`)\n // shareFactory(\"novel\")\n } else {\n cache.delete(`collect${novel.id}`)\n sleepToast(`🖤 取消收藏\\n\\n✅ 已经取消收藏\\n${novel.title}`)\n\n let likeNovels = getFromCacheObject(\"likeNovels\")\n likeNovels = likeNovels.filter(item => item !== Number(novel.id))\n putInCacheObject(\"likeNovels\", likeNovels)\n\n let novelObj = getAjaxJson(urlNovelDetailed(novel.id))\n novelObj.body.isBookmark = false\n putInCacheObject(urlNovelDetailed(novel.id), novelObj, cacheSaveSeconds)\n }\n}\n\nfunction seriesWatch() {\n let novel = getNovel()\n let resp = getPostBody(\n `https://www.pixiv.net/ajax/novel/series/${novel.seriesId}/watch`,\n \"{}\"\n )\n if (resp.error === true) {\n sleepToast(`📃 追更系列\\n\\n⚠️ 追更【${novel.seriesTitle}】失败`, 1)\n // shareFactory(\"series\")\n } else {\n putInCache(`watch${novel.seriesId}`, true)\n sleepToast(`📃 追更系列\\n\\n✅ 已追更【${novel.seriesTitle}】`)\n\n let watchedSeries = getFromCacheObject(\"watchedSeries\")\n watchedSeries.push(Number(novel.seriesId))\n putInCacheObject(\"watchedSeries\", watchedSeries)\n\n let novelObj = getAjaxJson(urlSeriesDetailed(novel.seriesId))\n novelObj.body.isWatched = true\n putInCacheObject(urlSeriesDetailed(novel.seriesId), novelObj, cacheSaveSeconds)\n }\n}\n\nfunction seriesUnWatch() {\n let novel = getNovel()\n let resp = getPostBody(\n `https://www.pixiv.net/ajax/novel/series/${novel.seriesId}/unwatch`,\n \"{}\"\n )\n if (resp.error === true) {\n sleepToast(`📃 追更系列\\n\\n⚠️ 取消追更【${novel.seriesTitle}】失败`, 1)\n // shareFactory(\"series\")\n } else {\n cache.delete(`watch${novel.seriesId}`)\n sleepToast(`📃 追更系列\\n\\n✅ 已取消追更【${novel.seriesTitle}】`)\n\n let watchedSeries = getFromCacheObject(\"watchedSeries\")\n watchedSeries = watchedSeries.filter(item => item !== Number(novel.seriesId))\n putInCacheObject(\"watchedSeries\", watchedSeries)\n\n let novelObj = getAjaxJson(urlSeriesDetailed(novel.seriesId))\n novelObj.body.isWatched = false\n putInCacheObject(urlSeriesDetailed(novel.seriesId), novelObj, cacheSaveSeconds)\n }\n}\n\nfunction shareBook() {\n let text = `我正在看:【${book.author.replace(\"@\", \"\")}】创作的《${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分享自【开源阅读】Pixiv书源。使用添加网址,快速添加本文`\n java.copyText(text)\n return true\n}\n\nfunction clearCache() {\n let novel = getNovel()\n if (novel.id) {\n cache.delete(`${urlNovelUrl(novel.id)}`)\n cache.delete(`${urlNovelDetailed(novel.id)}`)\n cache.delete(`${urlSearchNovel(novel.title, 1)}`)\n }\n if (novel.seriesId) {\n cache.delete(`${urlSeriesUrl(novel.seriesId)}`)\n cache.delete(`${urlSeriesDetailed(novel.seriesId)}`)\n cache.delete(`${urlSearchSeries(novel.seriesTitle, 1)}`)\n\n let novelIds = getFromCacheObject(`novelIds${novel.seriesId}`)\n if (!novelIds) novelIds = getAjaxJson(urlIP(urlSeriesNovelsTitles(seriesId)), true).body.map(item => item.id)\n if (novelIds && novelIds.length > 0) {\n novelIds.forEach(novelId => {\n cache.delete(`${urlNovelUrl(novelId)}`)\n cache.delete(`${urlNovelDetailed(novelId)}`)\n })\n }\n }\n return true\n}\n\nfunction copyBookUrl() {\n java.copyText(book.bookUrl)\n return true\n}\nfunction copyTocUrl() {\n java.copyText(book.tocUrl)\n return true\n}\n\n// 添加书籍、删除书籍\nfunction addBookShelf() {\n let settings = getFromCacheObject(\"pixivSettings\")\n if (!settings.AUTO_LIKE_NOVELS) return\n\n let novel = getNovel()\n if (novel.seriesId) seriesWatch()\n else if (novel.id) novelBookmarkAdd()\n}\nfunction delBookShelf() {\n let settings = getFromCacheObject(\"pixivSettings\")\n if (!settings.AUTO_DISLIKE_NOVELS) return\n\n let novel = getNovel()\n if (novel.seriesId) seriesUnWatch()\n else if (novel.id) novelBookmarkDelete()\n}\n\n// 保存阅读,更新登录界面的章节名称\nfunction saveRead() {\n let novel = getNovel()\n novelData = {}\n novelData[\"章节名称\"] = book.durChapterTitle\n // novelData[\"章节名称\"] = novel.title\n novelData[\"问卷调查\"] = novel.question\n source.putLoginInfo(JSON.stringify(novelData))\n}\n\nfunction startShelfRefresh() {\n source.putConcurrent(\"18/30000\")\n}\nfunction endShelfRefresh() {\n source.putConcurrent(\"25/5000\")\n}\n\nfunction customButton(){\n java.open(\"login\")\n}\nfunction longCustomButton(){\n java.open(\"login\")\n}\n\nfunction clickBookName() {\n java.open(\"search\", null, book.name)\n return true\n}\nfunction longClickBookName() {\n let novel = getNovel()\n startBrowser(urlNovelUrl(novel.id), novel.title)\n return true\n}\n\nfunction clickAuthor() {\n java.open(\"search\", null, book.author)\n return true\n}\nfunction longClickAuthor() {\n let novel = getNovel()\n startBrowser(urlUserUrl(novel.userId), novel.userName)\n return true\n}\n\n// function clickBookLabel() {\n// java.open(\"search\", null, book.tags)\n// return true\n// }\n// function longClickBookLabel() {\n// startBrowser(urlSearchNovelWeb(book.tags,1), book.tag)\n// return true\n// }\n\nfunction callBackFactory(event) {\n switch (event) {\n case \"clickBookName\":\n return clickBookName()\n case \"longClickBookName\":\n return longClickBookName()\n\n case \"clickAuthor\":\n return clickAuthor()\n case \"longClickAuthor\":\n return longClickAuthor()\n\n // case \"clickBookLabel\":\n // return clickBookLabel()\n // case \"longClickBookLabel\":\n // return longClickBookLabel()\n\n case \"clickCustomButton\":\n return customButton()\n case \"longClickCustomButton\":\n return longCustomButton()\n\n case \"clickShareBook\":\n return shareBook()\n case \"clickClearCache\":\n return clearCache()\n\n case \"clickCopyBookUrl\":\n return copyBookUrl()\n case \"clickCopyTocUrl\":\n return copyTocUrl()\n\n // 下面的事件无法被回调结果消费\n case \"addBookShelf\":\n return addBookShelf()\n case \"delBookShelf\":\n return delBookShelf()\n\n case \"saveRead\":\n return saveRead()\n case \"startRead\":\n return saveRead()\n // case \"endRead\":\n // return endRead()\n\n case \"startShelfRefresh\":\n return startShelfRefresh()\n case \"endShelfRefresh\":\n return endShelfRefresh()\n }\n}\n\n(() => {\n return callBackFactory(event)\n})()" }, "ruleExplore": { "author": "userName", "bookList": "@js:\nvar util = objParse(String(java.get(\"util\")))\nvar seriesSet = new Set(); // 存储seriesID 有BUG无法处理翻页\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (isFunctionString(v)) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction handlerFactory() {\n if (baseUrl.includes(\"https://cdn.jsdelivr.net\")) {\n return () => {updateSource(); return []}\n }\n if (baseUrl.includes(\"https://github.com\")) {\n return () => {startBrowser(baseUrl, \"\"); return []}\n }\n if (baseUrl.includes(\"https://pixivsource.pages.dev\")) {\n return () => {startBrowser(baseUrl, \"\"); return []}\n }\n if (!isLogin() && !util.settings.DEBUG) {\n return handlerNoLogin()\n }\n\n if (baseUrl.includes(\"/bookmark\")) {\n return handlerBookMarks()\n }\n if (baseUrl.includes(\"/top\")) {\n return handlerRecommend()\n }\n if (baseUrl.includes(\"/follow_latest\")) {\n return handlerFollowLatest()\n }\n if (baseUrl.includes(\"/watch_list\")) {\n return handlerWatchList()\n }\n if (baseUrl.includes(\"/genre\")) {\n return handlerWatchList()\n }\n if (baseUrl.includes(\"/discovery\")) {\n return handlerDiscovery()\n }\n if (baseUrl.includes(\"/new\")) {\n return handlerDiscovery()\n }\n if (baseUrl.includes(\"/commission/\")) {\n return handlerFollowLatest()\n }\n if (baseUrl.includes(\"/user_event/portal\")) {\n return handlerFollowLatest()\n }\n if (baseUrl.includes(\"/editors_picks\")) {\n return handlerFollowLatest()\n }\n if (baseUrl.includes(\"/ranking\")) {\n return handlerRanking()\n }\n if (baseUrl.includes(\"/search/novels\")) {\n return handlerSearch()\n }\n if (baseUrl.includes(\"/street/for_you\")) {\n return handlerHome()\n }\n if (baseUrl.includes(\"/profile/all\")) {\n return handlerUserNovels()\n }\n // 正则匹配网址内容\n if (baseUrl.includes(\"/marker_all\")) {\n return handlerRegex()\n }\n}\n\nfunction handlerNoLogin() {\n return () => {\n sleepToast(\"⭐️ 发现小说\\n\\n⚠️ 当前未登录账号\\n请登录 Pixiv 账号\", 1.5)\n util.removeCookie(); util.login()\n sleepToast(\"⭐️ 发现小说\\n\\n登录成功后,请重新进入发现\", 2)\n return []\n }\n}\n\n// 推荐小说\nfunction handlerRecommend() {\n return () => {\n let resp = JSON.parse(result)\n const recommend = resp.body.page.recommend\n const novels = resp.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(util.handNovels(util.combineNovels(list)))\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 return util.formatNovels(util.handNovels(resp))\n }\n}\n\n//关注作者,小说委托,小说企划,编辑部推荐\nfunction handlerFollowLatest() {\n return () => {\n let resp = JSON.parse(result)\n return util.formatNovels(util.handNovels(util.combineNovels(resp.body.thumbnails.novel)))\n }\n}\n\n//推荐小说,最近小说\nfunction handlerDiscovery() {\n return () => {\n let resp = JSON.parse(result)\n return util.formatNovels(util.handNovels(util.combineNovels(resp.body.novels)))\n }\n}\n\n// 搜索标签\nfunction handlerSearch() {\n return () => {\n let resp = JSON.parse(result)\n return util.formatNovels(util.handNovels(util.combineNovels(resp.body.novel.data)))\n }\n}\n\n// 追更列表,热门分类\nfunction handlerWatchList() {\n return () => {\n let resp = JSON.parse(result)\n return util.formatNovels(util.handNovels(resp.body.thumbnails.novelSeries))\n }\n}\n\n// 排行榜\nfunction handlerRanking() {\n return () => {\n try {\n let resp = JSON.parse(result)\n return util.formatNovels(util.handNovels(resp.body.display_a.rank_a))\n } catch (e) {\n return []\n }\n }\n}\n\n// 首页\nfunction handlerHome() {\n return () => {\n let resp = JSON.parse(result)\n let novels = resp.body.contents\n .filter(item => item.kind === \"novel\")\n .flatMap(item => {\n return item.thumbnails.map(novel => {\n novel.tags = novel.tags.map(tag => tag.name)\n return novel\n })\n })\n // novels.forEach(novel => novel.tags = novel.tags.map(tag => tag.name))\n // java.log(JSON.stringify(novels))\n return util.formatNovels(util.handNovels(util.combineNovels(novels)))\n }\n}\n\n// 作者小说\nfunction handlerUserNovels() {\n return () => {\n let resp = JSON.parse(result)\n return util.formatNovels(util.handNovels(util.getUserNovelsById(resp)))\n }\n}\n\n// 书签,顺序相同\nfunction handlerRegex() {\n return () => {\n let novelIds = []\n let matched = result.match(RegExp(/\\/novel\\/show\\.php\\?id=\\d{5,}/gm))\n for (let i in matched) {\n let novelId = matched[i].match(RegExp(/\\d{5,}/))[0]\n if (novelIds.indexOf(novelId) === -1) {\n novelIds.push(novelId)\n }\n }\n\n let novels = getAjaxJson(urlIP(urlNovelsDetailed(getFromCache(\"pixivUid\"), novelIds))).body\n return util.formatNovels(util.handNovels(util.combineNovels(Object.values(novels).reverse())))\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 (isFunctionString(v)) {\n return eval(`(${v})`)\n }\n return v;\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\nfunction getUserIdCache() {\n let userId\n let userName = String(java.get(\"keyword\"))\n //cache.delete(\"pixivAuthors\")\n let pixivAuthors = getFromCacheObject(\"pixivAuthors\")\n // java.log(JSON.stringify(pixivAuthors))\n if (pixivAuthors) userId = pixivAuthors[userName]\n if (userId) {\n java.log(`👤 缓存作者ID:${userId}`)\n return [userId]\n }\n}\n\n// 模糊搜索作者\nfunction getUserNovelsByName() {\n let userName = String(java.get(\"keyword\"))\n let page = Number(java.get(\"page\"))\n\n // cache.delete(urlIP(urlSearchUser(userName, page, full)))\n let resp = getAjaxJson(urlIP(urlSearchUser(userName, page))).body\n let novels = resp.thumbnails.novel\n let userIds = resp.page.userIds\n\n java.log(`👤 获取作者ID:${JSON.stringify(userIds)}`)\n if (userIds.length === 1) {\n let pixivAuthors = getFromCacheObject(\"pixivAuthors\")\n pixivAuthors[userName] = userIds[0]\n putInCacheObject(\"pixivAuthors\", pixivAuthors)\n }\n if (userIds.length === 0) sleepToast(`\\n暂无名为【${userName}】的作者发布过小说\\n请尝试其他关键词`)\n return [userIds, novels]\n}\n\nfunction getUserNovelsById(uid) {\n return util.getUserNovelsById(getAjaxJson(urlIP(urlUserAllWorks(uid)), true))\n}\n\nfunction getUserNovels() {\n let novels = []\n let uidList = getUserIdCache()\n if (!uidList) [uidList, novels] = getUserNovelsByName()\n if (uidList.length === 1 ) novels = getUserNovelsById(uidList[0])\n return novels\n}\n\nfunction search(name, type, page) {\n let resp = {}\n if (type.includes(\"novel\")) {\n resp = getAjaxJson(urlIP(urlSearchNovel(name, page)))\n java.log(urlIP(urlSearchNovel(name, page)))\n }\n if (type.includes(\"series\")) {\n resp = getAjaxJson(urlIP(urlSearchSeries(name, page)))\n java.log(urlIP(urlSearchSeries(name, page)))\n }\n if (resp.error === true || resp.total === 0) {\n return {\"data\": [], \"total\":0, \"lastPage\": 0}\n }\n return resp.body.novel\n}\n\nfunction getSeries() {\n let resp = JSON.parse(result)\n if (resp.error === true) {\n return []\n }\n let name = String(java.get(\"keyword\"))\n let page = Number(java.get(\"page\"))\n java.log(urlIP(urlSearchSeries(name, page)))\n putInCacheObject(urlIP(urlSearchSeries(name, page)), resp, cacheSaveSeconds) // 加入缓存\n return resp.body.novel.data\n}\n\nfunction getNovels() {\n let name = String(java.get(\"keyword\"))\n let page = Number(java.get(\"page\"))\n let resp = search(name, \"novel\", page)\n return util.combineNovels(resp.data)\n}\n\nfunction getConvertNovels() {\n let novels = []\n let name = String(java.get(\"keyword\"))\n let page = Number(java.get(\"page\"))\n let name1 = String(java.s2t(name))\n let name2 = String(java.t2s(name))\n if (name1 !== name) novels = novels.concat(search(name1, \"novel\", page).data)\n if (name2 !== name) novels = novels.concat(search(name2, \"novel\", page).data)\n novels = util.combineNovels(novels)\n if (util.settings.COMBINE_NOVELS) return novels\n\n if (name1 !== name ) novels = novels.concat(search(name1, \"series\", page).data)\n if (name2 !== name) novels = novels.concat(search(name2, \"series\", page).data)\n return novels\n}\n\nfunction novelFilter(novels) {\n let textCount = 0, tags = []\n let limitedTextCount = String(java.get(\"limitedTextCount\")).replace(\"字数\", \"\").replace(\"字數\", \"\")\n // limitedTextCount = `3w 3k 3w5 3k5`.[0]\n if (limitedTextCount.includes(\"w\") || limitedTextCount.includes(\"W\")) {\n let num = limitedTextCount.toLowerCase().split(\"w\")\n textCount = 10000 * num[0] + 1000 * num[1]\n } else if (limitedTextCount.includes(\"k\") || limitedTextCount.includes(\"K\")) {\n let num = limitedTextCount.toLowerCase().split(\"k\")\n textCount = 1000 * num[0] + 100 * num[1]\n }\n\n let novels0 = novels.map(novel => novel.id)\n if (textCount >= 1) {\n novels = novels.filter(novel => novel.textCount >= textCount)\n let novels1 = novels.map(novel => novel.id)\n java.log(`🔢 字数限制:${limitedTextCount}`)\n java.log(`⏬ 字数限制:过滤前${novels0.length};过滤后${novels1.length}`)\n }\n\n let inputTags = String(java.get(\"inputTags\")).split(\" \")\n for (let i in inputTags) {\n let tag = inputTags[i].trim()\n if (tag !== \"\") tags.push(`${tag}`)\n }\n\n if (tags.length >= 1) {\n // 仅保留含有所有标签的小说\n // novels = novels.filter(novel => {\n // // java.log(`${JSON.stringify(novel.tags)}\\n${tags.every(item => novel.tags.includes(item))}`)\n // return tags.every(item => novel.tags.includes(item))\n // })\n novels = novels.filter(novel => tags.every(item => novel.tags.includes(item)))\n let novels2 = novels.map(novel => novel.id)\n java.log(`#️⃣ 过滤标签:${tags.join(\"、\")}`)\n java.log(`#️⃣ 过滤标签:过滤前${novels0.length};过滤后${novels2.length}`)\n }\n\n let inputAuthor = String(java.get(\"inputAuthor\")).trim()\n if (inputAuthor) {\n // novels = novels.filter(novel => {\n // java.log(`${novel.userName}-${novel.userName.includes(inputAuthor)}`)\n // return novel.userName.includes(inputAuthor)\n // })\n novels = novels.filter(novel => novel.userName.includes(inputAuthor))\n let novels2 = novels.map(novel => novel.id)\n java.log(`👤 过滤作者:${inputAuthor.join(\"、\")}`)\n java.log(`👤 过滤作者:过滤前${novels0.length};过滤后${novels2.length}`)\n }\n return novels\n}\n\nfunction handlerFactory() {\n let novels = []\n let keyword = String(java.get(\"keyword\"))\n if (keyword.startsWith(\"#\")) {\n java.put(\"keyword\", keyword.slice(1))\n novels = novels.concat(getNovels())\n if (util.settings.COMBINE_NOVELS) novels = novels.concat(getSeries())\n }\n\n // 登录检测\n else if (!isLogin() && !util.settings.DEBUG) {\n sleepToast(\"🔍 搜索小说\\n\\n⚠️ 当前未登录账号\\n请登录 Pixiv 账号\", 1.5)\n util.removeCookie(); util.login()\n sleepToast(\"🔍 搜索小说\\n\\n登录成功后,请重新搜索\", 2)\n return novels\n }\n\n else if (keyword.startsWith(\"@\") ) {\n java.put(\"keyword\", keyword.slice(1))\n novels = novels.concat(getUserNovels())\n\n } else if (keyword.startsWith(\"$\") || util.settings.SEARCH_AUTHOR) {\n if (keyword.startsWith(\"$\")) {\n keyword = keyword.slice(1)\n java.put(\"keyword\", keyword)\n }\n sleepToast(`\\n\\n👤 模糊搜索作者:${keyword}`)\n novels = novels.concat(getUserIdOnline()[1])\n\n } else {\n novels = novels.concat(getNovels())\n if (util.settings.COMBINE_NOVELS) novels = novels.concat(getSeries())\n if (util.settings.CONVERT_CHINESE) novels = novels.concat(getConvertNovels())\n }\n // java.log(JSON.stringify(novels))\n return novelFilter(util.formatNovels(util.handNovels(novels)))\n}\n\n(() => {\n return handlerFactory()\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 (isFunctionString(v)) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction urlNovel(novelId){\n if (util.settings.SHOW_ORIGINAL_LINK) {\n return urlNovelUrl(novelId)\n } else {\n return urlNovelDetailed(novelId)\n }\n}\n\nfunction oneShotHandler(res) {\n res.textCount = res.userNovels[`${res.id}`].textCount\n res.createDate = timeTextFormat(res.createDate)\n return [{\n title: res.title.trim(),\n chapterUrl: urlIP(urlNovel(res.id)),\n chapterInfo: `${res.createDate}  ${res.textCount}字`\n }]\n}\n\nfunction seriesHandler(res) {\n const limit = 30\n let returnList = [], novelIds = []\n let seriesID = res.id, allChaptersCount = res.total\n util.debugFunc(() => {\n java.log(`本系列 ${seriesID} 一共有${allChaptersCount}章`)\n })\n\n //发送请求获得相应数量的目录列表\n function sendAjaxForGetChapters(lastIndex) {\n let resp = getAjaxJson(urlIP(urlSeriesNovels(seriesID, limit, lastIndex)), true)\n let novels = resp.body.thumbnails.novel\n // let novels = resp.body.page.seriesContents\n\n novels.forEach((v, i) => {\n novelIds.push(v.id)\n v.chapterUrl = urlIP(urlNovel(v.id))\n v.title = v.title.trim()\n if (util.settings.ADD_CHAPTER_INDEX) v.title = \"第\"+ (i+1) +\"章 \" + v.title\n if (v.updateDate && v.textCount) {\n v.updateDate = timeTextFormat(v.createDate)\n v.chapterInfo = `${v.updateDate}  ${v.textCount}字`\n }\n\n util.debugFunc(() => {\n java.log(`${v.title}`)\n })\n })\n return novels\n }\n\n if (!util.settings.SHOW_UPDATE_TIME) {\n returnList = getAjaxJson(urlIP(urlSeriesNovelsTitles(seriesID)), true).body\n returnList.forEach((v, i) => {\n novelIds.push(v.id)\n v.chapterUrl = urlIP(urlNovel(v.id))\n v.title = v.title.trim()\n if (util.settings.ADD_CHAPTER_INDEX) v.title = \"第\"+ (i+1) +\"章 \" + v.title\n\n util.debugFunc(() => {\n java.log(`${v.title}`)\n })\n })\n } else {\n //逻辑控制者 也就是使用上面定义的两个函数来做对应功能\n //要爬取的总次数\n let max = Math.ceil(allChaptersCount / limit)\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 }\n putInCacheObject(`novelIds${seriesID}`, novelIds, cacheSaveSeconds)\n // java.log(JSON.stringify(returnList))\n return returnList\n}\n\n(() => {\n let res = util.getNovelRespFirst(result).body\n if ((res.firstNovelId || res.seriesNavData) && util.settings.COMBINE_NOVELS) {\n return seriesHandler(res)\n } else {\n return oneShotHandler(res)\n }\n})()", "chapterName": "title", "chapterUrl": "chapterUrl", "updateTime": "chapterInfo" }, "searchUrl": "@js:\njava.put(\"key\", key)\njava.put(\"page\", page)\nlet keyword = key.split(\" \")\nlet limitedTextCount\nif (key.includes(\"字数\") || key.includes(\"字數\") ) {\n limitedTextCount = keyword.pop()\n keyword = keyword.join(\" \")\n} else {\n limitedTextCount = \"\"\n keyword = key\n}\njava.put(\"keyword\", keyword)\njava.put(\"limitedTextCount\", limitedTextCount)\n\nif (keyword.startsWith(\"@\") || keyword.startsWith(\"@\")) {\n keyword = keyword.slice(1)\n if (keyword.includes(\"#\") || keyword.includes(\"#\")) {\n let author = keyword.split(\" \")[0]\n let tags = keyword.replace(author, \"\").trim().slice(1)\n java.put(\"keyword\", `@${author}`)\n java.put(\"inputTags\", tags)\n java.log(`👤 搜索作者:${author} #️⃣ 过滤标签:${tags.replace(\" \", \"、\")}`)\n } else {\n java.put(\"keyword\", `@${keyword}`)\n java.log(`👤 搜索作者:${keyword}`)\n }\n\n} else if (keyword.startsWith(\"$\") || keyword.startsWith(\"$\")) {\n keyword = keyword.slice(1)\n // java.log(`👤 粗略搜索作者:${keyword}`)\n java.put(\"keyword\", `$${keyword}`)\n\n} else if (keyword.startsWith(\"#\") || keyword.startsWith(\"#\")) {\n keyword = keyword.slice(1)\n java.log(`#️⃣ 搜索标签:${keyword}`)\n java.put(\"keyword\", `#${keyword}`)\n\n} else {\n java.log(`🔍 搜索内容:${keyword}`)\n}\nurlIP(urlSearchSeries(keyword, page))", "variableComment": "⚙️ 书源设置:\n1️⃣ 书架 - 阅读界面 - Pixiv 小说 - 登录\n2️⃣ 我的 - 书源管理 - Pixiv 小说 - 登录\n点击【👀 书源设置】【👀 发现设置】显示相关设置按钮\n\n▶️ 自定功能:\n小说书架 - 阅读界面 - Pixiv 小说 - 登录\n\n💬 发送评论:\n1️⃣ 文本框内 输入内容,点击【✅ 发送评论】\n\n🚫 屏蔽标签/描述(本地):\n1️⃣ 点击【👀 查看屏蔽】,切换至屏蔽列表\n2️⃣ 文本框内 输入内容,点击【🚫 加入屏蔽】\n\n📌 喜欢标签(本地):\n1️⃣ 文本框内 输入标签,点击【📌 喜欢标签】\n\n❤️ 关注 他人收藏(本地):\n1️⃣ 文本框内 输入作者ID,点击【❤️ 他人收藏】\n\n", "weight": 0 }, { "bookSourceComment": "🅿️ Pixiv 漫画(更新📆:2026/09/21)\n\n书源版本:284\n使用说明:📌阅读 Sigma 3.26.0216 版本可用\n可用功能:✅搜索✅发现✅添加网址✅订阅源\n搜索插画:✅插画✅漫画✅动图✅标签\n发现漫画:✅关注✅追更✅推荐✅发现✅收藏\n添加网址:✅插画链接✅漫画目录\n订阅用法:点击订阅源打开插画/漫画目录,【刷新】,点击【加入书架】按钮,添加到书架\n\n书源发布:Pixiv 书源频道 https://t.me/PixivSource\n项目地址:https://github.com/DowneyRem/PixivSource\n使用教程:https://pixivsource.pages.dev/Pixiv\n\n⚙️ 书源设置:\n设置1️⃣:打开小说 - 菜单 - 登录 - 点击下方按钮\n设置2️⃣:编辑书源 - 基本 - 变量说明 - 修改并保存", "bookSourceGroup": "🔞 Pixiv 漫画", "bookSourceName": "🅿️ Pixiv 漫画", "bookSourceType": 2, "bookSourceUrl": "https://www.pixiv.net/manga", "bookUrlPattern": "(https?://)?(www\\.)?pixiv\\.net/((artworks|user/\\d+/series)|ajax/(illust|series))/\\d+", "concurrentRate": "30/5000", "customButton": true, "customOrder": 2, "enabled": false, "enabledCookieJar": true, "enabledExplore": true, "eventListener": true, "exploreUrl": "@js:\nlet SHOW_GENERAL_NEW, SHOW_GENERAL_RANK\ntry {\n settings = JSON.parse(String(source.variableComment).match(RegExp(/{([\\s\\S]*?)}/gm)))\n SHOW_GENERAL_NEW = settings.SHOW_GENERAL_NEW // 发现:最新、企划、约稿显示一般小说\n} catch (e) {\n SHOW_GENERAL_NEW = false\n}\n\nli = [\n {\"⭐️ 关注\": \"https://www.pixiv.net/ajax/follow_latest/illust?p={{page}}&mode=all&lang=zh\"},\n {\"📃 追更\": \"https://www.pixiv.net/ajax/watch_list/manga?p={{page}}&new=1&lang=zh\"},\n {\"💯 推荐\": \"https://www.pixiv.net/ajax/top/illust?mode=all&lang=zh\"},\n {\"🔍 发现\": \"https://www.pixiv.net/ajax/illust/discovery?mode=all&lang=zh\"},\n {\"❤️ 收藏\": \"https://www.pixiv.net/ajax/user/{{cache.get(\\\"pixivUid\\\")}}/illusts/bookmarks?tag=&offset={{(page-1)*24}}&limit=24&rest=show&lang=zh\"},\n {\"㊙️ 收藏\": \"https://www.pixiv.net/ajax/user/{{cache.get(\\\"pixivUid\\\")}}/illusts/bookmarks?tag=&offset={{(page-1)*24}}&limit=24&rest=hide&lang=zh\"},\n {\"🏠 首页\": \"https://www.pixiv.net\"},\n {\"🆙 更新\": \"https://cdn.jsdelivr.net/gh/DowneyRem/PixivSource@main/pixiv.json\"}\n]\n\nr18New = [\n {\"🆕 最新 企划 约稿 💰\": \"\"},\n {\"🆕 最新\": \"https://www.pixiv.net/ajax/illust/new?lastId=0&limit=20&type=manga&r18=true&lang=zh\"},\n {\"📑 企划\": \"https://www.pixiv.net/ajax/user_event/portal/artworks?mode=r18&p={{page}}&lang=zh\"},\n {\"💰 约稿\": \"https://www.pixiv.net/ajax/commission/page/request/complete/manga?mode=r18&p={{page}}&lang=zh\"},\n {\"🔍 发现\": \"https://www.pixiv.net/ajax/illust/discovery?mode=r18&lang=zh\"},\n]\n\ngeneralNew = [\n {\"✅ 最新 企划 约稿 ✅\": \"\"},\n {\"最新\": \"https://www.pixiv.net/ajax/illust/new?lastId=0&limit=20&type=manga&lang=zh\"},\n {\"企划\": \"https://www.pixiv.net/ajax/user_event/portal/artworks?mode=all&p={{page}}&lang=zh\"},\n {\"约稿\": \"https://www.pixiv.net/ajax/commission/page/request/complete/manga?mode=all&p={{page}}&lang=zh\"},\n {\"发现\": \"https://www.pixiv.net/ajax/illust/discovery?mode=all&lang=zh\"}\n]\n\nr18Rank = [\n {\"👑 排行榜单 👑\": \"\"},\n {\"今日\": \"https://www.pixiv.net/ranking.php?mode=daily_r18&p={{page}}&format=json\"},\n {\"本周\": \"https://www.pixiv.net/ranking.php?mode=weekly_r18&p={{page}}&format=json\"},\n {\"R18G\": \"https://www.pixiv.net/ranking.php?mode=r18g&p={{page}}&format=json\"},\n {\"男性\": \"https://www.pixiv.net/ranking.php?mode=male_r18&p={{page}}&format=json\"},\n {\"女性\": \"https://www.pixiv.net/ranking.php?mode=female_r18&p={{page}}&format=json\"}\n]\n\ngeneralRank = [\n {\"🏆 排行榜单 🏆\": \"\"},\n {\"今日\": \"https://www.pixiv.net/ranking.php?mode=daily&p={{page}}&format=json\"},\n {\"本周\": \"https://www.pixiv.net/ranking.php?mode=weekly&p={{page}}&format=json\"},\n {\"本月\": \"https://www.pixiv.net/ranking.php?mode=monthly&p={{page}}&format=json\"},\n {\"新人\": \"https://www.pixiv.net/ranking.php?mode=rookie&p={{page}}&format=json\"},\n {\"原创\": \"https://www.pixiv.net/ranking.php?mode=original&p={{page}}&format=json\"},\n {\"A I \": \"https://www.pixiv.net/ranking.php?mode=original&p={{page}}&format=json\"},\n {\"男性\": \"https://www.pixiv.net/ranking.php?mode=male&p={{page}}&format=json\"},\n {\"女性\": \"https://www.pixiv.net/ranking.php?mode=female&p={{page}}&format=json\"},\n]\n\nlet source = [\n {\"📘 书源相关 📘\": \"\"},\n {\"🏠 主页\": \"https://pixivsource.pages.dev\"},\n {\"🔰 指南\": \"https://pixivsource.pages.dev/Pixiv\"},\n {\"🐞 反馈\": \"https://github.com/DowneyRem/PixivSource/issues\"},\n {\"💰 打赏\": \"https://pixivsource.pages.dev/Sponsor\"},\n]\n\nli = li.concat(r18New)\nif (SHOW_GENERAL_NEW) li = li.concat(generalNew)\nli = li.concat(r18Rank)\nif (SHOW_GENERAL_RANK) li = li.concat(generalRank)\nli = li.concat(source)\n\n// 添加格式\nli.forEach(item => {\n item.title = Object.keys(item)[0]\n item.url = Object.values(item)[0]\n if (item.url.includes(\"https://www.pixiv.net\")) item.url = urlIP(item.url)\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})\n\nJSON.stringify(li)", "header": "{\"Referer\":\"https://www.pixiv.net\"}", "jsLib": "var cacheSaveSeconds = 30*24*60*60 // 长期缓存 30 天\nvar cacheTempSeconds = 10*60*1000 // 冷却时间 10 分钟\n\nfunction cacheGetAndSet(key, supplyFunc, requestUpdate) {\n const {java, cache} = this\n let timestamp = 0\n let v = this.getFromCacheObject(key)\n if (Array.isArray(v)) {\n try {\n timestamp = v[0].timestamp\n } catch (e) {\n timestamp = 0\n }\n } else if (v) {\n timestamp = v.timestamp\n }\n\n const isExpired = v && (new Date().getTime() >= timestamp + cacheTempSeconds)\n const isError = v && (v.error === true) && isExpired\n requestUpdate = requestUpdate && isExpired\n\n if (!v || requestUpdate || isError) {\n v = supplyFunc()\n let now = new Date().getTime()\n // getAjaxJson getWebviewJson 时间戳写入对象本身\n if (!Array.isArray(v)) {\n v = Object.assign({timestamp: now}, v)\n }\n // else {\n // // getAjaxAllJson 时间戳写入第一个元素(读取时 v[0].timestamp)// 不重复写入\n // if (v.length > 0) v[0] = Object.assign({timestamp: now}, v[0])\n // }\n this.putInCacheObject(key, v, cacheSaveSeconds)\n }\n return v\n}\n\nfunction putInCache(name, object, saveSeconds) {\n const {java, cache} = this\n if (saveSeconds === undefined) saveSeconds = 0\n if (object) {\n cache.put(name, object, saveSeconds)\n }\n}\nfunction getFromCache(name) {\n const {java, cache} = this\n let object = cache.get(name)\n if (object === undefined) return null // 兼容源阅\n return object\n}\n\nfunction normalizeUrl(url) {\n if (!url.startsWith(\"https://210.140\")) return url\n return url.replace(\"210.140.139.155\", \"www.pixiv.net\")\n .replace(\"210.140.139.133\", \"i.pximg.net\")\n .split(\",\")[0]\n}\nfunction putInCacheObject(objectName, object, saveSeconds) {\n const {java, cache} = this\n if (object === undefined) object = null\n if (saveSeconds === undefined) saveSeconds = 0\n // if (objectName === \"pixivSettings\") {\n // this._settings = object\n // }\n cache.put(this.normalizeUrl(objectName), JSON.stringify(object), saveSeconds)\n}\nfunction getFromCacheObject(objectName) {\n const {java, cache} = this\n // if (objectName === \"pixivSettings\" && this._settings) {\n // return this._settings\n // }\n let object = cache.get(this.normalizeUrl(objectName))\n if (object === undefined) return null // 兼容源阅,避免 parse 报错\n return JSON.parse(object)\n}\n\nfunction isHtmlString(str) {\n return str.startsWith(\"\") || str.startsWith(\"\")\n}\nfunction isJsonString(str) {\n try {\n let result = JSON.parse(str)\n return typeof result === \"object\" && result !== null\n } catch(e) {\n return false\n }\n}\nfunction isFunctionString(str) {\n return typeof str == \"string\" && (\n str.trim().startsWith(\"function\") ||\n RegExp(/^\\s*(\\([^)]*\\)|[A-Za-z_$][\\w$]*)\\s*=>/).test(str)\n )\n}\n\nfunction isLogin() {\n const {java, cache} = this\n return !!this.getFromCache(\"pixivCsrfToken\")\n}\n\nfunction getAjaxJson(url, requestUpdate) {\n const {java, cache} = this\n return this.cacheGetAndSet(url, () => {\n return JSON.parse(java.ajax(url))\n }, requestUpdate)\n}\nfunction getAjaxAllJson(urls, requestUpdate) {\n const {java, cache} = this\n let batchKey = JSON.stringify(urls)\n return this.cacheGetAndSet(batchKey, () => {\n let results = []\n let now = new Date().getTime()\n let responses = java.ajaxAll(urls)\n for (let i in urls) {\n let data = JSON.parse(responses[i].body())\n data = Object.assign({timestamp: now}, data)\n results.push(data)\n this.putInCacheObject(urls[i], data, cacheSaveSeconds)\n }\n return results\n }, requestUpdate)\n}\nfunction getAjaxParseJson(url, parseFunc, requestUpdate) {\n const {java, cache} = this\n return this.cacheGetAndSet(url, () => {\n let resp = parseFunc(java.ajax(url))\n if (resp instanceof Object) return resp\n else return JSON.parse(resp)\n }, requestUpdate)\n}\nfunction getWebviewJson(url, parseFunc, requestUpdate) {\n const {java, cache} = this\n return this.cacheGetAndSet(url, () => {\n let html = java.webView(null, url, null)\n return JSON.parse(parseFunc(html))\n }, requestUpdate)\n}\n\nfunction getWebViewUA() {\n const {java, cache} = this\n let userAgent = this.getFromCache(\"userAgent\")\n if (userAgent) return String(userAgent)\n\n 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 this.putInCache(\"userAgent\", userAgent, cacheSaveSeconds/7)\n return String(userAgent)\n}\nfunction startBrowser(url, title) {\n const {java, cache} = this\n if (!title) title = url\n let msg = \"\"\n let headers = {}\n headers[\"User-Agent\"] = this.getWebViewUA()\n\n if (url.includes(\"https://www.pixiv.net\")) {\n if (url.includes(\"settings\")) msg += \"⚙️ 账号设置\"\n else msg += \"⤴️ 分享小说\"\n msg += \"\\n\\n即将打开 Pixiv\\n请确认已开启代理/梯子/VPN等\"\n } else if (url.includes(\"github.com\") || url.includes(\"github.io\")) {\n if (url.includes(\"issues\")) msg += \"🐞 反馈问题\"\n msg += \"\\n\\n即将打开 Github\\n请确认已开启代理/梯子/VPN等\"\n }\n this.sleepToast(msg, 0.01)\n java.startBrowser(`${url}, ${JSON.stringify({headers: headers})}`, title)\n}\n\n// 直连功能参考自 洛娅橙的阅读仓库\n// https://github.com/Luoyacheng/yuedu\n// 其直连功能参考自 PixEz Flutter\n// https://github.com/Notsfsssf/pixez-flutter\nfunction urlIP(url) {\n const {java, cache} = this\n let settings = this.getFromCacheObject(\"pixivIllustSettings\")\n if (!settings) settings = this.setDefaultSettings()\n if (settings.IPDirect) {\n url = url.replace(\"http://\", \"https://\").replace(\"www.pixiv.net\", \"210.140.139.155\")\n let headers = {\n \"User-Agent\": \"Mozilla/5.0 (Linux; Android 14)\",\n \"X-Requested-With\": \"XMLHttpRequest\",\n \"Host\": \"www.pixiv.net\",\n \"Referer\": \"https://www.pixiv.net/\",\n \"X-csrf-token\": this.getFromCache(\"pixivCsrfToken\") || \"\",\n \"Cookie\": this.getFromCache(\"pixivCookie\") || \"\"\n }\n return `${url}, ${JSON.stringify({headers: headers})}`\n }\n return url\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 urlIllustsDetailed(userId, idList) {\n return `https://www.pixiv.net/ajax/user/${userId}/illusts?${idList.map(v => \"ids[]=\" + v).join(\"&\")}`\n}\nfunction urlSeriesUrl(userId, seriesId) {\n return `https://www.pixiv.net/user/${userId}/series/${seriesId}`\n}\nfunction urlSeriesDetailed(seriesId, page) {\n if (page === undefined) page = 1\n return `https://www.pixiv.net/ajax/series/${seriesId}?p=${page}&lang=zh`\n}\n\nfunction urlUserAllWorks(userId) {\n return `https://www.pixiv.net/ajax/user/${userId}/profile/all?lang=zh`\n}\n\nfunction urlSearchArtwork(name, page) {\n return `https://www.pixiv.net/ajax/search/artworks/${encodeURI(name)}?word=${encodeURI(name)}&order=date_d&mode=all&p=${page}&s_mode=s_tc&type=all&lang=zh`\n}\nfunction urlSearchIllust(name, page) {\n return `https://www.pixiv.net/ajax/search/artworks/${encodeURI(name)}?word=${encodeURI(name)}&order=date_d&mode=all&p=${page}&s_mode=s_tc&type=illust&lang=zh`\n}\nfunction urlSearchManga(name, page) {\n return `https://www.pixiv.net/ajax/search/artworks/${encodeURI(name)}?word=${encodeURI(name)}&order=date_d&mode=all&p=${page}&s_mode=s_tc&type=manga&lang=zh`\n}\nfunction urlSearchUgoira(name, page) {\n return `https://www.pixiv.net/ajax/search/artworks/${encodeURI(name)}?word=${encodeURI(name)}&order=date_d&mode=all&p=${page}&s_mode=s_tc&type=ugoira&lang=zh`\n}\n// 完全匹配用户名\nfunction urlSearchUser(name) {\n return `https://www.pixiv.net/search/users?nick=${encodeURI(name)}&s_mode=s_usr&nick_mf=1`\n}\n\n// 直连功能参考自 洛娅橙的阅读仓库\n// https://github.com/Luoyacheng/yuedu\n// 其直连功能参考自 PixEz Flutter\n// https://github.com/Notsfsssf/pixez-flutter\nfunction urlCoverUrl(url) {\n const {java, cache} = this\n if (url && !url.trim()) return \"\"\n\n let settings = this.getFromCacheObject(\"pixivIllustSettings\")\n if (!settings) settings = this.setDefaultSettings()\n if (!settings.IPDirect) return url\n\n let headers = {\"Referer\": \"https://www.pixiv.net/\"}\n if (settings.IPDirect && 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}\n\nfunction addZero(num) {\n return String(num).padStart(2, '0')\n}\nfunction dateFormat(str) {\n let time = new Date(str);\n let Y = time.getFullYear() + \"年\";\n let M = this.addZero(time.getMonth() + 1) + \"月\";\n let D = this.addZero(time.getDate()) + \"日\";\n return Y + M + D;\n}\nfunction timeFormat(str) {\n let time = new Date(str);\n let YY = time.getFullYear()\n let MM = this.addZero(time.getMonth() + 1)\n let DD = this.addZero(time.getDate())\n let hh = this.addZero(time.getHours())\n let mm = this.addZero(time.getMinutes())\n let ss = this.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}\n\nfunction sleep(seconds) {\n return Packages.java.lang.Thread.sleep(1000*seconds)\n}\nfunction sleepToast(text, seconds) {\n let {java} = this\n java.log(text)\n java.longToast(text)\n if (seconds === undefined) {seconds = 0.01}\n this.sleep(seconds)\n}\n\nfunction setDefaultSettings() {\n const {java, cache} = this\n let settings = {}\n settings.CONVERT_CHINESE = true // 搜索:搜索时进行繁简转换\n settings.SHOW_ORIGINAL_LINK = true // 目录:显示源链接\n settings.QUALITY_REGULAR = true // 正文:图片质量,regular 或 original\n\n settings.IPDirect = false // 全局:直连模式\n settings.DEBUG = false // 全局:调试模式\n\n this.putInCacheObject(\"pixivIllustSettings\", settings)\n return settings\n}\nfunction checkSettings() {\n const {java, cache} = this\n let settings = this.getFromCacheObject(\"pixivIllustSettings\")\n if (!settings) settings = this.setDefaultSettings()\n if (settings.IPDirect) {\n settings.SEARCH_AUTHOR = false // 搜索:默认关闭搜索作者名称\n settings.SHOW_ORIGINAL_LINK = false // 目录:不显示章节源链接\n }\n this.putInCacheObject(\"pixivIllustSettings\", settings)\n return settings\n}\n\nfunction updateSource() {\n try {\n this.updateSourceHtml()\n } catch(e) {\n this.updateSourceLink()\n }\n}\n\nfunction updateSourceLink(){\n const {java, source} = this\n let sourceName\n if (source.bookSourceUrl.includes(\"pixiv\")) sourceName = \"pixiv\"\n else if (source.bookSourceUrl.includes(\"furrynovel\")) sourceName = \"linpx\"\n\n try {\n java.openUrl(`legado://import/importonline?src=https://cdn.jsdelivr.net/gh/DowneyRem/PixivSource@main/${sourceName}.json`)\n } catch (e) {\n java.openUrl(`legado://import/importonline?src=https://raw.githubusercontent.com/DowneyRem/PixivSource/main/${sourceName}.json`)\n }\n}\n\nfunction updateSourceHtml() {\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 comment = onlineSource.bookSourceComment.split(\"\\n\")\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 comment = onlineSource.bookSourceComment.split(\"\\n\")\n } catch (e) {\n onlineSource = {lastUpdateTime: new Date().getTime()}\n comment = source.bookSourceComment.split(\"\\n\")\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, 9).join(\"
\")}
${comment.slice(comment.length-3, 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": 1789920000251, "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\n// 检测 源阅\n// 可用 java.ajax() 不可用 java.webview() java.ajaxAll()\n// 可用 java.getCookie() cache.put() cache.get() 默认值为 undefined\n// 可用 java.startBrowser() 不可用 java.startBrowserAwaitAwait\n// 可用 source.bookSourceName source.getVariable() source.setVariable()等\n// java.getUserAgent() java.getWebViewUA() 目前返回内容相同\nfunction isSourceRead() {\n return java.getUserAgent() === java.getWebViewUA()\n}\n// 正式版 不支持在 JSlib 的函数直接设置默认参数\n// 正式版 不支持 a?.b 的写法\n// 检测 阅读 正式版 与 Beta 版本\nfunction isLegadoOfficial() {\n let isLegadoOfficialStatus\n try {\n eval('({})?.value')\n isLegadoOfficialStatus = false\n } catch (e) {\n isLegadoOfficialStatus = true\n }\n return isLegadoOfficialStatus\n}\n// 检测 阅读 Beta 版本 与 LYC 版本\n// LYC 版本新增函数\n// java.ajaxTestAll()\n// java.openVideoPlayer(url: String, title: String, float: Boolean)\n// cookie.setWebCookie(url,cookie)\n// source.refreshExplore()\n// source.refreshJSLib()\nfunction isLegadoLYC() {\n return typeof java.ajaxTestAll === \"function\"\n}\n\nfunction publicFunc() {\n let u = {}, settings = {}\n // 输出书源信息\n java.log(`${source.bookSourceComment.split(\"\\n\")[0]}`)\n java.log(`📌 ${source.bookSourceComment.split(\"\\n\")[2]}`)\n java.log(`📆 更新时间:${java.timeFormat(source.lastUpdateTime)}`)\n\n if (isSourceRead()) {\n java.log(\"📱 软件平台:🍎 源阅 SourceRead\")\n } else if (isLegadoOfficial()) {\n java.log(\"📱 软件平台:🤖 阅读 正式版\")\n sleepToast(\"\\n⚠️当前软件为:阅读【正式版】\\n【正式版】已年久失修,不推荐继续使用\\n\\n为了更好的使用体验,请用:\\n【阅读 Plus】或【阅读 Beta 新包名】\\n\\n即将为您打开【阅读 Plus】下载界面\")\n sleep(3); startBrowser(\"https://pixivsource.pages.dev/Download\", \"下载阅读 Plus\")\n\n } else {\n if (isLegadoLYC()) {\n java.log(\"📱 软件平台:🤖 阅读 Beta【新包名】/ 阅读 Plus\")\n } else {\n java.log(\"📱 软件平台:🤖 阅读 Beta【原包名】\")\n sleepToast(\"\\n⚠️当前软件为:阅读 Beta【原包名】\\n\\n为了更好的使用体验,请用:\\n【阅读 Plus】或【阅读 Beta 新包名】\\n\\n即将为您打开【阅读 Plus】下载界面\")\n sleep(3); startBrowser(\"https://pixivsource.pages.dev/Download\", \"下载阅读 Plus\")\n }\n }\n\n // 设置初始化\n // cache.delete(\"pixivIllustSettings\")\n settings = getFromCacheObject(\"pixivIllustSettings\")\n if (settings) {\n java.log(\"⚙️ 使用自定义设置\")\n } else {\n java.log(\"⚙️ 使用默认设置\")\n settings = setDefaultSettings()\n }\n settings = checkSettings()\n if (settings.IPDirect) {\n java.log(\"✈️ 直连模式:✅ 已开启\")\n } else {\n java.log(\"✈️ 直连模式:❌ 已关闭\")\n }\n u.settings = settings\n putInCacheObject(\"pixivIllustSettings\", settings) // 设置写入缓存\n\n u.environment = {}\n u.environment.IS_SOURCEREAD = isSourceRead()\n u.environment.IS_LEGADO = !isSourceRead()\n u.environment.IS_LYC_BRUNCH = isLegadoLYC()\n putInCacheObject(\"pixivEnvironment\", u.environment) // 设置写入缓存\n\n u.debugFunc = (func) => {\n if (util.settings.DEBUG === true) {\n func()\n }\n }\n\n u.checkStatus = function(status) {\n if (status === true) return \"✅ 已\"\n else if (status === false) return \"❌ 未\"\n else if (status === undefined) return \"🈚️ 无数据:\"\n }\n\n u.login = function() {\n let resp = java.startBrowserAwait(`https://accounts.pixiv.net/login,\n {\"headers\": {\"User-Agent\": \"${java.getWebViewUA()}\"}}`, '登录账号', false)\n if (resp.code() === 200) {\n this.getCsrfToken(); this.getCookie()\n } else {\n java.log(resp.code()); sleepToast(\"⚠️ 登录失败\")\n }\n }\n\n u.logout = function() {\n this.removeCookie()\n java.startBrowser(\"https://www.pixiv.net/logout.php\", \"退出账号\")\n this.removeCookie()\n sleepToast(`✅ 已退出当前账号\\n\\n退出后请点击右上角的 ✔️ 退出\\n\\n登录请点击【登录账号】进行登录`)\n }\n\n u.getCookie = function() {\n let pixivCookie = String(java.getCookie(\"https://www.pixiv.net/\", null))\n if (isLogin()) putInCache(\"pixivCookie\", pixivCookie, 60*60) // 缓存1h\n }\n\n u.removeCookie = function() {\n cookie.removeCookie('https://www.pixiv.net')\n cookie.removeCookie('https://accounts.pixiv.net')\n cookie.removeCookie('https://accounts.google.com')\n cookie.removeCookie('https://api.weibo.com')\n cache.delete(\"pixivCookie\")\n cache.delete(\"pixivUid\")\n cache.delete(\"pixivCsrfToken\") // 与登录设备有关\n cache.delete(\"headers\")\n }\n\n // 获取 Csrf Token,以便进行收藏等请求\n // 获取方法来自脚本 Pixiv Previewer\n // https://github.com/Ocrosoft/PixivPreviewer\n // https://greasyfork.org/zh-CN/scripts/30766-pixiv-previewer/code\n u.getCsrfToken = function() {\n let pixivCsrfToken = getFromCache(\"pixivCsrfToken\")\n if (!pixivCsrfToken) {\n let html = java.ajax(\"https://www.pixiv.net/\")\n try {\n pixivCsrfToken = html.match(/token\\\\\":\\\\\"([a-z0-9]{32})/)[1]\n putInCache(\"pixivCsrfToken\", pixivCsrfToken) // 与登录设备有关,无法存储 nul\n } catch (e) {\n pixivCsrfToken = null\n cache.delete(\"pixivCsrfToken\") // 与登录设备有关,无法存储 nul\n // sleepToast(\"⚠️ 未登录账号(pixivCsrfToken)\")\n }\n java.log(`pixivCsrfToken:\\n${pixivCsrfToken}`)\n }\n return pixivCsrfToken\n }\n\n u.handIllusts = function (illusts) {\n illusts.forEach(illust => {\n // illust.id = illust.id\n // illust.title = illust.title\n // illust.userName = illust.userName\n // illust.tags = illust.tags\n if (!(illust.tags instanceof Array)) {\n illust.tags = illust.tags.tags.map(item => item.tag)\n illust.coverUrl = illust.url = illust.urls.regular // 兼容正文搜索\n illust.updateDate = illust.uploadDate\n }\n illust.textCount = null\n // illust.pageCount = illust.pageCount\n // illust.description = illust.description\n illust.coverUrl = illust.url\n illust.detailedUrl = urlIP(urlIllustDetailed(illust.id))\n // illust.createDate = illust.createDate\n // illust.updateDate = illust.updateDate\n // illust.aiType = illust.aiType\n\n if (illust.seriesNavData === undefined || illust.seriesNavData === null) {\n illust.latestChapter = illust.title\n } else {\n illust.seriesId = illust.seriesNavData.seriesId\n illust.title = illust.seriesNavData.title\n }\n\n if (illust.seriesId !== undefined) {\n let resp = getAjaxJson(urlIP(urlSeriesDetailed(illust.seriesId))).body\n let series = resp.illustSeries.filter(item => item.id === illust.seriesId)[0]\n // illust.title = illust.title\n illust.tags = illust.tags.concat(series.tags)\n illust.latestChapter = resp.thumbnails.illust.filter(item => item.id === series.latestIllustId)[0].title\n illust.description = series.description\n if (series.url === undefined) {\n let firstChapter = getAjaxJson(urlIP(urlIllustDetailed(series.firstIllustId))).body\n illust.coverUrl = firstChapter.urls.regular\n illust.tags = illust.tags.concat(firstChapter.tags.tags.map(item => item.tag))\n }\n illust.createDate = series.createDate\n illust.updateDate = series.updateDate\n illust.total = series.total\n }\n })\n return illusts\n }\n\n u.formatIllusts = function (illusts) {\n illusts.forEach(illust => {\n illust.title = illust.title.trim()\n if (!illust.userName.startsWith(\"@\")) illust.userName = `@${illust.userName}`\n illust.tags = Array.from(new Set(illust.tags))\n illust.tags = illust.tags.map(item => `#${item}`)\n illust.tags = illust.tags.join(\",\")\n\n illust.coverUrl = urlCoverUrl(illust.coverUrl)\n illust.createDate = dateFormat(illust.createDate)\n illust.updateDate = dateFormat(illust.updateDate)\n if (util.MORE_INFORMATION) {\n illust.description = `\\n书名:${illust.title}\\n作者:${illust.userName}\\n标签:${illust.tags}\\n页面:${illust.pageCount}\\n上传:${illust.createDate}\\n更新:${illust.updateDate}\\n简介:${illust.description}`\n } else {\n illust.description = `\\n${illust.title},共${illust.pageCount}页\\n${illust.description}\\n上传时间:${illust.createDate}\\n更新时间:${illust.updateDate}`\n }\n })\n return illusts\n }\n\n u.getIllustRes = function (result) {\n let illustId = 0, res = {}\n let isJson = isJsonString(result)\n let isHtml = result.startsWith(\"\")\n if (!isJson && isHtml) {\n let pattern1 = \"(https?://)?(www\\\\.)?pixiv\\\\.net/(artworks|ajax/illust)/(\\\\d+)\"\n let isIllust = baseUrl.match(new RegExp(pattern1))\n let pattern2 = \"(https?://)?(www\\\\.)?pixiv\\\\.net/(user/\\\\d+|ajax)/series/(\\\\d+)\"\n let isSeries = baseUrl.match(new RegExp(pattern2))\n\n if (isIllust) {\n illustId = isIllust[4]\n } else if (isSeries) {\n seriesId = isSeries[4]\n java.log(`匹配系列ID:${seriesId}`)\n illustId = getAjaxJson(urlIP(urlSeriesDetailed(seriesId))).body.page.series.reverse()[0].workId\n }\n }\n if (isJson) {\n res = JSON.parse(result)\n }\n\n if (illustId) {\n java.log(`匹配插画ID:${illustId}`)\n res = getAjaxJson(urlIP(urlIllustDetailed(illustId)))\n }\n if (res.error) {\n java.log(`无法从 Pixiv 获取当前漫画`)\n java.log(JSON.stringify(res))\n return []\n }\n return res.body\n }\n\n util = u\n java.put(\"util\", objStringify(u))\n}\n\n// 获取请求的user id方便其他ajax请求构造\nfunction getPixivUid() {\n // cache.delete(\"pixivUid\")\n let pixivUid = getFromCache(\"pixivUid\")\n let pixivCookie = getFromCache(\"pixivCookie\")\n if (!pixivUid && pixivCookie) {\n pixivUid = pixivCookie.match(new RegExp(\"\\\\d+\"))[0]\n java.log(`pixivUid: ${pixivUid}`)\n putInCache(\"pixivUid\", pixivUid)\n }\n return pixivUid\n}\n\npublicFunc()\nif (result.code() === 200) {\n getPixivUid(); util.getCookie(); util.getCsrfToken()\n}\njava.getStrResponse(null, null)", "loginUi": "@js:\nlet source = [\n {\"🅿️ 登录账号\": \"login()\" },\n {\"⚙️ 账号设置\": \"startPixivSettings()\" },\n {\"🔙 退出账号\": \"logout()\" },\n {\"🆙 更新书源\": \"updateSource()\" },\n {\"🔰 使用指南\": \"startGithubReadme()\" },\n {\"🚫 ✈️ 直连模式\": \"editSettings('IPDirect')\" },\n]\n\nlet settings = [\n {\"书源设置\": \"text\" },\n {\"💾 备份恢复\": \"backupRestore()\" },\n {\"⚙️ 当前设置\": \"showSettings()\" },\n {\"🔧 默认设置\": \"setDefaultSettingsLoginUrl()\" },\n\n {\"🀄 🚫 繁简通搜\": \"editSettings('CONVERT_CHINESE')\" },\n {\"🔗 🚫 原始链接\": \"editSettings('SHOW_ORIGINAL_LINK')\" },\n {\"🚫 🐞 调试模式\": \"editSettings('DEBUG')\" },\n\n {\"⏳ 图片解析\": \"editSettings('PIC_SOURCE')\" },\n {\"🔗 图片链接\": \"editSettings('PIC_LINK')\" },\n {\"↔️ 图片大小\": \"editSettings('PIC_SIZE')\" },\n]\n\nlet li = []\nli = source.concat(settings)\n\n// 处理按钮\nli.forEach(item => {\n item.name = Object.keys(item)[0]\n let list = item.name.split(\" \")\n if (list.length === 1 ) {\n item.type = \"text\"\n } else if (list.length === 2) {\n item.type = \"button\"\n item.action = Object.values(item)[0]\n } else {\n item.name = list[list.length - 1]\n item.type = \"toggle\"\n item.default = `${list[0]} `\n list.length = list.length - 1\n item.chars = list.map(char => `${char} `)\n item.action = Object.values(item)[0]\n }\n delete item[Object.keys(item)[0]]\n // 添加格式\n if (item.type === \"button\" || item.type === \"toggle\") {\n item.style = {}\n item.style.layout_flexGrow = 1\n item.style.layout_flexBasisPercent = -1\n }\n})\n\nJSON.stringify(li)", "loginUrl": "function login() {\n sleepToast(\"🔄 正在检测登陆状态,请稍候\")\n if (isLogin()) {\n sleepToast(\"️🅿️ 登录账号\\n✅ 已经登录过账号了\\n\\n可以点击【🔙 退出账号】来切换账号\")\n return false\n }\n\n let resp = java.startBrowserAwait(`https://accounts.pixiv.net/login,\n {\"headers\": {\"User-Agent\": ${getWebViewUA()}}}`, '登录账号', false)\n if (resp.code() === 200) {\n getCsrfToken(); getCookie()\n return true\n } else {\n java.log(resp.code()); sleepToast(\"🅿️ 登录账号\\n\\n⚠️ 登录失败\")\n return false\n }\n}\n\nfunction logout() {\n removeCookie()\n java.startBrowser(\"https://www.pixiv.net/logout.php\", \"退出账号\")\n removeCookie(); removeLikeDataCache(); removeSettingsCache()\n sleepToast(`✅ 已退出当前账号\\n\\n退出后请点击右上角的 ✔️ 退出\\n\\n登录请点击【登录账号】进行登录`)\n}\n\nfunction removeCookie() {\n cookie.removeCookie('https://www.pixiv.net')\n cookie.removeCookie('https://accounts.pixiv.net')\n cookie.removeCookie('https://accounts.google.com')\n cookie.removeCookie('https://api.weibo.com')\n cache.delete(\"pixivCookie\")\n cache.delete(\"pixivUid\")\n cache.delete(\"pixivCsrfToken\") // 与登录设备有关\n cache.delete(\"headers\")\n}\n\nfunction getCookie() {\n let pixivCookie = String(java.getCookie(\"https://www.pixiv.net/\", null))\n if (isLogin()) putInCache(\"pixivCookie\", pixivCookie, 60*60)\n}\n\n// 获取 Csrf Token,以便进行收藏等请求\n// 获取方法来自脚本 Pixiv Previewer\n// https://github.com/Ocrosoft/PixivPreviewer\n// https://greasyfork.org/zh-CN/scripts/30766-pixiv-previewer/code\nfunction getCsrfToken() {\n let pixivCsrfToken = getFromCache(\"pixivCsrfToken\")\n if (!pixivCsrfToken) {\n let html = java.ajax(\"https://www.pixiv.net/\")\n try {\n pixivCsrfToken = html.match(/token\\\\\":\\\\\"([a-z0-9]{32})/)[1]\n putInCache(\"pixivCsrfToken\", pixivCsrfToken) // 与登录设备有关,无法存储 nul\n } catch (e) {\n pixivCsrfToken = null\n cache.delete(\"pixivCsrfToken\") // 与登录设备有关,无法存储 nul\n // sleepToast(\"⚠️ 未登录账号(pixivCsrfToken)\")\n }\n java.log(`pixivCsrfToken:\\n${pixivCsrfToken}`)\n }\n return pixivCsrfToken\n}\n\nfunction getIllust() {}\n\nfunction getPostBody(url, body, headers) {\n if (headers === undefined) headers = getFromCacheObject(\"headers\")\n if (isJsonString(body)) {\n headers[\"content-type\"] = \"application/json; charset=utf-8\"\n } else if (typeof body === \"string\") {\n headers[\"content-type\"] = \"application/x-www-form-urlencoded; charset=utf-8\"\n }\n try {\n java.log(`getPostBody(${url}, ${body}, ${headers})`)\n return JSON.parse(java.post(url, body, headers).body())\n } catch (e) {\n e = String(e)\n // sleepToast(e)\n // sleepToast(JSON.stringify(headers))\n if (e.includes(\"400\")) sleepToast(`📤 getPostBody\\n\\n⚠️ 缺少 headers`, 1)\n else if (e.includes(\"403\")) sleepToast(`📤 getPostBody\\n\\n⚠️ 缺少 cookie 或 cookie 过期`, 1)\n else if (e.includes(\"404\")) sleepToast(`📤 getPostBody\\n\\n⚠️ 404 缺少 pixivCsrfToken `, 1)\n else if (e.includes(\"422\")) sleepToast(`📤 getPostBody\\n\\n⚠️ 请求信息有误`, 1)\n return {error: true, errMsg:e}\n }\n}\n\nfunction userFollow(restrict) {\n if (restrict === undefined) restrict = 0\n let novel = getNovel()\n let resp = getPostBody(\n \"https://www.pixiv.net/bookmark_add.php\",\n `mode=add&type=user&user_id=${novel.userId}&tag=\"\"&restrict=${restrict}&format=json`\n )\n if (resp.error === true) {\n sleepToast(`⭐️ 关注作者\\n\\n⚠️ 关注【${novel.userName}】失败`, 1)\n shareFactory(\"author\")\n } else {\n sleepToast(`⭐️ 关注作者\\n\\n✅ 已关注【${novel.userName}】`)\n putInCache(`follow${novel.userId}`, true)\n }\n}\n\nfunction userUnFollow() {\n let novel = getNovel()\n let resp = getPostBody(\n \"https://www.pixiv.net/rpc_group_setting.php\",\n `mode=del&type=bookuser&id=${novel.userId}`\n )\n if (resp.error === true) {\n sleepToast(`⭐️ 关注作者\\n\\n⚠️ 取消关注【${novel.userName}】失败`, 1)\n shareFactory(\"author\")\n } else {\n sleepToast(`⭐️ 关注作者\\n\\n✅ 已取消关注【${novel.userName}】`)\n cache.delete(`follow${novel.userId}`)\n }\n}\n\nfunction userFollowFactory(code) {\n if (code === undefined) code = 1\n let novel = getNovel()\n let lastStatus = getFromCacheObject(`follow${novel.userId}`)\n if (lastStatus === true) code = 0\n\n if (code === 0) userUnFollow()\n else if (code === 1) userFollow()\n}\n\nfunction startPixivSettings() {\n startBrowser(\"https://www.pixiv.net/settings/viewing\", \"账号设置\")\n}\nfunction startGithubReadme() {\n startBrowser(\"https://pixivsource.pages.dev/Pixiv\", \"使用指南\")\n}\n\nlet settingsName = {\n \"SHOW_ORIGINAL_LINK\": \"🔗 原始链接\",\n \"CONVERT_CHINESE\": \"🀄️ 繁简通搜\",\n \"QUALITY_REGULAR\": \"🖼️ 常规质量\",\n \"IPDirect\": \"✈️ 直连模式\",\n \"DEBUG\": \"🐞 调试模式\"\n}\n\nfunction statusMsg(status) {\n if (status === true) return \"✅ 已开启\"\n else if (status === false) return \"🚫 已关闭\"\n else return \"🈚️ 未设置\"\n}\n\n// 检测快速模式修改的4个设置\nfunction getSettingStatus(mode) {\n if (mode === undefined) mode = \"\"\n let keys = [], msgList = []\n let settings = getFromCacheObject(\"pixivIllustSettings\")\n // if (mode === \"FAST\") {\n // keys = Object.keys(settingsName).slice(0, 5)\n // }\n if (mode === \"IPDirect\") {\n keys = Object.keys(settingsName).slice(0, 1)\n } else {\n keys = Object.keys(settingsName)\n }\n for (let i in keys) {\n msgList.push(`${statusMsg(settings[keys[i]])} ${settingsName[keys[i]]}`)\n }\n return msgList.join(\"\\n\").trim()\n}\n\nfunction showSettings() {\n sleepToast(`\\n⚙️ 当前设置\\n\\n${getSettingStatus()}`)\n}\n\nfunction setDefaultSettingsLoginUrl() {\n setDefaultSettings()\n sleepToast(`\\n✅ 已恢复 🔧 默认设置\\n\\n${getSettingStatus()}`)\n}\n\nfunction editSettings(settingName) {\n let msg, status\n let settings = getFromCacheObject(\"pixivIllustSettings\")\n if (!settings) settings = setDefaultSettings()\n if (!!settings[settingName]) {\n status = settings[settingName] = !settings[settingName]\n } else {\n status = settings[settingName] = true\n }\n putInCacheObject(\"pixivIllustSettings\", settings)\n\n if (settingName === \"IPDirect\") {\n if (settings.IPDirect && !isLogin()) {\n msg = \"✈️ 直连模式\\n\\n✈️ 直连模式 需登录账号\\n当前未登录账号,现已关闭直连模式\"\n settings.IPDirect = false\n checkSettings()\n putInCacheObject(\"pixivIllustSettings\", settings)\n } else {\n checkSettings()\n msg = `\\n\\n${statusMsg(status)} ${settingsName[settingName]}\\n\\n${getSettingStatus(settingName)}`\n }\n\n } else if (settingName === \"QUALITY_REGULAR\") {\n msg = `\\n\\n${statusMsg(status)} ${settingsName[settingName]}\\n\\n`\n if (settings.QUALITY_REGULAR)\n msg += \"图片质量设置为 regular \"\n else msg += \"图片质量设置为 original\"\n\n } else {\n msg = `\\n\\n${statusMsg(status)} ${settingsName[settingName]}`\n }\n sleepToast(msg)\n}", "respondTime": 180000, "ruleBookInfo": { "init": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (isFunctionString(v)) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction illustHandler(illust){\n illust = util.formatIllusts(util.handIllusts([illust]))[0]\n book.bookUrl = illust.detailedUrl = urlIllustUrl(illust.id)\n book.tocUrl = illust.catalogUrl = urlIP(urlIllustDetailed(illust.id))\n return illust\n}\n\n(() => {\n return illustHandler(util.getIllustRes(result))\n})()", "name": "title", "author": "userName", "kind": "tags", "wordCount": "textCount", "lastChapter": "latestChapter", "intro": "description", "coverUrl": "coverUrl", "tocUrl": "catalogUrl", "canReName": "true" }, "ruleContent": { "content": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (isFunctionString(v)) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction getContent(res) {\n let content = [\"\"]\n // li = \"mini thumb small regular original\".split(\" \")\n let quality = util.settings.QUALITY_REGULAR ? \"regular\" : \"original\"\n let illustLink = getAjaxJson(urlIP(urlIllustDetailed(res.id))).body.urls[quality]\n for (let order = 0; order < res.pageCount; order++) {\n content.push(``)\n illustLink = illustLink.replace(`_p${order}`, `_p${order + 1}`)\n }\n content = content.join(\"\\n\")\n return content\n}\n\n(function () {\n return getContent(util.getIllustRes(result))\n})()", "imageStyle": "FULL", "callBackJs": "// function getNovel() {\n// let novel = {}\n// novel.author = novel.userName = book.author.replace(\"@\", \"\")\n// if (book.bookUrl.includes(\"series\")) {\n// novel.seriesId = book.bookUrl.match(/\\d+/)[0]\n// novel.seriesTitle = book.name\n//\n// let novelIds = getFromCacheObject(`novelIds${novel.seriesId}`)\n// novel.id = novelIds[book.durChapterIndex]\n// novel.title = book.durChapterTitle\n// } else {\n// novel.seriesId = 0\n// novel.seriesTitle = \"\"\n// novel.id = book.bookUrl.match(/\\d+/)[0]\n// novel.title = book.name\n// }\n// return novel\n// }\n\nfunction shareBook() {\n let text = `我正在看:【${book.author.replace(\"@\", \"\")}】创作的《${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分享自【开源阅读】Pixiv书源。使用添加网址,快速添加本文`\n java.copyText(text)\n return true\n}\n\nfunction copyBookUrl() {\n java.copyText(book.bookUrl)\n return true\n}\nfunction copyTocUrl() {\n java.copyText(book.tocUrl)\n return true\n}\n\n// 保存阅读,更新登录界面的章节名称\nfunction saveRead() {\n source.putLoginInfo(JSON.stringify({\"章节名称\": book.durChapterTitle}))\n}\n\nfunction startShelfRefresh() {\n source.putConcurrent(\"18/30000\")\n}\n\nfunction endShelfRefresh() {\n source.putConcurrent(\"25/5000\")\n}\n\nfunction callBackFactory(event) {\n switch (event) {\n // case \"clickBookName\":\n // return clickBookName()\n // case \"longClickBookName\":\n // return longClickBookName()\n // case \"clickAuthor\":\n // return clickAuthor()\n // case \"longClickAuthor\":\n // return longClickAuthor()\n // case \"clickCustomButton\":\n // return customButton()\n // case \"longClickCustomButton\":\n // return longcustomButton()\n\n case \"clickShareBook\":\n return shareBook()\n // case \"clickClearCache\":\n // return clearCache()\n case \"clickCopyBookUrl\":\n return copyBookUrl()\n case \"clickCopyTocUrl\":\n return copyTocUrl()\n\n // 下面的事件无法被回调结果消费\n // case \"addBookShelf\":\n // return addBookShelf()\n // case \"delBookShelf\":\n // return delBookShelf()\n case \"saveRead\":\n return saveRead()\n // case \"startRead\":\n // return startRead()\n // case \"endRead\":\n // return endRead()\n case \"startShelfRefresh\":\n return startShelfRefresh()\n case \"endShelfRefresh\":\n return endShelfRefresh()\n }\n}\n\n(() => {\n return callBackFactory(event)\n})()" }, "ruleExplore": { "author": "userName", "bookList": "@js:\nvar util = objParse(String(java.get(\"util\")))\nvar seriesSet = new Set(); // 存储seriesID 有BUG无法处理翻页\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (isFunctionString(v)) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction handlerFactory() {\n if (baseUrl.includes(\"https://cdn.jsdelivr.net\")) {\n return () => {updateSource(); return []}\n }\n if (baseUrl.includes(\"github\")) {\n return () => {startBrowser(baseUrl, \"\"); return []}\n }\n if (!isLogin()) {\n return handlerNoLogin()\n }\n if (baseUrl.includes(\"/follow_latest\")) {\n return handlerFollowLatest()\n }\n if (baseUrl.includes(\"/watch_list\")) {\n return handlerWatchList()\n }\n if (baseUrl.includes(\"/top\")) {\n return handlerRecommend()\n }\n if (baseUrl.includes(\"/discovery\")) {\n return handlerDiscovery()\n }\n if (baseUrl.includes(\"/bookmark\")) {\n return handlerBookMarks()\n }\n if (baseUrl.includes(\"/new\")) {\n return handlerDiscovery()\n }\n if (baseUrl.includes(\"/commission/\")) {\n return handlerFollowLatest()\n }\n if (baseUrl.includes(\"/user_event/portal\")) {\n return handlerFollowLatest()\n }\n // 正则匹配网址内容\n if (baseUrl.includes(\"/ranking\") && (baseUrl.endsWith(\"json\"))) {\n return handlerRanking()\n }\n if (baseUrl.includes(\"/ranking\")) {\n return handlerRegexIllusts()\n }\n else {\n return () => {startBrowser(baseUrl, \"\"); return []}\n }\n}\n\nfunction handlerNoLogin() {\n return () => {\n sleepToast(\"⚠️ 当前未登录账号\\n\\n请登录 Pixiv 账号\", 1.5)\n util.removeCookie(); util.login()\n sleepToast(\"登录成功后,请重新进入发现\", 2)\n return []\n }\n}\n\n//关注作者,漫画委托,漫画企划\nfunction handlerFollowLatest() {\n return () => {\n let res = JSON.parse(result)\n return util.formatIllusts(util.handIllusts(res.body.thumbnails.illust))\n }\n}\n\n// 追更列表\nfunction handlerWatchList() {\n return () => {\n let res = JSON.parse(result)\n // li = res.body.page.watchedSeriesIds\n return util.formatIllusts(util.handIllusts(res.body.thumbnails.illust))\n }\n}\n\n// 推荐漫画\nfunction handlerRecommend() {\n return () => {\n let res = JSON.parse(result)\n const recommend = res.body.page.recommend\n const illusts = res.body.thumbnails.illust\n let nidSet = new Set(recommend.ids)\n // java.log(nidSet.size)\n let list = illusts.filter(illust => nidSet.has(String(illust.id)))\n // java.log(`过滤结果:${JSON.stringify(list)}`)\n return util.formatIllusts(util.handIllusts(list))\n }\n}\n\n//发现漫画\nfunction handlerDiscovery() {\n return () => {\n let res = JSON.parse(result)\n return util.formatIllusts(util.handIllusts(res.body.illusts))\n }\n}\n\n// 收藏漫画\nfunction handlerBookMarks() {\n return () => {\n let res = JSON.parse(result).body.works\n if (res === undefined || res.length === 0) {\n //流程无法本环节中止 只能交给下一流程处理\n return []\n }\n return util.formatIllusts(util.handIllusts(res))\n }\n}\n\n// 排行榜,顺序相同\nfunction handlerRanking() {\n return () => {\n let res = JSON.parse(result)\n res.contents.forEach(item =>{\n item.id = item.illust_id\n // item.title = item.title\n item.userName = item.user_name\n // item.tags = item.tags\n item.latestChapter = item.title\n item.description = null\n item.coverUrl = item.url\n item.detailedUrl = urlIP(urlIllustDetailed(item.id))\n item.createDate = item.updateDate = item.illust_upload_timestamp * 1000\n\n if (item.illust_series !== false) {\n let series = item.illust_series\n item.seriesId = series.illust_series_id\n item.order = series.illust_series_content_order\n item.total = series.illust_series_content_count\n if (item.order === item.total) item.latestChapter = item.title\n item.title = series.illust_series_title\n item.description = series.illust_series_caption\n item.pageCount = series.illust_page_count\n item.createDate = item.updateDate = series.illust_series_create_datetime\n }\n })\n return util.formatIllusts(util.handIllusts(res.contents))\n }\n}\n\n//首页,顺序随机\nfunction handlerRegexIllusts() {\n return () => {\n let illustIds = [] // 正则获取网址中的 illustId\n let matched = result.match(RegExp(/\\/artworks\\/\\d{5,}/gm))\n for (let i in matched) {\n let illustId = matched[i].match(RegExp(/\\d{5,}/))[0]\n if (illustIds.indexOf(illustId) === -1) {\n illustIds.push(illustId)\n }\n }\n let userIllusts = getWebviewJson(\n urlIP(urlIllustsDetailed(getFromCache(\"pixivUid\"), illustIds)), html => {\n return (html.match(new RegExp(\">\\\\{.*?}<\"))[0].replace(\">\", \"\").replace(\"<\", \"\"))\n }).body\n return util.formatIllusts(util.handIllusts(Object.values(userIllusts)))\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 (isFunctionString(v)) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction getArtwork() {\n let resp = JSON.parse(result)\n if (resp.error === true) {\n return []\n }\n let name = String(java.get(\"key\"))\n let page = Number(java.get(\"page\"))\n java.log(urlIP(urlSearchArtwork(name, page)))\n putInCacheObject(urlIP(urlSearchArtwork(name, page)), resp, cacheSaveSeconds) // 加入缓存\n return resp.body.illustManga.data\n}\n\nfunction search(name, page) {\n let resp = getAjaxJson(urlIP(urlSearchArtwork(name, page)))\n java.log(urlIP(urlSearchArtwork(name, page)))\n if (resp.error === true || resp.total === 0) {\n return {\"data\": [], \"total\":0, \"lastPage\": 0}\n }\n return resp.body.illustManga\n}\n\nfunction getConvertArtwork() {\n let illusts = []\n let name = String(java.get(\"key\"))\n let name1 = String(java.s2t(name))\n let name2 = String(java.t2s(name))\n if (name1 !== name) illusts = illusts.concat(search(name1, 1).data)\n if (name2 !== name) illusts = illusts.concat(search(name2, 1).data)\n return illusts\n}\n\n(() => {\n let artworks = []\n artworks = artworks.concat(getArtwork())\n if (util.settings.CONVERT_CHINESE) artworks = artworks.concat(getConvertArtwork())\n // java.log(JSON.stringify(artworks))\n // 返回空列表中止流程\n if (artworks.length === 0) {\n return []\n }\n return util.formatIllusts(util.handIllusts(artworks))\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 (isFunctionString(v)) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction urlIllust(novelId){\n if (util.settings.SHOW_ORIGINAL_LINK) {\n return urlIllustUrl(novelId)\n } else {\n return urlIllustDetailed(novelId)\n }\n}\n\nfunction oneShotHandler(res) {\n return [{\n title: res.title.replace(RegExp(/^\\s+|\\s+$/g), \"\"),\n chapterUrl: urlIP(urlIllust(res.id)),\n chapterInfo: `${timeTextFormat(res.createDate)}`\n }]\n}\n\nfunction seriesHandler(res) {\n let limit = 12, total = 0, illusts = []\n let seriesId = res.seriesNavData.seriesId\n if (res.seriesId === undefined) {\n total = getAjaxJson(urlIP(urlSeriesDetailed(res.seriesNavData.seriesId))).body.page.total\n } else {\n total = res.total\n }\n util.debugFunc(() => {\n java.log(`本系列 ${seriesId} 一共有${total}章`);\n })\n\n //要爬取的总次数\n let max = (total / limit) + 1\n for (let page = 1; page < max; page++) {\n // java.log(urlIP(urlSeriesDetailed(seriesId, page)))\n res = getAjaxJson(urlIP(urlSeriesDetailed(seriesId, page))).body\n let illusts_id = res.page.series.map(item => item.workId)\n illusts = illusts.concat(res.thumbnails.illust.filter(illust => illusts_id.includes(illust.id)))\n }\n illusts.reverse().forEach(illust => {\n illust.title = illust.title.replace(RegExp(/^\\s+|\\s+$/g), \"\")\n illust.chapterUrl = urlIP(urlIllust(illust.id))\n illust.chapterInfo = timeTextFormat(illust.createDate)\n })\n // java.log(JSON.stringify(illusts))\n return illusts\n}\n\n(() => {\n let res = util.getIllustRes(result)\n if (res.seriesNavData !== null) {\n return seriesHandler(res)\n } else {\n return oneShotHandler(res)\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}\n\njava.put(\"key\", key)\nurlIP(urlSearchArtwork(key, page))", "variableComment": "⚙️ 书源设置:\n设置1️⃣:打开小说 - 菜单 - 登录 - 点击下方按钮\n▶️ 搜索任意小说,同步设置数据\n\n设置2️⃣:编辑书源 - 基本 - 变量说明 - 修改并保存\n⚙️ 自定义设置:将 true 改为 false,或相反\n⚠️ 设置源变量【无法】更改书源自定义设置\n⚠️ 注意不要添加或删除尾随逗号\",\"\n⚠️ 更新发现页需要长按\"Pixiv\",手动刷新\n以下内容为书源设置:\n{\n\"SHOW_GENERAL_NEW\": false,\n}\n\n// SHOW_GENERAL_NEW\n// 发现:最新、企划、约稿显示一般漫画\n\n", "weight": 0 } ]