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 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
import {
type Actor,
addTicketComment,
addTicketParticipant,
editTicket,
} from "@stealth-developers/db";
import { ActionRowBuilder, ButtonBuilder, ChannelType } 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 { channelName, 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}`),
);
if (ticketChannel.type === ChannelType.GuildText) {
ticketChannel
.edit({
name: channelName(ticket.localId, "claimed", actor.displayName || actor.username!),
})
.catch((err) =>
logger.error(err, `failed to update channel name on claim for ${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}`),
);
if (ticketChannel.type === ChannelType.GuildText) {
ticketChannel
.edit({
name: channelName(ticket.localId, "open"),
})
.catch((err) =>
logger.error(err, `Failed to update channel name on unclaim for ${ticketChannel.id}`),
);
}
}
return newTicket;
}
export async function addParticipant(
guildId: string,
data: {
actor: Actor;
ticketId: string;
role: "participant" | "moderator" | "subject";
reason?: string;
},
) {
const botActor = getGuildActor(guildId);
if (!botActor) throw new PublicError("Failed to close ticket: Bot actor not found.");
await addTicketParticipant({
actorId: botActor.id,
ticketId: data.ticketId,
role: data.role,
reason: data.reason,
});
await addTicketComment({
actorId: botActor.id,
ticketId: data.ticketId,
content: `Added participant: ${data.actor.displayName || data.actor.username!} - ${data.reason ?? "no reason provided"}`,
});
}
|