๐ป Commands
Hey there ๐! This guide is here to give you a fast understanding of how nyx's command system works, so you can use it right away. For in-depth details, see the respective guides of each command related object.
๐ Descriptionโ
The command system in nyx is made up of several objects that work together to receive commands from users and routing
said execution to the correspondent command object, all coordinated by a CommandManager
.
Specifically, the command related objects are:
๐ผ CommandManager
: The entry point for the command system, holding all the command-related objects and methods that use all of these objects. All objects below are contained here.๐ฌ CommandCustomIdCodec
: De/serializes commands names to/from customId strings, useful for creating message components that will trigger commands.๐ CommandResolver
: Resolves the command that a given command interaction refers to.โก CommandExecutor
: Executes commands, checking itsCommandMiddlewareList
and passing errors to the๐ซ ErrorHandler
.๐ CommandRepository
: Stores all the currently registered commands.๐ช CommandDeployer
: Deploys commands to Discord and stores the ApplicationCommand mappings.๐ CommandSubscriptionsContainer
: Stores the ๐ฉ Event Subscribers that are subscribed to the ๐ Client event bus to listen for command interactions.๐ฃ EventBus
: An ๐ฃ Event Bus that emits command related events.
As well as the actual commands:
๐ Standalone Command
: A slash command with no children.๐งพ Context Menu Command
: A context menu command (either User or Message).๏ธ๐จโ๐งโ๐ฆ Parent Command
: A non-executable command that only serves to store subcommands or subcommand groups.๐งฉ SubCommand
: An executable command inside aParentCommand
. Can only be executed via a slash command.๐๏ธ SubCommand Group
: An non-executable command inside aParentCommand
that only serves to store subcommands.
๐ข Slash command execution sequenceโ
- A dashed step means it's executed asynchronously, so the next one is inmediately executed.
- You can hover over steps with
(?)
to see extra details.
๐ Creating a commandโ
The overall process of creating a command is to instantiate and register it to a CommandManager
.
If the bot has started, it will immediately be deployed to Discord, and its ApplicationCommand
mapping will be
available on the CommandDeployer
. If the bot hasn't started, it will be queued to be deployed once it starts if
deployCommands
is true.
Aditionally, commands don't depend on a specific bot, and you can reuse the same instance for many.
By default, commands are registered using ApplicationCommandManager#set()
. This means that commands deployed to
Discord that are no longer registered will be deleted from there.
Make sure to register all your commands before starting the bot to minimize the amount of API calls, since registering them after the bot has started will cause another API call.
โจ Quick examplesโ
- Creating a Ping Command
- Creating a ContextMenuCommand
- Creating a SubCommand
- Creating a SubCommandGroup
- Autocompletion
- Extend
AbstractStandaloneCommand
, implementingexecute()
andcreateData()
. - Register it to a bot's
CommandManager
.
class PingCommand extends AbstractStandaloneCommand {
protected createData() {
return new SlashCommandBuilder()
.setName('ping')
.setDescription('Pong!');
}
public async execute(interaction: ChatInputCommandInteraction) {
await interaction.reply('Pong!');
}
}
const command = new PingCommand();
await bot.getCommandManager().addCommands(command);
- Extend
AbstractContextMenuCommand
, implementingcreateData()
. - Implement
executeUser()
if you're making a User Context Menu, orexecuteMessage()
if you're making a Message Context Menu. - Register it to a bot's
CommandManager
.
- User Context Menu:
class NameContextMenuCommand extends AbstractContextMenuCommand {
protected createData() {
return new ContextMenuCommandBuilder()
.setName('name')
.setType(ApplicationCommandType.User);
}
public async executeUser(interaction: UserContextMenuCommandInteraction) {
const user = interaction.targetUser;
await interaction.reply(`This is ${user.username}`);
}
}
const command = new NameContextMenuCommand();
await bot.getCommandManager().addCommands(command);
- Message Context Menu:
class MessageContentContextMenuCommand extends AbstractContextMenuCommand {
protected createData() {
return new ContextMenuCommandBuilder()
.setName('content')
.setType(ApplicationCommandType.Message);
}
public async executeMessage(interaction: MessageContextMenuCommandInteraction) {
const message = interaction.targetMessage;
await interaction.reply(`This message's content is ${message.content}`);
}
}
const command = new MessageContentContextMenuCommand();
await bot.getCommandManager().addCommands(command);
There won't be any type errors if you don't implement the correspondent execute method for your command (like if you
don't implement executeUser()
with ApplicationCommandType.User
), but you'll get a NotImplementedError
once the
command is actually executed, which will be handled by the ErrorHandler
.
- Extend
AbstractParentCommand
, implementingchildren
andcreateData()
. This will be the command containing the subcommand. - Extend
AbstractSubCommand
, implementingexecute()
andcreateData()
. - Register the parent command to a bot's
CommandManager
.
Do not add the subcommand as an option inside the SlashCommandBuilder
. The serialization will do that for you, and
will actually throw an AssertionError
if you do that.
class UserParentCommand extends AbstractParentCommand {
protected createData() {
return new SlashCommandBuilder()
.setName('user')
.setDescription('User-related commands');
}
protected children = [
new NameSubCommand(this),
]
}
class NameSubCommand extends AbstractSubCommand {
protected createData() {
return new SlashCommandSubcommandBuilder()
.setName('name')
.setDescription('Returns your name.');
}
public async execute(interaction: ChatInputCommandInteraction) {
await interaction.reply(interaction.user.username);
}
}
const parent = new UserParentCommand();
await bot.getCommandManager().addCommands(parent);
Instead of implementing children
, you could also add the subcommand with #addChildren()
:
// Same classes as above but parent doesn't implement children:
const parent = new UserParentCommand();
const subCommand = new NameSubCommand(group);
group.addChildren(subCommand);
await bot.getCommandManager().addCommands(parent);
- Extend
AbstractParentCommand
, implementingchildren
andcreateData()
. This will be the command containing the subcommand group. - Extend
AbstractSubCommandGroup
, implementingcreateData()
. - Extend
AbstractSubCommand
, implementingexecute()
andcreateData()
. - Register the parent command to a bot's
CommandManager
.
Do not add the subcommand group as an option inside the SlashCommandBuilder
or the subcommand inside the
SlashCommandSubcommandGroupBuilder
. The serialization will do that for you, and will actually throw an AssertionError
if you do that.
// Create the ParentCommand
class PhotoParentCommand extends AbstractParentCommand {
protected createData() {
return new SlashCommandBuilder()
.setName('photo')
.setDescription('See photos of various things');
}
protected children = [
new AnimalPhotoSubCommandGroup(this),
]
}
// Create the SubCommandGroup
class AnimalPhotoSubCommandGroup extends AbstractSubCommandGroup {
protected createData() {
return new SlashCommandSubcommandGroupBuilder()
.setName('animal')
.setDescription('See animal-related photos');
}
protected children = [
new DogSubCommand(this),
]
}
// Create the SubCommand
class DogSubCommand extends AbstractSubCommand {
protected createData() {
return new SlashCommandSubcommandBuilder()
.setName('dog')
.setDescription('See a random photo of a dog.');
}
public async execute(interaction: ChatInputCommandInteraction) {
const photo = await getRandomDogPhoto();
await interaction.reply(photo);
}
}
const parent = new PhotoParentCommand();
await bot.getCommandManager().addCommands(parent);
Instead of implementing children
, you could also add the group (and/or the subcommand) with #addChildren()
:
// Same classes as above but parent and group don't implement children:
const parent = new PhotoParentCommand();
// You can add the group first and then the subcommand or the way around,
// order doesn't matter.
const group = new AnimalPhotoSubCommandGroup(parent);
parent.addChildren(group);
const subCommand = new DogSubCommand(group);
group.addChildren(subCommand);
await bot.getCommandManager().addCommands(parent);
From your executable command (a StandaloneCommand
or SubCommand
) implement autocomplete()
.
class AutocompletableCommand extends AbstractStandaloneCommand {
public async autocomplete(interaction: AutocompleteInteraction) {
await interaction.respond([
{ name: 'foo', value: 'bar' },
]);
}
}
๐ Next...โ
Check the documentation of each command type:
๐ Standalone Command
: A slash command with no children.๐ค ContextMenu Command
: A context menu command (either User or Message).๏ธ๐จโ๐งโ๐ฆ Parent Command
: A non-executable command that only serves to store subcommands or subcommand groups.๐งฉ SubCommand
: An executable command inside aParentCommand
. Can only be executed via a slash command.๐๏ธ SubCommand Group
: An non-executable command inside aParentCommand
that only serves to store subcommands.
Or check what you can do with commands:
- Make components that call commands with the
๐ฌ CommandCustomIdCodec
. - Type safely get command instances on the
๐ CommandRepository
- Listen to command events on the ๐ฃ EventBus.