Kord/src/commands/voice.ts

187 lines
6.9 KiB
TypeScript

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
});
}
},
};