chipmunkbot3/plugins/commands.js

150 lines
4.6 KiB
JavaScript

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_source')
const TextMessage = require('../util/text_message')
function inject (bot) {
bot.commands = {
dispatcher: new CommandDispatcher(),
add,
execute,
info,
isCommand,
loadFromDir,
isValid
}
bot.on('message', (player, message) => {
if (!message.startsWith(bot.prefix)) return
function sendFeedback (message) {
bot.core.run('minecraft:tellraw @a ' + JSON.stringify(message))
}
bot.commands.execute(message.substring(bot.prefix.length), new CommandSource({ bot, player, 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