apps/bot/src/feats/tickets/actions/helpers.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 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
import type { TicketWithRelations } from "@stealth-developers/db";
import { Result } from "@sapphire/framework";
import {
ActionRowBuilder,
ButtonBuilder,
type Channel,
ContainerBuilder,
DiscordAPIError,
Message,
TextDisplayBuilder,
} from "discord.js";
import type { AnyUser, MessageSendPayload } from "@/types";
import { client } from "@/client";
import { PublicError, logger } from "@/lib";
import TicketButtonPresets from "../buttons";
export async function canDmUser(user: AnyUser): Promise<boolean> {
if (!("send" in user)) return false;
return user
.send({
content: "Hi! I'm just checking if your DMs are open.",
flags: ["SuppressNotifications"],
})
.then(() => true)
.catch(() => false);
}
export function channelName(localId: number, status: string, claimedBy?: string) {
const initNumber = localId.toString().padStart(4, "0");
// discord errors if we try to create channels with certain phrases in them
const bad = ["1488", "1919"];
if (bad.includes(initNumber)) return `${status}-filtered`;
if (claimedBy) {
const firstLetters = claimedBy.slice(0, 3);
return `${firstLetters}-${status}-${initNumber}`;
}
return `${status}-ticket-${localId}`;
}
export async function messageUser(user: AnyUser, message: MessageSendPayload) {
if (!("send" in user))
throw new PublicError("Failed to message user: User is not a Discord user");
const result = await Result.fromAsync(user.send(message));
if (result.isErr()) {
const error = result.unwrapErr();
if (error instanceof DiscordAPIError && [50007, 50278].includes(error.code as number))
throw new PublicError("Failed to message user: User has DMs disabled");
throw error;
}
return result.unwrap();
}
export async function messageChannel(channelIn: string | Channel, message: MessageSendPayload) {
const channel =
typeof channelIn === "string" ? await client.channels.fetch(channelIn) : channelIn;
if (!channel) throw new PublicError("Failed to message channel: Channel not found");
if (!("send" in channel))
throw new PublicError("Failed to message channel: Channel is not a text channel");
const result = await Result.fromAsync<Message<boolean>>(channel.send(message));
if (result.isErr()) {
const error = result.unwrapErr();
logger.warn({ error }, "Failed to message channel");
if (error instanceof DiscordAPIError && [50007, 50278].includes(error.code as number))
throw new PublicError("Failed to message channel: Channel is not a text channel");
throw error;
}
return result.unwrap();
}
export const text = (content: string) => new TextDisplayBuilder().setContent(content);
/**
* @param ticket the ticket to get the container for
* @param limitedDetails whether to exclude comments & other internal details
*/
export function getTicketContainer(ticket: TicketWithRelations, limitedDetails = false) {
const container = new ContainerBuilder();
container.addTextDisplayComponents(text(`# Ticket #${ticket.localId} - ${ticket.status}`));
let closedBySubject = true;
if (ticket.status !== "open" && ticket.closedByActor && ticket.subjectActor) {
closedBySubject = ticket.subjectActor.id === ticket.closedByActor.id;
container.addTextDisplayComponents(
text(`Closed by ${closedBySubject ? "the subject" : `<@${ticket.closedByActor.discordId}>`}`),
);
container.addTextDisplayComponents(text(["**Reason**", `> ${ticket.closeReason}`].join("\n")));
if (!limitedDetails && ticket.privateReason)
container.addTextDisplayComponents(
text(["**Private Reason**", `> ${ticket.privateReason}`].join("\n")),
);
}
const hasAttachments = ticket.attachments.length > 0;
if (hasAttachments)
container.addTextDisplayComponents(text(`**Attachments:** ${ticket.attachments.length}`));
else container.addTextDisplayComponents(text("**Attachments:** no attachments"));
container.addTextDisplayComponents(text(`-# ${ticket.id}`));
const actionRow = new ActionRowBuilder<ButtonBuilder>();
if (limitedDetails) actionRow.addComponents(TicketButtonPresets.thankButton(ticket.id));
if (!limitedDetails)
actionRow.addComponents(
TicketButtonPresets.viewCommentsButton(ticket.id, ticket.comments.length),
);
actionRow.addComponents(TicketButtonPresets.transcriptsLink(ticket.id));
container.addActionRowComponents(actionRow);
return container;
}
|