import { SlashCommandBuilder, PermissionFlagsBits, ChatInputCommandInteraction, ChannelType, EmbedBuilder, TextChannel } from 'discord.js'; import { prisma } from '../database'; import { SupportedLocale, t } from '../i18n'; import { logger } from '../utils/logger'; export default { data: new SlashCommandBuilder() .setName('voice') .setDescription('Manage temporary voice channels.') .setDescriptionLocalizations({ ko: '임시 음성 채널 설정을 관리합니다.', }) .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) // --- Generator Subcommand --- .addSubcommand(subcommand => subcommand .setName('generator') .setDescription('Configure the voice generator channel.') .setDescriptionLocalizations({ ko: '음성 생성기 채널을 설정합니다.' }) .addStringOption(option => option.setName('action') .setDescription('Action to perform') .setRequired(true) .addChoices( { name: 'set (Set Existing)', value: 'set' }, { name: 'create (Create New)', value: 'create' }, ) ) .addChannelOption(option => option.setName('channel') .setDescription('Voice channel (for set action)') .addChannelTypes(ChannelType.GuildVoice) ) .addStringOption(option => option.setName('name') .setDescription('New channel name (for create action)') ) .addChannelOption(option => option.setName('category') .setDescription('Target category for temp channels') .addChannelTypes(ChannelType.GuildCategory) ) ) // --- Settings Subcommand --- .addSubcommand(subcommand => subcommand .setName('settings') .setDescription('Manage default settings for temporary channels.') .setDescriptionLocalizations({ ko: '임시 채널의 기본 설정을 관리합니다.' }) .addStringOption(option => option.setName('action') .setDescription('Action to perform') .setRequired(true) .addChoices( { name: 'name (Template)', value: 'name' }, { name: 'limit (User Count)', value: 'limit' }, { name: 'status (Check Config)', value: 'status' }, ) ) .addStringOption(option => option.setName('template') .setDescription('Naming template (for name action)') ) .addIntegerOption(option => option.setName('limit') .setDescription('User limit (for limit action)') .setMinValue(0) .setMaxValue(99) ) ), async execute(interaction: ChatInputCommandInteraction, locale: SupportedLocale) { const subcommand = interaction.options.getSubcommand(); const guildId = interaction.guildId!; try { // --- GENERATOR --- if (subcommand === 'generator') { const action = interaction.options.getString('action', true); const category = interaction.options.getChannel('category'); if (action === 'set') { const channel = interaction.options.getChannel('channel'); if (!channel) return interaction.reply({ content: '❌ `channel` 옵션을 선택해주세요.', ephemeral: true }); await prisma.voiceGenerator.upsert({ where: { channelId: channel.id }, update: { categoryId: category?.id || null, guildId }, create: { channelId: channel.id, guildId, categoryId: category?.id || null } }); return interaction.reply({ content: t(locale, 'commands.voiceSetup.setSuccess', { channel: `${channel}` }), ephemeral: true }); } if (action === 'create') { const name = interaction.options.getString('name'); if (!name) return interaction.reply({ content: '❌ `name` 옵션을 입력해주세요.', ephemeral: true }); const newChannel = await interaction.guild!.channels.create({ name, type: ChannelType.GuildVoice, parent: category?.id || null, }); await prisma.voiceGenerator.create({ data: { channelId: newChannel.id, guildId, categoryId: category?.id || null } }); return interaction.reply({ content: t(locale, 'commands.voiceSetup.createSuccess', { channel: `${newChannel}` }), ephemeral: true }); } } // --- SETTINGS --- if (subcommand === 'settings') { const action = interaction.options.getString('action', true); if (action === 'name') { const template = interaction.options.getString('template'); if (!template) return interaction.reply({ content: '❌ `template` 옵션을 입력해주세요.', ephemeral: true }); await prisma.voiceGuildConfig.upsert({ where: { guildId }, update: { defaultNameTemplate: template }, create: { guildId, defaultNameTemplate: template } }); return interaction.reply({ content: t(locale, 'commands.voiceConfig.setSuccess'), ephemeral: true }); } if (action === 'limit') { const limit = interaction.options.getInteger('limit'); if (limit === null) return interaction.reply({ content: '❌ `limit` 옵션을 입력해주세요.', ephemeral: true }); await prisma.voiceGuildConfig.upsert({ where: { guildId }, update: { defaultUserLimit: limit }, create: { guildId, defaultUserLimit: limit } }); return interaction.reply({ content: t(locale, 'commands.voiceConfig.setSuccess'), ephemeral: true }); } if (action === 'status') { const config = await prisma.voiceGuildConfig.findUnique({ where: { guildId } }); const embed = new EmbedBuilder() .setTitle(t(locale, 'commands.voiceConfig.statusTitle')) .setColor(0x5865F2) .addFields( { name: t(locale, 'commands.voiceConfig.templateLabel'), value: `\`${config?.defaultNameTemplate || t(locale, 'voice.defaultRoomName')}\``, inline: true }, { name: t(locale, 'commands.voiceConfig.limitLabel'), value: t(locale, 'commands.voiceConfig.limitValue', { limit: String(config?.defaultUserLimit ?? 0) }), inline: true } ); return interaction.reply({ embeds: [embed], ephemeral: true }); } } } catch (error) { logger.error('Error in voice command', error); return interaction.reply({ content: t(locale, 'errors.E3003.userMessage'), ephemeral: true }); } }, };