256 lines
12 KiB
TypeScript
256 lines
12 KiB
TypeScript
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.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<TextInputBuilder>().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<TextInputBuilder>().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<UserSelectMenuBuilder>().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<UserSelectMenuBuilder>().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<UserSelectMenuBuilder>().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);
|
|
}
|
|
}
|
|
},
|
|
};
|