Refactor the chat system
not sure if it's better or worse now. also, i have tried to optimize color codes, but this optimization seems unreliable (i might fix or remove it in the future)
This commit is contained in:
parent
a67478cfd9
commit
5f3560910b
18 changed files with 546 additions and 328 deletions
|
@ -1,16 +1,37 @@
|
||||||
{
|
{
|
||||||
bots: [
|
bots: [
|
||||||
{
|
{
|
||||||
host: 'localhost',
|
host: 'example.com',
|
||||||
port: 25565,
|
port: 25565,
|
||||||
brand: 'kaboom', // TODO: Rename this
|
|
||||||
|
|
||||||
username: ' ',
|
matrix: {
|
||||||
prefix: "'",
|
enabled: false,
|
||||||
colors: { primary: 'green', secondary: 'dark_green', error: 'red' },
|
client: 'example',
|
||||||
version: '1.20.4',
|
roomId: 'put the matrix room id here',
|
||||||
randomizeUsername: true,
|
commandPrefix: '!'
|
||||||
autoReconnect: true
|
}
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
|
||||||
|
all: {
|
||||||
|
username: 'chipmunkbot',
|
||||||
|
prefix: "default.",
|
||||||
|
colors: { primary: 'green', secondary: 'dark_green', error: 'red' },
|
||||||
|
version: '1.20.4',
|
||||||
|
randomizeUsername: true,
|
||||||
|
autoReconnect: true,
|
||||||
|
|
||||||
|
features: {
|
||||||
|
amnesicCommandBlocks: true,
|
||||||
|
commandNamespaces: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
matrixClients: {
|
||||||
|
example: {
|
||||||
|
baseUrl: 'put the homeserver url here',
|
||||||
|
accessToken: 'put your access token here',
|
||||||
|
userId: 'put the user id here'
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -9,20 +9,19 @@ function inject (bot) {
|
||||||
_lines: Array(100).fill(''),
|
_lines: Array(100).fill(''),
|
||||||
_filter
|
_filter
|
||||||
}
|
}
|
||||||
/*
|
bot.on('chat_color_code', message => {
|
||||||
bot.on('chat', ({ raw }) => {
|
const filtered = _filter(message)
|
||||||
const filtered = _filter(raw)
|
|
||||||
bot.chatFilter._lines = [...bot.chatFilter._lines, ...filtered.split('\n')]
|
bot.chatFilter._lines = [...bot.chatFilter._lines, ...filtered.split('\n')]
|
||||||
while (bot.chatFilter._lines.length > 100) {
|
while (bot.chatFilter._lines.length > 100) {
|
||||||
bot.chatFilter._lines.shift()
|
bot.chatFilter._lines.shift()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (raw !== filtered) {
|
if (message !== filtered) {
|
||||||
bot._client.write('set_creative_slot', {
|
bot._client.write('set_creative_slot', {
|
||||||
slot: 36,
|
slot: 36,
|
||||||
item: {
|
item: {
|
||||||
present: true,
|
present: true,
|
||||||
itemId: /* id *//*1,
|
itemId: 1,
|
||||||
itemCount: 1,
|
itemCount: 1,
|
||||||
nbtData: nbt.comp({
|
nbtData: nbt.comp({
|
||||||
i: nbt.string('\xa7r' + bot.chatFilter._lines.join('\xa7r\n'))
|
i: nbt.string('\xa7r' + bot.chatFilter._lines.join('\xa7r\n'))
|
||||||
|
@ -30,11 +29,10 @@ function inject (bot) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
bot.core.run('minecraft:tellraw @a ' + JSON.stringify({ nbt: 'SelectedItem.tag.i', entity: bot.uuid }))
|
bot.core.run('minecraft:tellmessage @a ' + JSON.stringify({ nbt: 'SelectedItem.tag.i', entity: bot.uuid }))
|
||||||
}, 50)
|
}, 50)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _filter (message) {
|
function _filter (message) {
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
const { parseNbtText } = require('../util/chat/serialization.js')
|
const { parseNbtText } = require('../util/chat/utility')
|
||||||
const parseText = require('../util/text_parser.js')
|
|
||||||
|
const plainStringify = require('../util/chat/stringify/plain')
|
||||||
|
const colorCodeStringify = require('../util/chat/stringify/color_code')
|
||||||
|
const htmlStringify = require('../util/chat/stringify/html')
|
||||||
|
const ansiStringify = require('../util/chat/stringify/ansi')
|
||||||
|
|
||||||
|
const kaboomParser = require('../util/chat/message_parser/kaboom')
|
||||||
|
|
||||||
function inject (bot) {
|
function inject (bot) {
|
||||||
bot.chat = {
|
bot.chat = {
|
||||||
queue: [],
|
queue: [],
|
||||||
patterns: [],
|
parsers: [kaboomParser],
|
||||||
|
|
||||||
message (message) {
|
message (message) {
|
||||||
bot._client.write('chat_message', {
|
bot._client.write('chat_message', {
|
||||||
message,
|
message,
|
||||||
|
@ -31,7 +38,7 @@ function inject (bot) {
|
||||||
}
|
}
|
||||||
|
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
if (!bot.loggedIn) { return }
|
if (!bot.loggedIn) return
|
||||||
|
|
||||||
const message = bot.chat.queue.shift()
|
const message = bot.chat.queue.shift()
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
|
@ -47,15 +54,20 @@ function inject (bot) {
|
||||||
|
|
||||||
bot.emit('profileless_chat', { message, senderName, type })
|
bot.emit('profileless_chat', { message, senderName, type })
|
||||||
bot.emit('chat', message)
|
bot.emit('chat', message)
|
||||||
|
|
||||||
|
tryParsingMessage(message, { senderName, players: bot.players, lang: bot.registry.language })
|
||||||
})
|
})
|
||||||
|
|
||||||
bot.on('packet.player_chat', (packet) => {console
|
bot.on('packet.player_chat', (packet) => {
|
||||||
|
const plain = packet.plainMessage
|
||||||
const unsigned = parseNbtText(packet.unsignedChatContent)
|
const unsigned = parseNbtText(packet.unsignedChatContent)
|
||||||
const sender = bot.players.find(player => player.uuid === packet.senderUuid)
|
const sender = bot.players.find(player => player.uuid === packet.senderUuid)
|
||||||
const type = bot.registry?.chatFormattingById[packet.type]
|
const type = bot.registry?.chatFormattingById[packet.type]
|
||||||
|
|
||||||
bot.emit('player_chat', { unsigned, sender, type })
|
bot.emit('player_chat', { plain, unsigned, sender, type: type.name })
|
||||||
bot.emit('chat', unsigned)
|
bot.emit('chat', unsigned)
|
||||||
|
|
||||||
|
tryParsingMessage(unsigned, { senderUuid: sender.uuid, players: bot.players, lang: bot.registry.language, plain })
|
||||||
})
|
})
|
||||||
|
|
||||||
bot.on('packet.system_chat', (packet) => {
|
bot.on('packet.system_chat', (packet) => {
|
||||||
|
@ -67,6 +79,28 @@ function inject (bot) {
|
||||||
bot.emit('system_chat', message)
|
bot.emit('system_chat', message)
|
||||||
bot.emit('chat', message)
|
bot.emit('chat', message)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
bot.on('chat', message => {
|
||||||
|
const stringifyOptions = { lang: bot.registry.language }
|
||||||
|
bot.emit('chat_plain', plainStringify(message, stringifyOptions))
|
||||||
|
bot.emit('chat_color_code', colorCodeStringify(message, stringifyOptions))
|
||||||
|
bot.emit('chat_ansi', ansiStringify(message, stringifyOptions))
|
||||||
|
bot.emit('chat_html', htmlStringify(message, stringifyOptions))
|
||||||
|
|
||||||
|
bot.console.log(message)
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
function tryParsingMessage (message, data) {
|
||||||
|
let parsed
|
||||||
|
for (const parser of bot.chat.parsers) {
|
||||||
|
parsed = parser(message, data)
|
||||||
|
if (parsed) break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parsed) return
|
||||||
|
bot.emit('message', parsed)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = inject
|
module.exports = inject
|
||||||
|
|
|
@ -4,6 +4,7 @@ const util = require('util')
|
||||||
const { CommandDispatcher, builder: { LiteralArgumentBuilder: { literal }, RequiredArgumentBuilder: { argument } }, arguments: { StringArgumentType: { greedyString } }, exceptions: { CommandSyntaxException } } = require('brigadier-commands')
|
const { CommandDispatcher, builder: { LiteralArgumentBuilder: { literal }, RequiredArgumentBuilder: { argument } }, arguments: { StringArgumentType: { greedyString } }, exceptions: { CommandSyntaxException } } = require('brigadier-commands')
|
||||||
const CommandSource = require('../util/command/command_source')
|
const CommandSource = require('../util/command/command_source')
|
||||||
const TextMessage = require('../util/command/text_message')
|
const TextMessage = require('../util/command/text_message')
|
||||||
|
const colorCodeStringify = require('../util/chat/stringify/color_code')
|
||||||
|
|
||||||
function inject (bot) {
|
function inject (bot) {
|
||||||
bot.commands = {
|
bot.commands = {
|
||||||
|
@ -16,16 +17,14 @@ function inject (bot) {
|
||||||
isValid
|
isValid
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
bot.on('message', ({ sender, plain }) => {
|
||||||
bot.on('message', (player, message) => {
|
if (!plain.startsWith(bot.prefix)) return
|
||||||
if (!message.startsWith(bot.prefix)) return
|
|
||||||
|
|
||||||
function sendFeedback (message) {
|
function sendFeedback (message) {
|
||||||
bot.core.run('minecraft:tellraw @a ' + JSON.stringify(message))
|
bot.tellraw(message, '@a')
|
||||||
}
|
}
|
||||||
bot.commands.execute(message.substring(bot.prefix.length), new CommandSource({ bot, player, sendFeedback }))
|
bot.commands.execute(plain.substring(bot.prefix.length), new CommandSource({ bot, player: sender, sendFeedback }))
|
||||||
})
|
})
|
||||||
*/
|
|
||||||
|
|
||||||
function add (command) {
|
function add (command) {
|
||||||
if (command.register) {
|
if (command.register) {
|
||||||
|
|
|
@ -1,32 +1,8 @@
|
||||||
const fs = require('fs')
|
const fs = require('fs')
|
||||||
const util = require('util')
|
const util = require('util')
|
||||||
const moment = require('moment')
|
const colorCodeStringify = require('../util/chat/stringify/color_code')
|
||||||
|
const ansiStringify = require('../util/chat/stringify/ansi')
|
||||||
const CommandSource = require('../util/command/command_source')
|
const CommandSource = require('../util/command/command_source')
|
||||||
const parseText = require('../util/text_parser')
|
|
||||||
const ansimap = {
|
|
||||||
0: '\x1b[0m\x1b[30m',
|
|
||||||
1: '\x1b[0m\x1b[34m',
|
|
||||||
2: '\x1b[0m\x1b[32m',
|
|
||||||
3: '\x1b[0m\x1b[36m',
|
|
||||||
4: '\x1b[0m\x1b[31m',
|
|
||||||
5: '\x1b[0m\x1b[35m',
|
|
||||||
6: '\x1b[0m\x1b[33m',
|
|
||||||
7: '\x1b[0m\x1b[37m',
|
|
||||||
8: '\x1b[0m\x1b[90m',
|
|
||||||
9: '\x1b[0m\x1b[94m',
|
|
||||||
a: '\x1b[0m\x1b[92m',
|
|
||||||
b: '\x1b[0m\x1b[96m',
|
|
||||||
c: '\x1b[0m\x1b[91m',
|
|
||||||
d: '\x1b[0m\x1b[95m',
|
|
||||||
e: '\x1b[0m\x1b[93m',
|
|
||||||
f: '\x1b[0m\x1b[97m',
|
|
||||||
r: '\x1b[0m',
|
|
||||||
l: '\x1b[1m',
|
|
||||||
o: '\x1b[3m',
|
|
||||||
n: '\x1b[4m',
|
|
||||||
m: '\x1b[9m',
|
|
||||||
k: '\x1b[6m'
|
|
||||||
}
|
|
||||||
|
|
||||||
function inject (bot) {
|
function inject (bot) {
|
||||||
bot.console = {
|
bot.console = {
|
||||||
|
@ -50,19 +26,30 @@ function inject (bot) {
|
||||||
}
|
}
|
||||||
function _log (prefix, stdout, data) {
|
function _log (prefix, stdout, data) {
|
||||||
// format it
|
// format it
|
||||||
data = `[${moment().format('HH:mm:ss')} ${prefix}\u00a7r] ${data}\n`
|
const _prefix = `[${formatDate()} ${prefix}\u00a7r] `
|
||||||
|
const stringifyOptions = { lang: bot.registry.language }
|
||||||
|
|
||||||
|
const formattedData = _prefix + colorCodeStringify(data, stringifyOptions) + '\n'
|
||||||
|
const ansi = ansiStringify(_prefix, stringifyOptions) + ansiStringify(data, stringifyOptions) + '\x1b[0m\n'
|
||||||
|
|
||||||
// log to file
|
// log to file
|
||||||
const filepath = bot.console.filepath
|
const filepath = bot.console.filepath
|
||||||
if (filepath != null) {
|
if (filepath != null) {
|
||||||
fs.appendFile(filepath, data, err => {
|
fs.appendFile(filepath, formattedData, err => {
|
||||||
if (err) console.error(err)
|
if (err) console.error(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
bot.tellraw(formattedData, '_ChipMC_')
|
||||||
// log to stdout
|
// log to stdout
|
||||||
data = data.replace(/\u00a7.?/g, m => ansimap[m.slice(1)] ?? '') + '\x1b[0m'
|
stdout.write(ansi + '')
|
||||||
stdout.write(data)
|
}
|
||||||
|
|
||||||
|
function formatDate (date = new Date()) {
|
||||||
|
const hours = date.getHours()
|
||||||
|
const minutes = date.getMinutes()
|
||||||
|
const seconds = date.getSeconds()
|
||||||
|
|
||||||
|
return [hours, minutes, seconds].map(n => n.toString().padStart(2, '0')).join(':')
|
||||||
}
|
}
|
||||||
|
|
||||||
function setRl (rl) {
|
function setRl (rl) {
|
||||||
|
@ -83,8 +70,7 @@ function inject (bot) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendFeedback (text, sendFeedback) {
|
function sendFeedback (text, sendFeedback) {
|
||||||
const { raw } = parseText(message)
|
bot.console.log(text)
|
||||||
bot.console.log(raw)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const matrix = require('matrix-js-sdk')
|
const matrix = require('matrix-js-sdk')
|
||||||
const htmlStringify = require('../util/chat/html_stringifier')
|
|
||||||
const CommandSource = require('../util/command/command_source')
|
const CommandSource = require('../util/command/command_source')
|
||||||
|
const htmlStringify = require('../util/chat/stringify/html')
|
||||||
|
|
||||||
function inject (bot, options) {
|
function inject (bot, options) {
|
||||||
if (!options.matrix?.enabled) return
|
if (!options.matrix?.enabled) return
|
||||||
|
@ -8,17 +8,33 @@ function inject (bot, options) {
|
||||||
bot.matrix = {
|
bot.matrix = {
|
||||||
client: options.matrix.client ?? matrix.createClient(options.matrix),
|
client: options.matrix.client ?? matrix.createClient(options.matrix),
|
||||||
roomId: options.matrix.roomId,
|
roomId: options.matrix.roomId,
|
||||||
commandPrefix: options.matrix.commandPrefix
|
commandPrefix: options.matrix.commandPrefix,
|
||||||
|
inviteUrl: String(options.matrix.inviteUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
bot.on('chat', async message => {
|
const startTime = Date.now()
|
||||||
sendMessage(message)
|
|
||||||
|
bot.on('chat_html', async html => {
|
||||||
|
sendMessage(html)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const matrixPrefix = {
|
||||||
|
text: 'ChipmunkBot Matrix',
|
||||||
|
hoverEvent: {
|
||||||
|
action: 'show_text',
|
||||||
|
contents: 'Click to copy the invite link for the Matrix space to your clipboard!'
|
||||||
|
},
|
||||||
|
clickEvent: {
|
||||||
|
action: 'copy_to_clipboard', // * Minecraft, and Java's URL class in general, seem to hate `#`, so open_url does not work.
|
||||||
|
value: bot.matrix.inviteUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bot.matrix.client.on('Room.timeline', (event, room, toStartOfTimeline) => {
|
bot.matrix.client.on('Room.timeline', (event, room, toStartOfTimeline) => {
|
||||||
if (event.getRoomId() !== bot.matrix.roomId || event.getType() !== 'm.room.message' || event.sender.userId === bot.matrix.client.getUserId()) return
|
if (event.getRoomId() !== bot.matrix.roomId || event.getType() !== 'm.room.message' || event.getTs() < startTime || event.sender.userId === bot.matrix.client.getUserId()) return
|
||||||
|
|
||||||
const content = event.getContent()
|
const content = event.getContent()
|
||||||
|
const permissionLevel = event.sender.powerLevelNorm
|
||||||
let message = content.body
|
let message = content.body
|
||||||
|
|
||||||
if (content.url) {
|
if (content.url) {
|
||||||
|
@ -31,7 +47,7 @@ function inject (bot, options) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (message.startsWith(bot.matrix.commandPrefix)) {
|
} else if (message.startsWith(bot.matrix.commandPrefix)) {
|
||||||
const source = new CommandSource({ bot, permissionLevel: 1, sendFeedback })
|
const source = new CommandSource({ bot, permissionLevel, sendFeedback })
|
||||||
bot.commands.execute(message.substring(bot.matrix.commandPrefix.length), source)
|
bot.commands.execute(message.substring(bot.matrix.commandPrefix.length), source)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -41,21 +57,19 @@ function inject (bot, options) {
|
||||||
text: String(event.sender.rawDisplayName || event.sender.name || event.sender.userId),
|
text: String(event.sender.rawDisplayName || event.sender.name || event.sender.userId),
|
||||||
hoverEvent: {
|
hoverEvent: {
|
||||||
action: 'show_text',
|
action: 'show_text',
|
||||||
contents: ['User ID: ', String(event.sender.userId), '\nClick to copy the user id']
|
contents: [String(event.sender.userId), '\nPermission Level: ', String(permissionLevel), '\n\nClick to copy the User ID']
|
||||||
},
|
},
|
||||||
clickEvent: {
|
clickEvent: {
|
||||||
action: 'copy_to_clipboard',
|
action: 'copy_to_clipboard',
|
||||||
value: String(event.sender.userId)
|
value: String(event.sender.userId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bot.fancyMsg('ChipmunkBot Matrix', senderText, message)
|
bot.fancyMsg(matrixPrefix, senderText, message)
|
||||||
})
|
})
|
||||||
|
|
||||||
let dequeuingMessages = false
|
let dequeuingMessages = false
|
||||||
let queue = []
|
let queue = []
|
||||||
async function sendMessage (message) {
|
async function sendMessage (html) {
|
||||||
const html = htmlStringify(message, { lang: bot.registry.language })
|
|
||||||
|
|
||||||
queue.push(html)
|
queue.push(html)
|
||||||
if (dequeuingMessages) return
|
if (dequeuingMessages) return
|
||||||
|
|
||||||
|
@ -85,7 +99,8 @@ function inject (bot, options) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function sendFeedback (text, sendFeedback) {
|
function sendFeedback (text, sendFeedback) {
|
||||||
sendMessage(text)
|
const html = htmlStringify(text, { lang: bot.registry.language })
|
||||||
|
sendMessage(html)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const { parseNbtText } = require('../util/chat/serialization.js')
|
const { parseNbtText } = require('../util/chat/utility')
|
||||||
|
|
||||||
const gamemodes = ['survival', 'creative', 'adventure', 'spectator']
|
const gamemodes = ['survival', 'creative', 'adventure', 'spectator']
|
||||||
|
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
const { colors, formatting, reset } = require('./formatting.json')
|
|
||||||
|
|
||||||
function escapeSequenceStringify (text, { sequences, lang = {}, parent } = {}) {
|
|
||||||
text = normalize(text)
|
|
||||||
|
|
||||||
let rootText = false
|
|
||||||
if (parent == null) {
|
|
||||||
parent = {}
|
|
||||||
rootText = true
|
|
||||||
}
|
|
||||||
|
|
||||||
let formattingString = ''
|
|
||||||
|
|
||||||
formattingString += sequences.colors[text.color ?? parent.color] || ''
|
|
||||||
|
|
||||||
if (text.bold ?? parent.bold) formattingString += sequences.formatting.bold
|
|
||||||
if (text.italic ?? parent.italic) formattingString += sequences.formatting.italic
|
|
||||||
if (text.underlined ?? parent.underlined) formattingString += sequences.formatting.underlined
|
|
||||||
if (text.strikethrough ?? parent.strikethrough) formattingString += sequences.formatting.strikethrough
|
|
||||||
if (text.obfuscated ?? parent.obfuscated) formattingString += sequences.formatting.obfuscated
|
|
||||||
|
|
||||||
if (!formattingString && !rootText) formattingString = sequences.reset
|
|
||||||
|
|
||||||
let string = formattingString
|
|
||||||
|
|
||||||
if (text.text != null) string += text.text
|
|
||||||
if (text.translate != null) {
|
|
||||||
let format
|
|
||||||
if (Object.hasOwn(lang, text.translate)) format = lang[text.translate]
|
|
||||||
else if (text.fallback != null) format = text.fallback
|
|
||||||
else format = text.translate
|
|
||||||
|
|
||||||
const _with = text.with || []
|
|
||||||
|
|
||||||
string += format.replace(/%(?:(\d+)\$)?(s|%)/g, (g0, g1) => {
|
|
||||||
if (g0 === '%%') return '%'
|
|
||||||
|
|
||||||
const idx = g1 ? parseInt(g1) : i++
|
|
||||||
if (_with[idx]) {
|
|
||||||
return escapeSequenceStringify(_with[idx], { sequences, lang, parent: text }) + formattingString
|
|
||||||
}
|
|
||||||
|
|
||||||
return ''
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (text.selector != null) string += text.selector
|
|
||||||
if (text.keybind) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
if (text.extra) {
|
|
||||||
for (const extra of text.extra) {
|
|
||||||
string += escapeSequenceStringify(extra, { sequences, lang, parent: text })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return string
|
|
||||||
}
|
|
||||||
|
|
||||||
const colorCodeMapper = colors => Object.fromEntries(colors.map(color => [color.name, color.code]))
|
|
||||||
const colorCodeSequences = { colors: colorCodeMapper(colors), formatting: colorCodeMapper(formatting), reset: reset.code }
|
|
||||||
const colorCodeStringify = (text, { lang }) => escapeSequenceStringify(text, { sequences: colorCodeSequences, lang })
|
|
||||||
|
|
||||||
const ansiMapper = colors => Object.fromEntries(colors.map(color => [color.name, color.ansi]))
|
|
||||||
const ansiSequences = { colors: ansiMapper(colors), formatting: ansiMapper(formatting), reset: reset.ansi }
|
|
||||||
const ansiSringify = (text, { lang }) => escapeSequenceStringify(text, { sequences: ansiSequences, lang })
|
|
||||||
|
|
||||||
function normalize (text) {
|
|
||||||
if (typeof text === 'string') return { text }
|
|
||||||
if (Array.isArray(text)) {
|
|
||||||
const text2 = [...text]
|
|
||||||
const text3 = { ...normalize(text2.shift()) }
|
|
||||||
text3.extra = text2
|
|
||||||
return text3
|
|
||||||
}
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { escapeSequenceStringify, colorCodeStringify, ansiSringify }
|
|
|
@ -18,11 +18,11 @@
|
||||||
{"name": "white", "code": "§f", "ansi": "\u001b[0m\u001b[97m", "rgb": 16777215}
|
{"name": "white", "code": "§f", "ansi": "\u001b[0m\u001b[97m", "rgb": 16777215}
|
||||||
],
|
],
|
||||||
"formatting": [
|
"formatting": [
|
||||||
{"name": "bold", "code": "§l"},
|
{"name": "bold", "code": "§l", "ansi": "\u001b[1m"},
|
||||||
{"name": "italic", "code": "§o"},
|
{"name": "italic", "code": "§o", "ansi": "\u001b[3m"},
|
||||||
{"name": "underlined", "code": "§n"},
|
{"name": "underlined", "code": "§n", "ansi": "\u001b[4m"},
|
||||||
{"name": "strikethrough", "code": "§m"},
|
{"name": "strikethrough", "code": "§m", "ansi": "\u001b[9m"},
|
||||||
{"name": "obfuscated", "code": "§k"}
|
{"name": "obfuscated", "code": "§k", "ansi": "\u001b[6m"}
|
||||||
],
|
],
|
||||||
"reset": {"name": "reset", "code": "§r", "ansi": "\u001b[0m"}
|
"reset": {"name": "reset", "code": "§r", "ansi": "\u001b[0m"}
|
||||||
}
|
}
|
92
util/chat/message_parser/kaboom.js
Normal file
92
util/chat/message_parser/kaboom.js
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
const util = require('util')
|
||||||
|
const { getChangedFormatting } = require('../utility')
|
||||||
|
|
||||||
|
const { colors } = require('../formatting.json')
|
||||||
|
const colormap = Object.fromEntries(colors.map(color => [color.name, '&' + color.code[1]]))
|
||||||
|
|
||||||
|
// This is the regex used for matching urls in extras, with ^ and $ added
|
||||||
|
const urlRegex = /^((https?:\/\/(ww(w|\d)\.)?|ww(w|\d))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&/=]*))$/
|
||||||
|
|
||||||
|
function parseMessage (message, data) {
|
||||||
|
if (message === null || typeof message !== 'object') return
|
||||||
|
|
||||||
|
if (message.text !== '' || !Array.isArray(message.extra) || message.extra.length < 3) return
|
||||||
|
|
||||||
|
const children = message.extra
|
||||||
|
|
||||||
|
const prefix = children[0]
|
||||||
|
let displayName = data.senderName ?? { text: '' }
|
||||||
|
let contents = { text: '' }
|
||||||
|
|
||||||
|
if (isSeparatorAt(children, 1)) { // Missing/blank display name
|
||||||
|
if (children.length > 3) contents = children[3]
|
||||||
|
} else if (isSeparatorAt(children, 2)) {
|
||||||
|
displayName = children[1]
|
||||||
|
if (children.length > 4) contents = children[4]
|
||||||
|
} else {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const playerListDisplayName = { extra: [prefix, displayName], text: '' }
|
||||||
|
let sender
|
||||||
|
if (data.senderUuid) {
|
||||||
|
sender = data.players.find(player => player.uuid === data.senderUuid)
|
||||||
|
} else {
|
||||||
|
const playerListDisplayName = { extra: [prefix, displayName], text: '' }
|
||||||
|
sender = data.players.find(player => util.isDeepStrictEqual(player.displayName, playerListDisplayName))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sender) return undefined
|
||||||
|
|
||||||
|
let plain = data.plain
|
||||||
|
if (!plain) plain = getPlainContents(contents)
|
||||||
|
|
||||||
|
return { sender, contents, type: 'minecraft:chat', displayName, plain }
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPlainContents (contents) {
|
||||||
|
let string = ''
|
||||||
|
|
||||||
|
let format = getPlainFormatting(contents, {})
|
||||||
|
if (format.startsWith('&r')) format = format.substring(2)
|
||||||
|
string += format
|
||||||
|
|
||||||
|
if (contents.text) string += contents.text
|
||||||
|
|
||||||
|
if (contents.extra) {
|
||||||
|
let previousChild = contents
|
||||||
|
for (const child of contents.extra) {
|
||||||
|
string += getPlainFormatting(child, previousChild)
|
||||||
|
string += child.text
|
||||||
|
|
||||||
|
previousChild = child
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPlainFormatting (text, previousText) {
|
||||||
|
if (isUrl(text)) return ''
|
||||||
|
|
||||||
|
const format = getChangedFormatting(text, previousText, {})
|
||||||
|
let string = ''
|
||||||
|
|
||||||
|
if ('color' in format) string += colormap[format.color] || '&r'
|
||||||
|
|
||||||
|
if (format.bold) string += '&l'
|
||||||
|
if (format.italic) string += '&o'
|
||||||
|
if (format.underlined) string += '&n'
|
||||||
|
if (format.strikethrough) string += '&m'
|
||||||
|
if (format.obfuscated) string += '&k'
|
||||||
|
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSeparatorAt (children, start) {
|
||||||
|
return (children[start]?.text === ':' || children[start]?.text === '\xa7f:') && children[start + 1]?.text === ' '
|
||||||
|
}
|
||||||
|
|
||||||
|
const isUrl = text => text.color === 'blue' && text.underlined && text.clickEvent?.action === 'open_url' && text.clickEvent?.value === (text.text?.includes?.('://') ? text.text : 'https://' + text.text) && urlRegex.test(text.text)
|
||||||
|
|
||||||
|
module.exports = parseMessage
|
|
@ -1,12 +0,0 @@
|
||||||
function parseJsonText (json) {
|
|
||||||
return JSON.parse(json)
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseNbtText (data) {
|
|
||||||
if (typeof data.value !== 'object') return data.value
|
|
||||||
if (Array.isArray(data.value)) return [...data.value]
|
|
||||||
if (data.type === 'list') return data.value.value.map(value => parseNbtText({ value }))
|
|
||||||
return Object.fromEntries(Object.entries(data.value).map(([key, value]) => ([key === '' ? 'text' : key, parseNbtText(value)])))
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { parseJsonText, parseNbtText }
|
|
100
util/chat/stringify/ansi.js
Normal file
100
util/chat/stringify/ansi.js
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
const { colors, formatting, reset } = require('../formatting.json')
|
||||||
|
const { normalize, intToRgb, getChangedFormatting } = require('../utility')
|
||||||
|
|
||||||
|
const colormap = Object.fromEntries(colors.map(color => [color.name, color.ansi]))
|
||||||
|
const formatmap = Object.fromEntries(formatting.map(format => [format.name, format.ansi]))
|
||||||
|
const colorcodemap = Object.fromEntries(colors.map(color => [color.code, color.ansi]))
|
||||||
|
const formatcodemap = Object.fromEntries(formatting.map(format => [format.code, format.ansi]))
|
||||||
|
const baseColors = colors.map(color => intToRgb(color.rgb))
|
||||||
|
|
||||||
|
function ansiStringify (text, { lang = {}, previousText, parent } = {}) {
|
||||||
|
text = normalize(text)
|
||||||
|
|
||||||
|
let string = getFormatting(text, previousText, parent)
|
||||||
|
|
||||||
|
if (text.text != null) string += preprocessText(text.text)
|
||||||
|
else if (text.translate != null) {
|
||||||
|
let format
|
||||||
|
if (Object.hasOwn(lang, text.translate)) format = lang[text.translate]
|
||||||
|
else if (text.fallback != null) format = text.fallback
|
||||||
|
else format = text.translate
|
||||||
|
|
||||||
|
const _with = text.with || []
|
||||||
|
let i = 0
|
||||||
|
|
||||||
|
string += preprocessText(format).replace(/%(?:(\d+)\$)?(s|%)/g, (g0, g1) => {
|
||||||
|
if (g0 === '%%') return '%'
|
||||||
|
|
||||||
|
const idx = g1 ? parseInt(g1) : i++
|
||||||
|
if (_with[idx]) {
|
||||||
|
return ansiStringify(_with[idx], { lang, previousText: text, parent: text }) + getFormatting(text, _with[idx], parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else if (text.selector != null) string += preprocessText(text.selector)
|
||||||
|
else if (text.keybind) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.extra) {
|
||||||
|
let previousChild = text
|
||||||
|
for (const child of text.extra) {
|
||||||
|
string += ansiStringify(child, { lang, previousText: previousChild, parent: text })
|
||||||
|
previousChild = child
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFormatting (text, previousText, parent) {
|
||||||
|
const format = getChangedFormatting(text, previousText, parent)
|
||||||
|
let string = ''
|
||||||
|
|
||||||
|
if ('color' in format) {
|
||||||
|
if (format.color && format.color[0] === '#') {
|
||||||
|
const rgb = parseInt(format.color.substring(1), 16) & 0xffffff
|
||||||
|
const [r, g, b] = intToRgb(rgb)
|
||||||
|
|
||||||
|
string += `\x1b[38;2;${r};${g};${b}m`
|
||||||
|
}
|
||||||
|
else if (colormap[format.color]) string += colormap[format.color]
|
||||||
|
else if (parent) string += reset.ansi
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format.bold) string += formatmap.bold
|
||||||
|
if (format.italic) string += formatmap.italic
|
||||||
|
if (format.underlined) string += formatmap.underlined
|
||||||
|
if (format.strikethrough) string += formatmap.strikethrough
|
||||||
|
if (format.obfuscated) string += formatmap.obfuscated
|
||||||
|
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
|
||||||
|
function preprocessText (input) {
|
||||||
|
let string = ''
|
||||||
|
|
||||||
|
for (let i = 0; i < input.length; i++) {
|
||||||
|
const c = input[i]
|
||||||
|
|
||||||
|
if (c === '§') {
|
||||||
|
if ((i + 1) >= input.length) break
|
||||||
|
|
||||||
|
const code = input.substring(i, i + 2)
|
||||||
|
i++
|
||||||
|
|
||||||
|
if (colorcodemap[code]) string += colorcodemap[code]
|
||||||
|
else if (formatcodemap[code]) string += formatcodemap[code]
|
||||||
|
else if (code === reset.code) string += reset.ansi
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
else string += c
|
||||||
|
}
|
||||||
|
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = ansiStringify
|
71
util/chat/stringify/color_code.js
Normal file
71
util/chat/stringify/color_code.js
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
const { colors, formatting, reset } = require('../formatting.json')
|
||||||
|
const { normalize, intToRgb, getNearestColor, getChangedFormatting } = require('../utility')
|
||||||
|
|
||||||
|
const colormap = Object.fromEntries(colors.map(color => [color.name, color.code]))
|
||||||
|
const formatmap = Object.fromEntries(formatting.map(format => [format.name, format.code]))
|
||||||
|
|
||||||
|
function colorCodeStringify (text, { lang = {}, previousText, parent } = {}) {
|
||||||
|
text = normalize(text)
|
||||||
|
|
||||||
|
let string = getFormatting(text, previousText, parent)
|
||||||
|
|
||||||
|
if (text.text != null) string += text.text
|
||||||
|
else if (text.translate != null) {
|
||||||
|
let format
|
||||||
|
if (Object.hasOwn(lang, text.translate)) format = lang[text.translate]
|
||||||
|
else if (text.fallback != null) format = text.fallback
|
||||||
|
else format = text.translate
|
||||||
|
|
||||||
|
const _with = text.with || []
|
||||||
|
let i = 0
|
||||||
|
|
||||||
|
string += format.replace(/%(?:(\d+)\$)?(s|%)/g, (g0, g1) => {
|
||||||
|
if (g0 === '%%') return '%'
|
||||||
|
|
||||||
|
const idx = g1 ? parseInt(g1) : i++
|
||||||
|
if (_with[idx]) {
|
||||||
|
return colorCodeStringify(_with[idx], { lang, previousText: text, parent: text }) + getFormatting(text, _with[idx], parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else if (text.selector != null) string += text.selector
|
||||||
|
else if (text.keybind) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.extra) {
|
||||||
|
let previousChild = text
|
||||||
|
for (const child of text.extra) {
|
||||||
|
string += colorCodeStringify(child, { lang, previousText: previousChild, parent: text })
|
||||||
|
previousChild = child
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFormatting (text, previousText, parent) {
|
||||||
|
const format = getChangedFormatting(text, previousText, parent)
|
||||||
|
let string = ''
|
||||||
|
|
||||||
|
if ('color' in format) {
|
||||||
|
if (format.color && format.color[0] === '#') {
|
||||||
|
const rgb = parseInt(format.color.substring(1), 16) & 0xffffff
|
||||||
|
string += colors[getNearestColor(rgb)].code
|
||||||
|
}
|
||||||
|
else if (colormap[format.color]) string += colormap[format.color]
|
||||||
|
else if (parent) string += reset.code
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format.bold) string += formatmap.bold
|
||||||
|
if (format.italic) string += formatmap.italic
|
||||||
|
if (format.underlined) string += formatmap.underlined
|
||||||
|
if (format.strikethrough) string += formatmap.strikethrough
|
||||||
|
if (format.obfuscated) string += formatmap.obfuscated
|
||||||
|
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = colorCodeStringify
|
|
@ -1,12 +1,8 @@
|
||||||
const { colors } = require('./formatting.json')
|
const { colors } = require('../formatting.json')
|
||||||
|
const { normalize } = require('../utility')
|
||||||
|
|
||||||
const colorsByName = {}
|
const colormap = Object.fromEntries(colors.map(color => [color.name, color.rgb]))
|
||||||
const colorsByCode = {}
|
const colorcodemap = Object.fromEntries(colors.map(color => [color.code, color.rgb]))
|
||||||
for (const color of colors) {
|
|
||||||
const hex = '#' + color.rgb.toString(16).padStart(6, '0')
|
|
||||||
colorsByName[color.name] = hex
|
|
||||||
colorsByCode[color.code] = hex
|
|
||||||
}
|
|
||||||
|
|
||||||
const reservedCharacters = {
|
const reservedCharacters = {
|
||||||
'"': '"',
|
'"': '"',
|
||||||
|
@ -22,7 +18,7 @@ function htmlStringify (text, { lang = {} } = {}) {
|
||||||
let string = ''
|
let string = ''
|
||||||
|
|
||||||
if (text.text != null) string += preprocessText(text.text)
|
if (text.text != null) string += preprocessText(text.text)
|
||||||
if (text.translate != null) {
|
else if (text.translate != null) {
|
||||||
let format
|
let format
|
||||||
if (Object.hasOwn(lang, text.translate)) format = lang[text.translate]
|
if (Object.hasOwn(lang, text.translate)) format = lang[text.translate]
|
||||||
else if (text.fallback != null) format = text.fallback
|
else if (text.fallback != null) format = text.fallback
|
||||||
|
@ -42,8 +38,8 @@ function htmlStringify (text, { lang = {} } = {}) {
|
||||||
return ''
|
return ''
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
if (text.selector != null) string += preprocessText(text.selector)
|
else if (text.selector != null) string += preprocessText(text.selector)
|
||||||
if (text.keybind) {
|
else if (text.keybind) {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,7 +49,10 @@ function htmlStringify (text, { lang = {} } = {}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (text.color) string = `<font color="${text.color[0] === '#' ? ('#' + text.color.substring(1).padStart(6, '0')) : colorsByName[text.color]}">${string}</font>`
|
if (text.color) {
|
||||||
|
const rgb = text.color[0] === '#' ? parseInt(text.color.substring(1), 16) : colormap[text.color]
|
||||||
|
if (rgb) string = `<font color="${rgb.toString(16).padStart(6, '0')}">${string}</font>`
|
||||||
|
}
|
||||||
|
|
||||||
// formatting
|
// formatting
|
||||||
if (text.bold) string = `<b>${string}</b>`
|
if (text.bold) string = `<b>${string}</b>`
|
||||||
|
@ -78,7 +77,7 @@ function preprocessText (input) {
|
||||||
const code = input.substring(i, i + 2)
|
const code = input.substring(i, i + 2)
|
||||||
i++
|
i++
|
||||||
|
|
||||||
const hex = colorsByCode[code]
|
const hex = colorcodemap[code]
|
||||||
if (hex) {
|
if (hex) {
|
||||||
string += closing
|
string += closing
|
||||||
string += `<font color="${hex}">`
|
string += `<font color="${hex}">`
|
||||||
|
@ -93,10 +92,10 @@ function preprocessText (input) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (code === '§l') { string += '<b>'; closing += '</b>' }
|
if (code === '§l') { string += '<b>'; closing += '</b>' }
|
||||||
if (code === '§o') { string += '<i>'; closing += '</i>' }
|
else if (code === '§o') { string += '<i>'; closing += '</i>' }
|
||||||
if (code === '§n') { string += '<u>'; closing += '</u>' }
|
else if (code === '§n') { string += '<u>'; closing += '</u>' }
|
||||||
if (code === '§m') { string += '<strike>'; closing += '</strike>' }
|
else if (code === '§m') { string += '<strike>'; closing += '</strike>' }
|
||||||
if (code === '§k') { string += '<blink>'; closing += '</blink>' }
|
else if (code === '§k') { string += '<blink>'; closing += '</blink>' }
|
||||||
|
|
||||||
continue // Do not append the escape sequence itself to the string
|
continue // Do not append the escape sequence itself to the string
|
||||||
}
|
}
|
||||||
|
@ -109,15 +108,4 @@ function preprocessText (input) {
|
||||||
return string
|
return string
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalize (text) {
|
|
||||||
if (typeof text === 'string') return { text }
|
|
||||||
if (Array.isArray(text)) {
|
|
||||||
const text2 = [...text]
|
|
||||||
const text3 = { ...normalize(text2.shift()) }
|
|
||||||
text3.extra = text2
|
|
||||||
return text3
|
|
||||||
}
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = htmlStringify
|
module.exports = htmlStringify
|
43
util/chat/stringify/plain.js
Normal file
43
util/chat/stringify/plain.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
const { normalize } = require('../utility')
|
||||||
|
|
||||||
|
function plainStringify (text, { lang = {} } = {}) {
|
||||||
|
text = normalize(text)
|
||||||
|
|
||||||
|
let string = ''
|
||||||
|
|
||||||
|
if (text.text != null) string += text.text
|
||||||
|
else if (text.translate != null) {
|
||||||
|
let format
|
||||||
|
if (Object.hasOwn(lang, text.translate)) format = lang[text.translate]
|
||||||
|
else if (text.fallback != null) format = text.fallback
|
||||||
|
else format = text.translate
|
||||||
|
|
||||||
|
const _with = text.with || []
|
||||||
|
let i = 0
|
||||||
|
|
||||||
|
string += format.replace(/%(?:(\d+)\$)?(s|%)/g, (g0, g1) => {
|
||||||
|
if (g0 === '%%') return '%'
|
||||||
|
|
||||||
|
const idx = g1 ? parseInt(g1) : i++
|
||||||
|
if (_with[idx]) {
|
||||||
|
return plainStringify(_with[idx], { lang })
|
||||||
|
}
|
||||||
|
|
||||||
|
return ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else if (text.selector != null) string += text.selector
|
||||||
|
else if (text.keybind) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text.extra) {
|
||||||
|
for (const extra of text.extra) {
|
||||||
|
string += plainStringify(extra, { lang })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return string
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = plainStringify
|
89
util/chat/utility.js
Normal file
89
util/chat/utility.js
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
const { colors, formatting } = require('./formatting.json')
|
||||||
|
|
||||||
|
const formatNames = Object.fromEntries(formatting.map(format => [format.name, true]))
|
||||||
|
const baseColors = colors.map(color => intToRgb(color.rgb))
|
||||||
|
|
||||||
|
function parseJsonText (json) {
|
||||||
|
return JSON.parse(json)
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseNbtText (data) {
|
||||||
|
if (typeof data.value !== 'object') return data.value
|
||||||
|
if (Array.isArray(data.value)) return [...data.value]
|
||||||
|
if (data.type === 'list') return data.value.value.map(value => parseNbtText({ value }))
|
||||||
|
return Object.fromEntries(Object.entries(data.value).map(([key, value]) => ([key === '' ? 'text' : key, parseNbtText(value)])))
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalize (text) {
|
||||||
|
if (typeof text === 'string') return { text }
|
||||||
|
if (Array.isArray(text)) {
|
||||||
|
const text2 = [...text]
|
||||||
|
const text3 = { ...normalize(text2.shift()) }
|
||||||
|
text3.extra = text2
|
||||||
|
return text3
|
||||||
|
}
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
|
||||||
|
function rgbToInt (r, g, b) {
|
||||||
|
return (r & 0xff) << 16 | (g & 0xff) << 8 | (b & 0xff)
|
||||||
|
}
|
||||||
|
|
||||||
|
function intToRgb (int) {
|
||||||
|
const r = (int >> 16) & 0xff
|
||||||
|
const g = (int >> 8) & 0xff
|
||||||
|
const b = int & 0xff
|
||||||
|
|
||||||
|
return [r, g, b]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// skidded from https://github.com/edqx/amongus-experiments/blob/master/index5.js#L34C1-L56C2
|
||||||
|
function getNearestColor (rgb) {
|
||||||
|
if (typeof rgb === 'number') rgb = intToRgb(rgb)
|
||||||
|
const [red, green, blue] = rgb
|
||||||
|
|
||||||
|
let nearestDist = Infinity
|
||||||
|
let nearestColorIdx
|
||||||
|
for (let i = 0; i < baseColors.length; i++) {
|
||||||
|
const baseColor = baseColors[i]
|
||||||
|
const [baseR, baseG, baseB] = baseColor
|
||||||
|
|
||||||
|
const diffR = (red - baseR) * (red - baseR)
|
||||||
|
const diffG = (green - baseG) * (green - baseG)
|
||||||
|
const diffB = (blue - baseB) * (blue - baseB)
|
||||||
|
|
||||||
|
const dist = Math.sqrt(diffR + diffG + diffB)
|
||||||
|
|
||||||
|
if (dist < nearestDist) {
|
||||||
|
nearestDist = dist
|
||||||
|
nearestColorIdx = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nearestColorIdx
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChangedFormatting (text, previousText, parent = {}) {
|
||||||
|
// Merge both texts with their parents
|
||||||
|
text = { ...parent, ...text }
|
||||||
|
if (previousText) previousText = { ...parent, ...previousText }
|
||||||
|
|
||||||
|
const getAllFormatting = () => Object.fromEntries(Object.entries(text).filter(([key, value]) => formatNames[key] || key === 'color'))
|
||||||
|
|
||||||
|
if (!previousText || text.color !== previousText.color) return getAllFormatting()
|
||||||
|
|
||||||
|
const format = {}
|
||||||
|
for (const key in formatNames) {
|
||||||
|
if (text[key] && previousText[key]) continue
|
||||||
|
if (previousText[key] && !text[key]) return getAllFormatting() // Color codes do not have any way to unset specific formattings
|
||||||
|
if (text[key] == null) continue
|
||||||
|
|
||||||
|
format[key] = format[key]
|
||||||
|
}
|
||||||
|
format.color = text.color
|
||||||
|
|
||||||
|
return format
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { parseJsonText, parseNbtText, normalize, rgbToInt, intToRgb, getNearestColor, getChangedFormatting }
|
|
@ -1,4 +1,4 @@
|
||||||
const parseText = require('../text_parser.js')
|
const plainStringify = require('../chat/stringify/plain')
|
||||||
|
|
||||||
class TextMessage {
|
class TextMessage {
|
||||||
constructor (text) {
|
constructor (text) {
|
||||||
|
@ -6,8 +6,7 @@ class TextMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
getString () {
|
getString () {
|
||||||
const { clean } = parseText(this.text)
|
return plainStringify(this.text, { lang: {} })
|
||||||
return clean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
toString () {
|
toString () {
|
||||||
|
|
|
@ -1,126 +0,0 @@
|
||||||
const { language } = require('minecraft-data')('1.20.4')
|
|
||||||
|
|
||||||
const colormap = {
|
|
||||||
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'
|
|
||||||
}
|
|
||||||
|
|
||||||
const ansimap = {
|
|
||||||
'§0': '\x1b[0m\x1b[30m',
|
|
||||||
'§1': '\x1b[0m\x1b[34m',
|
|
||||||
'§2': '\x1b[0m\x1b[32m',
|
|
||||||
'§3': '\x1b[0m\x1b[36m',
|
|
||||||
'§4': '\x1b[0m\x1b[31m',
|
|
||||||
'§5': '\x1b[0m\x1b[35m',
|
|
||||||
'§6': '\x1b[0m\x1b[33m',
|
|
||||||
'§7': '\x1b[0m\x1b[37m',
|
|
||||||
'§8': '\x1b[0m\x1b[90m',
|
|
||||||
'§9': '\x1b[0m\x1b[94m',
|
|
||||||
'§a': '\x1b[0m\x1b[92m',
|
|
||||||
'§b': '\x1b[0m\x1b[96m',
|
|
||||||
'§c': '\x1b[0m\x1b[91m',
|
|
||||||
'§d': '\x1b[0m\x1b[95m',
|
|
||||||
'§e': '\x1b[0m\x1b[93m',
|
|
||||||
'§f': '\x1b[0m\x1b[97m',
|
|
||||||
'§r': '\x1b[0m',
|
|
||||||
'§l': '\x1b[1m',
|
|
||||||
'§o': '\x1b[3m',
|
|
||||||
'§n': '\x1b[4m',
|
|
||||||
'§m': '\x1b[9m',
|
|
||||||
'§k': '\x1b[6m'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a native minecraft text component in string form.
|
|
||||||
* @param {string} json_string - A text component string, such as the chat packet's 'message' property.
|
|
||||||
* @returns {object} Parsed message in { raw, clean, ansi } form.
|
|
||||||
*/
|
|
||||||
function parseText (json) {
|
|
||||||
let raw = parseJson(json, { color: 'reset' })
|
|
||||||
if (raw.startsWith('§r')) {
|
|
||||||
raw = raw.substring(2)
|
|
||||||
}
|
|
||||||
const clean = raw.replace(/§./g, '').replace(/§/g, '')
|
|
||||||
const ansi = raw.replace(/§[a-f0-9rlonmk]/g, (m) => ansimap[m])
|
|
||||||
return { raw, clean, ansi }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a native minecraft text component in JSON form.
|
|
||||||
* @param {object} json - The json message.
|
|
||||||
* @param {object} parent - The parent json.
|
|
||||||
* @returns {string} The parsed raw string.
|
|
||||||
*/
|
|
||||||
function parseJson (json, parent) {
|
|
||||||
if (typeof json === 'string') {
|
|
||||||
json = { text: json }
|
|
||||||
} else if (Array.isArray(json)) {
|
|
||||||
const root = json.shift()
|
|
||||||
root.extra = json
|
|
||||||
json = root
|
|
||||||
}
|
|
||||||
|
|
||||||
json.color ??= parent.color
|
|
||||||
json.bold ??= parent.bold
|
|
||||||
json.italic ??= parent.italic
|
|
||||||
json.underlined ??= parent.underlined
|
|
||||||
json.strikethrough ??= parent.strikethrough
|
|
||||||
json.obfuscated ??= parent.obfuscated
|
|
||||||
|
|
||||||
let raw = ''
|
|
||||||
// if (json.color.startsWith('#'))
|
|
||||||
// raw += '§' + color
|
|
||||||
// else
|
|
||||||
raw += colormap[json.color] || ''
|
|
||||||
if (json.bold) raw += '§l'
|
|
||||||
if (json.italic) raw += '§o'
|
|
||||||
if (json.underlined) raw += '§n'
|
|
||||||
if (json.strikethrough) raw += '§m'
|
|
||||||
if (json.obfuscated) raw += '§k'
|
|
||||||
if (json.text) {
|
|
||||||
raw += json.text
|
|
||||||
}
|
|
||||||
if (json.translate) { // I checked with the native minecraft code. This is how Minecraft does the matching and group indexing. -hhhzzzsss
|
|
||||||
let format = language[json.translate]
|
|
||||||
if (typeof format !== 'string') format = json.fallback
|
|
||||||
if (typeof format !== 'string') format = json.translate
|
|
||||||
|
|
||||||
const _with = json.with ?? []
|
|
||||||
let i = 0
|
|
||||||
raw += format.replace(/%(?:(\d+)\$)?(s|%)/g, (g0, g1) => {
|
|
||||||
if (g0 === '%%') {
|
|
||||||
return '%'
|
|
||||||
} else {
|
|
||||||
const idx = g1 ? parseInt(g1) : i++
|
|
||||||
if (_with[idx]) {
|
|
||||||
return parseJson(_with[idx], json)
|
|
||||||
} else {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (json.extra) {
|
|
||||||
json.extra.forEach((extra) => {
|
|
||||||
raw += parseJson(extra, json)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return raw
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = parseText
|
|
Loading…
Reference in a new issue