Mark5

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

// ==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 });
})();