230 lines
8.8 KiB
TypeScript
230 lines
8.8 KiB
TypeScript
import {
|
|
EmbedBuilder,
|
|
ActionRowBuilder,
|
|
ButtonBuilder,
|
|
ButtonStyle,
|
|
StringSelectMenuBuilder,
|
|
ChannelSelectMenuBuilder,
|
|
ChannelType,
|
|
MessageComponentInteraction,
|
|
ChatInputCommandInteraction,
|
|
Colors,
|
|
} from 'discord.js';
|
|
import { SupportedLocale, t } from '../i18n';
|
|
import { PermissionAuditService } from './PermissionAuditService';
|
|
import { prisma } from '../database';
|
|
|
|
export class SetupWizardRenderer {
|
|
static async renderStep(
|
|
step: number,
|
|
interaction: MessageComponentInteraction | ChatInputCommandInteraction,
|
|
locale: SupportedLocale
|
|
): Promise<{ embed: EmbedBuilder; components: ActionRowBuilder<any>[] }> {
|
|
const embed = new EmbedBuilder().setColor(Colors.Blurple);
|
|
const components: ActionRowBuilder<any>[] = [];
|
|
|
|
switch (step) {
|
|
case 0: {
|
|
embed.setTitle(t(locale, 'commands.setup.step0.title'))
|
|
.setDescription(t(locale, 'commands.setup.step0.desc'));
|
|
|
|
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
new ButtonBuilder()
|
|
.setCustomId('setup_next_1')
|
|
.setLabel(t(locale, 'commands.setup.step0.startBtn'))
|
|
.setStyle(ButtonStyle.Primary)
|
|
);
|
|
components.push(row);
|
|
break;
|
|
}
|
|
|
|
case 1: {
|
|
embed.setTitle(t(locale, 'commands.setup.step1.title'))
|
|
.setDescription(t(locale, 'commands.setup.step1.desc', { locale: locale === 'ko' ? 'Korean' : 'English' }));
|
|
|
|
const select = new StringSelectMenuBuilder()
|
|
.setCustomId('setup_lang_select')
|
|
.setPlaceholder(t(locale, 'commands.setup.step1.placeholder'))
|
|
.addOptions([
|
|
{ label: 'Korean', value: 'ko', description: '한국어로 봇을 설정합니다.' },
|
|
{ label: 'English', value: 'en', description: 'Set bot to English.' }
|
|
]);
|
|
|
|
const btnRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
new ButtonBuilder()
|
|
.setCustomId('setup_next_2')
|
|
.setLabel(t(locale, 'commands.setup.step1.nextBtn'))
|
|
.setStyle(ButtonStyle.Secondary)
|
|
);
|
|
|
|
components.push(new ActionRowBuilder<StringSelectMenuBuilder>().addComponents(select));
|
|
components.push(btnRow);
|
|
break;
|
|
}
|
|
|
|
case 2: {
|
|
if (!interaction.guild) throw new Error('Guild not found');
|
|
const results = await PermissionAuditService.auditGuild(interaction.guild);
|
|
const hasFails = results.some(r => r.status === 'FAIL');
|
|
|
|
embed.setTitle(t(locale, 'commands.setup.step2.title'));
|
|
if (hasFails) {
|
|
embed.setDescription(t(locale, 'commands.setup.step2.descFail'))
|
|
.setColor(Colors.Red);
|
|
// ⚠️ 권한 부족 목록 렌더링
|
|
const failLines = results
|
|
.filter(r => r.status === 'FAIL')
|
|
.map(r => `- **${t(locale, `commands.permissionAudit.features.${r.featureKey as any}`) || r.featureKey}**\n > \`${r.missingPermissions.join('`, `')}\``);
|
|
if (failLines.length > 0) {
|
|
embed.addFields({ name: 'Missing Permissions', value: failLines.join('\n') });
|
|
}
|
|
} else {
|
|
embed.setDescription(t(locale, 'commands.setup.step2.descOk'))
|
|
.setColor(Colors.Green);
|
|
}
|
|
|
|
const btnRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
new ButtonBuilder()
|
|
.setCustomId('setup_refresh_2')
|
|
.setLabel(t(locale, 'commands.setup.step2.recheckBtn'))
|
|
.setStyle(ButtonStyle.Secondary),
|
|
new ButtonBuilder()
|
|
.setCustomId('setup_next_3')
|
|
.setLabel(t(locale, 'commands.setup.step2.nextBtn'))
|
|
.setStyle(hasFails ? ButtonStyle.Danger : ButtonStyle.Primary)
|
|
);
|
|
|
|
components.push(btnRow);
|
|
break;
|
|
}
|
|
|
|
case 3: {
|
|
embed.setTitle(t(locale, 'commands.setup.step3.title'))
|
|
.setDescription(t(locale, 'commands.setup.step3.desc'));
|
|
|
|
const select = new ChannelSelectMenuBuilder()
|
|
.setCustomId('setup_audit_select')
|
|
.setPlaceholder(t(locale, 'commands.setup.step3.placeholder'))
|
|
.setChannelTypes(ChannelType.GuildText);
|
|
|
|
const btnRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
new ButtonBuilder()
|
|
.setCustomId('setup_audit_disable')
|
|
.setLabel(t(locale, 'commands.setup.step3.disableBtn'))
|
|
.setStyle(ButtonStyle.Danger),
|
|
new ButtonBuilder()
|
|
.setCustomId('setup_next_4')
|
|
.setLabel(t(locale, 'commands.setup.step3.nextBtn'))
|
|
.setStyle(ButtonStyle.Secondary)
|
|
);
|
|
|
|
components.push(new ActionRowBuilder<ChannelSelectMenuBuilder>().addComponents(select));
|
|
components.push(btnRow);
|
|
break;
|
|
}
|
|
|
|
case 4: {
|
|
const audit = await prisma.auditChannel.findUnique({ where: { guildId: interaction.guildId! } });
|
|
const disabled = audit?.disabledCategories || [];
|
|
|
|
embed.setTitle(t(locale, 'commands.setup.step4.title'))
|
|
.setDescription(t(locale, 'commands.setup.step4.desc'));
|
|
|
|
const categories: ('BOOT' | 'SYSTEM' | 'VOICE' | 'PERMISSION' | 'INVITE')[] = ['BOOT', 'SYSTEM', 'VOICE', 'PERMISSION', 'INVITE'];
|
|
const row1 = new ActionRowBuilder<ButtonBuilder>();
|
|
|
|
categories.forEach(cat => {
|
|
const isEnabled = !disabled.includes(cat);
|
|
row1.addComponents(
|
|
new ButtonBuilder()
|
|
.setCustomId(`setup_audit_toggle_${cat}`)
|
|
.setLabel(t(locale, `commands.setup.auditCategories.${cat}`))
|
|
.setStyle(isEnabled ? ButtonStyle.Success : ButtonStyle.Danger)
|
|
);
|
|
});
|
|
|
|
const row2 = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
new ButtonBuilder()
|
|
.setCustomId('setup_next_5')
|
|
.setLabel(t(locale, 'commands.setup.step4.nextBtn'))
|
|
.setStyle(ButtonStyle.Primary)
|
|
);
|
|
|
|
components.push(row1, row2);
|
|
break;
|
|
}
|
|
|
|
case 5: {
|
|
embed.setTitle(t(locale, 'commands.setup.step5.title'))
|
|
.setDescription(t(locale, 'commands.setup.step5.desc'));
|
|
|
|
const select = new ChannelSelectMenuBuilder()
|
|
.setCustomId('setup_voice_select')
|
|
.setPlaceholder(t(locale, 'commands.setup.step5.placeholder'))
|
|
.setChannelTypes(ChannelType.GuildVoice);
|
|
|
|
const btnRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
new ButtonBuilder()
|
|
.setCustomId('setup_voice_auto')
|
|
.setLabel(t(locale, 'commands.setup.step5.autoBtn'))
|
|
.setStyle(ButtonStyle.Success),
|
|
new ButtonBuilder()
|
|
.setCustomId('setup_voice_disable')
|
|
.setLabel(t(locale, 'commands.setup.step5.skipBtn'))
|
|
.setStyle(ButtonStyle.Danger),
|
|
new ButtonBuilder()
|
|
.setCustomId('setup_next_6')
|
|
.setLabel(t(locale, 'commands.setup.step5.nextBtn'))
|
|
.setStyle(ButtonStyle.Secondary)
|
|
);
|
|
|
|
components.push(new ActionRowBuilder<ChannelSelectMenuBuilder>().addComponents(select));
|
|
components.push(btnRow);
|
|
break;
|
|
}
|
|
|
|
case 6: {
|
|
if (!interaction.guild) throw new Error('Guild not found');
|
|
|
|
const config = await prisma.guildConfig.findUnique({ where: { guildId: interaction.guild.id } });
|
|
const audit = await prisma.auditChannel.findUnique({ where: { guildId: interaction.guild.id } });
|
|
const voice = await prisma.voiceGenerator.findFirst({ where: { guildId: interaction.guild.id } });
|
|
|
|
embed.setTitle(t(locale, 'commands.setup.step6.title'))
|
|
.setColor(Colors.Green);
|
|
|
|
const langStr = config?.locale === 'ko' ? 'Korean' : 'English';
|
|
const auditStr = audit?.channelId ? `<#${audit.channelId}>` : 'Disabled';
|
|
const voiceStr = voice?.channelId ? `<#${voice.channelId}>` : 'Disabled';
|
|
|
|
// 감사 로그 카테고리 요약
|
|
let catStr = 'None';
|
|
if (audit?.channelId) {
|
|
const allCats: ('BOOT' | 'SYSTEM' | 'VOICE' | 'PERMISSION' | 'INVITE')[] = ['BOOT', 'SYSTEM', 'VOICE', 'PERMISSION', 'INVITE'];
|
|
const enabled = allCats.filter(c => !audit.disabledCategories.includes(c));
|
|
catStr = enabled.map(c => t(locale, `commands.setup.auditCategories.${c}`)).join(', ');
|
|
}
|
|
|
|
embed.setDescription(t(locale, 'commands.setup.step6.desc', {
|
|
lang: langStr,
|
|
audit: auditStr,
|
|
categories: catStr,
|
|
voice: voiceStr
|
|
}));
|
|
|
|
const btnRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
|
new ButtonBuilder()
|
|
.setCustomId('setup_finish')
|
|
.setLabel(t(locale, 'commands.setup.step6.finishBtn'))
|
|
.setStyle(ButtonStyle.Success)
|
|
);
|
|
|
|
components.push(btnRow);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return { embed, components };
|
|
}
|
|
}
|