Mark5

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

目前为 2025-03-27 提交的版本。查看 最新版本

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