all repos — stealth-developers @ 470d2c0df6a5bbd4e2128366d33bbe7b2ae69875

bot: adding comments to tickets
vi did:web:vt3e.cat
Fri, 26 Jun 2026 11:35:40 +0100
commit

470d2c0df6a5bbd4e2128366d33bbe7b2ae69875

parent

150c6916c5949d48883c1e2f51b092b07fb0669c

M apps/bot/src/feats/tickets/README.mdapps/bot/src/feats/tickets/README.md

@@ -1,7 +1,7 @@

- [ ] /ticket - [x] new - [x] for - - [ ] view [ticketId] + - [x] view [ticketId] - [ ] actions - [x] close - [ ] claim
M apps/bot/src/feats/tickets/commands.tsapps/bot/src/feats/tickets/commands.ts

@@ -1,7 +1,12 @@

import { option } from "@purrkit/router"; import { Result } from "@sapphire/result"; import { ButtonBuilder, ContainerBuilder, TextDisplayBuilder, ActionRowBuilder } from "discord.js"; -import { getTicketByGuild, getTicketByUser } from "@stealth-developers/db"; +import { + addTicketComment, + getTicketByChannelId, + getTicketByGuild, + getTicketByUser, +} from "@stealth-developers/db"; import { errorMessage, getTextChannel, logger, PublicError } from "@/lib"; import { TicketButtonPresets } from "./buttons";

@@ -9,6 +14,8 @@ import * as actions from "./actions";

import { manageConfig } from "./config"; import { getBasicData } from "@/middleware"; import { hasAccessToTicket } from "@/lib/tickets"; +import { TicketModalPresets } from "./modals"; +import { ChannelType } from "discord-api-types/v9"; const ticketCommand = getBasicData.command("ticket", { description: "Ticket-related commands",

@@ -128,6 +135,62 @@

await interaction.showModal(modal); }, }); + + // TODO)) claim + // TODO)) unclaim + // TODO)) reopen + + group.subcommand("comment", { + description: "Add a comment to the ticket", + options: { + id: option.number("The ID of the ticket", { required: false }), + comment: option.string("The comment to add", { required: false }), + }, + async run(interaction, { id, comment }, { actor, guild }) { + if (!interaction.guild || !guild || !actor) + return errorMessage(interaction, "This command can only be used in a server."); + + if (!actor.moderator) + return errorMessage(interaction, "You do not have permission to use this command."); + + let ticketId = id; + if (!id && !interaction.channel) + errorMessage(interaction, "Either provide an ID or run this in a ticket."); + + if (!id && interaction.channel) { + if (interaction.channel.type !== ChannelType.GuildText) + return errorMessage(interaction, "Either provide an ID or run this in a ticket."); + + const name = interaction.channel.name; + const segments = name.split("-"); + ticketId = parseInt(segments[segments.length - 1]!); + } + + if (!ticketId) return errorMessage(interaction, "Ticket not found."); + + const ticket = await getTicketByGuild(guild.id, ticketId); + if (!ticket) return errorMessage(interaction, "Ticket not found."); + + if (comment) { + await addTicketComment({ + actorId: actor.id, + content: comment, + ticketId: ticket.id, + }); + + await interaction.reply({ + content: `Comment added to ticket ${ticket.id}`, + flags: ["Ephemeral"], + }); + return; + } + + await interaction.showModal(TicketModalPresets.comment(ticket.id)); + }, + }); + + // TODO)) edit + // TODO)) clean }, );
M apps/bot/src/feats/tickets/modals.tsapps/bot/src/feats/tickets/modals.ts

@@ -8,7 +8,14 @@ TextInputStyle,

} from "discord.js"; import { option } from "@purrkit/router"; -import db, { eq, getGuildByDiscordId, getTicket, guilds } from "@stealth-developers/db"; +import db, { + addTicketComment, + eq, + getActorByDiscordIdAndGuildDiscordId, + getGuildByDiscordId, + getTicket, + guilds, +} from "@stealth-developers/db"; import { kitten } from "@/client"; import { errorMessage, handleError, upsertUser } from "@/lib"; import * as actions from "./actions";

@@ -70,6 +77,30 @@ return interaction.followUp({ content: "Ticket closed successfully" });

}, }); +export const commentModal = kitten.modal("m-comment-modal", { + options: { + ticketId: option.string({ required: true }), + }, + async run(interaction, { ticketId }) { + if (!interaction.guild) return; + + const actor = await getActorByDiscordIdAndGuildDiscordId( + interaction.user.id, + interaction.guild.id, + ); + if (!actor) + return interaction.reply({ content: "You are not a moderator", flags: ["Ephemeral"] }); + + await addTicketComment({ + actorId: actor.id, + content: interaction.fields.getTextInputValue("comment"), + ticketId, + }); + + await interaction.reply({ content: "Comment added successfully", flags: ["Ephemeral"] }); + }, +}); + export const TicketModalPresets = { close: (id: string, isModerator: boolean) => { const description = isModerator

@@ -105,6 +136,24 @@ .addLabelComponents(...labels);

return modal; }, + comment: (id: string) => { + const commentLabel = new LabelBuilder() + .setLabel("Comment") + .setDescription("The comment to add to the ticket") + .setTextInputComponent( + new TextInputBuilder() + .setStyle(TextInputStyle.Paragraph) + .setCustomId("comment") + .setRequired(true), + ); + + const modal = new ModalBuilder() + .setCustomId(commentModal.id({ ticketId: id })) + .setTitle("Add comment") + .addLabelComponents(commentLabel); + + return modal; + }, }; -export const TicketModals = [closeTicketModal, configTextModal]; +export const TicketModals = [closeTicketModal, configTextModal, commentModal];