Subcommands and groups
Put related slash commands under one name with subcommands and groups. Covers the route each branch gets, the parent that stops being a route, and the middle segment a group adds.
Discord lists your commands flat, so five related ones take five slots in the picker and five names you have to keep distinct. Subcommands gather them under one name.
Use them when the commands share a subject, the way role add and role remove both act on roles. If someone would go looking for a command by name, leave it at the top level.
A subcommand is its own BuilderComponent, attached to the parent command.
import {
BuilderComponent,
RegisterCommand
} from '@seedcord/gateway';
class AddRole extends BuilderComponent<'subcommand'> {
constructor() {
super('subcommand');
this.instance
.setName('add')
.setDescription('Give someone a role')
.addUserOption((option) =>
option
.setName('member')
.setDescription('Who')
.setRequired(true)
)
.addRoleOption((option) =>
option
.setName('role')
.setDescription('Which role')
.setRequired(true)
);
}
}
@RegisterCommand('global')
export class Role extends BuilderComponent<'command'> {
constructor() {
super('command');
this.instance
.setName('role')
.setDescription('Manage roles')
.addSubcommand(new AddRole().component);
}
}You put @RegisterCommand on the parent alone, since the parent is what you send to Discord. Your subcommand classes stay plain builders, and you can keep them in the same file.
The route
Discord shows this as /role add. seedcord flattens it to role/add. You write that string on both the decorator and the generic, so a mismatch is a compile error.
@SlashRoute<"role/add">(...routes: "role/add"[]): <TCtor>(constructor: AssertSlashRoute<"role/add", TCtor>) => voidSlashRoute('role/add')
export class class AddRoleAddRole extends class SlashHandler<
Route extends keyof SlashRegistry,
Cache extends CacheType = CacheFor<Route>
>
SlashHandler<'role/add'> {
public async AddRole.execute(): Promise<void>execute(): interface Promise<T>Promise<void> {
const const member: Usermember = this.SlashHandler<"role/add", "cached">.options: SlashOptions<"role/add", "cached">options.getUser: <"member">(name: "member") => UsergetUser('member');
const const role: Rolerole = this.SlashHandler<"role/add", "cached">.options: SlashOptions<"role/add", "cached">options.getRole: <"role">(name: "role") => RolegetRole('role');
await this.RepliableHandler<ChatInputCommandInteraction<"cached">, Core, SentMessage, BufferResolvable | Stream | JSONEncodable<...> | Attachment | AttachmentBuilder | AttachmentPayload, ReplySender>.reply(response: string | ReplyResponse<BufferResolvable | Stream | JSONEncodable<APIAttachment> | Attachment | AttachmentBuilder | AttachmentPayload>, opts?: SendOpts): Promise<SentMessage>reply(
`Gave ${const role: Rolerole.Role.name: stringname} to ${const member: Usermember.User.username: stringusername}.`
);
}
}Options belong to the branch that declares them, so member and role are on role/add alone.
The parent stops being a route
Adding a subcommand removes the parent's own route, since nobody can invoke /role by itself once it has branches. A handler for 'role' stops compiling.
class Role extends SlashHandler<'role'> { public async execute(): Promise<void> {}
}Groups add the middle segment
A group holds subcommands, and you attach it to the parent the same way.
class EnableNotifications extends BuilderComponent<'subcommand'> {
constructor() {
super('subcommand');
this.instance
.setName('enable')
.setDescription('Turn them on');
}
}
class NotificationSettings extends BuilderComponent<'group'> {
constructor() {
super('group');
this.instance
.setName('notifications')
.setDescription('Notification settings')
.addSubcommand(new EnableNotifications().component);
}
}
@RegisterCommand('global')
export class Settings extends BuilderComponent<'command'> {
constructor() {
super('command');
this.instance
.setName('settings')
.setDescription('Configure the bot')
.addSubcommandGroup(
new NotificationSettings().component
);
}
}Discord shows that as /settings notifications enable, and the route is settings/notifications/enable.
A path goes three segments deep at most, because Discord only accepts a group inside a command or subcommands inside a group.