Saltearse al contenido

Introducción a comandos

La entrada principal de cualquier bot de Discord son los comandos. En Seyfert, estos se definen utilizando decoradores de TypeScript, lo que facilita definir sus propiedades, opciones, middlewares y subcomandos. Esta guía te ayudará a comprender cómo funcionan los decoradores relacionados con comandos en Seyfert.

Declarando un comando

Todos los comandos en Seyfert son basados en clases y cada una de estas clases son extendidas de la clase base Command.

Además de esto, el nombre y la descripción son propiedades obligatorias en cada comando. A continuación, una lista de las posibles propiedades que puedes utilizar junto al decorador @Declare:

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
,
enum IgnoreCommand
IgnoreCommand
} 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
: 'tu-comando',
description: string
description
: 'Una descripción para este comando',
// Propiedades para pasar en el comando como metadata
props: {}
props
: {},
// Lista de permisos que necesita el miembro
defaultMemberPermissions: "Administrator"[]
defaultMemberPermissions
: ['Administrator'],
// Lista de permisos que necesita el bot
botPermissions: "ManageGuild"[]
botPermissions
: ['ManageGuild'],
// Lista de IDs de servidores para registrar el comando
guildId: string[]
guildId
: ['100000'],
// Determina si el comando es NSFW
nsfw: false
nsfw
: false,
// Lista de nombres alternos para el comando (en comandos de texto)
aliases: string[]
aliases
: ['un-comando'],
// Identifica los tipos de instalaciones que soporta el comando,
// por defecto solo en el servidor
integrationTypes: ("GuildInstall" | "UserInstall")[]
integrationTypes
: ['GuildInstall', 'UserInstall'],
// Determina dónde se puede usar un comando
contexts: ("Guild" | "BotDM" | "PrivateChannel")[]
contexts
: ['BotDM', 'Guild', 'PrivateChannel'],
// Establece si ignorar la ejecución del comando en slash
// o en su versión mensaje de texto
ignore: IgnoreCommand.Slash
ignore
:
enum IgnoreCommand
IgnoreCommand
.
function (enum member) IgnoreCommand.Slash = 0
Slash
,
Message
Slash
// Establece el tipo de comando:
/// type: ApplicationCommandType.User
})
class
class MiComando
MiComando
extends
class Command
Command
{}

Añadiendo opciones

Algo esencial es que los comandos se ajusten a las opciones ingresadas por el usuario. Para ello, utilizamos el decorador @Options, que necesita un objeto con todas las opciones del comando:

