mirror of
https://github.com/kirameki-cafe/Yumi.git
synced 2026-09-13 10:49:20 +00:00
✨ feat: Add purge message command
This commit is contained in:
@@ -23,7 +23,7 @@ model Guild {
|
||||
ServiceAnnouncement_Enabled Boolean @default(false)
|
||||
ServiceAnnouncement_Channel String?
|
||||
|
||||
Purge_Limit Int @default(200)
|
||||
Purge_Limit Int @default(300)
|
||||
}
|
||||
|
||||
model User {
|
||||
|
||||
+215
-79
@@ -4,7 +4,8 @@ import {
|
||||
PermissionsBitField,
|
||||
GuildBasedChannel,
|
||||
BaseGuildTextChannel,
|
||||
BaseInteraction
|
||||
BaseInteraction,
|
||||
GuildTextBasedChannel
|
||||
} from 'discord.js';
|
||||
import { I18n } from 'i18n';
|
||||
|
||||
@@ -20,6 +21,11 @@ import Locale from '../services/Locale';
|
||||
import Logger from '../libs/Logger';
|
||||
import Prisma from '../providers/Prisma';
|
||||
import Environment from '../providers/Environment';
|
||||
import {
|
||||
checkBotPermissionsInChannel,
|
||||
checkMemberPermissionsInChannel,
|
||||
checkMemberPermissionsInGuild
|
||||
} from '../utils/DiscordPermission';
|
||||
|
||||
const EMBEDS = {
|
||||
PURGE_INFO: (data: HybridInteractionMessage, locale: I18n) => {
|
||||
@@ -35,19 +41,65 @@ const EMBEDS = {
|
||||
user: data.getUser()
|
||||
});
|
||||
},
|
||||
NO_PERMISSION: (data: HybridInteractionMessage, locale: I18n) => {
|
||||
return makeErrorEmbed({
|
||||
title: locale.__('common.no_permissions'),
|
||||
description: locale.__('common.no_permissions_description', {
|
||||
PERMISSIONS: 'VIEW_CHANNEL, MANAGE_MESSAGES'
|
||||
}),
|
||||
PURGING_STAGE_1: (data: HybridInteractionMessage, locale: I18n, current: number, total: number) => {
|
||||
return makeInfoEmbed({
|
||||
title: locale.__('purge.title'),
|
||||
description:
|
||||
locale.__('purge.purging_progress', { CURRENT: current.toString(), TOTAL: total.toString() }) +
|
||||
'\n\n' +
|
||||
locale.__('purge.purging_step_bulk_delete_done', { COUNT: current.toString() }),
|
||||
user: data.getUser()
|
||||
});
|
||||
},
|
||||
PURGING: (data: HybridInteractionMessage, locale: I18n) => {
|
||||
PURGING_STAGE_2: (
|
||||
data: HybridInteractionMessage,
|
||||
locale: I18n,
|
||||
current: number,
|
||||
current_normal: number,
|
||||
total: number
|
||||
) => {
|
||||
return makeInfoEmbed({
|
||||
title: locale.__('purge.title'),
|
||||
description: locale.__('purge.purging'),
|
||||
description:
|
||||
locale.__('purge.purging_progress', {
|
||||
CURRENT: (current + current_normal).toString(),
|
||||
TOTAL: total.toString()
|
||||
}) +
|
||||
'\n\n' +
|
||||
locale.__('purge.purging_step_bulk_delete_done', { COUNT: current.toString() }) +
|
||||
'\n' +
|
||||
locale.__('purge.purging_step_normal_delete', { CURRENT: current_normal.toString() }),
|
||||
user: data.getUser()
|
||||
});
|
||||
},
|
||||
PURGING_DONE_EARLY: (data: HybridInteractionMessage, locale: I18n, current: number, total: number) => {
|
||||
return makeSuccessEmbed({
|
||||
title: locale.__('purge.title'),
|
||||
description:
|
||||
locale.__('purge.purge_progress', { COUNT: current.toString(), TOTAL: total.toString() }) +
|
||||
'\n\n' +
|
||||
locale.__('purge.purging_step_bulk_delete_done', { COUNT: current.toString() }),
|
||||
user: data.getUser()
|
||||
});
|
||||
},
|
||||
PURGING_DONE: (
|
||||
data: HybridInteractionMessage,
|
||||
locale: I18n,
|
||||
current: number,
|
||||
current_normal: number,
|
||||
total: number
|
||||
) => {
|
||||
return makeSuccessEmbed({
|
||||
title: locale.__('purge.title'),
|
||||
description:
|
||||
locale.__('purge.purge_progress', {
|
||||
COUNT: (current + current_normal).toString(),
|
||||
TOTAL: total.toString()
|
||||
}) +
|
||||
'\n\n' +
|
||||
locale.__('purge.purging_step_bulk_delete_done', { COUNT: current.toString() }) +
|
||||
'\n' +
|
||||
locale.__('purge.purging_step_normal_delete_done', { COUNT: current_normal.toString() }),
|
||||
user: data.getUser()
|
||||
});
|
||||
},
|
||||
@@ -60,21 +112,21 @@ const EMBEDS = {
|
||||
},
|
||||
PURGE_NAN_ERROR: (data: HybridInteractionMessage, locale: I18n) => {
|
||||
return makeErrorEmbed({
|
||||
title: locale.__('purge.error'),
|
||||
description: locale.__('purge.error_nan'),
|
||||
title: locale.__('purge.nan_error_title'),
|
||||
description: locale.__('purge.nan_error_description'),
|
||||
user: data.getUser()
|
||||
});
|
||||
},
|
||||
PURGE_OVER_LIMIT: (data: HybridInteractionMessage, locale: I18n) => {
|
||||
PURGE_OVER_LIMIT: (data: HybridInteractionMessage, locale: I18n, limit: number) => {
|
||||
return makeErrorEmbed({
|
||||
title: locale.__('purge.error'),
|
||||
description: locale.__('purge.error_over_limit'),
|
||||
title: locale.__('common.member_no_permissions'),
|
||||
description: locale.__('purge.error_over_limit', { MAX: limit.toString() }),
|
||||
user: data.getUser()
|
||||
});
|
||||
},
|
||||
SUCCESSFULLY_PURGED: (data: HybridInteractionMessage, locale: I18n) => {
|
||||
NOTHING_PURGED: (data: HybridInteractionMessage, locale: I18n) => {
|
||||
return makeSuccessEmbed({
|
||||
title: locale.__('purge.success'),
|
||||
title: locale.__('purge.nothing_purged'),
|
||||
user: data.getUser()
|
||||
});
|
||||
}
|
||||
@@ -94,34 +146,24 @@ export default class Purge extends DiscordModule {
|
||||
}
|
||||
|
||||
async run(data: HybridInteractionMessage, args: any) {
|
||||
// Disable this unfinished feature in production
|
||||
if (Environment.get().NODE_ENV !== 'production') return;
|
||||
|
||||
const guild = data.getGuild();
|
||||
if (!guild) return;
|
||||
|
||||
const member = data.getMember();
|
||||
if (!member) return;
|
||||
|
||||
const channel = data.getGuildChannel() as GuildTextBasedChannel;
|
||||
if (!channel) return;
|
||||
|
||||
const locale = await Locale.getGuildLocale(guild.id);
|
||||
|
||||
let query;
|
||||
|
||||
const channel = data.getChannel();
|
||||
if (!channel) return;
|
||||
|
||||
if (data.isMessage()) {
|
||||
const message = data.getMessage();
|
||||
|
||||
if (!message.member) return;
|
||||
|
||||
if (
|
||||
!message.member.permissions.has([
|
||||
PermissionsBitField.Flags.ViewChannel,
|
||||
PermissionsBitField.Flags.ManageMessages
|
||||
])
|
||||
)
|
||||
return await sendHybridInteractionMessageResponse(data, {
|
||||
embeds: [EMBEDS.NO_PERMISSION(data, locale)]
|
||||
});
|
||||
|
||||
if (args.length === 0)
|
||||
return await sendHybridInteractionMessageResponse(data, {
|
||||
embeds: [EMBEDS.PURGE_INFO(data, locale)]
|
||||
@@ -130,20 +172,6 @@ export default class Purge extends DiscordModule {
|
||||
query = args.join(' ');
|
||||
} else if (data.isApplicationCommand()) {
|
||||
const interaction = data.getSlashCommand();
|
||||
if (
|
||||
!data
|
||||
.getGuild()!
|
||||
.members.cache.get(interaction.user.id)
|
||||
?.permissions.has([PermissionsBitField.Flags.ViewChannel]) &&
|
||||
data
|
||||
.getGuild()!
|
||||
.members.cache.get(interaction.user.id)
|
||||
?.permissions.has([PermissionsBitField.Flags.ManageMessages])
|
||||
)
|
||||
return await sendHybridInteractionMessageResponse(data, {
|
||||
embeds: [EMBEDS.NO_PERMISSION(data, locale)]
|
||||
});
|
||||
|
||||
query = interaction.options.get('message', true).value?.toString();
|
||||
}
|
||||
|
||||
@@ -154,6 +182,28 @@ export default class Purge extends DiscordModule {
|
||||
});
|
||||
}
|
||||
|
||||
if (
|
||||
!(await checkMemberPermissionsInChannel({
|
||||
member,
|
||||
channel,
|
||||
data,
|
||||
locale,
|
||||
permissions: [PermissionsBitField.Flags.ViewChannel, PermissionsBitField.Flags.ManageMessages]
|
||||
}))
|
||||
)
|
||||
return;
|
||||
|
||||
if (
|
||||
!(await checkBotPermissionsInChannel({
|
||||
guild,
|
||||
data,
|
||||
locale,
|
||||
channel,
|
||||
permissions: [PermissionsBitField.Flags.ViewChannel, PermissionsBitField.Flags.ManageMessages]
|
||||
}))
|
||||
)
|
||||
return;
|
||||
|
||||
const PrismaGuild = await Prisma.client.guild.findFirst({ where: { id: guild.id } });
|
||||
if (!PrismaGuild) return;
|
||||
|
||||
@@ -161,13 +211,15 @@ export default class Purge extends DiscordModule {
|
||||
try {
|
||||
let totalMessagesToDelete = parseInt(query);
|
||||
|
||||
// if (totalMessagesToDelete > guildLimit) {
|
||||
// return await sendHybridInteractionMessageResponse(data, {
|
||||
// embeds: [EMBEDS.PURGE_OVER_LIMIT(data, locale)]
|
||||
// });
|
||||
// }
|
||||
if (totalMessagesToDelete > guildLimit) {
|
||||
return await sendHybridInteractionMessageResponse(data, {
|
||||
embeds: [EMBEDS.PURGE_OVER_LIMIT(data, locale, guildLimit)]
|
||||
});
|
||||
}
|
||||
|
||||
let totalMessageDeleted = 0;
|
||||
let totalBulkDeletedMessages = 0;
|
||||
|
||||
let amountToDelete = totalMessagesToDelete > 100 ? 100 : totalMessagesToDelete;
|
||||
let messages;
|
||||
let statusMessage;
|
||||
if (data.isMessage()) {
|
||||
@@ -178,20 +230,22 @@ export default class Purge extends DiscordModule {
|
||||
.catch(() => {});
|
||||
|
||||
statusMessage = await sendMessage(channel, data.getUser()!, {
|
||||
embeds: [EMBEDS.PURGING(data, locale)]
|
||||
embeds: [EMBEDS.PURGING_STAGE_1(data, locale, totalBulkDeletedMessages, totalMessagesToDelete)]
|
||||
});
|
||||
} else if (data.isApplicationCommand()) {
|
||||
statusMessage = await data
|
||||
.getMessageComponentInteraction()
|
||||
.reply({ embeds: [EMBEDS.PURGING(data, locale)], fetchReply: true });
|
||||
statusMessage = await data.getMessageComponentInteraction().reply({
|
||||
embeds: [EMBEDS.PURGING_STAGE_1(data, locale, totalBulkDeletedMessages, totalMessagesToDelete)],
|
||||
fetchReply: true
|
||||
});
|
||||
statusMessage = statusMessage as unknown as BaseInteraction;
|
||||
}
|
||||
|
||||
statusMessage = new HybridInteractionMessage(statusMessage!);
|
||||
|
||||
let amountToDelete = totalMessagesToDelete > 100 ? 100 : totalMessagesToDelete;
|
||||
messages = await channel.messages.fetch({
|
||||
limit: amountToDelete,
|
||||
before: statusMessage!.getMessage().id
|
||||
before: statusMessage.getMessage().id
|
||||
});
|
||||
|
||||
// Filter out statusMessage
|
||||
@@ -202,23 +256,70 @@ export default class Purge extends DiscordModule {
|
||||
// (msg) => msg.createdTimestamp < statusMessage!.getMessage().createdTimestamp
|
||||
//);
|
||||
|
||||
//TODO: Handle no messages to delete
|
||||
if (!messages) {
|
||||
return await sendHybridInteractionMessageResponse(data, {
|
||||
embeds: [EMBEDS.SUCCESSFULLY_PURGED(data, locale)]
|
||||
});
|
||||
// Edit the message
|
||||
if (messages.size === 0) {
|
||||
let embed = EMBEDS.NOTHING_PURGED(data, locale);
|
||||
if (data.isMessage()) {
|
||||
return await statusMessage.getMessage().edit({ embeds: [embed] });
|
||||
} else if (data.isApplicationCommand()) {
|
||||
return await statusMessage.getMessageComponentInteraction().editReply({ embeds: [embed] });
|
||||
}
|
||||
}
|
||||
|
||||
const deletedMessages = await (channel as BaseGuildTextChannel).bulkDelete(messages, true);
|
||||
let messageSize = deletedMessages.size;
|
||||
while (totalMessageDeleted < totalMessagesToDelete) {
|
||||
amountToDelete =
|
||||
totalMessagesToDelete - totalMessageDeleted > 100
|
||||
? 100
|
||||
: totalMessagesToDelete - totalMessageDeleted;
|
||||
|
||||
Logger.debug(
|
||||
'[DiscordPurge]',
|
||||
`Attempting to delete ${amountToDelete} messages. Total deleted: ${totalBulkDeletedMessages}/${totalMessagesToDelete}`
|
||||
);
|
||||
|
||||
messages = await channel.messages.fetch({
|
||||
limit: amountToDelete,
|
||||
before: statusMessage.getMessage().id
|
||||
});
|
||||
|
||||
if (messages.size === 0) {
|
||||
let embed = EMBEDS.PURGING_DONE_EARLY(data, locale, totalBulkDeletedMessages, amountToDelete);
|
||||
if (data.isMessage()) {
|
||||
return await statusMessage.getMessage().edit({ embeds: [embed] });
|
||||
} else if (data.isApplicationCommand()) {
|
||||
return await statusMessage.getMessageComponentInteraction().editReply({ embeds: [embed] });
|
||||
}
|
||||
}
|
||||
|
||||
Logger.debug(
|
||||
'[DiscordPurge]',
|
||||
`Bulk deleting: ${messages.size}. Total deleted: ${totalBulkDeletedMessages}/${totalMessagesToDelete}`
|
||||
);
|
||||
|
||||
const deletedMessages = await (channel as BaseGuildTextChannel).bulkDelete(messages, true);
|
||||
totalMessageDeleted += deletedMessages.size;
|
||||
totalBulkDeletedMessages += deletedMessages.size;
|
||||
|
||||
let embed = EMBEDS.PURGING_STAGE_1(data, locale, totalBulkDeletedMessages, totalMessagesToDelete);
|
||||
|
||||
if (data.isMessage()) {
|
||||
await statusMessage.getMessage().edit({ embeds: [embed] });
|
||||
} else if (data.isApplicationCommand()) {
|
||||
await statusMessage.getMessageComponentInteraction().editReply({ embeds: [embed] });
|
||||
}
|
||||
|
||||
// Unable to delete all the messages? Break the loop and start normal deletion
|
||||
if (deletedMessages.size !== messages.size) break;
|
||||
}
|
||||
|
||||
//let embed = EMBEDS.PURGING_DONE_EARLY(data, locale, totalBulkDeletedMessages, amountToDelete);
|
||||
Logger.debug(
|
||||
'[DiscordPurge]',
|
||||
`Bulk deleted ${messageSize} messages. Total deleted: ${messageSize}/${totalMessagesToDelete}`
|
||||
`Bulk deleted ${totalBulkDeletedMessages} messages. Total deleted: ${totalBulkDeletedMessages}/${totalMessagesToDelete}`
|
||||
);
|
||||
|
||||
if (messageSize !== totalMessagesToDelete) {
|
||||
const remainingMessages = totalMessagesToDelete - messageSize;
|
||||
if (totalMessageDeleted !== totalMessagesToDelete) {
|
||||
const remainingMessages = totalMessagesToDelete - totalMessageDeleted;
|
||||
const fetchLimit = 100;
|
||||
const fetchIterations = Math.ceil(remainingMessages / fetchLimit);
|
||||
|
||||
@@ -231,27 +332,62 @@ export default class Purge extends DiscordModule {
|
||||
|
||||
fetchedMessages = fetchedMessages.filter((msg) => msg.id !== statusMessage!.getMessage().id);
|
||||
|
||||
let messageCounter = 0; // Counter for tracking every 5 messages
|
||||
|
||||
for (const msg of fetchedMessages.values()) {
|
||||
await msg.delete();
|
||||
messageSize += 1;
|
||||
Logger.debug('[DiscordPurge]', `Total deleted: ${messageSize}/${totalMessagesToDelete}`);
|
||||
totalMessageDeleted += 1;
|
||||
messageCounter += 1;
|
||||
|
||||
if (messageCounter === 1 || messageCounter % 5 === 0) {
|
||||
let embed = EMBEDS.PURGING_STAGE_2(
|
||||
data,
|
||||
locale,
|
||||
totalBulkDeletedMessages,
|
||||
totalMessageDeleted,
|
||||
totalMessagesToDelete
|
||||
);
|
||||
|
||||
if (data.isMessage()) {
|
||||
await statusMessage.getMessage().edit({ embeds: [embed] });
|
||||
} else if (data.isApplicationCommand()) {
|
||||
await statusMessage.getMessageComponentInteraction().editReply({ embeds: [embed] });
|
||||
}
|
||||
|
||||
Logger.debug(
|
||||
'[DiscordPurge]',
|
||||
`Total deleted: ${totalMessageDeleted}/${totalMessagesToDelete}`
|
||||
);
|
||||
}
|
||||
|
||||
// One second delay to prevent rate limit
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logger.debug('[DiscordPurge]', `Total deleted: ${messageSize}/${totalMessagesToDelete}`);
|
||||
Logger.debug('[DiscordPurge]', `Total deleted: ${totalMessageDeleted}/${totalMessagesToDelete}`);
|
||||
|
||||
if (data.isMessage()) {
|
||||
return await sendMessage(channel, data.getUser()!, {
|
||||
embeds: [EMBEDS.SUCCESSFULLY_PURGED(data, locale)]
|
||||
});
|
||||
} else if (data.isApplicationCommand()) {
|
||||
return await data
|
||||
.getMessageComponentInteraction()
|
||||
.editReply({ embeds: [EMBEDS.SUCCESSFULLY_PURGED(data, locale)] });
|
||||
let embed = EMBEDS.PURGING_DONE(
|
||||
data,
|
||||
locale,
|
||||
totalBulkDeletedMessages,
|
||||
totalMessageDeleted - totalBulkDeletedMessages,
|
||||
amountToDelete
|
||||
);
|
||||
if (data.isMessage()) {
|
||||
return await statusMessage.getMessage().edit({ embeds: [embed] });
|
||||
} else if (data.isApplicationCommand()) {
|
||||
return await statusMessage.getMessageComponentInteraction().editReply({ embeds: [embed] });
|
||||
}
|
||||
} else {
|
||||
Logger.debug('[DiscordPurge]', `Total deleted: ${totalMessageDeleted}/${totalMessagesToDelete}`);
|
||||
|
||||
let embed = EMBEDS.PURGING_DONE_EARLY(data, locale, totalBulkDeletedMessages, amountToDelete);
|
||||
if (data.isMessage()) {
|
||||
return await statusMessage.getMessage().edit({ embeds: [embed] });
|
||||
} else if (data.isApplicationCommand()) {
|
||||
return await statusMessage.getMessageComponentInteraction().editReply({ embeds: [embed] });
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
return await sendHybridInteractionMessageResponse(data, {
|
||||
|
||||
Reference in New Issue
Block a user