193 lines
6.4 KiB
TypeScript
193 lines
6.4 KiB
TypeScript
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();
|