Kord/src/commands/voice.ts

204 lines
8.2 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)
// --- 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
});
}
},
};