import {
function Options(options: (new () => SubCommand)[] | OptionsRecord): <T extends {
new (...args: any[]): object;
}>(target: T) => {
new (...args: any[]): {
options: SubCommand[] | CommandOption[];
};
} & T
Options
,
class Command
Command
,
function createStringOption<R extends boolean, C extends SeyfertChoice<string>[] = SeyfertChoice<string>[], VC = never>(data: SeyfertStringOption<C, R, VC>): {
readonly type: ApplicationCommandOptionType.String;
readonly required?: R | undefined;
readonly choices?: C | undefined;
readonly value?: ValueCallback<ApplicationCommandOptionType.String, C, VC> | undefined;
readonly description: string;
readonly description_localizations?: APIApplicationCommandBasicOption["description_localizations"];
readonly name_localizations?: APIApplicationCommandBasicOption["name_localizations"];
readonly locales?: {
name?: FlatObjectKeys<DefaultLocale>;
description?: FlatObjectKeys<DefaultLocale>;
};
readonly autocomplete?: AutocompleteCallback;
readonly onAutocompleteError?: OnAutocompleteErrorCallback;
readonly min_length?: number;
readonly max_length?: number;
}
createStringOption
,
type
class CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>
interface CommandContext<T extends OptionsRecord = {}, M extends keyof RegisteredMiddlewares = never>
CommandContext
,
} from 'seyfert';
const
const options: {
message: {
readonly type: ApplicationCommandOptionType.String;
readonly required?: boolean | undefined;
... 9 more ...;
readonly max_length?: number;
};
}
options
= {
message: {
readonly type: ApplicationCommandOptionType.String;
readonly required?: boolean | undefined;
readonly choices?: SeyfertChoice<...>[] | undefined;
... 8 more ...;
readonly max_length?: number;
}
message
:
createStringOption<boolean, SeyfertChoice<string>[], never>(data: SeyfertStringOption<SeyfertChoice<string>[], boolean, never>): {
readonly type: ApplicationCommandOptionType.String;
... 10 more ...;
readonly max_length?: number;
}
createStringOption
({
SeyfertBaseChoiceableOption<T extends keyof ReturnOptionsTypes, C = T extends ChoiceableTypes ? SeyfertChoice<ChoiceableValues[T]>[] : never, R = boolean, VC = never>.description: string
description
: 'Mi opción de texto',
}),
};
@
function Options(options: (new () => SubCommand)[] | OptionsRecord): <T extends {
new (...args: any[]): object;
}>(target: T) => {
new (...args: any[]): {
options: SubCommand[] | CommandOption[];
};
} & T
Options
(
const options: {
message: {
readonly type: ApplicationCommandOptionType.String;
readonly required?: boolean | undefined;
... 9 more ...;
readonly max_length?: number;
};
}
options
)
class
class MiComando
MiComando
extends
class Command
Command
{
MiComando.run(ctx: CommandContext<typeof options>): void
run
(
ctx: CommandContext<{
message: {
readonly type: ApplicationCommandOptionType.String;
readonly required?: boolean | undefined;
... 9 more ...;
readonly max_length?: number;
};
}, 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: {
message: {
readonly type: ApplicationCommandOptionType.String;
readonly required?: boolean | undefined;
... 9 more ...;
readonly max_length?: number;
};
}
options
>) {
ctx: CommandContext<{
message: {
readonly type: ApplicationCommandOptionType.String;
readonly required?: boolean | undefined;
... 9 more ...;
readonly max_length?: number;
};
}, never>
ctx
.
CommandContext<{ message: { readonly type: ApplicationCommandOptionType.String; readonly required?: boolean | undefined; ... 9 more ...; readonly max_length?: number; }; }, never>.options: ContextOptions<{
message: {
readonly type: ApplicationCommandOptionType.String;
readonly required?: boolean | undefined;
... 9 more ...;
readonly max_length?: number;
};
}>
options
.message;
message?: string | undefined
}
}

Todas los tipos y posibles opciones para cada comando se pueden encontrar en su respectiva sección.


Utilizando middlewares

Seyfert cuenta con un sistema avanzado de middlewares, completamente tipado y de gran potencia. Este sistema permite procesar una lista de middlewares, que son funciones diseñadas para ejecutarse previamente a la ejecución de un comando.

Recuerda que debes haber hecho uso del declare module para que funcione correctamente:

import {
class Command
Command
,
function Middlewares(cbs: readonly (keyof RegisteredMiddlewares)[]): <T extends {
new (...args: any[]): object;
}>(target: T) => {
new (...args: any[]): {
middlewares: readonly never[];
};
} & T
Middlewares
} from 'seyfert';
@
function Middlewares(cbs: readonly (keyof RegisteredMiddlewares)[]): <T extends {
new (...args: any[]): object;
}>(target: T) => {
new (...args: any[]): {
middlewares: readonly never[];
};
} & T
Middlewares
(['mi-middleware', 'siguiente-middleware'])
class
class MiComando
MiComando
extends
class Command
Command
{}

Los casos de uso para los middlewares y cómo empezar a usarlos están en la sección de middlewares.


Idiomas

Con el sistema de idiomas avanzado de Seyfert, puedes automáticamente traducir todo el contenido de un comando utilizando el decorador @LocalesT.

Recuerda que para el funcionamiento correcto de este, debes haber usado el declare module correctamente.

import {
class Command
Command
,
function LocalesT(name?: FlatObjectKeys<DefaultLocale>, description?: FlatObjectKeys<DefaultLocale>): <T extends {
new (...args: any[]): object;
}>(target: T) => {
new (...args: any[]): {
__t: {
name: undefined;
description: undefined;
};
};
} & T
LocalesT
} from 'seyfert';
@
function LocalesT(name?: FlatObjectKeys<DefaultLocale>, description?: FlatObjectKeys<DefaultLocale>): <T extends {
new (...args: any[]): object;
}>(target: T) => {
new (...args: any[]): {
__t: {
name: undefined;
description: undefined;
};
};
} & T
LocalesT
('mi-comando .nombre', 'mi-comando.description')
mi-comando.nombre
mi-comando.description
class
class MiComando
MiComando
extends
class Command
Command
{}

