import { PermissionFlagsBits, type GuildBasedChannel, type GuildTextBasedChannel } from 'discord.js'; import { t } from '@nexumi/shared'; import type { SlashCommand } from '../../types.js'; import { getGuildLocale } from '../../i18n.js'; import { requirePermission } from '../../permissions.js'; import { ephemeral } from '../../interaction-reply.js'; import { verifyCommandData } from './command-definitions.js'; import { getVerificationConfig, updateVerificationConfig } from './service.js'; import { buildVerificationPanel } from './handlers.js'; function botCanPostPanel( channel: GuildBasedChannel, meId: string ): channel is GuildTextBasedChannel { if (!channel.isTextBased() || channel.isDMBased()) { return false; } const permissions = channel.permissionsFor(meId); if (!permissions) { return false; } return ( permissions.has(PermissionFlagsBits.ViewChannel) && permissions.has(PermissionFlagsBits.SendMessages) && permissions.has(PermissionFlagsBits.EmbedLinks) ); } const verifyCommand: SlashCommand = { data: verifyCommandData, async execute(interaction, context) { const locale = await getGuildLocale(context.prisma, interaction.guildId); if (!interaction.inGuild()) { await interaction.reply({ content: t(locale, 'generic.guildOnly'), ...ephemeral() }); return; } if (!(await requirePermission(interaction, PermissionFlagsBits.ManageGuild, locale))) { return; } const guildId = interaction.guildId!; const sub = interaction.options.getSubcommand(); if (sub === 'setup') { const channel = interaction.options.getChannel('channel', true); const verifiedRole = interaction.options.getRole('verified_role', true); const unverifiedRole = interaction.options.getRole('unverified_role'); const mode = (interaction.options.getString('mode') ?? 'BUTTON') as 'BUTTON' | 'CAPTCHA'; const captchaProvider = (interaction.options.getString('captcha_provider') ?? 'MATH') as | 'MATH' | 'RECAPTCHA_V2' | 'RECAPTCHA_V3' | 'HCAPTCHA' | 'TURNSTILE'; const minAccountAgeDays = interaction.options.getInteger('min_account_age_days') ?? 0; const failAction = (interaction.options.getString('fail_action') ?? 'KICK') as | 'KICK' | 'BAN' | 'NONE'; const altDetectionEnabled = interaction.options.getBoolean('alt_detection') ?? false; await updateVerificationConfig(context.prisma, guildId, { enabled: true, channelId: channel.id, verifiedRoleId: verifiedRole.id, unverifiedRoleId: unverifiedRole?.id ?? null, mode, captchaProvider, minAccountAgeDays, failAction, altDetectionEnabled }); await interaction.reply({ content: t(locale, 'verification.setupDone'), ...ephemeral() }); return; } const config = await getVerificationConfig(context.prisma, guildId); if (!config.enabled || !config.verifiedRoleId) { await interaction.reply({ content: t(locale, 'verification.notConfigured'), ...ephemeral() }); return; } const panelChannelOption = interaction.options.getChannel('channel'); const panelChannel = panelChannelOption ? await interaction.guild!.channels.fetch(panelChannelOption.id) : config.channelId ? await interaction.guild!.channels.fetch(config.channelId) : null; const me = interaction.guild!.members.me; if (!panelChannel || !me || !botCanPostPanel(panelChannel, me.id)) { await interaction.reply({ content: t(locale, 'verification.panelMissingPermission'), ...ephemeral() }); return; } const panel = buildVerificationPanel(config, locale); const message = await panelChannel.send(panel); await context.prisma.verificationConfig.update({ where: { guildId }, data: { panelChannelId: panelChannel.id, panelMessageId: message.id } }); await interaction.reply({ content: t(locale, 'verification.panelPosted'), ...ephemeral() }); } }; export const verificationCommands: SlashCommand[] = [verifyCommand];