import { SlashCommandBuilder, ChatInputCommandInteraction, PermissionFlagsBits, EmbedBuilder, Colors, RoleSelectMenuBuilder, ActionRowBuilder } from 'discord.js'; import { Command, CommandTrait } from '../core/command'; import { prisma } from '../database'; import { t, SupportedLocale } from '../i18n'; import { InviteService } from '../services/InviteService'; import { auditLogService } from '../services/AuditLogService'; class InviteCommand extends Command { protected override readonly trait = CommandTrait.General; protected override guildOnly = true; protected override define() { return new SlashCommandBuilder() .setName('invite') .setDescription('Manage roles mapped to invite codes.') .setDescriptionLocalizations({ ko: '초대 코드와 역할을 연동하여 관리합니다.', }) .setDefaultMemberPermissions(PermissionFlagsBits.Administrator) .addSubcommand(subcommand => subcommand .setName('list') .setDescription('List all invite codes in the server.') .setDescriptionLocalizations({ ko: '서버의 초대 코드 목록을 조회합니다.' }) .addStringOption(option => option.setName('filter') .setDescription('Lookup filter') .addChoices( { name: 'all', value: 'all' }, { name: 'managed', value: 'managed' } ) ) ) .addSubcommand(subcommand => subcommand .setName('link') .setDescription('Link a role to an existing invite code.') .setDescriptionLocalizations({ ko: '기존 초대 코드에 역할을 연동합니다.' }) .addStringOption(option => option.setName('code') .setDescription('Invite code string') .setRequired(true) ) .addRoleOption(option => option.setName('role') .setDescription('Role to assign') .setRequired(true) ) ) .addSubcommand(subcommand => subcommand .setName('create') .setDescription('Create a new invite code with a mapped role.') .setDescriptionLocalizations({ ko: '역할이 연동된 새로운 초대 코드를 생성합니다.' }) .addRoleOption(option => option.setName('role') .setDescription('Role to assign') .setRequired(true) ) .addIntegerOption(option => option.setName('max_uses') .setDescription('Maximum uses') ) .addIntegerOption(option => option.setName('max_age') .setDescription('Expiration (seconds)') ) ) .addSubcommand(subcommand => subcommand .setName('unlink') .setDescription('Unlink a role from an invite code.') .setDescriptionLocalizations({ ko: '초대 코드에 연동된 역할을 해제합니다.' }) .addStringOption(option => option.setName('code') .setDescription('Invite code string') .setRequired(true) ) ); } protected override async handle(interaction: ChatInputCommandInteraction, locale: SupportedLocale) { const subcommand = interaction.options.getSubcommand(); const guild = interaction.guild!; if (subcommand === 'list') { const filter = interaction.options.getString('filter') || 'managed'; const mappings = await prisma.inviteRole.findMany({ where: { guildId: guild.id } }); let displayInvites: any[] = []; if (filter === 'all') { const invites = await guild.invites.fetch(); displayInvites = invites.map(inv => ({ code: inv.code, roleId: mappings.find(m => m.inviteCode === inv.code)?.roleId, uses: inv.uses, maxUses: inv.maxUses, })); } else { displayInvites = mappings.map(m => ({ code: m.inviteCode, roleId: m.roleId, })); } if (displayInvites.length === 0) { await interaction.reply({ content: t(locale, 'commands.invite.listEmpty'), ephemeral: true }); return; } const embed = new EmbedBuilder() .setTitle(t(locale, 'commands.invite.listTitle')) .setColor(Colors.Blue) .setDescription(displayInvites.map(inv => `\`${inv.code}\`: ${inv.roleId ? `<@&${inv.roleId}>` : t(locale, 'autorole.notSet')}`).join('\n')); await interaction.reply({ embeds: [embed], ephemeral: true }); return; } if (subcommand === 'link') { const code = interaction.options.getString('code', true); const role = interaction.options.getRole('role', true); await prisma.inviteRole.upsert({ where: { guildId_inviteCode: { guildId: guild.id, inviteCode: code, }, }, update: { roleId: role.id }, create: { guildId: guild.id, inviteCode: code, roleId: role.id, }, }); await InviteService.cacheGuildInvites(guild); await interaction.reply({ content: t(locale, 'commands.invite.linkSuccess', { code, role: role.name }), ephemeral: true }); return; } if (subcommand === 'create') { const role = interaction.options.getRole('role', true); const maxUses = interaction.options.getInteger('max_uses') || 0; const maxAge = interaction.options.getInteger('max_age') || 0; const invite = await guild.invites.create(interaction.channelId, { maxUses, maxAge, unique: true, }); await prisma.inviteRole.create({ data: { guildId: guild.id, inviteCode: invite.code, roleId: role.id, }, }); await InviteService.cacheGuildInvites(guild); await interaction.reply({ content: t(locale, 'commands.invite.createSuccess', { code: invite.code, role: role.name }), ephemeral: true }); return; } if (subcommand === 'unlink') { const code = interaction.options.getString('code', true); await prisma.inviteRole.deleteMany({ where: { guildId: guild.id, inviteCode: code, }, }); await InviteService.cacheGuildInvites(guild); await interaction.reply({ content: t(locale, 'commands.invite.unlinkSuccess', { code }), ephemeral: true }); return; } } } export default new InviteCommand().toModule();