// ==UserScript==
// @name M-team 色盲辅助
// @namespace http://tampermonkey.net/
// @version 1.2
// @description 在m-team.cc网站根据颜色添加用户头衔
// @author You
// @match https://m-team.cc/*
// @match https://*.m-team.cc/*
// @grant none
// @license MIT
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
// 颜色和对应标签的配置字典
const COLOR_LABELS = {
'rgb(51, 51, 51)': '[小卒1]',
'rgb(218, 165, 32)': '[捕頭2]',
'rgb(0, 139, 139)': '[知縣3]',
'rgb(0, 191, 255)': '[通判4]',
'rgb(139, 0, 139)': '[知州5]',
'rgb(72, 61, 139)': '[府丞6]',
'rgb(255, 140, 0)': '[府尹7]',
'rgb(0, 100, 0)': '[總督8]',
'rgb(236, 56, 78)': '[大臣9]',
'rgb(0, 159, 0)': '[VIP]',
'rgb(220, 20, 60)': '[職人]',
'rgb(28, 198, 213)': '[巡查]',
'rgb(100, 149, 237)': '[總版]',
'rgb(75, 0, 130)': '[總管]',
'rgb(160, 82, 45)': '[維護開發員]',
'rgb(139, 0, 0)': '[站長]',
'rgb(156, 67, 67)': '[候選管理]',
'rgb(88, 55, 55)': '[波菜管理]',
};
// 处理单个span元素
function processSpan(span) {
const computedStyle = window.getComputedStyle(span);
const color = computedStyle.color;
// 检查颜色是否在配置中
if (COLOR_LABELS.hasOwnProperty(color)) {
// 检查是否仅包含一个strong子元素
const strongElements = span.querySelectorAll('strong');
if (strongElements.length === 1 && span.children.length === 1) {
const strong = strongElements[0];
const label = COLOR_LABELS[color];
// 检查是否已经添加过标签,避免重复添加
if (!strong.textContent.includes(label)) {
strong.textContent += label;
}
// 保存原始用户名和等级信息到span的data属性中,供tooltip使用
if (!span.dataset.originalUsername) {
span.dataset.originalUsername = strong.textContent.replace(label, '');
span.dataset.userLevel = label;
}
}
}
}
// 处理tooltip元素
function processTooltip(tooltip) {
// 查找tooltip中的文本内容
const textNodes = [];
const walker = document.createTreeWalker(
tooltip,
NodeFilter.SHOW_TEXT,
null,
false
);
let node;
while (node = walker.nextNode()) {
if (node.textContent.trim()) {
textNodes.push(node);
}
}
// 遍历所有用户等级,看tooltip文本是否包含用户名
for (const [color, label] of Object.entries(COLOR_LABELS)) {
// 查找页面上对应颜色的span元素
const spans = document.querySelectorAll('span');
spans.forEach(span => {
const computedStyle = window.getComputedStyle(span);
if (computedStyle.color === color && span.dataset.originalUsername) {
const username = span.dataset.originalUsername;
const userLevel = span.dataset.userLevel;
// 检查tooltip是否包含这个用户名
textNodes.forEach(textNode => {
if (textNode.textContent.includes(username) && !textNode.textContent.includes(userLevel)) {
// 在tooltip中添加等级信息
textNode.textContent = textNode.textContent.replace(username, username + userLevel);
}
});
}
});
}
}
// 创建一个全局的用户名-等级映射
const usernameToLevel = new Map();
// 更新用户名映射
function updateUsernameMap() {
const spans = document.querySelectorAll('span');
spans.forEach(span => {
const computedStyle = window.getComputedStyle(span);
const color = computedStyle.color;
if (COLOR_LABELS.hasOwnProperty(color)) {
const strongElements = span.querySelectorAll('strong');
if (strongElements.length === 1 && span.children.length === 1) {
const strong = strongElements[0];
const label = COLOR_LABELS[color];
const username = strong.textContent.replace(label, '');
if (username && !usernameToLevel.has(username)) {
usernameToLevel.set(username, label);
}
}
}
});
}
// 转义正则表达式特殊字符
function escapeRegExp(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
// 处理tooltip的新方法 - 使用全局映射
function processTooltipWithMapping(tooltip) {
if (!tooltip || tooltip.dataset.processed) return;
// 标记已处理,避免重复处理
tooltip.dataset.processed = 'true';
// 延迟处理,确保tooltip内容完全加载
setTimeout(() => {
const walker = document.createTreeWalker(
tooltip,
NodeFilter.SHOW_TEXT,
null,
false
);
let node;
while (node = walker.nextNode()) {
if (node.textContent && node.textContent.trim()) {
let text = node.textContent;
let modified = false;
// 检查所有已知用户名
for (const [username, level] of usernameToLevel) {
if (text.includes(username) && !text.includes(level)) {
// 转义用户名中的特殊字符,避免正则表达式错误
const escapedUsername = escapeRegExp(username);
text = text.replace(new RegExp(escapedUsername, 'g'), username + level);
modified = true;
}
}
if (modified) {
node.textContent = text;
}
}
}
}, 50);
}
// 处理所有符合条件的span元素和tooltip
function processAllElements() {
// 处理用户名span元素
const spans = document.querySelectorAll('span');
spans.forEach(processSpan);
// 更新用户名映射
updateUsernameMap();
// 处理已存在的tooltip元素
const tooltipSelectors = [
'[role="tooltip"]',
'.tooltip',
'.ant-tooltip',
'.rc-tooltip',
'[aria-describedby]',
'.tippy-content',
'[data-tooltip]'
];
tooltipSelectors.forEach(selector => {
const tooltips = document.querySelectorAll(selector);
tooltips.forEach(processTooltipWithMapping);
});
// 也处理可能是tooltip容器的div
const possibleTooltips = document.querySelectorAll('div[style*="position: absolute"], div[style*="position: fixed"]');
possibleTooltips.forEach(tooltip => {
// 简单检查是否可能是tooltip(包含用户名相关文本)
if (tooltip.textContent && tooltip.textContent.length < 200) {
processTooltipWithMapping(tooltip);
}
});
}
// 初始处理
processAllElements();
// 监听DOM变化,处理动态加载的内容
const observer = new MutationObserver(function(mutations) {
let shouldProcess = false;
let shouldProcessTooltip = false;
mutations.forEach(function(mutation) {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
// 检查是否有新增的节点
for (let node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE) {
// 如果新增的是span元素或包含这些元素
if (node.tagName === 'SPAN' || node.querySelector('span')) {
shouldProcess = true;
}
// 检查是否是tooltip相关元素
if (node.hasAttribute('role') && node.getAttribute('role') === 'tooltip' ||
node.className && (node.className.includes('tooltip') ||
node.className.includes('ant-tooltip') ||
node.className.includes('rc-tooltip')) ||
node.hasAttribute('aria-describedby') ||
(node.style && (node.style.position === 'absolute' || node.style.position === 'fixed')) ||
node.querySelector('[role="tooltip"]') ||
node.querySelector('.tooltip, .ant-tooltip, .rc-tooltip, .tippy-content')) {
shouldProcessTooltip = true;
}
}
}
}
});
if (shouldProcess) {
// 延迟处理用户名span
setTimeout(processAllElements, 100);
}
if (shouldProcessTooltip) {
// 立即处理tooltip,因为它们通常是临时的
setTimeout(() => {
// 更新映射
updateUsernameMap();
// 处理新出现的tooltip
const tooltipSelectors = [
'[role="tooltip"]:not([data-processed])',
'.tooltip:not([data-processed])',
'.ant-tooltip:not([data-processed])',
'.rc-tooltip:not([data-processed])',
'[aria-describedby]:not([data-processed])',
'.tippy-content:not([data-processed])',
'[data-tooltip]:not([data-processed])'
];
tooltipSelectors.forEach(selector => {
const tooltips = document.querySelectorAll(selector);
tooltips.forEach(processTooltipWithMapping);
});
// 处理新的绝对定位元素
const newTooltips = document.querySelectorAll('div[style*="position: absolute"]:not([data-processed]), div[style*="position: fixed"]:not([data-processed])');
newTooltips.forEach(tooltip => {
if (tooltip.textContent && tooltip.textContent.length < 200) {
processTooltipWithMapping(tooltip);
}
});
}, 10);
}
});
// 开始观察
observer.observe(document.body, {
childList: true,
subtree: true
});
console.log('M-team 颜色标签添加器已启动');
})();