Add IRC & better selfcare

This commit is contained in:
Chipmunk 2024-09-04 11:24:57 -04:00
parent 161c9a7c5a
commit 2333ba68fe
8 changed files with 467 additions and 58 deletions

View file

@ -3,6 +3,7 @@ const path = require('path')
const readline = require('readline')
const json5 = require('json5')
const matrix = require('matrix-js-sdk')
const irc = require('matrix-org-irc')
const createBot = require('./bot.js')
const fileExists = require('./util/file_exists')
@ -30,13 +31,38 @@ async function main () {
const console = new Console({ readline: rl })
await console.createLogFile(logdir)
for (const options of config.bots) {
// pass through the options to find irc channels
if (!options.irc || !config.ircClients || !config.ircClients[options.irc.client]) return
config.ircClients[options.irc.client].channels ??= []
config.ircClients[options.irc.client].channels.push(options.irc.channel)
}
const matrixClients = {}
if (config.matrixClients) {
for (const key in config.matrixClients) {
const client = matrix.createClient(config.matrixClients[key])
matrixClients[key] = client
client.startClient()
}
}
const ircClients = {}
if (config.ircClients) {
for (const key in config.ircClients) {
const options = config.ircClients[key]
options.autoRejoin ??= true
const client = new irc.Client(options.server, options.nick, options)
ircClients[key] = client
client.on('motd', () => {
if (options.oper) client.send('oper', options.oper[0], options.oper[1])
})
}
}
const bots = []
for (const options of config.bots) {
@ -47,6 +73,7 @@ async function main () {
...options
}
if (mergedOptions.matrix && typeof mergedOptions.matrix.client !== 'object') mergedOptions.matrix.client = matrixClients[mergedOptions.matrix.client]
if (mergedOptions.irc && typeof mergedOptions.irc.client !== 'object') mergedOptions.irc.client = ircClients[mergedOptions.irc.client]
const bot = createBot(mergedOptions)
bots.push(bot)

182
package-lock.json generated
View file

@ -15,6 +15,7 @@
"json5": "^2.2.3",
"kahoot.js-api": "^2.4.0",
"matrix-js-sdk": "^31.5.0",
"matrix-org-irc": "^3.0.0",
"minecraft-protocol": "^1.26.5",
"moment": "^2.29.1",
"prismarine-nbt": "^2.2.0",
@ -666,6 +667,12 @@
"node": ">=4"
}
},
"node_modules/chardet": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-2.0.0.tgz",
"integrity": "sha512-xVgPpulCooDjY6zH4m9YW3jbkaBe3FKIAvF5sj5t7aBNsVl2ljIE+xwJ4iNgiDZHFQvNIpjdKdVOQvvk5ZfxbQ==",
"license": "MIT"
},
"node_modules/clone-response": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
@ -1823,6 +1830,18 @@
"node": ">=10.19.0"
}
},
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@ -2429,6 +2448,34 @@
"uuid": "dist/bin/uuid"
}
},
"node_modules/matrix-org-irc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/matrix-org-irc/-/matrix-org-irc-3.0.0.tgz",
"integrity": "sha512-uSuyt7Uk9+AsrMnLoxFxqaIo+s6O0mjDT2DvUa1yS+i0oXTKagLdKbRvWrz1Zhn955oy3XdSzsk1D/uYH07+ww==",
"license": "GPL-3.0",
"dependencies": {
"chardet": "^2.0.0",
"iconv-lite": "^0.6.3",
"typed-emitter": "^2.1.0",
"utf-8-validate": "^6.0.3"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/matrix-org-irc/node_modules/utf-8-validate": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.4.tgz",
"integrity": "sha512-xu9GQDeFp+eZ6LnCywXN/zBancWvOpUMzgjLPSjy4BRHSmTelvn2E0DG0o1sTiw5hkCKBHo8rwSKncfRfv2EEQ==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"node_modules/matrix-widget-api": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/matrix-widget-api/-/matrix-widget-api-1.6.0.tgz",
@ -2552,6 +2599,17 @@
}
}
},
"node_modules/node-gyp-build": {
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz",
"integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==",
"license": "MIT",
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/node-rsa": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-0.4.2.tgz",
@ -3242,6 +3300,16 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/rxjs": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@ -3261,6 +3329,12 @@
}
]
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
"license": "MIT"
},
"node_modules/sdp-transform": {
"version": "2.14.2",
"resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.14.2.tgz",
@ -3642,6 +3716,13 @@
"json5": "lib/cli.js"
}
},
"node_modules/tslib": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
"license": "0BSD",
"optional": true
},
"node_modules/tweetnacl": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
@ -3666,6 +3747,15 @@
"node": ">=8"
}
},
"node_modules/typed-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz",
"integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==",
"license": "MIT",
"optionalDependencies": {
"rxjs": "*"
}
},
"node_modules/unbox-primitive": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
@ -3700,6 +3790,21 @@
"punycode": "^2.1.0"
}
},
"node_modules/utf-8-validate": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@ -4340,6 +4445,11 @@
"supports-color": "^5.3.0"
}
},
"chardet": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-2.0.0.tgz",
"integrity": "sha512-xVgPpulCooDjY6zH4m9YW3jbkaBe3FKIAvF5sj5t7aBNsVl2ljIE+xwJ4iNgiDZHFQvNIpjdKdVOQvvk5ZfxbQ=="
},
"clone-response": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
@ -5158,6 +5268,14 @@
"resolve-alpn": "^1.0.0"
}
},
"iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"requires": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
}
},
"ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@ -5603,6 +5721,27 @@
}
}
},
"matrix-org-irc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/matrix-org-irc/-/matrix-org-irc-3.0.0.tgz",
"integrity": "sha512-uSuyt7Uk9+AsrMnLoxFxqaIo+s6O0mjDT2DvUa1yS+i0oXTKagLdKbRvWrz1Zhn955oy3XdSzsk1D/uYH07+ww==",
"requires": {
"chardet": "^2.0.0",
"iconv-lite": "^0.6.3",
"typed-emitter": "^2.1.0",
"utf-8-validate": "^6.0.3"
},
"dependencies": {
"utf-8-validate": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.4.tgz",
"integrity": "sha512-xu9GQDeFp+eZ6LnCywXN/zBancWvOpUMzgjLPSjy4BRHSmTelvn2E0DG0o1sTiw5hkCKBHo8rwSKncfRfv2EEQ==",
"requires": {
"node-gyp-build": "^4.3.0"
}
}
}
},
"matrix-widget-api": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/matrix-widget-api/-/matrix-widget-api-1.6.0.tgz",
@ -5702,6 +5841,11 @@
"whatwg-url": "^5.0.0"
}
},
"node-gyp-build": {
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.1.tgz",
"integrity": "sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw=="
},
"node-rsa": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/node-rsa/-/node-rsa-0.4.2.tgz",
@ -6207,11 +6351,25 @@
"glob": "^7.1.3"
}
},
"rxjs": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"optional": true,
"requires": {
"tslib": "^2.1.0"
}
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sdp-transform": {
"version": "2.14.2",
"resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.14.2.tgz",
@ -6485,6 +6643,12 @@
}
}
},
"tslib": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==",
"optional": true
},
"tweetnacl": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
@ -6503,6 +6667,14 @@
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
"integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA=="
},
"typed-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz",
"integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==",
"requires": {
"rxjs": "*"
}
},
"unbox-primitive": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz",
@ -6531,6 +6703,16 @@
"punycode": "^2.1.0"
}
},
"utf-8-validate": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
"optional": true,
"peer": true,
"requires": {
"node-gyp-build": "^4.3.0"
}
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View file

