Skip to content

Creating Your First Command

The main input for any Discord bot is its commands. In Seyfert, you can easily create and manage commands. Seyfert will handle loading and executing all the commands you create within the commands folder (as specified in the configuration).

To get started, let’s create the following file and use the ping command as an example.
All commands must use the @Declare decorator for their information and a class that extends Command:

src/commands/ping.ts
import {
function Declare(declare: CommandDeclareOptions): <T extends {
new (...args: any[]): object;
}>(target: T) => {
new (...args: any[]): {
name: string;
nsfw: boolean | undefined;
props: ExtraProps | undefined;
contexts: InteractionContextType[];
integrationTypes: ApplicationIntegrationType[];
defaultMemberPermissions: bigint | undefined;
botPermissions: bigint | undefined;
description: string;
type: ApplicationCommandType;
guildId?: string[];
ignore?: IgnoreCommand;
aliases?: string[];
handler?: EntryPointCommandHandlerType;
};
} & T
Declare
,
class Command
Command
, type
class CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>
interface CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>
CommandContext
} from 'seyfert';
@
function Declare(declare: CommandDeclareOptions): <T extends {
new (...args: any[]): object;
}>(target: T) => {
new (...args: any[]): {
name: string;
nsfw: boolean | undefined;
props: ExtraProps | undefined;
contexts: InteractionContextType[];
integrationTypes: ApplicationIntegrationType[];
defaultMemberPermissions: bigint | undefined;
botPermissions: bigint | undefined;
description: string;
type: ApplicationCommandType;
guildId?: string[];
ignore?: IgnoreCommand;
aliases?: string[];
handler?: EntryPointCommandHandlerType;
};
} & T
Declare
({
name: string
name
: 'ping',
description: string
description
: 'Show latency with Discord'
})
export default class
class PingCommand
PingCommand
extends
class Command
Command
{
async
PingCommand.run(ctx: CommandContext): Promise<void>
run
(
ctx: CommandContext<{}, never>
ctx
:
class CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>
interface CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>
CommandContext
) {
// Average latency between existing connections
const
const ping: number
ping
=
ctx: CommandContext<{}, never>
ctx
.
CommandContext<{}, never>.client: UsingClient
client
.
Client<true>.gateway: ShardManager
gateway
.
ShardManager.latency: number
latency
;
await
ctx: CommandContext<{}, never>
ctx
.
CommandContext<{}, never>.write<false>(body: InteractionCreateBodyRequest, withResponse?: false | undefined): Promise<void | WebhookMessage>
write
({
content?: string | undefined

The message contents (up to 2000 characters)

content
: `The latency is \`${
const ping: number
ping
}\``
});
}
}

Before you can execute new commands, you need to register them with Discord. You can do this using the following code:

src/index.ts
import {
class Client<Ready extends boolean = boolean>
Client
} from 'seyfert';
const
const client: Client<boolean>
client
= new
new Client<boolean>(options?: ClientOptions): Client<boolean>
Client
();
const client: Client<boolean>
client
.
Client<boolean>.start(options?: Omit<DeepPartial<StartOptions>, "httpConnection">, execute?: boolean): Promise<void>
start
()
.
Promise<void>.then<void, never>(onfulfilled?: ((value: void) => void | PromiseLike<void>) | null | undefined, onrejected?: ((reason: any) => PromiseLike<never>) | null | undefined): Promise<...>

Attaches callbacks for the resolution and/or rejection of the Promise.

@paramonfulfilled The callback to execute when the Promise is resolved.

@paramonrejected The callback to execute when the Promise is rejected.

@returnsA Promise for the completion of which ever callback is executed.

then
(() =>
const client: Client<boolean>
client
.
BaseClient.uploadCommands({ applicationId, cachePath }?: {
applicationId?: string;
cachePath?: string;
}): Promise<void>
uploadCommands
({
cachePath?: string
cachePath
: './commands.json' }));

With this, you can run your bot and test the /ping command on Discord. You should see your bot’s latency!


Using Options

Simple enough, right? However, commands don’t always respond the same way every time you execute them. Sometimes, we need to interpret what the user actually needs. This is where options come into play.

Let’s configure this command so its response is invisible to other users (an ephemeral message).
To achieve this, we’ll use the @Options decorator and create a hide option of type boolean:

