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 130 131 132 133 134 135 136 137 138 139 |
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 | string): Promise<boolean> {
let discordUser = typeof user !== "string" ? user : undefined;
if (typeof user === "string") discordUser = await client.users.fetch(user);
if (!discordUser) return false;
if (!("send" in discordUser)) return false;
return discordUser
.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(
`https://tickets.vt3e.cat/guilds/${ticket.guild!.discordId}/tickets/${ticket.localId}`,
),
);
container.addActionRowComponents(actionRow);
return container;
}
|