US Card Forum User Tag

Tag users on US Card Forum with personal labels

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         US Card Forum User Tag
// @namespace    https://www.uscardforum.com/
// @version      1.2
// @description  Tag users on US Card Forum with personal labels
// @author       Your Name
// @match        https://www.uscardforum.com/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Configuration
    const STORAGE_KEY = 'uscardforum.user_tags.v1';
    const TAG_STYLE = 'background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 4px; padding: 2px 8px; margin-left: 8px; font-size: 11px; font-weight: 600; display: inline-block; box-shadow: 0 2px 4px rgba(0,0,0,0.2); vertical-align: middle;';
    const BUTTON_STYLE = 'background: #667eea; color: white; border: none; border-radius: 4px; padding: 3px 8px; margin-left: 8px; cursor: pointer; font-size: 11px; transition: all 0.2s; vertical-align: middle;';

    // Storage functions
    function getTagMap() {
        const tagJsonStr = GM_getValue(STORAGE_KEY, '{}');
        return JSON.parse(tagJsonStr);
    }

    function saveTagMap(tagMap) {
        GM_setValue(STORAGE_KEY, JSON.stringify(tagMap));
    }

    function deleteTag(username) {
        const tagMap = getTagMap();
        delete tagMap[username];
        saveTagMap(tagMap);
    }

    // Create and display tag element
    function createTagElement(tagText) {
        const tagSpan = document.createElement('span');
        tagSpan.textContent = tagText;
        tagSpan.setAttribute('style', TAG_STYLE);
        tagSpan.className = 'user-custom-tag';
        return tagSpan;
    }

    // Create tag button
    function createTagButton(username, containerElement) {
        const tagMap = getTagMap();

        const button = document.createElement('button');
        button.textContent = '🏷️';
        button.className = 'tag-user-btn';
        button.setAttribute('style', BUTTON_STYLE);
        button.title = 'Tag this user';

        button.addEventListener('mouseenter', () => {
            button.style.background = '#5568d3';
        });
        button.addEventListener('mouseleave', () => {
            button.style.background = '#667eea';
        });

        button.addEventListener('click', (e) => {
            e.preventDefault();
            e.stopPropagation();

            const currentTag = tagMap[username] || '';
            const newTag = prompt(
                `Edit tag for user: ${username}\n\nEnter tags (comma-separated):\nExamples: "excellent debater", "helpful", "expert"`,
                currentTag
            );

            if (newTag !== null) {
                if (newTag.trim() === '') {
                    if (currentTag && confirm(`Remove all tags for ${username}?`)) {
                        deleteTag(username);
                        location.reload();
                    }
                } else {
                    tagMap[username] = newTag;
                    saveTagMap(tagMap);
                    location.reload();
                }
            }
        });

        return button;
    }

    // Add tags and button to a user element
    function processUserElement(userLink) {
        // Avoid processing twice
        if (userLink.hasAttribute('data-tag-processed')) {
            return;
        }
        userLink.setAttribute('data-tag-processed', 'true');

        // Skip if inside avatar elements
        if (userLink.closest('.post-avatar') || userLink.closest('.topic-avatar') || userLink.closest('.topic-poster') || userLink.closest('.topic-list-data')) {
            return;
        }

        const username = userLink.getAttribute('data-user-card') || userLink.textContent.trim();
        if (!username) return;

        const tagMap = getTagMap();
        const parentSpan = userLink.closest('.first.username') || userLink.parentElement;

        if (!parentSpan) return;

        // Add existing tags
        if (tagMap[username]) {
            const tags = tagMap[username].split(',').map(t => t.trim()).filter(t => t);
            tags.forEach(tag => {
                const tagElement = createTagElement(tag);
                parentSpan.appendChild(tagElement);
            });
        }

        // Add tag button
        const button = createTagButton(username, parentSpan);
        parentSpan.appendChild(button);
    }

    // Process all user links in posts
    function processPosts() {
        // Find all user links in post headers
        const userLinks = document.querySelectorAll('a[data-user-card][href^="/u/"]');
        userLinks.forEach(link => {
            processUserElement(link);
        });
    }

    // Process user profile page
    function processUserProfile() {
        // Profile page has different structure
        const profileHeader = document.querySelector('.user-profile-names .username');
        if (profileHeader && !profileHeader.hasAttribute('data-tag-processed')) {
            profileHeader.setAttribute('data-tag-processed', 'true');

            const username = profileHeader.textContent.trim();
            const tagMap = getTagMap();

            // Add tags
            if (tagMap[username]) {
                const tags = tagMap[username].split(',').map(t => t.trim()).filter(t => t);
                tags.forEach(tag => {
                    const tagElement = createTagElement(tag);
                    profileHeader.appendChild(tagElement);
                });
            }

            // Add button
            const button = createTagButton(username, profileHeader);
            profileHeader.appendChild(button);
        }
    }

    // Process user cards (popup when clicking username)
    function processUserCards() {
        const userCards = document.querySelectorAll('.user-card .names-link, .user-card .username');
        userCards.forEach(nameElement => {
            if (nameElement.hasAttribute('data-tag-processed')) return;
            nameElement.setAttribute('data-tag-processed', 'true');

            const username = nameElement.textContent.trim();
            const tagMap = getTagMap();

            // Add tags
            if (tagMap[username]) {
                const tags = tagMap[username].split(',').map(t => t.trim()).filter(t => t);
                tags.forEach(tag => {
                    const tagElement = createTagElement(tag);
                    nameElement.appendChild(tagElement);
                });
            }

            // Add button
            const button = createTagButton(username, nameElement);
            nameElement.appendChild(button);
        });
    }

    // Process topic lists
    function processTopicLists() {
        // Topic list poster names
        const posterLinks = document.querySelectorAll('.topic-list .posters a[data-user-card], .latest-topic-list-item a[data-user-card]');
        posterLinks.forEach(link => {
            if (link.hasAttribute('data-tag-processed')) return;
            link.setAttribute('data-tag-processed', 'true');

            const username = link.getAttribute('data-user-card') || link.getAttribute('title');
            if (!username) return;

            const tagMap = getTagMap();
            if (tagMap[username]) {
                const tags = tagMap[username].split(',').map(t => t.trim()).filter(t => t);
                tags.forEach(tag => {
                    const tagElement = createTagElement(tag);
                    tagElement.style.marginLeft = '4px';
                    link.parentElement.appendChild(tagElement);
                });
            }
        });
    }

    // Main processing function
    function processAll() {
        processPosts();
        processUserProfile();
        processUserCards();
        processTopicLists();
    }

    // Observer for dynamic content
    const observer = new MutationObserver((mutations) => {
        // Debounce to avoid excessive processing
        clearTimeout(window.tagProcessTimeout);
        window.tagProcessTimeout = setTimeout(() => {
            processAll();
        }, 100);
    });

    // Initialize
    function init() {
        console.log('US Card Forum User Tag script initialized');
        processAll();

        // Start observing
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        console.log('Found', document.querySelectorAll('a[data-user-card][href^="/u/"]').length, 'user links');
    }

    // Wait for page to load
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();