Re-add v11 parser for chat messages

This commit is contained in:
7cc5c4f330d47060 2024-10-22 23:11:33 -04:00
parent 0aacd8e7f7
commit 5c3104153c
Signed by: 7cc5c4f330d47060
SSH key fingerprint: SHA256:e+4tcZut1nBpe10PqjaO+Rvie0Q7W4qIvFzcUw+7riA
17 changed files with 1137 additions and 21 deletions

View file

@ -1,13 +1,13 @@
# owobot
# botv12
owobot is a Minecraft bot originally designed for [Kaboom](https://kaboom.pw/) and its clones. It has many of the features that you would expect in a modern Kaboom bot:
botv12 is a Minecraft bot originally designed for [Kaboom](https://kaboom.pw/) and its clones. It has many of the features that you would expect in a modern Kaboom bot:
- commands (obviously)
- a self care system
- a command core, to run commands quickly
- a hashing system, to enable trusted users to securely run certain commands in chat
It supports all Minecraft versions from 1.13 to 1.20.4 that are supported by node-minecraft-protocol.
It supports all Minecraft versions from 1.13 to 1.20.4 that are supported by node-minecraft-protocol. It may work on other versions, however, support will not be provided for them.
If you are not sure if this code is safe to run, you can read through every line of code. You can also see the commit history by clicking on the (n) commits button, to make sure nobody has added any exploits or introduced vulnerabilities to the code.
@ -24,22 +24,5 @@ If you find any exploits, security issues, etc in the code, please send me an is
## Command list
| Name | Usage | Description |
|-|-|-|
| about | [serverlist \| servers \| server] | About the bot. May also show system information or a list of connected servers. |
| cb | \<command\> | Run a command in a command block |
| cloop | add <rate> <command>, remove <index>, list, clear | Manage command loops |
| eval | \<code\> | Run JavaScript code (must run through console)|
| help | [cmd] | Shows command help |
| logoff | | Disconnect and reconnect the bot from a server |
| netmsg | \<message\> | Send a message to all servers the bot is connected to |
| refill | | Refill core |
| restart | | Restart bot, closes when launched directly |
| say | \<message\> | Sends a message to chat |
| settings | get, set <key> <value> | Set your user preferences |
| stop | | Close bot |
| template | | Used in development, does nothing |
| test | | Debug command for the chat parser |
| tpr | | Teleport to a random location |
| validate | | Check the hashing system |
None yet...

47
chatParsers/chat_cmm.js Executable file
View file

@ -0,0 +1,47 @@
import { parse2 as parsePlain } from '../util/chatparse_plain.js'
const priority = 0
const parse = (data, b) => {
if (data.type === 'system' || data.type === 'legacy') {
if (data.json.translate === '%s %s %s' || data.json.translate === '[%s] %s %s') {
let subtype = 'chipmunkmod_'
if (data.json.translate === '%s %s %s') {
subtype += 'name3'
} else if (data.json.translate === '[%s] %s %s') {
subtype += 'chomens'
}
if (data.json.with && data.json.with[1] && data.json.with[2]) {
const username = parsePlain(data.json.with[1])
const uuid = b.findUUID(username)
const nickname = b.findDisplayName(uuid)
const message = parsePlain(data.json.with[2])
return {
parsed: true,
json: data.json,
type: data.type,
subtype,
uuid,
message,
nickname,
username
}
} else {
subtype += '_invalid'
return {
parsed: true,
json: data.json,
type: data.type,
subtype,
uuid: '00000000-0000-0000-0000-000000000000',
message: '',
nickname: '',
username: ''
}
}
}
}
return {
parsed: false
}
}
export { priority, parse }

27
chatParsers/chat_cmm_mcp.js Executable file
View file

@ -0,0 +1,27 @@
import { parse2 as parsePlain } from '../util/chatparse_plain.js'
const priority = 0
const parse = (data, b) => {
if (data.type === 'system' || data.type === 'legacy') {
if (data.json.extra && data.json.extra[4] && data.json.extra[3] && data.json.extra[5] && data.json.extra[4].text === ' » ') { // ChipmunkMod format - m_c_player
const username = parsePlain(data.json.extra[3])
const uuid = b.findUUID(username)
const nickname = b.findDisplayName(uuid)
const message = parsePlain(data.json.extra[5])
return {
parsed: true,
json: data.json,
type: data.type,
subtype: 'chipmunkmod_mcp',
uuid,
message,
nickname,
username
}
}
}
return {
parsed: false
}
}
export { priority, parse }

View file

@ -0,0 +1,31 @@
import { parse2 as parsePlain } from '../util/chatparse_plain.js'
const priority = 1
const parse = (data, b) => {
if (data.type === 'profileless') {
if (data.playerChatType.translation_key === '%s') {
const parsed = parsePlain(data.json)
const split = parsed.split(': ')
const chatName = split.splice(0, 1)[0]
const chatNameSplit = chatName.split(' ')
const nickname = chatNameSplit[chatNameSplit.length - 1]
const username = b.findRealName(chatName)
const uuid = b.findUUID(username)
const message = split.join(': ')
return {
parsed: true,
json: data.json,
type: data.type,
subtype: 'extras_profileless',
uuid,
message,
nickname,
username
}
}
}
return {
parsed: false
}
}
export { priority, parse }

19
chatParsers/chat_player.js Executable file
View file

@ -0,0 +1,19 @@
const priority = 2
const parse = (data, b) => {
if (data.type === 'player' || data.type === 'profileless') {
return {
parsed: true,
json: data.json,
type: data.type,
subtype: 'generic_player',
uuid: data.uuid,
message: data.message,
nickname: data.nickname,
username: data.username
}
}
return {
parsed: false
}
}
export { priority, parse }

40
chatParsers/chat_system.js Executable file
View file

@ -0,0 +1,40 @@
import { parse2 as parsePlain } from '../util/chatparse_plain.js'
const priority = 2
const parse = (data, b) => {
if (data.type === 'system' || data.type === 'legacy') {
let subtype = 'generic_system'
if (data.type === 'legacy' && data.uuid) subtype += '_withuuid'
const parsed = parsePlain(data.json)
const split = parsed.split(': ')
const chatName = split.splice(0, 1)[0]
const chatNameSplit = chatName.split(' ')
let uuid
let username
let nickname
if (data.uuid) {
uuid = data.uuid
username = b.findRealNameFromUUID(uuid)
nickname = b.findDisplayName(uuid)
} else {
nickname = chatNameSplit[chatNameSplit.length - 1]
username = b.findRealName(chatName)
uuid = b.findUUID(username)
}
return {
parsed: true,
json: data.json,
type: data.type,
subtype,
uuid,
message: split.join(': '),
nickname,
username
}
}
return {
parsed: false
}
}
export { priority, parse }

View file

@ -0,0 +1,33 @@
import { parse2 as parsePlain } from '../util/chatparse_plain.js'
const priority = 1
const parse = (data, b) => {
if (data.type === 'legacy') {
let subtype = 'vanilla_legacy'
if (data.type === 'legacy' && data.uuid) subtype += '_withuuid'
if (data.json.translate === 'chat.type.text') { // Servers without Extras chat
let message
let username
if (data.json.with && data.json.with.length >= 2) {
message = parsePlain(data.json.with[1])
username = parsePlain(data.json.with[0])
}
const uuid = b.findUUID(username)
const nickname = b.findDisplayName(uuid)
return {
parsed: true,
json: data.json,
type: data.type,
subtype,
uuid,
message,
nickname,
username
}
}
}
return {
parsed: false
}
}
export { priority, parse }

76
plugins/player.js Executable file
View file

@ -0,0 +1,76 @@
import { parse2 as parse } from '../util/chatparse_plain.js'
import { parse as parseNBT } from '../util/parseNBT.js'
const load = (b) => {
b.players = {}
b._client.on('player_info', (data) => {
const buffer2 = {}
for (const player of data.data) {
let uuid
if (player.uuid) {
uuid = player.uuid
} else if (player.UUID) {
uuid = player.UUID
}
let displayName
if (player.displayName !== undefined) {
displayName = player.displayName
} else {
displayName = '{"text":"[[[[ No display name ]]]]"}'
}
if (player.player && player.player.name !== undefined) {
buffer2[uuid] = { realName: player.player.name, displayName: parse(parseNBT(displayName)) }
} else if (player.name !== undefined) {
buffer2[uuid] = { realName: player.name, displayName: parse(parseNBT(displayName)) }
} else if (player.displayName !== undefined) {
buffer2[uuid] = { displayName: parse(parseNBT(displayName)) }
}
}
for (const uuid in buffer2) {
if (!b.players[uuid]) b.players[uuid] = { displayName: '', realName: '' }
let displayName = ''
let realName = ''
if (buffer2[uuid].displayName) {
displayName = buffer2[uuid].displayName
b.players[uuid].displayName = buffer2[uuid].displayName
}
if (buffer2[uuid].realName) {
realName = buffer2[uuid].realName
b.players[uuid].realName = buffer2[uuid].realName
}
b.emit('playerdata', uuid, displayName, realName)
}
})
b.findUUID = (name) => {
for (const i in b.players) {
if (b.players[i].realName === name) {
return i
}
}
return '00000000-0000-0000-0000-000000000000'
}
b.findRealName = (name) => {
for (const i in b.players) {
if (b.players[i].displayName === name) {
return b.players[i].realName
}
}
return '[[[[ no name ]]]]'
}
b.findRealNameFromUUID = (uuid) => {
if (b.players[uuid]) {
return b.players[uuid].realName
} else {
return '[[[[ no name ]]]]'
}
}
b.findDisplayName = (uuid) => {
if (b.players[uuid]) {
const displayName = b.players[uuid].displayName.split(' ')
return displayName[displayName.length - 1]
} else {
return '[[[[ No display name ]]]]'
}
}
}
export { load }

209
plugins/serverChat.js Executable file
View file

@ -0,0 +1,209 @@
import { default as settings } from '../settings.json' with {type: "json"}
import { parse2 as parsePlain } from '../util/chatparse_plain.js'
import { parse2 as parseConsole } from '../util/chatparse_console.js'
import { parse as parse1204 } from '../util/parseNBT.js'
import { getMessage } from '../util/lang.js'
import { readdirSync } from "node:fs"
const convertChatStyleItem = (item) => {
const output = {}
for (const i in item) {
output[i] = item[i].value
}
return output
}
const convertChatTypeItem = (item) => {
if (item.style) {
return {
translation_key: item.translation_key.value,
parameters: item.parameters.value.value,
style: convertChatStyleItem(item.style.value)
}
} else {
return {
translation_key: item.translation_key.value,
parameters: item.parameters.value.value,
style: {}
}
}
}
// Level 0: highly specific parsers for certain players
// Level 1: server chat format parsers
// Level 2: generic parsers
if(false){
for (const plugin of bpl) {
if (!plugin.endsWith('.js')) {
continue
}
try {
const parser = require(`./chatParsers/${plugin}`)
parsers[parser.priority].push(parser.parse)
} catch (e) { console.log(e) }
}
}
const parsers = [[], [], []]
const bpl = readdirSync('chatParsers')
for (const plugin of bpl) {
if (!plugin.endsWith('.js')) {
continue
}
try {
import(`../chatParsers/${plugin}`).then((pluginItem)=>{
parsers[pluginItem.priority].push(pluginItem.parse)
})
} catch (e) { console.log(e) }
}
const load = (b) => {
b.messageCount = 0
b.chatDisabledUntil = 0
b.interval.antiSpam = setInterval(() => {
b.messageCount = 0
}, 4000)
b.messageTypes = []
b._client.on('registry_data', (data) => {
if (data.codec.value['minecraft:chat_type']) {
b.messageTypes = data.codec.value['minecraft:chat_type']
const nbtItems = data.codec.value['minecraft:chat_type'].value.value.value.value
nbtItems.forEach((item, i) => {
b.messageTypes[i] = convertChatTypeItem(item.element.value.chat.value)
})
}
})
b._client.on('profileless_chat', (data) => {
let messageType = b.messageTypes[data.type]
if (messageType === undefined) messageType = { translation_key: '%s', parameters: ['content'] }
const json = { translate: messageType.translation_key, with: [] }
messageType.parameters.forEach((item, i) => {
if (item === 'content') {
json.with[i] = parse1204(data.message)
} else if (item === 'sender') {
json.with[i] = parse1204(data.name)
} else if (item === 'target') {
json.with[i] = parse1204(data.target)
}
})
for (const i in messageType.style) {
json[i] = messageType.style[i]
}
const message = parsePlain(parse1204(data.message))
const uuid = b.findUUID(parsePlain(parse1204(data.name)))
const nickname = b.findDisplayName(uuid)
const username = parsePlain(parse1204(data.name))
b.emit('chat_unparsed', {
json,
type: 'profileless',
uuid,
message,
nickname,
username,
playerChatType: messageType
})
})
b._client.on('player_chat', (data) => {
let messageType = b.messageTypes[data.type]
if (messageType === undefined) messageType = { translation_key: '%s', parameters: ['content'] }
const json = { translate: messageType.translation_key, with: [] }
messageType.parameters.forEach((item, i) => {
if (item === 'content') {
if (messageType.translation_key === '%s') {
json.with[i] = parse1204(data.unsignedChatContent)
} else {
json.with[i] = data.plainMessage
}
} else if (item === 'sender') {
json.with[i] = parse1204(data.networkName)
} else if (item === 'target') {
json.with[i] = parse1204(data.networkTargetName)
}
})
for (const i in messageType.style) {
json[i] = messageType.style[i]
}
b.emit('chat_unparsed', {
json,
type: 'player',
uuid: data.senderUuid,
message: data.plainMessage,
nickname: parsePlain(parse1204(data.networkName)),
username: b.findRealNameFromUUID(data.senderUuid),
playerChatType: messageType
})
})
b._client.on('system_chat', (data) => {
const json = parse1204(data.content)
b.emit('chat_unparsed', {
json,
type: 'system',
uuid: '00000000-0000-0000-0000-000000000000',
message: '',
nickname: '',
username: '',
playerChatType: {}
})
})
b._client.on('chat', (data) => { // Legacy chat for versions <1.19
const json = parse1204(data.message)
let nickname
let username
let message
let uuid
if (data.uuid) uuid = data.uuid
b.emit('chat_unparsed', {
json,
type: 'legacy',
uuid,
message,
nickname,
username,
playerChatType: {}
})
})
b.on('chat_unparsed', (data) => {
for (const lvl of parsers) {
for (const item of lvl) {
const output = item(data, b)
if (output.parsed) {
b.emit('chat', output)
return
}
}
}
b.emit('chat', {
parsed: true,
json: data.json,
type: data.type,
subtype: 'fallback',
uuid: '00000000-0000-0000-0000-000000000000',
message: '',
nickname: '',
username: ''
})
})
b.on('chat', (data) => {
b.messageCount++
if (Date.now() < b.chatDisabledUntil) return
if (b.messageCount >= 100) {
b.info(getMessage(settings.defaultLang, 'chat.antiSpamTriggered'))
b.chatDisabledUntil = Date.now() + 30000
return
}
const msgConsole = parseConsole(data.json)
const msgPlain = parsePlain(data.json)
if (settings.logJSONmessages) console.log(data.json)
if (msgPlain.endsWith('\n\n\n\n\nThe chat has been cleared')) return
if (msgPlain.startsWith('Command set: ')) return
b.emit('plainchat', msgPlain, data.type, data.subtype)
b.displayChat(data.type, data.subtype, `${msgConsole}\x1b[0m`)
})
}
export { load }

130
util/chatparse_console.js Executable file
View file

@ -0,0 +1,130 @@
import { default as settings } from '../settings.json' with {type: "json"}
import { lang } from './mc_lang.js'
import { default as _consoleColors } from './consolecolors.json' with {type: "json"}
let consoleColors
let consoleColors24
if (_consoleColors[settings.terminalMode]) {
consoleColors = _consoleColors[settings.terminalMode].fourBit
consoleColors24 = _consoleColors[settings.terminalMode].twentyFourBit
} else {
consoleColors = _consoleColors.none.fourBit
consoleColors24 = _consoleColors.none.twentyFourBit
}
const process8bitColorChannel = (value) => {
if (value < 65) return 0
if (value < 115) return 1
if (value < 155) return 2
if (value < 195) return 3
if (value < 235) return 4
return 5
}
const hexColorParser = (color) => {
if (!consoleColors24.enabled || consoleColors24.bit === 4) { // Hex color parsing to the 4 bit mode has not been implemented yet
return ''
}
if (consoleColors24.bit === 24) {
let out = '\x1B[0;'
const redChannel = Number(`0x${color.slice(1, 3)}`)
const greenChannel = Number(`0x${color.slice(3, 5)}`)
const blueChannel = Number(`0x${color.slice(5, 7)}`)
if (!consoleColors24.lightMode && redChannel < 64 && greenChannel < 64 && blueChannel < 64) {
out += '48;2;220;220;220;'
} else if (consoleColors24.lightMode && ((redChannel > 192 && greenChannel > 192 && blueChannel > 192) || greenChannel > 160)) {
out += '48;2;0;0;0;'
}
return out + `38;2;${redChannel};${greenChannel};${blueChannel}m`
} else if (consoleColors24.bit === 8) {
let out = '\x1B[0;'
const redChannel = Number(`0x${color.slice(1, 3)}`)
const greenChannel = Number(`0x${color.slice(3, 5)}`)
const blueChannel = Number(`0x${color.slice(5, 7)}`)
if (!consoleColors24.lightMode && redChannel < 65 && greenChannel < 65 && blueChannel < 65) {
out += '48;5;253;'
} else if (consoleColors24.lightMode && ((redChannel > 194 && greenChannel > 194 && blueChannel > 194) || greenChannel > 154)) {
out += '48;5;16;'
}
const redOut = process8bitColorChannel(redChannel)
const greenOut = process8bitColorChannel(greenChannel)
const blueOut = process8bitColorChannel(blueChannel)
const colorValue = 16 + 36 * redOut + 6 * greenOut + blueOut
return out + `38;5;${colorValue}m`
}
}
const processColor = (col, rcol) => {
let out
if (col === 'reset') {
out = rcol
} else if (col.startsWith('#')) {
out = hexColorParser(col)
} else {
out = consoleColors[col]
}
return out
}
const parse = function (_data, l = 0, resetColor = consoleColors.reset) {
if (l >= 4) {
return ''
}
let data
if (typeof _data === 'string') {
data = { text: _data, color: 'reset' }
} else if (typeof _data === 'number') {
data = { text: _data + '', color: 'reset' }
} else if (_data.constructor === Array) {
data = { extra: _data, color: 'reset' }
} else {
data = _data
}
if (data['']) {
data.text = data['']
if (!data.color) data.color = 'reset'
}
let out = ''
if (data.color) {
out += processColor(data.color, resetColor)
} else {
out += resetColor
}
if (data.text) {
let _text = data.text
if (typeof _text === 'number') {
_text = _text.toString()
}
out += _text.replaceAll('\x1b', '').replaceAll('\x0e', '')
}
if (data.translate) {
let trans = data.translate.replaceAll('%%', '\ud900\ud801').replaceAll('\x1b', '').replaceAll('\x0e', '')
if (lang[trans] !== undefined) {
trans = lang[trans].replace(/%%/g, '\ue123')
}
if (data.with) {
data.with.forEach((item, i) => {
const j2 = parse(item, l + 1, data.color ? processColor(data.color, resetColor) : resetColor)
trans = trans.replace(/%s/, j2.replaceAll('%s', '\ud900\ud804').replaceAll('$s', '\ud900\ud805'))
trans = trans.replaceAll(`%${+i + 1}$s`, j2.replaceAll('%s', '\ud900\ud804').replaceAll('$s', '\ud900\ud805'))
})
}
out += trans.replaceAll('\ud900\ud801', '%').replaceAll('\ud900\ud804', '%s').replaceAll('\ud900\ud805', '$s')
}
if (data.extra) {
for (const item of data.extra) {
const parsed = parse(item, l, data.color ? processColor(data.color, resetColor) : resetColor)
out += parsed
}
}
out += resetColor
return out
}
const parse2 = function (_data, l, resetColor) {
try {
return parse(_data)
} catch (e) {
console.error(e)
return `\x1B[0m\x1B[38;2;255;85;85mAn error occured while parsing a message. See console for more information.\nJSON that caused the error: ${JSON.stringify(_data)}`
}
}
export { parse2 }

96
util/chatparse_mc.js Executable file
View file

@ -0,0 +1,96 @@
import { lang } from './mc_lang.js'
const consoleColors = {
dark_red: '§4',
red: '§c',
dark_green: '§2',
green: '§a',
gold: '§6',
yellow: '§e',
dark_blue: '§1',
blue: '§9',
dark_purple: '§5',
light_purple: '§d',
dark_aqua: '§3',
aqua: '§b',
black: '§0',
gray: '§7',
dark_gray: '§8',
white: '§f',
reset: '§r§f'
}
const processColor = (col, rcol) => {
let out
if (col === 'reset') {
out = rcol
} else {
out = consoleColors[col]
}
return out
}
const parse = function (_data, l = 0, resetColor = consoleColors.reset) {
if (l >= 4) {
return ''
}
let data
if (typeof _data === 'string') {
data = { text: _data, color: 'reset' }
} else if (typeof _data === 'number') {
data = { text: _data + '', color: 'reset' }
} else if (_data.constructor === Array) {
data = { extra: _data, color: 'reset' }
} else {
data = _data
}
if (data['']) {
data.text = data['']
if (!data.color) data.color = 'reset'
}
let out = ''
if (data.color) {
out += processColor(data.color, resetColor)
} else {
out += resetColor
}
if (data.text) {
let _text = data.text
if (typeof _text === 'number') {
_text = _text.toString()
}
out += _text.replaceAll('\x1b', '').replaceAll('\x0e', '')
}
if (data.translate) {
let trans = data.translate.replaceAll('%%', '\ud900\ud801').replaceAll('\x1b', '').replaceAll('\x0e', '')
if (lang[trans] !== undefined) {
trans = lang[trans].replace(/%%/g, '\ue123')
}
if (data.with) {
data.with.forEach((item, i) => {
const j2 = parse(item, l + 1, data.color ? processColor(data.color, resetColor) : resetColor)
trans = trans.replace(/%s/, j2.replaceAll('%s', '\ud900\ud804').replaceAll('$s', '\ud900\ud805'))
trans = trans.replaceAll(`%${+i + 1}$s`, j2.replaceAll('%s', '\ud900\ud804').replaceAll('$s', '\ud900\ud805'))
})
}
out += trans.replaceAll('\ud900\ud801', '%').replaceAll('\ud900\ud804', '%s').replaceAll('\ud900\ud805', '$s')
}
if (data.extra) {
for (const item of data.extra) {
const parsed = parse(item, l, data.color ? processColor(data.color, resetColor) : resetColor)
out += parsed
}
}
out += resetColor
return out
}
const parse2 = function (_data, l, resetColor) {
try {
return parse(_data)
} catch (e) {
console.error(e)
return `\x1B[0m\x1B[38;2;255;85;85mAn error occured while parsing a message. See console for more information.\nJSON that caused the error: ${JSON.stringify(_data)}`
}
}
export { parse2 }

98
util/chatparse_mc_withHex.js Executable file
View file

@ -0,0 +1,98 @@
import { lang } from './mc_lang.js'
const consoleColors = {
dark_red: '§4',
red: '§c',
dark_green: '§2',
green: '§a',
gold: '§6',
yellow: '§e',
dark_blue: '§1',
blue: '§9',
dark_purple: '§5',
light_purple: '§d',
dark_aqua: '§3',
aqua: '§b',
black: '§0',
gray: '§7',
dark_gray: '§8',
white: '§f',
reset: '§r§f'
}
const processColor = (col, rcol) => {
let out
if (col === 'reset') {
out = rcol
} else if (col.startsWith('#')) {
out = `§${col}`
} else {
out = consoleColors[col]
}
return out
}
const parse = function (_data, l = 0, resetColor = consoleColors.reset) {
if (l >= 4) {
return ''
}
let data
if (typeof _data === 'string') {
data = { text: _data, color: 'reset' }
} else if (typeof _data === 'number') {
data = { text: _data + '', color: 'reset' }
} else if (_data.constructor === Array) {
data = { extra: _data, color: 'reset' }
} else {
data = _data
}
if (data['']) {
data.text = data['']
if (!data.color) data.color = 'reset'
}
let out = ''
if (data.color) {
out += processColor(data.color, resetColor)
} else {
out += resetColor
}
if (data.text) {
let _text = data.text
if (typeof _text === 'number') {
_text = _text.toString()
}
out += _text.replaceAll('\x1b', '').replaceAll('\x0e', '')
}
if (data.translate) {
let trans = data.translate.replaceAll('%%', '\ud900\ud801').replaceAll('\x1b', '').replaceAll('\x0e', '')
if (lang[trans] !== undefined) {
trans = lang[trans].replace(/%%/g, '\ue123')
}
if (data.with) {
data.with.forEach((item, i) => {
const j2 = parse(item, l + 1, data.color ? processColor(data.color, resetColor) : resetColor)
trans = trans.replace(/%s/, j2.replaceAll('%s', '\ud900\ud804').replaceAll('$s', '\ud900\ud805'))
trans = trans.replaceAll(`%${+i + 1}$s`, j2.replaceAll('%s', '\ud900\ud804').replaceAll('$s', '\ud900\ud805'))
})
}
out += trans.replaceAll('\ud900\ud801', '%').replaceAll('\ud900\ud804', '%s').replaceAll('\ud900\ud805', '$s')
}
if (data.extra) {
for (const item of data.extra) {
const parsed = parse(item, l, data.color ? processColor(data.color, resetColor) : resetColor)
out += parsed
}
}
out += resetColor
return out
}
const parse2 = function (_data, l, resetColor) {
try {
return parse(_data)
} catch (e) {
console.error(e)
return `\x1B[0m\x1B[38;2;255;85;85mAn error occured while parsing a message. See console for more information.\nJSON that caused the error: ${JSON.stringify(_data)}`
}
}
export { parse2 }

57
util/chatparse_plain.js Executable file
View file

@ -0,0 +1,57 @@
import { lang } from './mc_lang.js'
const parse = function (_data, l = 0) {
if (l >= 4) {
return ''
}
let data
if (typeof _data === 'string') {
data = { text: _data }
} else if (typeof _data === 'number') {
data = { text: _data + '' }
} else if (_data.constructor === Array) {
data = { extra: _data }
} else {
data = _data
}
let out = ''
if (data['']) {
data.text = data['']
}
if (data.text) {
let _text = data.text
if (typeof _text === 'number') {
_text = _text.toString()
}
out += _text
}
if (data.translate) {
let trans = data.translate.replace(/%%/g, '\ue123')
if (lang[trans] !== undefined) {
trans = lang[trans].replace(/%%/g, '\ue123')
}
if (data.with) {
data.with.forEach((item, i) => {
const j2 = parse(item, l + 1)
trans = trans.replace(/%s/, j2.replaceAll('%s', '\ud900\ud804').replaceAll('$s', '\ud900\ud805'))
trans = trans.replaceAll(`%${+i + 1}$s`, j2.replaceAll('%s', '\ud900\ud804').replaceAll('$s', '\ud900\ud805'))
})
}
out += trans.replaceAll('\ud900\ud801', '%').replaceAll('\ud900\ud804', '%s').replaceAll('\ud900\ud805', '$s')
}
if (data.extra) {
for (const item of data.extra) {
const parsed = parse(item, l)
out += parsed
}
}
return out
}
const parse2 = function (_data, l) {
try {
return parse(_data)
} catch (e) {
console.error(e)
return `An error occured while parsing a message. See console for more information. JSON that caused the error: ${JSON.stringify(_data)}`
}
}
export { parse2 }

182
util/consolecolors.json Executable file
View file

@ -0,0 +1,182 @@
{
"blackTerminal_24bit":{
"fourBit":{
"dark_red": "\u001B[0;38;2;170;0;0m",
"red": "\u001B[0;38;2;255;85;85m",
"dark_green": "\u001B[0;38;2;0;170;0m",
"green": "\u001B[0;38;2;85;255;85m",
"gold": "\u001B[0;38;2;255;170;0m",
"yellow": "\u001B[0;38;2;255;255;85m",
"dark_blue": "\u001B[0;38;2;0;0;170m",
"blue": "\u001B[0;38;2;85;85;255m",
"dark_purple": "\u001B[0;38;2;170;0;170m",
"light_purple": "\u001B[0;38;2;255;85;255m",
"dark_aqua": "\u001B[0;38;2;0;170;170m",
"aqua": "\u001B[0;38;2;85;255;255m",
"black": "\u001B[0;48;2;220;220;220;38;2;0;0;0m",
"gray": "\u001B[0;38;2;170;170;170m",
"dark_gray": "\u001B[0;38;2;85;85;85m",
"white": "\u001B[0;38;2;255;255;255m",
"reset": "\u001B[0;38;2;255;255;255m"
},
"twentyFourBit":{
"enabled": true,
"bit": 24,
"lightMode": false
}
},
"whiteTerminal_24bit":{
"fourBit":{
"dark_red": "\u001B[0;38;2;170;0;0m",
"red": "\u001B[0;38;2;255;85;85m",
"dark_green": "\u001B[0;38;2;0;170;0m",
"green": "\u001B[0;48;2;20;20;20;38;2;85;255;85m",
"gold": "\u001B[0;38;2;255;170;0m",
"yellow": "\u001B[0;48;2;20;20;20;38;2;255;255;85m",
"dark_blue": "\u001B[0;38;2;0;0;170m",
"blue": "\u001B[0;38;2;85;85;255m",
"dark_purple": "\u001B[0;38;2;170;0;170m",
"light_purple": "\u001B[0;38;2;255;85;255m",
"dark_aqua": "\u001B[0;38;2;0;170;170m",
"aqua": "\u001B[0;48;2;20;20;20;38;2;85;255;255m",
"black": "\u001B[0;38;2;0;0;0m",
"gray": "\u001B[0;38;2;170;170;170m",
"dark_gray": "\u001B[0;38;2;85;85;85m",
"white": "\u001B[0;48;2;20;20;20;38;2;255;255;255m",
"reset": "\u001B[0;48;2;20;20;20;38;2;255;255;255m"
},
"twentyFourBit":{
"enabled": true,
"bit": 24,
"lightMode": true
}
},
"blackTerminal_8bit":{
"fourBit":{
"dark_red": "\u001B[0;38;5;124m",
"red": "\u001B[0;38;5;203m",
"dark_green": "\u001B[0;38;5;34m",
"green": "\u001B[0;38;5;83m",
"gold": "\u001B[0;38;5;214m",
"yellow": "\u001B[0;38;5;227m",
"dark_blue": "\u001B[0;38;5;19m",
"blue": "\u001B[0;38;5;63m",
"dark_purple": "\u001B[0;38;5;127m",
"light_purple": "\u001B[0;38;5;207m",
"dark_aqua": "\u001B[0;38;5;37m",
"aqua": "\u001B[0;38;5;87m",
"black": "\u001B[0;48;5;253;38;5;16m",
"gray": "\u001B[0;38;5;145m",
"dark_gray": "\u001B[0;38;5;59m",
"white": "\u001B[0;38;5;231m",
"reset": "\u001B[0;38;5;231m"
},
"twentyFourBit":{
"enabled": true,
"bit": 8,
"lightMode": false
}
},
"whiteTerminal_8bit":{
"fourBit":{
"dark_red": "\u001B[0;38;5;124m",
"red": "\u001B[0;38;5;203m",
"dark_green": "\u001B[0;38;5;34m",
"green": "\u001B[0;48;5;16;38;5;83m",
"gold": "\u001B[0;38;5;214m",
"yellow": "\u001B[0;48;5;16;38;5;227m",
"dark_blue": "\u001B[0;38;5;19m",
"blue": "\u001B[0;38;5;63m",
"dark_purple": "\u001B[0;38;5;127m",
"light_purple": "\u001B[0;38;5;207m",
"dark_aqua": "\u001B[0;38;5;37m",
"aqua": "\u001B[0;48;5;16;38;5;87m",
"black": "\u001B[0;38;5;16m",
"gray": "\u001B[0;38;5;145m",
"dark_gray": "\u001B[0;38;5;59m",
"white": "\u001B[0;48;5;16;38;5;231m",
"reset": "\u001B[0;48;5;16;38;5;231m"
},
"twentyFourBit":{
"enabled": true,
"bit": 8,
"lightMode": true
}
},
"blackTerminal_4bit":{
"fourBit":{
"dark_red": "\u001B[0;31m",
"red": "\u001B[0;1;31m",
"dark_green": "\u001B[0;32m",
"green": "\u001B[0;1;32m",
"gold": "\u001B[0;33m",
"yellow": "\u001B[0;1;33m",
"dark_blue": "\u001B[0;34m",
"blue": "\u001B[0;1;34m",
"dark_purple": "\u001B[0;35m",
"light_purple": "\u001B[0;1;35m",
"dark_aqua": "\u001B[0;36m",
"aqua": "\u001B[0;1;36m",
"black": "\u001B[0;1;47;30m",
"gray": "\u001B[0;37m",
"dark_gray": "\u001B[0;1;30m",
"white": "\u001B[0;1;37m",
"reset": "\u001B[0;1;37m"
},
"twentyFourBit":{
"enabled": true,
"bit": 4,
"lightMode": false
}
},
"whiteTerminal_4bit":{
"fourBit":{
"dark_red": "\u001B[0;31m",
"red": "\u001B[0;1;31m",
"dark_green": "\u001B[0;32m",
"green": "\u001B[0;40;1;32m",
"gold": "\u001B[0;33m",
"yellow": "\u001B[0;40;1;33m",
"dark_blue": "\u001B[0;34m",
"blue": "\u001B[0;1;34m",
"dark_purple": "\u001B[0;35m",
"light_purple": "\u001B[0;1;35m",
"dark_aqua": "\u001B[0;36m",
"aqua": "\u001B[0;40;1;36m",
"black": "\u001B[0;30m",
"gray": "\u001B[0;37m",
"dark_gray": "\u001B[0;1;30m",
"white": "\u001B[0;40;1;37m",
"reset": "\u001B[0;40;1;37m"
},
"twentyFourBit":{
"enabled": true,
"bit": 4,
"lightMode": true
}
},
"none":{
"fourBit":{
"dark_red": "",
"red": "",
"dark_green": "",
"green": "",
"gold": "",
"yellow": "",
"dark_blue": "",
"blue": "",
"dark_purple": "",
"light_purple": "",
"dark_aqua": "",
"aqua": "",
"black": "",
"gray": "",
"dark_gray": "",
"white": "",
"reset": ""
},
"twentyFourBit":{
"enabled": false
}
}
}

70
util/lang.js Executable file
View file

@ -0,0 +1,70 @@
//const fs = require('fs')
const languages = {}
import { default as settings } from '../settings.json' with {type: "json"}
const fallbackLocale = settings.fallbackLocale ? settings.fallbackLocale : 'en-US'
const loadplug = (botno) => {
const bpl = fs.readdirSync('lang')
for (const plugin of bpl) {
if (!plugin.endsWith('.json')) {
continue
}
try {
languages[plugin.split('.')[0]] = require(`../lang/${plugin}`)
} catch (e) { console.log(e) }
}
}
//loadplug()
const getMessage = function (l, msg, with2) {
let message = msg.replace(/%%/g, '\ue123')
if (languages[l] && languages[l][message] !== undefined) {
message = languages[l][message].replace(/%%/g, '\ue123')
} else if (languages[fallbackLocale] && languages['en-US'][message] !== undefined) {
message = languages[fallbackLocale][message].replace(/%%/g, '\ue123')
}fs
if (with2) {
with2.forEach((withItem, i) => {
message = message.replace(/%s/, withItem.replace(/%s/g, '\ue124').replace(/\$s/g, '\ue125'))
message = message.replaceAll(`%${+i + 1}$s`, withItem.replace(/%s/g, '\ue124').replace(/\$s/g, '\ue125'))
})
}
return message.replace(/\ue123/g, '%').replace(/\ue124/g, '%s').replace(/\ue125/g, '$s')
}
const languages_keys = Object.keys(languages)
const formatTime = function (time, language) {
let finalString = ''
const seconds = Math.floor(time / 1000) % 60
const minutes = Math.floor(time / 60000) % 60
const hours = Math.floor(time / 3600000) % 24
const days = Math.floor(time / 86400000) % 7
const weeks = Math.floor(time / 604800000)
if (weeks !== 0) {
finalString += weeks
finalString += `${weeks === 1 ? getMessage(language, 'time.week') : getMessage(language, 'time.weekPlural')}`
}
if (days !== 0) {
finalString += days
finalString += `${days === 1 ? getMessage(language, 'time.day') : getMessage(language, 'time.dayPlural')}`
}
if (hours !== 0) {
finalString += hours
finalString += `${hours === 1 ? getMessage(language, 'time.hour') : getMessage(language, 'time.hourPlural')}`
}
if (minutes !== 0) {
finalString += minutes
finalString += `${minutes === 1 ? getMessage(language, 'time.minute') : getMessage(language, 'time.minutePlural')}`
}
if (seconds !== 0) {
finalString += seconds
finalString += `${seconds === 1 ? getMessage(language, 'time.second') : getMessage(language, 'time.secondPlural')}`
}
return finalString
}
export {
languages_keys as languages,
formatTime,
getMessage
}

7
util/mc_lang.js Executable file
View file

@ -0,0 +1,7 @@
import MinecraftData from 'minecraft-data'
const _lang = MinecraftData('1.20.6').language
const lang = Object.create(null) // Without constructor function
for (const i in _lang) {
lang[i] = _lang[i]
}
export { lang }

11
util/parseNBT.js Executable file
View file

@ -0,0 +1,11 @@
import { processNbtMessage } from 'prismarine-chat'
const parse = function (data) {
if (typeof data.type === 'string') {
return JSON.parse(processNbtMessage(data))
} else if (typeof data === 'string') {
return JSON.parse(data)
} else {
return data
}
}
export { parse }