Files
Nexumi/apps/bot/src/modules/guildbackup/commands.ts
TheOnlyMace 8513848440 Implement command cooldown management and enhance command execution flow
- Introduced `applyCommandCooldown` function to manage per-command cooldowns after execution, improving command rate limiting.
- Updated command execution flow to include cooldown application, ensuring users are informed of cooldown status.
- Enhanced error handling in interaction replies, allowing for more graceful error management and user feedback.
- Improved the handling of interaction responses across various commands, ensuring consistent user experience.
- Added permission checks for new commands, ensuring only authorized users can execute specific actions.
2026-07-25 15:37:56 +02:00

210 lines
6.2 KiB
TypeScript

import { PermissionFlagsBits } from 'discord.js';
import { t, tf } from '@nexumi/shared';
import type { SlashCommand } from '../../types.js';
import { getGuildLocale } from '../../i18n.js';
import { requirePermission } from '../../permissions.js';
import { backupCommandData } from './command-definitions.js';
import {
createGuildBackup,
ensureBotCanRestore,
getGuildBackup,
GuildBackupError,
listGuildBackups,
parseBackupPayload
} from './service.js';
import { promptDeleteConfirmation, promptRestoreConfirmation } from './confirmations.js';
async function ensureManageGuild(
interaction: Parameters<SlashCommand['execute']>[0],
locale: 'de' | 'en'
): Promise<boolean> {
if (!interaction.inGuild()) {
await interaction.reply({ content: t(locale, 'generic.guildOnly'), ephemeral: true });
return false;
}
return requirePermission(interaction, PermissionFlagsBits.ManageGuild, locale);
}
function defaultBackupName(): string {
return `backup-${new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19)}`;
}
const backupCommand: SlashCommand = {
data: backupCommandData,
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 });
return;
}
const sub = interaction.options.getSubcommand();
const guild = interaction.guild!;
if (sub === 'create') {
if (!(await ensureManageGuild(interaction, locale))) {
return;
}
const name = interaction.options.getString('name')?.trim() || defaultBackupName();
if (name.length < 1 || name.length > 100) {
await interaction.reply({
content: t(locale, 'guildbackup.error.invalid_name'),
ephemeral: true
});
return;
}
await interaction.deferReply({ ephemeral: true });
try {
const backup = await createGuildBackup(context, guild, name, interaction.user.id);
await interaction.editReply({
content: tf(locale, 'guildbackup.created', { id: backup.id, name: backup.name })
});
} catch (error) {
if (error instanceof GuildBackupError) {
await interaction.editReply({
content: t(locale, `guildbackup.error.${error.code}`)
});
return;
}
throw error;
}
return;
}
if (sub === 'list') {
if (!(await ensureManageGuild(interaction, locale))) {
return;
}
const backups = await listGuildBackups(context, guild.id);
if (backups.length === 0) {
await interaction.reply({
content: t(locale, 'guildbackup.list.empty'),
ephemeral: true
});
return;
}
const lines = backups.map((backup) =>
tf(locale, 'guildbackup.list.line', {
id: backup.id,
name: backup.name,
date: backup.createdAt.toISOString().slice(0, 10)
})
);
await interaction.reply({
content: `${t(locale, 'guildbackup.list.header')}\n${lines.join('\n')}`,
ephemeral: true
});
return;
}
if (sub === 'info') {
if (!(await ensureManageGuild(interaction, locale))) {
return;
}
const backupId = interaction.options.getString('id', true);
const backup = await getGuildBackup(context, guild.id, backupId);
if (!backup) {
await interaction.reply({
content: t(locale, 'guildbackup.error.not_found'),
ephemeral: true
});
return;
}
let roleCount = 0;
let channelCount = 0;
try {
const payload = parseBackupPayload(backup.payload);
roleCount = payload.roles.length;
channelCount = payload.channels.length;
} catch {
await interaction.reply({
content: t(locale, 'guildbackup.error.invalid_payload'),
ephemeral: true
});
return;
}
await interaction.reply({
content: [
tf(locale, 'guildbackup.info.id', { id: backup.id }),
tf(locale, 'guildbackup.info.name', { name: backup.name }),
tf(locale, 'guildbackup.info.created', { date: backup.createdAt.toISOString() }),
tf(locale, 'guildbackup.info.createdBy', { userId: backup.createdById }),
tf(locale, 'guildbackup.info.roles', { count: roleCount }),
tf(locale, 'guildbackup.info.channels', { count: channelCount }),
t(locale, 'guildbackup.info.limitations')
].join('\n'),
ephemeral: true
});
return;
}
if (sub === 'delete') {
if (!(await ensureManageGuild(interaction, locale))) {
return;
}
const backupId = interaction.options.getString('id', true);
const backup = await getGuildBackup(context, guild.id, backupId);
if (!backup) {
await interaction.reply({
content: t(locale, 'guildbackup.error.not_found'),
ephemeral: true
});
return;
}
await promptDeleteConfirmation(interaction, context, locale, backupId);
return;
}
if (sub === 'restore') {
if (guild.ownerId !== interaction.user.id) {
await interaction.reply({
content: t(locale, 'guildbackup.error.owner_only'),
ephemeral: true
});
return;
}
if (!ensureBotCanRestore(guild)) {
await interaction.reply({
content: t(locale, 'guildbackup.error.bot_missing_permissions'),
ephemeral: true
});
return;
}
const backupId = interaction.options.getString('id', true);
const backup = await getGuildBackup(context, guild.id, backupId);
if (!backup) {
await interaction.reply({
content: t(locale, 'guildbackup.error.not_found'),
ephemeral: true
});
return;
}
try {
parseBackupPayload(backup.payload);
} catch {
await interaction.reply({
content: t(locale, 'guildbackup.error.invalid_payload'),
ephemeral: true
});
return;
}
await promptRestoreConfirmation(interaction, context, locale, backupId);
}
}
};
export const guildBackupCommands = [backupCommand];