您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a stylish, draggable menu with an internal data source for selecting animated emojis and stamping them on the Drawaria.online canvas.
// ==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(); })();