Animate Emoji for Drawaria

Adds a stylish, draggable menu with an internal data source for selecting animated emojis and stamping them on the Drawaria.online canvas.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Animate Emoji for Drawaria
// @namespace    Tampermonkey
// @version      3.0
// @description  Adds a stylish, draggable menu with an internal data source for selecting animated emojis and stamping them on the Drawaria.online canvas.
// @author       YouTubeDrawaria / Gemini
// @homepage     https://github.com/quarrel/animate-web-emoji
// @match        https://drawaria.online/
// @match        https://*.drawaria.online/*
// @match        https://drawaria.online/test
// @match        https://drawaria.online/room/*
// @run-at       document-idle
// @icon         https://fonts.gstatic.com/s/e/notoemoji/latest/1f603/512.webp
// @license      MIT
// @noframes
// @resource     LOTTIE_BACKUP_PUREJS_PLAYER_URL https://cdnjs.cloudflare.com/ajax/libs/lottie-web/5.12.2/lottie_canvas.min.js
// @grant        GM.getResourceUrl
// @grant        GM.addStyle
// @grant        GM.xmlHttpRequest
// @connect      cdn.jsdelivr.net
// ==/UserScript==

/* globals lottie */

(function () {
    'use strict';

    const scriptStartTime = Date.now();
    const config = {
        DEBUG_MODE: false,
        UNIQUE_EMOJI_CLASS: 'animated-emoji-q',
    };

    let emojiData = []; // New structure to hold all emoji data
    let codepointToLottie = new Map();
    let isPureLottiePlayerLoaded = false;

    // --- DATOS INTERNOS DE EMOJIS (HARDCODED) ---
    // C: Codepoint | E: Emoji Character | D: Description (for search)
    const ANIMATED_EMOJIS_DATA = [
        // Smileys and Emotion
        { c: '1f600', e: '😀', d: 'Smile' }, { c: '1f603', e: '😃', d: 'Smile big eyes' },
        { c: '1f604', e: '😄', d: 'Grin' }, { c: '1f601', e: '😁', d: 'Grinning' },
        { c: '1f606', e: '😆', d: 'Laughing' }, { c: '1f605', e: '😅', d: 'Grin sweat' },
        { c: '1f602', e: '😂', d: 'Joy tears' }, { c: '1f923', e: '🤣', d: 'Rofl' },
        { c: '1f62d', e: '😭', d: 'Loudly crying' }, { c: '1f609', e: '😉', d: 'Wink' },
        { c: '1f617', e: '😗', d: 'Kissing' }, { c: '1f619', e: '😙', d: 'Kissing smiling eyes' },
        { c: '1f61a', e: '😚', d: 'Kissing closed eyes' }, { c: '1f618', e: '😘', d: 'Kissing heart' },
        { c: '1f970', e: '🥰', d: 'Heart face' }, { c: '1f60d', e: '😍', d: 'Heart eyes' },
        { c: '1f929', e: '🤩', d: 'Star struck' }, { c: '1f973', e: '🥳', d: 'Partying face' },
        { c: '1fae0', e: '🫠', d: 'Melting' }, { c: '1f643', e: '🙃', d: 'Upside down' },
        { c: '1f642', e: '🙂', d: 'Slightly happy' }, { c: '1f972', e: '🥹', d: 'Happy cry' },
        { c: '1f979', e: '🥺', d: 'Holding back tears' }, { c: '1f60a', e: '😊', d: 'Blush' },
        { c: '1f631', e: '😱', d: 'Scream fear' }, { c: '1f60c', e: '😌', d: 'Relieved' },
        { c: '1f60b', e: '😋', d: 'Yummy food' }, { c: '1f61b', e: '😛', d: 'Tongue' },
        { c: '1f911', e: '🤑', d: 'Money face' }, { c: '1f974', e: '🤯', d: 'Exploding head' },
        { c: '1f92a', e: '🤪', d: 'Crazy wacky' }, { c: '1f92b', e: '🤫', d: 'Shushing' },
        { c: '1f92d', e: '🤦', d: 'Facepalm' }, { c: '1f97a', e: '🥺', d: 'Pleading face' },
        { c: '1f64f', e: '🙏', d: 'Praying hands' }, { c: '1f44f', e: '👏', d: 'Clapping hands' },

        // Hands and Symbols
        { c: '1f44d', e: '👍', d: 'Thumbs up' }, { c: '1f44e', e: '👎', d: 'Thumbs down' },
        { c: '1f44c', e: '👌', d: 'OK hand' }, { c: '1f44a', e: '✊', d: 'Raised fist' },
        { c: '1f496', e: '💖', d: 'Sparkling heart' }, { c: '1f499', e: '💙', d: 'Blue heart' },
        { c: '1f4af', e: '💯', d: 'Hundred points' }, { c: '1f525', e: '🔥', d: 'Fire flame hot' },
        { c: '1f389', e: '🎉', d: 'Party popper' }, { c: '1f4a9', e: '💩', d: 'Poop pile' },
        { c: '1f31f', e: '🌟', d: 'Glowing star' }, { c: '1f47d', e: '👽', d: 'Alien monster' },
    ];


    // --- UTILITIES & INITIALIZATION ---

    function loadScript(url, id, callback) {
        if (document.getElementById(id)) {
            if (callback) callback();
            return;
        }
        const script = document.createElement('script');
        script.id = id;
        script.src = url;
        script.onload = () => {
             if (id === 'lottie-canvas-player') isPureLottiePlayerLoaded = true;
             if (callback) callback();
        };
        document.head.appendChild(script);
    }

    async function getLottieAnimationData(codepoint) {
        if (codepointToLottie.has(codepoint)) {
            return codepointToLottie.get(codepoint);
        }

        try {
            // Animation JSONs are consistently hosted here by the original project
            const lottieUrl = `https://cdn.jsdelivr.net/gh/quarrel/noto-emoji-animation-web/emoji/${codepoint}.json`;

            // Use GM.xmlHttpRequest for cross-origin fetch for reliable data loading
            const response = await new Promise((resolve, reject) => {
                GM.xmlHttpRequest({
                    method: 'GET',
                    url: lottieUrl,
                    onload: (res) => resolve(res),
                    onerror: (err) => reject(err),
                });
            });

            const data = JSON.parse(response.responseText);
            codepointToLottie.set(codepoint, data);
            return data;
        } catch (error) {
            if (config.DEBUG_MODE) {
                console.error(
                    `🇦🇺: Failed to fetch Lottie JSON for ${codepoint}. This emoji cannot be stamped.`,
                    error
                );
            }
            return null;
        }
    }

    // Simplified initialization: just copy the hardcoded data
    function initializeEmojiData() {
        emojiData = ANIMATED_EMOJIS_DATA;
    }


    // --- DRAGGABLE MENU IMPLEMENTATION ---

    const MENU_ID = 'animated-emoji-menu';
    const MENU_TITLE = '🎨 Emojis Animados';
    let selectedEmojiCodepoint = null;

    function createDraggableMenu() {
        const menu = document.createElement('div');
        menu.id = MENU_ID;
        menu.innerHTML = `
            <div class="menu-header">
                ${MENU_TITLE}
                <span class="close-btn">×</span>
            </div>
            <div class="menu-content">
                <input type="text" id="emoji-search" placeholder="Buscar emoji (ej: heart, smile)..." title="Busca por el emoji o su descripción (ej: heart, dog)">
                <div id="emoji-gallery"></div>
            </div>
        `;
        document.body.appendChild(menu);

        // Make it draggable
        const header = menu.querySelector('.menu-header');
        header.addEventListener('mousedown', initDrag, false);
        menu.querySelector('.close-btn').addEventListener('click', () => {
            menu.style.display = 'none';
        });

        // Initialize drag functionality
        let drag = false,
            offsetX,
            offsetY;

        function initDrag(e) {
            if (e.button !== 0) return;
            drag = true;
            offsetX = e.clientX - menu.offsetLeft;
            offsetY = e.clientY - menu.offsetTop;
            document.addEventListener('mousemove', doDrag, false);
            document.addEventListener('mouseup', stopDrag, false);
            e.preventDefault();
        }

        function doDrag(e) {
            if (drag) {
                let newLeft = e.clientX - offsetX;
                let newTop = e.clientY - offsetY;

                newLeft = Math.max(0, Math.min(newLeft, window.innerWidth - menu.offsetWidth));
                newTop = Math.max(0, Math.min(newTop, window.innerHeight - menu.offsetHeight));

                menu.style.left = newLeft + 'px';
                menu.style.top = newTop + 'px';
            }
        }

        function stopDrag() {
            drag = false;
            document.removeEventListener('mousemove', doDrag, false);
            document.removeEventListener('mouseup', stopDrag, false);
        }

        // Add a button to toggle the menu
        const toggleBtn = document.createElement('button');
        toggleBtn.id = 'toggle-emoji-menu-btn';
        toggleBtn.textContent = '🎨 Emojis Animados';
        toggleBtn.onclick = () => {
            menu.style.display = menu.style.display === 'block' ? 'none' : 'block';
        };
        document.body.appendChild(toggleBtn);

        return menu;
    }

    function populateEmojiGallery(menu) {
        const gallery = menu.querySelector('#emoji-gallery');
        gallery.innerHTML = '';

        // Use the guaranteed internal data
        for (const item of emojiData) {
            const emojiItem = document.createElement('span');
            emojiItem.className = 'emoji-item';
            emojiItem.textContent = item.e; // The actual emoji character
            emojiItem.dataset.codepoint = item.c;
            emojiItem.title = item.d; // Description for hover and search

            // Pre-fetch Lottie data asynchronously to speed up stamping
            getLottieAnimationData(item.c).catch(() => {});

            emojiItem.addEventListener('click', () => {
                selectEmojiForDrawing(emojiItem);
            });
            gallery.appendChild(emojiItem);
        }

        // Add search functionality
        const searchInput = menu.querySelector('#emoji-search');
        searchInput.addEventListener('input', (e) => {
            const searchTerm = e.target.value.toLowerCase();
            gallery.querySelectorAll('.emoji-item').forEach((item) => {
                const description = item.title.toLowerCase();
                const emojiChar = item.textContent;

                // Search by the emoji character or the description
                const isMatch =
                    description.includes(searchTerm) ||
                    emojiChar.includes(searchTerm);

                item.style.display = isMatch ? 'inline-block' : 'none';
            });
        });
    }

    function selectEmojiForDrawing(emojiItem) {
        document.querySelectorAll('.emoji-item.selected').forEach((item) => {
            item.classList.remove('selected');
        });

        emojiItem.classList.add('selected');
        selectedEmojiCodepoint = emojiItem.dataset.codepoint;

        if (config.DEBUG_MODE) {
            console.log(
                '🇦🇺: Emoji selected for drawing:',
                emojiItem.textContent
            );
        }

        // Show a temporary message to the user
        const menu = document.getElementById(MENU_ID);
        if (menu) {
             const message = document.createElement('div');
             message.textContent = `Seleccionado: ${emojiItem.textContent}. 🖌️ Haz clic en el canvas para estampar.`;
             message.style.cssText = 'position: absolute; bottom: 0; left: 0; right: 0; background: #98c379; color: #282c34; padding: 5px; text-align: center; border-radius: 0 0 12px 12px; font-size: 14px; font-weight: bold; animation: fadein 0.5s;';
             message.classList.add('selection-message');

             // Remove any previous message and add the new one
             menu.querySelectorAll('.selection-message').forEach(m => m.remove());
             menu.appendChild(message);

             // Auto-hide the message after 2.5 seconds
             setTimeout(() => message.remove(), 2500);
        }
    }

    // --- CANVAS STAMP FUNCTIONALITY ---

    async function stampEmojiOnCanvas(e) {
        if (!selectedEmojiCodepoint) return;

        const canvas = document.getElementById('canvas');
        if (!canvas) return;

        // Ensure the Lottie Pure JS Player is loaded before proceeding
        if (!isPureLottiePlayerLoaded) {
            const LOTTIE_BACKUP_PUREJS_PLAYER_URL = await GM.getResourceUrl('LOTTIE_BACKUP_PUREJS_PLAYER_URL');
            loadScript(LOTTIE_BACKUP_PUREJS_PLAYER_URL, 'lottie-canvas-player', () => stampEmojiOnCanvas(e));
            return;
        }

        const ctx = canvas.getContext('2d');
        if (!ctx) return;

        const animationData = await getLottieAnimationData(selectedEmojiCodepoint);
        if (!animationData) return;

        // --- RENDER LOGIC ---

        const tempCanvas = document.createElement('canvas');
        const emojiSize = 72; // Larger stamp size
        tempCanvas.width = emojiSize;
        tempCanvas.height = emojiSize;

        // The Lottie player is globally available now:
        const player = lottie.loadAnimation({
            renderer: 'canvas',
            loop: false,
            autoplay: false,
            animationData: animationData,
            container: tempCanvas,
            rendererSettings: {
                context: tempCanvas.getContext('2d'),
                preserveAspectRatio: 'xMidYMid meet',
                clearCanvas: true,
                hideOnTransparent: true,
            },
        });

        player.addEventListener('loaded', () => {
            // Render the first frame
            player.goToAndStop(0, true);

            // Get mouse coordinates relative to the main canvas
            const rect = canvas.getBoundingClientRect();
            // Center the stamp on the click position
            const x = e.clientX - rect.left - emojiSize / 2;
            const y = e.clientY - rect.top - emojiSize / 2;

            // Draw the rendered frame onto the main Drawaria canvas
            ctx.drawImage(tempCanvas, x, y, emojiSize, emojiSize);

            // Clean up
            player.destroy();
            tempCanvas.remove();

            if (config.DEBUG_MODE) console.log('🇦🇺: Emoji stamped successfully.');
        });

        player.addEventListener('error', (err) => {
             if (config.DEBUG_MODE) console.error('🇦🇺: Lottie rendering error:', err);
             player.destroy();
        });
    }

    function attachCanvasListener() {
        const canvas = document.getElementById('canvas');
        if (canvas) {
            canvas.addEventListener('click', stampEmojiOnCanvas);
            if (config.DEBUG_MODE)
                console.log('🇦🇺: Canvas listener attached.');
        } else {
            setTimeout(attachCanvasListener, 500);
        }
    }

    // --- MAIN FUNCTION & STYLES ---

    const main = () => {
        try {
            initializeEmojiData();

            // 1. Initialize Menu and Canvas Interaction
            const menu = createDraggableMenu();
            populateEmojiGallery(menu);
            attachCanvasListener();

            // 2. Load the essential Lottie player asynchronously
            GM.getResourceUrl('LOTTIE_BACKUP_PUREJS_PLAYER_URL').then(url => {
                 loadScript(url, 'lottie-canvas-player');
            });

            // 3. Add Styles
            GM.addStyle(`
                /* --- STYLES FOR DRAGGABLE MENU --- */
                #${MENU_ID} {
                    position: fixed;
                    top: 50px;
                    left: 50px;
                    width: 320px;
                    height: 450px;
                    min-width: 200px;
                    min-height: 200px;
                    background: #282c34;
                    border: 1px solid #61afef;
                    border-radius: 12px;
                    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.7);
                    z-index: 99999;
                    display: none;
                    resize: both;
                    overflow: hidden;
                    color: #abb2bf;
                    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
                    transition: box-shadow 0.2s;
                }

                #${MENU_ID}:hover {
                     box-shadow: 0 8px 20px rgba(0, 0, 0, 0.9), 0 0 10px #61afef80;
                }

                #${MENU_ID} .menu-header {
                    cursor: grab;
                    padding: 12px;
                    background: #3e4451;
                    color: #c678dd;
                    font-weight: bold;
                    font-size: 1.1em;
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    border-bottom: 1px solid #61afef;
                }

                #${MENU_ID} .menu-header:active {
                    cursor: grabbing;
                }

                #${MENU_ID} .close-btn {
                    cursor: pointer;
                    font-size: 1.5em;
                    line-height: 1;
                    color: #e06c75;
                    transition: color 0.2s;
                }

                #${MENU_ID} .close-btn:hover {
                    color: #ff0000;
                }

                #${MENU_ID} .menu-content {
                    padding: 10px;
                    height: calc(100% - 47px);
                    display: flex;
                    flex-direction: column;
                }

                #emoji-search {
                    width: 100%;
                    padding: 8px;
                    margin-bottom: 10px;
                    border: 1px solid #555;
                    background: #1e2127;
                    color: #ffffff;
                    border-radius: 6px;
                    font-size: 14px;
                }

                #emoji-gallery {
                    flex-grow: 1;
                    overflow-y: auto;
                    border: 1px solid #3e4451;
                    padding: 5px;
                    border-radius: 6px;
                    background: #21252b;
                }

                #emoji-gallery::-webkit-scrollbar {
                    width: 8px;
                }
                #emoji-gallery::-webkit-scrollbar-thumb {
                    background-color: #565d6c;
                    border-radius: 10px;
                }

                .emoji-item {
                    cursor: pointer;
                    font-size: 28px;
                    padding: 4px;
                    margin: 3px;
                    border-radius: 6px;
                    display: inline-block;
                    transition: background-color 0.1s, transform 0.1s;
                    line-height: 1;
                }

                .emoji-item:hover {
                    background-color: #3e4451;
                    transform: scale(1.1);
                }

                .emoji-item.selected {
                    border: 2px solid #98c379;
                    background-color: #546a48;
                    box-shadow: 0 0 5px #98c379;
                }

                /* --- TOGGLE BUTTON STYLES --- */
                #toggle-emoji-menu-btn {
                    position: fixed;
                    top: 10px;
                    right: 10px;
                    z-index: 100000;
                    padding: 8px 15px;
                    cursor: pointer;
                    background: #56b6c2;
                    color: #1e2127;
                    font-weight: bold;
                    border: none;
                    border-radius: 6px;
                    box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
                    transition: background-color 0.2s, transform 0.1s;
                }

                #toggle-emoji-menu-btn:hover {
                    background: #61afef;
                    transform: translateY(-2px);
                }

                /* Keyframes for selection message */
                @keyframes fadein {
                    from { opacity: 0; }
                    to   { opacity: 1; }
                }
            `);

            if (config.DEBUG_MODE) {
                console.log(
                    '🇦🇺: ',
                    'Script startup time: ' +
                        (Date.now() - scriptStartTime) +
                        'ms'
                );
            }
        } catch (error) {
            if (config.DEBUG_MODE) {
                console.error(
                    '🇦🇺: ',
                    'Failed to initialize emoji animation script:',
                    error
                );
            }
        }
    };

    main();
})();