Creating types for slash command options.

Hello guys. I have been search the Internet about this, with little to no success, so I thought I'd ask here. Is it possible to provide custom types for my slash command options. For example, I can restrict the interaction and command name and commandNames respectively, and also the type like this:
import type { ApplicationCommandType, ChatInputApplicationCommandData, CommandInteraction } from 'discord.js';

type ISlashChatCommandNames = 'foo' | 'bar';

export interface ISlashChatCommandData extends ChatInputApplicationCommandData {
name: ISlashChatCommandNames;
type: ApplicationCommandType.ChatInput,
}

export interface ISlashChatCommandInteraction extends CommandInteraction {
commandName: ISlashChatCommandNames;
commandType: ApplicationCommandType.ChatInput,
}
import type { ApplicationCommandType, ChatInputApplicationCommandData, CommandInteraction } from 'discord.js';

type ISlashChatCommandNames = 'foo' | 'bar';

export interface ISlashChatCommandData extends ChatInputApplicationCommandData {
name: ISlashChatCommandNames;
type: ApplicationCommandType.ChatInput,
}

export interface ISlashChatCommandInteraction extends CommandInteraction {
commandName: ISlashChatCommandNames;
commandType: ApplicationCommandType.ChatInput,
}
ISlashChatCommandData is used for the slash command builder andISlashChatCommandInteraction is for when the users are interacting with the bot.
2 Replies
d.js docs
d.js docs2y ago
• What's your exact discord.js npm list discord.js and node node -v version? • Post the full error stack trace, not just the top part! • Show your code! • Explain what exactly your issue is. • Not a discord.js issue? Check out #useful-servers.
decho
decho2y ago
Now as we all know, every interaction can accept a number of options, which can be boolean, number, string and so on. Is it possible to create an interface/type for my foo command for example, and define it's options there.
const isValidInteraction = (int: CommandInteraction): int is ISlashChatCommandInteraction => {
if ((['foo', 'bar'] as ISlashChatCommandNames[]).some(name => name === int.commandName)) {
return true;
}
return false;
};

const InteractionController = async (int: Interaction) => {
if (int.type === InteractionType.ApplicationCommand && isValidInteraction(int)) {
await SlashCommandController({ command: int });
}
};

const SlashCommandController = async ({ command }: { command: ISlashChatCommandInteraction }) => {
switch (command.commandName) {
/* This part here works and the commandNames are narrowed down */
case 'foo': await handleFoo(command);
case 'bar': await handleBar(command);
default: break;
}
};

const handleFoo = async (int: ISlashChatCommandInteraction) => {
/*
How to define options in my "foo" interface,
so that I get intellisense and type safety here?
*/
int.options.get('....');
};

const handleBar = async (int: ISlashChatCommandInteraction) => {
// ...
};
const isValidInteraction = (int: CommandInteraction): int is ISlashChatCommandInteraction => {
if ((['foo', 'bar'] as ISlashChatCommandNames[]).some(name => name === int.commandName)) {
return true;
}
return false;
};

const InteractionController = async (int: Interaction) => {
if (int.type === InteractionType.ApplicationCommand && isValidInteraction(int)) {
await SlashCommandController({ command: int });
}
};

const SlashCommandController = async ({ command }: { command: ISlashChatCommandInteraction }) => {
switch (command.commandName) {
/* This part here works and the commandNames are narrowed down */
case 'foo': await handleFoo(command);
case 'bar': await handleBar(command);
default: break;
}
};

const handleFoo = async (int: ISlashChatCommandInteraction) => {
/*
How to define options in my "foo" interface,
so that I get intellisense and type safety here?
*/
int.options.get('....');
};

const handleBar = async (int: ISlashChatCommandInteraction) => {
// ...
};
discord.js@14.0.2, node v16.15.1 in the example I provided I haven't narrowed down my interaction to ChatInputCommandInteraction so the getString, getUser, etc. were missing, I only figured that out later after asking the question. but i was asking if you could somehow type the subcomands and options inside the interface, for example:
export interface IFoo extends ChatInputCommandInteraction {
commandName: 'foo',
commandType: ApplicationCommandType.ChatInput,
options: [
{
name: 'test',
description: '....',
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: 'option1',
description: '...',
type: ApplicationCommandOptionType.String,
required: true,
},
{
name: 'option2',
description: '...',
type: ApplicationCommandOptionType.Integer,
required: true,
},
],
},
]
}
export interface IFoo extends ChatInputCommandInteraction {
commandName: 'foo',
commandType: ApplicationCommandType.ChatInput,
options: [
{
name: 'test',
description: '....',
type: ApplicationCommandOptionType.Subcommand,
options: [
{
name: 'option1',
description: '...',
type: ApplicationCommandOptionType.String,
required: true,
},
{
name: 'option2',
description: '...',
type: ApplicationCommandOptionType.Integer,
required: true,
},
],
},
]
}
Obviously this doesn't work, I was just asking if it's possible to have something like this so that you get intellisense and type safety later within the handleBar function that accesses the interaction yes but you can still use completely arbitrary keys in there, what I wanted to get is hinting and type safety where the only accepted keys are option1 and option1, or what about the subcommands themselves, for example:
int.options.getSubcommand('randomSubCommandThatDoesNotExist')
int.options.getSubcommand('randomSubCommandThatDoesNotExist')
this will totally work (but it shouldn't). so i guess the whole question is if there was some clever way to define (type) these somehow all right, i searched online none of the courses, tutorials or example repos go in depth or cover this area if you do happen to come up with something, do let me know, I appreciate your help, cheers! yeah, it becomes a question of whether or not you want to invest all that time into it for relatively little gain, and also one additional problem would be that you'd only be taking care of the interactions this way, but you'd still have a separate interface for the command builder so... for guild.commands.set(commandList) I mean I think perhaps for now I am going to settle for something like:
interface FooOpts {
option1: string;
option2: number;
}
// ...

const opts: FooOpts = {
option1: int.options.getString('option1', true),
option2: int.options.getNumber('option2', true)
};
interface FooOpts {
option1: string;
option2: number;
}
// ...

const opts: FooOpts = {
option1: int.options.getString('option1', true),
option2: int.options.getNumber('option2', true)
};
but I will try and see about the idea you gave with the CommandInteractionResolver, though that's probably too advanced for me