import { DiscordAPIError, MessageFlags, } from 'discord.js'; import logger from './logger.js'; /** * Replies or follows up with an ephemeral error message. * @param {import('discord.js').ChatInputCommandInteraction} interaction * @param {string} content */ export async function replyEphemeralError(interaction, content) { try { if (interaction.replied || interaction.deferred) { await interaction.followUp({ content, flags: MessageFlags.Ephemeral }); } else { await interaction.reply({ content, flags: MessageFlags.Ephemeral }); } } catch (error) { logger.error('Failed to send ephemeral error reply', { error: error instanceof Error ? error.message : String(error), commandName: interaction.commandName, }); } } /** * Maps known failure modes to user-facing messages. * @param {unknown} error * @returns {string} */ export function describeCommandError(error) { if (error instanceof Error) { switch (error.message) { case 'DUPLICATE_MAPPING': return 'A reaction role for that emoji already exists on this message.'; case 'MESSAGE_NOT_FOUND': return 'Message not found in this channel. Make sure the message ID is correct and the message is in the current channel.'; case 'INVALID_EMOJI': return 'Invalid emoji. Use a unicode emoji (🎮) or custom emoji markup such as `<:name:id>` or ``.'; case 'CUSTOM_EMOJI_UNAVAILABLE': return 'That custom emoji is unavailable to the bot. Use an emoji from a server the bot is in.'; case 'ROLE_NOT_FOUND': return 'That role could not be found in this server.'; case 'MAPPING_NOT_FOUND': return 'No reaction-role mapping was found for that message and emoji.'; case 'NO_MAPPINGS_FOR_MESSAGE': return 'No reaction-role mappings were found for that message.'; default: break; } if (error.message.startsWith('PERMISSION:')) { return error.message.slice('PERMISSION:'.length).trim(); } } if (error instanceof DiscordAPIError) { logger.error('Discord API error', { code: error.code, message: error.message, status: error.status, }); if (error.code === 50013) { return 'Missing bot permissions. I need Manage Roles, Add Reactions, View Channels, and Read Message History.'; } if (error.code === 10008) { return 'Message not found. It may have been deleted.'; } if (error.code === 10014) { return 'Unknown emoji. The emoji may be invalid or unavailable.'; } return `Discord API error: ${error.message}`; } logger.error('Unexpected command error', { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, }); return 'An unexpected error occurred. Please try again later.'; } /** * @param {unknown} error * @returns {boolean} */ export function isDatabaseError(error) { return ( error instanceof Error && (error.message.includes('SQLITE_') || error.message.toLowerCase().includes('database')) ); }