@ -10,6 +10,7 @@
"json5": "^2.2.3",
"kahoot.js-api": "^2.4.0",
"matrix-js-sdk": "^31.5.0",
"matrix-org-irc": "^3.0.0",
"minecraft-protocol": "^1.26.5",
"moment": "^2.29.1",
"prismarine-nbt": "^2.2.0",

View file

@ -1,53 +0,0 @@
function inject (bot) {
bot.on('login', () => {
bot.chat.queue.push('/commandspy:commandspy on')
// bot.chat.queue.push('/essentials:vanish enable')
bot.chat.queue.push('/essentials:god enable')
})
bot.on('cspy', (player, command) => {
if (command.startsWith('/')) { command = command.slice(1) }
const args = command.split(' ')
command = args.shift()
switch (command) {
case 'icu':
case 'icontrolu:icu':
if (args[0] === 'control' && (bot.username.startsWith(args[1]) || args[1] === bot.uuid)) { bot.core.run(`essentials:sudo ${player.uuid} icontrolu:icu stop`) }
}
})
bot.on('chat', (message) => {
if (/\u00a76Vanish for .*\u00a76: disabled/.test(message.raw)) {
bot.core.run(`sudo ${bot.uuid} essentials:vanish enable`)
} else if (/§6God mode§c disabled§6./.test(message.raw)) {
bot.core.run(`sudo ${bot.uuid} essentials:god enable`)
} else if (/§rSuccessfully disabled CommandSpy/.test(message.raw)) {
bot.core.run(`sudo ${bot.uuid} commandspy:commandspy on`)
} else if (/§6Your nickname is now .*§6./.test(message.raw)) {
bot.core.run(`essentials:nick ${bot.uuid} off`)
}
})
bot.on('player_added', (player) => {
if (player.uuid === bot.uuid && player.username !== bot.username) bot.core.run(`essentials:sudo ${bot.uuid} username ${bot.username}`)
})
bot.on('packet.game_state_change', (packet) => {
switch (packet.reason) {
case 3:
if (packet.gameMode !== 1) { bot.chat.queue.push('/minecraft:gamemode creative @s[type=player]') }
break
case 4:
bot._client.write('client_command', { payload: 0 })
}
})
bot.on('packet.update_health', (packet) => {
if (packet.health <= 0) { bot._client.write('client_command', { payload: 0 }) }
})
bot.on('packet.declare_commands', () => bot.chat.queue.push(bot.brand === 'kaboom' ? '/op @s[type=player]' : '/trigger opme')) // assumes that the vanilla server has an 'opme' trigger
}
module.exports = inject

