import { Subcommand } from "@sapphire/plugin-subcommands"; import { ApplyOptions } from "@sapphire/decorators"; import { container } from "@sapphire/framework"; import * as actions from "./actions"; import { ButtonStyle, ButtonBuilder, ChannelType, ContainerBuilder, TextDisplayBuilder, } from "discord.js"; import { getGuildByDiscordId } from "@stealth-developers/db"; import { errorMessage, getTextChannel } from "@/lib"; import { Result } from "@sapphire/framework"; import { ActionRowBuilder } from "discord.js"; @ApplyOptions({ description: "ticket-related commands", }) export class TicketCommand extends Subcommand { public constructor(context: Subcommand.LoaderContext, options: Subcommand.Options) { super(context, { ...options, name: "ticket", subcommands: [ { name: "new", chatInputRun: "createTicket" }, { name: "for", chatInputRun: "createTicketFor", preconditions: ["ModeratorCheck"] }, { name: "actions", type: "group", entries: [ { name: "open", chatInputRun: "openTicket" }, { name: "close", chatInputRun: "closeTicket" }, { name: "add", chatInputRun: "addToTicket" }, { name: "claim", chatInputRun: "claimTicket" }, { name: "unclaim", chatInputRun: "unclaimTicket" }, { name: "delete", chatInputRun: "deleteTicket" }, ], }, { name: "view", type: "group", entries: [ { name: "info", chatInputRun: "viewTicketInfo" }, { name: "transcript", chatInputRun: "viewTicketTranscript" }, { name: "list", chatInputRun: "listTickets" }, { name: "export", chatInputRun: "exportTickets" }, ], }, { name: "manage", type: "group", entries: [ { name: "transcript-channel", chatInputRun: "manageConfig", preconditions: ["ModeratorCheck"], }, { name: "entrypoint-channel", chatInputRun: "manageConfig", preconditions: ["ModeratorCheck"], }, { name: "category", chatInputRun: "manageConfig", preconditions: ["ModeratorCheck"], }, { name: "prompt", chatInputRun: "manageConfig", preconditions: ["ModeratorCheck"], }, { name: "send-prompt", chatInputRun: "sendPrompt", preconditions: ["ModeratorCheck"], }, { name: "greeting", chatInputRun: "manageConfig", preconditions: ["ModeratorCheck"], }, ], }, ], }); } public override registerApplicationCommands(registry: Subcommand.Registry) { registry.registerChatInputCommand((builder) => builder .setName("ticket") .setDescription("root ticket command") .addSubcommand((command) => command.setName("new").setDescription("Open a new ticket")) .addSubcommand((command) => command .setName("for") .setDescription("Open a ticket for a user") .addUserOption((option) => option .setName("user") .setDescription("The user to open the ticket for") .setRequired(true), ), ) .addSubcommandGroup((subcommandGroup) => subcommandGroup .setName("action") .setDescription("ticket actions") .addSubcommand((command) => command.setName("open").setDescription("Reopen the ticket in the current channel"), ) .addSubcommand((command) => command .setName("close") .setDescription("Close the ticket in the current channel") .addStringOption((option) => option .setName("reason") .setDescription("Public reason for closing the ticket") .setRequired(true), ) .addStringOption((option) => option .setName("private") .setDescription( "Private reason for closing the ticket, only visible to moderators", ) .setRequired(false), ) .addBooleanOption((option) => option.setName("delete-channel").setDescription("Delete the channel immediately"), ), ), ) .addSubcommandGroup((subcommandGroup) => subcommandGroup .setName("manage") .setDescription("ticket management commands") .addSubcommand((command) => command .setName("transcript-channel") .setDescription("Sets the channel for ticket transcripts") .addChannelOption((option) => option .setName("channel") .setDescription("The channel to send ticket transcripts to") .setRequired(true) .addChannelTypes(ChannelType.GuildText), ), ) .addSubcommand((command) => command .setName("entrypoint-channel") .setDescription("Sets the entry point channel for ticket creation") .addChannelOption((option) => option .setName("channel") .setDescription("The channel to set as the entry point") .setRequired(true) .addChannelTypes(ChannelType.GuildText), ), ) .addSubcommand((command) => command .setName("category") .setDescription("Sets the category for new tickets") .addChannelOption((option) => option .setName("category") .setDescription("The category to create new tickets in") .setRequired(true) .addChannelTypes(ChannelType.GuildCategory), ), ) .addSubcommand((command) => command .setName("prompt") .setDescription("Set the message sent in the entry point channel"), ) .addSubcommand((command) => command .setName("send-prompt") .setDescription("Send the prompt to the entry point channel"), ) .addSubcommand((command) => command.setName("greeting").setDescription("Set the message sent in new tickets"), ), ), ); } public async createTicket(interaction: Subcommand.ChatInputCommandInteraction) { await actions.createTicket(interaction); } public async createTicketFor(interaction: Subcommand.ChatInputCommandInteraction) { await actions.createTicket(interaction); } public async manageConfig(interaction: Subcommand.ChatInputCommandInteraction) { await actions.manageConfig(interaction); } public async sendPrompt(interaction: Subcommand.ChatInputCommandInteraction) { if (!interaction.guildId) return errorMessage(interaction, "This command can only be used in a server."); const guild = await getGuildByDiscordId(interaction.guildId); if (!guild) return errorMessage(interaction, "Guild not configured."); const commandString = (sub: string) => ``; const channelId = guild.ticketPromptChannel; const promptText = guild.ticketPrompt; if (!channelId) { return errorMessage( interaction, `Prompt chnanel not configured. Use ${commandString("entrypoint-channel")} to set it up.`, ); } if (!promptText) return errorMessage( interaction, `Ticket prompt not configured. Use ${commandString("prompt")} to set it up.`, ); const channelRes = await Result.fromAsync(() => getTextChannel(channelId)); if (channelRes.isErr()) { const err = channelRes.unwrapErr(); const errorMessageText = err instanceof Error ? err.message : "Unknown error"; return errorMessage(interaction, `Failed to fetch prompt channel: ${errorMessageText}`); } const channel = channelRes.unwrap(); if (!channel.isSendable()) { return errorMessage( interaction, "I don't have permission to send messages in the prompt channel.", ); } const button = new ButtonBuilder() .setLabel("Open a Ticket") .setStyle(ButtonStyle.Success) .setCustomId("open-ticket"); const container = new ContainerBuilder() .addTextDisplayComponents(new TextDisplayBuilder().setContent(promptText)) .addActionRowComponents(new ActionRowBuilder().addComponents(button)); await channel.send({ flags: ["IsComponentsV2"], components: [container] }); return interaction.reply({ content: "Prompt sent!", flags: ["Ephemeral"], }); } } container.stores.loadPiece({ piece: TicketCommand, name: "ticket", store: "commands", });