Dos rutas de ejecución
No todos los eventos de Discord son iguales desde la perspectiva del framework. Algunos eventos llevan un mensaje o una interacción como su primer argumento, y otros eventos llevan otros tipos de payload como un objeto Client, un GuildMember o un canal. Esta distinción importa porque el framework la usa para decidir cómo ejecutar tu método listener y si aplicar el pipeline de guards y excepciones.
Cuando el primer argumento del evento es un BaseInteraction o un Message, el framework ejecuta el método a través del pipeline completo. Esto significa que los guards a nivel de clase y método se ejecutan primero y, si el método lanza una excepción, el pipeline de excepciones la maneja con renderizado de layout y comportamiento de respuesta opcional. Este es el mismo pipeline que usan slash y prefix commands, por eso los guards y layouts de excepción funcionan sin fricción en listeners de mensajes e interacciones.
Cuando el primer argumento es cualquier otra cosa, como el objeto Client para ClientReady o un GuildMember para GuildMemberAdd, el framework llama al método directamente sin el pipeline de guards ni de excepciones. Si el método lanza un error, el framework lo captura y lo registra como error no manejado del listener, pero no hay renderizado de layout ni comportamiento de respuesta porque ese tipo de payload no permite responder como lo hacen mensajes e interacciones.
| Tipo del primer argumento | Ruta de ejecución | Manejo de errores |
|---|---|---|
| BaseInteraction o Message | Pipeline completo con guards y manejador de excepciones | Layouts de excepción, comportamiento de respuesta y manejo estructurado |
| Cualquier otro tipo | Invocación directa del método | Error capturado y registrado, sin capacidad de respuesta |
Este detalle es importante cuando diseñas tus listeners. Si adjuntas guards a un listener que maneja GuildMemberAdd, esos guards no se ejecutarán porque el primer argumento es un GuildMember, no un BaseInteraction o Message. Los guards solo se activan cuando el framework puede construir un ExecutionContext desde el payload del evento, lo que requiere una interacción o un mensaje.
Uso de guards en listeners
Para eventos que pasan por el pipeline completo, puedes adjuntar guards a nivel de clase o método con @UseGuards, exactamente igual que en un command handler. Los guards se ejecutan antes de que se ejecute el método listener y, si alguno deniega, el método se omite y la denegación pasa por el pipeline de excepciones.
import { Events, Listener, On, UseGuards, withOptions } from '@spraxium/common';
import { GuildOnly, RoleGuard } from '@spraxium/core';
import type { Message } from 'discord.js';
@Listener(Events.MessageCreate)
@UseGuards(GuildOnly, withOptions(RoleGuard, { roles: ['Verified'] }))
export class ProtectedMessageListener {
@On()
async onMessage(message: Message): Promise<void> {
if (message.author.bot) return;
await message.reply('You are verified and in a server.');
}
}En este ejemplo, tanto GuildOnly como RoleGuard se ejecutan antes de onMessage. Si el mensaje viene de un DM, GuildOnly deniega y el método nunca se ejecuta. Si el miembro no tiene el rol "Verified", RoleGuard deniega. Solo cuando ambos guards pasan se ejecuta el método.
También puedes usar guards a nivel de método para control más fino cuando una clase listener tiene múltiples métodos handler. Los guards de clase aplican a todos los métodos, y los guards de método agregan restricciones extra solo a métodos concretos.
Lanzar excepciones desde listeners
Para eventos en el pipeline completo, puedes lanzar excepciones de dominio desde métodos listener y la capa de excepciones las maneja igual que las excepciones de comandos. Esto significa que tus layouts, comportamiento de respuesta y configuración de logs aplican a excepciones de listeners sin configuración adicional.
import { Events, Listener, On } from '@spraxium/common';
import type { Message } from 'discord.js';
import { SpraxiumException } from '@spraxium/core';
class AccountSuspendedException extends SpraxiumException {
constructor(props: { reason: string; userId: string }) {
super({
code: 'ACCOUNT_SUSPENDED',
message: 'Your account has been suspended: {{reason}}.',
props,
});
}
}
@Listener(Events.MessageCreate)
export class AccountPolicyListener {
@On()
async onMessage(message: Message): Promise<void> {
if (message.author.bot) return;
const isSuspended = await this.checkSuspension(message.author.id);
if (isSuspended) {
throw new AccountSuspendedException({
reason: 'Policy violation',
userId: message.author.id,
});
}
}
private async checkSuspension(userId: string): Promise<boolean> {
// replace with actual database lookup
return false;
}
}Para eventos que pasan por invocación directa en lugar de pipeline completo, los errores lanzados se capturan y registran, pero no se procesan mediante la capa de excepciones. Eso significa que no tienes renderizado de layouts ni respuestas estructuradas para esos eventos. Si necesitas manejo de errores confiable en eventos de invocación directa como GuildMemberAdd, maneja errores explícitamente dentro del método con try-catch y logging propio.
import { Events, Listener, On } from '@spraxium/common';
import { Logger } from '@spraxium/core';
import type { GuildMember } from 'discord.js';
@Listener(Events.GuildMemberAdd)
export class MemberJoinListener {
private readonly logger = new Logger(MemberJoinListener.name);
@On()
async onJoin(member: GuildMember): Promise<void> {
try {
const channel = await member.guild.channels.fetch('1234567890');
if (channel?.isTextBased()) {
await channel.send(`Welcome to the server, ${member}!`);
}
} catch (error) {
this.logger.error(
`Failed to send welcome message for ${member.user.username}`,
error,
);
}
}
}Los guards solo funcionan en eventos de mensaje e interacción
Adjuntar guards a un listener que maneja eventos como ClientReady o GuildMemberAdd no tiene efecto porque el framework no puede construir un ExecutionContext desde esos payloads de evento. Usa checks explícitos dentro del método en su lugar.
Página siguiente
Continúa con Descripción general de comandos prefix.