forked from syntaxbullet/AuroraBot-discord
refactor(modules): standardize error handling in interaction handlers
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { ButtonInteraction } from "discord.js";
|
||||
import { lootdropService } from "./lootdrop.service";
|
||||
import { createErrorEmbed } from "@/lib/embeds";
|
||||
import { UserError } from "@/lib/errors";
|
||||
import { getLootdropClaimedMessage } from "./lootdrop.view";
|
||||
|
||||
export async function handleLootdropInteraction(interaction: ButtonInteraction) {
|
||||
@@ -9,7 +9,10 @@ export async function handleLootdropInteraction(interaction: ButtonInteraction)
|
||||
|
||||
const result = await lootdropService.tryClaim(interaction.message.id, interaction.user.id, interaction.user.username);
|
||||
|
||||
if (result.success) {
|
||||
if (!result.success) {
|
||||
throw new UserError(result.error || "Failed to claim.");
|
||||
}
|
||||
|
||||
await interaction.editReply({
|
||||
content: `🎉 You successfully claimed **${result.amount} ${result.currency}**!`
|
||||
});
|
||||
@@ -26,11 +29,5 @@ export async function handleLootdropInteraction(interaction: ButtonInteraction)
|
||||
);
|
||||
|
||||
await interaction.message.edit({ embeds, components });
|
||||
|
||||
} else {
|
||||
await interaction.editReply({
|
||||
embeds: [createErrorEmbed(result.error || "Failed to claim.")]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,34 @@
|
||||
import { ButtonInteraction, MessageFlags } from "discord.js";
|
||||
import { inventoryService } from "@/modules/inventory/inventory.service";
|
||||
import { userService } from "@/modules/user/user.service";
|
||||
import { createErrorEmbed, createWarningEmbed } from "@/lib/embeds";
|
||||
import { UserError } from "@/lib/errors";
|
||||
|
||||
export async function handleShopInteraction(interaction: ButtonInteraction) {
|
||||
if (!interaction.customId.startsWith("shop_buy_")) return;
|
||||
|
||||
try {
|
||||
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
||||
|
||||
const itemId = parseInt(interaction.customId.replace("shop_buy_", ""));
|
||||
if (isNaN(itemId)) {
|
||||
await interaction.editReply({ embeds: [createErrorEmbed("Invalid Item ID.")] });
|
||||
return;
|
||||
throw new UserError("Invalid Item ID.");
|
||||
}
|
||||
|
||||
const item = await inventoryService.getItem(itemId);
|
||||
if (!item || !item.price) {
|
||||
await interaction.editReply({ embeds: [createErrorEmbed("Item not found or not for sale.")] });
|
||||
return;
|
||||
throw new UserError("Item not found or not for sale.");
|
||||
}
|
||||
|
||||
const user = await userService.getOrCreateUser(interaction.user.id, interaction.user.username);
|
||||
if (!user) {
|
||||
throw new UserError("User profiles could not be loaded. Please try again later.");
|
||||
}
|
||||
|
||||
// Double check balance here too, although service handles it, we want a nice message
|
||||
if ((user.balance ?? 0n) < item.price) {
|
||||
await interaction.editReply({ embeds: [createWarningEmbed(`You need ${item.price} 🪙 to buy this item. You have ${user.balance} 🪙.`)] });
|
||||
return;
|
||||
throw new UserError(`You need ${item.price} 🪙 to buy this item. You have ${user.balance?.toString() ?? "0"} 🪙.`);
|
||||
}
|
||||
|
||||
const result = await inventoryService.buyItem(user.id, item.id, 1n);
|
||||
await inventoryService.buyItem(user.id.toString(), item.id, 1n);
|
||||
|
||||
await interaction.editReply({ content: `✅ **Success!** You bought **${item.name}** for ${item.price} 🪙.` });
|
||||
|
||||
} catch (error: any) {
|
||||
console.error("Shop Purchase Error:", error);
|
||||
await interaction.editReply({ embeds: [createErrorEmbed(error.message || "An error occurred while processing your purchase.")] });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { config } from "@/lib/config";
|
||||
import { AuroraClient } from "@/lib/BotClient";
|
||||
import { buildFeedbackMessage, getFeedbackModal } from "./feedback.view";
|
||||
import { FEEDBACK_CUSTOM_IDS, type FeedbackType, type FeedbackData } from "./feedback.types";
|
||||
import { createErrorEmbed, createSuccessEmbed } from "@/lib/embeds";
|
||||
import { UserError } from "@/lib/errors";
|
||||
|
||||
export const handleFeedbackInteraction = async (interaction: Interaction) => {
|
||||
// Handle select menu for choosing feedback type
|
||||
@@ -12,11 +12,7 @@ export const handleFeedbackInteraction = async (interaction: Interaction) => {
|
||||
const feedbackType = interaction.values[0] as FeedbackType;
|
||||
|
||||
if (!feedbackType) {
|
||||
await interaction.reply({
|
||||
embeds: [createErrorEmbed("Invalid feedback type selected.")],
|
||||
ephemeral: true
|
||||
});
|
||||
return;
|
||||
throw new UserError("Invalid feedback type selected.");
|
||||
}
|
||||
|
||||
const modal = getFeedbackModal(feedbackType);
|
||||
@@ -34,22 +30,13 @@ export const handleFeedbackInteraction = async (interaction: Interaction) => {
|
||||
|
||||
if (!feedbackType || !["FEATURE_REQUEST", "BUG_REPORT", "GENERAL"].includes(feedbackType)) {
|
||||
console.error(`Invalid feedback type extracted: ${feedbackType} from customId: ${interaction.customId}`);
|
||||
await interaction.reply({
|
||||
embeds: [createErrorEmbed("An error occurred processing your feedback. Please try again.")],
|
||||
ephemeral: true
|
||||
});
|
||||
return;
|
||||
throw new UserError("An error occurred processing your feedback. Please try again.");
|
||||
}
|
||||
|
||||
if (!config.feedbackChannelId) {
|
||||
await interaction.reply({
|
||||
embeds: [createErrorEmbed("Feedback channel is not configured. Please contact an administrator.")],
|
||||
ephemeral: true
|
||||
});
|
||||
return;
|
||||
throw new UserError("Feedback channel is not configured. Please contact an administrator.");
|
||||
}
|
||||
|
||||
try {
|
||||
// Parse modal inputs
|
||||
const title = interaction.fields.getTextInputValue(FEEDBACK_CUSTOM_IDS.TITLE_FIELD);
|
||||
const description = interaction.fields.getTextInputValue(FEEDBACK_CUSTOM_IDS.DESCRIPTION_FIELD);
|
||||
@@ -68,11 +55,7 @@ export const handleFeedbackInteraction = async (interaction: Interaction) => {
|
||||
const channel = await AuroraClient.channels.fetch(config.feedbackChannelId).catch(() => null) as TextChannel | null;
|
||||
|
||||
if (!channel) {
|
||||
await interaction.reply({
|
||||
embeds: [createErrorEmbed("Feedback channel not found. Please contact an administrator.")],
|
||||
ephemeral: true
|
||||
});
|
||||
return;
|
||||
throw new UserError("Feedback channel not found. Please contact an administrator.");
|
||||
}
|
||||
|
||||
// Build and send beautiful message
|
||||
@@ -89,24 +72,8 @@ export const handleFeedbackInteraction = async (interaction: Interaction) => {
|
||||
|
||||
// Confirm to user
|
||||
await interaction.reply({
|
||||
embeds: [createSuccessEmbed("Your feedback has been submitted successfully! Thank you for helping improve Aurora.", "✨ Feedback Submitted")],
|
||||
ephemeral: true
|
||||
content: "✨ **Feedback Submitted**\nYour feedback has been submitted successfully! Thank you for helping improve Aurora.",
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
|
||||
} catch (error: any) {
|
||||
console.error("Error submitting feedback:", error);
|
||||
|
||||
if (!interaction.replied && !interaction.deferred) {
|
||||
await interaction.reply({
|
||||
embeds: [createErrorEmbed("An error occurred while submitting your feedback. Please try again later.")],
|
||||
ephemeral: true
|
||||
});
|
||||
} else {
|
||||
await interaction.followUp({
|
||||
embeds: [createErrorEmbed("An error occurred while submitting your feedback. Please try again later.")],
|
||||
ephemeral: true
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { tradeService } from "./trade.service";
|
||||
import { inventoryService } from "@/modules/inventory/inventory.service";
|
||||
import { createErrorEmbed, createWarningEmbed, createSuccessEmbed, createInfoEmbed } from "@lib/embeds";
|
||||
import { UserError } from "@lib/errors";
|
||||
import { getTradeDashboard, getTradeMoneyModal, getItemSelectMenu, getTradeCompletedEmbed } from "./trade.view";
|
||||
|
||||
|
||||
@@ -22,7 +23,6 @@ export async function handleTradeInteraction(interaction: Interaction) {
|
||||
|
||||
if (!threadId) return;
|
||||
|
||||
try {
|
||||
if (customId === 'trade_cancel') {
|
||||
await handleCancel(interaction, threadId);
|
||||
} else if (customId === 'trade_lock') {
|
||||
@@ -44,14 +44,6 @@ export async function handleTradeInteraction(interaction: Interaction) {
|
||||
} else if (customId === 'trade_remove_item_select') {
|
||||
await handleRemoveItemSelect(interaction as StringSelectMenuInteraction, threadId);
|
||||
}
|
||||
} catch (error: any) {
|
||||
const errorEmbed = createErrorEmbed(error.message);
|
||||
if (interaction.replied || interaction.deferred) {
|
||||
await interaction.followUp({ embeds: [errorEmbed], ephemeral: true });
|
||||
} else {
|
||||
await interaction.reply({ embeds: [errorEmbed], ephemeral: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCancel(interaction: ButtonInteraction | StringSelectMenuInteraction | ModalSubmitInteraction, threadId: string) {
|
||||
@@ -93,7 +85,7 @@ async function handleMoneySubmit(interaction: ModalSubmitInteraction, threadId:
|
||||
const amountStr = interaction.fields.getTextInputValue('amount');
|
||||
const amount = BigInt(amountStr);
|
||||
|
||||
if (amount < 0n) throw new Error("Amount must be positive");
|
||||
if (amount < 0n) throw new UserError("Amount must be positive");
|
||||
|
||||
tradeService.updateMoney(threadId, interaction.user.id, amount);
|
||||
await interaction.deferUpdate(); // Acknowledge modal
|
||||
@@ -126,7 +118,7 @@ async function handleItemSelect(interaction: StringSelectMenuInteraction, thread
|
||||
|
||||
// Assuming implementation implies adding 1 item for now
|
||||
const item = await inventoryService.getItem(itemId);
|
||||
if (!item) throw new Error("Item not found");
|
||||
if (!item) throw new UserError("Item not found");
|
||||
|
||||
tradeService.addItem(threadId, interaction.user.id, { id: item.id, name: item.name }, 1n);
|
||||
|
||||
|
||||
@@ -1,48 +1,38 @@
|
||||
import { ButtonInteraction, MessageFlags } from "discord.js";
|
||||
import { config } from "@/lib/config";
|
||||
import { getEnrollmentErrorEmbed, getEnrollmentSuccessMessage } from "./enrollment.view";
|
||||
import { getEnrollmentSuccessMessage } from "./enrollment.view";
|
||||
import { classService } from "@modules/class/class.service";
|
||||
import { userService } from "@modules/user/user.service";
|
||||
import { UserError } from "@/lib/errors";
|
||||
import { sendWebhookMessage } from "@/lib/webhookUtils";
|
||||
|
||||
export async function handleEnrollmentInteraction(interaction: ButtonInteraction) {
|
||||
if (!interaction.inCachedGuild()) {
|
||||
await interaction.reply({ content: "This action can only be performed in a server.", flags: MessageFlags.Ephemeral });
|
||||
return;
|
||||
throw new UserError("This action can only be performed in a server.");
|
||||
}
|
||||
|
||||
const { studentRole, visitorRole } = config;
|
||||
|
||||
if (!studentRole || !visitorRole) {
|
||||
await interaction.reply({
|
||||
...getEnrollmentErrorEmbed("No student or visitor role configured for enrollment.", "Configuration Error"),
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
return;
|
||||
throw new UserError("No student or visitor role configured for enrollment.");
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Ensure user exists in DB and check current enrollment status
|
||||
const user = await userService.getOrCreateUser(interaction.user.id, interaction.user.username);
|
||||
if (!user) {
|
||||
throw new UserError("User profiles could not be loaded. Please try again later.");
|
||||
}
|
||||
|
||||
// Check DB enrollment
|
||||
if (user.class) {
|
||||
await interaction.reply({
|
||||
...getEnrollmentErrorEmbed("You are already enrolled in a class.", "Enrollment Failed"),
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
return;
|
||||
throw new UserError("You are already enrolled in a class.");
|
||||
}
|
||||
|
||||
const member = interaction.member;
|
||||
|
||||
// Check Discord role enrollment (Double safety)
|
||||
if (member.roles.cache.has(studentRole)) {
|
||||
await interaction.reply({
|
||||
...getEnrollmentErrorEmbed("You already have the student role.", "Enrollment Failed"),
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
return;
|
||||
throw new UserError("You already have the student role.");
|
||||
}
|
||||
|
||||
// 2. Get available classes
|
||||
@@ -50,11 +40,7 @@ export async function handleEnrollmentInteraction(interaction: ButtonInteraction
|
||||
const validClasses = allClasses.filter(c => c.roleId);
|
||||
|
||||
if (validClasses.length === 0) {
|
||||
await interaction.reply({
|
||||
...getEnrollmentErrorEmbed("No classes with specified roles found in database.", "Configuration Error"),
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
return;
|
||||
throw new UserError("No classes with specified roles found in database.");
|
||||
}
|
||||
|
||||
// 3. Pick random class
|
||||
@@ -64,15 +50,10 @@ export async function handleEnrollmentInteraction(interaction: ButtonInteraction
|
||||
// Check if the role exists in the guild
|
||||
const classRole = interaction.guild.roles.cache.get(classRoleId);
|
||||
if (!classRole) {
|
||||
await interaction.reply({
|
||||
...getEnrollmentErrorEmbed(`The configured role ID \`${classRoleId}\` for class **${selectedClass.name}** does not exist in this server.`, "Configuration Error"),
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
return;
|
||||
throw new UserError(`The configured role ID \`${classRoleId}\` for class **${selectedClass.name}** does not exist in this server.`);
|
||||
}
|
||||
|
||||
// 4. Perform Enrollment Actions
|
||||
|
||||
await member.roles.remove(visitorRole);
|
||||
await member.roles.add(studentRole);
|
||||
await member.roles.add(classRole);
|
||||
@@ -109,12 +90,4 @@ export async function handleEnrollmentInteraction(interaction: ButtonInteraction
|
||||
.catch((err: any) => console.error("Failed to send welcome message:", err));
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("Enrollment error:", error);
|
||||
await interaction.reply({
|
||||
...getEnrollmentErrorEmbed("An unexpected error occurred during enrollment. Please contact an administrator.", "System Error"),
|
||||
flags: MessageFlags.Ephemeral
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user