import type { ButtonInteraction, ChannelSelectMenuInteraction, MentionableSelectMenuInteraction, ModalSubmitInteraction, RoleSelectMenuInteraction, StringSelectMenuInteraction, UserSelectMenuInteraction } from 'discord.js'; import { decodeComponentCustomId, t, type DashboardModuleId } from '@nexumi/shared'; import { env } from './env.js'; import { getGuildLocale } from './i18n.js'; import { ephemeral } from './interaction-reply.js'; import { isModuleEnabled, moduleForComponent } from './module-gates.js'; import { isMaintenanceMode } from './presence.js'; import type { BotContext } from './types.js'; import { handleModerationConfirmation, isModerationConfirmation } from './modules/moderation/confirmations.js'; import { handleVerificationButton } from './modules/verification/handlers.js'; import { isVerificationButton } from './modules/verification/service.js'; import { handleBlackjackButton, isBlackjackButton } from './modules/economy/blackjack-buttons.js'; import { handleEmbedModal, handlePollButton, isEmbedModal, isPollButton } from './modules/utility/index.js'; import { handleFunButton, isFunButton } from './modules/fun/index.js'; import { handleGiveawayButton, isGiveawayButton } from './modules/giveaways/index.js'; import { handleTicketInteraction, isTicketInteraction } from './modules/tickets/index.js'; import { handleSelfRoleInteraction, isSelfRoleInteraction } from './modules/selfroles/index.js'; import { handleSuggestionButton, isSuggestionButton } from './modules/suggestions/index.js'; import { handleTempVoiceInteraction, isTempVoiceInteraction, isTempVoiceModal } from './modules/tempvoice/index.js'; import { handleGuildBackupButton, isGuildBackupButton } from './modules/guildbackup/index.js'; import { handleComponentsV2Interaction, isComponentsV2Interaction } from './modules/components-v2/index.js'; type AnyComponentInteraction = | ButtonInteraction | StringSelectMenuInteraction | UserSelectMenuInteraction | RoleSelectMenuInteraction | ChannelSelectMenuInteraction | MentionableSelectMenuInteraction | ModalSubmitInteraction; type InteractionHandler = { match: (customId: string) => boolean; handle: (interaction: AnyComponentInteraction, context: BotContext) => Promise; }; function asButton(interaction: AnyComponentInteraction): ButtonInteraction { return interaction as ButtonInteraction; } function asModal(interaction: AnyComponentInteraction): ModalSubmitInteraction { return interaction as ModalSubmitInteraction; } const handlers: InteractionHandler[] = [ { match: isModerationConfirmation, handle: (interaction, context) => handleModerationConfirmation(asButton(interaction), context) }, { match: isVerificationButton, handle: (interaction, context) => handleVerificationButton(asButton(interaction), context) }, { match: isBlackjackButton, handle: (interaction, context) => handleBlackjackButton(asButton(interaction), context) }, { match: isPollButton, handle: (interaction, context) => handlePollButton(asButton(interaction), context) }, { match: isFunButton, handle: (interaction, context) => handleFunButton(asButton(interaction), context) }, { match: isGiveawayButton, handle: (interaction, context) => handleGiveawayButton(asButton(interaction), context) }, { match: isTicketInteraction, handle: (interaction, context) => handleTicketInteraction( interaction as Parameters[0], context ) }, { match: isSelfRoleInteraction, handle: (interaction, context) => handleSelfRoleInteraction( interaction as Parameters[0], context ) }, { match: isSuggestionButton, handle: (interaction, context) => handleSuggestionButton(asButton(interaction), context) }, { match: (customId) => isTempVoiceInteraction(customId) || isTempVoiceModal(customId), handle: (interaction, context) => handleTempVoiceInteraction( interaction as Parameters[0], context ) }, { match: isGuildBackupButton, handle: (interaction, context) => handleGuildBackupButton(asButton(interaction), context) }, { match: isEmbedModal, handle: (interaction, context) => handleEmbedModal(asModal(interaction), context) }, { match: isComponentsV2Interaction, handle: (interaction, context) => handleComponentsV2Interaction( interaction as Parameters[0], context ) } ]; function moduleForComponentsV2(customId: string): DashboardModuleId | null { const decoded = decodeComponentCustomId(customId); if (!decoded) { return null; } switch (decoded.source) { case 'w': return 'welcome'; case 't': return 'tags'; case 's': return 'scheduler'; case 'm': return 'messages'; default: return 'messages'; } } export async function routeComponentInteraction( interaction: AnyComponentInteraction, context: BotContext ): Promise { const customId = interaction.customId; const locale = await getGuildLocale(context.prisma, interaction.guildId); const maintenance = await isMaintenanceMode(context); const ownerIds = env.OWNER_USER_IDS ?? []; if (maintenance.enabled && !ownerIds.includes(interaction.user.id)) { if (!interaction.replied && !interaction.deferred) { await interaction.reply({ content: maintenance.message?.trim() || t(locale, 'generic.maintenance'), ...ephemeral() }); } return true; } let moduleId = moduleForComponent(customId); if (!moduleId && isComponentsV2Interaction(customId)) { moduleId = moduleForComponentsV2(customId); } if (moduleId && interaction.guildId) { const enabled = await isModuleEnabled( context.prisma, context.redis, interaction.guildId, moduleId ); if (!enabled) { if (!interaction.replied && !interaction.deferred) { await interaction.reply({ content: t(locale, 'generic.moduleDisabled'), ...ephemeral() }); } return true; } } for (const handler of handlers) { if (handler.match(customId)) { await handler.handle(interaction, context); return true; } } return false; }