View file

@ -4,6 +4,7 @@ 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 ircStringify = require('../util/chat/stringify/irc')
const vanillaParser = require('../util/chat/message_parser/vanilla')
const kaboomParser = require('../util/chat/message_parser/kaboom')
@ -57,6 +58,8 @@ function inject (bot) {
setInterval(() => {
if (!bot.loggedIn) return
bot.emit('chat_queue_tick')
const message = bot.chat.queue.shift()
if (message != null) {
if (message[0] === '/') bot.chat.command(message.substring(1))
@ -118,6 +121,7 @@ function inject (bot) {
bot.emit('chat_color_code', colorCodeStringify(message, stringifyOptions), message)
bot.emit('chat_ansi', ansiStringify(message, stringifyOptions), message)
bot.emit('chat_html', htmlStringify(message, stringifyOptions), message)
bot.emit('chat_irc', ircStringify(message, stringifyOptions), message)
if (bot.chat.shouldLog(message)) bot.console.log(message)
})

70
plugins/irc.js Normal file
View file

@ -0,0 +1,70 @@
const irc = require('matrix-org-irc')
const ircStringify = require('../util/chat/stringify/irc')
const CommandSource = require('../util/command/command_source')
function inject (bot, options) {
if (!options.irc?.enabled) return
bot.irc = {
client: options.irc.client,
channel: options.irc.channel,
commandPrefix: options.irc.commandPrefix,
receivedMotd: false
}
if (!bot.irc.client) {
bot.irc.client = new irc.Client(options.irc.server, options.irc.nick, options.irc)
}
const ircPrefix = {
text: 'ChipmunkBot IRC',
hoverEvent: {
action: 'show_text',
value: [
'Server: ',
String(bot.irc.client.server),
'\nChannel: ',
String(bot.irc.channel)
]
}
}
bot.on('chat_irc', (string, msg) => {
if (!bot.irc.receivedMotd || !bot.chat.shouldLog(msg)) return
bot.irc.client.say(bot.irc.channel, string)
})
bot.irc.client.on('motd', () => {
bot.irc.receivedMotd = true
bot.irc.client.once('end', () => { bot.irc.receivedMotd = false })
})
bot.irc.client.on('message' + bot.irc.channel, (sender, msg, command) => {
const senderText = {
text: sender,
insertion: command.prefix,
hoverEvent: {
action: 'show_text',
value: command.prefix
}
}
if (msg.startsWith(bot.irc.commandPrefix)) {
function sendFeedback (text, broadcast) {
const string = ircStringify(text, { lang: bot.registry?.language })
bot.irc.client.say(bot.irc.channel, sender + ': ' + string)
}
const source = new CommandSource({ bot, permissionLevel: 0, sendFeedback, displayName: senderText })
bot.commands.execute(msg.substring(bot.irc.commandPrefix.length), source)
return
}
bot.fancyMsg(ircPrefix, senderText, msg)
})
bot.irc.client.on('error', error => bot.console.error('IRC Error:\n' + error.stack || error))
}
module.exports = inject

56
plugins/self_care.js Normal file
View file

@ -0,0 +1,56 @@
const util = require('util')
const COMMANDSPY_ENABLED_MESSAGE = { extra: [ 'Successfully ', 'enabled', ' CommandSpy' ], text: '' }
const COMMANDSPY_DISABLED_MESSAGE = { extra: [ 'Successfully ', 'disabled', ' CommandSpy' ], text: '' }
function inject (bot) {
let permissionLevel, gamemode, commandSpyEnabled, vanished, godEnabled
bot.on('packet.login', packet => {console.log(packet)
permissionLevel = 0
gamemode = packet.gameMode
commandSpyEnabled = false
vanished = false
godEnabled = false
})
bot.on('chat_queue_tick', () => {
if (permissionLevel === 0) bot.chat.queue.unshift('/op @s[type=player]')
else if (gamemode !== 1) bot.chat.queue.unshift('/gamemode creative')
else if (!commandSpyEnabled) bot.chat.queue.unshift('/c on')
})
bot.on('packet.entity_status', packet => {
if (packet.entityStatus < 24 || packet.entityStatus > 28) return
permissionLevel = packet.entityStatus - 24
})
bot.on('system_chat', (message) => {
if (util.isDeepStrictEqual(message, COMMANDSPY_ENABLED_MESSAGE)) commandSpyEnabled = true
else if (util.isDeepStrictEqual(message, COMMANDSPY_DISABLED_MESSAGE)) commandSpyEnabled = false
})
bot.on('player_added', (player) => {
if (player.uuid === bot.uuid && player.username !== bot.username) bot.core.run(`essentials:sudo ${bot.uuid} username ${bot.username}`)
})
bot.on('packet.game_state_change', (packet) => {
switch (packet.reason) {
case 3:
gamemode = packet.gameMode
break
case 4:
bot._client.write('client_command', { payload: 0 })
}
})
bot.on('packet.respawn', packet => {
gamemode = packet.gameMode
})
bot.on('packet.update_health', (packet) => {
if (packet.health <= 0) bot._client.write('client_command', { payload: 0 })
})
}
module.exports = inject

122
util/chat/stringify/irc.js Normal file
View file

@ -0,0 +1,122 @@
const { colors, formatting, reset } = require('../formatting.json')
const { normalize, intToRgb, getChangedFormatting } = require('../utility')
const colormap = {
white: '\x0F\x0300',
black: '\x0F\x0301',
dark_blue: '\x0F\x0302',
dark_green: '\x0F\x0303',
red: '\x0F\x0304',
dark_red: '\x0F\x0305',
dark_purple: '\x0F\x0306',
gold: '\x0F\x0307',
yellow: '\x0F\x0308',
green: '\x0F\x0309',
dark_aqua: '\x0F\x0310',
aqua: '\x0F\x0311',
blue: '\x0F\x0312',
light_purple: '\x0F\x0313',
dark_gray: '\x0F\x0314',
gray: '\x0F\x0315',
}
const formatmap = {
bold: '\x02',
underlined: '\x1F',
italic: '\x1D',
strikethrough: '\x1E',
obfuscated: ''
}
const resetCode = '\x0F'
const colorcodemap = Object.fromEntries(colors.map(color => [color.code, colormap[color.name]]))
const formatcodemap = Object.fromEntries(formatting.map(format => [format.code, formatmap[format.name]]))
function ircStringify (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 ircStringify(_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 += ircStringify(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 hex = format.color.substring(1).padStart(6, '0')
string += '\x0F\x04' + hex
}
else if (colormap[format.color]) string += colormap[format.color]
else if (parent) string += resetCode
}
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 === '\xa7r') string += resetCode
continue
}
else string += c
}
return string
}
module.exports = ircStringify