diff --git a/locales b/locales index 2fbc4d9e..80e627ab 160000 --- a/locales +++ b/locales @@ -1 +1 @@ -Subproject commit 2fbc4d9ee3963d98b1a5850bc0e67285d19a64f4 +Subproject commit 80e627abbd6f1fd70474e3cd46e9364220d64ee1 diff --git a/src/commands/context-menu/deleteMsg.ts b/src/commands/context-menu/deleteMsg.ts index c6dd0abd..9319c558 100644 --- a/src/commands/context-menu/deleteMsg.ts +++ b/src/commands/context-menu/deleteMsg.ts @@ -3,11 +3,13 @@ import { MessageContextMenuCommandInteraction, RESTPostAPIApplicationCommandsJSONBody, } from 'discord.js'; +import db from '../../utils/Db.js'; import BaseCommand from '../../core/BaseCommand.js'; import { checkIfStaff } from '../../utils/Utils.js'; import { emojis } from '../../utils/Constants.js'; import { t } from '../../utils/Locale.js'; -import db from '../../utils/Db.js'; +import { logMsgDelete } from '../../utils/HubLogger/ModLogs.js'; +import { captureException } from '@sentry/node'; export default class DeleteMessage extends BaseCommand { readonly data: RESTPostAPIApplicationCommandsJSONBody = { @@ -20,7 +22,7 @@ export default class DeleteMessage extends BaseCommand { async execute(interaction: MessageContextMenuCommandInteraction): Promise { const isOnCooldown = await this.checkAndSetCooldown(interaction); - if (isOnCooldown) return; + if (isOnCooldown || !interaction.inCachedGuild()) return; await interaction.deferReply({ ephemeral: true }); @@ -72,30 +74,35 @@ export default class DeleteMessage extends BaseCommand { return; } - const results = originalMsg.broadcastMsgs?.map(async (element) => { + const waitReply = await interaction.editReply( + `${emojis.loading} Your request has been queued. Messages will be deleted shortly...`, + ); + + let passed = 0; + + for await (const dbMsg of originalMsg.broadcastMsgs) { const connection = interaction.client.connectionCache.find( - (c) => c.channelId === element.channelId, + (c) => c.channelId === dbMsg.channelId, ); - if (!connection) return false; + + if (!connection) break; const webhookURL = connection.webhookURL.split('/'); const webhook = await interaction.client .fetchWebhook(webhookURL[webhookURL.length - 2]) ?.catch(() => null); - if (webhook?.owner?.id !== interaction.client.user?.id) return false; + if (webhook?.owner?.id !== interaction.client.user?.id) break; // finally, delete the message - return await webhook - ?.deleteMessage(element.messageId, connection.parentId ? connection.channelId : undefined) - .then(() => true) - .catch(() => false); - }); + await webhook + ?.deleteMessage(dbMsg.messageId, connection.parentId ? connection.channelId : undefined) + .then(() => passed++) + .catch(() => null); + } - const resultsArray = await Promise.all(results); - const deleted = resultsArray.reduce((acc, cur) => acc + (cur ? 1 : 0), 0); - await interaction - .editReply( + await waitReply + .edit( t( { phrase: 'network.deleteSuccess', @@ -104,14 +111,23 @@ export default class DeleteMessage extends BaseCommand { { emoji: emojis.yes, user: `<@${originalMsg.authorId}>`, - deleted: deleted.toString(), - total: resultsArray.length.toString(), + deleted: passed.toString(), + total: originalMsg.broadcastMsgs.length.toString(), }, ), ) .catch(() => null); - // log the deleted message for moderation purposes TODO - // if (interaction.inCachedGuild()) networkMessageDelete(interaction.member, interaction.targetMessage); + const messageContent = + interaction.targetMessage.cleanContent ?? + interaction.targetMessage.embeds.at(0)?.description?.replaceAll('`', '`'); + + if (isStaffOrHubMod && messageContent) { + await logMsgDelete(interaction.client, messageContent, hub, { + userId: originalMsg.authorId, + serverId: originalMsg.serverId, + modName: interaction.user.username, + }).catch(captureException); + } } } diff --git a/src/utils/HubLogger/ModLogs.ts b/src/utils/HubLogger/ModLogs.ts index d349c9a2..5c64a49c 100644 --- a/src/utils/HubLogger/ModLogs.ts +++ b/src/utils/HubLogger/ModLogs.ts @@ -1,10 +1,11 @@ import { stripIndents } from 'common-tags'; -import { User, EmbedBuilder, Snowflake, Client } from 'discord.js'; +import { User, EmbedBuilder, Snowflake, Client, codeBlock } from 'discord.js'; import BlacklistManager from '../../managers/BlacklistManager.js'; import { emojis, colors } from '../Constants.js'; import { fetchHub, toTitleCase } from '../Utils.js'; import { sendLog } from './Default.js'; import SuperClient from '../../core/Client.js'; +import { hubs } from '@prisma/client'; /** * Logs the blacklisting of a user or server. @@ -58,7 +59,6 @@ export const logBlacklist = async ( type = 'User'; } - const embed = new EmbedBuilder() .setAuthor({ name: `${type} ${name} blacklisted`, iconURL }) .setDescription( @@ -135,3 +135,35 @@ export const logUnblacklist = async ( await sendLog(opts.mod.client, hub.logChannels.modLogs, embed); }; + +export const logMsgDelete = async ( + client: Client, + content: string, + hub: hubs, + opts: { userId: string; serverId: string; modName: string }, +) => { + if (!hub.logChannels?.modLogs) return; + + const { userId, serverId } = opts; + const user = await client.users.fetch(userId).catch(() => null); + const server = await client.fetchGuild(serverId).catch(() => null); + + const embed = new EmbedBuilder() + .setDescription( + stripIndents` + ### ${emojis.deleteDanger_icon} Message Deleted + **Content:** + ${codeBlock(content)} + `, + ) + .setColor(colors.invisible) + + .addFields([ + { name: `${emojis.connect_icon} User`, value: `${user?.username} (\`${userId}\`)` }, + { name: `${emojis.rules_icon} Server`, value: `${server?.name} (\`${serverId}\`)` }, + { name: `${emojis.globe_icon} Hub`, value: hub.name }, + ]) + .setFooter({ text: `Deleted by: ${opts.modName}` }); + + await sendLog(client, hub.logChannels.modLogs, embed); +};