Replace Twitter/X Emojis with System Emojis

Replace all Twitter emoji images (Twemoji) on x.com with native system emojis.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Replace Twitter/X Emojis with System Emojis
// @namespace    http://tampermonkey.net/
// @version      0.3  
// @description  Replace all Twitter emoji images (Twemoji) on x.com with native system emojis.
// @author       Your Name (Updated)
// @match        https://x.com/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Selector for Twitter's emoji images (covers SVG and potentially PNG)
    const EMOJI_SELECTOR = 'img[src*="/emoji/v2/"][alt]'; // Added alt requirement to selector

    /**
     * Replaces a given Twitter emoji IMG node with a SPAN containing the native system emoji.
     * @param {HTMLImageElement} imgNode The emoji image element to replace.
     */
    function replaceEmojiNode(imgNode) {
        // Check if already processed or lacks necessary attributes
        if (imgNode.dataset.emojiReplaced || !imgNode.parentNode || !imgNode.alt) {
            return;
        }

        const alt = imgNode.alt; // The actual emoji character(s)

        const span = document.createElement('span');
        span.textContent = alt; // Use the exact alt text (handles single/multiple emojis)
        span.setAttribute('role', 'img'); // Accessibility: Treat span like an image
        span.setAttribute('aria-label', alt); // Accessibility: Provide label

        // --- Styling ---
        // 1. Basic inline behavior and alignment (adjust if needed)
        span.style.display = 'inline-block'; // Or 'inline' might work depending on context
        span.style.verticalAlign = 'text-bottom'; // Common alignment, adjust based on visual tests

        // 2. Try to match the size of the original emoji image
        try {
            const computedStyle = window.getComputedStyle(imgNode);
            const height = computedStyle.height;
            // const width = computedStyle.width; // Width can be tricky with multiple chars, primarily use height

            // Use height for font size and line height for better vertical centering and scaling
            if (height && height !== 'auto' && parseFloat(height) > 0) {
                span.style.fontSize = height;
                span.style.lineHeight = height; // Match line height to font size for vertical centering
                span.style.height = height;     // Explicit height
                // Setting width can sometimes be problematic for multi-character emojis or variable-width system emojis.
                // Let the browser determine width based on content and font size unless specific issues arise.
                // span.style.width = 'auto'; // Allow natural width based on font/content
            } else {
                // Fallback size if computed style is unavailable or invalid
                span.style.fontSize = '1em'; // Inherit from parent or use a reasonable default
                span.style.lineHeight = '1';   // Normal line height
            }
            // Copy other potentially relevant styles (optional, can add complexity)
            // span.style.margin = computedStyle.margin;

        } catch (e) {
            console.warn("Replace Emojis Script: Couldn't get computed style for emoji, using default size.", e);
            // Apply fallback size on error
            span.style.fontSize = '1em';
            span.style.lineHeight = '1';
        }

        // Mark the original node as processed BEFORE replacing it
        imgNode.dataset.emojiReplaced = 'true';
        imgNode.style.display = 'none'; // Hide original immediately to prevent flash

        // Replace the image node with the new span node
        imgNode.parentNode.replaceChild(span, imgNode);
        // console.log(`Replaced emoji: ${alt}`); // For debugging
    }

    /**
     * Scans a given node and its descendants for emoji images and replaces them.
     * @param {Node} targetNode The node to scan.
     */
    function processNode(targetNode) {
        if (!targetNode || !targetNode.querySelectorAll) return;

        // Find all emoji images within the target node that haven't been replaced
        const emojiImages = targetNode.querySelectorAll(EMOJI_SELECTOR + ':not([data-emoji-replaced="true"])');
        emojiImages.forEach(replaceEmojiNode);
    }

    // --- Mutation Observer ---
    // Observe DOM changes to catch dynamically loaded content
    const observer = new MutationObserver(mutations => {
        mutations.forEach(mutation => {
            if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                mutation.addedNodes.forEach(node => {
                    // Check if the added node itself is an emoji
                    if (node.nodeType === Node.ELEMENT_NODE && node.matches(EMOJI_SELECTOR)) {
                        replaceEmojiNode(node);
                    }
                    // Check if the added node contains emojis
                    else if (node.nodeType === Node.ELEMENT_NODE && node.querySelector) {
                        processNode(node);
                    }
                });
            }
            // Optional: Handle attribute changes if emoji src/alt might change later
            // else if (mutation.type === 'attributes' && mutation.attributeName === 'src' && mutation.target.matches(EMOJI_SELECTOR)) {
            //     // Re-evaluate if needed, but usually replacement is permanent
            //     // Be careful not to cause infinite loops if you modify attributes observer listens to
            // }
        });
    });

    // Start observing the document body for additions and subtree modifications
    observer.observe(document.body, {
        childList: true,
        subtree: true
        // attributes: true, // Uncomment cautiously if needed
        // attributeFilter: ['src', 'alt'] // Specify attributes if uncommenting 'attributes'
    });

    // --- Initial Scan ---
    // Process existing emojis present on the page when the script initially runs
    console.log("Replace Emojis Script: Running initial scan...");
    processNode(document.body);
    console.log("Replace Emojis Script: Initial scan complete. Observer active.");

})();