Subcomandos

Basándonos en la estructura de Discord, los subcomandos de tipo slash pueden pertenecer a grupos o no. Es importante tener en cuenta que solo se pueden ejecutar los subcomandos directamente, sin incluir los grupos o el comando principal.

Para añadir subcomandos, se puede utilizar el decorador @Options en un comando principal, especificando los subcomandos disponibles con la clase SubCommand. Para mantener una buena organización, se recomienda usar una estructura de carpetas que permita gestionar los subcomandos de manera ordenada:

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
} from 'seyfert';
import
import MiSubComando
MiSubComando
from './sub';
@
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
: 'padre',
description: string
description
: 'Mi comando principal',
})
@
function Options(options: (new () => SubCommand)[] | OptionsRecord): <T extends {
new (...args: any[]): object;
}>(target: T) => {
new (...args: any[]): {
options: SubCommand[] | CommandOption[];
};
} & T
Options
([
import MiSubComando
MiSubComando
])
export default class
class PadreCommand
PadreCommand
extends
class Command
Command
{ }

Grupos de subcomandos

Si queremos una estructura más dividida, podemos crear grupos para los subcomandos. Para ello, se utiliza el decorador @Group y @Groups en el comando principal.

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 Groups(groups: Record<string, {
name?: [language: LocaleString, value: string][];
description?: [language: LocaleString, value: string][];
defaultDescription: string;
aliases?: string[];
}>): <T extends {
new (...args: any[]): object;
}>(target: T) => {
new (...args: any[]): {
groups: Record<string, {
name?: [language: LocaleString, value: string][];
description?: [language: LocaleString, value: string][];
defaultDescription: string;
aliases?: string[];
}>;
groupsAliases: Record<string, string>;
};
} & T
Groups
} from 'seyfert';
import
import MiSubComando
MiSubComando
from './sub';
@
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
: 'padre',
description: string
description
: 'Mi comando principal',
})
@
function Options(options: (new () => SubCommand)[] | OptionsRecord): <T extends {
new (...args: any[]): object;
}>(target: T) => {
new (...args: any[]): {
options: SubCommand[] | CommandOption[];
};
} & T
Options
([
import MiSubComando
MiSubComando
])
@
function Groups(groups: Record<string, {
name?: [language: LocaleString, value: string][];
description?: [language: LocaleString, value: string][];
defaultDescription: string;
aliases?: string[];
}>): <T extends {
new (...args: any[]): object;
}>(target: T) => {
new (...args: any[]): {
groups: Record<string, {
name?: [language: LocaleString, value: string][];
description?: [language: LocaleString, value: string][];
defaultDescription: string;
aliases?: string[];
}>;
groupsAliases: Record<string, string>;
};
} & T
Groups
({
'mi-grupo': {
defaultDescription: string
defaultDescription
: 'Un grupo de subcomandos',
}
})
export default class
class PadreCommand
PadreCommand
extends
class Command
Command
{ }

Carga automática de subcomandos

Para ahorrarnos el hecho de importar cada comando manualmente, Seyfert nos permite cargar automáticamente los comandos de una carpeta. Para ello, se utiliza el decorador @AutoLoad en el comando principal.

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 AutoLoad(): <T extends {
new (...args: any[]): object;
}>(target: T) => {
new (...args: any[]): {
__autoload: boolean;
};
} & T
AutoLoad
} 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
: 'padre',
description: string
description
: 'Mi comando principal',
})
@
function AutoLoad(): <T extends {
new (...args: any[]): object;
}>(target: T) => {
new (...args: any[]): {
__autoload: boolean;
};
} & T
AutoLoad
()
export default class
class PadreCommand
PadreCommand
extends
class Command
Command
{ }

No es necesario utilizar el decorador @Options si se utiliza @AutoLoad. Seyfert cargará automáticamente los subcomandos de la carpeta.