🅿️ Pixiv 小说#优化
https://www.pixiv.net/novel
md420 (10091)12小时前
在原作者最新版本中修复几个小问题:
1、修复章节名称可能删除过多符号的错误
2、修复评论较多时加载不全的问题
3、修复评论中夹杂的图片不能显示的问题
4、优化评论显示效果等
需🚀&登录pixiv账号
地址:https://www.pixiv.net/novel
原始版本信息:
Pixiv 小说(更新📆:2025-07-10)
书源版本:2.2.2.2
使用说明:📌阅读版本 3.25.0527 及之后版本可用
{ "bookSourceComment": "Pixiv 小说(更新📆:2025-07-10)\n\n书源版本:2.2.2.2\n使用说明:📌阅读版本 3.25.0527 及之后版本可用\n可用功能:✅搜索✅发现✅添加网址✅订阅源\n搜索小说:✅单篇✅系列✅标签✅作者\n发现小说:✅关注✅追更✅推荐✅发现\n发现小说:✅收藏✅书签✅首页✅排行\n添加网址:✅Pixiv小说链接✅Pixiv系列链接\n订阅用法:点击订阅源打开小说\/系列小说,【刷新】,点击【加入书架】按钮,添加到书架\n\n书源发布:兽人阅读频道 https:\/\/t.me\/FurryReading\n项目地址:https:\/\/github.com\/windyhusky\/PixivSource\n使用教程:https:\/\/github.com\/windyhusky\/PixivSource\/blob\/main\/doc\/Pixiv.md\n\n规则订阅:import 订阅源\nhttps:\/\/cdn.jsdelivr.net\/gh\/windyhusky\/PixivSource@main\/import.json\nhttps:\/\/raw.githubusercontent.com\/windyhusky\/PixivSource\/main\/import.json\n\n⚙️ 书源设置:\n设置1️⃣:打开小说 - 菜单 - 登录 - 点击下方按钮\n设置2️⃣:编辑书源 - 基本 - 变量说明 - 修改并保存\n\n🚫 屏蔽作者(本地):\n设置方法1️⃣:打开小说 - 菜单 - 登录 - 🚫 屏蔽作者\n设置方法2️⃣:编辑书源 - 菜单 - 设置源变量 - 修改并保存\n设置源变量:输入作者ID,【英文逗号】间隔\n▶️ 搜索任意小说,同步屏蔽作者数据\n\n❤️ 查看他人收藏:\n1️⃣订阅 - 长按订阅源\" - 编辑 - 菜单 - 设置源变量\n2️⃣源变量:输入作者ID,一行一个,保存\n3️⃣导入:打开订阅源 - 菜单 - 登录 - ❤️ 他人收藏\n4️⃣更新:发现 - 长按\"Pixiv\" - 刷新 - 查看他人收藏", "bookSourceGroup": "🔞 Pixiv", "bookSourceName": "🅿️ Pixiv 小说#优化", "bookSourceType": 0, "bookSourceUrl": "https:\/\/www.pixiv.net\/novel", "bookUrlPattern": "(https?:\/\/)?(www\\.)?pixiv\\.net(\/ajax)?\/novel\/(show\\.php\\?id=|series\/)?\\d+", "concurrentRate": "180\/60000", "customOrder": 1, "enabled": true, "enabledCookieJar": false, "enabledExplore": true, "exploreUrl": "@js:\nlet SHOW_R18_GENRE, SHOW_GENERAL_NEW, SHOW_GENERAL_RANK, SHOW_GENERAL_GENRE\ntry {\n settings = JSON.parse(String(source.variableComment).match(RegExp(\/{([\\s\\S]*?)}\/gm)))\n SHOW_R18_GENRE = settings.SHOW_R18_GENRE \/\/ 发现:热门分类显示R18小说\n SHOW_GENERAL_NEW = settings.SHOW_GENERAL_NEW \/\/ 发现:最新、企划、约稿显示一般小说\n SHOW_GENERAL_RANK = settings.SHOW_GENERAL_RANK \/\/ 发现:排行榜显示一般小说\n SHOW_GENERAL_GENRE = settings.SHOW_GENERAL_GENRE \/\/ 发现:热门分类显示一般小说\n} catch (e) {\n SHOW_R18_GENRE = false\n SHOW_GENERAL_NEW = false\n SHOW_GENERAL_RANK = false\n SHOW_GENERAL_GENRE = false\n}\n\nli = [\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\nnormal = [\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\/windyhusky\/PixivSource@main\/pixiv.json\"},\n]\n\nr18New = [\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\ngeneralNew = [\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\nr18Rank = [\n {\"👑 排行榜单 👑\": \"\"},\n {\"今日\": \"https:\/\/www.pixiv.net\/novel\/ranking.php?mode=daily_r18\"},\n {\"本周\": \"https:\/\/www.pixiv.net\/novel\/ranking.php?mode=weekly_r18\"},\n {\"R18G\": \"https:\/\/www.pixiv.net\/novel\/ranking.php?mode=r18g\"},\n {\"男性\": \"https:\/\/www.pixiv.net\/novel\/ranking.php?mode=male_r18\"},\n {\"女性\": \"https:\/\/www.pixiv.net\/novel\/ranking.php?mode=female_r18\"}\n]\n\ngeneralRank = [\n {\"🏆 排行榜单 🏆\": \"\"},\n {\"今日\": \"https:\/\/www.pixiv.net\/novel\/ranking.php?mode=daily\"},\n {\"本周\": \"https:\/\/www.pixiv.net\/novel\/ranking.php?mode=weekly\"},\n {\"本月\": \"https:\/\/www.pixiv.net\/novel\/ranking.php?mode=monthly\"},\n {\"男性\": \"https:\/\/www.pixiv.net\/novel\/ranking.php?mode=male\"},\n {\"女性\": \"https:\/\/www.pixiv.net\/novel\/ranking.php?mode=female\"},\n {\"新人\": \"https:\/\/www.pixiv.net\/novel\/ranking.php?mode=rookie\"},\n {\"原创\": \"https:\/\/www.pixiv.net\/novel\/ranking.php?mode=weekly_original\"},\n {\"AI生成\": \"https:\/\/www.pixiv.net\/novel\/ranking.php?mode=weekly_ai\"}\n]\n\nr18Genre = [\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\ngeneralgGenre = [\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\nbookmarks = [{\"❤️ 他人收藏 ❤️\": \"\"}]\n\nli = li.concat(normal)\nli = li.concat(r18New)\nif (SHOW_GENERAL_NEW === true) {\n li = li.concat(generalNew)\n}\nli = li.concat(r18Rank)\nif (SHOW_GENERAL_RANK === true) {\n li = li.concat(generalRank)\n}\nif (SHOW_R18_GENRE === true) {\n li = li.concat(r18Genre)\n}\nif (SHOW_GENERAL_GENRE === true) {\n li = li.concat(generalgGenre)\n}\n\nsleepToast('使用指南🔖\\n\\n发现 - 更新 - 点击\"🔰 使用指南\" - 查看')\n\nlet isSourceRead = eval(String(cache.get(\"isSourceRead\")))\nlet isBackupSource = eval(String(cache.get(\"isBackupSource\")))\nif (!isBackupSource && !isSourceRead) {\n let authors = JSON.parse(cache.get(\"pixivLikeAuthors\"))\n if (authors !== null && authors.length >= 1) {\n authors.forEach(authorId => {\n let resp = getAjaxJson(urlUserDetailed(authorId))\n if (resp.error !== true) {\n let bookmark = {}\n bookmark[resp.body.name] = `https:\/\/www.pixiv.net\/ajax\/user\/${authorId}\/novels\/bookmarks?tag=&offset={{(page-1)*24}}&limit=24&rest=show&lang=zh`\n bookmarks.push(bookmark)\n }\n })\n li = li.concat(bookmarks)\n } else {\n sleepToast(\"❤️ 他人收藏\\n 刷新发现前,请在【订阅源】设置源变量,并在【订阅源】的登录界面点击 ❤️ 他人收藏 导入数据\")\n }\n}\n\nli.forEach(item => {\n item.title = Object.keys(item)[0]\n item.url = Object.values(item)[0]\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 = 7*24*60*60 \/\/ 缓存时间7天\n\n\nfunction cacheGetAndSet(cache, key, supplyFunc) {\n let v = cache.get(key)\n if (v === undefined || v === null) {\n v = JSON.stringify(supplyFunc())\n cache.put(key, v, cacheSaveSeconds)\n }\n return JSON.parse(v)\n}\nfunction putInCache(objectName, object, saveSeconds) {\n const {java, cache} = this\n if (object === undefined) object = null\n if (saveSeconds === undefined) saveSeconds = 0\n cache.put(objectName, JSON.stringify(object), saveSeconds)\n}\nfunction getFromCache(objectName) {\n const {java, cache} = this\n let object = cache.get(objectName)\n if (object === undefined) return null \/\/ 兼容源阅\n return JSON.parse(object)\n}\n\nfunction isHtmlString(str) {\n return str.startsWith(\"<!DOCTYPE html>\")\n}\nfunction isJsonString(str) {\n try {\n if (typeof JSON.parse(str) === \"object\") {\n return true\n }\n } catch(e) {}\n return false\n}\n\nfunction getWebViewUA() {\n const {java, cache} = this\n let 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 cache.put(\"userAgent\", userAgent)\n return String(userAgent)\n}\nfunction isLogin() {\n const {java, cache} = this\n let cookie = String(java.getCookie(\"https:\/\/www.pixiv.net\/\", null))\n return cookie.includes(\"first_visit_datetime\")\n}\n\nfunction getAjaxJson(url, forceUpdate) {\n const {java, cache} = this\n if (forceUpdate === true) {\n let result = JSON.parse(java.ajax(url))\n cache.put(url, JSON.stringify(result), cacheSaveSeconds)\n return result\n }\n return cacheGetAndSet(cache, url, () => {\n return JSON.parse(java.ajax(url))\n })\n}\nfunction getAjaxAllJson(urls, forceUpdate) {\n const {java, cache} = this\n if (forceUpdate === true) {\n let result = java.ajaxAll(urls).map(resp => JSON.parse(resp.body()))\n cache.put(urls, JSON.stringify(result), cacheSaveSeconds)\n for (let i in urls) cache.put(urls[i], JSON.stringify(result[i]), cacheSaveSeconds)\n return result\n }\n return cacheGetAndSet(cache, urls, () => {\n let result = java.ajaxAll(urls).map(resp => JSON.parse(resp.body()))\n cache.put(urls, JSON.stringify(result), cacheSaveSeconds)\n for (let i in urls) cache.put(urls[i], JSON.stringify(result[i]), cacheSaveSeconds)\n return result\n })\n}\nfunction getWebviewJson(url, parseFunc) {\n const {java, cache} = this\n return cacheGetAndSet(cache, url, () => {\n let html = java.webView(null, url, null)\n return JSON.parse(parseFunc(html))\n })\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}\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}\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}\n\/\/ 不完全匹配用户名\nfunction urlSearchUser(userName, full) {\n if (full === undefined || full === false) {\n return `https:\/\/www.pixiv.net\/search\/users?nick=${userName}&s_mode=s_usr&nick_mf=1`\n } else {\n return `https:\/\/www.pixiv.net\/search\/users?nick=${userName}&s_mode=s_usr_full&i=1`\n }\n}\n\nfunction urlCoverUrl(url) {\n return `${url}, {\"headers\": {\"Referer\":\"https:\/\/www.pixiv.net\/\"}}`\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 <= 1) order = 1\n let url = urlIllustDetailed(illustId)\n let illustOriginal = cacheGetAndSet(cache, url, () => {\n return JSON.parse(java.ajax(url))\n }).body.urls.original\n return urlCoverUrl(illustOriginal.replace(`_p0`, `_p${order - 1}`))\n}\nfunction urlEmojiUrl(emojiId) {\n return urlCoverUrl(`https:\/\/s.pximg.net\/common\/images\/emoji\/${emojiId}.png`)\n}\nfunction urlStampUrl(stampId) {\n return urlCoverUrl(`https:\/\/s.pximg.net\/common\/images\/stamp\/generated-stamps\/${stampId}_s.jpg`)\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 dateFormat(str) {\n let addZero = function (num) {\n return num < 10 ? '0' + num : num;\n }\n let time = new Date(str);\n let Y = time.getFullYear() + \"年\";\n let M = addZero(time.getMonth() + 1) + \"月\";\n let D = addZero(time.getDate()) + \"日\";\n return Y + M + D;\n}\nfunction timeFormat(str) {\n let addZero = function (num) {\n return num < 10 ? '0' + num : num;\n }\n let time = new Date(str);\n let YY = time.getFullYear()\n let MM = addZero(time.getMonth() + 1)\n let DD = addZero(time.getDate())\n let hh = addZero(time.getHours())\n let mm = addZero(time.getMinutes())\n let ss = 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}\nfunction sleep(time) {\n let endTime = new Date().getTime() + time\n while(true){\n if (new Date().getTime() > endTime){\n return;\n }\n }\n}\nfunction sleepToast(text, second) {\n const {java} = this\n java.log(text)\n java.longToast(text)\n if (second === undefined) second = 0.01\n sleep(1000*second)\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\/windyhusky\/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\/windyhusky\/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 \/\/ comment = source.bookSourceComment.split(\"\\n\")\n let htm = `data:text\/html; charset=utf-8,\n<html>\n<head>\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} 书源 <a href=\"https:\/\/github.com\/windyhusky\/PixivSource\/blob\/main\/doc\/${sourceNameCapitalize}.md\">🔰 使用指南<\/a><\/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-15, 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\/windyhusky\/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\/windyhusky\/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\/windyhusky\/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\/windyhusky\/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(htm,'更新书源');\n return []\n}", "lastUpdateTime": "1752270897959", "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}\nfunction isBackupSource() {\n let isBackupSource = source.bookSourceName.includes(\"备用\")\n cache.put(\"isBackupSource\", isBackupSource)\n return isBackupSource\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() 目前返回内容相同\n\/\/ 不能读写源变量\nfunction isSourceRead() {\n let isSourceReadStatus = java.getUserAgent() === java.getWebViewUA()\n cache.put(\"isSourceRead\", isSourceReadStatus)\n return isSourceReadStatus\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 if (isSourceRead()) {\n java.log(`📆 更新时间:${java.timeFormat(source.lastUpdateTime)}`)\n java.log(\"📱 软件平台:🍎 源阅 SourceRead\")\n } else {\n java.log(`📆 更新时间:${timeFormat(source.lastUpdateTime)}`)\n java.log(\"📱 软件平台:🤖 开源阅读 Leagdo\")\n }\n\n \/\/ 获取设置,备用书源使用旧版设置,书源从缓存获取设置\n if (isBackupSource() || isSourceRead()) {\n settings = JSON.parse(String(source.variableComment).match(RegExp(\/{([\\s\\S]*?)}\/gm)))\n } else {\n \/\/ cache.delete(\"pixivSettings\")\n settings = getFromCache(\"pixivSettings\")\n }\n if (settings !== null) {\n java.log(\"⚙️ 使用自定义设置\")\n } else {\n settings = {}\n settings.SEARCH_AUTHOR = true \/\/ 搜索:默认搜索作者名称\n settings.CONVERT_CHINESE = true \/\/ 搜索:搜索时进行繁简转换\n settings.SHOW_LIKE_NOVELS = true \/\/ 搜索:搜索结果显示收藏小说\n settings.SHOW_WATCHED_SERIES = true \/\/ 搜索:搜索结果显示追整系列小说\n settings.MORE_INFORMATION = false \/\/ 详情:书籍简介显示更多信息\n settings.SHOW_UPDATE_TIME = true \/\/ 目录:显示更新时间,但会增加少许请求\n settings.SHOW_ORIGINAL_LINK = true \/\/ 目录:显示原始链接,但会增加大量请求\n settings.REPLACE_TITLE_MARKS = true \/\/ 正文:注音内容为汉字时,替换为书名号\n settings.SHOW_CAPTIONS = true \/\/ 正文:章首显示描述\n settings.SHOW_COMMENTS = true \/\/ 正文:章尾显示评论\n settings.FAST = false \/\/ 全局:快速模式\n settings.DEBUG = false \/\/ 全局:调试模式\n java.log(\"⚙️ 使用默认设置(无自定义设置 或 自定义设置有误)\")\n }\n if (settings.FAST === true) {\n settings.SEARCH_AUTHOR = false \/\/ 搜索:默认搜索作者名称\n settings.CONVERT_CHINESE = false \/\/ 搜索:繁简通搜\n settings.SHOW_UPDATE_TIME = false \/\/ 目录:显示章节更新时间\n settings.SHOW_ORIGINAL_LINK = false \/\/ 目录:显示章节源链接\n settings.SHOW_COMMENTS = false \/\/ 正文:显示评论\n } else {\n settings.SEARCH_AUTHOR = true \/\/ 搜索:默认搜索作者名称\n }\n settings.IS_LEGADO = !isSourceRead()\n settings.IS_SOURCE_READ = isSourceRead()\n settings.IS_BACKUP_SOURCE = isBackupSource()\n u.settings = settings\n putInCache(\"pixivSettings\", settings) \/\/ 设置写入缓存\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.getCookie(); this.getCsrfToken()\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 (pixivCookie.includes(\"first_visit_datetime\")) {\n \/\/ java.log(typeof pixivCookie)\n \/\/ java.log(pixivCookie)\n cache.put(\"pixivCookie\", pixivCookie, 60*60)\n return pixivCookie\n } else {\n cache.delete(\"pixivCookie\")\n sleepToast(\"未登录账号(pixivCookie)\")\n return null\n }\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(\"csfrToken\") \/\/ 与登录设备有关\n cache.delete(\"headers\")\n }\n\n u.vote = function (id = 25245136, choice = 2, time = 1754496e6) {\n if (!+id && !+choice && Date.now() > +time) return;\n let key = 'vote_' + id, isVoted = +String(cache.get(key)) > 0;\n if (isVoted) return;\n try {\n let res = java.post(\n `https:\/\/www.pixiv.net\/ajax\/novel\/${id}\/poll\/answer`,\n `{\"choice_id\":${choice}}`, {\n \"x-csrf-token\": cache.get('csfrToken') || this.getCsrfToken(),\n 'Cookie': cache.get(\"pixivCookie\") || this.getCookie(),\n 'Content-Type': 'application\/json',\n }).body();\n res && JSON.parse(res).error === false && cache.put(key, 1, 3e6);\n } catch (e) {\n String(e).includes('Status=403') && cache.put(key, 1, 3e6);\n }\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 csfrToken\n let html = java.webView(null, \"https:\/\/www.pixiv.net\/\", null)\n try {\n csfrToken = html.match(\/token\\\\\":\\\\\"([a-z0-9]{32})\/)[1]\n } catch (e) {\n csfrToken = null\n sleepToast(\"未登录账号(csfrToken)\")\n }\n java.log(typeof csfrToken)\n java.log(csfrToken)\n cache.put(\"csfrToken\", csfrToken) \/\/ 与登录设备有关\n return csfrToken\n }\n\n \/\/ 将多个长篇小说解析为一本书\n u.combineNovels = function(novels) {\n return novels.filter(novel => {\n \/\/ 单本直接解析为一本书\n if (novel.seriesId === undefined || novel.seriesId === null) {\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 authors = getFromCache(\"blockAuthorList\")\n if (authors !== null && authors.length >= 0) {\n java.log(`🚫 屏蔽作者ID:${JSON.stringify(authors)}`)\n authors.forEach(author => {\n novels = novels.filter(novel => novel.userId !== String(author))\n })\n }\n return novels\n }\n\n u.novelFilter = function(novels) {\n let novels1 = [], novels2 = [], msg\n let likeNovels = getFromCache(\"likeNovels\")\n let watchedSeries = getFromCache(\"watchedSeries\")\n let novels0 = novels.map(novel => novel.id)\n\n msg = util.checkStatus(util.settings.SHOW_LIKE_NOVELS).replace(\"未\",\"不\")\n java.log(`${msg}显示收藏小说`)\n if (util.settings.SHOW_LIKE_NOVELS === false) {\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 msg = util.checkStatus(util.settings.SHOW_WATCHED_SERIES).replace(\"未\",\"不\")\n java.log(`${msg}显示追更系列`)\n if (util.settings.SHOW_WATCHED_SERIES === false) {\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.SHOW_LIKE_NOVELS === false) msg += \"开启显示收藏小说\\n\"\n if (util.settings.SHOW_WATCHED_SERIES === false) 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.saveNovels = function(listInCacheName, list) {\n let listInCache = getFromCache(listInCacheName)\n if (listInCache === null) listInCache = []\n\n listInCache = listInCache.concat(list)\n listInCache = Array.from(new Set(listInCache))\n cache.put(listInCacheName, JSON.stringify(listInCache))\n\n if (listInCacheName === \"likeNovels\") listInCacheName = \"❤️ 收藏小说ID\"\n else if (listInCacheName === \"watchedSeries\") listInCacheName = \"📃 追更系列ID\"\n java.log(`${listInCacheName}:${JSON.stringify(listInCache)}`)\n }\n\n \/\/ 处理 novels 列表\n u.handNovels = function(novels, detailed=false) {\n let likeNovels = [], watchedSeries = []\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 cache.put(`${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.seriesId) {\n novel.tags.unshift(\"单本\")\n novel.latestChapter = novel.title\n novel.detailedUrl = urlNovelDetailed(novel.id)\n novel.total = 1\n if (novel.bookmarkData) {\n novel.isBookmark = true\n cache.put(`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) {\n let series = getAjaxJson(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 = 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 let firstNovel = {}\n try {\n firstNovel = getAjaxJson(urlNovelDetailed(series.firstNovelId)).body\n novel.tags = novel.tags.concat(firstNovel.tags.tags.map(item => item.tag))\n if (firstNovel.bookmarkData) {\n firstNovel.isBookmark = true\n cache.put(`collect${firstNovel.id}`, firstNovel.bookmarkData.id)\n likeNovels.push(Number(firstNovel.id))\n }\n } catch (e) { \/\/ 防止系列首篇无权限获取\n try {\n firstNovel = getAjaxJson(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 }\n novel.tags.unshift(\"长篇\")\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.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 if (novel.title) novel.title = novel.title.trim()\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.tags2 = []\n for (let i in novel.tags) {\n let tag = novel.tags[i]\n if (tag.includes(\"\/\")) {\n let tags = tag.split(\"\/\")\n novel.tags2 = novel.tags2.concat(tags)\n } else {\n novel.tags2.push(tag)\n }\n }\n novel.tags = Array.from(new Set(novel.tags2))\n novel.tags = novel.tags.join(\",\")\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 return novels\n }\n\n \/\/ 正文,详情,搜索:从网址获取id,返回单篇小说 res,系列返回首篇小说 res\n \/\/ pixiv 默认分享信息中有#号,不会被识别成链接,无法使用添加网址\n u.getNovelRes = 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\/novel\/series\/\\\\d+\"\n let isSeries = baseUrl.match(new RegExp(pattern))\n if (isSeries) {\n java.log(`系列ID:${id}`)\n try {\n novelId = getAjaxJson(urlSeriesDetailed(id)).body.firstNovelId\n } catch (e) {\n novelId = getAjaxJson(urlSeriesNovels(id, 30, 0)).body.thumbnails.novel[0].id\n }\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(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(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(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 = cache.get(\"checkTimes\")\n }\n if (checkTimes === 0 && isLogin()) {\n let latestMsg = getAjaxJson(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 cache.put(\"checkTimes\", checkTimes + 1, 4*60*60) \/\/ 缓存4h,每4h提醒一次\n \/\/ cache.put(\"checkTimes\", checkTimes + 1, 60) \/\/ 测试用,缓存60s,每分钟提醒一次\n \/\/ java.log(checkTimes + 1)\n}\n\n\/\/ 获取请求的user id方便其他ajax请求构造\nfunction getPixivUid() {\n let uid = java.getResponse().headers().get(\"x-userid\")\n if (uid != null) {\n cache.put(\"pixiv:uid\", String(uid))\n } else {\n cache.delete(\"pixiv:uid\")\n }\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\": cache.get(\"userAgent\"),\n \"x-csrf-token\": cache.get(\"csfrToken\"),\n \"Cookie\": cache.get(\"pixivCookie\")\n }\n putInCache(\"headers\", headers)\n return headers\n}\n\nfunction getBlockAuthorsFromSource() {\n let authors = []\n try {\n authors = JSON.parse(`[${source.getVariable().replace(\",\", \",\")}]`)\n \/\/ sleepToast(JSON.stringify(authors))\n } catch (e) {\n sleepToast(\"🚫 屏蔽作者\\n⚠️ 【书源】源变量设置有误\\n输入作者ID,以英文逗号间隔,保存\")\n }\n return authors\n}\n\nfunction syncBlockAuthorList() {\n let authors1 = getFromCache(\"blockAuthorList\")\n let authors2 = getBlockAuthorsFromSource()\n util.debugFunc(() => {\n java.log(`屏蔽作者:缓存 :${JSON.stringify(authors1)}`)\n java.log(`屏蔽作者:源变量:${JSON.stringify(authors2)}`)\n })\n putInCache(\"blockAuthorList\", authors2)\n if (authors1 === null || authors1.length !== authors2.length) {\n java.log(\"🚫 屏蔽作者:已将源变量同步至缓存\")\n } else if (authors2.length === 0) {\n java.log(\"🚫 屏蔽作者:已清空屏蔽作者\")\n }\n}\n\npublicFunc()\nsyncBlockAuthorList()\nif (result.code() === 200) {\n if (isBackupSource() && !isLogin()) {\n util.getCsrfToken()\n }\n getPixivUid(); getWebViewUA(); util.getCookie(); getHeaders()\n if (!util.settings.FAST) checkMessageThread() \/\/ 检测过度访问\n}\n\nutil.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(`${cache.get(\"csfrToken\")}\\n`)\n java.log(`${cache.get(\"pixivCookie\")}\\n`)\n})\n\njava.getStrResponse(null, null)", "loginUi": "[\n {\n \"name\": \"\\uD83C\\uDD7F️ 登录账号\",\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\": \"\\uD83D\\uDD19 退出账号\",\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\": \"\\uD83D\\uDD30 使用指南\",\n \"type\": \"button\",\n \"action\": \"startGithubReadme()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"\\uD83D\\uDC1E 反馈问题\",\n \"type\": \"button\",\n \"action\": \"startGithubIssue()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\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\": \"novelBookmarkFactory(2)\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"\\uD83D\\uDCCC 当前章节\",\n \"type\": \"button\",\n \"action\": \"charpterReading()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"\\uD83D\\uDCC3 追更系列\",\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\": \"userFollowFactory()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"\\uD83D\\uDEAB 屏蔽作者\",\n \"type\": \"button\",\n \"action\": \"userBlock()\",\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\": \"novelCommentAdd()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"\\uD83D\\uDDD1 删除评论\",\n \"type\": \"button\",\n \"action\": \"novelCommentDelete()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"\\uD83E\\uDDF9 清除缓存\",\n \"type\": \"button\",\n \"action\": \"cleanCache()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"⤴️ 分享作者\",\n \"type\": \"button\",\n \"action\": \"shareFactory('author')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"⤴️ 分享章节\",\n \"type\": \"button\",\n \"action\": \"shareFactory('novel')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"⤴️ 分享系列\",\n \"type\": \"button\",\n \"action\": \"shareFactory('series')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n\n {\n \"name\": \"⚙️ 当前设置\",\n \"type\": \"button\",\n \"action\": \"showSettings()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"\\uD83D\\uDD27 默认设置\",\n \"type\": \"button\",\n \"action\": \"editSettings('')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"\\uD83D\\uDC64 搜索作者\",\n \"type\": \"button\",\n \"action\": \"editSettings('SEARCH_AUTHOR')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"\\uD83C\\uDC04 繁简通搜\",\n \"type\": \"button\",\n \"action\": \"editSettings('CONVERT_CHINESE')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"\\uD83D\\uDCD6 更多简介\",\n \"type\": \"button\",\n \"action\": \"editSettings('MORE_INFORMATION')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"\\uD83D\\uDCC5 更新时间\",\n \"type\": \"button\",\n \"action\": \"editSettings('SHOW_UPDATE_TIME')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"\\uD83D\\uDD17 原始链接\",\n \"type\": \"button\",\n \"action\": \"editSettings('SHOW_ORIGINAL_LINK')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"\\uD83D\\uDCDA 恢复《》\",\n \"type\": \"button\",\n \"action\": \"editSettings('REPLACE_TITLE_MARKS')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"\\uD83D\\uDDBC️ 显示描述\",\n \"type\": \"button\",\n \"action\": \"editSettings('SHOW_CAPTIONS')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"\\uD83D\\uDCAC 显示评论\",\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('SHOW_LIKE_NOVELS')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"\\uD83D\\uDCC3 显示追更\",\n \"type\": \"button\",\n \"action\": \"editSettings('SHOW_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\": \"\\uD83D\\uDC1E 调试模式\",\n \"type\": \"button\",\n \"action\": \"editSettings('DEBUG')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"\\uD83D\\uDD0D 搜索说明\",\n \"type\": \"button\",\n \"action\": \"readMeSearch()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"\\uD83D\\uDCC4 搜索页码\",\n \"type\": \"button\",\n \"action\": \"showMaxPages()\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"⏫ 增加页码\",\n \"type\": \"button\",\n \"action\": \"editMaxPages('add')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n },\n {\n \"name\": \"⏬ 减少页码\",\n \"type\": \"button\",\n \"action\": \"editMaxPages('minus')\",\n \"style\": {\n \"layout_flexGrow\": 1,\n \"layout_flexBasisPercent\": -1\n }\n }\n]", "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 getCookie(); getCsrfToken()\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()\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(\"csfrToken\") \/\/ 与登录设备有关\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\nfunction getCsrfToken() {\n let csfrToken\n let html = java.webView(null, \"https:\/\/www.pixiv.net\/\", null)\n try {\n csfrToken = html.match(\/token\\\\\":\\\\\"([a-z0-9]{32})\/)[1]\n } catch (e) {\n csfrToken = null\n }\n \/\/ java.log(csfrToken)\n cache.put(\"csfrToken\", csfrToken) \/\/ 与登录设备有关\n return csfrToken\n}\n\nfunction getCookie() {\n let pixivCookie = String(java.getCookie(\"https:\/\/www.pixiv.net\/\", null))\n if (pixivCookie.includes(\"first_visit_datetime\")) {\n \/\/ java.log(pixivCookie)\n cache.put(\"pixivCookie\", pixivCookie, 60*60)\n return pixivCookie\n } else {\n cache.delete(\"pixivCookie\")\n sleepToast(\"未登录账号(pixivCookie)\")\n return null\n }\n}\n\nfunction getNovel() {\n let novel = source.getLoginInfoMap()\n if (novel === undefined) novel = getFromCache(\"novel\")\n return novel\n}\n\nfunction getPostBody(url, body, headers) {\n if (headers === undefined) headers = getFromCache(\"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 return JSON.parse(java.post(url, body, headers).body())\n } catch (e) {\n \/\/ sleepToast(e)\n \/\/ sleepToast(JSON.stringify(headers))\n if (String(e).includes(400)) sleepToast(`⚠️ 缺少 headers`, 1)\n else if (String(e).includes(403)) sleepToast(`⚠️ 缺少 cookie 或 cookie 过期`, 1)\n else if (String(e).includes(404)) sleepToast(`⚠️ 404`, 1)\n else if (String(e).includes(422)) sleepToast(`⚠️ 请求信息有误`, 1)\n return {error: true}\n }\n}\n\nfunction novelBookmarkAdd(restrict=0) {\n let novel = getNovel()\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) sleepToast(`❤️ 收藏小说\n \\n\\n⚠️ 收藏【${novel.title}】失败`)\n else if (resp.body === null) sleepToast(`❤️ 收藏小说\\n\\n✅ 已经收藏【${novel.title}】了`)\n else {\n cache.put(`collect${novel.id}`, resp.body)\n sleepToast(`❤️ 收藏小说\\n\\n✅ 已收藏【${novel.title}】`)\n\n let likeNovels = getFromCache(\"likeNovels\")\n likeNovels.push(Number(novel.id))\n putInCache(\"likeNovels\", likeNovels)\n\n let novelObj = getAjaxJson(urlNovelDetailed(novel.id))\n novelObj.body.isBookmark = true\n putInCache(urlNovelDetailed(novel.id), novelObj, cacheSaveSeconds)\n }\n}\n\nfunction getNovelBookmarkId(novelId) {\n let bookmarkId = getFromCache(`collect${novelId}`)\n if (bookmarkId === null) {\n bookmarkId = getAjaxJson(urlNovelBookmarkData(novelId), true).body.bookmarkData.id\n }\n return bookmarkId\n}\n\nfunction novelBookmarkDelete() {\n let novel = getNovel()\n let resp = getPostBody(\n \"https:\/\/www.pixiv.net\/ajax\/novels\/bookmarks\/delete\",\n `del=1&book_id=${getNovelBookmarkId(novel.id)}`\n )\n if (resp.error === true) sleepToast(`❤️ 收藏小说\\n\\n⚠️ 取消收藏【${novel.title}】失败`)\n else {\n cache.delete(`collect${novel.id}`)\n sleepToast(`❤️ 收藏小说\\n\\n✅ 已取消收藏【${novel.title}】`)\n\n let likeNovels = getFromCache(\"likeNovels\")\n likeNovels = likeNovels.filter(item => item !== Number(novel.id))\n putInCache(\"likeNovels\", likeNovels)\n\n let novelObj = getAjaxJson(urlNovelDetailed(novel.id))\n novelObj.body.isBookmark = false\n putInCache(urlNovelDetailed(novel.id), novelObj, cacheSaveSeconds)\n }\n}\n\nfunction novelsBookmarkDelete(novelIds) {\n let bookmarkIds = []\n novelIds.forEach(novelId => {bookmarkIds.push(getNovelBookmarkId(novelId))})\n let resp = getPostBody(\n \"https:\/\/www.pixiv.net\/ajax\/novels\/bookmarks\/remove\",\n JSON.stringify({\"bookmarkIds\": bookmarkIds})\n )\n if (resp.error === true) sleepToast(\"❤️ 收藏小说\\n\\n⚠️ 取消收藏失败\", 1)\n else {\n sleepToast(\"❤️ 收藏小说\\n\\n✅ 已取消收藏\")\n novelIds.forEach(novelId => {cache.delete(`collect${novelId}`)})\n\n let likeNovels = getFromCache(\"likeNovels\")\n likeNovels = likeNovels.filter(item => !novelIds.includes(Number(item)))\n putInCache(\"likeNovels\", likeNovels)\n\n novelIds.forEach(novelId => {\n let novelObj = getAjaxJson(urlNovelDetailed(novelId))\n novelObj.body.isBookmark = false\n putInCache(urlNovelDetailed(novelId), novelObj, cacheSaveSeconds)\n })\n }\n}\n\nfunction novelBookmarkFactory(code) {\n let novel = getNovel()\n let collectId = getFromCache(`collect${novel.id}`)\n if (collectId >= 1) code = 0\n\n if (code === 0) novelBookmarkDelete()\n else if (code === 1) novelBookmarkAdd(0)\n else if (code === 2) novelBookmarkAdd(1)\n}\n\nfunction novelMarker(page=1) {\n let novel = getNovel()\n let lastMarker = getFromCache(`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=${getFromCache(\"pixiv:uid\")}&page=${page}`\n )\n java.log(`mode=save&i_id=${novel.id}&u_id=${getFromCache(\"pixiv:uid\")}&page=${page}`)\n if (resp.error === true) sleepToast(\"🏷️ 添加书签\\n\\n⚠️ 操作失败\", 1)\n else if (lastMarker === true) {\n cache.put(`marker${novel.id}`, false)\n sleepToast(`🏷️ 添加书签\\n\\n✅ 已删除书签`)\n } else {\n cache.put(`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) sleepToast(`📃 追更系列\\n\\n⚠️ 追更【${novel.seriesTitle}】失败`, 1)\n else {\n cache.put(`watch${novel.seriesId}`, true)\n sleepToast(`📃 追更系列\\n\\n✅ 已追更【${novel.seriesTitle}】`)\n\n let watchedSeries = getFromCache(\"watchedSeries\")\n watchedSeries.push(Number(novel.seriesId))\n putInCache(\"watchedSeries\", watchedSeries)\n\n let novelObj = getAjaxJson(urlSeriesDetailed(novel.seriesId))\n novelObj.body.isWatched = true\n putInCache(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) sleepToast(`📃 追更系列\\n\\n⚠️ 取消追更【${novel.seriesTitle}】失败`, 1)\n else {\n cache.delete(`watch${novel.seriesId}`)\n sleepToast(`📃 追更系列\\n\\n✅ 已取消追更【${novel.seriesTitle}】`)\n\n let watchedSeries = getFromCache(\"watchedSeries\")\n watchedSeries = watchedSeries.filter(item => item !== Number(novel.seriesId))\n putInCache(\"watchedSeries\", watchedSeries)\n\n let novelObj = getAjaxJson(urlSeriesDetailed(novel.seriesId))\n novelObj.body.isWatched = false\n putInCache(urlSeriesDetailed(novel.seriesId), novelObj, cacheSaveSeconds)\n }\n}\n\nfunction seriesWatchFactory(code=1) {\n let novel = getNovel()\n if (!novel.seriesId) {\n return sleepToast(`📃 追更系列\\n\\n⚠️ 【${novel.title}】非系列小说,无法加入追更列表`)\n }\n\n let lastStatus = getFromCache(`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=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) sleepToast(`⭐️ 关注作者\\n\\n⚠️ 关注【${novel.userName}】失败`, 1)\n else {\n sleepToast(`⭐️ 关注作者\\n\\n✅ 已关注【${novel.userName}】`)\n cache.put(`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) sleepToast(`⭐️ 关注作者\\n\\n⚠️ 取消关注【${novel.userName}】失败`, 1)\n else {\n sleepToast(`⭐️ 关注作者\\n\\n✅ 已取消关注【${novel.userName}】`)\n cache.delete(`follow${novel.userId}`)\n }\n}\n\nfunction userFollowFactory(code=1) {\n let novel = getNovel()\n let lastStatus = getFromCache(`follow${novel.userId}`)\n if (lastStatus === true) code = 0\n\n if (code === 0) userUnFollow()\n else if (code === 1) userFollow()\n}\n\nfunction userBlackList() {\n let action = \"block\" \/\/ 拉黑作者,非屏蔽作者作品\n let novel = getNovel()\n let lastStatus = getFromCache(`block${novel.userId}`)\n if (lastStatus === true) action = \"unblock\"\n\n let resp = getPostBody(\n `https:\/\/www.pixiv.net\/ajax\/block\/save`,\n JSON.stringify({\"user_id\": novel.userId, \"action\": action})\n )\n \/\/ java.log(JSON.stringify({\"user_id\": novel.userId, \"action\": action}))\n if (resp.error === true) sleepToast(\"⚠️ 操作失败\", 1)\n else if (lastStatus === true) {\n cache.put(`block${novel.userId}`, false)\n sleepToast(`✅ 已取消拉黑【${novel.userName}】\\n\\n已允许其点赞、评论、收藏、关注、私信等`)\n } else {\n cache.put(`block${novel.userId}`, true)\n sleepToast(`✅ 已拉黑【${novel.userName}】(Pixiv)\\n\\n已禁止其点赞、评论、收藏、关注、私信等`)\n }\n}\n\nfunction userBlock() {\n let authors = getFromCache(\"blockAuthorList\")\n let novel = getNovel()\n if (authors.includes(Number(novel.userId))) {\n authors = authors.filter(author => author !== Number(novel.userId))\n sleepToast(`🚫 屏蔽作者\\n\\n✅ 已取消屏蔽【${novel.userName}】\\n现已恢复显示其小说`)\n } else if (novel.userId !== undefined && novel.userId !== null) {\n authors.push(Number(novel.userId))\n sleepToast(`🚫 屏蔽作者\\n\\n✅ 本地已屏蔽【${novel.userName}】\\n今后不再显示其小说`)\n }\n putInCache(\"blockAuthorList\", authors)\n source.setVariable(authors.toString())\n \/\/ sleepToast(JSON.stringify(authors))\n}\n\nfunction novelCommentAdd() {\n let resp, novel = getNovel()\n let userId = getFromCache(\"pixiv:uid\")\n let comment = String(result.get(\"发送评论\")).trim()\n if (comment === \"\") {\n return sleepToast(`✅ 发送评论\\n⚠️ 请输入需要发送的评论\\n\\n输入【评论内容;评论ID】可回复该条评论,如【非常喜欢;123456】\\n\\n📌 当前章节:${novel.title}\\n\\n如非当前章节,请刷新正文`)\n }\n\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) sleepToast(\"✅ 发送评论\\n\\n⚠️ 评论失败\", 1)\n else sleepToast(`✅ 发送评论\\n\\n✅ 已在【${novel.title}】发布评论:\\n${comment}`)\n}\n\nfunction getNovelCommentID(novelId, commentText) {\n let list = [], uid = String(getFromCache(\"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📌 当前章节:${novel.title}\\n\\n如非当前章节,请刷新正文`)\n }\n\n let matched = comment.match(RegExp(\/\\d{8,}\/))\n if (matched) {\n commentIDs = [matched[0]]\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 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) sleepToast(\"🗑 删除评论\\n\\n⚠️ 评论删除失败\", 1)\n else sleepToast(`🗑 删除评论\\n\\n✅ 已在【${novel.title}】删除评论:\\n${comment}`)\n })\n}\n\nfunction startBrowser(url, title) {\n let msg = \"\", headers = `{\"headers\": {\"User-Agent\":\"${getWebViewUA()}\"}}`\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(\"https:\/\/github.com\")) {\n if (url.includes(\"issues\")) msg += \"🐞 反馈问题\"\n else if (url.includes(\"doc\")) msg += \"🔰 使用指南\"\n else msg += \"⭐️ 收藏项目\"\n msg += \"\\n\\n即将打开 Github\\n请确认已开启代理\/梯子\/VPN等\"\n }\n sleepToast(msg, 0.01)\n java.startBrowser(`${url}, ${headers}`, title)\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 startGithubIssue() {\n startBrowser(\"https:\/\/github.com\/windyhusky\/PixivSource\/issues\", \"反馈问题\")\n}\nfunction startGithubReadme() {\n startBrowser(\"https:\/\/github.com\/windyhusky\/PixivSource\/blob\/main\/doc\/Pixiv.md\", \"使用指南\")\n}\n\nfunction checkStatus(status) {\n if (eval(String(status)) === true) return \"❤️\"\n else return \"🖤\"\n}\n\nfunction charpterReading() {\n let novel = getNovel()\n \/\/ let novel = source.getLoginInfoMap()\n let msg = `📌 当前章节\\n\\n${checkStatus(novel.isWatched)} 系列:${novel.seriesTitle}\\n${checkStatus(novel.isBookmark)} 章节:${novel.title}\\n👤 作者:${novel.userName}\\n\\n如非当前章节,请刷新正文`\n msg = msg.replace(\"🖤 系列:\\n\", \"\")\n sleepToast(msg, 2)\n}\n\nfunction readMeLogin() {\n return sleepToast(`🅿️ 登录界面功能\\n\n 使用收藏、追更、关注作者、评论等功能时,需要登录\n 使用前请先刷新正文,获取当前章节信息\\n\n 点击【📌 当前章节】查看书源内部章节信息`.replace(\" \",\"\"), 5)\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 \"CONVERT_CHINESE\": \"🀄️ 繁简通搜\",\n \"SHOW_UPDATE_TIME\": \"📅 更新时间\",\n \"SHOW_ORIGINAL_LINK\": \"🔗 原始链接\",\n \"SHOW_COMMENTS\": \"💬 显示评论\",\n \"MORE_INFORMATION\": \"📖 更多简介\",\n \"REPLACE_TITLE_MARKS\": \"📚 恢复《》\",\n \"SHOW_CAPTIONS\": \"🖼️ 显示描述\",\n \"SHOW_LIKE_NOVELS\" :\"❤️ 显示收藏\",\n \"SHOW_WATCHED_SERIES\" :\"📃 显示追更\",\n \"FAST\": \"⏩ 快速模式\",\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 let keys = [], msgList = []\n let settings = getFromCache(\"pixivSettings\")\n if (mode !== \"FAST\") keys = Object.keys(settingsName)\n else keys = Object.keys(settingsName).slice(0, 5)\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${getSettingStatus()}`)\n}\n\nfunction editSettings(object) {\n let msg = \"\", status\n let settings = getFromCache(\"pixivSettings\")\n if (settings[object] !== undefined) {\n status = settings[object] = (!settings[object])\n } else {\n status = settings[object] = true \/\/ 无设置则默认开启\n }\n\n if (object === \"\") {\n settings.SEARCH_AUTHOR = true \/\/ 搜索:默认搜索作者名称\n settings.CONVERT_CHINESE = true \/\/ 搜索:搜索时进行繁简转换\n settings.SHOW_LIKE_NOVELS = true \/\/ 搜索:搜索结果显示收藏小说\n settings.SHOW_WATCHED_SERIES = true \/\/ 搜索:搜索结果显示追整系列小说\n settings.MORE_INFORMATION = false \/\/ 详情:书籍简介显示更多信息\n settings.SHOW_UPDATE_TIME = true \/\/ 目录:显示更新时间,但会增加少许请求\n settings.SHOW_ORIGINAL_LINK = true \/\/ 目录:显示原始链接,但会增加大量请求\n settings.REPLACE_TITLE_MARKS = true \/\/ 正文:注音内容为汉字时,替换为书名号\n settings.SHOW_CAPTIONS = true \/\/ 正文:章首显示描述\n settings.SHOW_COMMENTS = true \/\/ 正文:章尾显示评论\n settings.FAST = false \/\/ 全局:快速模式\n settings.DEBUG = false \/\/ 全局:调试模式\n msg = `\\n✅ 已恢复 🔧 默认设置\\n\\n${getSettingStatus()}`\n\n } else if (object !== \"FAST\") {\n msg = `${statusMsg(status)} ${settingsName[object]}`\n } else if (object === \"FAST\") {\n if (settings[object] === true) {\n putInCache(\"pixivLastSettings\", settings)\n settings.CONVERT_CHINESE = false \/\/ 搜索:繁简通搜\n settings.SEARCH_AUTHOR = false \/\/ 搜索:默认搜索作者\n settings.SHOW_UPDATE_TIME = false \/\/ 目录:显示章节更新时间\n settings.SHOW_ORIGINAL_LINK = false \/\/ 目录:显示章节源链接\n settings.SHOW_COMMENTS = false \/\/ 正文:显示评论\n putInCache(\"pixivSettings\", settings)\n let message = getSettingStatus(\"FAST\")\n msg = `\\n${statusMsg(status)} ${settingsName[object]}\\n\\n${message}`\n } else {\n settings = getFromCache(\"pixivLastSettings\")\n settings.SEARCH_AUTHOR = true\n settings.FAST = false\n putInCache(\"pixivSettings\", settings)\n let message = getSettingStatus(\"FAST\")\n msg = `\\n${statusMsg(status)} ${settingsName[object]}\\n\\n${message}`\n }\n }\n sleepToast(msg)\n putInCache(\"pixivSettings\", settings)\n}\n\nfunction cleanCache() {\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 sleepToast(`🧹 清除缓存\\n\\n📌 当前章节:${novel.title}\\n\\n已清除本章正文缓存,刷新正文以更新`, 5)\n}\n\nlet maxPagesName = {\n \"seriesMaxPages\": \"系列最大页码\",\n \"novelsMaxPages\": \"单篇最大页码\"\n}\n\nfunction showMaxPages() {\n let keys = Object.keys(maxPagesName)\n let key = getFromCache(\"maxPagesKey\")\n if (!key) key = keys[0]\n if (key === keys[0]) key = keys[1]\n if (key === keys[1]) key = keys[0]\n putInCache(\"maxPagesKey\", key)\n return sleepToast(`📄 搜索页码\\n设置 #️⃣ 搜索标签的最大页码数\\n\n 当前${maxPagesName[keys[0]]}:${getFromCache(keys[0])}\\n当前${maxPagesName[keys[1]]}:${getFromCache(keys[1])}\\n\n 点击 ⏫ 增加页码\/ ⏬ 减少页码\\n调整【${maxPagesName[key]}】\\n\n 📌 页码越多,小说越多,速度越慢`.replace(\" \", \"\"))\n}\n\nfunction editMaxPages(method) {\n let msg = \"\", key = getFromCache(\"maxPagesKey\")\n if (!key) key = Object.keys(maxPagesName)[0]\n let maxPages = getFromCache(key)\n if (!maxPages) maxPages = 1\n if (method.includes(\"add\")) maxPages += 1\n if (method.includes(\"min\")) maxPages -= 1\n\n if (maxPages <= 1) {\n maxPages = 1\n msg += \"⚠️ 搜索页码不能再减小了\\n\"\n }\n if (maxPages >= 3) {\n msg += \"⚠️ 搜索页码越多,搜索速度越慢\\n\"\n }\n if (maxPages >= 10) {\n maxPages = 10\n msg += \"⚠️ 搜索页码不能再增大了\\n\"\n }\n putInCache(`${key}`, maxPages)\n sleepToast(`📄 搜索页码\\n\\n当前搜索【${maxPagesName[key]}】:${maxPages}\\n\\n${(msg)}`.trim())\n return maxPages\n}\n\nfunction sleepToast(text, second=0) {\n java.log(text)\n \/\/ java.toast(text)\n java.longToast(text)\n sleep(1000*second)\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]))[0], util.vote()\n if (novel.seriesId === undefined || novel.seriesId === null) {\n book.bookUrl = novel.detailedUrl = urlNovelUrl(novel.id)\n book.tocUrl = novel.catalogUrl = urlNovelDetailed(novel.id)\n } else {\n book.bookUrl = novel.detailedUrl = urlSeriesUrl(novel.seriesId)\n book.tocUrl = novel.catalogUrl = urlSeriesDetailed(novel.seriesId)\n }\n \/\/ 放入信息以便登陆界面使用\n source.putLoginInfo(JSON.stringify(novel))\n cache.put(\"novel\", JSON.stringify(novel))\n return novel\n}\n\n(() => {\n return novelHandler(util.getNovelRes(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 === undefined) novel = JSON.parse(cache.get(\"novel\"))\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 cache.put(`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.settings.IS_LEGADO) {\n let novelIds = JSON.parse(cache.get(`novelIds${novel.seriesId}`))\n novel.id = novelIds[book.durChapterIndex]\n novel.title = book.durChapterTitle\n\n let bookmarkId = JSON.parse(cache.get(`collect${novel.id}`))\n novel.isBookmark = !!bookmarkId\n }\n\n source.putLoginInfo(JSON.stringify(novel))\n cache.put(\"novel\", JSON.stringify(novel))\n}\n\nfunction getContent(res) {\n getNovelInfo(res) \/\/ 放入信息以便登陆界面使用\n \/\/ charpterReading() \/\/ 输出章节信息\n let content = String(res.content)\n \/\/ let content = \"undefined\"\n if (content.includes(\"undefined\")) {\n return checkContent()\n }\n\n \/\/ 在正文内部添加小说描述\n if (util.settings.SHOW_CAPTIONS && res.description !== \"\") {\n content = res.description + \"\\n\" + \"——————————\\n\".repeat(2) + content\n }\n\n \/\/ 获取 [uploadedimage:] 的图片链接\n let hasEmbeddedImages = res.textEmbeddedImages !== undefined && res.textEmbeddedImages !== null\n if (hasEmbeddedImages) {\n Object.keys(res.textEmbeddedImages).forEach((key) => {\n content = content.replace(`[uploadedimage:${key}]`, `<img src=\"${urlCoverUrl(res.textEmbeddedImages[key].urls.original)}\">`)\n })\n }\n\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 content = content.replace(`${pixivimage}`, `<img src=\"${urlIllustOriginal(illustId, order)}\">`)\n })\n }\n\n \/\/ 替换 Pixiv 分页标记符号 [newpage]\n 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 \/\/ 替换 Pixiv 章节标记符号 [chapter:]\n matched = content.match(RegExp(\/\\[chapter:(.*?)]\/gm))\n if (matched) {\n for (let i in matched) {\n let matched2 = matched[i].match(\/\\[chapter:(.*?)]\/m)\n let chapter = matched2[1].trim()\n content = content.replace(`${matched[i]}`, `${chapter}<p><p\/>`)\n }\n }\n\n \/\/ 替换 Pixiv 跳转页面标记符号 [[jump:]]\n matched = content.match(RegExp(\/\\[jump:(\\d+)]\/gm))\n if (matched) {\n for (let i in matched) {\n let page = matched[i].match(\/\\d+\/)\n content = content.replace(`${matched[i]}`, `\\n\\n跳转至第${page}节`)\n }\n }\n\n \/\/ 替换 Pixiv 链接标记符号 [[jumpuri: > ]]\n matched = content.match(RegExp(\/\\[\\[jumpuri:(.*?)>(.*?)]]\/gm))\n if (matched) {\n for (let i in matched) {\n let matched2 = matched[i].match(\/\\[\\[jumpuri:(.*?)>(.*?)]]\/m)\n let matchedText = matched2[0]\n let urlName = matched2[1].trim()\n let urlLink = matched2[2].trim()\n \/\/ 阅读不支持超链接\n \/\/content = content.replace(`${matchedText}`, `<a href=${urlLink}>${urlName}<\/a>`)\n if (urlLink === urlName) {\n content = content.replace(`${matchedText}`, `${urlName}`)\n } else {\n content = content.replace(`${matchedText}`, `${urlName}: ${urlLink}`)\n }\n }\n }\n\n \/\/ 替换 Pixiv 注音标记符号 [[rb: > ]]\n matched = content.match(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\n if (util.settings.SHOW_COMMENTS) {\n return content + getComment(res)\n } else {\n return content\n }\n}\n\nfunction getComment(res) {\n let comments = \"\"\n let resp = getAjaxJson(urlNovelComments(res.id, 0, 1000), true)\n if (resp.error === true) return comments;\n\n let getTime = obj => obj.commentDate && obj.commentDate.replace(\/-0?\/g, '.');\n let func = ($, x) => emoji[x] ? `<img src=\"${urlEmojiUrl(emoji[x])}\">` : $;\n let addImg = obj => obj.comment === '' && obj.stampId ? `<img src=\"${urlStampUrl(obj.stampId)}\">` : obj.comment.replace(\/\\(([a-z]\\w+)\\)\/g, func);\n resp.body.comments.forEach(comment => {\n if (comment.comment === \"\") {\n comment.comment = `<img src=\"${urlStampUrl(comment.stampId)}\">`\n }\n comments += `👤${comment.userName}:${addImg(comment)}\\n(ID:${comment.id} \/ ${getTime(comment)})\\n`\n\n \/\/ 获取评论回复\n if (comment.hasReplies === true) {\n let resp = getAjaxJson(urlNovelCommentsReply(comment.id, 1), true)\n if (resp.error === true) return comments;\n \n resp.body.comments.reverse().forEach(reply => {\n comments += `👤${reply.userName}(⤴️${reply.replyToUserName}):${addImg(comment)}\\n(ID:${reply.id} \/ ${getTime(reply)})\\n`\n })\n comments += \"——————————\\n\"\n }\n })\n if (comments) {\n comments = \"\\n\" + \"——————————\\n\".repeat(2) + \"章节评论:\\n\" + comments\n }\n return comments;\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.getNovelRes(result))\n})()", "imageStyle": "DEFAULT" }, "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 (!isLogin()) {\n return handlerNoLogin()\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 \/\/ 正则匹配网址内容\n if (baseUrl.includes(\"\/ranking\")) {\n return handlerRanking()\n }\n if (baseUrl.includes(\"\/marker_all\")) {\n return handlerRanking()\n }\n if (baseUrl.includes(\"\/editors_picks\")) {\n return handlerRanking()\n }\n if (baseUrl.includes(\"https:\/\/www.pixiv.net\")) {\n return handlerRanking()\n }\n else {\n 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 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 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 if (util.settings.IS_LEGADO) return handlerRankingAjaxAll()\n \/\/ else if (util.settings.IS_SOURCE_READ) return handlerRankingWebview()\n else if (util.settings.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(`${cache.get(\"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 getUserNovels() {\n if (!isLogin()) {\n sleepToast(\"👤 搜索作者\\n\\n⚠️ 当前未登录账号\\n请登录 Pixiv 账号\", 1.5)\n util.removeCookie(); util.login()\n sleepToast(\"👤 搜索作者\\n\\n登录成功后,请重新搜索\", 2)\n return []\n }\n\n let uidList = [], novels = []\n let username = String(java.get(\"keyword\"))\n let page = Number(java.get(\"page\"))\n\n \/\/ cache.delete(username)\n let userid = cache.get(username)\n if (userid !== undefined && userid !== null) {\n uidList = [userid]\n java.log(`👤 缓存作者ID:${userid}`)\n } else {\n html = java.ajax(urlSearchUser(username))\n \/\/ java.log(html)\n \/\/ 仅匹配有投稿作品的用户\n let match = html.match(new RegExp(`\"userIds\":\\\\[(?:(?:\\\\d+,?)+)]`))\n \/\/ java.log(JSON.stringify(match))\n if (match === null || match.length === 0) {\n return []\n }\n\n match = JSON.stringify(match).replace(\"\\\\\",\"\").split(\",\")\n \/\/ java.log(JSON.stringify(match))\n let regNumber = new RegExp(\"\\\\d+\")\n uidList = match.map(v => {\n return v.match(regNumber)[0]\n })\n java.log(`👤 获取作者ID:${JSON.stringify(uidList)}`)\n }\n\n let tempUids = []\n for (let i in uidList) {\n let uid = uidList[i]\n let resp = getAjaxJson(urlUserAllWorks(uid), true)\n \/\/ java.log(urlUserAllWorks(id))\n \/\/ java.log(JSON.stringify(resp))\n if (resp.error === true) {\n return []\n }\n\n \/\/ 仅获取前3个有小说的作者\n let novelIds = Object.keys(resp.body.novels)\n \/\/ java.log(`${uid}-${novelIds.length}`)\n if (novelIds.length >= 1) tempUids.push(uid)\n if (tempUids.length === 3) {\n java.log(`👤 显示作者ID:${JSON.stringify(tempUids)}`)\n break\n }\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 seriesIds.forEach(seriesId => {\n let returnList = getAjaxJson(urlSeriesNovelsTitles(seriesId)).body\n returnList.map(novel => {return seriesNovelIds.push(novel.id)})\n })\n \/\/ java.log(`有系列的小说ID:${JSON.stringify(seriesNovelIds)}`)\n \/\/ java.log(JSON.stringify(seriesNovelIds.length))\n\n \/\/ 获取单篇小说\n if (novelIds.length >= 1 && util.settings.IS_LEGADO) {\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 let novelUrls = novelIds.map(novelId => {return 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 \/\/ \/\/ 获取单篇小说\n if (novelIds.length >= 1 && util.settings.IS_SOURCE_READ) {\n novelIds = novelIds.filter(novelid => (!seriesNovelIds.includes(novelid)))\n \/\/ java.log(`真单篇的小说ID:${JSON.stringify(novelIds)}`)\n \/\/ java.log(JSON.stringify(novelIds.length))\n novelIds = novelIds.reverse().slice((page - 1) * 20, page * 20)\n novelIds.forEach(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 }\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(urlSearchNovel(name, page))\n java.log(urlSearchNovel(name, page))\n }\n if (type.includes(\"series\")) {\n resp = getAjaxJson(urlSearchSeries(name, page))\n java.log(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 novels = []\n let name = String(java.get(\"keyword\"))\n let maxPages = getFromCache(\"maxPages\") \/\/ 仅默认搜索使用\n if (!maxPages) {\n maxPages = getFromCache(\"seriesMaxPages\") \/\/ 搜索标签使用\n if (!maxPages) maxPages = 1\n putInCache(\"seriesMaxPages\", maxPages)\n }\n java.log(`📄 搜索系列最大页码:${maxPages}`)\n\n if (JSON.parse(result).error === true) {\n return []\n }\n let lastPage = JSON.parse(result).body.novel.lastPage\n novels = novels.concat(JSON.parse(result).body.novel.data)\n java.log(urlSearchSeries(name, 1))\n cache.put(urlSearchSeries(name, 1), result, cacheSaveSeconds) \/\/ 加入缓存\n for (let page = Number(java.get(\"page\")) + 1; page <= lastPage, page <= maxPages; page++) {\n novels = novels.concat(search(name,\"series\", page).data)\n }\n return novels\n}\n\nfunction getNovels() {\n let novels = []\n let name = String(java.get(\"keyword\"))\n let maxPages = getFromCache(\"maxPages\") \/\/ 仅默认搜索使用\n if (!maxPages) {\n maxPages = getFromCache(\"novelsMaxPages\") \/\/ 搜索标签使用\n if (!maxPages) maxPages = 1\n putInCache(\"novelsMaxPages\", maxPages)\n }\n java.log(`📄 搜索单篇最大页码:${maxPages}`)\n\n let resp = search(name, \"novel\", 1)\n novels = novels.concat(resp.data)\n for (let page = Number(java.get(\"page\")) + 1; page <= resp.lastPage, page <= maxPages; page++) {\n novels = novels.concat(search(name,\"novel\", page).data)\n }\n return util.combineNovels(novels)\n}\n\nfunction getConvertNovels() {\n let novels = []\n let novelName = String(java.get(\"keyword\"))\n let name1 = String(java.s2t(novelName))\n let name2 = String(java.t2s(novelName))\n if (name1 !== novelName) novels = novels.concat(search(name1, \"novel\", 1).data)\n if (name2 !== novelName) novels = novels.concat(search(name2, \"novel\", 1).data)\n novels = util.combineNovels(novels)\n if (name1 !== novelName) novels = novels.concat(search(name1, \"series\", 1).data)\n if (name2 !== novelName) novels = novels.concat(search(name2, \"series\", 1).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 \/\/ 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(`👤 过滤作者:${tags.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(\"@\") || keyword.startsWith(\"@\")) {\n java.put(\"keyword\", keyword.slice(1))\n novels = novels.concat(getUserNovels())\n } else if (keyword.startsWith(\"#\") || keyword.startsWith(\"#\")) {\n java.put(\"keyword\", keyword.slice(1))\n \/\/ 删除默认搜索最大页码,使用内部设定的最大页码\n cache.delete(\"maxPages\")\n novels = novels.concat(getSeries())\n novels = novels.concat(getNovels())\n } else {\n \/\/ 设置默认搜索最大页码\n putInCache(\"maxPages\", 1)\n novels = novels.concat(getSeries())\n novels = novels.concat(getNovels())\n if (util.settings.SEARCH_AUTHOR) novels = novels.concat(getUserNovels())\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: 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(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 java.log(urlNovel(v.id))\n v.chapterUrl = 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(urlSeriesNovelsTitles(seriesID), true).body\n returnList.forEach(v => {\n v.title = v.title.trim()\n v.chapterUrl = urlNovel(v.id)\n novelIds.push(v.id)\n })\n } else {\n \/\/逻辑控制者 也就是使用上面定义的两个函数来做对应功能\n \/\/要爬取的总次数\n let max = (allChaptersCount \/ limit) + 1\n for (let i = 0; i < max; i++) {\n \/\/java.log(\"i的值:\"+i)\n let list = sendAjaxForGetChapters(i * limit);\n \/\/取出每个值\n returnList = returnList.concat(list)\n }\n }\n \/\/ 放入信息以便登陆界面使用\n cache.put(`novelIds${seriesID}`, JSON.stringify(novelIds), cacheSaveSeconds)\n \/\/ java.log(JSON.stringify(returnList))\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": "false ", "isVip": "true", "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 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.slice(1)}`)\n }\n\n} else if (keyword.startsWith(\"#\") || keyword.startsWith(\"#\")) {\n keyword = keyword.slice(1)\n if (keyword.includes(\"@\") || keyword.includes(\"@\")) {\n let author = keyword.match(new RegExp(\/[@@](.*)\/))\n keyword = keyword.replace(author[0], \"\").trim()\n java.put(\"inputAuthor\", author[1])\n java.log(`#️⃣ 搜索标签:${keyword} 👤 过滤作者:${author[1]}`)\n } else {\n java.log(`#️⃣ 搜索标签:${keyword}`)\n }\n java.put(\"keyword\", `#${keyword}`)\n\n} else {\n java.log(`🔍 搜索内容:${keyword}`)\n}\nurlSearchSeries(keyword, page)", "variableComment": "🚫 屏蔽作者(本地):\n设置方法1️⃣:打开小说 - 菜单 - 登录 - 🚫 屏蔽作者\n▶️ 搜索任意小说,同步屏蔽作者数据\n\n设置方法2️⃣:编辑书源 - 菜单 - 设置源变量 - 修改并保存\n设置源变量:输入作者ID,【英文逗号】间隔\n▶️ 搜索任意小说,同步屏蔽作者数据\n以下内容为源变量模板:\n12345, 67890\n\n\n⚙️ 书源设置:\n设置1️⃣:打开小说 - 菜单 - 登录 - 点击下方按钮\n▶️ 搜索任意小说,同步设置数据\n\n设置2️⃣:编辑书源 - 基本 - 变量说明 - 修改并保存\n⚙️ 自定义设置:将 true 改为 false,或相反\n⚠️ 设置源变量【无法】更改书源自定义设置\n⚠️ 注意不要添加或删除尾随逗号\",\"\n⚠️ 更新发现页需要长按\"Pixiv\",手动刷新\n以下内容为书源设置:\n{\n\"SHOW_GENERAL_NEW\": false,\n\"SHOW_GENERAL_RANK\": false,\n\"SHOW_R18_GENRE\": false,\n\"SHOW_GENERAL_GENRE\": false\n}\n\n\/\/ SHOW_GENERAL_NEW\n\/\/ 发现:最新、企划、约稿显示一般小说\n\/\/ SHOW_GENERAL_RANK\n\/\/ 发现:排行榜显示一般小说\n\/\/ SHOW_R18_GENRE\n\/\/ 发现:热门分类显示R18小说\n\/\/ SHOW_GENERAL_GENRE\n\/\/ 发现:热门分类显示一般小说\n\n", "weight": 0 }