const fs = require('fs') const path = require('path') const util = require('util') const { CommandDispatcher, builder: { LiteralArgumentBuilder: { literal }, RequiredArgumentBuilder: { argument } }, arguments: { StringArgumentType: { greedyString } }, exceptions: { 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') function inject (bot) { bot.commands = { dispatcher: new CommandDispatcher(), add, execute, info, isCommand, loadFromDir, isValid } bot.on('message', ({ sender, plain }) => { if (!plain.startsWith(bot.prefix)) return function sendFeedback (message) { bot.tellraw(message, '@a') } bot.commands.execute(plain.substring(bot.prefix.length), new CommandSource({ bot, player: sender, sendFeedback })) }) function add (command) { if (command.register) { command.register(bot.commands.dispatcher) return } if (isValid(command)) { bot.console.warn(`Command '${command.aliases[0]}' is using the legacy command system!`) const _execute = (c, args) => { try { const player = c.source.player command.execute(bot, command.aliases[0], { UUID: player?.uuid, name: player?.username }, args) } catch (error) { bot.core.run('minecraft:tellraw @a ' + JSON.stringify({ text: error.toString(), color: 'red' })) } } const requirement = source => source.permissionLevel >= command.permLevel const node = bot.commands.dispatcher.register( literal(command.aliases[0]) .executes(c => { _execute(c, []); return 0 }) .requires(requirement) .then( argument('args', greedyString()) .executes(c => { _execute(c, c.getArgument('args').split(' ')); return 0 }) ) ) for (let i = 1; i < command.aliases.length; i++) { bot.commands.dispatcher.register( literal(command.aliases[i]) .executes(context => { _execute([]); return 0 }) .requires(requirement) .redirect(node) ) } // add metadata for help command node.description = command.description node.permissionLevel = command.permLevel return } throw new Error('Invalid command', 'invalid_command') } function loadFromDir (dirpath) { fs.readdirSync(dirpath).forEach(filename => { const filepath = path.resolve(dirpath, filename) if (!filepath.endsWith('js') || !fs.statSync(filepath).isFile()) return try { bot.commands.add(require(filepath)) } catch (error) { bot.console.error('Error loading command ' + filepath + ': ' + util.inspect(error)) } }) } function info (command) { const info = bot.commands.commands[command] ?? command if (isValid(info)) { return info } } function isCommand (command) { return true } function execute (command, source) { try { bot.commands.dispatcher.execute(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 } source.sendError({ translate: 'command.failed', hoverEvent: { action: 'show_text', contents: error.stack } }) } } 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 isValid (command) { return command != null && typeof command.execute === 'function' && typeof command.name === 'string' && typeof command.description === 'string' && Array.isArray(command.usages) && Array.isArray(command.aliases) && typeof command.enabled === 'boolean' && command.aliases.length > 0 && typeof command.permLevel === 'number' } module.exports = inject