-
-
Notifications
You must be signed in to change notification settings - Fork 30
Expand file tree
/
Copy pathpixiv.json
More file actions
224 lines (224 loc) · 376 KB
/
pixiv.json
File metadata and controls
224 lines (224 loc) · 376 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
[
{
"bookSourceComment": "🅿️ Pixiv 小说(更新📆:2026/04/04)\n\n书源版本:265\n使用说明:📌阅读 Plus 3.26.0129 版本可用\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()\n\nlet li = [\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(\\\"pixiv:uid\\\")}}/novels/bookmarks?tag=&offset={{(page-1)*24}}&limit=24&rest=show&lang=zh\"},\n {\"㊙️ 收藏\": \"https://www.pixiv.net/ajax/user/{{cache.get(\\\"pixiv:uid\\\")}}/novels/bookmarks?tag=&offset={{(page-1)*24}}&limit=24&rest=hide&lang=zh\"},\n {\"🏷️ 书签\": \"https://www.pixiv.net/novel/marker_all.php\"},\n {\"🏠 首页\": \"https://www.pixiv.net\"},\n]\n\nlet normal = [\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 r18New = [\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/novel/editors_picks\"},\n]\n\nlet r18Rank = [\n {\"👑 排行榜单 👑\": \"\"},\n {\"今日\": \"https://www.pixiv.net/novel/ranking.php?mode=daily_r18&p={{page}}\"},\n {\"本周\": \"https://www.pixiv.net/novel/ranking.php?mode=weekly_r18&p={{page}}\"},\n {\"R18G\": \"https://www.pixiv.net/novel/ranking.php?mode=r18g&p={{page}}\"},\n {\"男性\": \"https://www.pixiv.net/novel/ranking.php?mode=male_r18&p={{page}}\"},\n {\"女性\": \"https://www.pixiv.net/novel/ranking.php?mode=female_r18&p={{page}}\"}\n]\n\nlet generalRank = [\n {\"🏆 排行榜单 🏆\": \"\"},\n {\"今日\": \"https://www.pixiv.net/novel/ranking.php?mode=daily&p={{page}}\"},\n {\"本周\": \"https://www.pixiv.net/novel/ranking.php?mode=weekly&p={{page}}\"},\n {\"本月\": \"https://www.pixiv.net/novel/ranking.php?mode=monthly&p={{page}}\"},\n {\"男性\": \"https://www.pixiv.net/novel/ranking.php?mode=male&p={{page}}\"},\n {\"女性\": \"https://www.pixiv.net/novel/ranking.php?mode=female&p={{page}}\"},\n {\"新人\": \"https://www.pixiv.net/novel/ranking.php?mode=rookie&p={{page}}\"},\n {\"原创\": \"https://www.pixiv.net/novel/ranking.php?mode=weekly_original&p={{page}}\"},\n {\"AI生成\": \"https://www.pixiv.net/novel/ranking.php?mode=weekly_ai&p={{page}}\"}\n]\n\nlet r18Genre = [\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 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\nlet likeTagLinks = [{\"📌 喜欢标签 📌\":\"\"}]\nlet othersBookmarks = [{\"❤️ 他人收藏 ❤️\": \"\"}]\n\nif (settings.SHOW_GENERAL) li = li.concat(normal)\nif (settings.SHOW_NEW_ADULT) li = li.concat(r18New)\nif (settings.SHOW_NEW_GENERAL) li = li.concat(generalNew)\nif (settings.SHOW_RANK_ADULT)li = li.concat(r18Rank)\nif (settings.SHOW_RANK_GENERAL) li = li.concat(generalRank)\nif (settings.SHOW_GENRE_ADULT) li = li.concat(r18Genre)\nif (settings.SHOW_GENRE_GENERAL) li = li.concat(generalGenre)\nsleepToast('使用指南🔖\\n\\n发现 - 更新 - 点击\"🔰 使用指南\" - 查看')\n\n// 收藏标签\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 li = li.concat(likeTagLinks)\n}\n\n// 他人收藏\nlet 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 li = li.concat(othersBookmarks)\n}\n\n// 书源相关\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 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 // [{'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(\"<!DOCTYPE html>\")\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}\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 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\": \"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 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 urlSearchNovel(novelName, page) {\n return `https://www.pixiv.net/ajax/search/novels/${encodeURI(novelName)}?word=${encodeURI(novelName)}&order=date_d&mode=all&p=${page}&s_mode=s_tag&lang=zh`\n}\nfunction urlSearchSeries(seriesName, page) {\n return`https://www.pixiv.net/ajax/search/novels/${encodeURI(seriesName)}?word=${encodeURI(seriesName)}&order=date_d&mode=all&p=${page}&s_mode=s_tag&gs=1&lang=zh`\n}\nfunction urlSearchUser(userName, page, full) {\n let pageUrl = \"\", fullUrl = \"\"\n if (full) fullUrl = \"_full\"\n if (page && page >= 2) pageUrl = `&p=${page}`\n return `https://www.pixiv.net/search/users?nick=${userName}&s_mode=s_usr${fullUrl}&i=1${pageUrl}`;\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 if (!this._settings) {\n this._settings = this.getFromCacheObject(\"pixivSettings\") || this.setDefaultSettings()\n }\n if (!this._settings.IPDirect) return url\n\n let headers = {\"Referer\": \"https://www.pixiv.net/\"}\n if (url.trim()) {\n if (url.includes(\"i.pximg.net\")) {\n url = url.replace(\"https://i.pximg.net\", \"https://210.140.139.133\")\n headers.host = \"i.pximg.net\"\n } else {\n url = url.replace(\"https://s.pximg.net\", \"https://210.140.139.133\")\n headers.host = \"s.pximg.net\"\n }\n }\n return `${url}, ${JSON.stringify({headers: headers})}`\n}\nfunction 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.HIDE_LIKE_NOVELS = false // 搜索:搜索结果 隐藏收藏小说\n settings.HIDE_WATCHED_SERIES = false// 搜索:搜索结果 隐藏追整系列\n\n settings.MORE_INFORMATION = false // 详情:书籍简介显示更多信息\n settings.SHOW_UPDATE_TIME = true // 目录:显示更新时间,但会增加少许请求\n settings.SHOW_ORIGINAL_LINK = true // 目录:显示原始链接,但会增加大量请求\n\n settings.REPLACE_TITLE_MARKS = true // 正文:注音内容为汉字时,替换为书名号\n settings.SHOW_CAPTIONS = true // 正文:章首显示描述\n settings.SHOW_COMMENTS = true // 正文:章尾显示评论,但会增加大量请求\n\n settings.IPDirect = false // 全局:直连模式\n settings.FAST = false // 全局:快速模式\n settings.DEBUG = false // 全局:调试模式\n\n settings.SHOW_GENERAL = true // 发现:显示 常规小说\n settings.SHOW_NEW_ADULT = true // 发现:显示 最新企划约稿 R18\n settings.SHOW_NEW_GENERAL = false // 发现:显示 最新企划约稿 常规\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 settings.SHOW_FURRY = false // 发现:显示 兽人小说推荐作者\n\n settings.SHOW_SETTINGS = true // 设置:显示 书源设置\n settings.SHOW_DISCOVER = true // 设置:显示 发现设置\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.FAST || settings.IPDirect) {\n settings.SEARCH_AUTHOR = false // 搜索:默认不搜索作者名称\n settings.SHOW_ORIGINAL_LINK = false // 目录:显示章节源链接\n }\n if (!settings.FAST && !settings.IPDirect) {\n // settings.SEARCH_AUTHOR = true // 搜索:默认不搜索作者名称\n settings.SHOW_ORIGINAL_LINK = true // 目录:显示章节源链接\n }\n\n if (settings.FAST) {\n settings.SEARCH_AUTHOR = false // 搜索:默认不搜索作者名称\n settings.CONVERT_CHINESE = false // 搜索:繁简通搜\n settings.SHOW_UPDATE_TIME = false // 目录:显示章节更新时间\n settings.SHOW_COMMENTS = false // 正文:显示评论\n }\n this.putInCacheObject(\"pixivSettings\", settings)\n return settings\n}\n\nfunction updateSource() {\n const {java, source} = this\n java.longToast(\"🆙 更新书源\\n\\nJsdelivr CDN 更新有延迟\\nGithub 更新需代理\")\n let onlineSource, comment, sourceName, sourceNameCapitalize, index = 0\n if (source.bookSourceUrl.includes(\"pixiv\")) sourceName = \"pixiv\"\n else if (source.bookSourceUrl.includes(\"furrynovel\")) sourceName = \"linpx\"\n sourceNameCapitalize = sourceName[0].toUpperCase() + sourceName.substring(1)\n\n if (source.bookSourceName.includes(\"备用\")) index = 1\n else if (source.bookSourceName.includes(\"漫画\")) index = 2\n if (source.bookSourceUrl.includes(\"furrynovel.com\")) {\n sourceNameCapitalize = \"FurryNovel\"\n index = 1\n }\n\n try {\n let updateUrl = `https://cdn.jsdelivr.net/gh/DowneyRem/PixivSource@main/${sourceName}.json`\n onlineSource = JSON.parse(java.get(updateUrl,{'User-Agent': 'Mozilla/5.0 (Linux; Android 14)','X-Requested-With': 'XMLHttpRequest'}).body())[index]\n } catch (e) {\n try {\n let updateUrl = `https://raw.githubusercontent.com/DowneyRem/PixivSource/main/${sourceName}.json`\n onlineSource = JSON.parse(java.get(updateUrl,{'User-Agent': 'Mozilla/5.0 (Linux; Android 14)','X-Requested-With': 'XMLHttpRequest'}).body())[index]\n } catch (e) {\n onlineSource = {lastUpdateTime: new Date().getTime(), bookSourceComment: source.bookSourceComment}\n }\n }\n comment = onlineSource.bookSourceComment.split(\"\\n\")\n // onlineSource = source\n // comment = source.bookSourceComment.split(\"\\n\")\n\n let htm = `\n<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>更新 ${source.bookSourceName} 书源</title>\n <style> \n table { text-align: center; margin: 0 auto; } .ann { display: flex; justify-content: center; align-items: center; height: 5vh; } \n button { background-color: rgb(76, 175, 80); color: white; border: none; border-radius: 4px; height: 6vh; width: 30vw; overflow: hidden; } \n button span { cursor: pointer; display: inline-block; position: relative; transition: 0.4s; } \n button span:after { content: '>'; position: absolute; opacity: 0; top: 0; right: 30px; transition: 0.2s; } \n button:active span { padding-right: 20px; } \n button:active span:after { opacity: 1; right: -40px; }\n </style>\n</head>\n\n<body>\n <table border=\"1\" cellspacing=\"0\">\n <th colspan=\"2\"> ${source.bookSourceName} 书源 \n <a href=\"https://pixivsource.pages.dev/${sourceNameCapitalize}\">🔰 使用指南</a>\n || <a href=\"https://pixivsource.pages.dev/Sponsor\">❤️ 赞助开发</a>\n </th>\n <tr>\n <td>☁️ 远程版本:${onlineSource.bookSourceComment.split(\"\\n\")[2].replace(\"书源版本:\", \"\")}</td>\n <td>📆 更新:${timeFormat(onlineSource.lastUpdateTime)}</td>\n </tr>\n <tr>\n <td>📥 本地版本:${source.bookSourceComment.split(\"\\n\")[2].replace(\"书源版本:\", \"\")}</td>\n <td>📆 更新:${timeFormat(source.lastUpdateTime)}</td>\n </tr> \n <tr><td colspan=\"2\" style=\"text-align: left;\">${comment.slice(3, 10).join(\"<br>\")}</td></tr>\n <tr><td colspan=\"2\" style=\"text-align: left;\">${comment.slice(comment.length-20, comment.length).join(\"<br>\")}</td></tr>\n </table>\n \n <table border=\"0\" cellspacing=\"20\">\n <th colspan=\"2\"> 更新 ${source.bookSourceName} 书源 </th>\n <tr><td><div class=\"ann\">\n <a href=\"legado://import/importonline?src=https://cdn.jsdelivr.net/gh/DowneyRem/PixivSource@main/${sourceName}.json\">\n <button><span>更新书源<br>(Jsdelivr CDN)</span></button>\n </a></div></td>\n \n <td><div class=\"ann\">\n <a href=\"legado://import/importonline?src=https://cdn.jsdelivr.net/gh/DowneyRem/PixivSource@main/btsrk.json\">\n <button><span>更新订阅<br>(Jsdelivr CDN)</span></button>\n </a></div></td>\n </tr>\n \n <tr><td><div class=\"ann\">\n <a href=\"legado://import/importonline?src=https://raw.githubusercontent.com/DowneyRem/PixivSource/main/${sourceName}.json\">\n <button><span>书源链接<br>(GitHub)</span></button>\n </a></div></td>\n \n <td><div class=\"ann\">\n <a href=\"legado://import/importonline?src=https://raw.githubusercontent.com/DowneyRem/PixivSource/main/btsrk.json\">\n <button><span>订阅链接<br>(GitHub)</span></button>\n </a></div></td>\n </tr>\n \n <tr><td><div class=\"ann\">\n <a href=\"legado://import/importonline?src=https://codeberg.org/DowneyRem/PixivSource/raw/branch/main/${sourceName}.json\">\n <button><span>备用书源链接<br>(Codeberg)</span></button>\n </a></div></td>\n \n <td><div class=\"ann\">\n <a href=\"legado://import/importonline?src=https://codeberg.org/DowneyRem/PixivSource/raw/branch/main/btsrk.json\">\n <button><span>备用订阅链接<br>(Codeberg)</span></button>\n </a></div></td>\n </tr>\n </table>\n</body>\n</html>`\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": 1775232000251,
"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 版本 与 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// 检测 阅读 T 版本\nfunction isLegadoT() {\n return typeof java.ocr === \"function\"\n}\n\nfunction publicFunc() {\n let u = {}, settings = {}\n\n let isFirstInit = false;\n if (!globalThis.ALREADY_LOGGED_INFO) {\n globalThis.ALREADY_LOGGED_INFO = true\n isFirstInit = true\n }\n\n if (!globalThis.settings) {\n // cache.delete(\"pixivSettings\")\n settings = getFromCacheObject(\"pixivSettings\")\n if (!settings) settings = setDefaultSettings()\n globalThis.settings = checkSettings(settings)\n }\n // 环境信息不会改变,可以使用 globalThis.environment\n if (!globalThis.environment) {\n globalThis.environment = {}\n globalThis.environment.IS_SOURCEREAD = isSourceRead()\n globalThis.environment.IS_LEGADO = !isSourceRead()\n globalThis.environment.IS_LEGADO_OFFICIAL = isLegadoOfficial()\n globalThis.environment.IS_LEGADO_SIGMA = isLegadoSigma()\n globalThis.environment.IS_LEGADO_T = isLegadoT()\n }\n\n // 只有第一次初始化时才输出日志\n if (isFirstInit) {\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 (globalThis.environment.IS_SOURCEREAD) {\n java.log(\"📱 软件平台:🍎 源阅 SourceRead\")\n } else if (globalThis.environment.IS_LEGADO_SIGMA) {\n java.log(\"📱 软件平台:🤖 阅读 Beta【新包名】/ 阅读 Plus\")\n } else if (globalThis.environment.IS_LEGADO_T) {\n java.log(\"📱 软件平台:🤖 阅读 T\")\n java.log(\"\\n⚠️当前软件为:阅读 T】\\n目前不确定兼容性如何\")\n } else if (globalThis.environment.IS_LEGADO_OFFICIAL) {\n java.log(\"📱 软件平台:🤖 阅读 正式版\")\n sleepToast(\"\\n⚠️当前软件为:阅读【正式版】\\n【正式版】已年久失修,不推荐继续使用\\n\\n为了更好的使用体验,请用:\\n【阅读 Plus】或【阅读 Beta 新包名】\\n\\n即将为您打开【阅读 Plus】下载界面\")\n sleep(3);\n startBrowser(\"https://loyc.xyz/c/legado.html#download\", \"下载阅读 Plus\")\n } else {\n java.log(\"📱 软件平台:🤖 阅读 Beta【原包名】\")\n sleepToast(\"\\n⚠️当前软件为:阅读 Beta【原包名】\\n\\n为了更好的使用体验,请用:\\n【阅读 Plus】或【阅读 Beta 新包名】\\n\\n即将为您打开【阅读 Plus】下载界面\")\n sleep(3);\n startBrowser(\"https://loyc.xyz/c/legado.html#download\", \"下载阅读 Plus\")\n }\n\n if (globalThis.settings.IPDirect) {\n java.log(\"✈️ 直连模式:✅ 已开启\")\n } else {\n java.log(\"✈️ 直连模式:❌ 已关闭\")\n }\n }\n\n // 使用 globalThis.settings 无法获取最新的设置\n // 使用 globalThis.environment 可能无法写入 environment\n // cache.delete(\"pixivSettings\")\n settings = getFromCacheObject(\"pixivSettings\")\n if (!settings) settings = setDefaultSettings()\n u.settings = checkSettings(settings)\n putInCacheObject(\"pixivEnvironment\", globalThis.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(\"pixiv:uid\")\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 // 将多个长篇小说解析为一本书\n u.combineNovels = function(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 = [], msg\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\n util.debugFunc(() => {\n // java.log(JSON.stringify(novels0))\n java.log(JSON.stringify(novels0.length))\n // java.log(JSON.stringify(novels1))\n java.log(JSON.stringify(novels1.length))\n // java.log(JSON.stringify(novels2))\n java.log(JSON.stringify(novels2.length))\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 captionBlockWords = getFromCacheObject(\"captionBlockWords\")\n if (!captionBlockWords) captionBlockWords = []\n else {\n // 仅保留没有任何屏蔽词的小说\n // novels = novels.filter(novel => {\n // return !captionBlockWords.some(item => {\n // if (novel.description !== undefined) return novel.description.includes(item)\n // })\n // })\n novels = novels.filter(novel => !captionBlockWords.some(item => novel.description.includes(item)))\n let novels2 = novels.map(novel => novel.id)\n java.log(`🚫 屏蔽描述:${captionBlockWords.join(\"\\n\")}`)\n java.log(`🚫 屏蔽描述:过滤前${novels0.length};过滤后${novels2.length}`)\n }\n\n let tagsBlockWords = getFromCacheObject(\"tagsBlockWords\")\n if (!tagsBlockWords) tagsBlockWords = []\n else {\n // 仅保留没有任何屏蔽词的小说\n // novels = novels.filter(novel => {\n // return !tagsBlockWords.some(item => {\n // if (novel.tags !== undefined) return novel.tags.includes(item)\n // })\n // })\n novels = novels.filter(novel => !tagsBlockWords.some(item => novel.tagsList.includes(item)))\n let novels2 = novels.map(novel => novel.id)\n java.log(`🚫 屏蔽标签:${tagsBlockWords.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.id = novel.novelId // 获取真正的 novelId\n novel.seriesTitle = undefined\n } else {\n novel.seriesId = novel.id\n novel.id = novel.novelId = novel.latestEpisodeId // 获取真正的 novelId\n novel.seriesTitle = novel.title\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) {\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 if (novel.seriesId && !isDetail) {\n novel.title = novel.seriesTitle\n novel.tags.unshift(\"长篇\")\n novel.detailedUrl = urlIP(urlSeriesDetailed(novel.seriesId))\n }\n // 系列添加更多信息\n if (novel.seriesId && isDetail) {\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.join(\",\")\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 // 正文,详情,搜索:从网址获取id,返回单篇小说 res,系列返回首篇小说 res\n // pixiv 默认分享信息中有#号,不会被识别成链接,无法使用添加网址\n u.getNovelResFirst = 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.body\n }\n\n // 目录:从网址获取id,尽可能返回系列 res,单篇小说返回小说 res\n u.getNovelResSeries = function(result) {\n let seriesId = 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/novel/series/\\\\d+\"\n let isSeries = baseUrl.match(new RegExp(pattern))\n if (isSeries) {\n seriesId = 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 java.log(`匹配小说ID:${id}`)\n res = getAjaxJson(urlIP(urlNovelDetailed(id)))\n }\n }\n }\n if (isJson) {\n res = JSON.parse(result)\n }\n\n if (res.body && res.body.seriesNavData) {\n seriesId = res.body.seriesNavData.seriesId\n }\n if (seriesId) {\n java.log(`系列ID:${seriesId}`)\n res = getAjaxJson(urlIP(urlSeriesDetailed(seriesId)))\n }\n if (res.error === true) {\n java.log(`无法从 Pixiv 获取当前小说`)\n java.log(JSON.stringify(res))\n }\n return res.body\n }\n\n util = u\n 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(\"pixiv:uid\")\n let uid = getFromCache(\"pixiv:uid\")\n if (!uid && isLogin()) {\n let html = java.ajax(\"https://www.pixiv.net/\")\n uid = html.match(/user_id:'(\\d+)'/)[1]\n putInCache(\"pixiv:uid\", uid)\n }\n return uid\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(\"headers\", headers)\n return headers\n}\n\npublicFunc()\nif (!isLogin() && !util.settings.DEBUG) {\n sleepToast(\"🔍 搜索小说\\n\\n⚠️ 当前未登录账号\\n请登录 Pixiv 账号\", 1.5)\n util.removeCookie(); util.login()\n sleepToast(\"🔍 搜索小说\\n\\n登录成功后,请重新搜索/进入发现\", 2)\n}\nif (result.code() === 200) {\n getPixivUid(); getWebViewUA(); util.getCookie(); util.getCsrfToken(); getHeaders()\n if (!util.settings.FAST) checkMessageThread() // 检测过度访问\n}\n\n// util.debugFunc(() => {\n// java.log(`DEBUG = ${util.settings.DEBUG}\\n`)\n// java.log(JSON.stringify(util.settings, null, 4))\n// java.log(`${getWebViewUA()}\\n`)\n// java.log(`${getFromCache(\"pixivCsrfToken\")}\\n`)\n// java.log(`${getFromCache(\"pixivCookie\")}\\n`)\n// java.log(`${getFromCache(\"headers\")}\\n`)\n// })\n\njava.getStrResponse(null, null)",
"loginUi": "@js:\nlet pixivSettings = getFromCacheObject(\"pixivSettings\")\nif (!pixivSettings) pixivSettings = setDefaultSettings()\n\nlet source = [\n {\"🅿️ 登录账号\": \"login()\" },\n {\"⚙️ 账号设置\": \"startPixivSettings()\" },\n {\"🔙 退出账号\": \"logout()\" },\n\n {\"🆙 更新书源\": \"updateSource()\" },\n {\"🔰 使用指南\": \"startGithubReadme()\" },\n {\"🐞 反馈问题\": \"startGithubIssue()\" },\n]\n\nlet methord = \"\"\nif (book) methord = 2\nlet settingsBase = [\n {\"👀 书源设置\": `editSettings('SHOW_SETTINGS${methord}')` },\n {\"👀 发现设置\": `editSettings('SHOW_DISCOVER${methord}')` },\n {\"🚫 ✈️ 直连模式\": \"editSettings('IPDirect')\" },\n]\n\nlet novel = [\n {\"章节名称\": \"text\" },\n {\"❤️ ㊙️ 收藏本章\": \"novelBookmarkAdd()\" },\n {\"📃 🚫 追更系列\": \"seriesWatchFactory()\" },\n {\"❤️ 收藏系列\": \"novelsBookmarkAdd()\" },\n\n {\"🖤 取消收藏\": \"novelsBookmarkDelete()\" },\n {\"⭐️ ⚫️ 关注作者\": \"userFollowFactory()\"},\n {\"🚫 ⭕️ 屏蔽作者\": \"userBlock()\"},\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 {\"⚙️ 当前设置\": \"showSettings()\" },\n {\"🔧 默认设置\": \"setDefaultSettingsLoginUrl()\" },\n {\"🚫 👤 搜索作者\": \"editSettings('SEARCH_AUTHOR')\" },\n\n {\"🀄 🚫 繁简通搜\": \"editSettings('CONVERT_CHINESE')\" },\n {\"🚫 📖 更多简介\": \"editSettings('MORE_INFORMATION')\" },\n {\"📅 🚫 更新时间\": \"editSettings('SHOW_UPDATE_TIME')\" },\n\n {\"🔗 🚫 原始链接\": \"editSettings('SHOW_ORIGINAL_LINK')\" },\n {\"📚 🚫 恢复《》\": \"editSettings('REPLACE_TITLE_MARKS')\" },\n {\"🖼️ 🚫 显示描述\": \"editSettings('SHOW_CAPTIONS')\" },\n\n {\"💬 🚫 显示评论\": \"editSettings('SHOW_COMMENTS')\" },\n {\"🚫 ❤️ 隐藏收藏\": \"editSettings('HIDE_LIKE_NOVELS')\" },\n {\"🚫 📃 隐藏追更\": \"editSettings('HIDE_WATCHED_SERIES')\" },\n\n {\"🚫 ⏩ 快速模式\": \"editSettings('FAST')\" },\n {\"🚫 🐞 调试模式\": \"editSettings('DEBUG')\" },\n // {\"🔍 搜索说明\": \"readMeSearch()\" },\n {\"🐺 兽人作者\": \"updatePixivAuthors()\" },\n]\n\nlet discoverSettings = [\n {\"发现设置\": \"text\" },\n {\"🔍 当前发现\": \"showSettingsDiscover()\" },\n {\"🆗 常规小说\": \"editSettings('SHOW_GENERAL')\" },\n {\"🔞 最新企划\": \"editSettings('SHOW_NEW_ADULT')\" },\n\n {\"🆗 最新企划\": \"editSettings('SHOW_NEW_GENERAL')\" },\n {\"🔞 排行榜单\": \"editSettings('SHOW_RANK_ADULT')\" },\n {\"🆗 排行榜单\": \"editSettings('SHOW_RANK_GENERAL')\" },\n\n {\"🔞 原创热门\": \"editSettings('SHOW_GENRE_ADULT')\" },\n {\"🆗 原创热门\": \"editSettings('SHOW_GENRE_GENERAL')\" },\n {\"🐺 兽人小说\": \"editSettings('SHOW_FURRY')\" },\n]\n\nlet li = []\ntry {\n if (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} catch (e) {}\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(\"pixiv:uid\")\n cache.delete(\"pixivCsrfToken\") // 与登录设备有关\n cache.delete(\"headers\")\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 removeSettingsCache() {\n // 删除 屏蔽作者名单\n // removeCacheList(\"blockAuthorList\")\n // 删除 屏蔽关键词\n // cache.delete(\"tagsBlockWords\")\n // cache.delete(\"captionBlockWords\")\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 getNovel() {\n let environment = getFromCacheObject(\"pixivEnvironment\")\n if (environment.IS_LEGADO_SIGMA) {\n try {\n let novel = {}\n try {\n novel.id = chapter.url.match(/novel\\/(\\d+)/)[1] // 直连模式\n } catch(e){\n novel.id = chapter.url.match(/\\d+/)[0]\n }\n novel.title = chapter.title\n 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 } else {\n novel.seriesId = 0\n novel.seriesTitle = \"\"\n }\n\n let resp = getAjaxJson(urlIP(urlNovelDetailed(novel.id))).body\n novel.userId = resp.userId\n if (resp.pollData) {\n novel.pollChoicesCount = resp.pollData.choices.length\n } else {\n novel.pollChoicesCount = 0\n }\n // java.log(JSON.stringify(novel))\n return novel\n } catch (e) {\n // 无法阻止后续函数在日志中报错\n return sleepToast(\"🔰 功能提示\\n\\n⚠️ 请在【小说正文】使用该功能\")\n }\n } else { // 兼容用\n let novel = source.getLoginInfoMap()\n if (!novel) novel = getFromCacheObject(\"novel\")\n return novel\n }\n}\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\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(\"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(\"pixiv:uid\")}&page=${page}`\n )\n java.log(`mode=save&i_id=${novel.id}&u_id=${getFromCacheObject(\"pixiv:uid\")}&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 === true) {\n sleepToast(`⭐️ 关注作者\\n\\n⚠️ 关注【${novel.userName}】失败`, 1)\n\n java.startBrowserAwait(`${urlUserUrl(novel.userId)},\n {\"headers\": {\"User-Agent\": \"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Mobile Safari/537.36\" }}`, `关注${novel.userName}`, false)\n let lastStatus = getAjaxJson(urlUserDetailed(novel.userId), true).body.isFollowed\n if (lastStatus) sleepToast(`⭐️ 关注作者\\n\\n✅ 已关注【${novel.userName}】`)\n\n } else {\n sleepToast(`⭐️ 关注作者\\n\\n✅ 已关注【${novel.userName}】`)\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 }\n}\n\nfunction userFollowFactory(code) {\n if (code === undefined) code = 1\n let novel = getNovel()\n\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(\"pixiv:uid\")\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(\"pixiv:uid\"))\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() {\n let novel = getNovel()\n // novel.pollChoicesCount = getAjaxJson(urlNovelDetailed(novel.id)).body.pollData.selectedValue\n if (!novel.pollChoicesCount) {\n return sleepToast(`📃 小说投票\\n\\n⚠️ 该小说【${novel.title}】无投票信息,建议【清除缓存】【刷新】后重试`)\n }\n\n let choiceId = String(result.get(\"文本框\")).trim()\n if (!choiceId) {\n return sleepToast(`📃 小说投票\\n\\n⚠️ 投票失败:请在【文本框】内输入投票选项(数字)`)\n } else if (Number(choiceId) > novel.pollData.selectedValue) {\n return sleepToast(`📃 小说投票\\n\\n⚠️ 投票失败:选项${choiceId}超出范围`)\n } else if (Number(choiceId) <= 0 || Number(choiceId) > novel.pollChoicesCount) {\n return sleepToast(`📃 小说投票\\n\\n⚠️ 投票失败:选项${choiceId}超出范围`)\n }\n\n let resp = getPostBody(\n `https://www.pixiv.net/ajax/novel/${novel.id}/poll/answer`,\n JSON.stringify({\"choice_id\": choiceId})\n )\n // 200 成功,403 重复投票,400 选项超过范围\n if (resp.error === true) {\n if (resp.errMsg.includes(\"403\")) {\n sleepToast(`📃 小说投票\\n\\n✅ 已经投过票了`)\n } else {\n sleepToast(`📃 小说投票\\n\\n⚠️ 投票失败`)\n shareFactory(\"novel\")\n }\n } else {\n sleepToast(`📃 小说投票\\n\\n✅ 投票成功`)\n }\n}\n\nlet wordsType = {\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(wordsType)\n let key = getFromCacheObject(\"wordsType\")\n\n // 切换屏蔽列表\n let index = keys.indexOf(key) + 1\n if (index === keys.length) index = 0\n key = keys[index]\n putInCacheObject(\"wordsType\", key)\n\n if (key === \"authors\") {\n let words = printAuthorMap(getFromCacheMap(\"blockAuthorMap\"))\n if (!words) words = \"\"\n sleepToast(`👀 查看屏蔽\\n${wordsType[key]}\\n\\n${words}`, 2)\n } else {\n let words = getFromCacheObject(`${key}BlockWords`)\n if (!words) words = []\n sleepToast(`👀 查看屏蔽\\n${wordsType[key]}\\n\\n${words.join(\"\\n\")}`, 2)\n }\n}\n\nfunction blockWordAdd() {\n let method = getFromCacheObject(\"wordsType\")\n let blockWords = getFromCacheObject(`${method}BlockWords`)\n if (blockWords === null) blockWords = []\n\n let word = String(result.get(\"文本框\")).trim()\n if (word === \"\") {\n sleepToast(`🚫 添加屏蔽\\n${wordsType[method]}\\n\\n⚠️ 请在【文本框】内输入屏蔽词`)\n } else if (blockWords.includes(word)) {\n sleepToast(`🚫 添加屏蔽\\n${wordsType[method]}\\n\\n✅ 【${word}】已经加入屏蔽列表了`)\n } else {\n blockWords.push(word)\n putInCacheObject(`${method}BlockWords`, blockWords)\n sleepToast(`🚫 添加屏蔽\\n${wordsType[method]}\\n\\n✅ 已将【${word}】加入屏蔽列表中`)\n }\n}\n\nfunction blockWordDelete() {\n let method = getFromCacheObject(\"wordsType\")\n let blockWords = getFromCacheObject(`${method}BlockWords`)\n if (blockWords === null) blockWords = []\n\n let word = String(result.get(\"文本框\")).trim()\n if (word === \"\") {\n sleepToast(`⭕️ 删除屏蔽\\n${wordsType[method]}\\n\\n⚠️ 请在【文本框】内输入屏蔽词`)\n } else if (!blockWords.includes(word)) {\n sleepToast(`⭕️ 删除屏蔽\\n${wordsType[method]}\\n\\n⚠️ 【${word}】不在屏蔽列表\\n请检查是否有错别字或标点符号是否一致`)\n } else {\n blockWords = blockWords.filter(item => item !== word)\n putInCacheObject(`${method}BlockWords`, blockWords)\n sleepToast(`⭕️ 删除屏蔽\\n${wordsType[method]}\\n\\n✅ 已删除屏蔽词【${word}】`)\n }\n}\n\nfunction blockAuthorAdd() {\n let method = getFromCacheObject(\"wordsType\")\n let blockAuthors = getFromCacheMap(`blockAuthorMap`)\n\n let word = String(result.get(\"文本框\")).trim()\n if (word === \"\") {\n sleepToast(`🚫 添加屏蔽\\n${wordsType[method]}\\n\\n⚠️ 请在【文本框】内输入【作者ID】\\n或使用上方 🚫 屏蔽作者`)\n } else if (blockAuthors.has(word)) {\n let text = `${blockAuthors.get(word)} ${word}`\n sleepToast(`🚫 添加屏蔽\\n${wordsType[method]}\\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${wordsType[method]}\\n\\n✅ 已将【${text}】加入屏蔽列表中`)\n }\n else if (word) {\n sleepToast(`🚫 添加屏蔽\\n${wordsType[method]}\\n\\n⚠️ 输入【用户ID】可屏蔽该作者`)\n }\n putInCacheMap(`blockAuthorMap`, blockAuthors)\n}\n\nfunction blockAuthorDelete() {\n let method = getFromCacheObject(\"wordsType\")\n let blockAuthors = getFromCacheMap(`blockAuthorMap`)\n\n let word = String(result.get(\"文本框\")).trim()\n if (word === \"\") {\n sleepToast(`⭕️ 删除屏蔽\\n${wordsType[method]}\\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${wordsType[method]}\\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${wordsType[method]}\\n\\n✅ 已删除【${text}】`)\n }\n else if (word) {\n sleepToast(`⭕️ 删除屏蔽\\n${wordsType[method]}\\n\\n⚠️ 输入【用户ID】可屏蔽该作者`)\n }\n putInCacheMap(`blockAuthorMap`, blockAuthors)\n}\n\nfunction blockAddFactory() {\n if (getFromCacheObject(\"wordsType\") === \"authors\") return blockAuthorAdd()\n else return blockWordAdd()\n}\n\nfunction blockDeleteFactory() {\n if (getFromCacheObject(\"wordsType\") === \"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 text = printAuthorMap(getFromCacheMap(`likeAuthors`))\n sleepToast(`👀 查看收藏\\n❤️ 他人收藏\\n\\n${text.trim()}`, 2)\n}\n\nfunction likeAuthorsAdd() {\n let likeAuthors = getFromCacheMap(`likeAuthors`)\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(`likeAuthors`, likeAuthors)\n try {source.refreshExplore()} catch (e) {}\n}\n\nfunction likeAuthorsDelete() {\n let likeAuthors = getFromCacheMap(`likeAuthors`)\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(`likeAuthors`, 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\nfunction startPixivSettings() {\n startBrowser(\"https://www.pixiv.net/settings/viewing\", \"账号设置\")\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\nlet settingsName = {\n \"SEARCH_AUTHOR\": \"🔍 搜索作者\",\n \"SHOW_ORIGINAL_LINK\": \"🔗 原始链接\",\n \"CONVERT_CHINESE\": \"🀄️ 繁简通搜\",\n \"SHOW_UPDATE_TIME\": \"📅 更新时间\",\n \"SHOW_COMMENTS\": \"💬 显示评论\",\n \"MORE_INFORMATION\": \"📖 更多简介\",\n \"REPLACE_TITLE_MARKS\": \"📚 恢复《》\",\n \"SHOW_CAPTIONS\": \"🖼️ 显示描述\",\n \"HIDE_LIKE_NOVELS\": \"❤️ 隐藏收藏\",\n \"HIDE_WATCHED_SERIES\": \"📃 隐藏追更\",\n \"IPDirect\": \"✈️ 直连模式\",\n \"FAST\": \"⏩ 快速模式\",\n \"DEBUG\": \"🐞 调试模式\",\n \"SHOW_GENERAL\": \"🆗 常规小说\",\n \"SHOW_NEW_ADULT\": \"🔞 最新企划\",\n \"SHOW_NEW_GENERAL\": \"🆗 最新企划\",\n \"SHOW_RANK_ADULT\": \"🔞 排行榜单\",\n \"SHOW_RANK_GENERAL\": \"🆗 排行榜单\",\n \"SHOW_GENRE_ADULT\": \"🔞 原创热门\",\n \"SHOW_GENRE_GENERAL\": \"🆗 原创热门\",\n \"SHOW_FURRY\": \"🐺 兽人小说\",\n \"SHOW_DISCOVER\": \"⚙️ 发现设置\\n(书源编辑界面)\",\n \"SHOW_SETTINGS\": \"⚙️ 书源设置\\n(书源编辑界面)\",\n \"SHOW_DISCOVER2\": \"⚙️ 发现设置\\n(小说阅读界面)\",\n \"SHOW_SETTINGS2\": \"⚙️ 书源设置\\n(小说阅读界面)\",\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(\"pixivSettings\")\n if (mode === \"FAST\") {\n keys = Object.keys(settingsName).slice(0, 5)\n } else if (mode === \"IPDirect\") {\n keys = Object.keys(settingsName).slice(0, 2)\n } else if (mode.includes(\"DISCOVER\")) {\n keys = Object.keys(settingsName).slice(13, 21)\n } else {\n keys = Object.keys(settingsName).slice(0, 13)\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}\nfunction showSettingsDiscover() {\n sleepToast(`\\n⚙️ 当前发现设置\\n\\n${getSettingStatus(\"DISCOVER\")}`)\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(\"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 } else if (settingName === \"IPDirect\") {\n if (settings.IPDirect && !isLogin()) {\n msg = \"✈️ 直连模式\\n\\n✈️ 直连模式 需登录账号\\n当前未登录账号,现已关闭直连模式\"\n settings.IPDirect = false\n checkSettings(settings)\n } else {\n checkSettings(settings)\n msg = `\\n\\n${statusMsg(status)} ${settingsName[settingName]}\\n\\n${getSettingStatus(settingName)}`\n }\n try {source.refreshExplore()} catch (e) {}\n } else {\n msg = `\\n\\n${statusMsg(status)} ${settingsName[settingName]}`\n if (settingName.startsWith(\"SHOW\")) try {source.refreshExplore()} catch (e) {}\n }\n sleepToast(msg)\n}\n\nfunction cleanCache() {\n let novel = getNovel()\n cache.delete(`${urlNovelUrl(novel.id)}`)\n cache.delete(`${urlNovelDetailed(novel.id)}`)\n try {java.refreshContent()} catch(err) {}\n sleepToast(`🔄 刷新本章\\n\\n若正文未更新,请手动刷新`, 5)\n}\n\nfunction getFurryAuthors() {\n cache.delete(\"https://api.furrynovel.ink/fav/user/cache\") // 删除缓存实时请求数据\n let furryAuthorsMap = {}\n let authorsListLinpx = getAjaxJson(\"https://api.furrynovel.ink/fav/user/cache\")\n authorsListLinpx.forEach(author => {\n furryAuthorsMap[author.name] = author.id || 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": {
"author": "userName",
"canReName": "true",
"coverUrl": "coverUrl",
"init": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction novelHandler(novel){\n novel = util.formatNovels(util.handNovels([novel], true))[0]\n if (novel.seriesId === undefined || novel.seriesId === null) {\n book.bookUrl = novel.detailedUrl = urlNovelUrl(novel.id)\n book.tocUrl = novel.catalogUrl = urlIP(urlNovelDetailed(novel.id))\n } else {\n book.bookUrl = novel.detailedUrl = urlSeriesUrl(novel.seriesId)\n book.tocUrl = novel.catalogUrl = urlIP(urlSeriesDetailed(novel.seriesId))\n }\n // 放入信息以便登陆界面使用\n source.putLoginInfo(JSON.stringify(novel))\n putInCacheObject(\"novel\", novel)\n return novel\n}\n\n(() => {\n return novelHandler(util.getNovelResFirst(result))\n})()",
"intro": "description",
"kind": "tags",
"lastChapter": "latestChapter",
"name": "title",
"tocUrl": "catalogUrl",
"wordCount": "textCount"
},
"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 (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction getNovelInfo(res) {\n // 放入小说信息以便登陆界面使用\n let novel = source.getLoginInfoMap()\n if (!novel) novel = getFromCacheObject(\"novel\")\n if (res && res.error === true) return\n novel.id = Number(res.id)\n novel.title = res.title\n novel.userId = res.userId\n novel.userName = res.userName\n\n if (res.bookmarkData) {\n novel.isBookmark = true\n putInCache(`collect${novel.id}`, res.bookmarkData.id)\n util.saveNovels(\"likeNovels\", [Number(novel.id)])\n } else {\n novel.isBookmark = false\n }\n\n if (res.seriesNavData) {\n novel.seriesId = Number(res.seriesNavData.seriesId)\n novel.seriesTitle = res.seriesNavData.title\n novel.isWatched = res.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 && globalThis.environment.IS_LEGADO) {\n let novelIds = getFromCacheObject(`novelIds${novel.seriesId}`)\n novel.id = novelIds[book.durChapterIndex]\n novel.title = book.durChapterTitle\n let bookmarkId = getFromCacheObject(`collect${novel.id}`)\n novel.isBookmark = !!bookmarkId\n }\n\n // 添加投票信息\n if (res.pollData) novel.pollChoicesCount = res.pollData.choices.length\n else novel.pollChoicesCount = 0\n novel[\"章节名称\"] = novel.title\n source.putLoginInfo(JSON.stringify(novel))\n putInCacheObject(\"novel\", novel)\n}\n\nfunction getCaptions(res, content) {\n // 在正文内部添加小说描述\n if (globalThis.settings.SHOW_CAPTIONS && res.description !== \"\") {\n content = res.description + \"\\n\" + \"——————————\\n\".repeat(2) + content\n }\n return content\n}\n\nfunction replaceUploadedImage(res, content) {\n // 获取 [uploadedimage:] 的图片链接\n if (res.textEmbeddedImages) {\n Object.keys(res.textEmbeddedImages).forEach((key) => {\n content = content.replace(`[uploadedimage:${key}]`, `<img src=\"${urlCoverUrl(res.textEmbeddedImages[key].urls.original)}\">`)\n })\n }\n return content\n}\n\nfunction replacePixivImage(content) {\n // 获取 [pixivimage:] 的图片链接 [pixivimage:1234] [pixivimage:1234-1]\n let matched = content.match(RegExp(/\\[pixivimage:(\\d+)-?(\\d+)]/gm))\n if (matched) {\n matched.forEach(pixivimage => {\n let matched2, illustId, order = 0\n if (pixivimage.includes(\"-\")) {\n matched2 = pixivimage.match(RegExp(\"(\\\\d+)-(\\\\d+)\"))\n illustId = matched2[1]; order = matched2[2]\n } else {\n matched2 = pixivimage.match(RegExp(\"\\\\d+\"))\n illustId = matched2[0];\n }\n if (urlIllustOriginal(illustId, order)) {\n content = content.replace(`${pixivimage}`, `<img src=\"${urlIllustOriginal(illustId, order)}\">`)\n } else {\n content = content.replace(`${pixivimage}`, ``)\n }\n })\n }\n return content\n}\n\nfunction replaceNewPage(content) {\n // 替换 Pixiv 分页标记符号 [newpage]\n if (!globalThis.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]}`, `${\"<p><p/>\".repeat(3)}`)\n }\n }\n }\n return content\n}\n\nfunction replaceChapter(content) {\n // 替换 Pixiv 章节标记符号 [chapter:]\n let matched = content.match(RegExp(/\\[chapter:(.*?)]/gm))\n if (matched) {\n for (let i in matched) {\n let matched2 = matched[i].match(/\\[chapter:(.*?)]/m)\n let chapter = matched2[1].trim()\n // 替换 Pixiv 分页标记符号 [newpage]\n if (globalThis.environment.IS_LEGADO_SIGMA) {\n content = content.replace(`${matched[i]}`, `<usehtml><h3>${chapter}</h3></usehtml>`)\n } else {\n content = content.replace(`${matched[i]}`, `${chapter}<p><p/>`)\n }\n }\n }\n return content\n}\n\nfunction replaceJumpPage(content) {\n // 替换 Pixiv 跳转页面标记符号 [[jump:]]\n let matched = content.match(RegExp(/\\[jump:(\\d+)]/gm))\n if (matched) {\n for (let i in matched) {\n let page = matched[i].match(/\\d+/)\n content = content.replace(`${matched[i]}`, `\\n\\n跳转至第${page}节`)\n }\n }\n return content\n}\n\nfunction replaceJumpUrl(content) {\n // 替换 Pixiv 链接标记符号 [[jumpuri: > ]]\n let matched = content.match(RegExp(/\\[\\[jumpuri:(.*?)>(.*?)]]/gm))\n if (matched) {\n for (let i in matched) {\n let matched2 = matched[i].match(/\\[\\[jumpuri:(.*?)>(.*?)]]/m)\n let matchedText = matched2[0]\n let urlName = matched2[1].trim()\n let urlLink = matched2[2].trim()\n\n if (globalThis.environment.IS_LEGADO_SIGMA) {\n content = content.replace(`${matchedText}`, `<usehtml><p> <a href=${urlLink}>${urlName}</a></p></usehtml>`)\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\nfunction replaceRb(content) {\n // 替换 Pixiv 注音标记符号 [[rb: > ]]\n let matched = content.match(RegExp(/\\[\\[rb:(.*?)>(.*?)]]/gm))\n if (matched) {\n for (let i in matched) {\n let matched2 = matched[i].match(/\\[\\[rb:(.*?)>(.*?)]]/m)\n let matchedText = matched2[0]\n let kanji = matched2[1].trim()\n let kana = matched2[2].trim()\n\n if (!globalThis.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 // 阅读不支持 <ruby> <rt> 注音\n // content = content.replace(`${matchedText}`, `<ruby>${kanji}<rt>${kana}</rt></ruby>`)\n content = content.replace(`${matchedText}`, `${kanji}(${kana})`)\n }\n }\n }\n }\n return content\n}\n\nfunction getPollData(res, content) {\n // 添加投票\n if (res.pollData) {\n let poll = `📃 投票(✅${res.pollData.total}已投):\\n${res.pollData.question}\\n`\n res.pollData.choices.forEach(choice => {\n poll += `选项${choice.id}:${choice.text}(✅${choice.count})\\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 `<img src=\"${urlStampUrl(item.stampId)}\">`\n }\n return text.replace(/\\(([^)]+)\\)/g, (match, key) => {\n if (emoji.hasOwnProperty(key)) {\n return `<img src=\"${urlEmojiUrl(emoji[key])}\" >`\n }\n return match\n })\n}\n\nfunction formatComment(item, replyToName = null) {\n const content = processComment(item)\n const isMyComment = item.userId === String(getFromCache(\"pixiv:uid\"))\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(res, content) {\n if (!globalThis.settings.SHOW_COMMENTS || res.commentCount === 0) return content\n\n const limit = 50\n let comments = [], commentUrls = [];\n let maxPage = Math.ceil(res.commentCount / limit)\n if (maxPage >= 2 && globalThis.environment.IS_LEGADO) {\n for (let i = 0; i < maxPage; i++) {\n commentUrls.push(urlIP(urlNovelComments(res.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(res.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(res) {\n getNovelInfo(res) // 放入信息以便登陆界面使用\n let content = String(res.content)\n // let content = \"undefined\"\n if (content.includes(\"undefined\")) {\n return checkContent()\n }\n\n content = getCaptions(res, content)\n content = replaceUploadedImage(res, content)\n content = replacePixivImage(content)\n content = replaceNewPage(content)\n content = replaceChapter(content)\n content = replaceJumpPage(content)\n content = replaceJumpUrl(content)\n content = replaceRb(content)\n content = getPollData(res, content)\n content = getComment(res, 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.getNovelResFirst(result))\n})()",
"title": "",
"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 let resp = getAjaxJson(urlIP(urlNovelDetailed(novel.id))).body\n novel.authorId = novel.userId = resp.userId\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 clearCache() {\n let novel = getNovel()\n cache.delete(`${urlNovelUrl(novel.id)}`)\n cache.delete(`${urlNovelDetailed(novel.id)}`)\n cache.delete(`${urlSearchNovel(novel.title, 1)}`)\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 novelIds.forEach(novelId => {\n cache.delete(`${urlNovelUrl(novelId)}`)\n cache.delete(`${urlNovelDetailed(novelId)}`)\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 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 customButton(){\n java.open(\"login\")\n}\n// function longCustomButton(){\n// java.open(\"login\")\n// }\n//\n// function clickBookName() {\n// java.open(\"search\", null, book.name)\n// return true\n// }\n//\n// function longClickBookName() {\n// let novel = getNovel()\n// startBrowser(urlNovelUrl(novel.id), novel.title)\n// return true\n// }\n//\n// function clickAuthor() {\n// java.open(\"search\", null, book.author)\n// return true\n// }\n//\n// function longClickAuthor() {\n// let novel = getNovel()\n// startBrowser(urlUserUrl(novel.userId), novel.userName)\n// return true\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 (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction handlerFactory() {\n if (baseUrl.includes(\"https://cdn.jsdelivr.net\")) {\n return () => {updateSource(); return []}\n }\n if (baseUrl.includes(\"github\")) {\n return () => {startBrowser(baseUrl, \"\"); return []}\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(\"/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(\"/genre\")) {\n return handlerWatchList()\n }\n // 匹配 html 中的 json\n if (baseUrl.includes(\"/ranking\")) {\n return handlerRanking()\n }\n // 正则匹配网址内容\n if (baseUrl.includes(\"/marker_all\")) {\n return handlerRankingOld()\n }\n if (baseUrl.includes(\"/editors_picks\")) {\n return handlerRankingOld()\n }\n if (baseUrl.includes(\"/ajax/search/novels\")) {\n return handlerSearch()\n }\n if (baseUrl.startsWith(\"https://www.pixiv.net\")) {\n return handlerRankingOld()\n }\n else {\n return () => {startBrowser(baseUrl, \"\"); return []}\n }\n}\n\n\n// 推荐小说\nfunction handlerRecommend() {\n return () => {\n let res = JSON.parse(result)\n const recommend = res.body.page.recommend\n const novels = res.body.thumbnails.novel\n let nidSet = new Set(recommend.ids)\n // java.log(nidSet.size)\n let list = novels.filter(novel => nidSet.has(String(novel.id)))\n // java.log(`过滤结果:${JSON.stringify(list)}`)\n return util.formatNovels(util.handNovels(util.combineNovels(list)))\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.formatNovels(util.handNovels(res))\n }\n}\n\n//关注作者,小说委托,小说企划\nfunction handlerFollowLatest() {\n return () => {\n let res = JSON.parse(result)\n return util.formatNovels(util.handNovels(util.combineNovels(res.body.thumbnails.novel)))\n }\n}\n\n//推荐小说,最近小说\nfunction handlerDiscovery() {\n return () => {\n let res = JSON.parse(result)\n return util.formatNovels(util.handNovels(util.combineNovels(res.body.novels)))\n }\n}\n\n// 搜索标签\nfunction handlerSearch() {\n return () => {\n let res = JSON.parse(result)\n return util.formatNovels(util.handNovels(util.combineNovels(res.body.novel.data)))\n }\n}\n\n// 追更列表,热门分类\nfunction handlerWatchList() {\n return () => {\n let res = JSON.parse(result)\n return util.formatNovels(util.handNovels(res.body.thumbnails.novelSeries))\n }\n}\n\n// 排行榜,顺序相同\nfunction handlerRanking() {\n return () => {\n try {\n let resp = JSON.parse(result.match(/<script id=\"__NEXT_DATA__\"[^>]*>([\\s\\S]*?)<\\/script>/)[1])\n let novels = resp.props.pageProps.assign.display_a.rank_a\n // java.log(JSON.stringify(novels))\n return util.formatNovels(util.handNovels(util.combineNovels(novels)))\n } catch (e) {\n return []\n }\n }\n}\n\n// 书签,首页,编辑部推荐,顺序相同\nfunction handlerRankingOld() {\n if (globalThis.environment.IS_LEGADO) return handlerRankingAjaxAll()\n // else if (globalThis.environment.IS_SOURCE_READ) return handlerRankingWebview()\n else if (globalThis.environment.IS_SOURCE_READ) return handlerRankingAjax()\n else return []\n}\n\n// 书签,首页,编辑部推荐,顺序相同\nfunction handlerRankingAjaxAll() {\n return () => {\n let novelIds = [], novelUrls = []\n // let result = result + java.ajax(`${baseUrl}&p=2`) // 正则获取网址中的 novelId\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 novelUrls.push(urlNovelDetailed(novelId))\n }\n }\n // java.log(JSON.stringify(novelIds))\n let novels = getAjaxAllJson(novelUrls).map(resp => resp.body)\n return util.formatNovels(util.handNovels(util.combineNovels(novels)))\n }\n}\n\n// 书签,首页\nfunction handlerRankingWebview() {\n return () => {\n let novelIds = [] // 正则获取网址中的 novelId\n // let result = result + java.ajax(`${baseUrl}&p=2`) // 正则获取网址中的 novelId\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 // java.log(JSON.stringify(novelIds))\n let userNovels = getWebviewJson(\n urlNovelsDetailed(getFromCache(\"pixiv:uid\"), novelIds), html => {\n return (html.match(new RegExp(\">\\\\{.*?}<\"))[0].replace(\">\", \"\").replace(\"<\", \"\"))\n }).body\n return util.formatNovels(util.handNovels(util.combineNovels(Object.values(userNovels))))\n }\n}\n\n// 排行榜,书签,顺序相同\nfunction handlerRankingAjax() {\n return () => {\n let novels = [], novelIds = []\n // let result = result + java.ajax(`${baseUrl}&p=2`) // 正则获取网址中的 novelId\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 // java.log(urlNovelDetailed(novelId))\n let res = getAjaxJson(urlNovelDetailed(novelId))\n if (res.error !== true) {\n novels.push(res.body)\n } else {\n java.log(JSON.stringify(res))\n }\n }\n }\n return util.formatNovels(util.handNovels(util.combineNovels(novels)))\n }\n}\n\n(() => {\n return handlerFactory()()\n})()",
"bookUrl": "detailedUrl",
"coverUrl": "coverUrl",
"intro": "description",
"kind": "tags",
"lastChapter": "latestChapter",
"name": "title",
"wordCount": "textCount"
},
"ruleSearch": {
"author": "userName",
"bookList": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\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\nfunction getUserIdOnline(full) {\n let userName = String(java.get(\"keyword\"))\n let page = Number(java.get(\"page\"))\n // cache.delete(urlSearchUser(userName, page, full))\n let resp = getAjaxParseJson(urlSearchUser(userName, page, full), html => {\n // java.log(urlIP(urlSearchUser(userName, page, full)))\n return JSON.parse(html.match(/<script id=\"__NEXT_DATA__\"[^>]*>([\\s\\S]*?)<\\/script>/)[1])\n }\n )\n\n let novels = Object.values(JSON.parse(resp.props.pageProps.serverSerializedPreloadedState).thumbnail.novel)\n let userIds = Array.from(new Set(novels.map(novel => novel.userId)))\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 getUserNovels() {\n let novels = []\n let page = Number(java.get(\"page\"))\n let uidList = getUserIdCache()\n if (!uidList) [uidList, novels] = getUserIdOnline()\n\n if(uidList.length === 0 || uidList.length >=2 ) return novels\n else if(uidList.length === 1 ) {\n let uid = uidList[0]\n let resp = getAjaxJson(urlIP(urlUserAllWorks(uid)), true)\n // java.log(urlIP(urlUserAllWorks(id)))\n\n // 获取系列小说,与 util.handnovels 系列详情兼容\n let seriesIds = []\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 (globalThis.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 (globalThis.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 // 获取单篇小说\n let novelIds = Object.keys(resp.body.novels)\n novelIds = novelIds.filter(novelId => (!seriesNovelIds.includes(novelId)))\n novelIds = novelIds.reverse().slice((page - 1) * 20, page * 20)\n // java.log(`真单篇的小说ID:${JSON.stringify(novelIds)}`)\n // java.log(JSON.stringify(novelIds.length))\n\n if (globalThis.environment.IS_LEGADO) {\n let novelUrls = novelIds.map(novelId => urlIP(urlNovelDetailed(novelId)))\n // java.log(JSON.stringify(novelUrls))\n // cache.delete(novelUrls)\n novels = novels.concat(getAjaxAllJson(novelUrls).map(resp => resp.body))\n }\n\n if (globalThis.environment.IS_SOURCEREAD) {\n novelIds.forEach(novelId => {\n // java.log(urlIP(urlNovelDetailed(novelId)))\n let res = getAjaxJson(urlIP(urlNovelDetailed(novelId)))\n if (res.error !== true) {\n novels.push(res.body)\n } else {\n java.log(JSON.stringify(res))\n }\n })\n }\n }\n \n util.debugFunc(() => {\n java.log(`获取用户搜索小说结束`)\n })\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 (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\n(() => {\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(getUserNovels())\n } else if (keyword.startsWith(\"#\")) {\n java.put(\"keyword\", keyword.slice(1))\n novels = novels.concat(getSeries())\n novels = novels.concat(getNovels())\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 java.log(`👤 粗略搜索作者:${keyword}`)\n novels = novels.concat(getUserIdOnline()[1])\n } else {\n novels = novels.concat(getSeries())\n novels = novels.concat(getNovels())\n if (util.settings.CONVERT_CHINESE) novels = novels.concat(getConvertNovels())\n }\n // java.log(JSON.stringify(novels))\n // 返回空列表中止流程\n if (novels.length === 0) {\n return []\n }\n return novelFilter(util.formatNovels(util.handNovels(novels)))\n})()",
"bookUrl": "detailedUrl",
"checkKeyWord": "#测试页面",
"coverUrl": "coverUrl",
"intro": "description",
"kind": "tags",
"lastChapter": "latestChapter",
"name": "title",
"wordCount": "textCount"
},
"ruleToc": {
"chapterList": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction 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 resp = getAjaxJson(urlIP(urlSeriesNovels(seriesID, limit, lastIndex)), true)\n res = resp.body.thumbnails.novel\n // res = resp.body.page.seriesContents\n res.forEach(v => {\n v.title = v.title.trim()\n v.chapterUrl = urlIP(urlNovel(v.id))\n novelIds.push(v.id)\n if (v.updateDate !== undefined) {\n v.updateDate = timeTextFormat(v.createDate)\n v.chapterInfo = `${v.updateDate} ${v.textCount}字`\n } else {\n v.updateDate = java.timeFormat(v.uploadTimestamp)\n v.chapterInfo = `${v.updateDate} ${v.textLength}字`\n }\n util.debugFunc(() => {\n java.log(`${v.title}`)\n })\n })\n return res;\n }\n\n if (!util.settings.SHOW_UPDATE_TIME) {\n returnList = getAjaxJson(urlIP(urlSeriesNovelsTitles(seriesID)), true).body\n returnList.forEach(v => {\n v.title = v.title.trim()\n v.chapterUrl = urlIP(urlNovel(v.id))\n novelIds.push(v.id)\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 // 放入小说信息以便登陆界面使用\n let novel = source.getLoginInfoMap()\n if (novel === undefined) novel = getFromCacheObject(\"novel\")\n novel.novelIds = novelIds\n putInCacheObject(`novelIds${seriesID}`, novelIds, cacheSaveSeconds)\n // java.log(JSON.stringify(returnList))\n source.putLoginInfo(JSON.stringify(novel))\n putInCacheObject(\"novel\", novel)\n return returnList\n}\n\n(function (res) {\n res = util.getNovelResSeries(result)\n if (res.firstNovelId === undefined || res.seriesNavData === null) {\n return oneShotHandler(res)\n } else {\n return seriesHandler(res)\n }\n})()",
"chapterName": "title",
"chapterUrl": "chapterUrl",
"isPay": "",
"isVip": "",
"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/04/04)\n\n书源版本:265\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": false,
"exploreUrl": "@js:\nlet settings = getFromCacheObject(\"pixivSettings\")\nif (!settings) settings = setDefaultSettings()\n\nlet li = [\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(\\\"pixiv:uid\\\")}}/novels/bookmarks?tag=&offset={{(page-1)*24}}&limit=24&rest=show&lang=zh\"},\n {\"㊙️ 收藏\": \"https://www.pixiv.net/ajax/user/{{cache.get(\\\"pixiv:uid\\\")}}/novels/bookmarks?tag=&offset={{(page-1)*24}}&limit=24&rest=hide&lang=zh\"},\n {\"🏷️ 书签\": \"https://www.pixiv.net/novel/marker_all.php\"},\n {\"🏠 首页\": \"https://www.pixiv.net\"},\n]\n\nlet normal = [\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 r18New = [\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/novel/editors_picks\"},\n]\n\nlet r18Rank = [\n {\"👑 排行榜单 👑\": \"\"},\n {\"今日\": \"https://www.pixiv.net/novel/ranking.php?mode=daily_r18&p={{page}}\"},\n {\"本周\": \"https://www.pixiv.net/novel/ranking.php?mode=weekly_r18&p={{page}}\"},\n {\"R18G\": \"https://www.pixiv.net/novel/ranking.php?mode=r18g&p={{page}}\"},\n {\"男性\": \"https://www.pixiv.net/novel/ranking.php?mode=male_r18&p={{page}}\"},\n {\"女性\": \"https://www.pixiv.net/novel/ranking.php?mode=female_r18&p={{page}}\"}\n]\n\nlet generalRank = [\n {\"🏆 排行榜单 🏆\": \"\"},\n {\"今日\": \"https://www.pixiv.net/novel/ranking.php?mode=daily&p={{page}}\"},\n {\"本周\": \"https://www.pixiv.net/novel/ranking.php?mode=weekly&p={{page}}\"},\n {\"本月\": \"https://www.pixiv.net/novel/ranking.php?mode=monthly&p={{page}}\"},\n {\"男性\": \"https://www.pixiv.net/novel/ranking.php?mode=male&p={{page}}\"},\n {\"女性\": \"https://www.pixiv.net/novel/ranking.php?mode=female&p={{page}}\"},\n {\"新人\": \"https://www.pixiv.net/novel/ranking.php?mode=rookie&p={{page}}\"},\n {\"原创\": \"https://www.pixiv.net/novel/ranking.php?mode=weekly_original&p={{page}}\"},\n {\"AI生成\": \"https://www.pixiv.net/novel/ranking.php?mode=weekly_ai&p={{page}}\"}\n]\n\nlet r18Genre = [\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 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\nlet likeTagLinks = [{\"📌 喜欢标签 📌\":\"\"}]\nlet othersBookmarks = [{\"❤️ 他人收藏 ❤️\": \"\"}]\n\nif (settings.SHOW_GENERAL) li = li.concat(normal)\nif (settings.SHOW_NEW_ADULT) li = li.concat(r18New)\nif (settings.SHOW_NEW_GENERAL) li = li.concat(generalNew)\nif (settings.SHOW_RANK_ADULT)li = li.concat(r18Rank)\nif (settings.SHOW_RANK_GENERAL) li = li.concat(generalRank)\nif (settings.SHOW_GENRE_ADULT) li = li.concat(r18Genre)\nif (settings.SHOW_GENRE_GENERAL) li = li.concat(generalGenre)\nsleepToast('使用指南🔖\\n\\n发现 - 更新 - 点击\"🔰 使用指南\" - 查看')\n\n// 收藏标签\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 li = li.concat(likeTagLinks)\n}\n\n// 他人收藏\nlet 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 li = li.concat(othersBookmarks)\n}\n\n// 书源相关\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 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 // [{'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(\"<!DOCTYPE html>\")\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}\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(\"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\": \"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 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 urlSearchNovel(novelName, page) {\n return `https://www.pixiv.net/ajax/search/novels/${encodeURI(novelName)}?word=${encodeURI(novelName)}&order=date_d&mode=all&p=${page}&s_mode=s_tag&lang=zh`\n}\nfunction urlSearchSeries(seriesName, page) {\n return`https://www.pixiv.net/ajax/search/novels/${encodeURI(seriesName)}?word=${encodeURI(seriesName)}&order=date_d&mode=all&p=${page}&s_mode=s_tag&gs=1&lang=zh`\n}\nfunction urlSearchUser(userName, page, full) {\n let pageUrl = \"\", fullUrl = \"\"\n if (full) fullUrl = \"_full\"\n if (page && page >= 2) pageUrl = `&p=${page}`\n return `https://www.pixiv.net/search/users?nick=${userName}&s_mode=s_usr${fullUrl}&i=1${pageUrl}`;\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 let headers = {\"Referer\": \"https://www.pixiv.net/\"}\n\n let settings = this.getFromCacheObject(\"pixivSettings\")\n if (!settings) settings = this.setDefaultSettings()\n if (!settings.IPDirect) return url\n\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}\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.HIDE_LIKE_NOVELS = false // 搜索:搜索结果 隐藏收藏小说\n settings.HIDE_WATCHED_SERIES = false// 搜索:搜索结果 隐藏追整系列\n\n settings.MORE_INFORMATION = false // 详情:书籍简介显示更多信息\n settings.SHOW_UPDATE_TIME = true // 目录:显示更新时间,但会增加少许请求\n settings.SHOW_ORIGINAL_LINK = true // 目录:显示原始链接,但会增加大量请求\n\n settings.REPLACE_TITLE_MARKS = true // 正文:注音内容为汉字时,替换为书名号\n settings.SHOW_CAPTIONS = true // 正文:章首显示描述\n settings.SHOW_COMMENTS = true // 正文:章尾显示评论,但会增加大量请求\n\n settings.IPDirect = false // 全局:直连模式\n settings.FAST = false // 全局:快速模式\n settings.DEBUG = false // 全局:调试模式\n\n settings.SHOW_GENERAL = true // 发现:显示 常规小说\n settings.SHOW_NEW_ADULT = true // 发现:显示 最新企划约稿 R18\n settings.SHOW_NEW_GENERAL = false // 发现:显示 最新企划约稿 常规\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 settings.SHOW_FURRY = false // 发现:显示 兽人小说推荐作者\n\n settings.SHOW_SETTINGS = true // 设置:显示 书源设置\n settings.SHOW_DISCOVER = true // 设置:显示 发现设置\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.FAST || settings.IPDirect) {\n settings.SEARCH_AUTHOR = false // 搜索:默认不搜索作者名称\n settings.SHOW_ORIGINAL_LINK = false // 目录:显示章节源链接\n }\n if (!settings.FAST && !settings.IPDirect) {\n // settings.SEARCH_AUTHOR = true // 搜索:默认不搜索作者名称\n settings.SHOW_ORIGINAL_LINK = true // 目录:显示章节源链接\n }\n\n if (settings.FAST) {\n settings.SEARCH_AUTHOR = false // 搜索:默认不搜索作者名称\n settings.CONVERT_CHINESE = false // 搜索:繁简通搜\n settings.SHOW_UPDATE_TIME = false // 目录:显示章节更新时间\n settings.SHOW_COMMENTS = false // 正文:显示评论\n }\n this.putInCacheObject(\"pixivSettings\", settings)\n return settings\n}\n\nfunction updateSource() {\n const {java, source} = this\n java.longToast(\"🆙 更新书源\\n\\nJsdelivr CDN 更新有延迟\\nGithub 更新需代理\")\n let onlineSource, comment, sourceName, sourceNameCapitalize, index = 0\n if (source.bookSourceUrl.includes(\"pixiv\")) sourceName = \"pixiv\"\n else if (source.bookSourceUrl.includes(\"furrynovel\")) sourceName = \"linpx\"\n sourceNameCapitalize = sourceName[0].toUpperCase() + sourceName.substring(1)\n\n if (source.bookSourceName.includes(\"备用\")) index = 1\n else if (source.bookSourceName.includes(\"漫画\")) index = 2\n if (source.bookSourceUrl.includes(\"furrynovel.com\")) {\n sourceNameCapitalize = \"FurryNovel\"\n index = 1\n }\n\n try {\n let updateUrl = `https://cdn.jsdelivr.net/gh/DowneyRem/PixivSource@main/${sourceName}.json`\n onlineSource = JSON.parse(java.get(updateUrl,{'User-Agent': 'Mozilla/5.0 (Linux; Android 14)','X-Requested-With': 'XMLHttpRequest'}).body())[index]\n } catch (e) {\n try {\n let updateUrl = `https://raw.githubusercontent.com/DowneyRem/PixivSource/main/${sourceName}.json`\n onlineSource = JSON.parse(java.get(updateUrl,{'User-Agent': 'Mozilla/5.0 (Linux; Android 14)','X-Requested-With': 'XMLHttpRequest'}).body())[index]\n } catch (e) {\n onlineSource = {lastUpdateTime: new Date().getTime(), bookSourceComment: source.bookSourceComment}\n }\n }\n comment = onlineSource.bookSourceComment.split(\"\\n\")\n // onlineSource = source\n // comment = source.bookSourceComment.split(\"\\n\")\n\n let htm = `\n<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>更新 ${source.bookSourceName} 书源</title>\n <style> \n table { text-align: center; margin: 0 auto; } .ann { display: flex; justify-content: center; align-items: center; height: 5vh; } \n button { background-color: rgb(76, 175, 80); color: white; border: none; border-radius: 4px; height: 6vh; width: 30vw; overflow: hidden; } \n button span { cursor: pointer; display: inline-block; position: relative; transition: 0.4s; } \n button span:after { content: '>'; position: absolute; opacity: 0; top: 0; right: 30px; transition: 0.2s; } \n button:active span { padding-right: 20px; } \n button:active span:after { opacity: 1; right: -40px; }\n </style>\n</head>\n\n<body>\n <table border=\"1\" cellspacing=\"0\">\n <th colspan=\"2\"> ${source.bookSourceName} 书源 \n <a href=\"https://pixivsource.pages.dev/${sourceNameCapitalize}\">🔰 使用指南</a>\n || <a href=\"https://pixivsource.pages.dev/Sponsor\">❤️ 赞助开发</a>\n </th>\n <tr>\n <td>☁️ 远程版本:${onlineSource.bookSourceComment.split(\"\\n\")[2].replace(\"书源版本:\", \"\")}</td>\n <td>📆 更新:${timeFormat(onlineSource.lastUpdateTime)}</td>\n </tr>\n <tr>\n <td>📥 本地版本:${source.bookSourceComment.split(\"\\n\")[2].replace(\"书源版本:\", \"\")}</td>\n <td>📆 更新:${timeFormat(source.lastUpdateTime)}</td>\n </tr> \n <tr><td colspan=\"2\" style=\"text-align: left;\">${comment.slice(3, 10).join(\"<br>\")}</td></tr>\n <tr><td colspan=\"2\" style=\"text-align: left;\">${comment.slice(comment.length-3, comment.length).join(\"<br>\")}</td></tr>\n </table>\n \n <table border=\"0\" cellspacing=\"20\">\n <th colspan=\"2\"> 更新 ${source.bookSourceName} 书源 </th>\n <tr><td><div class=\"ann\">\n <a href=\"legado://import/importonline?src=https://cdn.jsdelivr.net/gh/DowneyRem/PixivSource@main/${sourceName}.json\">\n <button><span>更新书源<br>(Jsdelivr CDN)</span></button>\n </a></div></td>\n \n <td><div class=\"ann\">\n <a href=\"legado://import/importonline?src=https://cdn.jsdelivr.net/gh/DowneyRem/PixivSource@main/btsrk.json\">\n <button><span>更新订阅<br>(Jsdelivr CDN)</span></button>\n </a></div></td>\n </tr>\n \n <tr><td><div class=\"ann\">\n <a href=\"legado://import/importonline?src=https://raw.githubusercontent.com/DowneyRem/PixivSource/main/${sourceName}.json\">\n <button><span>书源链接<br>(GitHub)</span></button>\n </a></div></td>\n \n <td><div class=\"ann\">\n <a href=\"legado://import/importonline?src=https://raw.githubusercontent.com/DowneyRem/PixivSource/main/btsrk.json\">\n <button><span>订阅链接<br>(GitHub)</span></button>\n </a></div></td>\n </tr>\n \n <tr><td><div class=\"ann\">\n <a href=\"legado://import/importonline?src=https://codeberg.org/DowneyRem/PixivSource/raw/branch/main/${sourceName}.json\">\n <button><span>备用书源链接<br>(Codeberg)</span></button>\n </a></div></td>\n \n <td><div class=\"ann\">\n <a href=\"legado://import/importonline?src=https://codeberg.org/DowneyRem/PixivSource/raw/branch/main/btsrk.json\">\n <button><span>备用订阅链接<br>(Codeberg)</span></button>\n </a></div></td>\n </tr>\n </table>\n</body>\n</html>`\n java.startBrowser(`data:text/html;charset=utf-8;base64, ${java.base64Encode(htm)}`, '更新书源')\n return []\n}",
"lastUpdateTime": 1775232000251,
"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 isLegadoSigma() {\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 // 设置写入缓存\n u.settings = getFromCacheObject(\"pixivSettings\")\n if (!u.settings) u.settings = setDefaultSettings()\n u.settings = checkSettings()\n putInCacheObject(\"pixivSettings\", u.settings)\n\n // 环境写入缓存\n u.environment = {}\n u.environment.IS_SOURCEREAD = isSourceRead()\n u.environment.IS_LEGADO = !isSourceRead()\n u.environment.IS_LEGADO_SIGMA = isLegadoSigma()\n putInCacheObject(\"pixivEnvironment\", u.environment)\n\n // 输出环境信息\n if (u.environment.IS_SOURCEREAD) {\n java.log(\"📱 软件平台:🍎 源阅 SourceRead\")\n } else if (u.environment.IS_LEGADO_SIGMA) {\n java.log(\"📱 软件平台:🤖 阅读 Beta【新包名】/ 阅读 Plus\")\n } else if (u.environment.IS_LEGADO_OFFICIAL) {\n java.log(\"📱 软件平台:🤖 阅读 正式版\")\n // sleepToast(\"\\n⚠️当前软件为:阅读【正式版】\\n【正式版】已年久失修,不推荐继续使用\\n\\n为了更好的使用体验,请用:\\n【阅读 Plus】或【阅读 Beta 新包名】\\n\\n即将为您打开【阅读 Plus】下载界面\")\n // sleep(3);\n // startBrowser(\"https://loyc.xyz/c/legado.html#download\", \"下载阅读 Plus\")\n } else {\n java.log(\"📱 软件平台:🤖 阅读 Beta【原包名】\")\n // sleepToast(\"\\n⚠️当前软件为:阅读 Beta【原包名】\\n\\n为了更好的使用体验,请用:\\n【阅读 Plus】或【阅读 Beta 新包名】\\n\\n即将为您打开【阅读 Plus】下载界面\")\n // sleep(3);\n // startBrowser(\"https://loyc.xyz/c/legado.html#download\", \"下载阅读 Plus\")\n }\n if (u.settings.IPDirect) {\n java.log(\"✈️ 直连模式:✅ 已开启\")\n } else {\n java.log(\"✈️ 直连模式:❌ 已关闭\")\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\": \"${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(\"pixiv:uid\")\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 // 将多个长篇小说解析为一本书\n u.combineNovels = function(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 = [], msg\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\n util.debugFunc(() => {\n // java.log(JSON.stringify(novels0))\n java.log(JSON.stringify(novels0.length))\n // java.log(JSON.stringify(novels1))\n java.log(JSON.stringify(novels1.length))\n // java.log(JSON.stringify(novels2))\n java.log(JSON.stringify(novels2.length))\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 captionBlockWords = getFromCacheObject(\"captionBlockWords\")\n if (!captionBlockWords) captionBlockWords = []\n else {\n // 仅保留没有任何屏蔽词的小说\n // novels = novels.filter(novel => {\n // return !captionBlockWords.some(item => {\n // if (novel.description !== undefined) return novel.description.includes(item)\n // })\n // })\n novels = novels.filter(novel => !captionBlockWords.some(item => novel.description.includes(item)))\n let novels2 = novels.map(novel => novel.id)\n java.log(`🚫 屏蔽描述:${captionBlockWords.join(\"\\n\")}`)\n java.log(`🚫 屏蔽描述:过滤前${novels0.length};过滤后${novels2.length}`)\n }\n\n let tagsBlockWords = getFromCacheObject(\"tagsBlockWords\")\n if (!tagsBlockWords) tagsBlockWords = []\n else {\n // 仅保留没有任何屏蔽词的小说\n // novels = novels.filter(novel => {\n // return !tagsBlockWords.some(item => {\n // if (novel.tags !== undefined) return novel.tags.includes(item)\n // })\n // })\n novels = novels.filter(novel => !tagsBlockWords.some(item => novel.tagsList.includes(item)))\n let novels2 = novels.map(novel => novel.id)\n java.log(`🚫 屏蔽标签:${tagsBlockWords.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.id = novel.novelId // 获取真正的 novelId\n novel.seriesTitle = undefined\n } else {\n novel.seriesId = novel.id\n novel.id = novel.novelId = novel.latestEpisodeId // 获取真正的 novelId\n novel.seriesTitle = novel.title\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) {\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 if (novel.seriesId && !isDetail) {\n novel.title = novel.seriesTitle\n novel.tags.unshift(\"长篇\")\n novel.detailedUrl = urlIP(urlSeriesDetailed(novel.seriesId))\n }\n // 系列添加更多信息\n if (novel.seriesId && isDetail) {\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.join(\",\")\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 // 正文,详情,搜索:从网址获取id,返回单篇小说 res,系列返回首篇小说 res\n // pixiv 默认分享信息中有#号,不会被识别成链接,无法使用添加网址\n u.getNovelResFirst = 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.body\n }\n\n // 目录:从网址获取id,尽可能返回系列 res,单篇小说返回小说 res\n u.getNovelResSeries = function(result) {\n let seriesId = 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/novel/series/\\\\d+\"\n let isSeries = baseUrl.match(new RegExp(pattern))\n if (isSeries) {\n seriesId = 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 java.log(`匹配小说ID:${id}`)\n res = getAjaxJson(urlIP(urlNovelDetailed(id)))\n }\n }\n }\n if (isJson) {\n res = JSON.parse(result)\n }\n\n if (res.body && res.body.seriesNavData) {\n seriesId = res.body.seriesNavData.seriesId\n }\n if (seriesId) {\n java.log(`系列ID:${seriesId}`)\n res = getAjaxJson(urlIP(urlSeriesDetailed(seriesId)))\n }\n if (res.error === true) {\n java.log(`无法从 Pixiv 获取当前小说`)\n java.log(JSON.stringify(res))\n }\n return res.body\n }\n\n util = u\n 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(\"pixiv:uid\")\n let uid = getFromCache(\"pixiv:uid\")\n if (!uid && isLogin()) {\n let html = java.ajax(\"https://www.pixiv.net/\")\n uid = html.match(/user_id:'(\\d+)'/)[1]\n putInCache(\"pixiv:uid\", uid)\n }\n return uid\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(\"headers\", headers)\n return headers\n}\n\npublicFunc()\nif (!isLogin() && !util.settings.DEBUG) {\n sleepToast(\"🔍 搜索小说\\n\\n⚠️ 当前未登录账号\\n请登录 Pixiv 账号\", 1.5)\n util.removeCookie(); util.login()\n sleepToast(\"🔍 搜索小说\\n\\n登录成功后,请重新搜索/进入发现\", 2)\n}\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\": \"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('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('REPLACE_TITLE_MARKS')\",\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_COMMENTS')\",\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('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\": \"updatePixivAuthors()\",\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_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_FURRY')\",\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(); 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(\"pixiv:uid\")\n cache.delete(\"pixivCsrfToken\") // 与登录设备有关\n cache.delete(\"headers\")\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 removeSettingsCache() {\n // 删除 屏蔽作者名单\n // removeCacheList(\"blockAuthorList\")\n // 删除 屏蔽关键词\n // cache.delete(\"tagsBlockWords\")\n // cache.delete(\"captionBlockWords\")\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 getNovel() {\n let environment = getFromCacheObject(\"pixivEnvironment\")\n if (environment.IS_LEGADO_SIGMA) {\n try {\n let novel = {}\n try {\n novel.id = chapter.url.match(/novel\\/(\\d+)/)[1] // 直连模式\n } catch(e){\n novel.id = chapter.url.match(/\\d+/)[0]\n }\n novel.title = chapter.title\n 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 } else {\n novel.seriesId = 0\n novel.seriesTitle = \"\"\n }\n\n let resp = getAjaxJson(urlIP(urlNovelDetailed(novel.id))).body\n novel.userId = resp.userId\n if (resp.pollData) {\n novel.pollChoicesCount = resp.pollData.choices.length\n } else {\n novel.pollChoicesCount = 0\n }\n // java.log(JSON.stringify(novel))\n return novel\n } catch (e) {\n // 无法阻止后续函数在日志中报错\n return sleepToast(\"🔰 功能提示\\n\\n⚠️ 请在【小说正文】使用该功能\")\n }\n } else { // 兼容用\n let novel = source.getLoginInfoMap()\n if (!novel) novel = getFromCacheObject(\"novel\")\n return novel\n }\n}\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\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(\"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(\"pixiv:uid\")}&page=${page}`\n )\n java.log(`mode=save&i_id=${novel.id}&u_id=${getFromCacheObject(\"pixiv:uid\")}&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 === true) {\n sleepToast(`⭐️ 关注作者\\n\\n⚠️ 关注【${novel.userName}】失败`, 1)\n\n java.startBrowserAwait(`${urlUserUrl(novel.userId)},\n {\"headers\": {\"User-Agent\": \"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Mobile Safari/537.36\" }}`, `关注${novel.userName}`, false)\n let lastStatus = getAjaxJson(urlUserDetailed(novel.userId), true).body.isFollowed\n if (lastStatus) sleepToast(`⭐️ 关注作者\\n\\n✅ 已关注【${novel.userName}】`)\n\n } else {\n sleepToast(`⭐️ 关注作者\\n\\n✅ 已关注【${novel.userName}】`)\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 }\n}\n\nfunction userFollowFactory(code) {\n if (code === undefined) code = 1\n let novel = getNovel()\n\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(\"pixiv:uid\")\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(\"pixiv:uid\"))\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() {\n let novel = getNovel()\n // novel.pollChoicesCount = getAjaxJson(urlNovelDetailed(novel.id)).body.pollData.selectedValue\n if (!novel.pollChoicesCount) {\n return sleepToast(`📃 小说投票\\n\\n⚠️ 该小说【${novel.title}】无投票信息,建议【清除缓存】【刷新】后重试`)\n }\n\n let choiceId = String(result.get(\"文本框\")).trim()\n if (!choiceId) {\n return sleepToast(`📃 小说投票\\n\\n⚠️ 投票失败:请在【文本框】内输入投票选项(数字)`)\n } else if (Number(choiceId) > novel.pollData.selectedValue) {\n return sleepToast(`📃 小说投票\\n\\n⚠️ 投票失败:选项${choiceId}超出范围`)\n } else if (Number(choiceId) <= 0 || Number(choiceId) > novel.pollChoicesCount) {\n return sleepToast(`📃 小说投票\\n\\n⚠️ 投票失败:选项${choiceId}超出范围`)\n }\n\n let resp = getPostBody(\n `https://www.pixiv.net/ajax/novel/${novel.id}/poll/answer`,\n JSON.stringify({\"choice_id\": choiceId})\n )\n // 200 成功,403 重复投票,400 选项超过范围\n if (resp.error === true) {\n if (resp.errMsg.includes(\"403\")) {\n sleepToast(`📃 小说投票\\n\\n✅ 已经投过票了`)\n } else {\n sleepToast(`📃 小说投票\\n\\n⚠️ 投票失败`)\n shareFactory(\"novel\")\n }\n } else {\n sleepToast(`📃 小说投票\\n\\n✅ 投票成功`)\n }\n}\n\nlet wordsType = {\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(wordsType)\n let key = getFromCacheObject(\"wordsType\")\n\n // 切换屏蔽列表\n let index = keys.indexOf(key) + 1\n if (index === keys.length) index = 0\n key = keys[index]\n putInCacheObject(\"wordsType\", key)\n\n if (key === \"authors\") {\n let words = printAuthorMap(getFromCacheMap(\"blockAuthorMap\"))\n if (!words) words = \"\"\n sleepToast(`👀 查看屏蔽\\n${wordsType[key]}\\n\\n${words}`, 2)\n } else {\n let words = getFromCacheObject(`${key}BlockWords`)\n if (!words) words = []\n sleepToast(`👀 查看屏蔽\\n${wordsType[key]}\\n\\n${words.join(\"\\n\")}`, 2)\n }\n}\n\nfunction blockWordAdd() {\n let method = getFromCacheObject(\"wordsType\")\n let blockWords = getFromCacheObject(`${method}BlockWords`)\n if (blockWords === null) blockWords = []\n\n let word = String(result.get(\"文本框\")).trim()\n if (word === \"\") {\n sleepToast(`🚫 添加屏蔽\\n${wordsType[method]}\\n\\n⚠️ 请在【文本框】内输入屏蔽词`)\n } else if (blockWords.includes(word)) {\n sleepToast(`🚫 添加屏蔽\\n${wordsType[method]}\\n\\n✅ 【${word}】已经加入屏蔽列表了`)\n } else {\n blockWords.push(word)\n putInCacheObject(`${method}BlockWords`, blockWords)\n sleepToast(`🚫 添加屏蔽\\n${wordsType[method]}\\n\\n✅ 已将【${word}】加入屏蔽列表中`)\n }\n}\n\nfunction blockWordDelete() {\n let method = getFromCacheObject(\"wordsType\")\n let blockWords = getFromCacheObject(`${method}BlockWords`)\n if (blockWords === null) blockWords = []\n\n let word = String(result.get(\"文本框\")).trim()\n if (word === \"\") {\n sleepToast(`⭕️ 删除屏蔽\\n${wordsType[method]}\\n\\n⚠️ 请在【文本框】内输入屏蔽词`)\n } else if (!blockWords.includes(word)) {\n sleepToast(`⭕️ 删除屏蔽\\n${wordsType[method]}\\n\\n⚠️ 【${word}】不在屏蔽列表\\n请检查是否有错别字或标点符号是否一致`)\n } else {\n blockWords = blockWords.filter(item => item !== word)\n putInCacheObject(`${method}BlockWords`, blockWords)\n sleepToast(`⭕️ 删除屏蔽\\n${wordsType[method]}\\n\\n✅ 已删除屏蔽词【${word}】`)\n }\n}\n\nfunction blockAuthorAdd() {\n let method = getFromCacheObject(\"wordsType\")\n let blockAuthors = getFromCacheMap(`blockAuthorMap`)\n\n let word = String(result.get(\"文本框\")).trim()\n if (word === \"\") {\n sleepToast(`🚫 添加屏蔽\\n${wordsType[method]}\\n\\n⚠️ 请在【文本框】内输入【作者ID】\\n或使用上方 🚫 屏蔽作者`)\n } else if (blockAuthors.has(word)) {\n let text = `${blockAuthors.get(word)} ${word}`\n sleepToast(`🚫 添加屏蔽\\n${wordsType[method]}\\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${wordsType[method]}\\n\\n✅ 已将【${text}】加入屏蔽列表中`)\n }\n else if (word) {\n sleepToast(`🚫 添加屏蔽\\n${wordsType[method]}\\n\\n⚠️ 输入【用户ID】可屏蔽该作者`)\n }\n putInCacheMap(`blockAuthorMap`, blockAuthors)\n}\n\nfunction blockAuthorDelete() {\n let method = getFromCacheObject(\"wordsType\")\n let blockAuthors = getFromCacheMap(`blockAuthorMap`)\n\n let word = String(result.get(\"文本框\")).trim()\n if (word === \"\") {\n sleepToast(`⭕️ 删除屏蔽\\n${wordsType[method]}\\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${wordsType[method]}\\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${wordsType[method]}\\n\\n✅ 已删除【${text}】`)\n }\n else if (word) {\n sleepToast(`⭕️ 删除屏蔽\\n${wordsType[method]}\\n\\n⚠️ 输入【用户ID】可屏蔽该作者`)\n }\n putInCacheMap(`blockAuthorMap`, blockAuthors)\n}\n\nfunction blockAddFactory() {\n if (getFromCacheObject(\"wordsType\") === \"authors\") return blockAuthorAdd()\n else return blockWordAdd()\n}\n\nfunction blockDeleteFactory() {\n if (getFromCacheObject(\"wordsType\") === \"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 text = printAuthorMap(getFromCacheMap(`likeAuthors`))\n sleepToast(`👀 查看收藏\\n❤️ 他人收藏\\n\\n${text.trim()}`, 2)\n}\n\nfunction likeAuthorsAdd() {\n let likeAuthors = getFromCacheMap(`likeAuthors`)\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(`likeAuthors`, likeAuthors)\n try {source.refreshExplore()} catch (e) {}\n}\n\nfunction likeAuthorsDelete() {\n let likeAuthors = getFromCacheMap(`likeAuthors`)\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(`likeAuthors`, 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\nfunction startPixivSettings() {\n startBrowser(\"https://www.pixiv.net/settings/viewing\", \"账号设置\")\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\nlet settingsName = {\n \"SEARCH_AUTHOR\": \"🔍 搜索作者\",\n \"SHOW_ORIGINAL_LINK\": \"🔗 原始链接\",\n \"CONVERT_CHINESE\": \"🀄️ 繁简通搜\",\n \"SHOW_UPDATE_TIME\": \"📅 更新时间\",\n \"SHOW_COMMENTS\": \"💬 显示评论\",\n \"MORE_INFORMATION\": \"📖 更多简介\",\n \"REPLACE_TITLE_MARKS\": \"📚 恢复《》\",\n \"SHOW_CAPTIONS\": \"🖼️ 显示描述\",\n \"HIDE_LIKE_NOVELS\": \"❤️ 隐藏收藏\",\n \"HIDE_WATCHED_SERIES\": \"📃 隐藏追更\",\n \"IPDirect\": \"✈️ 直连模式\",\n \"FAST\": \"⏩ 快速模式\",\n \"DEBUG\": \"🐞 调试模式\",\n \"SHOW_GENERAL\": \"🆗 常规小说\",\n \"SHOW_NEW_ADULT\": \"🔞 最新企划\",\n \"SHOW_NEW_GENERAL\": \"🆗 最新企划\",\n \"SHOW_RANK_ADULT\": \"🔞 排行榜单\",\n \"SHOW_RANK_GENERAL\": \"🆗 排行榜单\",\n \"SHOW_GENRE_ADULT\": \"🔞 原创热门\",\n \"SHOW_GENRE_GENERAL\": \"🆗 原创热门\",\n \"SHOW_FURRY\": \"🐺 兽人小说\",\n \"SHOW_DISCOVER\": \"⚙️ 发现设置\\n(书源编辑界面)\",\n \"SHOW_SETTINGS\": \"⚙️ 书源设置\\n(书源编辑界面)\",\n \"SHOW_DISCOVER2\": \"⚙️ 发现设置\\n(小说阅读界面)\",\n \"SHOW_SETTINGS2\": \"⚙️ 书源设置\\n(小说阅读界面)\",\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(\"pixivSettings\")\n if (mode === \"FAST\") {\n keys = Object.keys(settingsName).slice(0, 5)\n } else if (mode === \"IPDirect\") {\n keys = Object.keys(settingsName).slice(0, 2)\n } else if (mode.includes(\"DISCOVER\")) {\n keys = Object.keys(settingsName).slice(13, 21)\n } else {\n keys = Object.keys(settingsName).slice(0, 13)\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}\nfunction showSettingsDiscover() {\n sleepToast(`\\n⚙️ 当前发现设置\\n\\n${getSettingStatus(\"DISCOVER\")}`)\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(\"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 } else if (settingName === \"IPDirect\") {\n if (settings.IPDirect && !isLogin()) {\n msg = \"✈️ 直连模式\\n\\n✈️ 直连模式 需登录账号\\n当前未登录账号,现已关闭直连模式\"\n settings.IPDirect = false\n checkSettings(settings)\n } else {\n checkSettings(settings)\n msg = `\\n\\n${statusMsg(status)} ${settingsName[settingName]}\\n\\n${getSettingStatus(settingName)}`\n }\n try {source.refreshExplore()} catch (e) {}\n } else {\n msg = `\\n\\n${statusMsg(status)} ${settingsName[settingName]}`\n if (settingName.startsWith(\"SHOW\")) try {source.refreshExplore()} catch (e) {}\n }\n sleepToast(msg)\n}\n\nfunction cleanCache() {\n let novel = getNovel()\n cache.delete(`${urlNovelUrl(novel.id)}`)\n cache.delete(`${urlNovelDetailed(novel.id)}`)\n try {java.refreshContent()} catch(err) {}\n sleepToast(`🔄 刷新本章\\n\\n若正文未更新,请手动刷新`, 5)\n}\n\nfunction getFurryAuthors() {\n cache.delete(\"https://api.furrynovel.ink/fav/user/cache\") // 删除缓存实时请求数据\n let furryAuthorsMap = {}\n let authorsListLinpx = getAjaxJson(\"https://api.furrynovel.ink/fav/user/cache\")\n authorsListLinpx.forEach(author => {\n furryAuthorsMap[author.name] = author.id || 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": {
"author": "userName",
"canReName": "true",
"coverUrl": "coverUrl",
"init": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction novelHandler(novel){\n novel = util.formatNovels(util.handNovels([novel], true))[0]\n if (novel.seriesId === undefined || novel.seriesId === null) {\n book.bookUrl = novel.detailedUrl = urlNovelUrl(novel.id)\n book.tocUrl = novel.catalogUrl = urlIP(urlNovelDetailed(novel.id))\n } else {\n book.bookUrl = novel.detailedUrl = urlSeriesUrl(novel.seriesId)\n book.tocUrl = novel.catalogUrl = urlIP(urlSeriesDetailed(novel.seriesId))\n }\n // 放入信息以便登陆界面使用\n source.putLoginInfo(JSON.stringify(novel))\n putInCacheObject(\"novel\", novel)\n return novel\n}\n\n(() => {\n return novelHandler(util.getNovelResFirst(result))\n})()",
"intro": "description",
"kind": "tags",
"lastChapter": "latestChapter",
"name": "title",
"tocUrl": "catalogUrl",
"wordCount": "textCount"
},
"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 (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction getNovelInfo(res) {\n // 放入小说信息以便登陆界面使用\n let novel = source.getLoginInfoMap()\n if (!novel) novel = getFromCacheObject(\"novel\")\n if (res && res.error === true) return\n novel.id = Number(res.id)\n novel.title = res.title\n novel.userId = res.userId\n novel.userName = res.userName\n\n if (res.bookmarkData) {\n novel.isBookmark = true\n putInCache(`collect${novel.id}`, res.bookmarkData.id)\n util.saveNovels(\"likeNovels\", [Number(novel.id)])\n } else {\n novel.isBookmark = false\n }\n\n if (res.seriesNavData) {\n novel.seriesId = Number(res.seriesNavData.seriesId)\n novel.seriesTitle = res.seriesNavData.title\n novel.isWatched = res.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 let novelIds = getFromCacheObject(`novelIds${novel.seriesId}`)\n novel.id = novelIds[book.durChapterIndex]\n novel.title = book.durChapterTitle\n let bookmarkId = getFromCacheObject(`collect${novel.id}`)\n novel.isBookmark = !!bookmarkId\n }\n\n // 添加投票信息\n if (res.pollData) novel.pollChoicesCount = res.pollData.choices.length\n else novel.pollChoicesCount = 0\n novel[\"章节名称\"] = novel.title\n source.putLoginInfo(JSON.stringify(novel))\n putInCacheObject(\"novel\", novel)\n}\n\nfunction getCaptions(res, content) {\n // 在正文内部添加小说描述\n if (util.settings.SHOW_CAPTIONS && res.description !== \"\") {\n content = res.description + \"\\n\" + \"——————————\\n\".repeat(2) + content\n }\n return content\n}\n\nfunction replaceUploadedImage(res, content) {\n // 获取 [uploadedimage:] 的图片链接\n if (res.textEmbeddedImages) {\n Object.keys(res.textEmbeddedImages).forEach((key) => {\n content = content.replace(`[uploadedimage:${key}]`, `<img src=\"${urlCoverUrl(res.textEmbeddedImages[key].urls.original)}\">`)\n })\n }\n return content\n}\n\nfunction replacePixivImage(content) {\n // 获取 [pixivimage:] 的图片链接 [pixivimage:1234] [pixivimage:1234-1]\n let matched = content.match(RegExp(/\\[pixivimage:(\\d+)-?(\\d+)]/gm))\n if (matched) {\n matched.forEach(pixivimage => {\n let matched2, illustId, order = 0\n if (pixivimage.includes(\"-\")) {\n matched2 = pixivimage.match(RegExp(\"(\\\\d+)-(\\\\d+)\"))\n illustId = matched2[1]; order = matched2[2]\n } else {\n matched2 = pixivimage.match(RegExp(\"\\\\d+\"))\n illustId = matched2[0];\n }\n if (urlIllustOriginal(illustId, order)) {\n content = content.replace(`${pixivimage}`, `<img src=\"${urlIllustOriginal(illustId, order)}\">`)\n } else {\n content = content.replace(`${pixivimage}`, ``)\n }\n })\n }\n return content\n}\n\nfunction replaceNewPage(content) {\n // 替换 Pixiv 分页标记符号 [newpage]\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]}`, `${\"<p><p/>\".repeat(3)}`)\n }\n }\n }\n return content\n}\n\nfunction replaceChapter(content) {\n // 替换 Pixiv 章节标记符号 [chapter:]\n let matched = content.match(RegExp(/\\[chapter:(.*?)]/gm))\n if (matched) {\n for (let i in matched) {\n let matched2 = matched[i].match(/\\[chapter:(.*?)]/m)\n let chapter = matched2[1].trim()\n // 替换 Pixiv 分页标记符号 [newpage]\n if (util.environment.IS_LEGADO_SIGMA) {\n content = content.replace(`${matched[i]}`, `<usehtml><h3>${chapter}</h3></usehtml>`)\n } else {\n content = content.replace(`${matched[i]}`, `${chapter}<p><p/>`)\n }\n }\n }\n return content\n}\n\nfunction replaceJumpPage(content) {\n // 替换 Pixiv 跳转页面标记符号 [[jump:]]\n let matched = content.match(RegExp(/\\[jump:(\\d+)]/gm))\n if (matched) {\n for (let i in matched) {\n let page = matched[i].match(/\\d+/)\n content = content.replace(`${matched[i]}`, `\\n\\n跳转至第${page}节`)\n }\n }\n return content\n}\n\nfunction replaceJumpUrl(content) {\n // 替换 Pixiv 链接标记符号 [[jumpuri: > ]]\n let matched = content.match(RegExp(/\\[\\[jumpuri:(.*?)>(.*?)]]/gm))\n if (matched) {\n for (let i in matched) {\n let matched2 = matched[i].match(/\\[\\[jumpuri:(.*?)>(.*?)]]/m)\n let matchedText = matched2[0]\n let urlName = matched2[1].trim()\n let urlLink = matched2[2].trim()\n\n if (util.environment.IS_LEGADO_SIGMA) {\n content = content.replace(`${matchedText}`, `<usehtml><p> <a href=${urlLink}>${urlName}</a></p></usehtml>`)\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\nfunction replaceRb(content) {\n // 替换 Pixiv 注音标记符号 [[rb: > ]]\n let matched = content.match(RegExp(/\\[\\[rb:(.*?)>(.*?)]]/gm))\n if (matched) {\n for (let i in matched) {\n let matched2 = matched[i].match(/\\[\\[rb:(.*?)>(.*?)]]/m)\n let matchedText = matched2[0]\n let kanji = matched2[1].trim()\n let kana = matched2[2].trim()\n\n if (!util.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 // 阅读不支持 <ruby> <rt> 注音\n // content = content.replace(`${matchedText}`, `<ruby>${kanji}<rt>${kana}</rt></ruby>`)\n content = content.replace(`${matchedText}`, `${kanji}(${kana})`)\n }\n }\n }\n }\n return content\n}\n\nfunction getPollData(res, content) {\n // 添加投票\n if (res.pollData) {\n let poll = `📃 投票(✅${res.pollData.total}已投):\\n${res.pollData.question}\\n`\n res.pollData.choices.forEach(choice => {\n poll += `选项${choice.id}:${choice.text}(✅${choice.count})\\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 `<img src=\"${urlStampUrl(item.stampId)}\">`\n }\n return text.replace(/\\(([^)]+)\\)/g, (match, key) => {\n if (emoji.hasOwnProperty(key)) {\n return `<img src=\"${urlEmojiUrl(emoji[key])}\" >`\n }\n return match\n })\n}\n\nfunction formatComment(item, replyToName = null) {\n const content = processComment(item)\n const isMyComment = item.userId === String(getFromCache(\"pixiv:uid\"))\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(res, content) {\n if (!util.settings.SHOW_COMMENTS || res.commentCount === 0) return content\n\n const limit = 50\n let comments = [], commentUrls = [];\n let maxPage = Math.ceil(res.commentCount / limit)\n if (maxPage >= 2 && util.environment.IS_LEGADO) {\n for (let i = 0; i < maxPage; i++) {\n commentUrls.push(urlIP(urlNovelComments(res.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(res.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(res) {\n getNovelInfo(res) // 放入信息以便登陆界面使用\n let content = String(res.content)\n // let content = \"undefined\"\n if (content.includes(\"undefined\")) {\n return checkContent()\n }\n\n content = getCaptions(res, content)\n content = replaceUploadedImage(res, content)\n content = replacePixivImage(content)\n content = replaceNewPage(content)\n content = replaceChapter(content)\n content = replaceJumpPage(content)\n content = replaceJumpUrl(content)\n content = replaceRb(content)\n content = getPollData(res, content)\n content = getComment(res, 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.getNovelResFirst(result))\n})()",
"title": "",
"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 let resp = getAjaxJson(urlIP(urlNovelDetailed(novel.id))).body\n novel.authorId = novel.userId = resp.userId\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 clearCache() {\n let novel = getNovel()\n cache.delete(`${urlNovelUrl(novel.id)}`)\n cache.delete(`${urlNovelDetailed(novel.id)}`)\n cache.delete(`${urlSearchNovel(novel.title, 1)}`)\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 novelIds.forEach(novelId => {\n cache.delete(`${urlNovelUrl(novelId)}`)\n cache.delete(`${urlNovelDetailed(novelId)}`)\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 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 customButton(){\n java.open(\"login\")\n}\n// function longCustomButton(){\n// java.open(\"login\")\n// }\n//\n// function clickBookName() {\n// java.open(\"search\", null, book.name)\n// return true\n// }\n//\n// function longClickBookName() {\n// let novel = getNovel()\n// startBrowser(urlNovelUrl(novel.id), novel.title)\n// return true\n// }\n//\n// function clickAuthor() {\n// java.open(\"search\", null, book.author)\n// return true\n// }\n//\n// function longClickAuthor() {\n// let novel = getNovel()\n// startBrowser(urlUserUrl(novel.userId), novel.userName)\n// return true\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 (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction handlerFactory() {\n if (baseUrl.includes(\"https://cdn.jsdelivr.net\")) {\n return () => {updateSource(); return []}\n }\n if (baseUrl.includes(\"github\")) {\n return () => {startBrowser(baseUrl, \"\"); return []}\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(\"/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(\"/genre\")) {\n return handlerWatchList()\n }\n // 匹配 html 中的 json\n if (baseUrl.includes(\"/ranking\")) {\n return handlerRanking()\n }\n // 正则匹配网址内容\n if (baseUrl.includes(\"/marker_all\")) {\n return handlerRankingOld()\n }\n if (baseUrl.includes(\"/editors_picks\")) {\n return handlerRankingOld()\n }\n if (baseUrl.includes(\"/ajax/search/novels\")) {\n return handlerSearch()\n }\n if (baseUrl.startsWith(\"https://www.pixiv.net\")) {\n return handlerRankingOld()\n }\n else {\n return () => {startBrowser(baseUrl, \"\"); return []}\n }\n}\n\n\n// 推荐小说\nfunction handlerRecommend() {\n return () => {\n let res = JSON.parse(result)\n const recommend = res.body.page.recommend\n const novels = res.body.thumbnails.novel\n let nidSet = new Set(recommend.ids)\n // java.log(nidSet.size)\n let list = novels.filter(novel => nidSet.has(String(novel.id)))\n // java.log(`过滤结果:${JSON.stringify(list)}`)\n return util.formatNovels(util.handNovels(util.combineNovels(list)))\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.formatNovels(util.handNovels(res))\n }\n}\n\n//关注作者,小说委托,小说企划\nfunction handlerFollowLatest() {\n return () => {\n let res = JSON.parse(result)\n return util.formatNovels(util.handNovels(util.combineNovels(res.body.thumbnails.novel)))\n }\n}\n\n//推荐小说,最近小说\nfunction handlerDiscovery() {\n return () => {\n let res = JSON.parse(result)\n return util.formatNovels(util.handNovels(util.combineNovels(res.body.novels)))\n }\n}\n\n// 搜索标签\nfunction handlerSearch() {\n return () => {\n let res = JSON.parse(result)\n return util.formatNovels(util.handNovels(util.combineNovels(res.body.novel.data)))\n }\n}\n\n// 追更列表,热门分类\nfunction handlerWatchList() {\n return () => {\n let res = JSON.parse(result)\n return util.formatNovels(util.handNovels(res.body.thumbnails.novelSeries))\n }\n}\n\n// 排行榜,顺序相同\nfunction handlerRanking() {\n return () => {\n try {\n let resp = JSON.parse(result.match(/<script id=\"__NEXT_DATA__\"[^>]*>([\\s\\S]*?)<\\/script>/)[1])\n let novels = resp.props.pageProps.assign.display_a.rank_a\n // java.log(JSON.stringify(novels))\n return util.formatNovels(util.handNovels(util.combineNovels(novels)))\n } catch (e) {\n return []\n }\n }\n}\n\n// 书签,首页,编辑部推荐,顺序相同\nfunction handlerRankingOld() {\n if (globalThis.environment.IS_LEGADO) return handlerRankingAjaxAll()\n // else if (globalThis.environment.IS_SOURCE_READ) return handlerRankingWebview()\n else if (globalThis.environment.IS_SOURCE_READ) return handlerRankingAjax()\n else return []\n}\n\n// 书签,首页,编辑部推荐,顺序相同\nfunction handlerRankingAjaxAll() {\n return () => {\n let novelIds = [], novelUrls = []\n // let result = result + java.ajax(`${baseUrl}&p=2`) // 正则获取网址中的 novelId\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 novelUrls.push(urlNovelDetailed(novelId))\n }\n }\n // java.log(JSON.stringify(novelIds))\n let novels = getAjaxAllJson(novelUrls).map(resp => resp.body)\n return util.formatNovels(util.handNovels(util.combineNovels(novels)))\n }\n}\n\n// 书签,首页\nfunction handlerRankingWebview() {\n return () => {\n let novelIds = [] // 正则获取网址中的 novelId\n // let result = result + java.ajax(`${baseUrl}&p=2`) // 正则获取网址中的 novelId\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 // java.log(JSON.stringify(novelIds))\n let userNovels = getWebviewJson(\n urlNovelsDetailed(getFromCache(\"pixiv:uid\"), novelIds), html => {\n return (html.match(new RegExp(\">\\\\{.*?}<\"))[0].replace(\">\", \"\").replace(\"<\", \"\"))\n }).body\n return util.formatNovels(util.handNovels(util.combineNovels(Object.values(userNovels))))\n }\n}\n\n// 排行榜,书签,顺序相同\nfunction handlerRankingAjax() {\n return () => {\n let novels = [], novelIds = []\n // let result = result + java.ajax(`${baseUrl}&p=2`) // 正则获取网址中的 novelId\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 // java.log(urlNovelDetailed(novelId))\n let res = getAjaxJson(urlNovelDetailed(novelId))\n if (res.error !== true) {\n novels.push(res.body)\n } else {\n java.log(JSON.stringify(res))\n }\n }\n }\n return util.formatNovels(util.handNovels(util.combineNovels(novels)))\n }\n}\n\n(() => {\n return handlerFactory()()\n})()",
"bookUrl": "detailedUrl",
"coverUrl": "coverUrl",
"intro": "description",
"kind": "tags",
"lastChapter": "latestChapter",
"name": "title",
"wordCount": "textCount"
},
"ruleSearch": {
"author": "userName",
"bookList": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\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\nfunction getUserIdOnline(full) {\n let userName = String(java.get(\"keyword\"))\n let page = Number(java.get(\"page\"))\n // cache.delete(urlSearchUser(userName, page, full))\n let resp = getAjaxParseJson(urlSearchUser(userName, page, full), html => {\n // java.log(urlIP(urlSearchUser(userName, page, full)))\n return JSON.parse(html.match(/<script id=\"__NEXT_DATA__\"[^>]*>([\\s\\S]*?)<\\/script>/)[1])\n }\n )\n\n let novels = Object.values(JSON.parse(resp.props.pageProps.serverSerializedPreloadedState).thumbnail.novel)\n let userIds = Array.from(new Set(novels.map(novel => novel.userId)))\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 getUserNovels() {\n let novels = []\n let page = Number(java.get(\"page\"))\n let uidList = getUserIdCache()\n if (!uidList) [uidList, novels] = getUserIdOnline()\n\n if(uidList.length === 0 || uidList.length >=2 ) return novels\n else if(uidList.length === 1 ) {\n let uid = uidList[0]\n let resp = getAjaxJson(urlIP(urlUserAllWorks(uid)), true)\n // java.log(urlIP(urlUserAllWorks(id)))\n\n // 获取系列小说,与 util.handnovels 系列详情兼容\n let seriesIds = []\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 // 获取单篇小说\n let novelIds = Object.keys(resp.body.novels)\n novelIds = novelIds.filter(novelId => (!seriesNovelIds.includes(novelId)))\n novelIds = novelIds.reverse().slice((page - 1) * 20, page * 20)\n // java.log(`真单篇的小说ID:${JSON.stringify(novelIds)}`)\n // java.log(JSON.stringify(novelIds.length))\n\n if (globalThis.environment.IS_LEGADO) {\n let novelUrls = novelIds.map(novelId => urlIP(urlNovelDetailed(novelId)))\n // java.log(JSON.stringify(novelUrls))\n // cache.delete(novelUrls)\n novels = novels.concat(getAjaxAllJson(novelUrls).map(resp => resp.body))\n }\n\n if (util.environment.IS_SOURCEREAD) {\n novelIds.forEach(novelId => {\n // java.log(urlIP(urlNovelDetailed(novelId)))\n let res = getAjaxJson(urlIP(urlNovelDetailed(novelId)))\n if (res.error !== true) {\n novels.push(res.body)\n } else {\n java.log(JSON.stringify(res))\n }\n })\n }\n }\n \n util.debugFunc(() => {\n java.log(`获取用户搜索小说结束`)\n })\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 (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\n(() => {\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(getUserNovels())\n } else if (keyword.startsWith(\"#\")) {\n java.put(\"keyword\", keyword.slice(1))\n novels = novels.concat(getSeries())\n novels = novels.concat(getNovels())\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 java.log(`👤 粗略搜索作者:${keyword}`)\n novels = novels.concat(getUserIdOnline()[1])\n } else {\n novels = novels.concat(getSeries())\n novels = novels.concat(getNovels())\n if (util.settings.CONVERT_CHINESE) novels = novels.concat(getConvertNovels())\n }\n // java.log(JSON.stringify(novels))\n // 返回空列表中止流程\n if (novels.length === 0) {\n return []\n }\n return novelFilter(util.formatNovels(util.handNovels(novels)))\n})()",
"bookUrl": "detailedUrl",
"checkKeyWord": "#测试页面",
"coverUrl": "coverUrl",
"intro": "description",
"kind": "tags",
"lastChapter": "latestChapter",
"name": "title",
"wordCount": "textCount"
},
"ruleToc": {
"chapterList": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction 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 resp = getAjaxJson(urlIP(urlSeriesNovels(seriesID, limit, lastIndex)), true)\n res = resp.body.thumbnails.novel\n // res = resp.body.page.seriesContents\n res.forEach(v => {\n v.title = v.title.trim()\n v.chapterUrl = urlIP(urlNovel(v.id))\n novelIds.push(v.id)\n if (v.updateDate !== undefined) {\n v.updateDate = timeTextFormat(v.createDate)\n v.chapterInfo = `${v.updateDate} ${v.textCount}字`\n } else {\n v.updateDate = java.timeFormat(v.uploadTimestamp)\n v.chapterInfo = `${v.updateDate} ${v.textLength}字`\n }\n util.debugFunc(() => {\n java.log(`${v.title}`)\n })\n })\n return res;\n }\n\n if (!util.settings.SHOW_UPDATE_TIME) {\n returnList = getAjaxJson(urlIP(urlSeriesNovelsTitles(seriesID)), true).body\n returnList.forEach(v => {\n v.title = v.title.trim()\n v.chapterUrl = urlIP(urlNovel(v.id))\n novelIds.push(v.id)\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 // 放入小说信息以便登陆界面使用\n let novel = source.getLoginInfoMap()\n if (novel === undefined) novel = getFromCacheObject(\"novel\")\n novel.novelIds = novelIds\n putInCacheObject(`novelIds${seriesID}`, novelIds, cacheSaveSeconds)\n // java.log(JSON.stringify(returnList))\n source.putLoginInfo(JSON.stringify(novel))\n putInCacheObject(\"novel\", novel)\n return returnList\n}\n\n(function (res) {\n res = util.getNovelResSeries(result)\n if (res.firstNovelId === undefined || res.seriesNavData === null) {\n return oneShotHandler(res)\n } else {\n return seriesHandler(res)\n }\n})()",
"chapterName": "title",
"chapterUrl": "chapterUrl",
"isPay": "",
"isVip": "",
"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/04/04)\n\n书源版本:265\n使用说明:📌阅读 Plus 3.26.0129 版本可用\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旧版书源:\nhttps://cdn.jsdelivr.net/gh/DowneyRem/PixivSource@191/pixiv.json\nhttps://raw.githubusercontent.com/DowneyRem/PixivSource/191/pixiv.json\n\n规则订阅:import 订阅源\nhttps://cdn.jsdelivr.net/gh/DowneyRem/PixivSource@main/import.json\nhttps://raw.githubusercontent.com/DowneyRem/PixivSource/main/import.json\n\n⚙️ 书源设置:\n设置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": false,
"customOrder": 2,
"enabled": false,
"enabledCookieJar": true,
"enabledExplore": true,
"eventListener": false,
"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(\\\"pixiv:uid\\\")}}/illusts/bookmarks?tag=&offset={{(page-1)*24}}&limit=24&rest=show&lang=zh\"},\n {\"㊙️ 收藏\": \"https://www.pixiv.net/ajax/user/{{cache.get(\\\"pixiv:uid\\\")}}/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(\"<!DOCTYPE html>\")\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}\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 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<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>更新 ${source.bookSourceName} 书源</title>\n <style> \n table { text-align: center; margin: 0 auto; } .ann { display: flex; justify-content: center; align-items: center; height: 5vh; } \n button { background-color: rgb(76, 175, 80); color: white; border: none; border-radius: 4px; height: 6vh; width: 30vw; overflow: hidden; } \n button span { cursor: pointer; display: inline-block; position: relative; transition: 0.4s; } \n button span:after { content: '>'; position: absolute; opacity: 0; top: 0; right: 30px; transition: 0.2s; } \n button:active span { padding-right: 20px; } \n button:active span:after { opacity: 1; right: -40px; }\n </style>\n</head>\n\n<body>\n <table border=\"1\" cellspacing=\"0\">\n <th colspan=\"2\"> ${source.bookSourceName} 书源 \n <a href=\"https://pixivsource.pages.dev/FurryNovel\">🔰 使用指南</a>\n || <a href=\"https://pixivsource.pages.dev/Sponsor\">❤️ 赞助开发</a>\n </th>\n <tr>\n <td>☁️ 远程版本:${onlineSource.bookSourceComment.split(\"\\n\")[2].replace(\"书源版本:\", \"\")}</td>\n <td>📆 更新:${timeFormat(onlineSource.lastUpdateTime)}</td>\n </tr>\n <tr>\n <td>📥 本地版本:${source.bookSourceComment.split(\"\\n\")[2].replace(\"书源版本:\", \"\")}</td>\n <td>📆 更新:${timeFormat(source.lastUpdateTime)}</td>\n </tr> \n <tr><td colspan=\"2\" style=\"text-align: left;\">${comment.slice(3, 9).join(\"<br>\")}</td></tr>\n <tr><td colspan=\"2\" style=\"text-align: left;\">${comment.slice(comment.length-3, comment.length).join(\"<br>\")}</td></tr>\n </table>\n \n <table border=\"0\" cellspacing=\"20\">\n <th colspan=\"2\"> 更新 ${source.bookSourceName} 书源 </th>\n <tr><td><div class=\"ann\">\n <a href=\"legado://import/importonline?src=https://cdn.jsdelivr.net/gh/DowneyRem/PixivSource@main/${sourceName}.json\">\n <button><span>更新书源<br>(Jsdelivr CDN)</span></button>\n </a></div></td>\n \n <td><div class=\"ann\">\n <a href=\"legado://import/importonline?src=https://cdn.jsdelivr.net/gh/DowneyRem/PixivSource@main/btsrk.json\">\n <button><span>更新订阅<br>(Jsdelivr CDN)</span></button>\n </a></div></td>\n </tr>\n \n <tr><td><div class=\"ann\">\n <a href=\"legado://import/importonline?src=https://raw.githubusercontent.com/DowneyRem/PixivSource/main/${sourceName}.json\">\n <button><span>书源链接<br>(GitHub)</span></button>\n </a></div></td>\n \n <td><div class=\"ann\">\n <a href=\"legado://import/importonline?src=https://raw.githubusercontent.com/DowneyRem/PixivSource/main/btsrk.json\">\n <button><span>订阅链接<br>(GitHub)</span></button>\n </a></div></td>\n </tr>\n \n <tr><td><div class=\"ann\">\n <a href=\"legado://import/importonline?src=https://codeberg.org/DowneyRem/PixivSource/raw/branch/main/${sourceName}.json\">\n <button><span>备用书源链接<br>(Codeberg)</span></button>\n </a></div></td>\n \n <td><div class=\"ann\">\n <a href=\"legado://import/importonline?src=https://codeberg.org/DowneyRem/PixivSource/raw/branch/main/btsrk.json\">\n <button><span>备用订阅链接<br>(Codeberg)</span></button>\n </a></div></td>\n </tr>\n </table>\n</body>\n</html>`\n java.startBrowser(`data:text/html;charset=utf-8;base64, ${java.base64Encode(htm)}`, '更新书源')\n return []\n}",
"lastUpdateTime": 1775232000251,
"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://loyc.xyz/c/legado.html#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://loyc.xyz/c/legado.html#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(\"pixiv:uid\")\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.join(\",\")\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(\"<!DOCTYPE html>\")\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(\"pixiv:uid\")\n let uid = getFromCache(\"pixiv:uid\")\n if (!uid && isLogin()) {\n let html = java.ajax(\"https://www.pixiv.net/\")\n uid = html.match(/user_id:'(\\d+)'/)[1]\n putInCache(\"pixiv:uid\", uid)\n }\n return uid\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 {\"⚙️ 当前设置\": \"showSettings()\" },\n {\"🔧 默认设置\": \"setDefaultSettingsLoginUrl()\" },\n {\"🀄 🚫 繁简通搜\": \"editSettings('CONVERT_CHINESE')\" },\n\n {\"🔗 🚫 原始链接\": \"editSettings('SHOW_ORIGINAL_LINK')\" },\n {\"🖼️ 🚫 常规质量\": \"editSettings('SHOW_CAPTIONS')\" },\n {\"🚫 🐞 调试模式\": \"editSettings('DEBUG')\" },\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(\"pixiv:uid\")\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": {
"author": "userName",
"canReName": "true",
"coverUrl": "coverUrl",
"init": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction 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})()",
"intro": "description",
"kind": "tags",
"lastChapter": "latestChapter",
"name": "title",
"tocUrl": "catalogUrl"
},
"ruleContent": {
"content": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction 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(`<img src=\"${urlCoverUrl(illustLink)}\">`)\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})()",
"title": "",
"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 (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction handlerFactory() {\n if (baseUrl.includes(\"https://cdn.jsdelivr.net\")) {\n return () => {updateSource(); 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(\"pixiv:uid\"), 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": ""
},
"ruleSearch": {
"author": "userName",
"bookList": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction 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": ""
},
"ruleToc": {
"chapterList": "@js:\nvar util = objParse(String(java.get(\"util\")))\n\nfunction objParse(obj) {\n return JSON.parse(obj, (n, v) => {\n if (typeof v == \"string\" && v.match(\"()\")) {\n return eval(`(${v})`)\n }\n return v;\n })\n}\n\nfunction 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
}
]