import { db, guild as guildTable } from "@/database"; import { getTicketMessage } from "@/database/queries"; import { ActionRowBuilder, ButtonBuilder, ButtonStyle, type ChatInputCommandInteraction, type Client, ContainerBuilder, type GuildMember, LabelBuilder, ModalBuilder, type ModalSubmitInteraction, PermissionFlagsBits, SlashCommandBuilder, TextDisplayBuilder, TextInputBuilder, TextInputStyle, } from "discord.js"; import { makeComponentId } from "@/utils/discord/components"; import { hasManagerPermissions } from "@/utils/discord/permissions"; import { loggers } from "@/utils/logging"; import { formatMessage } from "@/utils/tickets/formatting"; import { eq } from "drizzle-orm"; const logger = loggers.interactions.child({ name: "messageCommand" }); const commandData = new SlashCommandBuilder() .setName("manage-tickets") .setDescription("Manage aspects of the ticket system") .setDefaultMemberPermissions(PermissionFlagsBits.ModerateMembers) .addSubcommand((subcommand) => subcommand .setName("prompt") .setDescription("Send the prompt to the designated channel") .addChannelOption((c) => c .setName("channel") .setDescription("The channel to send the prompt in") .setRequired(true), ), ) .addSubcommand((subcommand) => subcommand .setName("message") .setDescription( "Edit the saved ticket message (shown when a ticket is created)", ), ); const MODAL_ID = makeComponentId(commandData, "message"); const INPUT_ID = "message_content"; async function execute( _client: Client, interaction: ChatInputCommandInteraction, ) { if (!interaction.guild || !interaction.member) { await interaction.reply({ content: "❌ This command can only be used in a server.", flags: ["Ephemeral"], }); return; } const hasPerms = await hasManagerPermissions( interaction.member as GuildMember, ); if (!hasPerms) { await interaction.reply({ content: "❌ You don't have permission to use this command.", flags: ["Ephemeral"], }); return; } const sub = interaction.options.getSubcommand(); if (sub === "prompt") { const targetChannel = interaction.options.getChannel("channel", true); const textInput = new LabelBuilder() .setLabel("Message Content") .setDescription( "This message will be followed by a button to create a new ticket.", ) .setTextInputComponent( new TextInputBuilder() .setCustomId(INPUT_ID) .setStyle(TextInputStyle.Paragraph) .setRequired(true), ); await interaction.showModal( new ModalBuilder() .setCustomId(`${MODAL_ID}:${targetChannel.id}`) .setTitle("Ticket Prompt") .setLabelComponents(textInput), ); return; } if (sub === "message") { const { data: currentMessage } = await getTicketMessage( interaction.guild.id, ); const textInput = new LabelBuilder() .setLabel("Message Content") .setDescription( "This message will be shown to users when they create a ticket.", ) .setTextInputComponent( new TextInputBuilder() .setCustomId(INPUT_ID) .setStyle(TextInputStyle.Paragraph) .setRequired(true) .setValue(currentMessage ?? ""), ); await interaction.showModal( new ModalBuilder() .setCustomId(MODAL_ID) .setTitle("Edit Ticket Message") .setLabelComponents(textInput), ); return; } } async function modalExecute( _client: Client, interaction: ModalSubmitInteraction, ) { await interaction.deferReply({ flags: ["Ephemeral"] }); if (!interaction.channelId || !interaction.guild || !interaction.guildId) { await interaction.editReply({ content: "This command can only be used in a server.", }); return; } const parts = interaction.customId.split(":"); const channelId = parts[2]; const messageContent = interaction.fields.getTextInputValue(INPUT_ID); if (channelId) { const channel = interaction.guild.channels.cache.get(channelId); if (!channel) { await interaction.editReply({ content: "❌ The channel specified is invalid.", }); return; } if (!channel.isSendable()) { await interaction.editReply({ content: "❌ I'm not able to send messages in the specified channel.", }); return; } const formatted = formatMessage(messageContent, interaction); const controls = new ActionRowBuilder().addComponents( new ButtonBuilder() .setCustomId("ticket:create") .setLabel("Open a Ticket") .setEmoji("📩") .setStyle(ButtonStyle.Success), ); const container = new ContainerBuilder() .addTextDisplayComponents(new TextDisplayBuilder().setContent(formatted)) .addActionRowComponents(controls); const sent = await channel .send({ components: [container], flags: ["IsComponentsV2"] }) .catch((err) => { logger.error({ err, channelId }, "Failed to send ticket message"); return null; }); if (!sent) { await interaction.editReply({ content: "❌ Failed to send the message in the specified channel.", }); return; } await interaction.editReply({ content: `✅ Message sent successfully! ${sent.url}`, allowedMentions: { users: [], }, }); return; } try { await db .update(guildTable) .set({ ticket_message: messageContent }) .where(eq(guildTable.guildId, interaction.guildId)); await interaction.editReply({ content: "✅ Ticket message updated successfully!", }); } catch (err) { logger.error(err, "Failed to update ticket message in DB"); await interaction.editReply({ content: "❌ Failed to update ticket message.", }); } } export default { data: commandData, execute, modalExecute, };