Mark5

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

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(У мене вже є менеджер скриптів, дайте мені встановити його!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Mark5
// @namespace    https://github.com/yptd-1024
// @version      1.0
// @description  为特定用户添加自定义标签,支持本地和远程(GitHub)用户清单。
// @author       yptd-1024
// @match        *://*/*
// @license      MIT
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// ==/UserScript==


(function() {
    'use strict';

    // 只在 t66y.com 上运行(可通过设置扩展到其他网站)
    if (!window.location.hostname.includes('t66y.com')) return;

    // 工具函数:规范化文本
    function normalizeText(text) {
        return text.replace(/\s+/g, ' ').trim();
    }

    // 从 GitHub 获取远程用户清单
    async function fetchRemoteUserList(repoUrl) {
        try {
            const response = await fetch(`https://raw.githubusercontent.com/${repoUrl}/main/userlist.txt`);
            if (!response.ok) throw new Error('无法获取远程用户清单');
            const text = await response.text();
            return text.split(',').map(u => normalizeText(u)).filter(u => u.length > 0);
        } catch (error) {
            return [];
        }
    }

    // 主逻辑函数
    async function applyTags() {
        const userListStr = GM_getValue('userList', '');
        const localList = userListStr ? userListStr.split(',').map(u => normalizeText(u)) : [];
        const tagContent = GM_getValue('tagContent', '五毛');
        const defaultCSS = `
            position: absolute;
            font-size: 12px;
            color: red;
            border: 2px solid red;
            padding: 3px;
            z-index: 9999;
        `;
        const tagCSS = GM_getValue('tagCSS', defaultCSS);
        const listMode = GM_getValue('listMode', 'both'); // 默认改为 'both'
        const repoUrl = GM_getValue('repoUrl', 'yptd-1024/mark5');

        let userList = [];
        if (listMode === 'local') {
            userList = localList;
        } else if (listMode === 'remote') {
            userList = await fetchRemoteUserList(repoUrl);
        } else if (listMode === 'both') {
            const remoteList = await fetchRemoteUserList(repoUrl);
            userList = [...new Set([...localList, ...remoteList])];
        }

        if (userList.length === 0) return;

        const allElements = document.querySelectorAll('th, li span a, td a:not(.w70)');
        allElements.forEach((element) => {
            let targetElement = element;
            let username = normalizeText(element.textContent);

            const bElement = element.querySelector('b');
            if (bElement) {
                username = normalizeText(bElement.textContent);
                targetElement = bElement;
            }

            if (!username || username === '' || /[\d\/]+/.test(username)) return;

            if (userList.includes(username) && !element.parentElement.querySelector('.custom-user-tag')) {
                const parent = targetElement.parentElement;
                if (window.getComputedStyle(parent).position === 'static') {
                    parent.style.position = 'relative';
                }

                const tag = document.createElement('span');
                tag.textContent = tagContent;
                tag.style.cssText = tagCSS;
                tag.className = 'custom-user-tag';
                targetElement.insertAdjacentElement('afterend', tag);
            }
        });
    }

    // 创建设置弹窗
    function createSettingsWindow() {
        const existingWindow = document.getElementById('mark5-settings-window');
        if (existingWindow) existingWindow.remove();

        const settingsDiv = document.createElement('div');
        settingsDiv.id = 'mark5-settings-window';
        settingsDiv.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            padding: 20px;
            border: 1px solid #ccc;
            box-shadow: 0 0 10px rgba(0,0,0,0.3);
            z-index: 10000;
            font-family: Arial, sans-serif;
            width: 350px;
        `;

        settingsDiv.innerHTML = `
            <h2>Mark5 设置</h2>
            <label>本地用户清单(英文逗号分隔):</label><br>
            <textarea id="userList" style="width: 100%; height: 80px;"></textarea><br>
            <label>用户清单模式:</label><br>
            <select id="listMode" style="width: 100%;">
                <option value="local">仅本地</option>
                <option value="remote">仅远程</option>
                <option value="both">本地和远程</option>
            </select><br>
            <label>GitHub 仓库地址:</label><br>
            <input type="text" id="repoUrl" style="width: 100%;"><br>
            <label>标签内容:</label><br>
            <input type="text" id="tagContent" style="width: 100%;"><br>
            <label>标签 CSS 样式:</label><br>
            <textarea id="tagCSS" style="width: 100%; height: 80px;"></textarea><br>
            <button id="saveSettings" style="margin-top: 10px;">保存</button>
            <button id="closeSettings" style="margin-top: 10px; margin-left: 10px;">关闭</button>
            <p id="status" style="color: green;"></p>
        `;

        document.body.appendChild(settingsDiv);

        document.getElementById('userList').value = GM_getValue('userList', '');
        document.getElementById('listMode').value = GM_getValue('listMode', 'both'); // 默认显示 'both'
        document.getElementById('repoUrl').value = GM_getValue('repoUrl', 'yptd-1024/mark5');
        document.getElementById('tagContent').value = GM_getValue('tagContent', '五毛');
        document.getElementById('tagCSS').value = GM_getValue('tagCSS', `
            position: absolute;
            font-size: 12px;
            color: red;
            border: 2px solid red;
            padding: 3px;
            z-index: 9999;
        `);

        document.getElementById('saveSettings').addEventListener('click', () => {
            GM_setValue('userList', document.getElementById('userList').value);
            GM_setValue('listMode', document.getElementById('listMode').value);
            GM_setValue('repoUrl', document.getElementById('repoUrl').value);
            GM_setValue('tagContent', document.getElementById('tagContent').value);
            GM_setValue('tagCSS', document.getElementById('tagCSS').value || `
                position: absolute;
                font-size: 12px;
                color: red;
                border: 2px solid red;
                padding: 3px;
                z-index: 9999;
            `);

            const status = document.getElementById('status');
            status.textContent = '设置已保存!';
            setTimeout(() => {
                status.textContent = '';
                settingsDiv.remove();
                applyTags();
            }, 1000);
        });

        document.getElementById('closeSettings').addEventListener('click', () => {
            settingsDiv.remove();
        });
    }

    // 注册菜单命令
    GM_registerMenuCommand('Mark5 设置', createSettingsWindow);

    // 页面初次加载时执行
    document.addEventListener('DOMContentLoaded', applyTags);

    // 监听动态内容变化
    const observer = new MutationObserver(applyTags);
    observer.observe(document.body, { childList: true, subtree: true });
})();