experimental new command system & fixes

This commit is contained in:
Chipmunk 2024-02-13 19:37:06 -05:00
parent 716257c45f
commit 5a69865919
10 changed files with 237 additions and 140 deletions

View file

@ -1,16 +0,0 @@
const name = 'eval'
const description = 'secure!!1'
const usages = ['<code...>']
const aliases = ['eval']
const enabled = true
const permLevel = 0
const util = require('util')
function execute (bot, cmd, player, args, handler) {
const result = bot.eval(args.join(' ').replace(/\xa7.?/g, ''))
bot.core.run('minecraft:tellraw @a ' + JSON.stringify({ text: util.inspect(result), color: bot.colors.primary }))
}
module.exports = { name, description, usages, aliases, enabled, execute, permLevel }

View file

@ -1,81 +1,98 @@
const name = 'help'
const description = 'Lists commands or shows info about a command.'
const usages = ['[command]']
const aliases = ['help']
const enabled = true
const { CommandDispatcher, builder: { LiteralArgumentBuilder: { literal }, RequiredArgumentBuilder: { argument } }, arguments: { StringArgumentType: { greedyString } } } = require('brigadier-commands')
const permLevel = 0
module.exports = {
register (dispatcher) {
const node = dispatcher.register(
literal('help')
.executes(this.listCommands)
.then(
argument('command', greedyString())
.executes(this.showCommandInfo)
)
)
function execute (bot, cmd, entity, args) {
if (args.length > 0) {
if (!bot.commands.isCommand(args[0])) { return bot.core.run(`/tellraw @a ${JSON.stringify({ text: `Unknown command: ${bot.prefix}${args[0]}`, color: bot.colors.error })}`) }
node.description = 'Lists commands or shows info about a command'
node.permissionLevel = 0
},
const command = bot.commands.info(args.shift())
listCommands (context) {
const source = context.source
const bot = source.bot
const nodes = bot.commands.dispatcher.root.getChildren()
const publicList = []
const trustedList = []
const adminList = []
const unknownList = []
nodes.forEach(node => {
if (node.redirect) return // ignore aliases
const msg = {
color: 'dark_aqua',
text: bot.prefix + node.name + ' ',
clickEvent: { action: 'suggest_command', value: bot.prefix + 'help ' + node.name },
hoverEvent: { action: 'show_text', value: 'Click to see info about the command' }
}
if (node.permissionLevel === 0) {
msg.color = 'green'
publicList.push(msg)
} else if (node.permissionLevel === 1) {
msg.color = 'red'
trustedList.push(msg)
} else if (node.permissionLevel === 2) {
msg.color = 'dark_red'
adminList.push(msg)
} else {
unknownList.push(msg)
}
})
const msg = [{ text: 'Commands - ', color: 'gray' }, ...publicList, ...trustedList, ...adminList, ...unknownList]
source.sendFeedback(msg, false)
},
showCommandInfo (context) {
const source = context.source
const bot = source.bot
const nodes = bot.commands.dispatcher.root.getChildren()
const commandName = context.getArgument('command')
let node = nodes.find(node => node.name === commandName)
if (node.redirect) node = node.redirect
const aliases = [node, ...nodes.filter(_node => _node.redirect === node)].map(node => node.name)
const usages = [...bot.commands.dispatcher.getSmartUsage(node, source, false).values()]
let msg
if (command.usages.length !== 1) {
if (usages.length !== 1) {
msg = [
{ text: bot.prefix + command.name, color: bot.colors.primary },
{ text: ' (' + command.aliases.join(', ') + ')', color: 'white' },
{ text: ` - ${command.description}\n`, color: 'gray' }
{ text: bot.prefix + node.name, color: bot.colors.primary },
{ text: ' (' + aliases.join(', ') + ')', color: 'white' },
{ text: ` - ${node.description}\n`, color: 'gray' }
]
command.usages.forEach((usage, i) => {
msg.push(bot.prefix + command.name)
usages.forEach((usage, i) => {
msg.push(bot.prefix + node.name)
msg.push({
text: ` ${usage}\n`,
color: bot.colors.secondary,
clickEvent: { action: 'suggest_command', value: command.name + ' ' + usage }
// hoverEvent: { action: 'show_text', value: 'Click to teleport' }
clickEvent: { action: 'suggest_command', value: node.name + ' ' + usage }
})
})
msg[msg.length - 1].text = msg[msg.length - 1].text.slice(0, -1)
} else {
msg = [
{ text: bot.prefix + command.name, color: bot.colors.primary },
{ text: ' (' + command.aliases.join(', ') + ')', color: 'white' },
{ text: bot.prefix + node.name, color: bot.colors.primary },
{ text: ' (' + aliases.join(', ') + ')', color: 'white' },
{
text: ` ${command.usages[0]}`,
text: ` ${usages[0]}`,
color: bot.colors.secondary,
clickEvent: { action: 'suggest_command', value: command.name + ' ' + command.usages[0] }
clickEvent: { action: 'suggest_command', value: node.name + ' ' + usages[0] }
},
{ text: ` - ${command.description}`, color: 'gray' }
{ text: ` - ${node.description}`, color: 'gray' }
]
}
return bot.core.run(`minecraft:tellraw @a ${JSON.stringify(msg)}`)
source.sendFeedback(msg, false)
}
let commands = []
Object.keys(bot.commands.commands).forEach((command) => {
if (bot.commands.isCommand(command) && !commands.includes(bot.commands.info(command))) { commands.push(bot.commands.info(command)) }
})
commands = commands.filter((command) => command.enabled)
const publicList = []
const trustedList = []
const adminList = []
const unknownList = []
commands.forEach((command) => {
const msg = {
color: 'dark_aqua',
text: bot.prefix + command.name + ' ',
clickEvent: { action: 'run_command', value: bot.prefix + aliases[0] + ' ' + command.name },
hoverEvent: { action: 'show_text', value: 'Click to see info about the command' }
}
if (command.permLevel === 0) {
msg.color = 'green'
publicList.push(msg)
} else if (command.permLevel === 1) {
msg.color = 'red'
trustedList.push(msg)
} else if (command.permLevel === 2) {
msg.color = 'dark_red'
adminList.push(msg)
} else {
unknownList.push(msg)
}
})
const msg = [{ text: 'Commands - ', color: 'gray' }, ...publicList, ...trustedList, ...adminList, ...unknownList]
bot.core.run(`/tellraw @a ${JSON.stringify(msg)}`)
}
module.exports = { name, description, usages, aliases, enabled, execute, permLevel }

View file

@ -27,15 +27,16 @@ filepath += '.log'
fs.writeFileSync(filepath, '')
const servers = [
'play.kaboom.pw:25565:kaboom',
'chipmunk.land:25565:kaboom'
]
const bots = createBots(servers, {
username: 'MusicBot',
username: ' ',
prefix: "'",
colors: { primary: 'green', secondary: 'dark_green', error: 'red' },
version: '1.20.4',
randomizeUsername: false,
randomizeUsername: true,
autoReconnect: true
// 'online-mode': { enabled: false, username: 'removed lol', password: null }
})

10
package-lock.json generated
View file

@ -4,11 +4,11 @@
"requires": true,
"packages": {
"": {
"name": "chipmunktest",
"dependencies": {
"@mozilla/readability": "^0.4.1",
"@skeldjs/client": "^2.15.17",
"@tonejs/midi": "^2.0.27",
"brigadier-commands": "git+https://code.chipmunk.land/ChipmunkMC/node-brigadier-commands.git",
"colorsys": "^1.0.22",
"fluent-ffmpeg": "^2.1.2",
"kahoot.js-api": "^2.4.0",
@ -517,6 +517,10 @@
"concat-map": "0.0.1"
}
},
"node_modules/brigadier-commands": {
"version": "1.0.0",
"resolved": "git+https://code.chipmunk.land/ChipmunkMC/node-brigadier-commands.git#c89271d021a1537d3045a93850e0c7ccb6efd9ae"
},
"node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
@ -3941,6 +3945,10 @@
"concat-map": "0.0.1"
}
},
"brigadier-commands": {
"version": "git+https://code.chipmunk.land/ChipmunkMC/node-brigadier-commands.git#c89271d021a1537d3045a93850e0c7ccb6efd9ae",
"from": "brigadier-commands@git+https://code.chipmunk.land/ChipmunkMC/node-brigadier-commands.git"
},
"buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",

View file

@ -11,6 +11,7 @@
"prismarine-nbt": "^2.2.0",
"rfb2": "^0.2.2",
"standard": "^16.0.4",
"urban-dictionary": "git+https://code.chipmunk.land/ChipmunkMC/urban-dictionary.git"
"urban-dictionary": "git+https://code.chipmunk.land/ChipmunkMC/urban-dictionary.git",
"brigadier-commands": "git+https://code.chipmunk.land/ChipmunkMC/node-brigadier-commands.git"
}
}

View file

@ -1,10 +1,13 @@
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 = {
commands: {},
dispatcher: new CommandDispatcher(),
add,
execute,
info,
@ -14,57 +17,109 @@ function inject (bot) {
}
bot.on('message', (player, message) => {
if (!message.startsWith(bot.prefix)) { return }
if (!message.startsWith(bot.prefix)) return
const args = message.slice(bot.prefix.length).split(' ')
const command = args.shift().toLowerCase()
if (!isCommand(command)) { return bot.core.run(`/tellraw @a ${JSON.stringify({ text: `Unknown command: ${bot.prefix}${command}`, color: bot.colors.error })}`) }
bot.commands.execute(bot, command, player, args)
const sendFeedback = message => bot.core.run('minecraft:tellraw @a ' + JSON.stringify(message))
bot.commands.execute(message.substring(bot.prefix.length), new CommandSource({ bot, sendFeedback }))
})
function add (command) {
if (!isValid(command)) throw new Error('Invalid command', 'invalid_command')
command.aliases.forEach(alias => (bot.commands.commands[alias.toLowerCase()] = 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 = args => command.execute(bot, command.aliases[0], {}, args)
const node = bot.commands.dispatcher.register(
literal(command.aliases[0])
.executes(context => { _execute([]); return 0 })
.then(
argument('args', greedyString())
.executes(context => { _execute(context.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 })
.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 (err) {
bot.console.error('Error loading command ' + filepath + ': ' + util.inspect(err))
} 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 bot.commands.info(command) != null }
async function execute (bot, command, player, args, ...custom) {
const info = bot.commands.info(command)
if (info == null) {
bot.core.run('minecraft:tellraw @a ' + JSON.stringify({ text: 'Unknown command: ' + bot.prefix + command, color: bot.colors.error }))
return
}
if (!info.enabled) {
bot.core.run('minecraft:tellraw @a ' + JSON.stringify({ text: bot.prefix + command + 'is disabled', color: bot.colors.error }))
return
}
if (info.permLevel > 0) {
bot.core.run('minecraft:tellraw @a ' + JSON.stringify({ text: 'Trusted commands are currently disabled', color: bot.colors.error }))
return
}
function isCommand (command) { return true }
function execute (command, source) {
try {
return await info.execute(bot, command, player, args, ...custom)
} catch (err) {
bot.console.error('Error executing command ' + command + ': ' + util.inspect(err))
bot.core.run('minecraft:tellraw @a ' + JSON.stringify({ text: err.message, color: bot.colors.error }))
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) {

View file

@ -1,6 +1,8 @@
const fs = require('fs')
const util = require('util')
const moment = require('moment')
const CommandSource = require('../util/command_source')
const parseText = require('../util/text_parser')
const ansimap = {
0: '\x1b[0m\x1b[30m',
1: '\x1b[0m\x1b[34m',
@ -72,24 +74,18 @@ function inject (bot) {
function handleLine (line) {
if (bot.host !== bot.console.host && bot.console.host !== 'all') return
if (line.startsWith('.')) {
const args = line.slice(1).trim().split(' ')
const command = args.shift()
if (!bot.commands.isCommand(command)) {
bot.console.error('Unknown command: ' + command)
return
}
const info = bot.commands.info(command)
try {
info.execute(bot, command, bot.player, args)
} catch (err) {
bot.console.error(`Error executing ${command} in console: ${util.inspect(err)}`)
}
const source = new CommandSource({ bot, sendFeedback })
bot.commands.execute(line.substring(1), source)
} else {
bot.fancyMsg('test', '_ChipMC_', line)
rl?.prompt(true)
}
}
function sendFeedback (message) {console.log(message)
const { raw } = parseText(message);console.log(raw)
bot.console.log(raw)
}
}
}

13
util/command_source.js Normal file
View file

@ -0,0 +1,13 @@
class CommandSource {
constructor ({ bot, permissionLevel = 0, sendFeedback = () => {} } = {}) {
this.bot = bot
this.permissionLevel = permissionLevel
this.sendFeedback = sendFeedback
}
sendError (error) {
this.sendFeedback([{ text: '', color: 'red' }, error], false)
}
}
module.exports = CommandSource

18
util/text_message.js Normal file
View file

@ -0,0 +1,18 @@
const parseText = require('./text_parser.js')
class TextMessage {
constructor (text) {
this.text = text
}
getString () {
const { clean } = parseText(this.text)
return clean
}
toString () {
return this.getString()
}
}
module.exports = TextMessage

View file

@ -1,4 +1,4 @@
const { language } = require('minecraft-data')('1.17.1')
const { language } = require('minecraft-data')('1.20.4')
const colormap = {
black: '§0',
@ -69,6 +69,10 @@ function parseText (json) {
function parseJson (json, parent) {
if (typeof json === 'string') {
json = { text: json }
} else if (Array.isArray(json)) {
const root = json.shift()
root.extra = json
json = root
}
json.color ??= parent.color
@ -94,24 +98,24 @@ function parseJson (json, parent) {
raw += json['']
}
if (json.translate) { // I checked with the native minecraft code. This is how Minecraft does the matching and group indexing. -hhhzzzsss
if (language[json.translate]) {
const _with = json.with ?? []
let i = 0
raw += language[json.translate].replace(/%(?:(\\d+)\\$)?(s|%)/g, (g0, g1) => {
if (g0 === '%%') {
return '%'
let format = language[json.translate]
if (typeof format !== 'string') format = json.fallback
if (typeof format !== 'string') format = json.translate
const _with = json.with ?? []
let i = 0
raw += format.replace(/%(?:(\\d+)\\$)?(s|%)/g, (g0, g1) => {
if (g0 === '%%') {
return '%'
} else {
const idx = g1 ? parseInt(g1) : i++
if (_with[idx]) {
return parseJson(_with[idx], json)
} else {
const idx = g1 ? parseInt(g1) : i++
if (_with[idx]) {
return parseJson(_with[idx], json)
} else {
return ''
}
return ''
}
})
} else {
raw += json.translate
}
}
})
}
if (json.extra) {
json.extra.forEach((extra) => {