Options
Declare options on a slash command and read them back through getters typed from what you declared. Covers the option kinds, required options dropping the null, choices, channel types, and a getter that goes missing.
Every option you declare becomes a getter on this.options, typed from what you declared. Let's take this command as an example.
import {
BuilderComponent,
RegisterCommand
} from '@seedcord/gateway';
import { ChannelType } from 'discord.js';
@RegisterCommand('global')
export class Maintenance extends BuilderComponent<'command'> {
constructor() {
super('command');
this.instance
.setName('maintenance')
.setDescription('Post a maintenance notice')
.addUserOption((option) =>
option
.setName('notify')
.setDescription('Member to notify')
.setRequired(true)
)
.addChannelOption((option) =>
option
.setName('target')
.setDescription('Channel to post in')
.setRequired(true)
.addChannelTypes(
ChannelType.GuildText,
ChannelType.GuildAnnouncement
)
)
.addStringOption((option) =>
option.setName('reason').setDescription('Why')
);
}
}The handler reads notify, target, and reason back by name.
@SlashRoute<"maintenance">(...routes: "maintenance"[]): <TCtor>(constructor: AssertSlashRoute<"maintenance", TCtor>) => voidSlashRoute('maintenance')
export class class MaintenanceMaintenance extends class SlashHandler<
Route extends keyof SlashRegistry,
Cache extends CacheType = CacheFor<Route>
>
SlashHandler<'maintenance'> {
public async Maintenance.execute(): Promise<void>execute(): interface Promise<T>Promise<void> {
const const notify: Usernotify = this.SlashHandler<"maintenance", "cached">.options: SlashOptions<"maintenance", "cached">options.getUser: <"notify">(name: "notify") => UsergetUser('notify');
const const target: NewsChannel | TextChanneltarget = this.SlashHandler<"maintenance", "cached">.options: SlashOptions<"maintenance", "cached">options.getChannel: <"target">(name: "target") => NewsChannel | TextChannelgetChannel('target');
const const reason: string | nullreason = this.SlashHandler<"maintenance", "cached">.options: SlashOptions<"maintenance", "cached">options.getString: <"reason">(name: "reason") => string | nullgetString('reason');
await const target: NewsChannel | TextChanneltarget.PartialTextBasedChannelFields<InGuild extends boolean = boolean>.send(options: string | MessagePayload | MessageCreateOptions): Promise<Message<true>>send(`Maintenance incoming. ${const reason: string | nullreason ?? ''}`);
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(`Notified ${const notify: Usernotify.User.username: stringusername}.`);
}
}Run seedcord codegen after you add or change an option, since the handler keeps the old shape until you do.
The kinds
Each getter only accepts the names that command declared for its own kind. A typo is a compile error.
maintenance declares a user, a channel, and a string. That gives it four getters, since a user option answers both getUser and getMember.
this.options.- getChannel
- getMember
- getString
- getUser
Each one then offers that command's options of its own kind. maintenance declared one string, so getString lists reason.
this.options.getString('- reason
Required options drop the null
notify and target were declared with setRequired(true), so they arrive as values. reason was left optional, so it arrives as string | null. Discord rejects the command before your handler runs when a required option is missing.
Mark an option required when your handler has nothing sensible to do without it. Discord then turns the caller away at the picker, where they can still fix it, and your handler reads the value with no guard.
const notify = this.options.getUser('notify');const reason = this.options.getString('reason');| you declare | you call | you get |
|---|---|---|
addStringOption | getString | string |
addIntegerOption | getInteger | number, whole |
addNumberOption | getNumber | number, decimal allowed |
addBooleanOption | getBoolean | boolean |
addUserOption | getUser | the user |
addUserOption | getMember | the member, or null |
addChannelOption | getChannel | the channel |
addRoleOption | getRole | the role |
addMentionableOption | getMentionable | a user, member, or role |
addAttachmentOption | getAttachment | the attachment |
Gateway and http differ
The last six return live discord.js objects on gateway and raw discord-api-types payloads on http. Every one of them carries id on both transports. The exception is an http getMember payload, which holds the guild fields alone, since Discord sends that person's user object once under resolved.users.
getUser carries username, and getMentionable carries it when someone picked a user. A role has name, a channel has name, and an attachment has filename.
getMember returns null even for a required user option, since the person picked may have left the server.
Those are the option getters. On gateway the rest of the discord.js resolver is on this.event.options.
Choices
Adding choices narrows the return to those values.
@RegisterCommand('global')
export class Probe extends BuilderComponent<'command'> {
constructor() {
super('command');
this.instance
.setName('probe')
.setDescription('Search the catalogue')
.addStringOption((option) =>
option
.setName('category')
.setDescription('Scope the query')
.addChoices(
{ name: 'Books', value: 'books' },
{ name: 'Films', value: 'films' }
)
);
}
}The getter gives back the union of the values, so a switch over it is exhaustive.
const category = this.options.getString('category');Channel types
addChannelTypes narrows getChannel to the types you listed. target above allows text and announcement channels, so send is there to call.
const target = this.options.getChannel('target');Without addChannelTypes you get every channel kind Discord might send, so send is no longer guaranteed. A forum channel doesn't have it.
Threads are the one pair you cannot split. Asking for PublicThread alone gives you PublicThread | AnnouncementThread, since discord.js types a thread's type as both at once.
When a getter is missing
A getter only exists when the command declares an option of that kind. maintenance declares no integer option, so this.options.getInteger does not exist there.
const count = this.options.getInteger('count');Seeing this error on a getter you believe you declared means codegen hasn't run since. Run it to update the types.