180 lines
7.1 KiB
TypeScript
180 lines
7.1 KiB
TypeScript
import { createCommand } from "@shared/lib/utils";
|
|
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags, ComponentType } from "discord.js";
|
|
import { config } from "@/lib/config";
|
|
import { PruneService } from "@shared/modules/moderation/prune.service";
|
|
import {
|
|
getConfirmationMessage,
|
|
getProgressEmbed,
|
|
getSuccessEmbed,
|
|
getPruneErrorEmbed,
|
|
getPruneWarningEmbed,
|
|
getCancelledEmbed
|
|
} from "@/modules/moderation/prune.view";
|
|
|
|
export const prune = createCommand({
|
|
data: new SlashCommandBuilder()
|
|
.setName("prune")
|
|
.setDescription("Delete messages in bulk (admin only)")
|
|
.addIntegerOption(option =>
|
|
option
|
|
.setName("amount")
|
|
.setDescription(`Number of messages to delete (1-${config.moderation?.prune?.maxAmount || 100})`)
|
|
.setRequired(false)
|
|
.setMinValue(1)
|
|
.setMaxValue(config.moderation?.prune?.maxAmount || 100)
|
|
)
|
|
.addUserOption(option =>
|
|
option
|
|
.setName("user")
|
|
.setDescription("Only delete messages from this user")
|
|
.setRequired(false)
|
|
)
|
|
.addBooleanOption(option =>
|
|
option
|
|
.setName("all")
|
|
.setDescription("Delete all messages in the channel")
|
|
.setRequired(false)
|
|
)
|
|
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator),
|
|
|
|
execute: async (interaction) => {
|
|
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
|
|
|
|
try {
|
|
const amount = interaction.options.getInteger("amount");
|
|
const user = interaction.options.getUser("user");
|
|
const all = interaction.options.getBoolean("all") || false;
|
|
|
|
// Validate inputs
|
|
if (!amount && !all) {
|
|
// Default to 10 messages
|
|
} else if (amount && all) {
|
|
await interaction.editReply({
|
|
embeds: [getPruneErrorEmbed("Cannot specify both `amount` and `all`. Please use one or the other.")]
|
|
});
|
|
return;
|
|
}
|
|
|
|
const finalAmount = all ? 'all' : (amount || 10);
|
|
const confirmThreshold = config.moderation.prune.confirmThreshold;
|
|
|
|
// Check if confirmation is needed
|
|
const needsConfirmation = all || (typeof finalAmount === 'number' && finalAmount > confirmThreshold);
|
|
|
|
if (needsConfirmation) {
|
|
// Estimate message count for confirmation
|
|
let estimatedCount: number | undefined;
|
|
if (all) {
|
|
try {
|
|
estimatedCount = await PruneService.estimateMessageCount(interaction.channel!);
|
|
} catch {
|
|
estimatedCount = undefined;
|
|
}
|
|
}
|
|
|
|
const { embeds, components } = getConfirmationMessage(finalAmount, estimatedCount);
|
|
const response = await interaction.editReply({ embeds, components });
|
|
|
|
try {
|
|
const confirmation = await response.awaitMessageComponent({
|
|
filter: (i) => i.user.id === interaction.user.id,
|
|
componentType: ComponentType.Button,
|
|
time: 30000
|
|
});
|
|
|
|
if (confirmation.customId === "cancel_prune") {
|
|
await confirmation.update({
|
|
embeds: [getCancelledEmbed()],
|
|
components: []
|
|
});
|
|
return;
|
|
}
|
|
|
|
// User confirmed, proceed with deletion
|
|
await confirmation.update({
|
|
embeds: [getProgressEmbed({ current: 0, total: estimatedCount || finalAmount as number })],
|
|
components: []
|
|
});
|
|
|
|
// Execute deletion with progress callback for 'all' mode
|
|
const result = await PruneService.deleteMessages(
|
|
interaction.channel!,
|
|
{
|
|
amount: typeof finalAmount === 'number' ? finalAmount : undefined,
|
|
userId: user?.id,
|
|
all
|
|
},
|
|
all ? async (progress) => {
|
|
await interaction.editReply({
|
|
embeds: [getProgressEmbed(progress)]
|
|
});
|
|
} : undefined
|
|
);
|
|
|
|
// Show success
|
|
await interaction.editReply({
|
|
embeds: [getSuccessEmbed(result)],
|
|
components: []
|
|
});
|
|
|
|
} catch (error) {
|
|
if (error instanceof Error && error.message.includes("time")) {
|
|
await interaction.editReply({
|
|
embeds: [getPruneWarningEmbed("Confirmation timed out. Please try again.")],
|
|
components: []
|
|
});
|
|
} else {
|
|
throw error;
|
|
}
|
|
}
|
|
} else {
|
|
// No confirmation needed, proceed directly
|
|
const result = await PruneService.deleteMessages(
|
|
interaction.channel!,
|
|
{
|
|
amount: finalAmount as number,
|
|
userId: user?.id,
|
|
all: false
|
|
}
|
|
);
|
|
|
|
// Check if no messages were found
|
|
if (result.deletedCount === 0) {
|
|
if (user) {
|
|
await interaction.editReply({
|
|
embeds: [getPruneWarningEmbed(`No messages found from **${user.username}** in the last ${finalAmount} messages.`)]
|
|
});
|
|
} else {
|
|
await interaction.editReply({
|
|
embeds: [getPruneWarningEmbed("No messages found to delete.")]
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
|
|
await interaction.editReply({
|
|
embeds: [getSuccessEmbed(result)]
|
|
});
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error("Prune command error:", error);
|
|
|
|
let errorMessage = "An unexpected error occurred while trying to delete messages.";
|
|
if (error instanceof Error) {
|
|
if (error.message.includes("permission")) {
|
|
errorMessage = "I don't have permission to delete messages in this channel.";
|
|
} else if (error.message.includes("channel type")) {
|
|
errorMessage = "This command cannot be used in this type of channel.";
|
|
} else {
|
|
errorMessage = error.message;
|
|
}
|
|
}
|
|
|
|
await interaction.editReply({
|
|
embeds: [getPruneErrorEmbed(errorMessage)]
|
|
});
|
|
}
|
|
}
|
|
});
|