NodeSeek 全部已读增强版

一键标记 NodeSeek 所有通知为已读,支持自定义功能。功能包括:三页全部已读、无弹窗提示、自动跳转到未读消息列表等

  1. // ==UserScript==
  2. // @name NodeSeek 全部已读增强版
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.6
  5. // @description 一键标记 NodeSeek 所有通知为已读,支持自定义功能。功能包括:三页全部已读、无弹窗提示、自动跳转到未读消息列表等
  6. // @author wzsxh
  7. // @match https://www.nodeseek.com/notification*
  8. // @grant GM_setValue
  9. // @grant GM_getValue
  10. // @grant GM_registerMenuCommand
  11. // @license MIT
  12. // @supportURL https://www.nodeseek.com/space/9074#/general
  13. // ==/UserScript==
  14.  
  15. (function () {
  16. 'use strict';
  17.  
  18. // 默认设置
  19. const defaultSettings = {
  20. enableTripleRead: true, // 启用三页全部已读
  21. enableNoPrompt: true, // 启用原有已读功能不弹窗
  22. enableAutoRedirect: true, // 启用自动跳转到未读消息列表
  23. disableOriginal: false, // 禁用原有已读功能
  24. };
  25.  
  26. // 获取设置
  27. let settings = Object.assign({}, defaultSettings, GM_getValue('nodeseek_settings', {}));
  28.  
  29. // 注册(不可用)菜单命令
  30. function registerMenuCommands() {
  31. GM_registerMenuCommand(`${settings.enableTripleRead ? '✅' : '❌'} 三页全部已读功能`, () => {
  32. settings.enableTripleRead = !settings.enableTripleRead;
  33. GM_setValue('nodeseek_settings', settings);
  34. location.reload();
  35. });
  36.  
  37. GM_registerMenuCommand(`${settings.enableNoPrompt ? '✅' : '❌'} 原有已读功能不弹窗`, () => {
  38. settings.enableNoPrompt = !settings.enableNoPrompt;
  39. GM_setValue('nodeseek_settings', settings);
  40. location.reload();
  41. });
  42. GM_registerMenuCommand(`${settings.enableAutoRedirect ? '✅' : '❌'} 自动跳转到未读列表`, () => {
  43. settings.enableAutoRedirect = !settings.enableAutoRedirect;
  44. GM_setValue('nodeseek_settings', settings);
  45. location.reload();
  46. });
  47.  
  48. GM_registerMenuCommand(`${settings.disableOriginal ? '✅' : '❌'} 禁用原有已读功能`, () => {
  49. settings.disableOriginal = !settings.disableOriginal;
  50. GM_setValue('nodeseek_settings', settings);
  51. location.reload();
  52. });
  53.  
  54. }
  55.  
  56. window.addEventListener('load', () => {
  57. // 注册(不可用)菜单
  58. registerMenuCommands();
  59.  
  60. // API URL配置
  61. const apiUrls = {
  62. 'atMe': 'https://www.nodeseek.com/api/notification/at-me/markViewed?all=true',
  63. 'reply': 'https://www.nodeseek.com/api/notification/reply-to-me/markViewed?all=true',
  64. 'message': 'https://www.nodeseek.com/api/notification/message/markViewed?all=true'
  65. };
  66.  
  67. // 创建统一的标记已读函数
  68. async function markAsRead(button, originalText, urls, removeAllUnread = true) {
  69. const originalBgColor = button.style.backgroundColor;
  70. try {
  71. button.innerHTML = '处理中...';
  72. button.style.backgroundColor = '#52c41a';
  73.  
  74. const responses = await Promise.all(urls.map(url =>
  75. fetch(url, {
  76. method: 'POST',
  77. headers: {
  78. 'Content-Type': 'application/json'
  79. }
  80. }).then(res => res.json())
  81. ));
  82.  
  83. const allSuccess = responses.every(res => res.success === true);
  84.  
  85. if (allSuccess) {
  86. button.innerHTML = '已完成';
  87. const listItems = document.querySelectorAll('.ant-list-item');
  88. listItems.forEach(item => item.style.display = 'none');
  89.  
  90. // 移除全部未读数字
  91. if (removeAllUnread) {
  92. const unreadCounts = document.querySelectorAll('span.unread-count');
  93. unreadCounts.forEach(unreadCount => unreadCount.remove());
  94. } else {
  95. // 移除当前页面的未读数字
  96. const hash = window.location.hash;
  97. let currentSpan;
  98. if (hash.includes('/atMe')) {
  99. currentSpan = `a[href="#/atMe"] span.unread-count`;
  100. } else if (hash.includes('/reply')) {
  101. currentSpan = `a[href="#/reply"] span.unread-count`;
  102. } else if (hash.includes('/message')) {
  103. currentSpan = `a[href^="#/message"] span.unread-count`;
  104. }
  105. const currentPageUnreadCount = document.querySelector(currentSpan);
  106. if (currentPageUnreadCount) {
  107. currentPageUnreadCount.remove();
  108. }
  109. }
  110. } else {
  111. throw new Error('部分请求失败');
  112. }
  113.  
  114. } catch (error) {
  115. console.error('标记已读失败:', error);
  116. button.innerHTML = '失败';
  117. button.style.backgroundColor = '#ff4d4f';
  118. } finally {
  119. setTimeout(() => {
  120. button.innerHTML = originalText;
  121. button.style.backgroundColor = originalBgColor;
  122. }, 1000);
  123. }
  124. }
  125.  
  126. // 仅在启用三页全部已读功能时创建按钮
  127. if (settings.enableTripleRead) {
  128. const button = document.createElement('button');
  129. // 根据设备类型设置不同的按钮文字
  130. const buttonText = window.matchMedia('(max-width: 768px)').matches ? '三页已读' : '三页全部已读';
  131. button.innerHTML = buttonText;
  132. // 基础样式
  133. const baseStyle = `
  134. background-color: #1890ff;
  135. color: white;
  136. border: none;
  137. border-radius: 4px;
  138. cursor: pointer;
  139. font-weight: 900;
  140. transition: all 0.3s;
  141. `;
  142. // 使用媒体查询区分手机端和桌面端
  143. if (window.matchMedia('(max-width: 768px)').matches) {
  144. // 手机端样式
  145. button.style.cssText = `
  146. ${baseStyle}
  147. padding: 6px 10px;
  148. margin: 0 5px;
  149. font-size: 14px;
  150. `;
  151. } else {
  152. // 桌面端样式
  153. button.style.cssText = `
  154. ${baseStyle}
  155. padding: 6px 12px;
  156. margin: 0 20px;
  157. font-size: 16px;
  158. `;
  159. }
  160.  
  161. button.addEventListener('mouseenter', () => {
  162. button.style.backgroundColor = '#40a9ff';
  163. });
  164.  
  165. button.addEventListener('mouseleave', () => {
  166. button.style.backgroundColor = '#1890ff';
  167. });
  168.  
  169. button.addEventListener('click', () => {
  170. const urls = Object.values(apiUrls);
  171. markAsRead(button, buttonText, urls);
  172. });
  173.  
  174. const appSwitch = document.querySelector('.app-switch');
  175. if (appSwitch) {
  176. appSwitch.appendChild(button);
  177. }
  178. }
  179.  
  180. // 处理原有的单个已读按钮
  181. function handleOriginalButtons() {
  182. const originalReadButtons = document.querySelectorAll('button.btn');
  183. originalReadButtons.forEach(btn => {
  184. if (btn.textContent.includes('全部标为已读')) {
  185. if (settings.disableOriginal) {
  186. btn.style.display = 'none';
  187. return;
  188. }
  189.  
  190. // 在手机端修改按钮文字
  191. if (window.matchMedia('(max-width: 768px)').matches) {
  192. btn.textContent = '已读';
  193. }
  194.  
  195. if (settings.enableNoPrompt) {
  196. const newBtn = btn.cloneNode(true);
  197. newBtn.addEventListener('click', (e) => {
  198. e.preventDefault();
  199. e.stopPropagation();
  200.  
  201. const hash = window.location.hash;
  202. let apiUrl;
  203. if (hash.includes('/atMe')) {
  204. apiUrl = apiUrls.atMe;
  205. } else if (hash.includes('/reply')) {
  206. apiUrl = apiUrls.reply;
  207. } else if (hash.includes('/message')) {
  208. apiUrl = apiUrls.message;
  209. }
  210.  
  211. if (apiUrl) {
  212. markAsRead(newBtn, newBtn.textContent, [apiUrl], false);
  213. }
  214. });
  215.  
  216. btn.replaceWith(newBtn);
  217. }
  218. }
  219. });
  220. }
  221.  
  222. function checkAndHandleButtons() {
  223. const checkInterval = setInterval(() => {
  224. const buttons = document.querySelectorAll('button.btn');
  225. if (buttons.length > 0) {
  226. handleOriginalButtons();
  227. clearInterval(checkInterval);
  228. }
  229. }, 500);
  230.  
  231. setTimeout(() => clearInterval(checkInterval), 30000);
  232. }
  233.  
  234. // 检查未读消息,跳转到未读消息页面
  235. function checkAndRedirectUnread() {
  236. if (!settings.enableAutoRedirect) return; // 如果功能未启用,直接返回
  237. const currentHash = window.location.hash;
  238. const routes = [
  239. '#/atMe',
  240. '#/reply',
  241. '#/message'
  242. ];
  243. // 获取当前路由对应的索引
  244. const currentIndex = routes.findIndex(route =>
  245. currentHash.startsWith(route)
  246. );
  247. if (currentIndex === -1) return;
  248.  
  249. // 首先检查当前页面是否有未读
  250. const currentLink = `a[href^="${routes[currentIndex]}"]`;
  251. const currentUnread = document.querySelector(`${currentLink} span.unread-count`);
  252. if (currentUnread) return;
  253.  
  254. // 按顺序检查其他页面
  255. for (let i = 0; i < routes.length; i++) {
  256. if (i === currentIndex) continue;
  257. const link = document.querySelector(`a[href^="${routes[i]}"]`);
  258. const hasUnread = link?.querySelector('span.unread-count');
  259. if (hasUnread) {
  260. link.click();
  261. break;
  262. }
  263. }
  264. }
  265.  
  266. // 在页面加载时检查一次
  267. checkAndRedirectUnread();
  268. checkAndHandleButtons();
  269. // 监听路由变化,并在路由变化时检查按钮
  270. let lastUrl = location.href;
  271. new MutationObserver(() => {
  272. const url = location.href;
  273. if (url !== lastUrl) {
  274. lastUrl = url;
  275. setTimeout(() => {
  276. checkAndHandleButtons();
  277. }, 100);
  278. }
  279. }).observe(document, {subtree: true, childList: true});
  280.  
  281. });
  282. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址