From 74d38c66c127e9b71ca5d206d95a3067dfd44213 Mon Sep 17 00:00:00 2001 From: Yaode_owo Date: Sat, 8 Jun 2024 03:01:17 -0400 Subject: [PATCH] Delete ChatMessage.js --- ChatMessage.js | 487 ------------------------------------------------- 1 file changed, 487 deletions(-) delete mode 100644 ChatMessage.js diff --git a/ChatMessage.js b/ChatMessage.js deleted file mode 100644 index 097c81e..0000000 --- a/ChatMessage.js +++ /dev/null @@ -1,487 +0,0 @@ -const mojangson = require('mojangson') -const vsprintf = require('./format.js') -const debug = require('debug')('minecraft-protocol') -const nbt = require('prismarine-nbt') -const getValueSafely = (obj, key, def) => Object.hasOwn(obj, key) ? obj[key] : def -const MAX_CHAT_DEPTH = 8 -const MAX_CHAT_LENGTH = 4096 - -function loader (registryOrVersion) { - const registry = typeof registryOrVersion === 'string' ? require('prismarine-registry')(registryOrVersion) : registryOrVersion - const defaultLang = registry.language - const defaultAnsiCodes = { - '§0': '\u001b[30m', - '§1': '\u001b[34m', - '§2': '\u001b[32m', - '§3': '\u001b[36m', - '§4': '\u001b[31m', - '§5': '\u001b[35m', - '§6': '\u001b[33m', - '§7': '\u001b[37m', - '§8': '\u001b[90m', - '§9': '\u001b[94m', - '§a': '\u001b[92m', - '§b': '\u001b[96m', - '§c': '\u001b[91m', - '§d': '\u001b[95m', - '§e': '\u001b[93m', - '§f': '\u001b[97m', - '§l': '\u001b[1m', - '§o': '\u001b[3m', - '§n': '\u001b[4m', - '§m': '\u001b[9m', - '§k': '\u001b[6m', - '§r': '\u001b[0m' - } - const cssDefaultStyles = { - black: 'color:#000000', - dark_blue: 'color:#0000AA', - dark_green: 'color:#00AA00', - dark_aqua: 'color:#00AAAA', - dark_red: 'color:#AA0000', - dark_purple: 'color:#AA00AA', - gold: 'color:#FFAA00', - gray: 'color:#AAAAAA', - dark_gray: 'color:#555555', - blue: 'color:#5555FF', - green: 'color:#55FF55', - aqua: 'color:#55FFFF', - red: 'color:#FF5555', - light_purple: 'color:#FF55FF', - yellow: 'color:#FFFF55', - white: 'color:#FFFFFF', - bold: 'font-weight:900', - strikethrough: 'text-decoration:line-through', - underlined: 'text-decoration:underline', - italic: 'font-style:italic' - } - const formatMembers = ['color', 'bold', 'strikethrough', 'underlined', 'italic'] - const { MessageBuilder } = require('./MessageBuilder')(registry) - - /** - * ChatMessage Constructor - * @param {String|Object|Number} message content of ChatMessage - */ - class ChatMessage { - constructor (message, displayWarning = false) { - if (typeof message === 'string') { - if (message === '') { - this.json = { text: '' } - } else { - this.json = MessageBuilder.fromString(message, { colorSeparator: '§' }) - } - } else if (typeof message === 'number') { - this.json = { text: message } - } else if (typeof message === 'object' && Array.isArray(message)) { - this.json = { extra: message } - } else if (typeof message === 'object') { - this.json = message - } else { - throw new Error('Expected String or Object for Message argument') - } - this.warn = displayWarning ? console.warn : debug - this.parse(displayWarning) - } - - /** - * Parses the this.json property to decorate the properties of the ChatMessage. - * Called by the Constructor - * @return {void} - */ - parse () { - const json = this.json - // Message scope for callback functions - // There is EITHER, a text property or a translate property - // If there is no translate property, there is no with property - // HOWEVER! If there is a translate property, there may not be a with property - if (typeof json.text === 'string' || typeof json.text === 'number') { - this.text = json.text - } else if (typeof json.translate === 'string') { - this.translate = json.translate - if (typeof json.with === 'object') { - if (!Array.isArray(json.with)) { - throw new Error('Expected with property to be an Array in ChatMessage') - } - this.with = json.with.map(entry => new ChatMessage(entry)) - } - } - // Parse extra property - // Extras are appended to the initial text - if (typeof json.extra === 'object') { - if (!Array.isArray(json.extra)) { - throw new Error('Expected extra property to be an Array in ChatMessage') - } - this.extra = json.extra.map(entry => new ChatMessage(entry)) - } - // Text modifiers - this.bold = json.bold - this.italic = json.italic - this.underlined = json.underlined - this.strikethrough = json.strikethrough - this.obfuscated = json.obfuscated - - // Supported constants @ 2014-04-21 - const supportedColors = [ - 'black', - 'dark_blue', - 'dark_green', - 'dark_aqua', - 'dark_red', - 'dark_purple', - 'gold', - 'gray', - 'dark_gray', - 'blue', - 'green', - 'aqua', - 'red', - 'light_purple', - 'yellow', - 'white', - 'obfuscated', - 'bold', - 'strikethrough', - 'underlined', - 'italic', - 'reset' - ] - const supportedClick = [ - 'open_url', - 'open_file', - 'run_command', - 'suggest_command' - ] - const supportedHover = [ - 'show_text', - 'show_achievement', - 'show_item', - 'show_entity' - ] - - // Parse color - this.color = json.color - switch (this.color) { - case 'obfuscated': - this.obfuscated = true - this.color = null - break - case 'bold': - this.bold = true - this.color = null - break - case 'strikethrough': - this.strikethrough = true - this.color = null - break - case 'underlined': - this.underlined = true - this.color = null - break - case 'italic': - this.italic = true - this.color = null - break - case 'reset': - this.reset = true - this.color = null - break - } - // Make sure color is valid - if (this.color && !supportedColors.includes(this.color) && !this.color.match(/#[a-fA-F\d]{6}/)) { - this.warn('ChatMessage parsed with unsupported color', this.color) - this.color = null - } - - // Parse click event - if (typeof json.clickEvent === 'object') { - this.clickEvent = json.clickEvent - if (typeof this.clickEvent.action !== 'string') { - throw new Error('ClickEvent action missing in ChatMessage') - } else if (!supportedClick.includes(this.clickEvent.action)) { - this.warn('ChatMessage parsed with unsupported clickEvent', this.clickEvent.action) - } - } - - // Parse hover event - if (typeof json.hoverEvent === 'object') { - this.hoverEvent = json.hoverEvent - if (typeof this.hoverEvent.action !== 'string') { - throw new Error('HoverEvent action missing in ChatMessage') - } else if (!supportedHover.includes(this.hoverEvent.action)) { - this.warn('ChatMessage parsed with unsupported hoverEvent', this.hoverEvent.action) - } - // Special case - if (this.hoverEvent.action === 'show_item') { - let content - if (this.hoverEvent.value instanceof Array) { - if (this.hoverEvent.value[0] instanceof Object) { - content = this.hoverEvent.value[0].text - } else { - content = this.hoverEvent.value[0] - } - } else { - if (this.hoverEvent.value instanceof Object) { - content = this.hoverEvent.value.text - } else { - content = this.hoverEvent.value - } - } - try { - this.hoverEvent.value = mojangson.parse(content) - } catch (err) { - debug(err) - } - } - } - } - - /** - * Append one or more ChatMessages - * @param {...object|string} messages ChatMessage - * @return {void} - */ - append (...messages) { - if (this.extra === undefined) this.extra = [] - messages.forEach((message) => { - if (typeof message === 'object' && !Array.isArray(message)) { - this.extra.push(message) - } else if (typeof message === 'string') { - this.extra.push(new ChatMessage(message)) - } - }) - } - - /** - * Returns a clone of the ChatMessage - * @return {ChatMessage} - */ - clone () { - return new ChatMessage(JSON.parse(JSON.stringify(this.json))) - } - - /** - * Returns the count of text extras and child ChatMessages - * Does not count recursively in to the children - * @return {Number} - */ - length () { - let count = 0 - if (this.text) count++ - else if (this.with) count += this.with.length - - if (this.extra) count += this.extra.length - return count - } - - /** - * Returns a text part from the message - * @param {Number} idx Index of the part - * @return {String} - */ - getText (idx, lang = defaultLang) { - // If the index is not defined is is invalid, return toString - if (typeof idx !== 'number') return this.toString(lang) - // If we are not a translating message, return the text - if (this.text && idx === 0) return this.text.replace(/§[0-9a-flnmokr]/g, '') - // Else return the with child if it's in range - else if (this.with.length > idx) return this.with[idx].toString(lang) - // Else return the extra if it's in range - if (this.extra && this.extra.length + (this.text ? 1 : this.with.length) > idx) { - return this.extra[idx - (this.text ? 1 : this.with.length)].toString(lang) - } - // Not sure how you want to default this - // Undefined, an error ? - return '' - } - - /** - * Flattens the message in to plain-text - * @return {String} - */ - toString (lang = defaultLang, _depth = 0) { - if (_depth > MAX_CHAT_DEPTH) return '' - let message = '' - if (typeof this.text === 'string' || typeof this.text === 'number') message += this.text - else if (this.translate !== undefined) { - const _with = this.with ?? [] - - const args = _with.map(entry => entry.toString(lang, _depth + 1)) - const format = getValueSafely(lang, this.translate, this.translate) - message += vsprintf(format, args) - } - if (this.extra) { - message += this.extra.map((entry) => entry.toString(lang, _depth + 1)).join('') - } - return message.replace(/§[0-9a-flnmokr]/g, '').slice(0, MAX_CHAT_LENGTH) - } - - valueOf () { - return this.toString() - } - - toMotd (lang = defaultLang, parent = {}, _depth = 0) { - if (_depth > MAX_CHAT_DEPTH) return '' - const codes = { - color: { - black: '§0', - dark_blue: '§1', - dark_green: '§2', - dark_aqua: '§3', - dark_red: '§4', - dark_purple: '§5', - gold: '§6', - gray: '§7', - dark_gray: '§8', - blue: '§9', - green: '§a', - aqua: '§b', - red: '§c', - light_purple: '§d', - yellow: '§e', - white: '§f', - reset: '§r' - }, - bold: '§l', - italic: '§o', - underlined: '§n', - strikethrough: '§m', - obfuscated: '§k' - } - - let message = Object.keys(codes).map((code) => { - this[code] = this[code] || parent[code] - if (!this[code] || this[code] === 'false'/* || this.text === '' */) return null - if (code === 'color') { - // return hex codes in this format - if (this.color.startsWith('#')) return `§${this.color}` - return codes.color[this.color] - } - return codes[code] - }).join('') - - if ((typeof this.text === 'string') || (typeof this.text === 'number')) message += this.text - else if (this.translate !== undefined) { - const _with = this.with ?? [] - - const args = _with.map(entry => { - const entryAsMotd = entry.toMotd(lang, this, _depth + 1) - return entryAsMotd + (entryAsMotd.includes('§') ? '§r' + message : '') - }) - const format = getValueSafely(lang, this.translate, this.translate) - message += vsprintf(format, args) - } - if (this.extra) { - message += this.extra.map(entry => entry.toMotd(lang, this, _depth + 1)).join('§r') - } - return message.slice(0, MAX_CHAT_LENGTH) - } - - toAnsi (lang = defaultLang, codes = defaultAnsiCodes) { - let message = this.toMotd(lang) - for (const k in codes) { - message = message.replace(new RegExp(k, 'g'), codes[k]) - } - const hexRegex = /§#?([a-fA-F\d]{2})([a-fA-F\d]{2})([a-fA-F\d]{2})/ - while (message.search(hexRegex) !== -1) { - // Stolen from https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb - const hexCodes = hexRegex.exec(message) - // Iterate over each hexColorCode match (§#69420, §#ABCDEF, §#A1B2C3) - const red = parseInt(hexCodes[1], 16) - const green = parseInt(hexCodes[2], 16) - const blue = parseInt(hexCodes[3], 16) - // ANSI from https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797#rgb-colors - message = message.replace(hexRegex, `\u001b[38;2;${red};${green};${blue}m`) - } - return codes['§r'] + message.slice(0, MAX_CHAT_LENGTH) + codes['§r'] - } - - // NOTE : Have to be be mindful here as bad HTML gen may lead to arbitrary code execution from server - toHTML (lang = registry.language, styles = cssDefaultStyles, allowedFormats = formatMembers, _depth = 0) { - if (_depth > MAX_CHAT_DEPTH) return '' - let str = '' - if (allowedFormats.some(member => this[member])) { - const cssProps = allowedFormats.reduce((acc, cur) => this[cur] - ? acc.push(cur === 'color' - ? (this.color.startsWith('#') ? escapeRGB(this.color.slice(1)) : styles[this.color]) - : styles[cur]) && - acc - : acc, []) - str += `` - } else { - str += '' - } - - if (this.text) { - str += escapeHtml(this.text) - } else if (this.translate) { - const params = [] - if (this.with) { - for (const param of this.with) { - params.push(param.toHTML(lang, styles, allowedFormats, _depth + 1)) - } - } - const format = getValueSafely(lang, this.translate, this.translate) - str += vsprintf(escapeHtml(format), params) - } - - if (this.extra) { - str += this.extra.map(entry => entry.toHTML(lang, styles, allowedFormats, _depth + 1)).join('') - } - str += '' - // It's not safe to truncate HTML so just return unformatted text - return str > MAX_CHAT_LENGTH - ? escapeHtml(this.toString()) - : str - } - - static fromNotch (msg) { - if (registry.supportFeature('chatPacketsUseNbtComponents') && msg.type) { - const json = processNbtMessage(msg) - return new ChatMessage(json ? JSON.parse(json) : '') - } else { - try { - return new ChatMessage(JSON.parse(msg)) - } catch (e) { - return new ChatMessage(msg) - } - } - } - - // 1.19 applies chat formatting on the client side. A format string is provided like in C printf - // syntax, including positional arguments which we poll from the supplied parameters map. - // For example, - // printf("<%s> %s" /* fmt string */, [sender], [content]) - static fromNetwork (type, params) { - const format = getValueSafely(registry.chatFormattingById, type) - if (format == null) { - throw new Error('unknown chat format code: ' + type) // Server may be attempting to send a chat message before sending a login codec, which is not allowed - } - return new ChatMessage({ translate: format.formatString, with: format.parameters.map(p => getValueSafely(params, p, '')) }) - } - } - - ChatMessage.MessageBuilder = MessageBuilder - return ChatMessage -} - -module.exports = loader -// mcpc 1.20.3 uses NBT instead of JSON in some places to store chat, so the schema is a bit different -// processNbtMessage normalizes the JS object obtained from nbt derealization to the old JSON schema -function uuidFromIntArray (arr) { - const buf = Buffer.alloc(16) - arr.forEach((num, index) => { buf.writeInt32BE(num, index * 4) }) - return buf.toString('hex') -} -function processNbtMessage (msg) { - if (!msg || msg.type === 'end') return null - const simplified = nbt.simplify(msg) - const json = JSON.stringify(simplified, (key, val) => { - if (key === 'id' && Array.isArray(val)) return uuidFromIntArray(val) - return val - }) - return json -} -module.exports.processNbtMessage = processNbtMessage - -const escapeHtml = (unsafe) => unsafe.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", ''') -const escapeRGB = (unsafe) => `color:rgb(${unsafe.match(/.{2}/g).map(e => parseInt(e, 16)).join(',')})`