Saltearse al contenido

Creando tu primer comando

La entrada principal de cualquier bot de Discord son los comandos, en Seyfert, puedes crear y administrar comandos de forma sencilla. Seyfert se encargará de cargar y ejecutar todos los comandos que hayas creado dentro de la carpeta commands (especificada también en la configuración).

Para empezar, vamos a crear el siguiente archivo y utilizar el comando ping como ejemplo. Todos los comandos deben usar el decorador @Declare para la información y una clase que extiende a 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
: 'Mostrar la latencia con 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
) {
// Latencia media entre las conexiones existentes
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
: `La latencia es \`${
const ping: number
ping
}\``
});
}
}

Antes de poder ejecutar comandos nuevos necesitas registrarlos en Discord. Para ello, puedes utilizar el siguiente código:

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' }));

Con esto puedes ejecutar tu bot y probar el comando /ping en Discord. ¡Deberías ver la latencia de tu bot!


Usando opciones

¿Todo simple, verdad? Sin embargo, los comandos no siempre se limitan a responder lo mismo cada que lo ejecutas, hay veces que necesitamos interpretar lo que realmente necesita el usuario. Aquí es donde entran las options.

Vamos a configurar este comando para que su respuesta sea invisible para el resto de usuarios (mensaje efímero — ephemeral). Para esto utilizaremos el decorador @Options y crearemos una opción hide de tipo booleano:

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
: "Ocultar la respuesta del comando",
}),
};
@
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
: 'Mostrar la latencia con 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
;
// Latencia media entre las conexiones existentes
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
: `La latencia es \`${
const ping: number
ping
}\``,
flags?: MessageFlags | undefined

Message flags combined as a bitfield

flags
,
});
}
}

Al CommandContext se le añade el genérico para inferir el tipo de las opciones y poder acceder a ellas.

Más información sobre las opciones de los comandos aquí.

La estructura de tu proyecto debería verse así:

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