- Added support for multiple captcha providers including Google reCAPTCHA (v2 and v3), hCaptcha, and Cloudflare Turnstile. - Introduced new fields in the verification configuration for selecting captcha providers and enabling alt-account detection. - Implemented logic to handle verification attempts and flag potential alt accounts based on IP and invite code analysis. - Updated environment configuration to include necessary keys for captcha providers. - Enhanced the user interface to allow selection of captcha providers in the verification setup. - Improved backend handling of verification records to store additional data related to captcha provider usage.
123 lines
4.1 KiB
TypeScript
123 lines
4.1 KiB
TypeScript
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];
|