From 8e86fb347166e413d85142b30155d96dc1a0bf0c Mon Sep 17 00:00:00 2001 From: unbravechimp Date: Fri, 8 May 2026 00:44:51 +0200 Subject: [PATCH 1/8] fix: confessions command --- src/commands/utility/confess.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/commands/utility/confess.ts b/src/commands/utility/confess.ts index 009cf7e..4fb8d5b 100644 --- a/src/commands/utility/confess.ts +++ b/src/commands/utility/confess.ts @@ -21,7 +21,9 @@ export class ConfessCommand extends Command { } public override registerApplicationCommands(_registry: Command.Registry) { - // Registration is handled per-guild at runtime in the Ready listener + _registry.registerChatInputCommand((builder) => + builder.setName('confess').setDescription('Create a confession') + ); } public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) { @@ -32,8 +34,13 @@ export class ConfessCommand extends Command { const settings = await getSettings(interaction.guild.id, interaction.guild.name); - if (!settings.confessionChannelId || !settings.confessionEnabled) { - await interaction.reply({ content: `${emojis.rightArrow2} Confessions are not enabled or configured for this server.`, flags: MessageFlags.Ephemeral }); + if (settings.confessionEnabled === false) { + await interaction.reply({ content: `${emojis.rightArrow2} Confessions are disabled in this server.`, flags: MessageFlags.Ephemeral }); + return; + } + + if (!settings.confessionChannelId) { + await interaction.reply({ content: `${emojis.rightArrow2} Confessions are not configured in this server.`, flags: MessageFlags.Ephemeral }); return; } From 46d4d71c20bfcc113aa5e72ef81386bff4da9203 Mon Sep 17 00:00:00 2001 From: unbravechimp Date: Fri, 8 May 2026 00:59:38 +0200 Subject: [PATCH 2/8] upd: confession storage --- prisma/schema.prisma | 14 +++++++ src/commands/utility/confess.ts | 8 +++- .../confession/confessionHandler.ts | 29 +++++-------- src/lib/confessions.ts | 42 +++++++++++++++---- src/listeners/messageDelete.ts | 2 + src/listeners/messageDeleteBulk.ts | 2 + 6 files changed, 69 insertions(+), 28 deletions(-) diff --git a/prisma/schema.prisma b/prisma/schema.prisma index d4bc933..b4f5027 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -23,6 +23,7 @@ model Server { tickets Ticket[] autoroles AutoRole[] automods AutoMod[] + confessions Confession[] @@map("server") } @@ -126,4 +127,17 @@ model AutoMod { @@unique([guildId, word]) @@index([guildId]) @@map("automods") +} + +model Confession { + messageId String @id + guildId String + channelId String + threadId String + createdAt DateTime @default(now()) + + server Server @relation(fields: [guildId], references: [id], onDelete: Cascade) + + @@index([guildId]) + @@map("confessions") } \ No newline at end of file diff --git a/src/commands/utility/confess.ts b/src/commands/utility/confess.ts index 4fb8d5b..2bae01b 100644 --- a/src/commands/utility/confess.ts +++ b/src/commands/utility/confess.ts @@ -94,7 +94,13 @@ export class ConfessCommand extends Command { await message.edit({ components: [row] }); - storeConfessionContext({ guildId: interaction.guild.id, channelId: confessionChannel.id, messageId: message.id, threadId: thread.id }); + try { + await storeConfessionContext({ guildId: interaction.guild.id, channelId: confessionChannel.id, messageId: message.id, threadId: thread.id }); + } catch (error) { + await thread.delete().catch(() => null); + await message.delete().catch(() => null); + throw error; + } await modalSubmit.reply({ content: `${emojis.rightArrow2} Confession sent.`, flags: MessageFlags.Ephemeral }); } diff --git a/src/interaction-handlers/confession/confessionHandler.ts b/src/interaction-handlers/confession/confessionHandler.ts index 0135e71..206ffba 100644 --- a/src/interaction-handlers/confession/confessionHandler.ts +++ b/src/interaction-handlers/confession/confessionHandler.ts @@ -126,17 +126,16 @@ export class ConfessionButtonHandler extends InteractionHandler { .setLabel('Report') .setStyle(ButtonStyle.Danger); - storeConfessionContext({ - guildId: interaction.guild.id, - channelId: confessionChannel.id, - messageId: message.id, - threadId: thread.id - }); - const row = new ActionRowBuilder().addComponents(reportButton); try { await message.edit({ components: [row] }); + await storeConfessionContext({ + guildId: interaction.guild.id, + channelId: confessionChannel.id, + messageId: message.id, + threadId: thread.id + }); } catch (error) { await thread.delete().catch(() => null); await message.delete().catch(() => null); @@ -161,7 +160,7 @@ export class ConfessionButtonHandler extends InteractionHandler { return; } - const context = getConfessionContext(parsed.messageId); + const context = await getConfessionContext(parsed.messageId); if (!context) { await interaction.reply({ @@ -283,7 +282,7 @@ export class ConfessionButtonHandler extends InteractionHandler { return; } - const context = getConfessionContext(parsed.messageId); + const context = await getConfessionContext(parsed.messageId); if (!context) { await interaction.reply({ @@ -302,15 +301,7 @@ export class ConfessionButtonHandler extends InteractionHandler { } const channel = await interaction.client.channels.fetch(context.channelId).catch(() => null); - - if (!(channel instanceof TextChannel)) { - await interaction.reply({ - content: `${emojis.rightArrow2} The confession channel is no longer available.` - }); - return; - } - - const message = await channel.messages.fetch(context.messageId).catch(() => null); + const message = channel instanceof TextChannel ? await channel.messages.fetch(context.messageId).catch(() => null) : null; if (message) { const deletedEmbed = new EmbedBuilder() @@ -322,7 +313,7 @@ export class ConfessionButtonHandler extends InteractionHandler { await message.edit({ embeds: [deletedEmbed], components: [] }).catch(() => null); } - removeConfessionContext(context.messageId); + await removeConfessionContext(context.messageId); await interaction.reply({ content: `${emojis.rightArrow2} Confession marked as deleted.` diff --git a/src/lib/confessions.ts b/src/lib/confessions.ts index 6e2cb91..cd7a5f3 100644 --- a/src/lib/confessions.ts +++ b/src/lib/confessions.ts @@ -1,3 +1,5 @@ +import { prisma } from "./prisma.js"; + export type ConfessionContext = { guildId: string; channelId: string; @@ -5,8 +7,6 @@ export type ConfessionContext = { threadId: string; }; -const confessionContexts = new Map(); - export function getModeratorIds() { return [...new Set((process.env.MODERATORS ?? '').split(',').map((id) => id.trim()).filter(Boolean))]; } @@ -15,14 +15,40 @@ export function buildConfessionLink(guildId: string, channelId: string, messageI return `https://discord.com/channels/${guildId}/${channelId}/${messageId}`; } -export function storeConfessionContext(context: ConfessionContext) { - confessionContexts.set(context.messageId, context); +export async function storeConfessionContext(context: ConfessionContext) { + await prisma.confession.upsert({ + where: { messageId: context.messageId }, + create: context, + update: context, + }); } -export function getConfessionContext(messageId: string) { - return confessionContexts.get(messageId) ?? null; +export async function getConfessionContext(messageId: string) { + return prisma.confession.findUnique({ + where: { messageId }, + select: { + guildId: true, + channelId: true, + messageId: true, + threadId: true, + }, + }); } -export function removeConfessionContext(messageId: string) { - confessionContexts.delete(messageId); +export async function removeConfessionContext(messageId: string) { + await prisma.confession.deleteMany({ + where: { messageId }, + }); +} + +export async function removeConfessionContexts(messageIds: string[]) { + if (messageIds.length === 0) return; + + await prisma.confession.deleteMany({ + where: { + messageId: { + in: messageIds, + }, + }, + }); } \ No newline at end of file diff --git a/src/listeners/messageDelete.ts b/src/listeners/messageDelete.ts index b65220a..4ffa91d 100644 --- a/src/listeners/messageDelete.ts +++ b/src/listeners/messageDelete.ts @@ -1,5 +1,6 @@ import { Listener } from '@sapphire/framework'; import { Events, EmbedBuilder, Colors, type Message, type PartialMessage } from 'discord.js'; +import { removeConfessionContext } from '#lib/confessions.js'; import { isLoggingChannel, logEmbed, truncate } from '#lib/logging.js'; export class MessageDeleteListener extends Listener { @@ -23,6 +24,7 @@ export class MessageDeleteListener extends Listener .setFooter({ text: `ID: ${message.id}` }) .setTimestamp(); + await removeConfessionContext(message.id).catch(() => null); await logEmbed(guild, embed); } } diff --git a/src/listeners/messageDeleteBulk.ts b/src/listeners/messageDeleteBulk.ts index 7169887..817a7d6 100644 --- a/src/listeners/messageDeleteBulk.ts +++ b/src/listeners/messageDeleteBulk.ts @@ -1,5 +1,6 @@ import { Listener } from '@sapphire/framework'; import { EmbedBuilder, Colors, type Collection } from 'discord.js'; +import { removeConfessionContexts } from '#lib/confessions.js'; import { isLoggingChannel, logEmbed } from '#lib/logging.js'; export class MessageDeleteBulkListener extends Listener { @@ -24,6 +25,7 @@ export class MessageDeleteBulkListener extends Listener { ) .setTimestamp(); + await removeConfessionContexts([...messages.keys()]).catch(() => null); await logEmbed(guild, embed); } } From 71353cbcef3e20c39b3d660835e6dbd450ef8666 Mon Sep 17 00:00:00 2001 From: unbravechimp Date: Fri, 8 May 2026 01:10:58 +0200 Subject: [PATCH 3/8] fix: confession deletion --- .../confession/confessionHandler.ts | 36 ++++++++++++++----- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/src/interaction-handlers/confession/confessionHandler.ts b/src/interaction-handlers/confession/confessionHandler.ts index 206ffba..94459a7 100644 --- a/src/interaction-handlers/confession/confessionHandler.ts +++ b/src/interaction-handlers/confession/confessionHandler.ts @@ -301,16 +301,36 @@ export class ConfessionButtonHandler extends InteractionHandler { } const channel = await interaction.client.channels.fetch(context.channelId).catch(() => null); - const message = channel instanceof TextChannel ? await channel.messages.fetch(context.messageId).catch(() => null) : null; - if (message) { - const deletedEmbed = new EmbedBuilder() - .setTitle('Confession') - .setDescription('This confession was deleted by moderators.') - .setColor(0xed4245) - .setTimestamp(); + if (!(channel instanceof TextChannel)) { + await interaction.reply({ + content: `${emojis.rightArrow2} The confession channel is no longer available.` + }); + return; + } + + const message = await channel.messages.fetch(context.messageId).catch(() => null); + + if (!message) { + await interaction.reply({ + content: `${emojis.rightArrow2} That confession no longer exists.` + }); + return; + } + + const deletedEmbed = new EmbedBuilder() + .setTitle('Confession') + .setDescription('This confession was deleted by moderators.') + .setColor(0xed4245) + .setTimestamp(); - await message.edit({ embeds: [deletedEmbed], components: [] }).catch(() => null); + try { + await message.edit({ embeds: [deletedEmbed], components: [] }); + } catch { + await interaction.reply({ + content: `${emojis.rightArrow2} I could not update the confession message.` + }); + return; } await removeConfessionContext(context.messageId); From 2cabe695e43958e452e9da978a02557d814c7cb4 Mon Sep 17 00:00:00 2001 From: unbravechimp Date: Fri, 8 May 2026 01:18:00 +0200 Subject: [PATCH 4/8] fix: getting confession --- .../confession/confessionHandler.ts | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/interaction-handlers/confession/confessionHandler.ts b/src/interaction-handlers/confession/confessionHandler.ts index 94459a7..c57ed24 100644 --- a/src/interaction-handlers/confession/confessionHandler.ts +++ b/src/interaction-handlers/confession/confessionHandler.ts @@ -23,7 +23,14 @@ import { getSettings } from '#lib/settings.js'; import { emojis } from '#utils/emoji.js'; function parseConfessionButton(customId: string) { - const [action, messageId] = customId.split(':'); + const [action, ...parts] = customId.split(':'); + + if (action === 'delete-confession' && parts.length >= 3) { + const [guildId, channelId, messageId] = parts; + return { action, guildId, channelId, messageId }; + } + + const [messageId] = parts; return { action, messageId }; } @@ -209,12 +216,12 @@ export class ConfessionButtonHandler extends InteractionHandler { } const reason = modalSubmit.fields.getTextInputValue('confession-report-reason'); + await modalSubmit.deferReply({ flags: MessageFlags.Ephemeral }); const channel = await interaction.client.channels.fetch(context.channelId).catch(() => null); if (!(channel instanceof TextChannel)) { - await modalSubmit.reply({ - content: `${emojis.rightArrow2} The confession channel is no longer available.`, - flags: MessageFlags.Ephemeral + await modalSubmit.editReply({ + content: `${emojis.rightArrow2} The confession channel is no longer available.` }); return; } @@ -222,15 +229,14 @@ export class ConfessionButtonHandler extends InteractionHandler { const message = await channel.messages.fetch(context.messageId).catch(() => null); if (!message) { - await modalSubmit.reply({ - content: `${emojis.rightArrow2} That confession no longer exists.`, - flags: MessageFlags.Ephemeral + await modalSubmit.editReply({ + content: `${emojis.rightArrow2} That confession no longer exists.` }); return; } const deleteButton = new ButtonBuilder() - .setCustomId(`delete-confession:${parsed.messageId}`) + .setCustomId(`delete-confession:${context.guildId}:${context.channelId}:${context.messageId}`) .setLabel('Delete Confession') .setStyle(ButtonStyle.Danger); @@ -282,7 +288,14 @@ export class ConfessionButtonHandler extends InteractionHandler { return; } - const context = await getConfessionContext(parsed.messageId); + const context = parsed.guildId && parsed.channelId && parsed.messageId + ? { + guildId: parsed.guildId, + channelId: parsed.channelId, + messageId: parsed.messageId, + threadId: '' + } + : await getConfessionContext(parsed.messageId); if (!context) { await interaction.reply({ From a72116098e7e3163477281f6998272725f53aa12 Mon Sep 17 00:00:00 2001 From: unbravechimp Date: Fri, 8 May 2026 01:22:29 +0200 Subject: [PATCH 5/8] fix: replace legacy "db" --- .../confession/confessionHandler.ts | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/interaction-handlers/confession/confessionHandler.ts b/src/interaction-handlers/confession/confessionHandler.ts index c57ed24..a2889d3 100644 --- a/src/interaction-handlers/confession/confessionHandler.ts +++ b/src/interaction-handlers/confession/confessionHandler.ts @@ -266,16 +266,14 @@ export class ConfessionButtonHandler extends InteractionHandler { const delivered = deliveries.some((result) => result.status === 'fulfilled'); if (!delivered) { - await modalSubmit.reply({ + await modalSubmit.editReply({ content: `${emojis.rightArrow2} I could not contact any configured moderators.`, - flags: MessageFlags.Ephemeral }); return; } - await modalSubmit.reply({ + await modalSubmit.editReply({ content: `${emojis.rightArrow2} Your report was sent to the bot moderators.`, - flags: MessageFlags.Ephemeral }); return; } @@ -288,14 +286,15 @@ export class ConfessionButtonHandler extends InteractionHandler { return; } - const context = parsed.guildId && parsed.channelId && parsed.messageId - ? { - guildId: parsed.guildId, - channelId: parsed.channelId, - messageId: parsed.messageId, - threadId: '' - } - : await getConfessionContext(parsed.messageId); + const context = + parsed.guildId && parsed.channelId && parsed.messageId + ? { + guildId: parsed.guildId, + channelId: parsed.channelId, + messageId: parsed.messageId, + threadId: '' + } + : await getConfessionContext(parsed.messageId); if (!context) { await interaction.reply({ From 724a42cb47b10dc34af1397e0a7950c2efeba1f2 Mon Sep 17 00:00:00 2001 From: unbravechimp Date: Fri, 8 May 2026 01:28:13 +0200 Subject: [PATCH 6/8] test 1 --- src/interaction-handlers/confession/confessionHandler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/interaction-handlers/confession/confessionHandler.ts b/src/interaction-handlers/confession/confessionHandler.ts index a2889d3..85758a5 100644 --- a/src/interaction-handlers/confession/confessionHandler.ts +++ b/src/interaction-handlers/confession/confessionHandler.ts @@ -219,7 +219,7 @@ export class ConfessionButtonHandler extends InteractionHandler { await modalSubmit.deferReply({ flags: MessageFlags.Ephemeral }); const channel = await interaction.client.channels.fetch(context.channelId).catch(() => null); - if (!(channel instanceof TextChannel)) { + if (!channel || !channel.isTextBased()) { await modalSubmit.editReply({ content: `${emojis.rightArrow2} The confession channel is no longer available.` }); @@ -314,7 +314,7 @@ export class ConfessionButtonHandler extends InteractionHandler { const channel = await interaction.client.channels.fetch(context.channelId).catch(() => null); - if (!(channel instanceof TextChannel)) { + if (!channel || !channel.isTextBased()) { await interaction.reply({ content: `${emojis.rightArrow2} The confession channel is no longer available.` }); From a2c474a7cb80f8d7e3babb1816fd56d53c66a897 Mon Sep 17 00:00:00 2001 From: unbravechimp Date: Fri, 8 May 2026 01:36:33 +0200 Subject: [PATCH 7/8] fix: is this a prod only err?? --- .../confession/confessionHandler.ts | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/src/interaction-handlers/confession/confessionHandler.ts b/src/interaction-handlers/confession/confessionHandler.ts index 85758a5..107b907 100644 --- a/src/interaction-handlers/confession/confessionHandler.ts +++ b/src/interaction-handlers/confession/confessionHandler.ts @@ -217,11 +217,22 @@ export class ConfessionButtonHandler extends InteractionHandler { const reason = modalSubmit.fields.getTextInputValue('confession-report-reason'); await modalSubmit.deferReply({ flags: MessageFlags.Ephemeral }); - const channel = await interaction.client.channels.fetch(context.channelId).catch(() => null); + let channel = null; + + try { + if (interaction.inGuild() && interaction.guild) { + channel = await interaction.guild.channels.fetch(context.channelId).catch(() => null); + } else { + channel = await interaction.client.channels.fetch(context.channelId).catch(() => null); + } + } catch (err) { + channel = null; + } if (!channel || !channel.isTextBased()) { + console.error('Confession channel fetch failed', { channelId: context.channelId, context }); await modalSubmit.editReply({ - content: `${emojis.rightArrow2} The confession channel is no longer available.` + content: `${emojis.rightArrow2} The confession channel (<#${context.channelId}>) is no longer available.` }); return; } @@ -312,11 +323,22 @@ export class ConfessionButtonHandler extends InteractionHandler { return; } - const channel = await interaction.client.channels.fetch(context.channelId).catch(() => null); + let channel = null; + + try { + if (interaction.inGuild() && interaction.guild) { + channel = await interaction.guild.channels.fetch(context.channelId).catch(() => null); + } else { + channel = await interaction.client.channels.fetch(context.channelId).catch(() => null); + } + } catch (err) { + channel = null; + } if (!channel || !channel.isTextBased()) { + console.error('Confession channel fetch failed (delete flow)', { channelId: context.channelId, context }); await interaction.reply({ - content: `${emojis.rightArrow2} The confession channel is no longer available.` + content: `${emojis.rightArrow2} The confession channel (<#${context.channelId}>) is no longer available.` }); return; } From 86409c2a37e41512f789c77917abdadf1e8f9c23 Mon Sep 17 00:00:00 2001 From: unbravechimp Date: Fri, 8 May 2026 01:45:50 +0200 Subject: [PATCH 8/8] fix: last one --- .../confession/confessionHandler.ts | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/interaction-handlers/confession/confessionHandler.ts b/src/interaction-handlers/confession/confessionHandler.ts index 107b907..98e074c 100644 --- a/src/interaction-handlers/confession/confessionHandler.ts +++ b/src/interaction-handlers/confession/confessionHandler.ts @@ -329,16 +329,30 @@ export class ConfessionButtonHandler extends InteractionHandler { if (interaction.inGuild() && interaction.guild) { channel = await interaction.guild.channels.fetch(context.channelId).catch(() => null); } else { - channel = await interaction.client.channels.fetch(context.channelId).catch(() => null); + const guild = await interaction.client.guilds.fetch(context.guildId).catch(() => null); + if (guild) { + channel = await guild.channels.fetch(context.channelId).catch(() => null); + } + if (!channel) { + channel = await interaction.client.channels.fetch(context.channelId).catch(() => null); + } } } catch (err) { channel = null; } if (!channel || !channel.isTextBased()) { - console.error('Confession channel fetch failed (delete flow)', { channelId: context.channelId, context }); + console.error('Confession channel fetch failed (delete flow)', { + channelId: context.channelId, + guildId: context.guildId, + context + }); + + // Still allow removal from database if channel is gone + await removeConfessionContext(context.messageId); + await interaction.reply({ - content: `${emojis.rightArrow2} The confession channel (<#${context.channelId}>) is no longer available.` + content: `${emojis.rightArrow2} The confession channel (<#${context.channelId}>) is no longer available, but the confession has been removed from records.` }); return; } @@ -346,8 +360,11 @@ export class ConfessionButtonHandler extends InteractionHandler { const message = await channel.messages.fetch(context.messageId).catch(() => null); if (!message) { + // Message was already deleted, just clean up the record + await removeConfessionContext(context.messageId); + await interaction.reply({ - content: `${emojis.rightArrow2} That confession no longer exists.` + content: `${emojis.rightArrow2} That confession no longer exists, but the record has been cleaned up.` }); return; }