您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Display emoticons in chat!
当前为
// ==UserScript== // @name Multiplayer Piano Optimizations [Emotes] // @namespace https://tampermonkey.net/ // @version 1.2.0 // @description Display emoticons in chat! // @author zackiboiz // @match *://multiplayerpiano.com/* // @match *://multiplayerpiano.net/* // @match *://multiplayerpiano.org/* // @match *://piano.mpp.community/* // @match *://mpp.7458.space/* // @match *://qmppv2.qwerty0301.repl.co/* // @match *://mpp.8448.space/* // @match *://mpp.autoplayer.xyz/* // @match *://mpp.hyye.xyz/* // @icon https://www.google.com/s2/favicons?sz=64&domain=multiplayerpiano.net // @grant GM_info // @license MIT // ==/UserScript== (async () => { function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } await sleep(1000); const BASE_URL = "https://raw.githubusercontent.com/ZackiBoiz/Multiplayer-Piano-Optimizations/refs/heads/main"; const OLD_RGB_PREFIX = 0x0D9E; const NEW_RGB_PREFIX = 0xF000; class EmotesManager { constructor(version, baseUrl) { this.version = version; this.baseUrl = baseUrl; this.emotes = {}; this.tokenRegex = null; } async init() { try { await this._loadEmotes(); this._buildTokenRegex(); this._initChatObserver(); this._replaceExistingMessages(); } catch (err) { console.error("EmotesManager failed:", err); MPP.chat.sendPrivate({ name: `[MPP Emotes] v${this.version}`, color: "#ff0000", message: "EmotesManager initialization failed. Check console for details", }); } } async _loadEmotes() { const res = await fetch(`${this.baseUrl}/emotes/meta.json?_=${Date.now()}`); if (!res.ok) throw new Error(`Failed to load emote metadata: ${res.status}`); const data = await res.json(); if (typeof data !== "object" || Array.isArray(data)) throw new Error("Unexpected emote metadata shape"); this.emotes = data; } _buildTokenRegex() { const tokens = Object.keys(this.emotes).map(t => t.replace(/[.*+?^${}()|[\\]\\]/g, "\\$&")); tokens.sort((a, b) => b.length - a.length); this.tokenRegex = new RegExp(`:(${tokens.join("|")}):`, "g"); } _initChatObserver() { const chatList = document.querySelector("#chat > ul"); if (!chatList) { console.warn("EmotesManager: chat container not found"); return; } const observer = new MutationObserver(muts => { muts.forEach(m => { m.addedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE && node.tagName === "LI") { this._replaceEmotesInElement(node.querySelector(".message")); } }); }); }); observer.observe(chatList, { childList: true }); } _replaceExistingMessages() { document.querySelectorAll("#chat > ul li .message").forEach(el => this._replaceEmotesInElement(el)); } _replaceEmotesInElement(el) { if (!el) return; const nodes = []; el.childNodes.forEach(node => { if (node.nodeType === Node.TEXT_NODE && node.nodeValue.includes("\n") && !node.nodeValue.includes("\\n")) { node.nodeValue.split("\n").forEach((seg, i, arr) => { nodes.push(document.createTextNode(seg)); if (i < arr.length - 1) nodes.push(document.createElement("br")); }); } else { nodes.push(node); } }); el.textContent = ""; nodes.forEach(n => el.appendChild(n)); el.childNodes.forEach(node => { if (node.nodeType !== Node.TEXT_NODE) return; const text = node.nodeValue; const frag = document.createDocumentFragment(); let i = 0; while (i < text.length) { const cp = text.codePointAt(i); // 0xamogus 0xFFRR 0xFFGG 0xFFBB if (cp === OLD_RGB_PREFIX && i + 3 < text.length) { const r = text.codePointAt(i + 1) & 0xFF; const g = text.codePointAt(i + 2) & 0xFF; const b = text.codePointAt(i + 3) & 0xFF; this._appendColor(frag, r, g, b, text); i += 4; continue; } // 0xF000 0xERGB [0xErgb] if (cp === NEW_RGB_PREFIX && i + 1 < text.length) { const high = text.codePointAt(i + 1); const rHigh = (high >> 8) & 0xF; const gHigh = (high >> 4) & 0xF; const bHigh = high & 0xF; let r, g, b, len; if (i + 2 < text.length) { const low = text.codePointAt(i + 2); const rLow = (low >> 8) & 0xF; const gLow = (low >> 4) & 0xF; const bLow = low & 0xF; r = (rHigh << 4) | rLow; g = (gHigh << 4) | gLow; b = (bHigh << 4) | bLow; len = 3; } else { r = rHigh * 17; g = gHigh * 17; b = bHigh * 17; len = 2; } this._appendColor(frag, r, g, b, text); i += len; continue; } // 0xFRGB if (cp >= 0xF000 && cp <= 0xFFFF) { const nibble = cp & 0x0FFF; const r = ((nibble >> 8) & 0xF) * 17; const g = ((nibble >> 4) & 0xF) * 17; const b = (nibble & 0xF) * 17; this._appendColor(frag, r, g, b, text); i += 1; continue; } this.tokenRegex.lastIndex = 0; const rest = text.slice(i); const m = this.tokenRegex.exec(rest); if (m && m.index === 0) { const token = m[0], key = m[1]; const ext = this.emotes[key] || 'png'; const url = `${this.baseUrl}/emotes/assets/${key}.${ext}`; const img = document.createElement('img'); img.src = url; img.title = token; img.alt = token; img.style.height = '0.75rem'; img.style.verticalAlign = 'middle'; img.style.cursor = 'pointer'; img.addEventListener('click', () => navigator.clipboard.writeText(token)); frag.appendChild(img); i += token.length; continue; } frag.appendChild(document.createTextNode(text[i])); i++; } node.replaceWith(frag); }); } _appendColor(frag, r, g, b, token) { const hex = ((r << 16) | (g << 8) | b).toString(16).padStart(6, '0').toUpperCase(); const span = document.createElement('span'); span.style.display = 'inline-block'; span.style.width = '0.75rem'; span.style.height = '0.75rem'; span.style.verticalAlign = 'middle'; span.style.backgroundColor = `#${hex}`; span.style.cursor = 'pointer'; span.title = `#${hex}`; span.addEventListener('click', () => navigator.clipboard.writeText(token)); frag.appendChild(span); } } const emotesManager = new EmotesManager(GM_info.script.version, BASE_URL); emotesManager.init(); })();