Mark5

为特定用户添加自定义标签,支持本地和远程(GitHub)用户清单。

  1. // ==UserScript==
  2. // @name Mark5
  3. // @namespace https://github.com/yptd-1024
  4. // @version 1.0
  5. // @description 为特定用户添加自定义标签,支持本地和远程(GitHub)用户清单。
  6. // @author yptd-1024
  7. // @match *://*/*
  8. // @license MIT
  9. // @grant GM_getValue
  10. // @grant GM_setValue
  11. // @grant GM_registerMenuCommand
  12. // ==/UserScript==
  13.  
  14.  
  15. (function() {
  16. 'use strict';
  17.  
  18. // 只在 t66y.com 上运行(可通过设置扩展到其他网站)
  19. if (!window.location.hostname.includes('t66y.com')) return;
  20.  
  21. // 工具函数:规范化文本
  22. function normalizeText(text) {
  23. return text.replace(/\s+/g, ' ').trim();
  24. }
  25.  
  26. // 从 GitHub 获取远程用户清单
  27. async function fetchRemoteUserList(repoUrl) {
  28. try {
  29. const response = await fetch(`https://raw.githubusercontent.com/${repoUrl}/main/userlist.txt`);
  30. if (!response.ok) throw new Error('无法获取远程用户清单');
  31. const text = await response.text();
  32. return text.split(',').map(u => normalizeText(u)).filter(u => u.length > 0);
  33. } catch (error) {
  34. return [];
  35. }
  36. }
  37.  
  38. // 主逻辑函数
  39. async function applyTags() {
  40. const userListStr = GM_getValue('userList', '');
  41. const localList = userListStr ? userListStr.split(',').map(u => normalizeText(u)) : [];
  42. const tagContent = GM_getValue('tagContent', '五毛');
  43. const defaultCSS = `
  44. position: absolute;
  45. font-size: 12px;
  46. color: red;
  47. border: 2px solid red;
  48. padding: 3px;
  49. z-index: 9999;
  50. `;
  51. const tagCSS = GM_getValue('tagCSS', defaultCSS);
  52. const listMode = GM_getValue('listMode', 'both'); // 默认改为 'both'
  53. const repoUrl = GM_getValue('repoUrl', 'yptd-1024/mark5');
  54.  
  55. let userList = [];
  56. if (listMode === 'local') {
  57. userList = localList;
  58. } else if (listMode === 'remote') {
  59. userList = await fetchRemoteUserList(repoUrl);
  60. } else if (listMode === 'both') {
  61. const remoteList = await fetchRemoteUserList(repoUrl);
  62. userList = [...new Set([...localList, ...remoteList])];
  63. }
  64.  
  65. if (userList.length === 0) return;
  66.  
  67. const allElements = document.querySelectorAll('th, li span a, td a:not(.w70)');
  68. allElements.forEach((element) => {
  69. let targetElement = element;
  70. let username = normalizeText(element.textContent);
  71.  
  72. const bElement = element.querySelector('b');
  73. if (bElement) {
  74. username = normalizeText(bElement.textContent);
  75. targetElement = bElement;
  76. }
  77.  
  78. if (!username || username === '' || /[\d\/]+/.test(username)) return;
  79.  
  80. if (userList.includes(username) && !element.parentElement.querySelector('.custom-user-tag')) {
  81. const parent = targetElement.parentElement;
  82. if (window.getComputedStyle(parent).position === 'static') {
  83. parent.style.position = 'relative';
  84. }
  85.  
  86. const tag = document.createElement('span');
  87. tag.textContent = tagContent;
  88. tag.style.cssText = tagCSS;
  89. tag.className = 'custom-user-tag';
  90. targetElement.insertAdjacentElement('afterend', tag);
  91. }
  92. });
  93. }
  94.  
  95. // 创建设置弹窗
  96. function createSettingsWindow() {
  97. const existingWindow = document.getElementById('mark5-settings-window');
  98. if (existingWindow) existingWindow.remove();
  99.  
  100. const settingsDiv = document.createElement('div');
  101. settingsDiv.id = 'mark5-settings-window';
  102. settingsDiv.style.cssText = `
  103. position: fixed;
  104. top: 50%;
  105. left: 50%;
  106. transform: translate(-50%, -50%);
  107. background: white;
  108. padding: 20px;
  109. border: 1px solid #ccc;
  110. box-shadow: 0 0 10px rgba(0,0,0,0.3);
  111. z-index: 10000;
  112. font-family: Arial, sans-serif;
  113. width: 350px;
  114. `;
  115.  
  116. settingsDiv.innerHTML = `
  117. <h2>Mark5 设置</h2>
  118. <label>本地用户清单(英文逗号分隔):</label><br>
  119. <textarea id="userList" style="width: 100%; height: 80px;"></textarea><br>
  120. <label>用户清单模式:</label><br>
  121. <select id="listMode" style="width: 100%;">
  122. <option value="local">仅本地</option>
  123. <option value="remote">仅远程</option>
  124. <option value="both">本地和远程</option>
  125. </select><br>
  126. <label>GitHub 仓库地址:</label><br>
  127. <input type="text" id="repoUrl" style="width: 100%;"><br>
  128. <label>标签内容:</label><br>
  129. <input type="text" id="tagContent" style="width: 100%;"><br>
  130. <label>标签 CSS 样式:</label><br>
  131. <textarea id="tagCSS" style="width: 100%; height: 80px;"></textarea><br>
  132. <button id="saveSettings" style="margin-top: 10px;">保存</button>
  133. <button id="closeSettings" style="margin-top: 10px; margin-left: 10px;">关闭</button>
  134. <p id="status" style="color: green;"></p>
  135. `;
  136.  
  137. document.body.appendChild(settingsDiv);
  138.  
  139. document.getElementById('userList').value = GM_getValue('userList', '');
  140. document.getElementById('listMode').value = GM_getValue('listMode', 'both'); // 默认显示 'both'
  141. document.getElementById('repoUrl').value = GM_getValue('repoUrl', 'yptd-1024/mark5');
  142. document.getElementById('tagContent').value = GM_getValue('tagContent', '五毛');
  143. document.getElementById('tagCSS').value = GM_getValue('tagCSS', `
  144. position: absolute;
  145. font-size: 12px;
  146. color: red;
  147. border: 2px solid red;
  148. padding: 3px;
  149. z-index: 9999;
  150. `);
  151.  
  152. document.getElementById('saveSettings').addEventListener('click', () => {
  153. GM_setValue('userList', document.getElementById('userList').value);
  154. GM_setValue('listMode', document.getElementById('listMode').value);
  155. GM_setValue('repoUrl', document.getElementById('repoUrl').value);
  156. GM_setValue('tagContent', document.getElementById('tagContent').value);
  157. GM_setValue('tagCSS', document.getElementById('tagCSS').value || `
  158. position: absolute;
  159. font-size: 12px;
  160. color: red;
  161. border: 2px solid red;
  162. padding: 3px;
  163. z-index: 9999;
  164. `);
  165.  
  166. const status = document.getElementById('status');
  167. status.textContent = '设置已保存!';
  168. setTimeout(() => {
  169. status.textContent = '';
  170. settingsDiv.remove();
  171. applyTags();
  172. }, 1000);
  173. });
  174.  
  175. document.getElementById('closeSettings').addEventListener('click', () => {
  176. settingsDiv.remove();
  177. });
  178. }
  179.  
  180. // 注册菜单命令
  181. GM_registerMenuCommand('Mark5 设置', createSettingsWindow);
  182.  
  183. // 页面初次加载时执行
  184. document.addEventListener('DOMContentLoaded', applyTags);
  185.  
  186. // 监听动态内容变化
  187. const observer = new MutationObserver(applyTags);
  188. observer.observe(document.body, { childList: true, subtree: true });
  189. })();