import { Events, Interaction, ButtonInteraction, ModalSubmitInteraction, StringSelectMenuInteraction, } from 'discord.js'; import { Event } from '@/types/EventTypes.js'; import { approveFact, deleteFact } from '@/db/db.js'; import * as GiveawayManager from '@/util/giveaways/giveawayManager.js'; import { ExtendedClient } from '@/structures/ExtendedClient.js'; import { safelyRespond, validateInteraction } from '@/util/helpers.js'; import { processCommandAchievements } from '@/util/achievementManager.js'; export default { name: Events.InteractionCreate, execute: async (interaction: Interaction) => { if (!(await validateInteraction(interaction))) return; try { if (interaction.isCommand()) { await handleCommand(interaction); } else if (interaction.isButton()) { await handleButton(interaction); } else if (interaction.isModalSubmit()) { await handleModal(interaction); } else if (interaction.isStringSelectMenu()) { await handleSelectMenu(interaction); } else { console.warn('Unhandled interaction type:', interaction); } } catch (error) { handleInteractionError(error, interaction); } }, } as Event; async function handleCommand(interaction: Interaction) { if (!interaction.isCommand()) return; const client = interaction.client as ExtendedClient; const command = client.commands.get(interaction.commandName); if (!command) { console.error(`No command matching ${interaction.commandName} was found.`); return; } if (interaction.isChatInputCommand()) { await command.execute(interaction); await processCommandAchievements( interaction.user.id, command.data.name, interaction.guild!, ); } else if ( interaction.isUserContextMenuCommand() || interaction.isMessageContextMenuCommand() ) { // @ts-expect-error await command.execute(interaction); await processCommandAchievements( interaction.user.id, command.data.name, interaction.guild!, ); } } async function handleButton(interaction: Interaction) { if (!interaction.isButton()) return; const { customId } = interaction; try { const giveawayHandlers: Record< string, (buttonInteraction: ButtonInteraction) => Promise > = { giveaway_start_builder: GiveawayManager.builder.startGiveawayBuilder, giveaway_next: GiveawayManager.builder.nextBuilderStep, giveaway_previous: GiveawayManager.builder.previousBuilderStep, giveaway_set_prize: GiveawayManager.modals.showPrizeModal, giveaway_set_duration: GiveawayManager.dropdowns.showDurationSelect, giveaway_set_winners: GiveawayManager.dropdowns.showWinnerSelect, giveaway_set_requirements: GiveawayManager.modals.showRequirementsModal, giveaway_toggle_logic: GiveawayManager.toggleRequirementLogic, giveaway_set_channel: (interaction.guild?.channels.cache.size ?? 0) > 25 ? GiveawayManager.modals.showChannelSelectModal : GiveawayManager.dropdowns.showChannelSelect, giveaway_bonus_entries: GiveawayManager.modals.showBonusEntriesModal, giveaway_set_ping_role: (interaction.guild?.roles.cache.size ?? 0) > 25 ? GiveawayManager.modals.showPingRoleSelectModal : GiveawayManager.dropdowns.showPingRoleSelect, giveaway_publish: GiveawayManager.publishGiveaway, enter_giveaway: GiveawayManager.handlers.handleGiveawayEntry, }; if (giveawayHandlers[customId]) { await giveawayHandlers[customId](interaction); return; } if ( customId.startsWith('approve_fact_') || customId.startsWith('reject_fact_') ) { await handleFactModeration(interaction, customId); return; } console.warn('Unhandled button interaction:', customId); } catch (error) { throw new Error(`Button interaction failed: ${error}`); } } async function handleFactModeration( interaction: Interaction, customId: string, ) { if (!interaction.isButton()) return; if (!interaction.memberPermissions?.has('ModerateMembers')) { await interaction.reply({ content: 'You do not have permission to moderate facts.', ephemeral: true, }); return; } const factId = parseInt(customId.replace(/^(approve|reject)_fact_/, ''), 10); const isApproval = customId.startsWith('approve_fact_'); if (isApproval) { await approveFact(factId); await interaction.update({ content: `✅ Fact #${factId} has been approved by <@${interaction.user.id}>`, components: [], }); } else { await deleteFact(factId); await interaction.update({ content: `❌ Fact #${factId} has been rejected by <@${interaction.user.id}>`, components: [], }); } } async function handleModal(interaction: Interaction) { if (!interaction.isModalSubmit()) return; const { customId } = interaction; const modalHandlers: Record< string, (modalInteraction: ModalSubmitInteraction) => Promise > = { giveaway_prize_modal: GiveawayManager.handlers.handlePrizeSubmit, giveaway_custom_duration: GiveawayManager.handlers.handleCustomDurationSubmit, giveaway_requirements_modal: GiveawayManager.handlers.handleRequirementsSubmit, giveaway_bonus_entries_modal: GiveawayManager.handlers.handleBonusEntriesSubmit, giveaway_ping_role_id_modal: GiveawayManager.handlers.handlePingRoleIdSubmit, giveaway_channel_id_modal: GiveawayManager.handlers.handleChannelIdSubmit, }; try { if (modalHandlers[customId]) { await modalHandlers[customId](interaction); } else { console.warn('Unhandled modal submission interaction:', customId); } } catch (error) { throw new Error(`Modal submission failed: ${error}`); } } async function handleSelectMenu(interaction: Interaction) { if (!interaction.isStringSelectMenu()) return; const { customId } = interaction; const selectHandlers: Record< string, (selectInteraction: StringSelectMenuInteraction) => Promise > = { giveaway_duration_select: GiveawayManager.handlers.handleDurationSelect, giveaway_winners_select: GiveawayManager.handlers.handleWinnerSelect, giveaway_channel_select: GiveawayManager.handlers.handleChannelSelect, giveaway_ping_role_select: GiveawayManager.handlers.handlePingRoleSelect, }; try { if (selectHandlers[customId]) { await selectHandlers[customId](interaction); } else { console.warn('Unhandled string select menu interaction:', customId); } } catch (error) { throw new Error(`Select menu interaction failed: ${error}`); } } function handleInteractionError(error: unknown, interaction: Interaction) { console.error('Interaction error:', error); const isUnknownInteractionError = (error as { code?: number })?.code === 10062 || String(error).includes('Unknown interaction'); if (isUnknownInteractionError) { console.warn( 'Interaction expired before response could be sent (code 10062)', ); return; } const errorMessage = 'An error occurred while processing your request.'; safelyRespond(interaction, errorMessage).catch(console.error); }