diff --git a/bot.js b/bot.js index e0220d8..613e3b0 100644 --- a/bot.js +++ b/bot.js @@ -16,9 +16,9 @@ function createBot (options = {}) { options.prefix ??= '!' options.brand ??= 'vanilla' // found that mineflayer has this so i added it here lol - options.colors ??= {} - options.colors.primary ??= 'white' - options.colors.secondary ??= 'green' + options.styles ??= {} + options.styles.primary ??= { color: 'white' } + options.styles.secondary ??= { color: 'green' } options.autoReconnect ??= false options.randomizeUsername ??= false @@ -48,7 +48,7 @@ function createBot (options = {}) { bot.prefix = options.prefix bot.brand = options.brand - bot.colors = options.colors + bot.styles = options.styles bot.autoReconnect = options.autoReconnect bot.randomizeUsername = options.randomizeUsername bot['online-mode'] = options['online-mode'] diff --git a/commands/badapple.js b/commands/badapple.js deleted file mode 100644 index 79f3fd6..0000000 --- a/commands/badapple.js +++ /dev/null @@ -1,23 +0,0 @@ -const name = 'badapple' -const description = 'Plays a badapple video.' -const usages = ['[stop]'] -const aliases = ['badapple'] -const enabled = true - -const permLevel = 0 - -function execute (bot, cmd, player, args) { - if (args[0] === 'stop') { - bot.video.stop() - bot.music.skip() - return - } - - bot.video.summon(player.UUID, (uuids) => { - bot.music.stop() - bot.music.queue.push('./music/badapple.mid') - bot.video.play('./videos/badapple.txt', uuids) - }) -} - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/blacklist.js b/commands/blacklist.js deleted file mode 100644 index 67bd2db..0000000 --- a/commands/blacklist.js +++ /dev/null @@ -1,47 +0,0 @@ -const name = 'blacklist' -const description = 'idk' -const usages = [ - 'add ', - 'remove ', - 'list' -] -const aliases = ['blacklist'] -const enabled = true - -const permLevel = 1 - -function execute (bot, cmd, player, args, handler) { - const subCmd = args.shift() - - let i, msg - switch (subCmd) { - case 'add': - bot.blacklist.push([args[0], args[1]]) - bot.core.run(`minecraft:tellraw @a ${JSON.stringify([ - { text: 'Added regex ', color: bot.colors.primary }, - { text: `/${args[0]}/${args[1]}`, color: bot.colors.secondary }, - ' to the blacklist.' - ])}`) - break - case 'remove': - i = bot.blacklist.indexOf([args[0], args[1]]) - if (i < 0) throw new Error(`There is no regex /${args[0]}/${args[1]} in the blacklist.`) - - bot.blacklist.splice(i, 1) - bot.core.run(`minecraft:tellraw @a ${JSON.stringify([ - { text: 'Removed regex ', color: bot.colors.primary }, - { text: `/${args[0]}/${args[1]}`, color: bot.colors.secondary }, - ' from the blacklist.' - ])}`) - break - case 'list': - msg = [{ text: 'Regexes:\n', color: bot.colors.primary }] - bot.blacklist.forEach(([pattern, flags]) => { - msg.push({ text: `/${pattern}/${flags}\n`, color: bot.colors.secondary }) - }) - msg[msg.length - 1].text = msg[msg.length - 1].text.slice(0, -1) - bot.core.run(`minecraft:tellraw @a ${JSON.stringify(msg)}`) - } -} - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/bruhify.js b/commands/bruhify.js index 4d9d59f..18f9bae 100755 --- a/commands/bruhify.js +++ b/commands/bruhify.js @@ -1,42 +1,51 @@ -const name = 'bruhify' -const description = 'recyclebot' -const usages = [''] -const aliases = ['bruhify'] -const enabled = true - -const permLevel = 0 - -let isBruhifying = false +const { literal, argument, greedyString } = require('brigadier-commands') const colorsys = require('colorsys') -function execute (bot, cmd, player, args, handler) { - if (isBruhifying) throw new Error('The bot is already bruhifying text!') - isBruhifying = true +module.exports = { + isBruhifying: false, - const message = args.join(' ') + register (dispatcher) { + const node = dispatcher.register( + literal('bruhify') + .then( + argument('message', greedyString()) + .executes(this.bruhifyCommand.bind(this)) + ) + ) - const lines = [] - let j = 0 - for (let i = 0; i < message.length; i++) { - const result = [] - let hue = j - message.split('').forEach((char) => { - result.push({ text: char, color: colorsys.hsv2Hex(hue, 100, 100) }) - hue += 355 / Math.max(message.length, 20) - }) + node.description = 'recyclebot' + node.permissionLevel = 0 + }, - lines.push(JSON.stringify([{ text: '▚ ', color: 'light_purple' }].concat(result, [' ▚']))) - j += 355 / Math.max(message.length, 20) - } + bruhifyCommand (context) { + if (this.isBruhifying) throw new Error('The bot is already bruhifying text!') + this.isBruhifying = true - let k = 0 - const interval = setInterval(() => { - bot.core.run(`minecraft:tellraw @a ${lines[k]}`) - if (++k > lines.length) { - clearInterval(interval) - isBruhifying = false + const source = context.source + const bot = source.bot + const message = context.getArgument('message') + + const lines = [] + let j = 0 + for (let i = 0; i < message.length; i++) { + const result = [] + let hue = j + message.split('').forEach((char) => { + result.push({ text: char, color: colorsys.hsv2Hex(hue, 100, 100) }) + hue += 355 / Math.max(message.length, 20) + }) + + lines.push([{ text: '▚ ', color: 'light_purple' }].concat(result, [' ▚'])) + j += 355 / Math.max(message.length, 20) } - }, 50) -} -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } + let k = 0 + const interval = setInterval(() => { + source.sendFeedback(lines[k], false) + if (++k >= lines.length) { + clearInterval(interval) + this.isBruhifying = false + } + }, 50) + } +} diff --git a/commands/cb.js b/commands/cb.js index 417c25e..71eab78 100644 --- a/commands/cb.js +++ b/commands/cb.js @@ -1,13 +1,21 @@ -const name = 'cb' -const description = 'Runs a command in the command core' -const usages = [''] -const aliases = ['cb'] -const enabled = true +const { literal, argument, greedyString } = require('brigadier-commands') -const permLevel = 0 +module.exports = { + register (dispatcher) { + const node = dispatcher.register( + literal('cb') + .then( + argument('command', greedyString()) + .executes(this.runCommand) + ) + ) -function execute (bot, cmd, player, args) { - bot.core.run(args.join(' ')) + node.description = 'Runs a command in the command core' + node.permissionLevel = 0 + }, + + runCommand (context) { + const bot = context.source.bot + bot.core.run(context.getArgument('command')) + } } - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/clearchat.js b/commands/clearchat.js index 70fd7e8..32b1431 100644 --- a/commands/clearchat.js +++ b/commands/clearchat.js @@ -1,19 +1,33 @@ -const name = 'clearchat' -const description = 'Clears the chat' -const usages = ['[selector]'] -const aliases = ['clearchat', 'cc'] -const enabled = true +const { literal, argument, greedyString } = require('brigadier-commands') -const permLevel = 0 +module.exports = { + register (dispatcher) { + const node = dispatcher.register( + literal('clearchat') + .executes(this.clearChatCommand.bind(this)) + .then( + argument('targets', greedyString()) + .executes(this.targettedclearChatCommand.bind(this)) + ) + ) -function execute (bot, cmd, player, args, handler) { - const text = [] - while (text.length < 100) { - text.push('\n') + dispatcher.register(literal('cc').executes(this.clearChatCommand.bind(this)).redirect(node)) + + node.description = 'Clears the chat of everyone or a specific player' + node.permissionLevel = 0 + }, + + clearChatCommand (context) { + const bot = context.source.bot + this.sendclearChatCommandMessage(bot, '@a') + }, + + targettedclearChatCommand (context) { + const bot = context.source.bot + this.sendclearChatCommandMessage(bot, context.getArgument('targets')) + }, + + sendclearChatCommandMessage (bot, targets) { + bot.tellraw({ text: '\n'.repeat(100) + 'The chat has been cleared', color: 'dark_green' }, targets) } - text.push({ text: 'The chat has been cleared', color: 'dark_green' }) - - bot.core.run(`/minecraft:tellraw ${args.join(' ') || '@a'} ${JSON.stringify(text)}`) } - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/client.js b/commands/client.js index 1aa1b14..59fc196 100644 --- a/commands/client.js +++ b/commands/client.js @@ -1,66 +1,110 @@ -const name = 'client' -const description = 'Creates and manages clients using minecraft-protocol.' -const usages = ['create ', 'end ', 'write ', 'list'] -const aliases = ['client'] -const enabled = true - -const permLevel = 0 - +const { literal, argument, string, DynamicCommandExceptionType } = require('brigadier-commands') +const { json } = require('../util/command/argument/json') +const TextMessage = require('../util/command/text_message') const mc = require('minecraft-protocol') -const clients = [] -const sectionRegex = /\u00a7.?/g -const util = require('util') -function execute (bot, cmd, player, args, handler) { - const subCmd = args.shift() +const UNKNOWN_CLIENT_ERROR = new DynamicCommandExceptionType(string => new TextMessage(['Unknown client: ', string])) - switch (subCmd) { - case 'create': { - const options = { host: bot.host, port: bot.port, ...JSON.parse(args.join(' ').replace(sectionRegex, '')) } +module.exports = { + clients: [], - const client = mc.createClient(options) - const idx = clients.length + register (dispatcher) { + const node = dispatcher.register( + literal('client') + .then( + literal('create') + .then( + argument('options', json()) + .executes(this.createCommand.bind(this)) + ) + ) + .then( + literal('write') + .then( + argument('client', string()) + .then( + argument('name', string()) + .then( + argument('data', json()) + .executes(this.writeCommand.bind(this)) + ) + ) + ) + ) + .then( + literal('end') + .then( + argument('client', string()) + .executes(this.endCommand.bind(this)) + ) + ) + .then( + literal('list') + .executes(this.listCommand.bind(this)) + ) + ) - client.once('login', () => bot.core.run('minecraft:tellraw @a ' + JSON.stringify([{ text: client.username, color: bot.colors.primary }, ' logged in']))) + node.description = 'Creates and manages this.clients using minecraft-protocol' + node.permissionLevel = 0 + }, - client.on('end', () => { - clients.splice(idx, 1) - bot.core.run('minecraft:tellraw @a ' + JSON.stringify([{ text: client.username, color: bot.colors.primary }, ' ended'])) - }) + createCommand (context) { + const source = context.source + const bot = source.bot - client.on('error', (err) => bot.core.run('minecraft:tellraw @a ' + JSON.stringify({ text: util.inspect(err).replace(/\n.*/g, ''), color: bot.colors.error }))) + const options = { host: bot.host, port: bot.port, ...context.getArgument('options') } - clients.push(client) - } - break - case 'end': { - const stridx = args.join(' ').replace(sectionRegex, '') - let idx = Number(stridx) - if (!Number.isInteger(idx)) clients.forEach((client, cidx) => { if (client.username === stridx) idx = cidx }) - if (!Number.isInteger(idx)) throw new Error('Unknown client: ' + stridx) + const client = mc.createClient(options) + const idx = this.clients.length - clients[idx].end() - clients.splice(idx, 1) - } - break - case 'write': { - const stridx = args.shift().replace(sectionRegex, '') - let idx = Number(stridx) - if (!Number.isInteger(idx)) clients.forEach((client, cidx) => { if (client.username === stridx) idx = cidx }) - if (!Number.isInteger(idx)) throw new Error('Unknown client: ' + stridx) + client.once('login', () => source.sendFeedback([{ text: client.username, ...bot.styles.primary }, ' logged in']), false) - const name = args.shift() - const data = JSON.parse(args.join(' ').replace(sectionRegex, '')) + client.on('end', () => { + this.clients.splice(idx, 1) + source.sendFeedback([{ text: client.username, ...bot.styles.primary }, ' ended'], false) + }) - clients[idx].write(name, data) - } - break - case 'list': - bot.core.run('minecraft:tellraw @a ' + JSON.stringify({ text: 'Clients: ' + clients.map(client => client.username).join('\u00a7r, '), color: bot.colors.primary })) - break - default: - throw new Error('Invalid or missing argument') + client.on('error', (err) => bot.tellraw({ text: err.toString(), ...bot.styles.error }, '@a')) + + this.clients.push(client) + }, + + writeCommand (context) { + const idx = this.findClientIdxByString(context.getArgument('client')) + + const name = context.getArgument('name') + const data = context.getArgument('data') + + this.clients[idx].write(name, data) + }, + + endCommand (context) { + const source = context.source + const idx = this.findClientIdxByString(context.getArgument('client')) + + this.clients[idx].end() + this.clients.splice(idx, 1) + }, + + listCommand (context) { + const source = context.source + const bot = source.bot + + const list = [] + for (let i = 0; i < this.clients.length; i++) { + const client = this.clients[i] + if (i !== 0) list.push(', ') + list.push(client.username) + } + + source.sendFeedback([{ text: 'Clients: ', ...bot.styles.primary }, list], false) + }, + + findClientIdxByString (string) { + let idx = Number(string) + if (!this.clients[idx]) this.clients.forEach((client, cidx) => { if (client.username === string) idx = cidx }) + if (!this.clients[idx]) throw UNKNOWN_CLIENT_ERROR.create(string) + + return idx } } - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/cloop.js b/commands/cloop.js index 83a9505..848a459 100644 --- a/commands/cloop.js +++ b/commands/cloop.js @@ -1,49 +1,92 @@ -const name = 'cloop' -const description = 'Loops commands' -const usages = [ - 'add ', - 'remove ', - 'list' -] -const aliases = ['cloop'] -const enabled = true +const { literal, argument, integer, greedyString } = require('brigadier-commands') -const permLevel = 1 +module.exports = { + register (dispatcher) { + const node = dispatcher.register( + literal('cloop') + .requires(source => source.permissionLevel >= node.permissionLevel) + .then( + literal('add') + .then( + argument('interval', integer()) + .then( + argument('command', greedyString()) + .executes(this.addCommand) + ) + ) + ) + .then( + literal('remove') + .then( + argument('index', integer()) + .executes(this.removeCommand) + ) + ) + .then( + literal('list') + .executes(this.listCommand) + ) + .then( + literal('clear') + .executes(this.clearCommand) + ) + ) -function execute (bot, cmd, player, args, handler) { - const subCommand = args.shift() + node.description = 'Loops commands in the command core' + node.permissionLevel = 1 + }, - let interval, command, i, msg - switch (subCommand) { - case 'add': - interval = parseFloat(args.shift()) - command = args.join(' ').replace(/u00a7.?/g, '') - bot.cloops.push({ command, interval }) + addCommand (context) { + const source = context.source + const bot = source.bot - bot.core.run(`/tellraw @a ${JSON.stringify([ - { text: 'Added command ', color: bot.colors.primary }, - { text: command, color: bot.colors.secondary }, - ' to cloops.' - ])}`) - break - case 'remove': - i = parseFloat(args.shift()) - bot.cloops.splice(i, 1) + const interval = context.getArgument('interval') + const command = context.getArgument('command') + bot.cloops.push({ command, interval }) - bot.core.run(`/tellraw @a ${JSON.stringify([ - { text: 'Removed cloop ', color: bot.colors.primary }, - { text: i, color: bot.colors.secondary }, - '.' - ])}`) - break - case 'list': - msg = [{ text: 'Cloops: \n', color: bot.colors.primary }] - for (const i in bot.cloops) { - msg.push({ text: `${i}: ` }) - msg.push({ text: `${bot.cloops[i].command}\n`, color: bot.colors.secondary }) - } - bot.core.run(`/tellraw @a ${JSON.stringify(msg)}`) + source.sendFeedback([ + { text: 'Looping command ', ...bot.styles.primary }, + { text: command, ...bot.styles.secondary }, + ' at interval ', + { text: String(interval), ...bot.styles.secondary } + ], false) + }, + + removeCommand (context) { + const source = context.source + const bot = source.bot + + const idx = context.getArgument('index') + bot.cloops.splice(idx, 1) + + source.sendFeedback([ + { text: 'Removed cloop ', ...bot.styles.primary }, + { text: String(idx), ...bot.styles.secondary } + ], false) + }, + + listCommand (context) { + const source = context.source + const bot = source.bot + + const msg = [{ text: 'Cloops: \n', ...bot.styles.primary }] + for (let i = 0; i < bot.cloops.length; i++) { + if (i !== 0) msg.push('\n') + msg.push( + String(i), + ': ', + { text: String(bot.cloops[i].command), ...bot.styles.secondary } + ) + } + + source.sendFeedback(msg, false) + }, + + clearCommand (context) { + const source = context.source + const bot = source.bot + + bot.cloops.splice(0, bot.cloops.length) + source.sendFeedback({ text: 'Cleared cloops', ...bot.styles.primary }, false) } } - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/consoleserver.js b/commands/consoleserver.js deleted file mode 100644 index 7eac66d..0000000 --- a/commands/consoleserver.js +++ /dev/null @@ -1,16 +0,0 @@ -const name = 'consoleserver' -const description = 'sets the console server' -const usages = [''] -const aliases = ['consoleserver', 'consolesvr', 'csvr'] -const enabled = false - -const permLevel = 0 - -function execute (bot, cmd, player, args) { - const host = args.join(' ') - bot.getBots().forEach(bot => { - bot.console.host = host - }) -} - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/crashmf.js b/commands/crashmf.js deleted file mode 100644 index f431460..0000000 --- a/commands/crashmf.js +++ /dev/null @@ -1,13 +0,0 @@ -const name = 'crashmf' -const description = 'Crashes mineflayer bots.' -const usage = '{prefix}crashmf' -const aliases = ['crashmf'] -const enabled = true - -const permLevel = 0 - -function execute (bot, cmd, entity, args, handler) { - bot.core.run(`/tellraw @a ${JSON.stringify({ translate: 'translation.test.invalid', with: ['amogeese'] })}`) -} - -module.exports = { name, description, usage, aliases, enabled, execute, permLevel } diff --git a/commands/credits.js b/commands/credits.js index 1695051..87a05ae 100644 --- a/commands/credits.js +++ b/commands/credits.js @@ -1,37 +1,43 @@ -const name = 'credits' -const description = 'Shows bot credits.' -const usages = [] -const aliases = ['credits'] -const enabled = true +const { literal } = require('brigadier-commands') +const pkg = require('../package.json') -const permLevel = 0 +module.exports = { + register (dispatcher) { + const node = dispatcher.register( + literal('credits') + .executes(this.creditsCommand) + ) -const pack = require('./../package.json') + node.description = 'Lists people who have contributed to the bot' + node.permissionLevel = 0 + }, -function execute (bot, cmd, entity, args, handler) { - bot.core.run(`/tellraw @a ${JSON.stringify([ - { text: '', color: 'gray' }, - { text: 'Credits\n', color: bot.colors.primary, bold: true }, + creditsCommand (context) { + const source = context.source + const bot = source.bot - { text: '_ChipMC_', color: 'blue' }, - ' - creating the bot\n', + source.sendFeedback([ + { text: '', color: 'gray' }, + { text: 'Credits\n', ...bot.styles.primary, bold: true }, - { text: 'hhhzzzsss', color: 'aqua', bold: true }, - ' and ', - { text: 'eva', color: 'light_purple', italic: true }, - ' - creating the midi converter.\n', + { text: '_ChipMC_', color: 'blue' }, + ' - creating the bot\n', - { text: 'ma', color: 'aqua' }, - { text: 'ni', color: 'light_purple' }, - { text: 'a', color: 'white' }, - { text: 'pl', color: 'light_purple' }, - { text: 'ay', color: 'aqua' }, - ' and ', - { text: 'ayunami2000', color: 'red' }, - ' - creating the image converter', + { text: 'hhhzzzsss', color: 'aqua', bold: true }, + ' and ', + { text: 'eva', color: 'light_purple', italic: true }, + ' - creating the midi converter.\n', - `\n\nDependencies: ${Object.entries(pack.dependencies).join(' ')}` -])}`) + { text: 'ma', color: 'aqua' }, + { text: 'ni', color: 'light_purple' }, + { text: 'a', color: 'white' }, + { text: 'pl', color: 'light_purple' }, + { text: 'ay', color: 'aqua' }, + ' and ', + { text: 'ayunami2000', color: 'red' }, + ' - creating the image converter', + + '\n\nDependencies: ' + Object.entries(pkg.dependencies).map(entry => entry.join(' ')).join(', ') + ], false) + } } - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/csvr.js b/commands/csvr.js new file mode 100644 index 0000000..bfbb9d3 --- /dev/null +++ b/commands/csvr.js @@ -0,0 +1,25 @@ +const { literal, argument, greedyString } = require('brigadier-commands') + +module.exports = { + register (dispatcher) { + const node = dispatcher.register( + literal('csvr') + .then( + argument('host', greedyString()) + .executes(this.consoleServerCommand) + ) + ) + + node.description = 'Sets the console server' + node.permissionLevel = 0 + }, + + consoleServerCommand (context) { + const bot = context.source.bot + const host = context.getArgument('host') + + for (const other of bot.bots) { + other.console.host = host + } + } +} diff --git a/commands/destroy.js b/commands/destroy.js deleted file mode 100644 index 38e3606..0000000 --- a/commands/destroy.js +++ /dev/null @@ -1,17 +0,0 @@ -const name = 'destroy' -const description = 'destroy' -const usages = ['destroy'] -const aliases = ['destroy'] -const enabled = true - -const permLevel = 1 - -function execute (bot, cmd, entity, args, handler) { - let i = 0 - setInterval(() => { - bot.core.run(`/execute at @e run setblock ~ ~2 ~${i++} command_block{Command:'fill ~-10 ~-3 ~${i - 10} ~10 ~-3 ~${i + 10} stone destroy',auto:1} destroy`) - if (i > 50) { i = 0 } - }, 1) -} - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/echo.js b/commands/echo.js index 88ed3e1..fc32f59 100644 --- a/commands/echo.js +++ b/commands/echo.js @@ -1,13 +1,21 @@ -const name = 'echo' -const description = 'Echoes text' -const usages = [''] -const aliases = ['echo'] -const enabled = true +const { literal, argument, greedyString } = require('brigadier-commands') -const permLevel = 0 +module.exports = { + register (dispatcher) { + const node = dispatcher.register( + literal('echo') + .then( + argument('message', greedyString()) + .executes(this.echoCommand) + ) + ) -function execute (bot, cmd, player, args, handler) { - bot.core.run(`essentials:sudo ${bot.uuid} c:${args.join(' ')}`) + node.description = 'Sends a message in chat as the bot' + node.permissionLevel = 0 + }, + + echoCommand (context) { + const bot = context.source.bot + bot.core.run(`essentials:sudo ${bot.uuid} c:${context.getArgument('message')}`) + } } - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/end.js b/commands/end.js index 7b4c054..451d3f3 100644 --- a/commands/end.js +++ b/commands/end.js @@ -1,13 +1,18 @@ -const name = 'end' -const description = 'Ends the bot\'s client.' -const usages = [] -const aliases = ['end'] -const enabled = true +const { literal } = require('brigadier-commands') -const permLevel = 0 +module.exports = { + register (dispatcher) { + const node = dispatcher.register( + literal('end') + .executes(this.endCommand) + ) -function execute (bot, cmd, player, args, handler) { - bot.end() + node.description = "Ends the bot's connection to the server" + node.permissionLevel = 0 + }, + + endCommand (context) { + const bot = context.source.bot + bot.end() + } } - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/esc.js b/commands/esc.js deleted file mode 100644 index 6f675e9..0000000 --- a/commands/esc.js +++ /dev/null @@ -1,13 +0,0 @@ -const name = 'esc' -const description = 'among.us website' -const usages = [] -const aliases = ['esc'] -const enabled = true - -const permLevel = 0 - -function execute (bot, cmd, player, args, handler) { - bot.core.run('/tellraw @a ' + JSON.stringify({ text: 'Click here to get kicked!', underlined: true, clickEvent: { action: 'run_command', value: '/\x1bi' } })) -} - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/function.js b/commands/function.js deleted file mode 100644 index a78740c..0000000 --- a/commands/function.js +++ /dev/null @@ -1,39 +0,0 @@ -const name = 'function' -const description = 'Runs mcfunctions.' -const usages = ['run ', 'list'] -const aliases = ['function', 'func'] -const enabled = true - -const permLevel = 0 - -const fs = require('fs') - -function execute (bot, cmd, player, args, handler) { - const subCmd = args.shift() - - let filepath, files, msg - switch (subCmd) { - case 'run': - filepath = `./functions/${args.join(' ').replace(/§./g, '')}` - if (!filepath.endsWith('.mcfunction')) filepath += '.mcfunction' - if (/\.\.\//.test(filepath) || !fs.existsSync(filepath)) throw new Error('Invalid function name') - bot.runFunction(filepath) - break - case 'list': - files = fs.readdirSync('./functions') - // files.filter((file) => file.endsWith('.mid')) - - msg = [{ text: 'Functions:\n', color: bot.colors.primary }] - files.forEach((file) => { - msg.push(file) - msg.push({ text: ', ', color: bot.colors.secondary }) - }) - msg.splice(-1, 1) // msg[msg.length - 1].text = '.' - bot.core.run(`/tellraw @a ${JSON.stringify(msg)}`) - break - default: - throw new Error('Missing or invalid argument') - } -} - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/help.js b/commands/help.js index 1296d80..32f2e64 100644 --- a/commands/help.js +++ b/commands/help.js @@ -1,4 +1,7 @@ -const { CommandDispatcher, builder: { LiteralArgumentBuilder: { literal }, RequiredArgumentBuilder: { argument } }, arguments: { StringArgumentType: { greedyString } } } = require('brigadier-commands') +const { literal, argument, greedyString, DynamicCommandExceptionType } = require('brigadier-commands') +const TextMessage = require('../util/command/text_message') + +const UNKNOWN_COMMAND_ERROR = new DynamicCommandExceptionType(command => new TextMessage(['Unknown command: ', command])) module.exports = { register (dispatcher) { @@ -60,6 +63,7 @@ module.exports = { const commandName = context.getArgument('command') let node = nodes.find(node => node.name === commandName) + if (!node) throw UNKNOWN_COMMAND_ERROR.create(commandName) if (node.redirect) node = node.redirect const aliases = [node, ...nodes.filter(_node => _node.redirect === node)].map(node => node.name) @@ -68,7 +72,7 @@ module.exports = { let msg if (usages.length !== 1) { msg = [ - { text: bot.prefix + node.name, color: bot.colors.primary }, + { text: bot.prefix + node.name, ...bot.styles.primary }, { text: ' (' + aliases.join(', ') + ')', color: 'white' }, { text: ` - ${node.description}\n`, color: 'gray' } ] @@ -76,18 +80,18 @@ module.exports = { msg.push(bot.prefix + node.name) msg.push({ text: ` ${usage}\n`, - color: bot.colors.secondary, + ...bot.styles.secondary, 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 + node.name, color: bot.colors.primary }, + { text: bot.prefix + node.name, ...bot.styles.primary }, { text: ' (' + aliases.join(', ') + ')', color: 'white' }, { text: ` ${usages[0]}`, - color: bot.colors.secondary, + ...bot.styles.secondary, clickEvent: { action: 'suggest_command', value: node.name + ' ' + usages[0] } }, { text: ` - ${node.description}`, color: 'gray' } diff --git a/commands/hole.js b/commands/hole.js index 6927480..4550b68 100644 --- a/commands/hole.js +++ b/commands/hole.js @@ -1,16 +1,28 @@ -const name = 'hole' -const description = 'Hole.' -const usage = '{prefix}hole ' -const aliases = ['hole'] -const enabled = true +const { literal, argument, greedyString } = require('brigadier-commands') +const { createNameSelector } = require('../util/command/utility') -const permLevel = 0 +module.exports = { + register (dispatcher) { + const node = dispatcher.register( + literal('hole') + .then( + argument('targets', greedyString()) + .executes(this.holeCommand) + ) + ) -function execute (bot, cmd, entity, args, handler) { - let selector = args.join(' ') - if (selector.includes(' ') && !selector.startsWith('@')) { selector = `@a[name="${selector.replace(/"/, '\\"')}"]` } + node.description = 'Creates a hole at the position of a players' + node.permissionLevel = 0 + }, - bot.core.run(`/execute at ${selector} run setblock ~ 1 ~ command_block{Command:'fill ~-1 0 ~-1 ~1 255 ~1 air destroy',auto:1}`) + holeCommand (context) { + const source = context.source + const bot = source.bot + + let selector = context.getArgument('targets') + const selector0 = selector[0] + if (selector0 !== '@' && selector0 !== '"' && selector0 !== "'" && selector.includes(' ')) selector = createNameSelector(selector) + + bot.core.run(`execute at ${selector} run setblock ~ 1 ~ command_block{Command:'fill ~-1 0 ~-1 ~1 255 ~1 air destroy',auto:1b}`) + } } - -module.exports = { name, description, usage, aliases, enabled, execute, permLevel } diff --git a/commands/image.js b/commands/image.js index 244816d..61e5d50 100644 --- a/commands/image.js +++ b/commands/image.js @@ -1,77 +1,78 @@ -const name = 'image' -const description = 'Renders an image.' -const usages = [ - 'render ', - 'list' -] -const aliases = ['image'] -const enabled = true - -const permLevel = 0 - +const { literal, argument, DynamicCommandExceptionType } = require('brigadier-commands') +const { location, path, isUrl } = require('../util/command/argument/location') +const TextMessage = require('../util/command/text_message') const fs = require('fs') -const convertImage = require('../util/convert-image.js') -const nbt = require('prismarine-nbt') -const SNBT = require('../util/snbt.js') -function execute (bot, cmd, player, args, handler) { - const subCmd = args.shift() +const IMAGE_DIRECTORY = 'images' +const FILE_DOES_NOT_EXIST_ERROR = new DynamicCommandExceptionType(filename => new TextMessage(['File ', filename, ' does not exist'])) - let src, files, primary, msg - switch (subCmd) { - case 'render': - src = args.join(' ').replace(/§.?/g, '') - if (/https?:\/\//.test(src)) { - bot.core.run(`/minecraft:tellraw @a ${JSON.stringify([ - { text: 'Attempting to convert image at ', color: bot.colors.primary }, - { text: src, color: bot.colors.secondary }, - '.' - ])}`) - } else { - src = `./images/${src}` +module.exports = { + register (dispatcher) { + const node = dispatcher.register( + literal('image') + .then( + literal('render') + .then( + argument('location', location(IMAGE_DIRECTORY)) + .executes(this.renderCommand) + ) + ) + .then( + literal('list') + .executes(this.listCommand.bind(this)) + .then( + argument('location', path(IMAGE_DIRECTORY)) + .executes(this.locationListCommand.bind(this)) + ) + ) + ) - if (!src.endsWith('.jpg')) { src += '.jpg' } + node.description = 'Renders images' + node.permissionLevel = 0 + }, - if (src.match(/\//g).length > 2) { throw new Error('Invalid image name.') } + renderCommand (context) { + const source = context.source + const bot = source.bot - if (!fs.existsSync(src)) { throw new Error('Invalid image name.') } + const src = context.getArgument('location') - bot.core.run(`/minecraft:tellraw @a ${JSON.stringify([ - { text: 'Attempting to convert image ', color: bot.colors.primary }, - { text: args.join(' ').replace(/§.?/g, ''), color: bot.colors.secondary }, - '.' - ])}`) + if (!isUrl(src) && !fs.existsSync(src)) throw FILE_DOES_NOT_EXIST_ERROR.create() + + source.sendFeedback([ + { text: 'Attempting to convert image ', ...bot.styles.primary }, + { text: src, ...bot.styles.secondary }, + ], true) + + convertImage(src, (err, lines) => { + if (err) { + source.sendError(err.toString(), false) + return } - convertImage(src, (err, lines) => { - if (err) { - bot.core.run(`minecraft:tellraw @a ${JSON.stringify({ text: err.message, color: bot.colors.error })}`) - return - } - lines.forEach((line, i) => { - bot.exploits.execute(`at ${player.UUID} run summon armor_stand ~ ~${(i * -0.05) + (lines.length * 0.05) - 0.3} ~ ${SNBT.stringify(nbt.comp({ CustomName: nbt.string(line), CustomNameVisible: nbt.byte(1), Invisible: nbt.byte(1), Marker: nbt.byte(1), Health: nbt.float(0), DeathTime: nbt.int(99999) }))}`) - if ((i + 1) >= lines.length) bot.core.run(`minecraft:tellraw @a ${JSON.stringify({ text: 'Finished rendering!', color: bot.colors.primary })}`) - }) + lines.forEach((line, i) => { + bot.exploits.execute(`at ${player.UUID} run summon armor_stand ~ ~${(i * -0.05) + (lines.length * 0.05) - 0.3} ~ ${SNBT.stringify(nbt.comp({ CustomName: nbt.string(line), CustomNameVisible: nbt.byte(1), Invisible: nbt.byte(1), Marker: nbt.byte(1), Health: nbt.float(0), DeathTime: nbt.int(99999) }))}`) + if ((i + 1) >= lines.length) source.sendFeedback({ text: 'Finished rendering!', ...bot.styles.primary }, true) }) - break - case 'list': - files = fs.readdirSync('./images') - files.filter((file) => file.endsWith('.jpg')) + }) + }, - primary = false - msg = [{ text: 'Images - ', color: 'gray' }] - files.forEach((file) => { - msg.push({ - text: `${file} `, - color: (((primary = !primary)) ? bot.colors.primary : bot.colors.secondary), - clickEvent: { action: 'run_command', value: `${bot.prefix}${name} render ${file}` }, - hoverEvent: { action: 'show_text', value: 'Click to render the image' } - }) - }) - bot.core.run(`/tellraw @a ${JSON.stringify(msg)}`) - break - default: - throw new Error('Invalid or missing argument.') + listCommand (context) { + this.listImages(context, IMAGE_DIRECTORY) + }, + + locationListCommand (context) { + this.listImages(context, context.getArgument('location')) + }, + + async listImages (context, path) { + const source = context.source + const bot = source.bot + + try { + const list = await bot.listFiles(path) + source.sendFeedback(['', { text: 'Images - ', ...bot.styles.primary }, ...list], false) + } catch (error) { + source.sendError(error.toString()) + } } } - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/kahoot.js b/commands/kahoot.js index 6421b8f..ced0f71 100644 --- a/commands/kahoot.js +++ b/commands/kahoot.js @@ -1,26 +1,48 @@ -const name = 'kahoot' -const description = 'kahoot client lol' -const usages = ['join ', 'leave', 'answer '] -const aliases = ['kahoot'] -const enabled = true +const { literal, argument, integer, greedyString } = require('brigadier-commands') -const permLevel = 0 +module.exports = { + register (dispatcher) { + const node = dispatcher.register( + literal('kahoot') + .then( + literal('join') + .then( + argument('pin', integer()) + .then( + argument('username', greedyString()) + .executes(this.joinCommand) + ) + ) + ) + .then( + literal('leave') + .executes(this.leaveCommand) + ) + .then( + literal('answer') + .then( + argument('answer', integer()) + .executes(this.answerCommand) + ) + ) + ) -function execute (bot, cmd, player, args, handler) { - const subCmd = args.shift() - switch (subCmd) { - case 'join': - bot.kahoot.join(parseFloat(args.shift()), args.join(' ')) - break - case 'leave': - bot.kahoot.leave() - break - case 'answer': - bot.kahoot.answer(parseFloat(args.join(' '))) - break - default: - throw new Error('Invalid or missing argument.') + node.description = 'kahoot client lol' + node.permissionLevel = 0 + }, + + joinCommand (context) { + const bot = context.source.bot + bot.kahoot.join(context.getArgument('pin'), context.getArgument('username')) + }, + + leaveCommand (context) { + const bot = context.source.bot + bot.kahoot.leave() + }, + + answerCommand (context) { + const bot = context.source.bot + bot.kahoot.answer(context.getArgument('answer')) } } - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/kbwl.js b/commands/kbwl.js deleted file mode 100644 index f50594b..0000000 --- a/commands/kbwl.js +++ /dev/null @@ -1,40 +0,0 @@ -const name = 'kbwl' -const description = 'white list' -const usages = ['on', 'off', 'add', 'remove', 'list'] -const aliases = ['kbwl'] -const enabled = true - -const permLevel = 1 - -function execute (bot, cmd, player, args, handler) { - const subCommand = args.shift() - const username = args.join(' ') - let i - switch (subCommand) { - case 'on': - bot.kbwl.players.push(player.name) - bot.kbwl.enabled = true - bot.core.run('bcraw &bKBWL is now on.') - break - case 'off': - bot.kbwl.enabled = false - bot.core.run('bcraw &bKBWL is now off.') - break - case 'add': - bot.kbwl.players.push(username) - bot.core.run(`bcraw &aAdded ${username} to the whitelist.`) - break - case 'remove': - i = bot.kbwl.players.indexOf(username) - if (i < 0) { return bot.core.run(`/bcraw &cThe player ${username} is not whitelisted!`) } - bot.kbwl.players.splice(1, i) - break - case 'list': - bot.core.run(`/tellraw @a ${JSON.stringify(`Whitelisted players: \n${bot.kbwl.players.join('\n')}`)}`) - break - default: - throw new Error('Invalid or missing argument.') - } -} - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/mail.js b/commands/mail.js index 76e7455..eecfac7 100644 --- a/commands/mail.js +++ b/commands/mail.js @@ -1,50 +1,84 @@ -const name = 'mail' -const description = 'Shows mail.' -const usages = ['send ', 'list', 'clear'] -const aliases = ['mail'] -const enabled = true +const { literal, argument, string, greedyString } = require('brigadier-commands') +const { createUuidSelector } = require('../util/command/utility') -const permLevel = 0 +module.exports = { + register (dispatcher) { + const listNode = literal('list').executes(this.listCommand).build() -const nbt = require('prismarine-nbt') -const SNBT = require('../util/snbt.js') -const toNBTUUID = require('../util/uuid-to-nbt-uuid.js') + const node = dispatcher.register( + literal('mail') + .then( + literal('send') + .then( + argument('username', string()) + .then( + argument('message', greedyString()) + .executes(this.sendCommand) + ) + ) + ) + .then( + listNode + ) + .then( + literal('read') + .executes(this.listCommand) + .redirect(listNode) + ) + .then( + literal('clear') + .executes(this.clearCommand) + ) + ) -function execute (bot, cmd, player, args, handler) { - const subCmd = args.shift() + node.description = 'Sends and receives mail from players' + node.permissionLevel = 0 + }, - let u, message, messages, msg - switch (subCmd) { - case 'send': - u = args.shift() - message = args.join(' ') - bot.sendMail(player.name, u, message) - bot.core.run(`minecraft:tellraw @a[nbt=${SNBT.stringify(nbt.comp({ UUID: toNBTUUID(player.UUID) }))}] ${JSON.stringify([ - { text: 'Sent ', color: bot.colors.primary }, - { text: message, color: bot.colors.secondary }, - ' to ', - { text: u, color: bot.colors.secondary }, - '.' - ])}`) - break - case 'list': - messages = bot.mail[player.name] - if (!messages || messages.length < 1) return bot.core.run(`minecraft:tellraw @a[nbt=${SNBT.stringify(nbt.comp({ UUID: toNBTUUID(player.UUID) }))}] ${JSON.stringify({ text: 'You have no mail', color: bot.colors.primary })}`) - msg = [{ text: 'Mail:\n', color: bot.colors.primary }] - messages.forEach((message) => { - msg.push(`${message.sender} (from ${message.host}): `) - msg.push({ text: `${message.message}\n`, color: bot.colors.secondary }) - }) - msg[msg.length - 1].text = msg[msg.length - 1].text.slice(0, -1) + sendCommand (context) { + const source = context.source + const bot = source.bot + const player = source.getPlayerOrThrow() - bot.core.run(`minecraft:tellraw @a[nbt=${SNBT.stringify(nbt.comp({ UUID: toNBTUUID(player.UUID) }))}] ${JSON.stringify(msg)}`) - break - case 'clear': - bot.mail[player.name] = [] - bot.core.run(`minecraft:tellraw @a[nbt=${SNBT.stringify(nbt.comp({ UUID: toNBTUUID(player.UUID) }))}] ${JSON.stringify([ - { text: 'Your mail has been cleared.', color: bot.colors.primary } - ])}`) + const username = context.getArgument('username') + const message = context.getArgument('message') + + bot.sendMail(player.username, username, message) + bot.tellraw([ + { text: 'Sent ', ...bot.styles.primary }, + { text: message, ...bot.styles.secondary }, + ' to ', + { text: username, ...bot.styles.secondary } + ], createUuidSelector(player.uuid)) + }, + + listCommand (context) { + const source = context.source + const bot = source.bot + const player = source.getPlayerOrThrow() + + const messages = bot.mail[player.username] + if (!messages || messages.length < 1) { + bot.tellraw({ text: 'You have no mail', ...bot.styles.primary }, createUuidSelector(player.uuid)) + return + } + + const msg = [{ text: 'Mail:\n', ...bot.styles.primary }] + messages.forEach((message) => { + msg.push(`${message.sender} (from ${message.host}): `) + msg.push({ text: `${message.message}\n`, ...bot.styles.secondary }) + }) + msg[msg.length - 1].text = msg[msg.length - 1].text.slice(0, -1) + + bot.tellraw(msg, createUuidSelector(player.uuid)) + }, + + clearCommand (context) { + const source = context.source + const bot = source.bot + const player = source.getPlayerOrThrow() + + delete bot.mail[player.username] + bot.tellraw({ text: 'Your mail has been cleared', ...bot.styles.primary }, createUuidSelector(player.uuid)) } } - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/matrix.js b/commands/matrix.js new file mode 100644 index 0000000..e0f391e --- /dev/null +++ b/commands/matrix.js @@ -0,0 +1,38 @@ +const { literal } = require('brigadier-commands') + +module.exports = { + register (dispatcher) { + const node = dispatcher.register( + literal('matrix') + .executes(this.matrixCommand) + ) + + node.description = 'Sends the Matrix invite' + node.permissionLevel = 0 + }, + + matrixCommand (context) { + const source = context.source + const bot = source.bot + + source.sendFeedback([ + { text: 'Join the ', color: 'gray' }, + { text: 'ChipmunkBot Matrix', ...bot.styles.primary }, + ' at ', + { + text: bot.matrix.inviteUrl, + ...bot.styles.secondary, + + hoverEvent: { + action: 'show_text', + contents: 'Click to copy the invite link to your clipboard!' + }, + clickEvent: { + action: 'copy_to_clipboard', // * Minecraft, and Java's URI class in general, seem to hate `#`, so open_url does not work. + value: bot.matrix.inviteUrl + } + }, + '!' + ], false) + } +} diff --git a/commands/music.js b/commands/music.js index dfa3860..b1557a0 100644 --- a/commands/music.js +++ b/commands/music.js @@ -1,95 +1,96 @@ -const name = 'music' -const description = 'Plays music' -const usages = [ - 'play ', - 'list', - 'skip', - 'stop' -] -const aliases = ['music'] -const enabled = true - -const permLevel = 0 - +const { literal, argument, DynamicCommandExceptionType } = require('brigadier-commands') +const { location, path, isUrl } = require('../util/command/argument/location') +const TextMessage = require('../util/command/text_message') const fs = require('fs') -const path = require('path') -const https = require('https') -function execute (bot, cmd, player, args, handler) { - const subCmd = args.shift() +const SONG_DIRECTORY = 'songs' +const FILE_DOES_NOT_EXIST_ERROR = new DynamicCommandExceptionType(filename => new TextMessage(['File ', filename, ' does not exist'])) - let filepath, file, files, primary, msg, split - switch (subCmd) { - case 'play': - filepath = args.join(' ').replace(/\xa7.?/g, '') - /* - if (/https?:\/\//.test(filepath)) { - https.get(filepath, (res) => { - // Open file in local filesystem - tmpobj = tmp.fileSync() - file = fs.createWriteStream(tmpobj.name) +module.exports = { + register (dispatcher) { + const node = dispatcher.register( + literal('music') + .then( + literal('play') + .then( + argument('location', location(SONG_DIRECTORY)) + .executes(this.playCommand) + ) + ) + .then( + literal('skip') + .executes(this.skipCommand) + ) + .then( + literal('stop') + .executes(this.stopCommand) + ) + .then( + literal('loop') + .executes(this.loopCommand) + ) + .then( + literal('list') + .executes(this.listCommand.bind(this)) + .then( + argument('location', path(SONG_DIRECTORY)) + .executes(this.locationListCommand.bind(this)) + ) + ) + ) - // Write data into local file - res.pipe(file) + node.description = 'Plays songs using note block sounds' + node.permissionLevel = 0 + }, - // Close the file - file.on('finish', () => { - file.close() - bot.music.queue.push(tmpobj.name) - }) - })// .on("error", (err) => { - // console.log("Error: ", err.message); - // }); - return - } - */ - filepath = path.join('music', filepath) - // if (!filepath.endsWith('.mid')) { filepath += '.mid' } + playCommand (context) { + const source = context.source + const bot = source.bot - split = filepath.split('/') - if (split[0] !== 'music') { throw new Error('geese') } + const filepath = context.getArgument('location') - if (!fs.existsSync(filepath)) { throw new Error('Invalid song name.') } + if (!isUrl(filepath) && !fs.existsSync(filepath)) throw FILE_DOES_NOT_EXIST_ERROR.create(filepath) - if (!bot.music.playing) { - bot.music.play(filepath) - } else { - bot.music.queue.push(filepath) - bot.core.run('minecraft:tellraw @a ' + JSON.stringify([ - { text: 'Added ', color: bot.colors.primary }, - { text: filepath.replace(/.+\//g, ''), color: bot.colors.secondary }, - ' to the music queue.' - ])) - } - break - case 'list': - files = fs.readdirSync('./music') - // files.filter((file) => file.endsWith('.mid')) + bot.music.queue.push(filepath) + source.sendFeedback([ + { text: 'Added ', ...bot.styles.primary }, + { text: filepath.replace(/.+\//g, ''), ...bot.styles.secondary }, + ' to the music queue.' + ], false) + }, - primary = false - msg = [{ text: 'Songs - ', color: 'gray' }] - files.forEach((file) => { - msg.push({ - text: `${file} `, - color: (((primary = !primary)) ? bot.colors.primary : bot.colors.secondary), - clickEvent: { action: 'run_command', value: `${bot.prefix}${name} play ${file}` }, - hoverEvent: { action: 'show_text', value: 'Click to play the song' } - }) - }) - bot.core.run(`/tellraw @a ${JSON.stringify(msg)}`) - break - case 'skip': - bot.music.skip() - break - case 'stop': - bot.music.stop() - break - case 'loop': - bot.music.looping = !bot.music.looping - break - default: - throw new Error('Invalid or missing argument.') + skipCommand (context) { + const bot = context.bot + bot.music.skip() + }, + + stopCommand (context) { + const bot = context.bot + bot.music.stop() + }, + + loopCommand (context) { + const bot = context.bot + bot.music.looping = !bot.music.looping + }, + + listCommand (context) { + this.listSongs(context, SONG_DIRECTORY) + }, + + locationListCommand (context) { + this.listSongs(context, context.getArgument('location')) + }, + + async listSongs (context, path) { + const source = context.source + const bot = source.bot + + try { + const list = await bot.listFiles(path) + source.sendFeedback(['', { text: 'Songs - ', ...bot.styles.primary }, ...list], false) + } catch (error) { + source.sendError(error.toString()) + } } } - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/myuser.js b/commands/myuser.js index ecd0003..08f0c98 100644 --- a/commands/myuser.js +++ b/commands/myuser.js @@ -1,17 +1,24 @@ -const name = 'myuser' -const description = 'Shows your username' -const usages = [] -const aliases = ['myuser'] -const enabled = true +const { literal } = require('brigadier-commands') -const permLevel = 0 +module.exports = { + register (dispatcher) { + const node = dispatcher.register( + literal('myuser') + .executes(this.myUserCommand) + ) -function execute (bot, cmd, player, args, handler) { - bot.core.run(`/tellraw @a ${JSON.stringify([ - { text: 'Your username is: ', color: bot.colors.primary }, - { text: player.name, color: bot.colors.secondary }, - '.' - ])}`) + node.description = 'Sends the username of the sender of the command' + node.permissionLevel = 0 + }, + + myUserCommand (context) { + const source = context.source + const bot = source.bot + const player = source.getPlayerOrThrow() + + source.sendFeedback([ + { text: 'Your username is: ', ...bot.styles.primary }, + { text: player.username, ...bot.styles.secondary } + ], false) + } } - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/netmsg.js b/commands/netmsg.js index 8d19cf1..17121c9 100755 --- a/commands/netmsg.js +++ b/commands/netmsg.js @@ -1,14 +1,25 @@ -const name = 'netmsg' -const description = 'Sends a message as each bot.' -const usages = [''] -const aliases = ['netmsg'] -const enabled = true +const { literal, argument, greedyString } = require('brigadier-commands') -const permLevel = 0 +module.exports = { + register (dispatcher) { + const node = dispatcher.register( + literal('netmsg') + .then( + argument('message', greedyString()) + .executes(this.netMsgCommand) + ) + ) -function execute (bot, cmd, player, args, handler) { - const host = bot.host - bot.getBots().forEach((bot) => bot.fancyMsg(host, player.name, args.join(' '))) + node.description = 'Runs a command in the command core' + node.permissionLevel = 0 + }, + + netMsgCommand (context) { + const source = context.source + const bot = source.bot + const message = context.getArgument('message') + + const host = bot.host + bot.bots.forEach((bot) => bot.fancyMsg(host, source.displayName, message)) + } } - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/pos.js b/commands/pos.js deleted file mode 100644 index ff40d4f..0000000 --- a/commands/pos.js +++ /dev/null @@ -1,25 +0,0 @@ -const name = 'pos' -const description = 'Gets the position of a player' -const usages = [''] -const aliases = ['pos'] -const enabled = true - -const permLevel = 0 - -function execute (bot, cmd, player, args) { - bot.getEntityPos(args.join(' '), (position) => { - const { x, y, z } = position - bot.core.run(`minecraft:tellraw @a ${JSON.stringify([ - { text: 'Position: ', color: bot.colors.primary }, - { - text: `[${x}, ${y}, ${z}]`, -color: bot.colors.secondary, - clickEvent: { action: 'run_command', value: `/essentials:tp ${x} ${y} ${z}` }, - hoverEvent: { action: 'show_text', value: 'Click to teleport' } - } - ])}`) - // bot.chatQueue.push(`&aPosition: &2${x} ${y} ${z}`) - }) -} - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/printcodes.js b/commands/printcodes.js deleted file mode 100644 index 52ca6d8..0000000 --- a/commands/printcodes.js +++ /dev/null @@ -1,14 +0,0 @@ -const name = 'printcodes' -const description = 'Prints permission codes to console.' -const usage = '{prefix}printcodes' -const aliases = ['printcodes'] -const enabled = true - -const permLevel = 0 - -function execute (bot, cmd, entity, args, handler) { - require('../cperms.js').printCodes() - bot.core.run('/bcraw &aPrinted codes to console.') -} - -module.exports = { name, description, usage, aliases, enabled, execute, permLevel } diff --git a/commands/rainbowify.js b/commands/rainbowify.js index 332011e..ca2a692 100644 --- a/commands/rainbowify.js +++ b/commands/rainbowify.js @@ -1,24 +1,31 @@ -const name = 'rainbowify' -const description = 'Makes text rainbow' -const usages = [''] -const aliases = ['rainbowify'] -const enabled = true - -const permLevel = 0 - +const { literal, argument, greedyString } = require('brigadier-commands') const colorsys = require('colorsys') -function execute (bot, cmd, player, args, handler) { - const message = args.join(' ') +module.exports = { + register (dispatcher) { + const node = dispatcher.register( + literal('rainbowify') + .then( + argument('message', greedyString()) + .executes(this.rainbowifyCommand) + ) + ) - const result = [] - let hue = 0 - message.split('').forEach((char) => { - result.push({ text: char, color: colorsys.hsv2Hex(hue, 100, 100) }) - hue += 355 / Math.max(message.length, 20) - }) + node.description = 'Makes text rainbow' + node.permissionLevel = 0 + }, - bot.core.run(`/tellraw @a ${JSON.stringify(result)}`) -} + rainbowifyCommand (context) { + const source = context.source + const message = context.getArgument('message') -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } + const result = [] + let hue = 0 + message.split('').forEach((char) => { + result.push({ text: char, color: colorsys.hsv2Hex(hue, 100, 100) }) + hue += 355 / Math.max(message.length, 20) + }) + + source.sendFeedback(result, false) + } +} \ No newline at end of file diff --git a/commands/randomteleport.js b/commands/randomteleport.js index b2ebb15..e551dd2 100644 --- a/commands/randomteleport.js +++ b/commands/randomteleport.js @@ -1,18 +1,27 @@ -const name = 'randomteleport' -const description = 'Teleports you to a random location.' -const usage = '{prefix}randomteleport' -const aliases = ['randomteleport', 'randomtele', 'randomtp', 'rtp'] -const enabled = true +const { literal } = require('brigadier-commands') -const permLevel = 0 +module.exports = { + register (dispatcher) { + const node = dispatcher.register( + literal('randomteleport') + .executes(this.randomTeleportCommand) + ) -function execute (bot, cmd, player, args, handler) { - bot.core.run(`/essentials:sudo ${player.UUID} c:/tppos ${randomInt(-30000000, 30000000)} 256 ${randomInt(-30000000, 30000000)}`) - // setTimeout(() => bot.core.run(`/essentials:sudo ${player.UUID} c:/top`), 100) + dispatcher.register(literal('rtp').executes(this.randomTeleportCommand).redirect(node)) + + node.description = 'Teleports the sender to a random location' + node.permissionLevel = 0 + }, + + randomTeleportCommand (context) { + const source = context.source + const bot = source.bot + const player = source.getPlayerOrThrow() + + bot.core.run(`tp ${player.uuid} ${randomInt(-30000000, 30000000)} 256 ${randomInt(-30000000, 30000000)}`) + } } function randomInt (min, max) { - return Math.floor((Math.random() * (max - min) + min) + 1) -} - -module.exports = { name, description, usage, aliases, enabled, execute, permLevel } + return Math.floor((Math.random() * (max - min)) + min) +} \ No newline at end of file diff --git a/commands/rc.js b/commands/rc.js index 1e83542..ccc2995 100644 --- a/commands/rc.js +++ b/commands/rc.js @@ -1,13 +1,18 @@ -const name = 'rc' -const description = 'Resets the bot\'s command core.' -const usages = [] -const aliases = ['rc'] -const enabled = true +const { literal } = require('brigadier-commands') -const permLevel = 0 +module.exports = { + register (dispatcher) { + const node = dispatcher.register( + literal('rc') + .executes(this.refillCoreCommand) + ) -function execute (bot, cmd, entity, args, handler) { - bot.core.reset() + node.description = "Refills the bot's command core" + node.permissionLevel = 0 + }, + + refillCoreCommand (context) { + const bot = context.source.bot + bot.core.reset() + } } - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/reload.js b/commands/reload.js index e355a4f..152a001 100644 --- a/commands/reload.js +++ b/commands/reload.js @@ -1,16 +1,21 @@ -const name = 'reload' -const description = 'Attempts to reload all commands.' -const usages = [] -const aliases = ['reload'] -const enabled = true +const { literal } = require('brigadier-commands') -const permLevel = 0 +module.exports = { + register (dispatcher) { + const node = dispatcher.register( + literal('reload') + .executes(this.reloadCommand) + ) -function execute (bot, cmd, player, args, handler) { - bot.core.run(`minecraft:tellraw @a ${JSON.stringify([ - { text: 'Reloading!', color: bot.colors.primary } - ])}`) - handler.reload() + node.description = 'Attempts to reload all commands' + node.permissionLevel = 0 + }, + + reloadCommand (context) { + const source = context.source + const bot = source.bot + + source.sendFeedback({ text: 'Reloading!', ...bot.styles.primary }, true) + bot.commands.reload() + } } - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/section.js b/commands/section.js deleted file mode 100755 index 096e2c0..0000000 --- a/commands/section.js +++ /dev/null @@ -1,13 +0,0 @@ -const name = 'section' -const description = 'sus ploit.' -const usages = [] -const aliases = ['section'] -const enabled = true - -const permLevel = 0 - -function execute (bot, cmd, entity, args, handler) { - bot.core.run(`/tellraw @a ${JSON.stringify({ text: 'Click here to get kicked!', underlined: true, clickEvent: { action: 'run_command', value: '/\u00a7' } })}`) -} - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/seen.js b/commands/seen.js index a179539..b9b7c39 100644 --- a/commands/seen.js +++ b/commands/seen.js @@ -1,24 +1,36 @@ -const name = 'seen' -const description = 'Shows when a player was first and last seen' -const usages = [] -const aliases = ['seen'] -const enabled = true +const { literal, argument, string, DynamicCommandExceptionType } = require('brigadier-commands') +const TextMessage = require('../util/command/text_message') +const NEVER_SEEN_ERROR = new DynamicCommandExceptionType(username => new TextMessage([username, ' was never seen'])) -const permLevel = 0 +module.exports = { + register (dispatcher) { + const node = dispatcher.register( + literal('seen') + .then( + argument('username', string()) + .executes(this.seenCommand) + ) + ) + + node.description = 'Shows when a player was first and last seen' + node.permissionLevel = 0 + }, + + seenCommand (context) { + const source = context.source + const bot = source.bot + + const username = context.getArgument('username') + if (bot.seen[username] == null) throw NEVER_SEEN_ERROR.create(username) -function execute (bot, cmd, player, args, handler) { - const username = args.join(' ') - if (bot.seen[username] != null) { const { first, last } = bot.seen[username] - bot.core.run('tellraw @a ' + JSON.stringify([ - { text: '', color: bot.colors.primary }, - { text: username, color: bot.colors.secondary }, + source.sendFeedback([ + { text: '', ...bot.styles.primary }, + { text: username, ...bot.styles.secondary }, ' was first seen on ', - { text: first, color: bot.colors.secondary }, + { text: first, ...bot.styles.secondary }, ' and last seen on ', - { text: last, color: bot.colors.secondary } - ])) - } else throw new Error(username + ' was never seen') + { text: last, ...bot.styles.secondary } + ], false) + } } - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/spawnmob.js b/commands/spawnmob.js index 50a89e7..96f4a16 100644 --- a/commands/spawnmob.js +++ b/commands/spawnmob.js @@ -1,20 +1,34 @@ -const name = 'spawnmob' -const description = 'but better' -const usages = [' [entity]'] -const aliases = ['spawnmob'] -const enabled = true +const { literal, argument, integer, string, greedyString } = require('brigadier-commands') +const nbt = require('prismarine-nbt') +const snbt = require('../util/snbt.js') -const permLevel = 0 +module.exports = { + register (dispatcher) { + const node = dispatcher.register( + literal('spawnmob') + .then( + argument('amount', integer()) + .then( + argument('entity', string()) + .executes(this.spawnMobCommand) + ) + ) + ) -function execute (bot, cmd, player, args, handler) { - const amount = parseInt(args.shift()) || 0 - const entity = args.shift() ?? 'pig' + node.description = 'Summons multiple entities of a specific type' + node.permissionLevel = 0 + }, - const arr = [] - while (arr.length < amount) { - arr.push(`{id:'${entity.replace(/'/g, '\\\'')}'}`) + spawnMobCommand (context) { + const source = context.source + const bot = source.bot + const player = source.getPlayerOrThrow() + + const amount = context.getArgument('amount') + const entity = context.getArgument('entity') + + const data = snbt.stringify(nbt.comp({ id: nbt.string(entity) })) + const passengers = Array(amount).fill(data) + bot.core.run(`execute at ${player.uuid} run setblock ~ ~-1 ~ command_block${snbt.stringify(nbt.comp({ auto: nbt.byte(1), Command: nbt.string(`summon area_effect_cloud ~ ~1 ~ {Passengers:[${passengers.join(',')}]}`) }))} destroy`) } - bot.exploits.execute(`at ${player.UUID} run summon area_effect_cloud ~ ~ ~ {Passengers:[${arr.join(',')}]}`) -} - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } +} \ No newline at end of file diff --git a/commands/teleport.js b/commands/teleport.js deleted file mode 100644 index fef3a09..0000000 --- a/commands/teleport.js +++ /dev/null @@ -1,13 +0,0 @@ -const name = 'teleport' -const description = 'halal tp command lol' -const usages = [''] -const aliases = ['teleport', 'tp'] -const enabled = true - -const permLevel = 0 - -function execute (bot, cmd, player, args, handler) { - bot.exploits.execute(`as ${player.UUID} at @s run teleport ${args.join(' ')}`) -} - -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } diff --git a/commands/urban.js b/commands/urban.js index 99ace9f..f3fa627 100755 --- a/commands/urban.js +++ b/commands/urban.js @@ -1,42 +1,73 @@ -const name = 'urban' -const description = 'Shows word definitions from the Urban Dictionary' -const usages = [''] -const aliases = ['urban'] -const enabled = true - -const permLevel = 0 - +const { literal, argument, greedyString } = require('brigadier-commands') const ud = require('urban-dictionary') -function execute (bot, cmd, player, args, handler) { - // Callback - ud.define(args.join(' ').replace(/§./, ''), (error, results) => { - if (error) { - bot.core.run(`/tellraw @a ${JSON.stringify([ - { text: error.message, color: bot.colors.error } - ])}`) - return - } +module.exports = { + register (dispatcher) { + const node = dispatcher.register( + literal('urban') + .then( + argument('word', greedyString()) + .executes(this.urbanCommand.bind(this)) + ) + ) + + node.description = 'Shows word definitions from the Urban Dictionary' + node.permissionLevel = 0 + }, + + async urbanCommand (context) { + const source = context.source + + const definitions = await ud.define(context.getArgument('word')) + .catch(error => source.sendError(error.toString())) + if (!definitions) return const msg = [{ text: '', color: 'gray' }] - results.forEach((result) => { - msg.push({ text: '[', color: 'dark_gray' }) - msg.push({ text: 'Urban', color: 'red' }) - msg.push({ text: '] ', color: 'dark_gray' }) - msg.push({ text: `${result.word} `, bold: true }) - const a = result.definition.replace(/\r\n?/g, '\n').split(/\[|\]/) - for (let i = 0; i < a.length; i += 2) { - msg.push({ text: a[i] }) - if (a[i + 1] != null) { - msg.push( - { text: a[i + 1], underlined: true, clickEvent: { action: 'suggest_command', value: `${bot.prefix}${name} ${a[i + 1]}` } } - ) - } - } - msg[msg.length - 1].text += '\n' - }) - bot.core.run(`minecraft:tellraw @a ${JSON.stringify(msg)}`) - }) -} + for (const definition of definitions) { + msg.push( + { text: '[', color: 'dark_gray' }, + { text: 'Urban', color: 'red' }, + { text: '] ', color: 'dark_gray' }, + { text: `${definition.word} `, bold: true }, + ...this.parseDefinitionText(definition.definition, source), + '\n' + ) + } -module.exports = { name, description, usages, aliases, enabled, execute, permLevel } + msg.pop() + source.sendFeedback(msg, false) + }, + + parseDefinitionText (text, source) { + const prefix = source.bot.prefix + const texts = [] + let string = '' + + for (let i = 0; i < text.length; i++) { + let c = text[i] + + if (c === '[') { + if (string) texts.push(string) + string = '' + let subword = '' + i++ + for (; i < text.length; i++) { + c = text[i] + if (c === ']') { + if (subword) texts.push({ text: subword, underlined: true, clickEvent: { action: 'suggest_command', value: prefix + 'urban ' + subword } }) + subword = '' + break + } + else subword += c + } + if (subword) texts.push({ text: subword, underlined: true, clickEvent: { action: 'suggest_command', value: prefix + 'urban ' + subword } }) + continue + } + + string += c + } + + if (string) texts.push(string) + return texts + } +} diff --git a/commands/validate.js b/commands/validate.js index 8395a1d..8b4771a 100644 --- a/commands/validate.js +++ b/commands/validate.js @@ -1,15 +1,21 @@ -const name = 'validate' -const description = 'Tests trusted code validation.' -const usage = '{prefix}validate' -const aliases = ['validate'] -const enabled = true +const { literal } = require('brigadier-commands') -const permLevel = 1 +module.exports = { + register (dispatcher) { + const node = dispatcher.register( + literal('validate') + .requires(source => source.permissionLevel >= 1) + .executes(this.validateCommand) + ) -function execute (bot, cmd, entity, args, handler) { - bot.core.run(`/tellraw @a ${JSON.stringify([ - { text: 'Valid code.', color: bot.colors.primary } - ])}`) + node.description = 'Checks if a hash is valid' + node.permissionLevel = 1 + }, + + validateCommand (context) { + const source = context.source + const bot = source.bot + + source.sendFeedback({ text: 'Valid hash', ...bot.styles.primary }, false) + } } - -module.exports = { name, description, usage, aliases, enabled, execute, permLevel } diff --git a/commands/video.js b/commands/video.js deleted file mode 100644 index 39b56d8..0000000 --- a/commands/video.js +++ /dev/null @@ -1,31 +0,0 @@ -const name = 'video' -const description = 'Plays videos' -const usages = ['play ', 'stop'] -const aliases = ['video'] -const enabled = true - -// const fs = require('fs') - -function execute (bot, cmd, entity, args, handler) { - const subCmd = args.shift().toLowerCase() - let filepath - switch (subCmd) { - case 'play': - filepath = args.join(' ').replace(/\u00a7.?/g, '') - // if (!fs.existsSync(filepath) || !fs.statSync(filepath).isFile()) throw new Error('Invalid filepath: '+filepath) - // client.util.writeChat({ text: 'Loading '+filepath }) - bot.video.play(filepath) - break - case 'stop': - bot.video.stop() - // client.util.writeChat({ text: 'Stopped video playback.' }) - break - // case 'nowplaing': - // client.util.writeChat({ text: 'Currently playing song: '+targetClient.util.music.nowPlaying }) - // break - default: - throw new Error('Invalid or missing argument') - } -} - -module.exports = { name, description, usages, aliases, enabled, permLevel: 0, execute } diff --git a/cperms2.js b/cperms2.js deleted file mode 100644 index 8f35c1e..0000000 --- a/cperms2.js +++ /dev/null @@ -1,17 +0,0 @@ -const secretNums = [null, -2.5, 6.9, 4.2069] - -function validate (level, username, code) { - for (let i = level; i < secretNums.length; i++) { - if (getCode(level, username) === code) { - return true - } - } - return false -} - -function getCode (level, username = ' ') { - const date = new Date() - return String.fromCharCode(Math.floor((date.getDate() + date.getMinutes() - date.getMonth()) / secretNums[level] * username.length * 69)) -} - -module.exports = { secretNums, validate, getCode } diff --git a/index.js b/index.js index c028848..c82400a 100644 --- a/index.js +++ b/index.js @@ -6,11 +6,12 @@ const moment = require('moment') const json5 = require('json5') const matrix = require('matrix-js-sdk') -if (!fs.existsSync('config.json5')) { - fs.copyFileSync(path.join(__dirname, 'default.json5'), 'config.json5') +const configPath = process.argv[2] ?? 'config.json5' +if (!fs.existsSync(configPath)) { + fs.copyFileSync(path.join(__dirname, 'default.json5'), configPath) console.info('No config file was found, so a default one was created.') } -const config = json5.parse(fs.readFileSync('config.json5', 'utf-8')) +const config = json5.parse(fs.readFileSync(configPath, 'utf-8')) const logdir = 'logs' if (!fs.existsSync(logdir)) fs.mkdirSync(logdir) @@ -40,15 +41,17 @@ for (const key in config.matrixClients) { client.startClient() } +const bots = [] for (const options of config.bots) { const mergedOptions = { ...(config.all ?? {}), ...options } if (mergedOptions.matrix && typeof mergedOptions.matrix.client !== 'object') mergedOptions.matrix.client = matrixClients[mergedOptions.matrix.client] const bot = createBot(mergedOptions) + bots.push(bot) + bot.bots = bots bot.on('error', console.error) bot.console.filepath = logfile bot.console.setRl(rl) - bot.commands.loadFromDir('commands') } diff --git a/package-lock.json b/package-lock.json index cd30859..9c0a8d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -561,7 +561,7 @@ }, "node_modules/brigadier-commands": { "version": "1.0.0", - "resolved": "git+https://code.chipmunk.land/ChipmunkMC/node-brigadier-commands.git#c89271d021a1537d3045a93850e0c7ccb6efd9ae" + "resolved": "git+https://code.chipmunk.land/ChipmunkMC/node-brigadier-commands.git#0fcbb5cc6416e9cfe3d045f7d008904eec5da386" }, "node_modules/bs58": { "version": "5.0.0", @@ -4176,7 +4176,7 @@ } }, "brigadier-commands": { - "version": "git+https://code.chipmunk.land/ChipmunkMC/node-brigadier-commands.git#c89271d021a1537d3045a93850e0c7ccb6efd9ae", + "version": "git+https://code.chipmunk.land/ChipmunkMC/node-brigadier-commands.git#0fcbb5cc6416e9cfe3d045f7d008904eec5da386", "from": "brigadier-commands@git+https://code.chipmunk.land/ChipmunkMC/node-brigadier-commands.git" }, "bs58": { diff --git a/plugins/console.js b/plugins/!console.js similarity index 91% rename from plugins/console.js rename to plugins/!console.js index 3af9dc6..70145ff 100644 --- a/plugins/console.js +++ b/plugins/!console.js @@ -26,8 +26,8 @@ function inject (bot) { } function _log (prefix, stdout, data) { // format it - const _prefix = `[${formatDate()} ${prefix}\u00a7r] ` - const stringifyOptions = { lang: bot.registry.language } + const _prefix = `[${formatDate()} ${prefix}\u00a7r] [${bot.host}] ` + const stringifyOptions = { lang: bot.registry?.language ?? {} } const formattedData = _prefix + colorCodeStringify(data, stringifyOptions) + '\n' const ansi = ansiStringify(_prefix, stringifyOptions) + ansiStringify(data, stringifyOptions) + '\x1b[0m\n' @@ -39,7 +39,7 @@ function inject (bot) { if (err) console.error(err) }) } -bot.tellraw(formattedData, '_ChipMC_') + // log to stdout stdout.write(ansi + '') } @@ -61,7 +61,7 @@ bot.tellraw(formattedData, '_ChipMC_') function handleLine (line) { if (bot.host !== bot.console.host && bot.console.host !== 'all') return if (line.startsWith('.')) { - const source = new CommandSource({ bot, permissionLevel: Infinity, sendFeedback }) + const source = new CommandSource({ bot, permissionLevel: Infinity, sendFeedback, displayName: 'console' }) bot.commands.execute(line.substring(1), source) } else { bot.fancyMsg('test', '_ChipMC_', line) diff --git a/plugins/chat.js b/plugins/chat.js index b0ce6a1..2d2e08b 100644 --- a/plugins/chat.js +++ b/plugins/chat.js @@ -61,13 +61,18 @@ function inject (bot) { bot.on('packet.player_chat', (packet) => { const plain = packet.plainMessage const unsigned = parseNbtText(packet.unsignedChatContent) - const sender = bot.players.find(player => player.uuid === packet.senderUuid) + let sender = bot.players.find(player => player.uuid === packet.senderUuid) const type = bot.registry?.chatFormattingById[packet.type] + if (!sender) { + bot.console.warn(`Unable to find player_chat sender in player list (uuid: ${packet.senderUuid})`) + sender = undefined + } + bot.emit('player_chat', { plain, unsigned, sender, type: type.name }) bot.emit('chat', unsigned) - tryParsingMessage(unsigned, { senderUuid: sender.uuid, players: bot.players, lang: bot.registry.language, plain }) + tryParsingMessage(unsigned, { senderUuid: sender?.uuid, players: bot.players, lang: bot.registry.language, plain }) }) bot.on('packet.system_chat', (packet) => { diff --git a/plugins/commands.js b/plugins/commands.js index ee30c63..5265865 100644 --- a/plugins/commands.js +++ b/plugins/commands.js @@ -1,20 +1,33 @@ 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 { 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 = [] + + fs.readdirSync('commands').forEach(filename => { + const filepath = path.resolve('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() + function inject (bot) { bot.commands = { - dispatcher: new CommandDispatcher(), - add, + dispatcher: null, execute, - info, - isCommand, - loadFromDir, - isValid + reload, } bot.on('message', ({ sender, plain }) => { @@ -23,75 +36,23 @@ function inject (bot) { 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 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 } - } - - 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 + }, + text: sender.username } - 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 } + bot.commands.execute(plain.substring(bot.prefix.length), new CommandSource({ bot, player: sender, sendFeedback, displayName })) + }) function execute (command, source) { try { @@ -134,18 +95,22 @@ function inject (bot) { 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' + function reload (loadFiles = true) { + bot.commands.dispatcher = new CommandDispatcher() + if (loadFiles) loadCommands() + + for (const command of commands) { + try { + command.register(bot.commands.dispatcher) + } catch (error) { + console.error('Unable to register command:', error) + } + } + + bot.emit('commands_loaded') + } + reload(false) } module.exports = inject diff --git a/plugins/core.js b/plugins/core.js index 79841be..c155629 100644 --- a/plugins/core.js +++ b/plugins/core.js @@ -87,7 +87,7 @@ function inject (bot) { bot.core.run(`minecraft:setblock ${Math.floor(oldPos.x)} ${Math.floor(oldPos.y - 1)} ${Math.floor(oldPos.z)} minecraft:air replace mincecraft:command:block`) // Clean up after refills bot.core.reset() }) - setInterval(() => bot.core.refill(), 60 * 1000) + setInterval(() => bot.core.reset(), 60 * 1000) } module.exports = inject diff --git a/plugins/fancyMsg.js b/plugins/fancyMsg.js index 65c71a0..55708a7 100644 --- a/plugins/fancyMsg.js +++ b/plugins/fancyMsg.js @@ -5,7 +5,7 @@ function inject (bot) { { text: '[', color: 'dark_gray' }, rank, { text: '] ', color: 'dark_gray' }, - [{ text: '', color: bot.colors.secondary }, username], + [{ text: '', ...bot.styles.secondary }, username], { text: ' › ', color: 'dark_gray' }, message ], '@a') diff --git a/plugins/matrix.js b/plugins/matrix.js index e11ee25..46e000e 100644 --- a/plugins/matrix.js +++ b/plugins/matrix.js @@ -14,7 +14,7 @@ function inject (bot, options) { const startTime = Date.now() - bot.on('chat_html', async html => { + bot.on('chat_html', html => { sendMessage(html) }) @@ -25,7 +25,7 @@ function inject (bot, options) { contents: 'Click to copy the invite link for the Matrix space to your clipboard!' }, clickEvent: { - action: 'copy_to_clipboard', // * Minecraft, and Java's URL class in general, seem to hate `#`, so open_url does not work. + action: 'copy_to_clipboard', // * Minecraft, and Java's URI class in general, seem to hate `#`, so open_url does not work. value: bot.matrix.inviteUrl } } @@ -37,22 +37,6 @@ function inject (bot, options) { const permissionLevel = event.sender.powerLevelNorm let message = content.body - if (content.url) { - message = { - text: '[Attachment]', - color: bot.colors.primary, - clickEvent: { - action: 'open_url', - value: bot.matrix.client.mxcUrlToHttp(content.url) - } - } - } else if (message.startsWith(bot.matrix.commandPrefix)) { - const source = new CommandSource({ bot, permissionLevel, sendFeedback }) - bot.commands.execute(message.substring(bot.matrix.commandPrefix.length), source) - - return - } - const senderText = { text: String(event.sender.rawDisplayName || event.sender.name || event.sender.userId), hoverEvent: { @@ -64,6 +48,23 @@ function inject (bot, options) { value: String(event.sender.userId) } } + + if (content.url) { + message = { + text: '[Attachment]', + ...bot.styles.primary, + clickEvent: { + action: 'open_url', + value: bot.matrix.client.mxcUrlToHttp(content.url) + } + } + } else if (message.startsWith(bot.matrix.commandPrefix)) { + const source = new CommandSource({ bot, permissionLevel, sendFeedback, displayName: senderText }) + bot.commands.execute(message.substring(bot.matrix.commandPrefix.length), source) + + return + } + bot.fancyMsg(matrixPrefix, senderText, message) }) diff --git a/plugins/music.js b/plugins/music.js index 873d285..dbbb4a9 100644 --- a/plugins/music.js +++ b/plugins/music.js @@ -27,9 +27,9 @@ function inject (bot) { setInterval(() => { if (!bot.music.playing) return const msg = [ - { text: 'Now Playing', color: bot.colors.primary }, + { text: 'Now Playing', ...bot.styles.primary }, { text: ' | ', color: 'dark_gray' }, - { text: bot.music.nowPlaying.name, color: bot.colors.secondary, bold: true }, + { text: bot.music.nowPlaying.name, ...bot.styles.secondary, bold: true }, { text: ' | ', color: 'dark_gray' }, format(bot.music.nowPlaying.time), { text: ' / ', color: 'gray' }, @@ -40,7 +40,7 @@ function inject (bot) { ] if (bot.music.looping) { msg.push({ text: ' | ', color: 'dark_gray' }) - msg.push({ text: 'Looping', color: bot.colors.secondary }) + msg.push({ text: 'Looping', ...bot.styles.secondary }) } bot.core.run('/title @a actionbar ' + JSON.stringify(msg)) }, 500) @@ -72,14 +72,14 @@ function inject (bot) { song.time = 0 bot.music.nowPlaying = song } catch (err) { - bot.core.run('minecraft:tellraw @a ' + JSON.stringify({ text: err.message, color: bot.colors.error })) + bot.core.run('minecraft:tellraw @a ' + JSON.stringify({ text: err.message, ...bot.styles.error })) return } // play the music lol bot.core.run(`/tellraw @a ${JSON.stringify([ - { text: 'Now playing ', color: bot.colors.primary }, - { text: song.name, color: bot.colors.secondary }, + { text: 'Now playing ', ...bot.styles.primary }, + { text: song.name, ...bot.styles.secondary }, '.' ])}`) bot.music.playing = true diff --git a/plugins/seen.js b/plugins/seen.js index ca8265c..0c653aa 100644 --- a/plugins/seen.js +++ b/plugins/seen.js @@ -27,8 +27,8 @@ function inject (bot) { if (seen[player.username].first == null) { seen[player.username].first = new Date() bot.core.run('minecraft:tellraw @a ' + JSON.stringify([ - { text: 'Welcome ', color: bot.colors.primary }, - { text: player.username, color: bot.colors.secondary }, + { text: 'Welcome ', ...bot.styles.primary }, + { text: player.username, ...bot.styles.secondary }, ' to the server!' ])) } diff --git a/plugins/tellraw.js b/plugins/tellraw.js deleted file mode 100644 index 5bbff9f..0000000 --- a/plugins/tellraw.js +++ /dev/null @@ -1,9 +0,0 @@ -function inject (bot) { - function tellraw (text, target = '@a') { - bot.core.run(`minecraft:tellraw ${target} ${JSON.stringify(text)}`) - } - - bot.tellraw = tellraw -} - -module.exports = inject diff --git a/plugins/utility.js b/plugins/utility.js new file mode 100644 index 0000000..772d0d0 --- /dev/null +++ b/plugins/utility.js @@ -0,0 +1,30 @@ +const fs = require('fs/promises') + +function inject (bot) { + function tellraw (text, target = '@a') { + bot.core.run(`minecraft:tellraw ${target} ${JSON.stringify(text)}`) + } + + async function listFiles (filepath, styling = {}) { + const list = await fs.readdir(filepath) + const msg = [] + + list.forEach((filename, idx) => { + if (idx !== 0) msg.push(' ') + const highlighting = !(idx & 1) ? bot.styles.secondary : bot.styles.primary + + msg.push({ + text: filename, + ...highlighting, + ...styling + }) + }) + + return msg + } + + bot.tellraw = tellraw + bot.listFiles = listFiles +} + +module.exports = inject diff --git a/songs/file 1 b/songs/file 1 new file mode 100644 index 0000000..e69de29 diff --git a/songs/file 2 b/songs/file 2 new file mode 100644 index 0000000..e69de29 diff --git a/songs/file 3 b/songs/file 3 new file mode 100644 index 0000000..e69de29 diff --git a/songs/file 4 b/songs/file 4 new file mode 100644 index 0000000..e69de29 diff --git a/songs/file 5 b/songs/file 5 new file mode 100644 index 0000000..e69de29 diff --git a/songs/file 6 b/songs/file 6 new file mode 100644 index 0000000..e69de29 diff --git a/songs/file 7 b/songs/file 7 new file mode 100644 index 0000000..e69de29 diff --git a/songs/file 8 b/songs/file 8 new file mode 100644 index 0000000..e69de29 diff --git a/songs/file 9 b/songs/file 9 new file mode 100644 index 0000000..e69de29 diff --git a/util/chat/stringify/html.js b/util/chat/stringify/html.js index acfbdad..518ede5 100644 --- a/util/chat/stringify/html.js +++ b/util/chat/stringify/html.js @@ -51,7 +51,7 @@ function htmlStringify (text, { lang = {} } = {}) { if (text.color) { const rgb = text.color[0] === '#' ? parseInt(text.color.substring(1), 16) : colormap[text.color] - if (rgb) string = `${string}` + if (rgb) string = `${string}` } // formatting @@ -80,7 +80,7 @@ function preprocessText (input) { const hex = colorcodemap[code] if (hex) { string += closing - string += `` + string += `` closing = '' continue } diff --git a/util/chat/utility.js b/util/chat/utility.js index 31d0a0e..ec64516 100644 --- a/util/chat/utility.js +++ b/util/chat/utility.js @@ -4,14 +4,23 @@ const formatNames = Object.fromEntries(formatting.map(format => [format.name, tr const baseColors = colors.map(color => intToRgb(color.rgb)) function parseJsonText (json) { - return JSON.parse(json) + try { + return JSON.parse(json) + } catch { + return { text: '' } + } } function parseNbtText (data) { - if (typeof data.value !== 'object') return data.value - if (Array.isArray(data.value)) return [...data.value] - if (data.type === 'list') return data.value.value.map(value => parseNbtText({ value })) - return Object.fromEntries(Object.entries(data.value).map(([key, value]) => ([key === '' ? 'text' : key, parseNbtText(value)]))) + try { + if (data.type === 'byte') return !!data.value + if (typeof data.value !== 'object') return data.value + if (Array.isArray(data.value)) return [...data.value] + if (data.type === 'list') return data.value.value.map(value => parseNbtText({ type: data.value.type, value })) + return Object.fromEntries(Object.entries(data.value).map(([key, value]) => ([key === '' ? 'text' : key, parseNbtText(value)]))) + } catch { + return { text: '' } + } } function normalize (text) { diff --git a/util/command/argument/json.js b/util/command/argument/json.js new file mode 100644 index 0000000..3295cf5 --- /dev/null +++ b/util/command/argument/json.js @@ -0,0 +1,48 @@ +const { ArgumentType } = require('brigadier-commands') + +const EXAMPLES = [123, -0.2, 'Hello, world!', true, false, { text: 'Hello, world!' }].map(v => JSON.stringify(v)) + +class JSONArgumentType extends ArgumentType { + static json () { + return new JSONArgumentType() + } + + parse (reader) { + let string = '' + let depth = 0 + let stringOpened = false + + while (reader.canRead() && (depth !== 0 || reader.peek() !== ' ')) { + const c = reader.peek() + + if (c === '\\') { + // Skip over escapes + let len = 2 + if (reader.string[reader.cursor + 1] === 'u') len += 4 + len = Math.max(len, reader.string.length - reader.cursor) + + string += reader.string.substring(reader.cursor, reader.cursor + len) + reader.cursor += len + continue + } + + if (c === '[' || c === '{') depth++ + else if (c === ']' || c === '}') depth-- + else if (c === '"') { + depth += stringOpened ? -1 : 1 + stringOpened = !stringOpened + } + + string += c + reader.skip() + } + + return JSON.parse(string) + } + + getExamples () { + return EXAMPLES + } +} + +module.exports = JSONArgumentType diff --git a/util/command/argument/location.js b/util/command/argument/location.js new file mode 100644 index 0000000..f7fedd4 --- /dev/null +++ b/util/command/argument/location.js @@ -0,0 +1,63 @@ +const { ArgumentType, SimpleCommandExceptionType, LiteralMessage } = require('brigadier-commands') +const path = require('path/posix') + +const EXAMPLES = ['songs/amogus.mid', 'images/cat.jpg', 'videos/badapple.mp4'] +const INVALID_URL_EXCEPTION = new SimpleCommandExceptionType(new LiteralMessage('Invalid URL')) + +class LocationArgumentType extends ArgumentType { + constructor (allowPaths, allowUrls, root = '') { + super() + this.allowPaths = allowPaths + this.allowUrls = allowUrls + this.root = root + } + + static location (root = '') { return new LocationArgumentType(true, true, root) } + static path (root = '') { return new LocationArgumentType(true, false, root) } + static url () { return new LocationArgumentType(false, true) } + + parse (reader) { + const string = reader.readString() + + if (this.allowUrls) { + if (string.startsWith('http://') || string.startsWith('https://')) { + return string + } + // TODO: Maybe support data URLs? + } + + if (!this.allowPaths) { + throw INVALID_URL_EXCEPTION.create() + } + + const root = path.resolve(this.root) + + const splitPath = string.split(path.sep) + const sanitizedSplitPath = [] + let depth = 0 + for (const part of splitPath) { + if (part === '' || part === '.') continue + + if (part === '..') { + if ((depth - 1) < 0) continue // Do not allow escaping the root directory + depth-- + } else { + depth++ + } + + sanitizedSplitPath.push(part) + } + + return [root, ...sanitizedSplitPath].join(path.sep) + } + + static isUrl (string) { + return string.startsWith('http://') || string.startsWith('https://') + } + + getExamples () { + return EXAMPLES + } +} + +module.exports = LocationArgumentType diff --git a/util/command/command_source.js b/util/command/command_source.js index 4635bee..accaaa9 100644 --- a/util/command/command_source.js +++ b/util/command/command_source.js @@ -1,13 +1,14 @@ -const { exceptions: { SimpleCommandExceptionType }, LiteralMessage } = require('brigadier-commands') +const { SimpleCommandExceptionType, LiteralMessage } = require('brigadier-commands') const COMMAND_MUST_BE_EXECUTED_BY_A_PLAYER_EXCEPTION = new SimpleCommandExceptionType(new LiteralMessage('Command must be executed by a player')) // TODO: Translations class CommandSource { - constructor ({ bot, player = null, permissionLevel = 0, sendFeedback = () => {} } = {}) { + constructor ({ bot, player = null, permissionLevel = 0, sendFeedback = () => {}, displayName = { text: '' } } = {}) { this.bot = bot this.player = player this.permissionLevel = permissionLevel this.sendFeedback = sendFeedback + this.displayName = displayName } sendError (error) { diff --git a/util/command/utility.js b/util/command/utility.js new file mode 100644 index 0000000..266cb7e --- /dev/null +++ b/util/command/utility.js @@ -0,0 +1,26 @@ +const nbt = require('prismarine-nbt') +const snbt = require('../snbt.js') +const toNbtUuid = require('../uuid-to-nbt-uuid.js') + +function escapeString (input) { + let string = '"' + + for (let i = 0; i < input.length; i++) { + const c = input[i] + if (c === '\\' || c === '"') string += '\\' + c + else string += c + } + + string += '"' + return string +} + +function createNameSelector (username) { + return `@a[limit=1,name=${escapeString(username)}]` +} + +function createUuidSelector (uuid) { + return `@a[limit=1,nbt=${snbt.stringify(nbt.comp({ UUID: toNbtUuid(uuid) }))}]` +} + +module.exports = { escapeString, createNameSelector, createUuidSelector } diff --git a/util/convert-image.js b/util/convert-image.js index 385cee1..25c383c 100644 --- a/util/convert-image.js +++ b/util/convert-image.js @@ -6,7 +6,7 @@ const colorsys = require('colorsys') async function convertImage (src, callback) { const img = await canvas.loadImage(src).catch(callback) - if (!(img ?? false)) return + if (!img) return ctx.drawImage(img, 0, 0, cnv.width, cnv.height) const rgba = ctx.getImageData(0, 0, cnv.width, cnv.height).data const lines = []