refactor(core): centralize interaction error handling and organize routes

This commit is contained in:
syntaxbullet
2025-12-24 22:26:10 +01:00
parent 1523a392c2
commit d0c48188b9
2 changed files with 62 additions and 3 deletions

View File

@@ -1,11 +1,14 @@
import { ButtonInteraction, StringSelectMenuInteraction, ModalSubmitInteraction } from "discord.js";
import { ButtonInteraction, StringSelectMenuInteraction, ModalSubmitInteraction, MessageFlags } from "discord.js";
import { logger } from "@lib/logger";
import { UserError } from "@lib/errors";
import { createErrorEmbed } from "@lib/embeds";
type ComponentInteraction = ButtonInteraction | StringSelectMenuInteraction | ModalSubmitInteraction;
/**
* Handles component interactions (buttons, select menus, modals)
* Routes to appropriate handlers based on customId patterns
* Provides centralized error handling with UserError differentiation
*/
export class ComponentInteractionHandler {
static async handle(interaction: ComponentInteraction): Promise<void> {
@@ -17,12 +20,59 @@ export class ComponentInteractionHandler {
const handlerMethod = module[route.method];
if (typeof handlerMethod === 'function') {
await handlerMethod(interaction);
return;
try {
await handlerMethod(interaction);
return;
} catch (error) {
await this.handleError(interaction, error, route.method);
return;
}
} else {
logger.error(`Handler method ${route.method} not found in module`);
}
}
}
}
/**
* Handles errors from interaction handlers
* Differentiates between UserError (user-facing) and system errors
*/
private static async handleError(
interaction: ComponentInteraction,
error: unknown,
handlerName: string
): Promise<void> {
const isUserError = error instanceof UserError;
// Determine error message
const errorMessage = isUserError
? (error as Error).message
: 'An unexpected error occurred. Please try again later.';
// Log system errors (non-user errors) for debugging
if (!isUserError) {
logger.error(`Error in ${handlerName}:`, error);
}
const errorEmbed = createErrorEmbed(errorMessage);
try {
// Handle different interaction states
if (interaction.replied || interaction.deferred) {
await interaction.followUp({
embeds: [errorEmbed],
flags: MessageFlags.Ephemeral
});
} else {
await interaction.reply({
embeds: [errorEmbed],
flags: MessageFlags.Ephemeral
});
}
} catch (replyError) {
// If we can't send a reply, log it
logger.error(`Failed to send error response in ${handlerName}:`, replyError);
}
}
}

View File

@@ -19,11 +19,14 @@ interface InteractionRoute {
}
export const interactionRoutes: InteractionRoute[] = [
// --- TRADE MODULE ---
{
predicate: (i) => i.customId.startsWith("trade_") || i.customId === "amount",
handler: () => import("@/modules/trade/trade.interaction"),
method: 'handleTradeInteraction'
},
// --- ECONOMY MODULE ---
{
predicate: (i) => i.isButton() && i.customId.startsWith("shop_buy_"),
handler: () => import("@/modules/economy/shop.interaction"),
@@ -34,16 +37,22 @@ export const interactionRoutes: InteractionRoute[] = [
handler: () => import("@/modules/economy/lootdrop.interaction"),
method: 'handleLootdropInteraction'
},
// --- ADMIN MODULE ---
{
predicate: (i) => i.customId.startsWith("createitem_"),
handler: () => import("@/modules/admin/item_wizard"),
method: 'handleItemWizardInteraction'
},
// --- USER MODULE ---
{
predicate: (i) => i.isButton() && i.customId === "enrollment",
handler: () => import("@/modules/user/enrollment.interaction"),
method: 'handleEnrollmentInteraction'
},
// --- FEEDBACK MODULE ---
{
predicate: (i) => i.customId.startsWith("feedback_"),
handler: () => import("@/modules/feedback/feedback.interaction"),