diff --git a/index.js b/index.js index 4057522..8fde616 100644 --- a/index.js +++ b/index.js @@ -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,12 +31,37 @@ async function main () { const console = new Console({ readline: rl }) await console.createLogFile(logdir) - const matrixClients = {} - for (const key in config.matrixClients) { - const client = matrix.createClient(config.matrixClients[key]) - matrixClients[key] = client + 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 - client.startClient() + 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 = [] @@ -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) diff --git a/package-lock.json b/package-lock.json index c950161..a2ebe18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 4a89cb1..2d176ed 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/plugins/anti_stuff.js b/plugins/anti_stuff.js deleted file mode 100644 index b1d97e5..0000000 --- a/plugins/anti_stuff.js +++ /dev/null @@ -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 diff --git a/plugins/chat.js b/plugins/chat.js index 7b2f071..f0f1baf 100644 --- a/plugins/chat.js +++ b/plugins/chat.js @@ -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) }) diff --git a/plugins/irc.js b/plugins/irc.js new file mode 100644 index 0000000..f8951b0 --- /dev/null +++ b/plugins/irc.js @@ -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 diff --git a/plugins/self_care.js b/plugins/self_care.js new file mode 100644 index 0000000..c194c2a --- /dev/null +++ b/plugins/self_care.js @@ -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 diff --git a/util/chat/stringify/irc.js b/util/chat/stringify/irc.js new file mode 100644 index 0000000..3b85f56 --- /dev/null +++ b/util/chat/stringify/irc.js @@ -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