您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds a categorized emoji menu with dark mode and favorites to all Torn chat windows.
// ==UserScript== // @name Torn Chat Emoji+ // @namespace http://tampermonkey.net/ // @version 1.1 // @description Adds a categorized emoji menu with dark mode and favorites to all Torn chat windows. // @author HeyItzWerty [3626448] // @match https://www.torn.com/* // @grant none // ==/UserScript== (function() { 'use strict'; // --- 1. EMOJI DATA & STORAGE KEYS --- const FAVORITES_KEY = 'torn_emoji_plus_favorites'; const CUSTOM_KEY = 'torn_emoji_plus_custom'; const DARK_MODE_KEY = 'torn_emoji_plus_dark_mode'; const emojiCategories = { '⭐ Favorites': [], '✨ My Custom Emojis': [], '🙂 Smileys & Emotion': ['😀', '😃', '😄', '😁', '😆', '😅', '😂', '🤣', '🥲', '🥹', '😊', '😇', '🙂', '🙃', '😉', '😌', '😍', '🥰', '😘', '😗', '😙', '😚', '😋', '😛', '😝', '😜', '🤪', '🤨', '🧐', '🤓', '😎', '🥸', '🤩', '🥳', '😏', '😒', '😞', '😔', '😟', '😕', '🙁', '☹️', '😣', '😖', '😫', '😩', '🥺', '😢', '😭', '😤', '😠', '😡', '🤬', '🤯', '😳', '🥵', '🥶', '😱', '😨', '😰', '😥', '😓', '🫣', '🤗', '🫡', '🤔', '🫢', '🤭', '🤫', '🤥', '😶', '😶🌫️', '😐', '😑', '😬', '🙄', '😯', '😦', '😧', '😮', '😲', '🥱', '😴', '🤤', '😪', '😵', '😵💫', '🫥', '🤐', '🥴', '🤢', '🤮', '🤧', '😷', '🤒', '🤕', '🤑', '🤠', '🤡', '💩', '👻', '🎃', '😺', '😸', '😹', '😻', '😼', '😽', '🙀', '😿', '😾'], '👋 People & Body': ['👋', '🤚', '🖐️', '✋', '🖖', '👌', '🤌', '🤏', '✌️', '🤞', '🫰', '🤟', '🤘', '🤙', '👈', '👉', '👆', '🖕', '👇', '☝️', '👍', '👎', '✊', '👊', '🤛', '🤜', '👏', '🙌', '👐', '🤲', '🤝', '🙏', '✍️', '💅', '🤳', '💪', '🦾', '🦿', '🦵', '🦶', '👂', '🦻', '👃', '🧠', '🫀', '🫁', '🦷', '🦴', '👀', '👁️', '👅', '👄', '🫦', '👶', '👧', '🧒', '👦', '👩', '🧑', '👨', '👵', '🧓', '👴', '👮♀️', '👮', '👷♀️', '👷', '🕵️♀️', '🕵️', '👩⚕️', '🧑⚕️', '👨⚕️', '👩🎓', '🧑🎓', '👨🎓', '👩🏫', '🧑🏫', '👨🏫', '👩⚖️', '🧑⚖️', '👨⚖️', '👩🌾', '🧑🌾', '👨🌾', '👩🍳', '🧑🍳', '👨🍳', '👩🔧', '🧑🔧', '👨🔧', '👩💻', '🧑💻', '👨💻', '👩🎤', '🧑🎤', '👨🎤', '👩🎨', '🧑🎨', '👨🎨', '👩🚀', '🧑🚀', '👨🚀', '👸', '🤴', '🦸♀️', '🦸', '🦹♀️', '🦹', '🎅', '🤶', '🧙♀️', '🧙', '🧝♀️', '🧝', '🧛♀️', '🧛', '🧟♀️', '🧟', '🧞♀️', '🧞', '🧜♀️', '🧜', '🧚♀️', '🧚', '👼', '🤰', '🤱', '🙇♀️', '🙇', '💁♀️', '💁', '🙅♀️', '🙅', '🙆♀️', '🙆', '🙋♀️', '🙋', '🤦♀️', '🤦', '🤷♀️', '🤷', '🙎♀️', '🙎', '🙍♀️', '🙍', '💇♀️', '💇', '💆♀️', '💆', '💃', '🕺', '🏃♀️', '🏃', '🚶♀️', '🚶', '🫂', '🗣️', '👤', '👥'], '🐻 Animals': ['🐵', '🐒', '🦍', '🦧', '🐶', '🐕', '🦮', '🐕🦺', '🐩', '🐺', '🦊', '🦝', '🐱', '🐈', '🐈⬛', '🦁', '🐯', '🐅', '🐆', '🐴', '🐎', '🦄', '🦓', '🦌', '🦬', '🐮', '🐂', '🐃', '🐄', '🐷', '🐖', '🐗', '🐽', '🐏', '🐑', '🐐', '🐪', '🐫', '🦙', '🦒', '🐘', '🦣', '🦏', '🦛', '🐭', '🐁', '🐀', '🐹', '🐰', '🐇', '🐿️', '🦫', '🦔', '🦇', '🐻', '🐻❄️', '🐨', '🐼', '🦥', '🦦', '🦨', '🦘', '🦡', '🐾', '🦃', '🐔', '🐓', '🐣', '🐤', '🐥', '🐦', '🐧', '🕊️', '🦅', '🦆', '🦢', '🦉', '🦤', '🪶', '🐸', '🐊', '🐢', '🦎', '🐍', '🐲', '🐉', '🐳', '🐋', '🐬', '🦭', '🐟', '🐠', '🐡', '🦈', '🐙', '🐚', '🐌', '🦋', '🐛', '🐜', '🐝', '🪲', '🐞', '🦗', '🪳', '🕷️', '🕸️', '🦂', '🦟', '🪰', '🪱'], '🌳 Nature': ['💐', '🌸', '💮', '🏵️', '🌹', '🥀', '🌺', '🌻', '🌼', '🌷', '🌱', '🪴', '🌲', '🌳', '🌴', '🪵', '🌵', '🌾', '🌿', '☘️', '🍀', '🍁', '🍂', '🍃', '🌍', '🌎', '🌏', '🌕', '🌖', '🌗', '🌘', '🌑', '🌒', '🌓', '🌔', '🌙', '⭐', '🌟', '💫', '✨', '☄️', '☀️', '🌤️', '⛅', '🌥️', '🌦️', '☁️', '🌧️', '⛈️', '🌩️', '⚡', '🔥', '💥', '❄️', '🌨️', '☃️', '⛄', '🌬️', '💨', '🌪️', '🌫️', '🌊', '💧', '💦'], '🍔 Food & Drink': ['🍇', '🍈', '🍉', '🍊', '🍋', '🍌', '🍍', '🥭', '🍎', '🍏', '🍐', '🍑', '🍒', '🍓', '🫐', '🥝', '🍅', '🫒', '🥥', '🥑', '🍆', '🥔', '🥕', '🌽', '🌶️', '🫑', '🥒', '🥬', '🥦', '🧄', '🧅', '🍄', '🥜', '🫘', '🌰', '🍞', '🥐', '🥖', '🫓', '🥨', '🥯', '🥞', '🧇', '🧀', '🍖', '🍗', '🥩', '🥓', '🍔', '🍟', '🍕', '🌭', '🥪', '🥙', '🧆', '🌮', '🌯', '🫔', '🥗', '🥘', '🫕', '🥫', '🍝', '🍜', '🍲', '🍛', '🍣', '🍱', '🥟', '🦪', '🍤', '🍙', '🍚', '🍘', '🍥', '🥠', '🥮', '🍢', '🍡', '🍧', '🍨', '🍦', '🥧', '🧁', '🍰', '🎂', '🍮', '🍭', '🍬', '🍫', '🍿', '🍩', '🍪', '🍯', '🥛', '🍼', '☕️', '🫖', '🍵', '🍶', '🍾', '🍷', '🍸', '🍹', '🍺', '🍻', '🥂', '🥃', '🫗', '🥤', '🧋', '🧃', '🧉', '🧊', '🥢', '🍽️', '🍴', '🥄', '🏺'], '⚽ Activities & Events': ['⚽', '🏀', '🏈', '⚾', '🥎', '🎾', '🏐', '🏉', '🥏', '🎱', '🪀', '🏓', '🏸', '🏒', '🏑', '🥍', '🏏', '🪃', '🥅', '⛳', '🪁', '🏹', '🎣', '🤿', '🥊', '🥋', '🎽', '🛹', '🛼', '🛷', '⛸️', '🥌', '🎿', '⛷️', '🏂', '🪂', '🏋️♀️', '🏋️', '🏋️♂️', '🤼♀️', '', '🤼♂️', '🤸♀️', '🤸', '🤸♂️', '⛹️♀️', '⛹️', '⛹️♂️', '🤺', '🤾♀️', '🤾', '🤾♂️', '🏌️♀️', '🏌️', '🏌️♂️', '🏇', '🧘♀️', '🧘', '🧘♂️', '🏄♀️', '🏄', '🏄♂️', '🏊♀️', '🏊', '🏊♂️', '🤽♀️', '🤽', '🤽♂️', '🚣♀️', '🚣', '🚣♂️', '🧗♀️', '🧗', '🧗♂️', '🚵♀️', '🚵', '🚵♂️', '🚴♀️', '🚴', '🚴♂️', '🏆', '🥇', '🥈', '🥉', '🏅', '🎖️', '🏵️', '🎗️', '🎫', '🎟️', '🎪', '🤹', '🤹♂️', '🤹♀️', '🎭', '🩰', '🎨', '🎬', '🎤', '🎧', '🎼', '🎹', '🥁', '🪘', '🎷', '🎺', '🪗', '🎸', '🪕', '🎻', '🪈', '🎲', '♟️', '🎯', '🎳', '🎮', '🎰', '🧩', '🎄', '🎆', '🎇', '🎈', '🎉', '🎊', '🎁'], '💡 Objects': ['⌚', '📱', '📲', '💻', '⌨️', '🖥️', '🖨️', '🖱️', '🖲️', '🕹️', '💽', '💾', '💿', '📀', '📼', '📷', '📸', '📹', '🎥', '📞', '☎️', '📟', '📠', '📺', '📻', '🎙️', '💡', '🔦', '🕯️', '💰', '💵', '💴', '💶', '💷', '🪙', '💳', '💎', '⚖️', '🔧', '🔨', '⚒️', '🛠️', '⛏️', '🔩', '⚙️', '🧱', '⛓️', '🧲', '⚰️', '🪦', '⚱️', '🔮', '📿', '🧿', '💈', '🔭', '🔬', '🕳️', '🩹', '🩺', '🧬', '🦠', '🧪', '🌡️', '🧹', '🧺', '🚽', '🚰', '🚿', '🛁', '🧼', '🪥', '🧽', '🧯', '🔑', '🗝️', '🚪', '🛋️', '🛏️', '🧸', '🖼️', '🛍️', '🛒', '✉️', '🧧', '📫', '📮', '📦', '🏷️', '📜', '📃', '📄', '📑', '📊', '📈', '📉', '🗒️', '🗓️', '🗑️', '📁', '📂', '✂️', '🖊️', '✒️', '🖌️', '🖍️', '📝', '✏️', '🔍', '🔎', '🔒', '🔐', '🔓', '🔏'], '🔞 Torn-Themed': ['🔫', '💣', '🧨', '🪓', '🔪', '🗡️', '⚔️', '🛡️', '🚬', '💊', '💉', '🩸', '❤️🔥', '❤️🩹', '💋', '💯', '💢', '💦', '🍆', '🍑', '😈', '👿', '👹', '👺', '🔥', '💥', '💨', '💸', '⚗️', '🔞'], '❤️ Symbols & Flags': ['🆔', '⚛️', '☢️', '☣️', '📴', '🆚', '🉑', '㊗️', '㊙️', '🉐', '🆘', '⛔', '🚫', '❌', '⭕', '🛑', '📛', '♨️', '❗️', '❕', '❓', '❔', '‼️', '⁉️', '✅', '✔️', '☑️', '❎', '✝️', '☪️', '🕉️', '✡️', '☯️', '☮️', '☦️', '⚠️', '🚸', '♻️', '⚜️', '🔱', '🔰', '🏧', '♿', '🅿️', '🚾', '📶', '🈁', '🈂️', '🈚', '🈯', '🉐', '🈹', '🈺', '🈶', '🈷️', '🈸', '🈲', '🈵', '🈴', '🆎', '🅱️', '🅰️', '🅾️', '🆑', '🆒', '🆓', '🆕', '🆖', '🆗', '🆙', '↔️', '↕️', '↖️', '↗️', '↘️', '↙️', '↩️', '↪️', '⤴️', '⤵️', '➡️', '⬅️', '⬆️', '⬇️', '➕', '➖', '➗', '✖️', '♾️', '💲', '💱', '™️', '©️', '®️', '🔚', '🔙', '🔛', '🔝', '🔜', '🔴', '🟠', '🟡', '🟢', '🔵', '🟣', '🟤', '⚫', '⚪', '🟥', '🟧', '🟨', '🟩', '🟦', '🟪', '🟫', '⬛', '⬜', '🏁', '🚩', '🎌', '🏴', '🏳️', '🏳️🌈', '🏳️⚧️', '🏴☠️'], }; let favorites = JSON.parse(localStorage.getItem(FAVORITES_KEY)) || []; let customEmojis = JSON.parse(localStorage.getItem(CUSTOM_KEY)) || []; let isDarkMode = localStorage.getItem(DARK_MODE_KEY) === 'true'; emojiCategories['⭐ Favorites'] = favorites; emojiCategories['✨ My Custom Emojis'] = customEmojis; const allEmojis = Object.values(emojiCategories).flat(); // --- 2. THEMES & CORE LOGIC --- const themes = { light: { bg: '#f2f2f2', text: '#000', border: '#ccc', catBg: '#e9e9e9', scrollbar: '#ccc', credit: '#888' }, dark: { bg: '#2c2c2c', text: '#f1f1f1', border: '#555', catBg: '#222', scrollbar: '#555', credit: '#aaa' } }; const nativeTextareaValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set; function simulateUserInput(textarea, text) { const start = textarea.selectionStart; const end = textarea.selectionEnd; const newText = textarea.value.substring(0, start) + text + textarea.value.substring(end); nativeTextareaValueSetter.call(textarea, newText); const event = new Event('input', { bubbles: true }); textarea.dispatchEvent(event); textarea.selectionStart = textarea.selectionEnd = start + text.length; textarea.focus(); } function saveToStorage(key, data) { localStorage.setItem(key, JSON.stringify(data)); } function showNotification(message) { const notif = document.createElement('div'); notif.textContent = message; Object.assign(notif.style, { position: 'fixed', top: '20px', right: '20px', background: 'rgba(0,0,0,0.7)', color: 'white', padding: '10px 20px', borderRadius: '5px', zIndex: '9999', opacity: '1', transition: 'opacity 0.5s' }); document.body.appendChild(notif); setTimeout(() => { notif.style.opacity = '0'; setTimeout(() => notif.remove(), 500); }, 1500); } // --- 3. UI CREATION & LOGIC --- function addEmojiButtonTo(chatContainer) { if (chatContainer.querySelector('.custom-emoji-button-v3')) return; const emojiButton = document.createElement('button'); emojiButton.className = 'custom-emoji-button-v3'; Object.assign(emojiButton.style, { background: 'transparent', border: 'none', fontSize: '22px', cursor: 'pointer', height: '32px', width: '32px', marginRight: '5px', padding: '0' }); // FORCE the color to be visible against Torn's dark UI emojiButton.style.setProperty('color', '#f1f1f1', 'important'); emojiButton.textContent = allEmojis[Math.floor(Math.random() * allEmojis.length)] || '😊'; const picker = document.createElement('div'); picker.className = 'custom-emoji-picker'; Object.assign(picker.style, { position: 'absolute', display: 'none', bottom: '40px', left: '0', width: '300px', height: '320px', borderRadius: '8px', zIndex: '1001', boxShadow: '0 5px 15px rgba(0,0,0,0.3)', fontFamily: 'Arial, sans-serif', flexDirection: 'column' }); const categoryContainer = document.createElement('div'); Object.assign(categoryContainer.style, { display: 'flex', flexWrap: 'wrap', padding: '5px', alignItems: 'center' }); const emojiDisplay = document.createElement('div'); Object.assign(emojiDisplay.style, { padding: '5px', flexGrow: '1', overflowY: 'auto', position: 'relative' }); picker.append(categoryContainer, emojiDisplay); let activeCategory = '⭐ Favorites'; function applyTheme() { const theme = isDarkMode ? themes.dark : themes.light; Object.assign(picker.style, { background: theme.bg, border: `1px solid ${theme.border}`, color: theme.text }); categoryContainer.style.background = theme.catBg; categoryContainer.style.borderBottom = `1px solid ${theme.border}`; themeToggle.style.borderColor = theme.border; themeToggle.style.color = theme.text; themeToggle.textContent = isDarkMode ? '☀️' : '🌙'; const styleId = 'emoji-picker-scrollbar-style'; let scrollbarStyle = document.getElementById(styleId); if (!scrollbarStyle) { scrollbarStyle = document.createElement('style'); scrollbarStyle.id = styleId; document.head.appendChild(scrollbarStyle); } scrollbarStyle.textContent = ` .custom-emoji-picker div::-webkit-scrollbar { width: 8px; } .custom-emoji-picker div::-webkit-scrollbar-track { background: ${theme.bg}; } .custom-emoji-picker div::-webkit-scrollbar-thumb { background: ${theme.scrollbar}; border-radius: 4px; } `; } const renderEmojis = (emojiList, categoryName) => { emojiDisplay.innerHTML = ''; const isRemovable = (categoryName === '⭐ Favorites' || categoryName === '✨ My Custom Emojis'); const theme = isDarkMode ? themes.dark : themes.light; if (emojiList.length === 0) { let emptyText = 'Right-click an emoji to add it!'; if (categoryName === '✨ My Custom Emojis') { emptyText = 'Custom Emojis appear here.<br><small>Got one? Paste it!</small>'; } emojiDisplay.innerHTML = `<div style="padding: 20px; text-align: center; color: ${theme.credit}; position: absolute; top: 40%; left: 50%; transform: translate(-50%, -50%);">${emptyText}</div>`; } const emojiGrid = document.createElement('div'); Object.assign(emojiGrid.style, { display: 'flex', flexWrap: 'wrap' }); emojiList.forEach(emoji => { const emojiSpan = document.createElement('span'); emojiSpan.textContent = emoji; Object.assign(emojiSpan.style, { cursor: 'pointer', padding: '4px', fontSize: '22px' }); emojiSpan.addEventListener('click', () => { simulateUserInput(chatContainer.querySelector('textarea.textarea___V8HsV'), emoji); picker.style.display = 'none'; }); emojiSpan.addEventListener('contextmenu', (e) => { e.preventDefault(); if (isRemovable) { if (categoryName === '⭐ Favorites') { favorites = favorites.filter(f => f !== emoji); saveToStorage(FAVORITES_KEY, favorites); emojiCategories['⭐ Favorites'] = favorites; } else { customEmojis = customEmojis.filter(c => c !== emoji); saveToStorage(CUSTOM_KEY, customEmojis); emojiCategories['✨ My Custom Emojis'] = customEmojis; } renderCategory(categoryName); } else { if (!favorites.includes(emoji)) { favorites.unshift(emoji); saveToStorage(FAVORITES_KEY, favorites); emojiCategories['⭐ Favorites'] = favorites; showNotification(`'${emoji}' added to favorites!`); } } }); emojiGrid.appendChild(emojiSpan); }); emojiDisplay.appendChild(emojiGrid); if (categoryName === '⭐ Favorites') { const credit = document.createElement('a'); credit.textContent = 'Created by HeyItzWerty [3626448]'; credit.href = 'https://www.torn.com/profiles.php?XID=3626448'; credit.target = '_blank'; Object.assign(credit.style, { display: 'block', textAlign: 'center', width: '100%', paddingTop: '15px', marginTop: '15px', borderTop: `1px solid ${theme.border}`, fontSize: '10px', color: theme.credit, textDecoration: 'none' }); emojiDisplay.appendChild(credit); } }; const renderCategory = (categoryName) => { activeCategory = categoryName; renderEmojis(emojiCategories[categoryName], categoryName); }; Object.keys(emojiCategories).forEach(name => { const tab = document.createElement('button'); tab.textContent = name.split(' ')[0]; tab.title = name; Object.assign(tab.style, { background: 'none', border: 'none', cursor: 'pointer', fontSize: '20px', padding: '5px', color: 'inherit' }); tab.addEventListener('click', () => renderCategory(name)); categoryContainer.appendChild(tab); }); const themeToggle = document.createElement('button'); Object.assign(themeToggle.style, { border: '1px solid', // Restored border borderRadius: '5px', cursor: 'pointer', fontSize: '18px', width: '30px', height: '30px', flexShrink: '0', padding: '0', display: 'flex', alignItems: 'center', justifyContent: 'center', marginLeft: 'auto', background: 'none' }); themeToggle.addEventListener('click', () => { isDarkMode = !isDarkMode; localStorage.setItem(DARK_MODE_KEY, isDarkMode); applyTheme(); renderCategory(activeCategory); }); categoryContainer.appendChild(themeToggle); emojiDisplay.addEventListener('paste', e => { if (activeCategory === '✨ My Custom Emojis') { e.preventDefault(); const pastedText = (e.clipboardData || window.clipboardData).getData('text'); const emojiRegex = /(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])/g; const found = pastedText.match(emojiRegex); if (found) { found.forEach(emoji => { if (!customEmojis.includes(emoji)) { customEmojis.push(emoji); } }); saveToStorage(CUSTOM_KEY, customEmojis); emojiCategories['✨ My Custom Emojis'] = customEmojis; renderCategory('✨ My Custom Emojis'); showNotification(`${found.length} custom emoji(s) added!`); } } }); emojiButton.addEventListener('click', (event) => { event.stopPropagation(); const isHidden = picker.style.display === 'none'; document.querySelectorAll('.custom-emoji-picker').forEach(p => p.style.display = 'none'); if (isHidden) { favorites = JSON.parse(localStorage.getItem(FAVORITES_KEY)) || []; customEmojis = JSON.parse(localStorage.getItem(CUSTOM_KEY)) || []; emojiCategories['⭐ Favorites'] = favorites; emojiCategories['✨ My Custom Emojis'] = customEmojis; picker.style.display = 'flex'; applyTheme(); renderCategory(activeCategory); } }); chatContainer.insertBefore(emojiButton, chatContainer.querySelector('.iconWrapper___tyRRU')); chatContainer.appendChild(picker); applyTheme(); } document.addEventListener('click', (e) => { if (!e.target.closest('.custom-emoji-picker') && !e.target.closest('.custom-emoji-button-v3')) { document.querySelectorAll('.custom-emoji-picker').forEach(p => p.style.display = 'none'); } }); const observer = new MutationObserver(() => { document.querySelectorAll('.root___WUd1h').forEach(container => addEmojiButtonTo(container)); }); observer.observe(document.body, { childList: true, subtree: true }); })();