apps/bot/src/feats/tickets/actions/assignment.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 |
import {
type Actor,
addTicketComment,
addTicketParticipant,
editTicket,
} from "@stealth-developers/db";
import { ActionRowBuilder, ButtonBuilder } from "discord.js";
import { getGuildActor } from "f/guild-actor";
import { PublicError, logger } from "@/lib";
import type { TicketContext } from "../types";
import { TicketButtonPresets } from "../buttons";
import { hasAccessToTicket } from "../lib";
import { messageChannel } from "./helpers";
export async function claimTicket(ctx: TicketContext) {
const { ticket, actor, ticketChannel, guild } = ctx;
if (ticket.status !== "open") throw new Error("Ticket is not open");
const canAccess = hasAccessToTicket(actor, guild, ticket, { requireModerator: true });
if (!canAccess) throw new PublicError("You don't have permission to reopen this ticket.");
const lastClaimerActive = ticket.lastClaimantActivityAt
? new Date(ticket.lastClaimantActivityAt)
: null;
// if the last claimer was active in the last 15 minutes, don't allow claiming
if (lastClaimerActive && lastClaimerActive.getTime() > Date.now() - 1000 * 60 * 15) {
const timeAgo = Math.floor(lastClaimerActive.getTime() / 1000);
throw new PublicError(
`This ticket was claimed by <@${ticket.claimedByActor?.discordId}> <t:${timeAgo}:R>.`,
);
}
const newTicket = await editTicket(ticket.id, {
claimedAt: new Date(),
claimedBy: actor.id,
lastClaimantActivityAt: new Date(),
});
if (ticketChannel) {
const actionRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
TicketButtonPresets.unclaimTicketButton(ticket.id),
);
messageChannel(ticketChannel, {
content: `<@${actor.discordId}> has claimed this ticket, they will be with you shortly.`,
flags: ["SuppressNotifications"],
components: [actionRow],
}).catch((err) =>
logger.error(err, `failed to send claim message to channel ${ticketChannel.id}`),
);
}
return newTicket;
}
export async function unclaimTicket(ctx: TicketContext) {
const { ticket, actor, ticketChannel, guild } = ctx;
if (ticket.status !== "open") throw new Error("Ticket is not open");
const canAccess = hasAccessToTicket(actor, guild, ticket, { requireModerator: true });
if (!canAccess) throw new PublicError("You don't have permission to reopen this ticket.");
if (ticket.claimedBy !== actor.id) throw new PublicError("You have not claimed this ticket.");
const newTicket = await editTicket(ticket.id, {
claimedAt: null,
claimedBy: null,
lastClaimantActivityAt: null,
});
if (ticketChannel) {
const actionRow = new ActionRowBuilder<ButtonBuilder>().addComponents(
TicketButtonPresets.claimTicketButton(ticket.id),
);
messageChannel(ticketChannel, {
content: `<@${actor.discordId}> unclaimed this ticket, someone else will be with you shortly.`,
flags: ["SuppressNotifications"],
components: [actionRow],
}).catch((err) =>
logger.error(err, `Failed to send unclaim message to channel ${ticketChannel.id}`),
);
}
return newTicket;
}
export async function addParticipant(
guildId: string,
data: {
actor: Actor;
ticketId: string;
role: "participant" | "moderator" | "subject";
reason?: string;
},
redactUser = false,
) {
const botActor = getGuildActor(guildId);
if (!botActor) throw new PublicError("Failed to close ticket: Bot actor not found.");
await addTicketParticipant({
actorId: data.actor.id,
ticketId: data.ticketId,
role: data.role,
reason: data.reason,
});
await addTicketComment({
actorId: botActor.id,
ticketId: data.ticketId,
content: redactUser
? `Added participant - ${data.reason ?? "no reason provided"}`
: `Added participant: ${data.actor.displayName || data.actor.username!} - ${data.reason ?? "no reason provided"}`,
});
}
|