X 用户备注

为 X 用户添加自定义备注

// ==UserScript==
// @name         X 用户备注
// @namespace    https://github.com/SIXiaolong1117/Rules
// @version      0.1
// @description  为 X 用户添加自定义备注
// @license      MIT
// @icon         https://x.com/favicon.ico
// @author       SI Xiaolong
// @match        https://twitter.com/*
// @match        https://x.com/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// ==/UserScript==

(function() {
    'use strict';

    // 存储用户备注
    const notes = {};

    // 加载所有备注
    function loadNotes() {
        const saved = GM_getValue('userNotes', '{}');
        Object.assign(notes, JSON.parse(saved));
    }

    // 保存备注
    function saveNote(username, note) {
        if (note.trim()) {
            notes[username] = note;
        } else {
            delete notes[username];
        }
        GM_setValue('userNotes', JSON.stringify(notes));
    }

    // 创建备注元素
    function createNoteElement(username, note) {
        const noteSpan = document.createElement('span');
        noteSpan.className = 'user-note-custom';
        noteSpan.style.cssText = `
            margin-left: 6px;
            padding: 2px 8px;
            background: #1d9bf0;
            color: white;
            border-radius: 12px;
            font-size: 13px;
            font-weight: 500;
            cursor: pointer;
        `;
        noteSpan.textContent = note;
        noteSpan.title = '点击编辑备注';

        noteSpan.addEventListener('click', (e) => {
            e.stopPropagation();
            e.preventDefault();
            showEditDialog(username, note);
        });

        return noteSpan;
    }

    // 创建添加备注按钮
    function createAddButton(username) {
        const btn = document.createElement('button');
        btn.className = 'add-note-btn';
        btn.style.cssText = `
            margin-left: 6px;
            padding: 2px 8px;
            background: #eff3f4;
            color: #536471;
            border: none;
            border-radius: 12px;
            font-size: 13px;
            cursor: pointer;
            font-weight: 500;
        `;
        btn.textContent = '+ 备注';
        btn.title = '添加备注';

        btn.addEventListener('click', (e) => {
            e.stopPropagation();
            e.preventDefault();
            showEditDialog(username, '');
        });

        btn.addEventListener('mouseenter', () => {
            btn.style.background = '#e7e9ea';
        });

        btn.addEventListener('mouseleave', () => {
            btn.style.background = '#eff3f4';
        });

        return btn;
    }

    // 显示编辑对话框
    function showEditDialog(username, currentNote) {
        const dialog = document.createElement('div');
        dialog.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.5);
            display: flex;
            align-items: center;
            justify-content: center;
            z-index: 10000;
        `;

        const box = document.createElement('div');
        box.style.cssText = `
            background: white;
            padding: 24px;
            border-radius: 16px;
            width: 400px;
            max-width: 90%;
        `;

        box.innerHTML = `
            <h2 style="margin: 0 0 16px 0; font-size: 20px; font-weight: 700;">
                为 @${username} 添加备注
            </h2>
            <input type="text" id="noteInput" value="${currentNote}"
                   style="width: 100%; padding: 12px; border: 1px solid #cfd9de;
                          border-radius: 8px; font-size: 15px; box-sizing: border-box;"
                   placeholder="输入备注内容" />
            <div style="margin-top: 16px; display: flex; gap: 12px; justify-content: flex-end;">
                <button id="cancelBtn" style="padding: 10px 20px; border: 1px solid #cfd9de;
                        background: white; border-radius: 20px; cursor: pointer; font-weight: 600;">
                    取消
                </button>
                <button id="deleteBtn" style="padding: 10px 20px; border: 1px solid #f4212e;
                        background: white; color: #f4212e; border-radius: 20px; cursor: pointer; font-weight: 600;">
                    删除备注
                </button>
                <button id="saveBtn" style="padding: 10px 20px; border: none;
                        background: #1d9bf0; color: white; border-radius: 20px; cursor: pointer; font-weight: 600;">
                    保存
                </button>
            </div>
        `;

        dialog.appendChild(box);
        document.body.appendChild(dialog);

        const input = box.querySelector('#noteInput');
        input.focus();
        input.select();

        const save = () => {
            saveNote(username, input.value);
            document.body.removeChild(dialog);
            updateAllNotes();
        };

        box.querySelector('#saveBtn').addEventListener('click', save);
        box.querySelector('#cancelBtn').addEventListener('click', () => {
            document.body.removeChild(dialog);
        });
        box.querySelector('#deleteBtn').addEventListener('click', () => {
            saveNote(username, '');
            document.body.removeChild(dialog);
            updateAllNotes();
        });

        input.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') save();
        });

        dialog.addEventListener('click', (e) => {
            if (e.target === dialog) {
                document.body.removeChild(dialog);
            }
        });
    }

    // 处理用户名元素
    function processUserElement(element) {
        if (element.hasAttribute('data-note-processed')) return;
        element.setAttribute('data-note-processed', 'true');

        const linkElement = element.querySelector('a[href^="/"]');
        if (!linkElement) return;

        const href = linkElement.getAttribute('href');
        const match = href.match(/^\/([^\/]+)/);
        if (!match) return;

        const username = match[1];
        if (!username || username === 'i' || username === 'home') return;

        const existingNote = element.querySelector('.user-note-custom');
        if (existingNote) existingNote.remove();

        const existingBtn = element.querySelector('.add-note-btn');
        if (existingBtn) existingBtn.remove();

        const note = notes[username];
        const targetElement = element.querySelector('[dir="ltr"]') || element;

        if (note) {
            targetElement.appendChild(createNoteElement(username, note));
        } else {
            targetElement.appendChild(createAddButton(username));
        }
    }

    // 更新所有备注
    function updateAllNotes() {
        document.querySelectorAll('[data-note-processed]').forEach(el => {
            el.removeAttribute('data-note-processed');
        });
        observeTimeline();
    }

    // 监听时间线变化
    function observeTimeline() {
        const selectors = [
            '[data-testid="User-Name"]',
            '[data-testid="UserName"]',
            '[data-testid="UserCell"]'
        ];

        selectors.forEach(selector => {
            document.querySelectorAll(selector).forEach(processUserElement);
        });
    }

    // 初始化
    loadNotes();

    // 使用 MutationObserver 监听 DOM 变化
    const observer = new MutationObserver(() => {
        observeTimeline();
    });

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

    // 初始扫描
    setTimeout(observeTimeline, 1000);

    // 定期扫描
    setInterval(observeTimeline, 2000);
})();