const fs = require('fs') const path = require('path') const { CommandDispatcher, literal, argument, greedyString, CommandSyntaxException } = require('brigadier-commands') const CommandSource = require('../util/command/command_source') const TextMessage = require('../util/command/text_message') const colorCodeStringify = require('../util/chat/stringify/color_code') let commands function loadCommands () { commands = [] for (const filename of fs.readdirSync(path.resolve(__dirname, '..', 'commands'))) { const filepath = path.resolve(__dirname, '..', 'commands', filename) if (!filepath.endsWith('.js') || !fs.statSync(filepath).isFile()) return try { delete require.cache[require.resolve(filepath)] commands.push(require(filepath)) } catch (error) { console.error('Error loading command', filepath, ':', error) } } } loadCommands() const patchedExecuteSource = CommandDispatcher.prototype.execute .toString() .replace('context.command(', 'await context.command(') const patchedExecute = eval(`const { ParseResults, StringReader } = require('brigadier-commands'); ({ async ${patchedExecuteSource} })`).execute function inject (bot, options) { bot.commands = { dispatcher: null, execute, reload, prefixes: options.prefixes ?? ['default.'] } let cleanups = [] bot.on('message', ({ sender, plain }) => { const prefix = bot.commands.prefixes.find(prefix => plain.startsWith(prefix)) if (!prefix) return function sendFeedback (message) { bot.tellraw(message, '@a') } const displayName = { insertion: sender.username, clickEvent: { action: 'suggest_command', value: `/tell ${sender.username} ` }, hoverEvent: { action: 'show_entity', contents: { type: 'minecraft:player', id: sender.uuid, name: sender.username } }, text: sender.username } bot.commands.execute(plain.substring(prefix.length), new CommandSource({ bot, player: sender, sendFeedback, displayName })) }) async function execute (command, source) { try { return await patchedExecute.call(bot.commands.dispatcher, command, source) } catch (error) { if (error instanceof CommandSyntaxException) { const text = (error._message instanceof TextMessage) ? error._message.text : error._message.getString() source.sendError(text) const context = getContext(error) if (context) source.sendError(context) return 1 } source.sendError({ translate: 'command.failed', hoverEvent: { action: 'show_text', contents: error.stack } }) return 1 } } function getContext (error) { const _cursor = error.cursor const input = error.input if (input == null || _cursor < 0) { return null } const text = [{ text: '', color: 'gray', clickEvent: { action: 'suggest_command', value: bot.prefix + input } }] const cursor = Math.min(input.length, _cursor) if (cursor > CommandSyntaxException.CONTEXT_AMOUNT) { text.push('...') } text.push( input.substring(Math.max(0, cursor - CommandSyntaxException.CONTEXT_AMOUNT), cursor), { text: input.substring(cursor), color: 'red', underline: true }, { translate: 'command.context.here', color: 'red', italic: true } ) return text } function reload (loadFiles = true) { for (const cleanup of cleanups) cleanup() if (loadFiles) loadCommands() bot.commands.dispatcher = new CommandDispatcher() cleanups = [] for (let command of commands) { try { if (command.create) command = command.create() if (command.cleanup) cleanups.push(() => command.cleanup()) command.register(bot.commands.dispatcher, { bot }) } catch (error) { console.error('Unable to register command:', error) } } bot.emit('commands_loaded') } reload(false) } module.exports = inject