From 2d71067b813805dcef5cc138d29062b530401e8e Mon Sep 17 00:00:00 2001 From: Ali Hammoud Date: Mon, 24 Aug 2026 04:57:20 +0300 Subject: [PATCH 1/5] feat: handle user reactions to remove bot message --- src/common/events/index.ts | 2 + src/features/reactions/index.ts | 38 ++++++++++++++++++ .../reactions/remove-user-bot-message.ts | 40 +++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 src/features/reactions/index.ts create mode 100644 src/features/reactions/remove-user-bot-message.ts diff --git a/src/common/events/index.ts b/src/common/events/index.ts index d3f25bd..390008c 100644 --- a/src/common/events/index.ts +++ b/src/common/events/index.ts @@ -5,6 +5,7 @@ import { readyEvent } from '@/features/ready/index.js'; import type { DiscordEvent } from './types.js'; import archiveChannels from '@/features/archive-channels/index.js'; import { tagReceivedEvent } from '@/features/tags/tag-received.js'; +import { reactionAddEvent } from '@/features/reactions/index.js'; export const events: DiscordEvent[] = [ readyEvent, @@ -13,4 +14,5 @@ export const events: DiscordEvent[] = [ interactionCreateEvent, archiveChannels, tagReceivedEvent, + reactionAddEvent, ].flat(); diff --git a/src/features/reactions/index.ts b/src/features/reactions/index.ts new file mode 100644 index 0000000..1742c9e --- /dev/null +++ b/src/features/reactions/index.ts @@ -0,0 +1,38 @@ +import { createEvent } from '@/common/events/create-event.js'; +import { + Events, + MessageReaction, + PartialMessageReaction, + PartialUser, + User, +} from 'discord.js'; +import { removeUserBotMessage } from './remove-user-bot-message.js'; + +export type ReactionAddEvent = { + reaction: MessageReaction | PartialMessageReaction; + user: User | PartialUser; +}; + +const handlers = [removeUserBotMessage]; + +export const reactionAddEvent = createEvent( + { + name: Events.MessageReactionAdd, + }, + async (reaction, user, details) => { + if (user.bot) { + return; + } + try { + if (reaction.partial) { + await reaction.fetch(); + } + } catch { + return; + } + + for (const handler of handlers) { + await handler(reaction, user, details); + } + } +); diff --git a/src/features/reactions/remove-user-bot-message.ts b/src/features/reactions/remove-user-bot-message.ts new file mode 100644 index 0000000..e4022ec --- /dev/null +++ b/src/features/reactions/remove-user-bot-message.ts @@ -0,0 +1,40 @@ +import type { ClientEvents, Events, GuildMember } from 'discord.js'; +import { UserBotMessagesService } from '@/services/user-bot-messages/user-bot-messages-service.js'; + +const DELETE_EMOJIS = ['🗑️', '❌']; + +export const removeUserBotMessage: ( + ...args: ClientEvents[Events.MessageReactionAdd] +) => Promise | void = async (reaction, user) => { + if ( + reaction.emoji.name === null || + !DELETE_EMOJIS.includes(reaction.emoji.name) + ) { + return; + } + if (reaction.message.author?.id !== user.client.user.id) { + return; + } + const guild = reaction.message.guild; + if (guild === null) { + return; + } + let member: GuildMember; + try { + member = + guild.members.cache.get(user.id) ?? (await guild.members.fetch(user.id)); + } catch { + return; + } + try { + const deleted = await UserBotMessagesService.deleteUserBotMessage({ + messageId: reaction.message.id, + user: member, + }); + if (deleted) { + await reaction.message.delete(); + } + } catch { + return; + } +}; From bf7ee871499c3817abc28eece429a7493448d631 Mon Sep 17 00:00:00 2001 From: Ali Hammoud Date: Mon, 24 Aug 2026 04:59:31 +0300 Subject: [PATCH 2/5] feat: add `DAYS_TO_KEEP_USER_BOT_MESSAGES` option --- prisma/schema.prisma | 1 + src/options.ts | 5 +++++ .../user-bot-messages/user-bot-messages-service.ts | 9 ++++++--- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 559da2f..17cef68 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -11,6 +11,7 @@ enum OptionKey { TAG_PREFIX MAX_TAGS_PER_MESSAGE DAYS_TO_KEEP_TAGS + DAYS_TO_KEEP_USER_BOT_MESSAGES } // Options for the bot, stored in the database. diff --git a/src/options.ts b/src/options.ts index e3adc1e..001f629 100644 --- a/src/options.ts +++ b/src/options.ts @@ -23,6 +23,11 @@ const OptionsDefaults = { type: 'number', displayName: 'Days to Keep Tags', }, + [OptionKey.DAYS_TO_KEEP_USER_BOT_MESSAGES]: { + value: '7', + type: 'number', + displayName: 'Days to Keep User Bot Messages', + }, } as const satisfies Record; export type OptionTypeOf = diff --git a/src/services/user-bot-messages/user-bot-messages-service.ts b/src/services/user-bot-messages/user-bot-messages-service.ts index dcca54a..3a6bb60 100644 --- a/src/services/user-bot-messages/user-bot-messages-service.ts +++ b/src/services/user-bot-messages/user-bot-messages-service.ts @@ -2,8 +2,8 @@ import type { GuildMember } from 'discord.js'; import { prisma } from '@/db/prisma.js'; import { isStaff } from '@/util/permissions.js'; import { DAY } from '@/constants/time.js'; - -const CLEANUP_INTERVAL_MS = 60 * 60 * 1000; // every hour +import { getBotOption } from '@/options.js'; +import { OptionKey } from '@/generated/prisma/enums.js'; export const UserBotMessagesService = { async deleteUserBotMessage({ @@ -65,6 +65,9 @@ export const UserBotMessagesService = { }; void cleanup(); - setInterval(cleanup, CLEANUP_INTERVAL_MS); + const daysToKeep = getBotOption( + OptionKey.DAYS_TO_KEEP_USER_BOT_MESSAGES + ).value; + setInterval(cleanup, daysToKeep * DAY); }, }; From 50cfee7945e7327ee641f87e5bc71f22e4e16c92 Mon Sep 17 00:00:00 2001 From: Ali Hammoud Date: Mon, 24 Aug 2026 08:14:05 +0300 Subject: [PATCH 3/5] feat: call userBotMessages cleanup in bot ready event --- src/features/ready/index.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/features/ready/index.ts b/src/features/ready/index.ts index 95c365c..935abf1 100644 --- a/src/features/ready/index.ts +++ b/src/features/ready/index.ts @@ -6,6 +6,7 @@ import { fetchAndCachePublicChannelsMessages } from '@/util/cache.js'; import { syncGuidesToChannel } from '@/util/post-guides.js'; import { leaveIfNotAllowedServer } from '@/util/server-guard.js'; import { syncArchiveCategoryChannels } from '../archive-channels/util.js'; +import { UserBotMessagesService } from '@/services/user-bot-messages/user-bot-messages-service.js'; export const readyEvent = createEvent( { @@ -71,5 +72,8 @@ export const readyEvent = createEvent( error ); } + + // Start the cleanup interval for expired user bot messages + void UserBotMessagesService.startExpiredMessageCleanup(); } ); From fbd26f2d55bbae008cd69c726485b77190ad0213 Mon Sep 17 00:00:00 2001 From: Ali Hammoud Date: Tue, 25 Aug 2026 01:57:24 +0300 Subject: [PATCH 4/5] refactor: add line breaks --- src/features/reactions/remove-user-bot-message.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/features/reactions/remove-user-bot-message.ts b/src/features/reactions/remove-user-bot-message.ts index e4022ec..ccab885 100644 --- a/src/features/reactions/remove-user-bot-message.ts +++ b/src/features/reactions/remove-user-bot-message.ts @@ -12,13 +12,16 @@ export const removeUserBotMessage: ( ) { return; } + if (reaction.message.author?.id !== user.client.user.id) { return; } + const guild = reaction.message.guild; if (guild === null) { return; } + let member: GuildMember; try { member = @@ -26,11 +29,13 @@ export const removeUserBotMessage: ( } catch { return; } + try { const deleted = await UserBotMessagesService.deleteUserBotMessage({ messageId: reaction.message.id, user: member, }); + if (deleted) { await reaction.message.delete(); } From 3924cae643eed8132947e26c23cb9607646168ba Mon Sep 17 00:00:00 2001 From: Ali Hammoud Date: Tue, 25 Aug 2026 01:57:58 +0300 Subject: [PATCH 5/5] refactor: remove unnecessary comment --- src/features/ready/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/features/ready/index.ts b/src/features/ready/index.ts index 935abf1..93ac662 100644 --- a/src/features/ready/index.ts +++ b/src/features/ready/index.ts @@ -73,7 +73,6 @@ export const readyEvent = createEvent( ); } - // Start the cleanup interval for expired user bot messages void UserBotMessagesService.startExpiredMessageCleanup(); } );