apps/bot/src/commands/tickets/actions.ts (view raw)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
import { randomUUID } from "node:crypto";
import { TextChannel, ChannelType } from "discord.js";
import { container, Result } from "@sapphire/framework";
import type { Subcommand } from "@sapphire/plugin-subcommands";
import db, { desc, eq, tickets } from "@stealth-developers/db";
import config from "@stealth-developers/config";
export * from "./config";
import { errorMessage, upsertUser, getCategory, PublicError } from "@/lib";
export async function createTicket(interaction: Subcommand.ChatInputCommandInteraction) {
if (!interaction.guild)
return errorMessage(interaction, "Failed to create ticket: Guild not found");
const isOnBehalf = interaction.options.getSubcommand(true) === "for";
const publicId = randomUUID();
if (!interaction.member)
return errorMessage(interaction, "Failed to create ticket: Member not found");
const creator = await upsertUser(interaction.member, interaction.guild);
if (!creator) return errorMessage(interaction, "Failed to create ticket: Creator not found");
const subject = isOnBehalf ? interaction.options.getUser("user") : interaction.user;
const subjectUser = subject ? await upsertUser(subject, interaction.guild!) : creator;
if (!subjectUser)
return errorMessage(interaction, "Failed to create ticket: Subject user not found");
if (isOnBehalf && !creator.moderator)
return errorMessage(
interaction,
"You don't have permission to create a ticket for another user.",
);
const ticketCategory = await getCategory(config.discord.server.ticket_category);
let createdChannel: TextChannel | undefined = undefined;
const txResult = await Result.fromAsync(async () => {
return await db.transaction(async (tx) => {
const [maxTicket] = await tx
.select({ localId: tickets.localId })
.from(tickets)
.where(eq(tickets.guildId, creator.guildId))
.orderBy(desc(tickets.localId))
.limit(1);
const localId = (maxTicket?.localId ?? 0) + 1;
const [ticket] = await tx
.insert(tickets)
.values({
id: publicId,
guildId: creator.guildId,
localId: localId,
status: "open",
openedAt: new Date(),
openedBy: creator.id,
subject: subjectUser.id,
})
.returning();
if (!ticket) throw new PublicError("failed to add ticket to database.");
const ticketNumber = ticket.localId;
const paddedTicketNumber = String(ticketNumber).padStart(4, "0");
const channel = await interaction.guild?.channels.create({
name: `ticket-${paddedTicketNumber}`,
type: ChannelType.GuildText,
parent: ticketCategory,
});
if (!channel) throw new PublicError("failed to create Discord channel.");
createdChannel = channel;
await tx.update(tickets).set({ channelId: channel.id }).where(eq(tickets.id, ticket.id));
await channel.send({ content: `Hi! Thanks for submitting a report.` });
return channel.id;
});
});
if (txResult.isErr()) {
if (createdChannel) await Result.fromAsync(() => createdChannel?.delete());
const error = txResult.unwrapErr();
const message =
error instanceof PublicError
? `Failed to create ticket: ${error.message}`
: "There was an error creating the ticket.";
if (error instanceof Error) container.logger.error(error.message, error.stack);
return errorMessage(interaction, message);
}
const channelId = txResult.unwrap();
interaction.reply({
content: `Ticket created! See <#${channelId}>`,
flags: ["Ephemeral"],
});
}
|