feat(tickets): lock on closing tickets
vi v@vt3e.cat
Wed, 25 Mar 2026 22:33:07 +0000
1 files changed,
35 insertions(+),
2 deletions(-)
M
src/interactions/commands/tickets/index.ts
→
src/interactions/commands/tickets/index.ts
@@ -7,6 +7,8 @@ SlashCommandBuilder,
SlashCommandSubcommandBuilder, } from "discord.js"; +const closeLock = new Map<number, { by: string; expiresAt: number }>(); + import { parseComponentId } from "@/utils/components"; import { getGuild, getTicket } from "@/utils/queries"; import { getTicketReasonModal } from "./_shared";@@ -133,12 +135,43 @@ return;
} if (action === "close") { + const thirtySeconds = 30 * 1000; + + const lock = closeLock.get(ticket.id); + if (lock && Date.now() < lock.expiresAt) { + if (lock.by !== interaction.user.id) { + interaction.reply({ + content: `Someone else has a has a lock on this ticket, it expires <t:${Math.round(lock.expiresAt / 1000)}:R>.`, + flags: ["Ephemeral"], + }); + return; + } + } + const publicReason = interaction.options.getString("reason") ?? undefined; const privateReason = interaction.options.getString("private_reason") ?? undefined; - if (publicReason || privateReason) { - await interaction.deferReply({ flags: ["Ephemeral"]}) + closeLock.set(ticket.id, { + expiresAt: Date.now() + thirtySeconds, + by: interaction.user.id, + }); + + if (interaction.channel?.isSendable()) { + const message = interaction.channel.send({ + content: `<@${interaction.user.id}> is closing this ticket...`, + allowedMentions: { + parse: [], + }, + }); + + setTimeout(async () => { + (await message).delete().catch(() => {}); + }, thirtySeconds); + } + + if (publicReason || privateReason) { + await interaction.deferReply({ flags: ["Ephemeral"] }); await handleClose(client, interaction, ticket, guild, { publicReason, privateReason,