import { Events, Interaction, ModalBuilder, TextInputBuilder, TextInputStyle, ActionRowBuilder, ChannelType, VoiceChannel, PermissionFlagsBits, UserSelectMenuBuilder, GuildMember } from 'discord.js'; import { KordClient } from '../client/KordClient'; import { logger } from '../utils/logger'; import { prisma } from '../database'; import { BotError } from '../errors/BotError'; import { ErrorDefs, createBotError } from '../errors/ErrorCodes'; import { ErrorReporter, withErrorHandler } from '../errors/ErrorReporter'; import { t } from '../i18n'; import { getInteractionLocale } from '../i18n/localeHelper'; import { handleSetupWizardInteraction } from '../interactions/handlers/setupWizardHandler'; import { MusicService } from '../services/MusicService'; export default { name: Events.InteractionCreate, once: false, async execute(interaction: Interaction, client: KordClient) { if (interaction.isChatInputCommand()) { const command = client.commands.get(interaction.commandName); if (!command) return; const locale = await getInteractionLocale(interaction); await withErrorHandler(interaction, async () => { await command.execute(interaction, locale); }, locale); } else if (interaction.isButton() && interaction.customId.startsWith('music_')) { const locale = await getInteractionLocale(interaction); await withErrorHandler(interaction, async () => { await MusicService.handleControlInteraction(interaction, locale); }, locale); } else if (interaction.isButton() && interaction.customId.startsWith('fishing_')) { const { FishingService } = require('../services/FishingService'); const locale = await getInteractionLocale(interaction); await withErrorHandler(interaction, async () => { await FishingService.handleButton(interaction, locale); }, locale); } else if (interaction.isMessageComponent() && interaction.customId.startsWith('setup_')) { const locale = await getInteractionLocale(interaction); await withErrorHandler(interaction, async () => { await handleSetupWizardInteraction(interaction, locale); }, locale); } else if (interaction.isButton() && interaction.customId.startsWith('refine_')) { const { handleRefinementInteraction } = require('../interactions/handlers/refinementHandler'); const locale = await getInteractionLocale(interaction); await withErrorHandler(interaction, async () => { await handleRefinementInteraction(interaction, locale); }, locale); } else if (interaction.isStringSelectMenu()) { const customId = interaction.customId; if (customId.startsWith('vc_control_')) { const locale = await getInteractionLocale(interaction); await withErrorHandler(interaction, async () => { const parts = customId.split('_'); const ownerId = parts[2]; const action = interaction.values[0]; if (interaction.user.id !== ownerId) { throw createBotError(ErrorDefs.NOT_CHANNEL_OWNER); } const member = interaction.member as GuildMember; const voiceChannel = member?.voice?.channel as VoiceChannel; if (!voiceChannel || interaction.channelId !== voiceChannel.id) { const tempDb = await prisma.tempVoiceChannel.findUnique({ where: { channelId: voiceChannel?.id || '' }}); if (!tempDb || tempDb.ownerId !== ownerId) { throw createBotError(ErrorDefs.MUST_BE_IN_VOICE_CHANNEL); } } if (action === 'rename') { const modal = new ModalBuilder() .setCustomId(`modal_vc_rename_${ownerId}`) .setTitle(t(locale, 'modals.renameTitle')); const nameInput = new TextInputBuilder() .setCustomId('newName') .setLabel(t(locale, 'modals.renameLabel')) .setStyle(TextInputStyle.Short) .setValue(voiceChannel.name) .setRequired(true) .setMaxLength(100); modal.addComponents(new ActionRowBuilder().addComponents(nameInput)); await interaction.showModal(modal); } else if (action === 'limit') { const modal = new ModalBuilder() .setCustomId(`modal_vc_limit_${ownerId}`) .setTitle(t(locale, 'modals.limitTitle')); const limitInput = new TextInputBuilder() .setCustomId('limit') .setLabel(t(locale, 'modals.limitLabel')) .setStyle(TextInputStyle.Short) .setValue(voiceChannel.userLimit.toString()) .setRequired(true) .setMaxLength(2); modal.addComponents(new ActionRowBuilder().addComponents(limitInput)); await interaction.showModal(modal); } else if (action === 'lock') { const everyonePerms = voiceChannel.permissionOverwrites.cache.get(voiceChannel.guild.id); const isLocked = everyonePerms?.deny.has(PermissionFlagsBits.Connect); if (isLocked) { await voiceChannel.permissionOverwrites.edit(voiceChannel.guild.id, { Connect: null }); await interaction.reply({ content: t(locale, 'voice.responses.channelUnlocked'), ephemeral: true }); } else { await voiceChannel.permissionOverwrites.edit(voiceChannel.guild.id, { Connect: false }); await interaction.reply({ content: t(locale, 'voice.responses.channelLocked'), ephemeral: true }); } } else if (action === 'kick') { const select = new UserSelectMenuBuilder() .setCustomId(`select_vc_kick_${ownerId}`) .setPlaceholder(t(locale, 'selects.kickUser')) .setMinValues(1) .setMaxValues(1); await interaction.reply({ components: [new ActionRowBuilder().addComponents(select)], ephemeral: true }); } else if (action === 'ban') { const select = new UserSelectMenuBuilder() .setCustomId(`select_vc_ban_${ownerId}`) .setPlaceholder(t(locale, 'selects.banUser')) .setMinValues(1) .setMaxValues(1); await interaction.reply({ content: t(locale, 'voice.responses.banPrompt'), components: [new ActionRowBuilder().addComponents(select)], ephemeral: true }); } else if (action === 'transfer') { const select = new UserSelectMenuBuilder() .setCustomId(`select_vc_transfer_${ownerId}`) .setPlaceholder(t(locale, 'selects.transferOwner')) .setMinValues(1) .setMaxValues(1); await interaction.reply({ content: t(locale, 'voice.responses.transferPrompt'), components: [new ActionRowBuilder().addComponents(select)], ephemeral: true }); } }, locale); } } else if (interaction.isModalSubmit()) { const customId = interaction.customId; if (customId.startsWith('modal_vc_')) { const locale = await getInteractionLocale(interaction); await withErrorHandler(interaction, async () => { const parts = customId.split('_'); const action = parts[2]; const ownerId = parts[3]; if (interaction.user.id !== ownerId) { throw createBotError(ErrorDefs.NOT_CHANNEL_OWNER); } const member = interaction.member as GuildMember; const voiceChannel = member?.voice?.channel as VoiceChannel; if (!voiceChannel) { throw createBotError(ErrorDefs.MUST_BE_IN_VOICE_CHANNEL); } if (action === 'rename') { const newName = interaction.fields.getTextInputValue('newName'); await voiceChannel.setName(newName); await prisma.userVoiceProfile.upsert({ where: { userId_guildId: { userId: ownerId, guildId: interaction.guildId! } }, update: { customName: newName }, create: { userId: ownerId, guildId: interaction.guildId!, customName: newName } }); await interaction.reply({ content: t(locale, 'voice.responses.channelRenamed', { name: newName }), ephemeral: true }); } else if (action === 'limit') { const limitStr = interaction.fields.getTextInputValue('limit'); const limit = parseInt(limitStr); if (isNaN(limit) || limit < 0 || limit > 99) { throw createBotError(ErrorDefs.INVALID_USER_LIMIT); } await voiceChannel.setUserLimit(limit); await prisma.userVoiceProfile.upsert({ where: { userId_guildId: { userId: ownerId, guildId: interaction.guildId! } }, update: { userLimit: limit }, create: { userId: ownerId, guildId: interaction.guildId!, userLimit: limit } }); const limitDisplay = limit === 0 ? t(locale, 'voice.responses.limitUnlimited') : String(limit); await interaction.reply({ content: t(locale, 'voice.responses.limitSet', { limit: limitDisplay }), ephemeral: true }); } }, locale); } } else if (interaction.isUserSelectMenu()) { const customId = interaction.customId; if (customId.startsWith('select_vc_')) { const locale = await getInteractionLocale(interaction); await withErrorHandler(interaction, async () => { const parts = customId.split('_'); const action = parts[2]; const ownerId = parts[3]; if (interaction.user.id !== ownerId) { throw createBotError(ErrorDefs.NOT_CHANNEL_OWNER); } const member = interaction.member as GuildMember; const voiceChannel = member?.voice?.channel as VoiceChannel; if (!voiceChannel) { throw createBotError(ErrorDefs.MUST_BE_IN_VOICE_CHANNEL); } const targetUserId = interaction.values[0]; if (targetUserId === ownerId) { throw createBotError(ErrorDefs.SELF_TARGET_NOT_ALLOWED); } if (action === 'kick') { const targetMember = await interaction.guild?.members.fetch(targetUserId); if (targetMember && targetMember.voice.channelId === voiceChannel.id) { await targetMember.voice.disconnect('Kicked by channel owner'); await interaction.reply({ content: t(locale, 'voice.responses.kicked', { user: `<@${targetUserId}>` }), ephemeral: true }); } else { throw createBotError(ErrorDefs.USER_NOT_IN_CHANNEL); } } else if (action === 'ban') { await voiceChannel.permissionOverwrites.edit(targetUserId, { ViewChannel: false, Connect: false }); const targetMember = await interaction.guild?.members.fetch(targetUserId); if (targetMember && targetMember.voice.channelId === voiceChannel.id) { await targetMember.voice.disconnect('Banned by channel owner'); } await interaction.reply({ content: t(locale, 'voice.responses.banned', { user: `<@${targetUserId}>` }), ephemeral: true }); } else if (action === 'transfer') { const targetMember = await interaction.guild?.members.fetch(targetUserId); if (targetMember && targetMember.voice.channelId === voiceChannel.id) { await prisma.tempVoiceChannel.update({ where: { channelId: voiceChannel.id }, data: { ownerId: targetUserId } }); const { VoiceService } = require('../services/VoiceService'); await VoiceService.applyOwnershipTransfer(voiceChannel, ownerId, targetUserId); await interaction.reply({ content: t(locale, 'voice.responses.transferDone', { user: `<@${targetUserId}>` }), ephemeral: true }); } else { throw createBotError(ErrorDefs.USER_NOT_IN_CHANNEL); } } }, locale); } } }, };