mirror of
https://github.com/kirameki-cafe/Yumi.git
synced 2026-09-13 10:49:20 +00:00
✨ feat: Add unfinished purge message command
This commit is contained in:
@@ -22,6 +22,8 @@ model Guild {
|
|||||||
|
|
||||||
ServiceAnnouncement_Enabled Boolean @default(false)
|
ServiceAnnouncement_Enabled Boolean @default(false)
|
||||||
ServiceAnnouncement_Channel String?
|
ServiceAnnouncement_Channel String?
|
||||||
|
|
||||||
|
Purge_Limit Int @default(200)
|
||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
|
|||||||
@@ -0,0 +1,269 @@
|
|||||||
|
import {
|
||||||
|
Message,
|
||||||
|
CommandInteraction,
|
||||||
|
PermissionsBitField,
|
||||||
|
GuildBasedChannel,
|
||||||
|
BaseGuildTextChannel,
|
||||||
|
BaseInteraction
|
||||||
|
} from 'discord.js';
|
||||||
|
import { I18n } from 'i18n';
|
||||||
|
|
||||||
|
import DiscordModule, { HybridInteractionMessage } from '../utils/DiscordModule';
|
||||||
|
import {
|
||||||
|
makeSuccessEmbed,
|
||||||
|
sendHybridInteractionMessageResponse,
|
||||||
|
makeErrorEmbed,
|
||||||
|
makeInfoEmbed,
|
||||||
|
sendMessage
|
||||||
|
} from '../utils/DiscordMessage';
|
||||||
|
import Locale from '../services/Locale';
|
||||||
|
import Logger from '../libs/Logger';
|
||||||
|
import Prisma from '../providers/Prisma';
|
||||||
|
import Environment from '../providers/Environment';
|
||||||
|
|
||||||
|
const EMBEDS = {
|
||||||
|
PURGE_INFO: (data: HybridInteractionMessage, locale: I18n) => {
|
||||||
|
return makeInfoEmbed({
|
||||||
|
title: locale.__('purge.title'),
|
||||||
|
description: locale.__('purge.info'),
|
||||||
|
fields: [
|
||||||
|
{
|
||||||
|
name: locale.__('common.available_args'),
|
||||||
|
value: locale.__('purge.valid_args')
|
||||||
|
}
|
||||||
|
],
|
||||||
|
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'
|
||||||
|
}),
|
||||||
|
user: data.getUser()
|
||||||
|
});
|
||||||
|
},
|
||||||
|
PURGING: (data: HybridInteractionMessage, locale: I18n) => {
|
||||||
|
return makeInfoEmbed({
|
||||||
|
title: locale.__('purge.title'),
|
||||||
|
description: locale.__('purge.purging'),
|
||||||
|
user: data.getUser()
|
||||||
|
});
|
||||||
|
},
|
||||||
|
PURGE_ERROR: (data: HybridInteractionMessage, locale: I18n, error: Error) => {
|
||||||
|
return makeErrorEmbed({
|
||||||
|
title: locale.__('purge.error'),
|
||||||
|
description: error.message ? error.message : undefined,
|
||||||
|
user: data.getUser()
|
||||||
|
});
|
||||||
|
},
|
||||||
|
PURGE_NAN_ERROR: (data: HybridInteractionMessage, locale: I18n) => {
|
||||||
|
return makeErrorEmbed({
|
||||||
|
title: locale.__('purge.error'),
|
||||||
|
description: locale.__('purge.error_nan'),
|
||||||
|
user: data.getUser()
|
||||||
|
});
|
||||||
|
},
|
||||||
|
PURGE_OVER_LIMIT: (data: HybridInteractionMessage, locale: I18n) => {
|
||||||
|
return makeErrorEmbed({
|
||||||
|
title: locale.__('purge.error'),
|
||||||
|
description: locale.__('purge.error_over_limit'),
|
||||||
|
user: data.getUser()
|
||||||
|
});
|
||||||
|
},
|
||||||
|
SUCCESSFULLY_PURGED: (data: HybridInteractionMessage, locale: I18n) => {
|
||||||
|
return makeSuccessEmbed({
|
||||||
|
title: locale.__('purge.success'),
|
||||||
|
user: data.getUser()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class Purge extends DiscordModule {
|
||||||
|
public id = 'Discord_Purge';
|
||||||
|
public commands = ['purge'];
|
||||||
|
public commandInteractionName = 'purge';
|
||||||
|
|
||||||
|
async GuildOnModuleCommand(args: any, message: Message) {
|
||||||
|
await this.run(new HybridInteractionMessage(message), args);
|
||||||
|
}
|
||||||
|
|
||||||
|
async GuildModuleCommandInteractionCreate(interaction: CommandInteraction) {
|
||||||
|
await this.run(new HybridInteractionMessage(interaction), interaction.options);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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)]
|
||||||
|
});
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
const purgeMessages = async (query: any, data: HybridInteractionMessage) => {
|
||||||
|
if (isNaN(query)) {
|
||||||
|
return await sendHybridInteractionMessageResponse(data, {
|
||||||
|
embeds: [EMBEDS.PURGE_NAN_ERROR(data, locale)]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const PrismaGuild = await Prisma.client.guild.findFirst({ where: { id: guild.id } });
|
||||||
|
if (!PrismaGuild) return;
|
||||||
|
|
||||||
|
const guildLimit = PrismaGuild.Purge_Limit;
|
||||||
|
try {
|
||||||
|
let totalMessagesToDelete = parseInt(query);
|
||||||
|
|
||||||
|
// if (totalMessagesToDelete > guildLimit) {
|
||||||
|
// return await sendHybridInteractionMessageResponse(data, {
|
||||||
|
// embeds: [EMBEDS.PURGE_OVER_LIMIT(data, locale)]
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
let amountToDelete = totalMessagesToDelete > 100 ? 100 : totalMessagesToDelete;
|
||||||
|
let messages;
|
||||||
|
let statusMessage;
|
||||||
|
if (data.isMessage()) {
|
||||||
|
if (data.getMessage().deletable)
|
||||||
|
await data
|
||||||
|
.getMessage()
|
||||||
|
.delete()
|
||||||
|
.catch(() => {});
|
||||||
|
|
||||||
|
statusMessage = await sendMessage(channel, data.getUser()!, {
|
||||||
|
embeds: [EMBEDS.PURGING(data, locale)]
|
||||||
|
});
|
||||||
|
} else if (data.isApplicationCommand()) {
|
||||||
|
statusMessage = await data
|
||||||
|
.getMessageComponentInteraction()
|
||||||
|
.reply({ embeds: [EMBEDS.PURGING(data, locale)], fetchReply: true });
|
||||||
|
statusMessage = statusMessage as unknown as BaseInteraction;
|
||||||
|
}
|
||||||
|
|
||||||
|
statusMessage = new HybridInteractionMessage(statusMessage!);
|
||||||
|
|
||||||
|
messages = await channel.messages.fetch({
|
||||||
|
limit: amountToDelete,
|
||||||
|
before: statusMessage!.getMessage().id
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filter out statusMessage
|
||||||
|
// messages = messages.filter((msg) => msg.id !== statusMessage!.getMessage().id);
|
||||||
|
|
||||||
|
// Filter out message sent after the status message
|
||||||
|
// messages = messages.filter(
|
||||||
|
// (msg) => msg.createdTimestamp < statusMessage!.getMessage().createdTimestamp
|
||||||
|
//);
|
||||||
|
|
||||||
|
//TODO: Handle no messages to delete
|
||||||
|
if (!messages) {
|
||||||
|
return await sendHybridInteractionMessageResponse(data, {
|
||||||
|
embeds: [EMBEDS.SUCCESSFULLY_PURGED(data, locale)]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletedMessages = await (channel as BaseGuildTextChannel).bulkDelete(messages, true);
|
||||||
|
let messageSize = deletedMessages.size;
|
||||||
|
|
||||||
|
Logger.debug(
|
||||||
|
'[DiscordPurge]',
|
||||||
|
`Bulk deleted ${messageSize} messages. Total deleted: ${messageSize}/${totalMessagesToDelete}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (messageSize !== totalMessagesToDelete) {
|
||||||
|
const remainingMessages = totalMessagesToDelete - messageSize;
|
||||||
|
const fetchLimit = 100;
|
||||||
|
const fetchIterations = Math.ceil(remainingMessages / fetchLimit);
|
||||||
|
|
||||||
|
for (let i = 0; i < fetchIterations; i++) {
|
||||||
|
const fetchAmount = Math.min(fetchLimit, remainingMessages - i * fetchLimit);
|
||||||
|
let fetchedMessages = await channel.messages.fetch({
|
||||||
|
limit: fetchAmount,
|
||||||
|
before: statusMessage!.getMessage().id
|
||||||
|
});
|
||||||
|
|
||||||
|
fetchedMessages = fetchedMessages.filter((msg) => msg.id !== statusMessage!.getMessage().id);
|
||||||
|
|
||||||
|
for (const msg of fetchedMessages.values()) {
|
||||||
|
await msg.delete();
|
||||||
|
messageSize += 1;
|
||||||
|
Logger.debug('[DiscordPurge]', `Total deleted: ${messageSize}/${totalMessagesToDelete}`);
|
||||||
|
|
||||||
|
// One second delay to prevent rate limit
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Logger.debug('[DiscordPurge]', `Total deleted: ${messageSize}/${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)] });
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
return await sendHybridInteractionMessageResponse(data, {
|
||||||
|
embeds: [EMBEDS.PURGE_ERROR(data, locale, err)]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (data.isApplicationCommand() && data.getChannel()) {
|
||||||
|
await purgeMessages(query, data);
|
||||||
|
} else if (data.isMessage()) {
|
||||||
|
await purgeMessages(query, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ import Discord_VRChatLogin from '../discord/Developer/VRChatLogin';
|
|||||||
import Discord_UserInfo from '../discord/UserInfo';
|
import Discord_UserInfo from '../discord/UserInfo';
|
||||||
import Discord_Stats from '../discord/Stats';
|
import Discord_Stats from '../discord/Stats';
|
||||||
import Discord_Support from '../discord/Support';
|
import Discord_Support from '../discord/Support';
|
||||||
|
import Discord_Purge from '../discord/Purge';
|
||||||
|
|
||||||
import Discord_MusicPlayer_Play from '../discord/MusicPlayer/Play';
|
import Discord_MusicPlayer_Play from '../discord/MusicPlayer/Play';
|
||||||
import Discord_MusicPlayer_PlayMy from '../discord/MusicPlayer/PlayMy';
|
import Discord_MusicPlayer_PlayMy from '../discord/MusicPlayer/PlayMy';
|
||||||
@@ -86,6 +87,7 @@ class Discord {
|
|||||||
new Discord_VRChatLogin(),
|
new Discord_VRChatLogin(),
|
||||||
new Discord_Settings(),
|
new Discord_Settings(),
|
||||||
new Discord_Support(),
|
new Discord_Support(),
|
||||||
|
new Discord_Purge(),
|
||||||
|
|
||||||
new Discord_MusicPlayer_Play(),
|
new Discord_MusicPlayer_Play(),
|
||||||
new Discord_MusicPlayer_PlayMy(),
|
new Discord_MusicPlayer_PlayMy(),
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ export const GLOBAL_COMMANDS: Object[] = [
|
|||||||
option.setName('message').setDescription('Message you want me to say').setRequired(true)
|
option.setName('message').setDescription('Message you want me to say').setRequired(true)
|
||||||
),
|
),
|
||||||
|
|
||||||
|
new SlashCommandBuilder()
|
||||||
|
.setName('purge')
|
||||||
|
.setDescription('Purge messages')
|
||||||
|
.addStringOption((option) => option.setName('message').setDescription('Amount to purge').setRequired(true)),
|
||||||
|
|
||||||
new SlashCommandBuilder()
|
new SlashCommandBuilder()
|
||||||
.setName('osu')
|
.setName('osu')
|
||||||
.setDescription('Interact with the game osu!')
|
.setDescription('Interact with the game osu!')
|
||||||
|
|||||||
@@ -152,9 +152,8 @@ export async function sendMessage(
|
|||||||
else message = await channel.send(options);
|
else message = await channel.send(options);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (typeof user === 'undefined') {
|
if (typeof user === 'undefined') {
|
||||||
return Logger.error(
|
Logger.error(`Cannot find available destinations to send the message CID: ${channel.id} C_ERR: ${error}`);
|
||||||
`Cannot find available destinations to send the message CID: ${channel.id} C_ERR: ${error}`
|
return;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
message = await user.send(options);
|
message = await user.send(options);
|
||||||
|
|||||||
Reference in New Issue
Block a user