src/commands/ping.ts
import {
class Command
Command
,
function Declare(declare: CommandDeclareOptions): <T extends {
new (...args: any[]): object;
}>(target: T) => {
new (...args: any[]): {
name: string;
nsfw: boolean | undefined;
props: ExtraProps | undefined;
contexts: InteractionContextType[];
integrationTypes: ApplicationIntegrationType[];
defaultMemberPermissions: bigint | undefined;
botPermissions: bigint | undefined;
description: string;
type: ApplicationCommandType;
guildId?: string[];
ignore?: IgnoreCommand;
aliases?: string[];
handler?: EntryPointCommandHandlerType;
};
} & T
Declare
,
function Options(options: (new () => SubCommand)[] | OptionsRecord): <T extends {
new (...args: any[]): object;
}>(target: T) => {
new (...args: any[]): {
options: SubCommand[] | CommandOption[];
};
} & T
Options
,
function createBooleanOption<R extends boolean, T extends SeyfertBooleanOption<R> = SeyfertBooleanOption<R>>(data: T): T & {
readonly type: ApplicationCommandOptionType.Boolean;
}
createBooleanOption
,
type
class CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>
interface CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>
CommandContext
} from 'seyfert';
import { MessageFlags } from 'seyfert/lib/types';
const
const options: {
hide: {
description: string;
} & {
readonly type: ApplicationCommandOptionType.Boolean;
};
}
options
= {
hide: {
description: string;
} & {
readonly type: ApplicationCommandOptionType.Boolean;
}
hide
:
createBooleanOption<boolean, {
description: string;
}>(data: {
description: string;
}): {
description: string;
} & {
readonly type: ApplicationCommandOptionType.Boolean;
}
createBooleanOption
({
description: string
description
: "Hide the command's response",
}),
};
@
function Declare(declare: CommandDeclareOptions): <T extends {
new (...args: any[]): object;
}>(target: T) => {
new (...args: any[]): {
name: string;
nsfw: boolean | undefined;
props: ExtraProps | undefined;
contexts: InteractionContextType[];
integrationTypes: ApplicationIntegrationType[];
defaultMemberPermissions: bigint | undefined;
botPermissions: bigint | undefined;
description: string;
type: ApplicationCommandType;
guildId?: string[];
ignore?: IgnoreCommand;
aliases?: string[];
handler?: EntryPointCommandHandlerType;
};
} & T
Declare
({
name: string
name
: 'ping',
description: string
description
: 'Show latency with Discord'
})
@
function Options(options: (new () => SubCommand)[] | OptionsRecord): <T extends {
new (...args: any[]): object;
}>(target: T) => {
new (...args: any[]): {
options: SubCommand[] | CommandOption[];
};
} & T
Options
(
const options: {
hide: {
description: string;
} & {
readonly type: ApplicationCommandOptionType.Boolean;
};
}
options
)
export default class
class PingCommand
PingCommand
extends
class Command
Command
{
async
PingCommand.run(ctx: CommandContext<typeof options>): Promise<void>
run
(
ctx: CommandContext<{
hide: {
description: string;
} & {
readonly type: ApplicationCommandOptionType.Boolean;
};
}, never>
ctx
:
class CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>
interface CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>
CommandContext
<typeof
const options: {
hide: {
description: string;
} & {
readonly type: ApplicationCommandOptionType.Boolean;
};
}
options
>) {
const
const flags: MessageFlags.Ephemeral | undefined
flags
=
ctx: CommandContext<{
hide: {
description: string;
} & {
readonly type: ApplicationCommandOptionType.Boolean;
};
}, never>
ctx
.
CommandContext<{ hide: { description: string; } & { readonly type: ApplicationCommandOptionType.Boolean; }; }, never>.options: ContextOptions<{
hide: {
description: string;
} & {
readonly type: ApplicationCommandOptionType.Boolean;
};
}>
options
.
hide?: boolean | undefined
hide
? MessageFlags.
function (enum member) MessageFlags.Ephemeral = 64

This message is only visible to the user who invoked the Interaction

Ephemeral
:
var undefined
undefined
;
// Average latency between existing connections
const
const ping: number
ping
=
ctx: CommandContext<{
hide: {
description: string;
} & {
readonly type: ApplicationCommandOptionType.Boolean;
};
}, never>
ctx
.
CommandContext<{ hide: { description: string; } & { readonly type: ApplicationCommandOptionType.Boolean; }; }, never>.client: UsingClient
client
.
Client<true>.gateway: ShardManager
gateway
.
ShardManager.latency: number
latency
;
await
ctx: CommandContext<{
hide: {
description: string;
} & {
readonly type: ApplicationCommandOptionType.Boolean;
};
}, never>
ctx
.
CommandContext<{ hide: { description: string; } & { readonly type: ApplicationCommandOptionType.Boolean; }; }, never>.write<false>(body: InteractionCreateBodyRequest, withResponse?: false | undefined): Promise<void | WebhookMessage>
write
({
content?: string | undefined

The message contents (up to 2000 characters)

content
: `The latency is \`${
const ping: number
ping
}\``,
flags?: MessageFlags | undefined

Message flags combined as a bitfield

flags
,
});
}
}

The CommandContext is extended with a generic to infer the type of options and provide access to them.

More information about command options can be found here.

Your project structure should look like this:

  • Directorysrc
    • Directorycommands
      • ping.ts
    • index.ts
  • package.json
  • seyfert.config.mjs
  • tsconfig.json