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) // --- Setup Subcommands --- .addSubcommandGroup(group => group .setName('setup') .setDescription('Configure the voice generator channel.') .setDescriptionLocalizations({ ko: '음성 생성기 채널을 설정합니다.' }) .addSubcommand(subcommand => subcommand .setName('set') .setDescription('Set an existing voice channel as a Generator') .setDescriptionLocalizations({ ko: '기존 음성 채널을 생성기로 설정합니다' }) .addChannelOption(option => option.setName('channel') .setDescription('The voice channel to act as the Generator') .setDescriptionLocalizations({ ko: '생성기로 사용할 음성 채널' }) .setRequired(true) .addChannelTypes(ChannelType.GuildVoice) ) .addChannelOption(option => option.setName('category') .setDescription('(Optional) The category where temp channels will be created') .setDescriptionLocalizations({ ko: '(선택) 임시 채널이 생성될 카테고리' }) .setRequired(false) .addChannelTypes(ChannelType.GuildCategory) ) ) .addSubcommand(subcommand => subcommand .setName('create') .setDescription('Create a new voice channel and set it as a Generator') .setDescriptionLocalizations({ ko: '새 음성 채널을 만들고 생성기로 설정합니다' }) .addStringOption(option => option.setName('name') .setDescription('The name of the new generator voice channel') .setDescriptionLocalizations({ ko: '새 생성기 음성 채널의 이름' }) .setRequired(true) ) .addChannelOption(option => option.setName('category') .setDescription('(Optional) The category where the new channel will be created') .setDescriptionLocalizations({ ko: '(선택) 새 채널이 생성될 카테고리' }) .setRequired(false) .addChannelTypes(ChannelType.GuildCategory) ) ) ) // --- Config Subcommands --- .addSubcommandGroup(group => group .setName('config') .setDescription('Manage default settings for temporary channels.') .setDescriptionLocalizations({ ko: '임시 채널의 기본 설정을 관리합니다.' }) .addSubcommand(subcommand => subcommand .setName('name') .setDescription('Set the default naming template for new temp channels.') .setDescriptionLocalizations({ ko: '임시 채널의 기본 이름 템플릿을 설정합니다.' }) .addStringOption(option => option.setName('template') .setDescription('Template using {{username}} placeholder') .setDescriptionLocalizations({ ko: '{{username}}을 포함한 이름 템플릿' }) .setRequired(true) ) ) .addSubcommand(subcommand => subcommand .setName('limit') .setDescription('Set the default user limit for new temp channels.') .setDescriptionLocalizations({ ko: '임시 채널의 기본 인원 제한을 설정합니다.' }) .addIntegerOption(option => option.setName('limit') .setDescription('User limit (0-99, 0 = unlimited)') .setDescriptionLocalizations({ ko: '인원 제한 (0-99, 0 = 무제한)' }) .setRequired(true) .setMinValue(0) .setMaxValue(99) ) ) .addSubcommand(subcommand => subcommand .setName('status') .setDescription('View current guild voice settings.') .setDescriptionLocalizations({ ko: '현재 서버의 음성 설정을 확인합니다.' }) ) ), async execute(interaction: ChatInputCommandInteraction, locale: SupportedLocale) { const group = interaction.options.getSubcommandGroup(); const subcommand = interaction.options.getSubcommand(); const guildId = interaction.guildId!; try { // --- SETUP GROUP --- if (group === 'setup') { const category = interaction.options.getChannel('category'); if (subcommand === 'set') { const channel = interaction.options.getChannel('channel', 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 (subcommand === 'create') { const name = interaction.options.getString('name', 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 }); } } // --- CONFIG GROUP --- if (group === 'config') { if (subcommand === 'name') { const template = interaction.options.getString('template', 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 (subcommand === 'limit') { const limit = interaction.options.getInteger('limit', 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 (subcommand === '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 }); } }, };