From 4cfb151f0524f387d9a4b3ed82d6fe44945000b6 Mon Sep 17 00:00:00 2001 From: Ali Hammoud Date: Mon, 13 Apr 2026 13:03:16 +0300 Subject: [PATCH 1/2] feat: add `isDynoModerationMessage` and `isMessageInAPublicChannel` utils --- src/utils/messages.ts | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/src/utils/messages.ts b/src/utils/messages.ts index 0e840fe..1bdb738 100644 --- a/src/utils/messages.ts +++ b/src/utils/messages.ts @@ -1,4 +1,4 @@ -import { type Message, MessageType, type PartialMessage } from "discord.js"; +import { type Message, MessageType, type PartialMessage, PermissionFlagsBits } from "discord.js"; export type GuildMessage = Message; @@ -44,3 +44,31 @@ const normalizeText = (text: string) => { .split(/\s+/) .filter(Boolean); }; + +export const isDynoModerationMessage = (message: Message) => { + const phrases = [ + "was banned", + "was unbanned", + "was kicked", + "was muted", + "was unmuted", + "has been warned", + ]; + return phrases.some((phrase) => + message.embeds.some((embed) => embed.description?.includes(phrase)) + ); +}; + +/** + * Checks if the message is in a channel where `@everyone` has permission to view + * @param message + * @returns true if the message is in a public channel, false otherwise + */ +export const isMessageInAPublicChannel = (message: Message | PartialMessage) => { + return ( + message.inGuild() && + message.channel + .permissionsFor(message.guild.roles.everyone) + .has(PermissionFlagsBits.ViewChannel) + ); +}; From bb8554afab72005063475e65649122678de8cd55 Mon Sep 17 00:00:00 2001 From: Ali Hammoud Date: Mon, 13 Apr 2026 13:03:54 +0300 Subject: [PATCH 2/2] feat: add clean moderation messages event to handle message deletion --- src/events/clean-moderation-messages/index.ts | 22 +++++++++++++++++++ src/events/index.ts | 8 ++++++- 2 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 src/events/clean-moderation-messages/index.ts diff --git a/src/events/clean-moderation-messages/index.ts b/src/events/clean-moderation-messages/index.ts new file mode 100644 index 0000000..0d5bdd6 --- /dev/null +++ b/src/events/clean-moderation-messages/index.ts @@ -0,0 +1,22 @@ +import { Events } from "discord.js"; +import { isDynoModerationMessage, isMessageInAPublicChannel } from "../../utils/messages.js"; +import { createEvent } from "../helpers.js"; + +const DYNO_ID = "155149108183695360"; + +export const cleanModerationMessagesEvent = createEvent( + { + name: Events.MessageCreate, + }, + async (message) => { + const isDynoMessage = message.author.bot && message.author.id === DYNO_ID; + + if (isDynoMessage && isMessageInAPublicChannel(message) && isDynoModerationMessage(message)) { + try { + await message.delete(); + } catch (error) { + console.error("Failed to delete message:", error); + } + } + } +); diff --git a/src/events/index.ts b/src/events/index.ts index dcc8039..7755cd0 100644 --- a/src/events/index.ts +++ b/src/events/index.ts @@ -1,6 +1,12 @@ +import { cleanModerationMessagesEvent } from "./clean-moderation-messages/index.js"; import { interactionCreateEvent } from "./interaction-create/index.js"; import { readyEvent } from "./ready/index.js"; import { spamDetection } from "./spam-detection/index.js"; import type { DiscordEvent } from "./types.js"; -export const events: DiscordEvent[] = [readyEvent, interactionCreateEvent, spamDetection]; +export const events: DiscordEvent[] = [ + readyEvent, + interactionCreateEvent, + spamDetection, + cleanModerationMessagesEvent, +];