Enhance verification module with improved command handling and localization
- Updated the verification command to utilize a new `ephemeral` utility for consistent ephemeral message replies. - Refactored the `buildVerificationPanel` function to include localization support for panel titles and descriptions. - Added permission checks to ensure the bot can post in the designated verification channel. - Introduced a new verification queue in the queue management system for better handling of verification tasks. - Updated localization files to include new keys for verification-related messages in both English and German.
This commit is contained in:
6
apps/bot/src/interaction-reply.ts
Normal file
6
apps/bot/src/interaction-reply.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { MessageFlags } from 'discord.js';
|
||||
|
||||
/** Discord.js v14+ prefers flags over the deprecated `ephemeral` option. */
|
||||
export function ephemeral(): { flags: typeof MessageFlags.Ephemeral } {
|
||||
return { flags: MessageFlags.Ephemeral };
|
||||
}
|
||||
@@ -1,21 +1,41 @@
|
||||
import { PermissionFlagsBits } from 'discord.js';
|
||||
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 { 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: true });
|
||||
await interaction.reply({ content: t(locale, 'generic.guildOnly'), ...ephemeral() });
|
||||
return;
|
||||
}
|
||||
if (!(await requirePermission(interaction, PermissionFlagsBits.ManageGuild, locale))) {
|
||||
@@ -46,13 +66,16 @@ const verifyCommand: SlashCommand = {
|
||||
failAction
|
||||
});
|
||||
|
||||
await interaction.reply({ content: t(locale, 'verification.setupDone'), ephemeral: true });
|
||||
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: true });
|
||||
await interaction.reply({
|
||||
content: t(locale, 'verification.notConfigured'),
|
||||
...ephemeral()
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -63,12 +86,16 @@ const verifyCommand: SlashCommand = {
|
||||
? await interaction.guild!.channels.fetch(config.channelId)
|
||||
: null;
|
||||
|
||||
if (!panelChannel?.isTextBased() || panelChannel.isDMBased()) {
|
||||
await interaction.reply({ content: t(locale, 'verification.invalidChannel'), ephemeral: true });
|
||||
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);
|
||||
const panel = buildVerificationPanel(config, locale);
|
||||
const message = await panelChannel.send(panel);
|
||||
await context.prisma.verificationConfig.update({
|
||||
where: { guildId },
|
||||
@@ -77,7 +104,7 @@ const verifyCommand: SlashCommand = {
|
||||
panelMessageId: message.id
|
||||
}
|
||||
});
|
||||
await interaction.reply({ content: t(locale, 'verification.panelPosted'), ephemeral: true });
|
||||
await interaction.reply({ content: t(locale, 'verification.panelPosted'), ...ephemeral() });
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
} from './service.js';
|
||||
import { env } from '../../env.js';
|
||||
import { logger } from '../../logger.js';
|
||||
import { ephemeral } from '../../interaction-reply.js';
|
||||
|
||||
async function applyFailAction(
|
||||
member: GuildMember,
|
||||
@@ -85,10 +86,13 @@ export async function completeVerification(
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
export function buildVerificationPanel(config: Awaited<ReturnType<typeof getVerificationConfig>>) {
|
||||
export function buildVerificationPanel(
|
||||
config: Awaited<ReturnType<typeof getVerificationConfig>>,
|
||||
locale: 'de' | 'en'
|
||||
) {
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle('Verification')
|
||||
.setDescription('Click the button below to verify and gain access to the server.')
|
||||
.setTitle(t(locale, 'verification.panel.title'))
|
||||
.setDescription(t(locale, 'verification.panel.description'))
|
||||
.setColor(0x6366f1);
|
||||
|
||||
const customId =
|
||||
@@ -99,7 +103,11 @@ export function buildVerificationPanel(config: Awaited<ReturnType<typeof getVeri
|
||||
const row = new ActionRowBuilder<ButtonBuilder>().addComponents(
|
||||
new ButtonBuilder()
|
||||
.setCustomId(customId)
|
||||
.setLabel(config.mode === 'CAPTCHA' ? 'Verify (Captcha)' : 'Verify')
|
||||
.setLabel(
|
||||
config.mode === 'CAPTCHA'
|
||||
? t(locale, 'verification.panel.buttonCaptcha')
|
||||
: t(locale, 'verification.panel.button')
|
||||
)
|
||||
.setStyle(ButtonStyle.Success)
|
||||
);
|
||||
|
||||
@@ -119,7 +127,7 @@ export async function handleVerificationButton(
|
||||
const config = await getVerificationConfig(context.prisma, parsed.guildId);
|
||||
|
||||
if (!config.enabled) {
|
||||
await interaction.reply({ content: t(locale, 'verification.disabled'), ephemeral: true });
|
||||
await interaction.reply({ content: t(locale, 'verification.disabled'), ...ephemeral() });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -129,15 +137,16 @@ export async function handleVerificationButton(
|
||||
parsed.guildId,
|
||||
interaction.user.id
|
||||
);
|
||||
const url = `${env.PUBLIC_BASE_URL}/verify/captcha?token=${challenge.token}`;
|
||||
const base = (env.WEBUI_URL ?? env.PUBLIC_BASE_URL).replace(/\/$/, '');
|
||||
const url = `${base}/verify/captcha?token=${challenge.token}`;
|
||||
await interaction.reply({
|
||||
content: tf(locale, 'verification.captchaLink', { url }),
|
||||
ephemeral: true
|
||||
...ephemeral()
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
await interaction.deferReply(ephemeral());
|
||||
const result = await completeVerification(context, parsed.guildId, interaction.user.id);
|
||||
if (result.ok) {
|
||||
await interaction.editReply({ content: t(locale, 'verification.success') });
|
||||
|
||||
Reference in New Issue
Block a user