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 { logger } from "@lib/logger";
import { UserError } from "@lib/errors";
import { createErrorEmbed } from "@lib/embeds";
type ComponentInteraction = ButtonInteraction | StringSelectMenuInteraction | ModalSubmitInteraction; type ComponentInteraction = ButtonInteraction | StringSelectMenuInteraction | ModalSubmitInteraction;
/** /**
* Handles component interactions (buttons, select menus, modals) * Handles component interactions (buttons, select menus, modals)
* Routes to appropriate handlers based on customId patterns * Routes to appropriate handlers based on customId patterns
* Provides centralized error handling with UserError differentiation
*/ */
export class ComponentInteractionHandler { export class ComponentInteractionHandler {
static async handle(interaction: ComponentInteraction): Promise<void> { static async handle(interaction: ComponentInteraction): Promise<void> {
@@ -17,12 +20,59 @@ export class ComponentInteractionHandler {
const handlerMethod = module[route.method]; const handlerMethod = module[route.method];
if (typeof handlerMethod === 'function') { if (typeof handlerMethod === 'function') {
await handlerMethod(interaction); try {
return; await handlerMethod(interaction);
return;
} catch (error) {
await this.handleError(interaction, error, route.method);
return;
}
} else { } else {
logger.error(`Handler method ${route.method} not found in module`); 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[] = [ export const interactionRoutes: InteractionRoute[] = [
// --- TRADE MODULE ---
{ {
predicate: (i) => i.customId.startsWith("trade_") || i.customId === "amount", predicate: (i) => i.customId.startsWith("trade_") || i.customId === "amount",
handler: () => import("@/modules/trade/trade.interaction"), handler: () => import("@/modules/trade/trade.interaction"),
method: 'handleTradeInteraction' method: 'handleTradeInteraction'
}, },
// --- ECONOMY MODULE ---
{ {
predicate: (i) => i.isButton() && i.customId.startsWith("shop_buy_"), predicate: (i) => i.isButton() && i.customId.startsWith("shop_buy_"),
handler: () => import("@/modules/economy/shop.interaction"), handler: () => import("@/modules/economy/shop.interaction"),
@@ -34,16 +37,22 @@ export const interactionRoutes: InteractionRoute[] = [
handler: () => import("@/modules/economy/lootdrop.interaction"), handler: () => import("@/modules/economy/lootdrop.interaction"),
method: 'handleLootdropInteraction' method: 'handleLootdropInteraction'
}, },
// --- ADMIN MODULE ---
{ {
predicate: (i) => i.customId.startsWith("createitem_"), predicate: (i) => i.customId.startsWith("createitem_"),
handler: () => import("@/modules/admin/item_wizard"), handler: () => import("@/modules/admin/item_wizard"),
method: 'handleItemWizardInteraction' method: 'handleItemWizardInteraction'
}, },
// --- USER MODULE ---
{ {
predicate: (i) => i.isButton() && i.customId === "enrollment", predicate: (i) => i.isButton() && i.customId === "enrollment",
handler: () => import("@/modules/user/enrollment.interaction"), handler: () => import("@/modules/user/enrollment.interaction"),
method: 'handleEnrollmentInteraction' method: 'handleEnrollmentInteraction'
}, },
// --- FEEDBACK MODULE ---
{ {
predicate: (i) => i.customId.startsWith("feedback_"), predicate: (i) => i.customId.startsWith("feedback_"),
handler: () => import("@/modules/feedback/feedback.interaction"), handler: () => import("@/modules/feedback/feedback.interaction"),