Files

1045 lines
31 KiB
JavaScript
Raw Permalink Normal View History

2021-10-24 22:46:36 +01:00
const fs = require("fs");
2021-12-14 18:04:38 -06:00
const {
2022-07-23 07:50:12 +01:00
EmbedBuilder,
2022-07-22 13:19:05 +01:00
ApplicationCommandOptionType,
ApplicationCommandType,
2022-08-11 19:19:29 +01:00
ChannelType,
2024-06-12 12:54:31 +01:00
ActionRowBuilder,
ButtonBuilder,
StringSelectMenuBuilder,
StringSelectMenuOptionBuilder,
2024-07-09 22:49:49 +01:00
ButtonStyle,
2024-11-09 00:37:51 +00:00
MessageCollector,
2025-09-20 23:09:22 +01:00
MessageFlags,
2025-09-23 23:23:19 +01:00
SeparatorBuilder,
ContainerBuilder,
2026-03-10 20:02:43 +00:00
TextDisplayBuilder,
2021-12-14 18:04:38 -06:00
} = require("discord.js");
2021-10-24 22:46:36 +01:00
const yaml = require("js-yaml");
2022-08-12 01:35:36 +01:00
2025-09-20 23:09:22 +01:00
const db = require("../../Structures/Database.js");
2026-05-16 15:10:27 +01:00
const panelconfig = require("../../Structures/ConfigStore").ticketPanel;
const supportbot = require("../../Structures/ConfigStore").supportbot;
const cmdconfig = require("../../Structures/ConfigStore").commands;
const msgconfig = require("../../Structures/ConfigStore").messages;
2021-10-24 22:46:36 +01:00
2025-09-20 23:09:22 +01:00
const Command = require("../../Structures/Command.js");
const TicketNumberID = require("../../Structures/TicketID.js");
2021-10-24 22:46:36 +01:00
2024-11-09 00:37:51 +00:00
async function isBlacklisted(userId) {
2026-08-10 20:15:00 +01:00
return typeof db.isUserBlacklisted === "function" ? db.isUserBlacklisted(userId) : false;
2024-11-09 00:37:51 +00:00
}
2024-07-03 13:46:57 +01:00
async function getClockedInUsers(guild) {
const clockedInUsers = new Set();
2026-03-10 20:02:43 +00:00
if (typeof db.getProfile !== "function") {
2026-03-11 18:58:15 +00:00
console.error(
"db.getProfile is not a function. Check Structures/Database.js",
);
2026-03-10 20:02:43 +00:00
return clockedInUsers;
}
const rows = guild.members.cache.map((m) => m.id);
2025-09-20 23:09:22 +01:00
for (const id of rows) {
2026-03-10 20:02:43 +00:00
try {
const profile = db.getProfile(id);
if (profile && profile.clockedIn) {
clockedInUsers.add(id);
}
} catch (error) {
console.error(`Error getting profile for ${id}:`, error);
2021-12-18 20:54:01 -06:00
}
2024-07-03 13:46:57 +01:00
}
2026-03-10 20:02:43 +00:00
2024-07-03 13:46:57 +01:00
return clockedInUsers;
}
2026-03-10 20:02:43 +00:00
function getDepartmentSystem() {
return supportbot.Ticket?.DepartmentSystem || {};
}
function getPrioritySystem() {
return supportbot.Ticket?.PrioritySystem || {};
}
2026-03-11 18:58:15 +00:00
function getAIModeConfig() {
return supportbot.Ticket?.AIMode || {};
}
function isAIModeEnabledGlobally() {
return getAIModeConfig().Enabled === true;
}
2026-03-10 20:02:43 +00:00
function areDepartmentsEnabled() {
return (
supportbot.Ticket?.TicketType !== "threads" &&
supportbot.Ticket?.DepartmentSystem?.Enabled === true
);
}
function arePrioritiesEnabled() {
return (
supportbot.Ticket?.TicketType !== "threads" &&
supportbot.Ticket?.PrioritySystem?.Enabled === true
);
}
2026-03-11 18:58:15 +00:00
function getSelectedDepartmentKey(interaction) {
const departmentSystem = getDepartmentSystem();
const departmentsEnabled = areDepartmentsEnabled();
const departments = departmentSystem.Departments || {};
if (!departmentsEnabled) {
return departmentSystem.DefaultDepartment || "general";
2026-03-10 20:02:43 +00:00
}
2026-03-11 18:58:15 +00:00
const selectedDepartment =
interaction.options?.getString?.("department") ||
interaction.department ||
departmentSystem.DefaultDepartment ||
"general";
2026-03-10 20:02:43 +00:00
2026-03-11 18:58:15 +00:00
if (departments[selectedDepartment]) {
return selectedDepartment;
}
const firstDepartmentKey = Object.keys(departments)[0];
return firstDepartmentKey || departmentSystem.DefaultDepartment || "general";
2026-03-10 20:02:43 +00:00
}
function getDepartmentConfig(departmentKey) {
if (!departmentKey) return null;
return getDepartmentSystem().Departments?.[departmentKey] || null;
}
function getPriorityKey(interaction, departmentKey) {
if (!arePrioritiesEnabled()) {
return null;
}
const prioritySystem = getPrioritySystem();
const departmentConfig = getDepartmentConfig(departmentKey);
return (
2026-03-11 18:58:15 +00:00
interaction.options?.getString?.("priority") ||
2026-03-10 20:02:43 +00:00
interaction.priority ||
departmentConfig?.DefaultPriority ||
prioritySystem.DefaultPriority ||
"medium"
);
}
function getPriorityConfig(priorityKey) {
if (!priorityKey) return null;
return getPrioritySystem().Priorities?.[priorityKey] || null;
}
function buildTicketChannelName(ticketNumberID, priorityKey, departmentConfig) {
2026-03-11 18:58:15 +00:00
const defaultPrefix =
supportbot.Ticket?.ChannelPrefix || supportbot.Ticket?.Channel || "ticket-";
2026-03-10 20:02:43 +00:00
const departmentPrefix = departmentConfig?.ChannelPrefix || defaultPrefix;
const safePrefix = String(departmentPrefix).trim() || "ticket-";
const baseName = `${safePrefix}${ticketNumberID}`;
if (!arePrioritiesEnabled() || !priorityKey) {
return baseName;
}
return `${baseName}-${String(priorityKey).toLowerCase()}`;
}
2026-03-11 18:58:15 +00:00
function buildAIModeButtons(ticketId, aiEnabled = false) {
const enableButton = new ButtonBuilder()
.setCustomId(`ticket_ai_enable:${ticketId}`)
.setLabel(msgconfig.Ticket.AIMode.EnableButton)
.setStyle(ButtonStyle.Success)
.setDisabled(aiEnabled);
2024-11-09 00:37:51 +00:00
2026-03-11 18:58:15 +00:00
const disableButton = new ButtonBuilder()
.setCustomId(`ticket_ai_disable:${ticketId}`)
.setLabel(msgconfig.Ticket.AIMode.DisableButton)
.setStyle(ButtonStyle.Secondary)
.setDisabled(!aiEnabled);
2024-11-09 00:37:51 +00:00
2026-03-11 18:58:15 +00:00
return new ActionRowBuilder().addComponents(enableButton, disableButton);
}
2024-11-09 00:37:51 +00:00
2026-03-11 18:58:15 +00:00
async function createAITicketThread(parentTicketChannel, interaction) {
const threadName = supportbot.Ticket.AIMode?.ThreadName || "ai-chat";
const aiThread = await parentTicketChannel.threads.create({
name: threadName,
autoArchiveDuration: 60,
reason: "ticket ai mode thread",
});
const welcomeEmbed = new EmbedBuilder()
.setTitle(msgconfig.Ticket.AIMode.ThreadWelcomeTitle)
.setDescription(msgconfig.Ticket.AIMode.ThreadWelcomeMessage)
.setColor(supportbot.Embed.Colours.General);
await aiThread.send({ embeds: [welcomeEmbed] });
if (typeof db.setTicketAIState === "function") {
db.setTicketAIState(parentTicketChannel.id, {
enabled: true,
aiChannelId: aiThread.id,
aiType: "thread",
enabledBy: interaction.user.id,
enabledAt: new Date().toISOString(),
});
}
return aiThread;
}
async function askTicketQuestions(ticketChannel, interaction, questions) {
return new Promise(async (resolve) => {
const answers = [];
let questionIndex = 0;
const sendNextQuestion = async () => {
if (questionIndex < questions.length) {
const questionsEmbed = new EmbedBuilder()
.setDescription(
`**${questionIndex + 1}.** ${questions[questionIndex]}`,
)
.setColor(supportbot.Embed.Colours.General);
const questionMessage = await ticketChannel.send({
embeds: [questionsEmbed],
});
2024-11-09 00:37:51 +00:00
2026-03-11 18:58:15 +00:00
const collector = new MessageCollector(ticketChannel, {
filter: (m) => m.author.id === interaction.user.id,
max: 1,
time: 60000,
2026-03-10 20:02:43 +00:00
});
2024-11-09 00:37:51 +00:00
2026-03-11 18:58:15 +00:00
collector.on("collect", async (message) => {
answers.push({
question: questions[questionIndex],
answer: message.content,
});
questionIndex++;
await message.delete().catch(() => {});
await questionMessage.delete().catch(() => {});
2024-11-09 00:37:51 +00:00
2026-03-11 18:58:15 +00:00
sendNextQuestion();
});
} else {
const summaryEmbed = new EmbedBuilder()
.setTitle(msgconfig.Ticket.TicketQuestions.Details.Title)
.setDescription(
msgconfig.Ticket.TicketQuestions.Details.Description.replace(
"%user%",
interaction.user.id,
),
)
.setColor(supportbot.Embed.Colours.General)
.addFields(
answers.map((a) => ({
name: `Q: ${a.question}`,
value: `A: ${a.answer}`,
inline: false,
})),
)
.setTimestamp();
await ticketChannel.send({ embeds: [summaryEmbed] });
resolve(answers);
}
};
sendNextQuestion();
});
2024-11-09 00:37:51 +00:00
}
2026-03-10 20:02:43 +00:00
async function assignTicketToUser(
ticketChannel,
Staff,
Admin,
interaction,
departmentRole = null,
attempts = 0,
) {
2024-07-03 13:46:57 +01:00
try {
const clockedInUsers = await getClockedInUsers(interaction.guild);
2026-03-10 20:02:43 +00:00
let eligibleClockedInUsers = Array.from(clockedInUsers);
2024-06-12 12:54:31 +01:00
2026-03-10 20:02:43 +00:00
if (departmentRole) {
eligibleClockedInUsers = eligibleClockedInUsers.filter((userId) => {
const member = interaction.guild.members.cache.get(userId);
return member?.roles?.cache?.has(departmentRole.id);
});
}
2021-10-24 22:46:36 +01:00
2026-03-10 20:02:43 +00:00
const claimChannel = interaction.guild.channels.cache.get(
supportbot.Ticket.ClaimTickets.Channel,
);
2024-07-03 13:46:57 +01:00
2026-03-10 20:02:43 +00:00
if (!claimChannel) {
console.error("Claim channel not found");
return null;
2021-10-24 22:46:36 +01:00
}
2022-08-12 01:35:36 +01:00
2026-03-10 20:02:43 +00:00
const claimPing = departmentRole
? `<@&${departmentRole.id}> <@&${Admin.id}>`
: `<@&${Staff.id}> <@&${Admin.id}>`;
2022-08-12 01:35:36 +01:00
2024-07-03 13:46:57 +01:00
const claimEmbed = new EmbedBuilder()
2024-10-10 19:14:45 +01:00
.setTitle(msgconfig.Ticket.ClaimTickets.ClaimTitle)
2025-09-20 23:09:22 +01:00
.setDescription(
2026-03-11 18:58:15 +00:00
msgconfig.Ticket.ClaimTickets.ClaimMessage.replace(
"%user%",
interaction.user.id,
),
2025-09-20 23:09:22 +01:00
)
2024-07-03 13:46:57 +01:00
.setColor(supportbot.Embed.Colours.General);
2022-08-12 01:35:36 +01:00
2024-07-03 13:46:57 +01:00
const claimButton = new ButtonBuilder()
.setCustomId(`claimticket-${ticketChannel.id}`)
2025-09-20 23:09:22 +01:00
.setLabel(supportbot.Ticket.ClaimTickets.ButtonTitle || "Claim Ticket")
2026-03-10 20:02:43 +00:00
.setEmoji(supportbot.Ticket.ClaimTickets.ButtonEmoji || null)
2024-07-03 13:46:57 +01:00
.setStyle(ButtonStyle.Primary);
2024-06-12 12:54:31 +01:00
2024-07-03 13:46:57 +01:00
const row = new ActionRowBuilder().addComponents(claimButton);
2024-06-12 12:54:31 +01:00
2026-03-10 20:02:43 +00:00
if (eligibleClockedInUsers.length === 0 || attempts >= 2) {
const claimMessage = await claimChannel.send({
content: claimPing,
embeds: [claimEmbed],
components: [row],
});
const filter = (i) => {
if (i.customId !== `claimticket-${ticketChannel.id}`) return false;
const hasAdmin = i.member.roles.cache.has(Admin.id);
const hasStaff = i.member.roles.cache.has(Staff.id);
const hasDepartmentRole = departmentRole
? i.member.roles.cache.has(departmentRole.id)
: false;
return hasAdmin || hasStaff || hasDepartmentRole;
};
const collector = claimMessage.createMessageComponentCollector({
filter,
time: 120000,
});
collector.on("collect", async (i) => {
await i.deferUpdate().catch(() => {});
if (supportbot.Ticket.TicketType === "threads") {
await ticketChannel.members.add(i.user.id).catch(() => {});
}
await ticketChannel.send({
content: `<@${i.user.id}> has claimed the ticket.`,
});
if (typeof db.claimTicket === "function") {
db.claimTicket(ticketChannel.id, i.user.id);
}
2026-03-11 18:58:15 +00:00
await claimMessage.edit({ components: [] }).catch(() => {});
2026-03-10 20:02:43 +00:00
});
collector.on("end", async (collected) => {
if (collected.size === 0) {
await claimMessage.delete().catch(() => {});
await claimChannel.send({
content: `Ticket is still unclaimed.\nCC: ${claimPing}`,
embeds: [
new EmbedBuilder()
2026-03-11 18:58:15 +00:00
.setDescription(
`Ticket is still unclaimed: <#${ticketChannel.id}>`,
)
2026-03-10 20:02:43 +00:00
.setColor(supportbot.Embed.Colours.Warn),
],
});
}
});
return null;
}
const assignedUserId =
2026-03-11 18:58:15 +00:00
eligibleClockedInUsers[
Math.floor(Math.random() * eligibleClockedInUsers.length)
];
2026-03-10 20:02:43 +00:00
2026-03-11 18:58:15 +00:00
const assignedUser = await interaction.guild.members
.fetch(assignedUserId)
.catch(() => null);
2026-03-10 20:02:43 +00:00
if (!assignedUser) {
2024-07-03 13:46:57 +01:00
return null;
2021-12-14 18:04:38 -06:00
}
2024-06-12 12:54:31 +01:00
2024-07-03 13:46:57 +01:00
const claimMessage = await claimChannel.send({
content: `${assignedUser}`,
embeds: [claimEmbed],
components: [row],
});
2024-06-12 12:54:31 +01:00
2026-03-10 20:02:43 +00:00
const filter = (i) => {
if (i.customId !== `claimticket-${ticketChannel.id}`) return false;
return i.user.id === assignedUserId;
};
const collector = claimMessage.createMessageComponentCollector({
filter,
time: 120000,
});
2024-06-12 12:54:31 +01:00
2025-09-20 23:09:22 +01:00
collector.on("collect", async (i) => {
2026-03-10 20:02:43 +00:00
await i.deferUpdate().catch(() => {});
if (supportbot.Ticket.TicketType === "threads") {
await ticketChannel.members.add(i.user.id).catch(() => {});
}
2025-09-20 23:09:22 +01:00
await ticketChannel
2026-03-11 18:58:15 +00:00
.send({
content: `<@${assignedUserId}> has claimed the ticket.`,
})
2026-03-10 20:02:43 +00:00
.catch(() => {});
if (typeof db.claimTicket === "function") {
db.claimTicket(ticketChannel.id, assignedUserId);
}
2026-03-11 18:58:15 +00:00
await claimMessage.edit({ components: [] }).catch(() => {});
2024-07-03 13:46:57 +01:00
});
2024-06-12 12:54:31 +01:00
2025-09-20 23:09:22 +01:00
collector.on("end", async (collected) => {
2024-07-03 13:46:57 +01:00
if (collected.size === 0) {
2026-03-10 20:02:43 +00:00
await claimMessage.delete().catch(() => {});
2024-06-12 12:54:31 +01:00
2024-07-03 13:46:57 +01:00
if (attempts >= 1) {
await claimChannel.send({
2026-03-10 20:02:43 +00:00
content: `No one claimed the ticket. Tagging roles: ${claimPing}`,
2025-09-20 23:09:22 +01:00
embeds: [
new EmbedBuilder()
2026-03-11 18:58:15 +00:00
.setDescription(
`Ticket is still unclaimed: <#${ticketChannel.id}>`,
)
2025-09-20 23:09:22 +01:00
.setColor(supportbot.Embed.Colours.Warn),
],
2024-07-03 13:46:57 +01:00
});
2024-06-12 12:54:31 +01:00
2026-03-10 20:02:43 +00:00
const retryClaimMessage = await claimChannel.send({
content: claimPing,
2024-07-03 13:46:57 +01:00
embeds: [claimEmbed],
components: [row],
});
2024-06-12 12:54:31 +01:00
2026-03-10 20:02:43 +00:00
const retryFilter = (i) => {
if (i.customId !== `claimticket-${ticketChannel.id}`) return false;
const hasAdmin = i.member.roles.cache.has(Admin.id);
const hasStaff = i.member.roles.cache.has(Staff.id);
const hasDepartmentRole = departmentRole
? i.member.roles.cache.has(departmentRole.id)
: false;
return hasAdmin || hasStaff || hasDepartmentRole;
};
2026-03-11 18:58:15 +00:00
const retryCollector =
retryClaimMessage.createMessageComponentCollector({
filter: retryFilter,
time: 120000,
});
2024-06-12 12:54:31 +01:00
2026-03-10 20:02:43 +00:00
retryCollector.on("collect", async (i) => {
await i.deferUpdate().catch(() => {});
if (supportbot.Ticket.TicketType === "threads") {
await ticketChannel.members.add(i.user.id).catch(() => {});
}
await ticketChannel.send({
content: `<@${i.user.id}> has claimed the ticket.`,
});
2024-06-12 12:54:31 +01:00
2026-03-10 20:02:43 +00:00
if (typeof db.claimTicket === "function") {
db.claimTicket(ticketChannel.id, i.user.id);
}
2024-07-03 13:46:57 +01:00
2026-03-11 18:58:15 +00:00
await retryClaimMessage.edit({ components: [] }).catch(() => {});
2026-03-10 20:02:43 +00:00
});
retryCollector.on("end", async (retryCollected) => {
if (retryCollected.size === 0) {
await retryClaimMessage.delete().catch(() => {});
2024-07-03 13:46:57 +01:00
await claimChannel.send({
2026-03-10 20:02:43 +00:00
content: `No one claimed the ticket. Tagging roles: ${claimPing}`,
2024-07-03 13:46:57 +01:00
});
}
});
} else {
const ReassigningEmbed = new EmbedBuilder()
.setTitle(msgconfig.Ticket.ClaimTickets.ReassigningTitle)
2025-09-20 23:09:22 +01:00
.setDescription(
2026-03-11 18:58:15 +00:00
msgconfig.Ticket.ClaimTickets.ReassigningMessage.replace(
"%user%",
assignedUserId,
),
2025-09-20 23:09:22 +01:00
)
2024-07-03 13:46:57 +01:00
.setColor(supportbot.Embed.Colours.General);
2026-03-10 20:02:43 +00:00
await claimChannel.send({
embeds: [ReassigningEmbed],
components: [],
});
await assignTicketToUser(
ticketChannel,
Staff,
Admin,
interaction,
departmentRole,
attempts + 1,
);
2024-07-03 13:46:57 +01:00
}
2021-11-23 15:48:45 -06:00
}
2024-07-03 13:46:57 +01:00
});
2021-10-24 22:46:36 +01:00
2024-07-03 13:46:57 +01:00
return assignedUserId;
} catch (error) {
console.error("Error in assignTicketToUser function:", error);
return null;
}
}
2021-10-24 22:46:36 +01:00
2024-07-03 13:46:57 +01:00
module.exports = new Command({
name: cmdconfig.OpenTicket.Command,
description: cmdconfig.OpenTicket.Description,
type: ApplicationCommandType.ChatInput,
2026-03-10 20:02:43 +00:00
2024-07-03 13:46:57 +01:00
options: [
2026-03-10 20:02:43 +00:00
...(areDepartmentsEnabled()
? [
{
name: "department",
description: "Choose a department",
type: ApplicationCommandOptionType.String,
required: true,
choices: Object.entries(getDepartmentSystem().Departments || {})
.slice(0, 25)
.map(([key, dept]) => ({
name: dept.Name || key,
value: key,
})),
},
]
: []),
...(arePrioritiesEnabled()
? [
{
name: "priority",
description: "Choose a priority",
type: ApplicationCommandOptionType.String,
required: false,
choices: Object.entries(getPrioritySystem().Priorities || {})
.slice(0, 25)
.map(([key, priority]) => ({
name: priority.Name || key,
value: key,
})),
},
]
: []),
2024-07-03 13:46:57 +01:00
{
name: "reason",
description: "Ticket Reason",
type: ApplicationCommandOptionType.String,
2026-03-10 20:02:43 +00:00
required: false,
2024-07-03 13:46:57 +01:00
},
],
2024-06-12 12:54:31 +01:00
2026-03-10 20:02:43 +00:00
permissions: cmdconfig.OpenTicket.Permission,
2026-03-11 18:58:15 +00:00
2024-07-03 13:46:57 +01:00
async run(interaction) {
try {
2026-04-21 13:37:39 +01:00
if (!interaction.deferred && !interaction.replied) {
await interaction.deferReply({ flags: MessageFlags.Ephemeral }).catch(() => {});
}
2024-11-09 00:37:51 +00:00
if (await isBlacklisted(interaction.user.id)) {
2026-04-21 13:37:39 +01:00
return interaction.editReply({
2024-11-09 00:37:51 +00:00
content: msgconfig.Ticket.Blacklisted,
2026-03-10 20:02:43 +00:00
flags: MessageFlags.Ephemeral,
2024-11-09 00:37:51 +00:00
});
}
2024-07-09 22:49:49 +01:00
let TicketReason = null;
if (supportbot.Ticket.TicketReason) {
2026-03-11 18:58:15 +00:00
TicketReason =
interaction.options?.getString?.("reason") ||
interaction.reason ||
null;
2024-07-09 22:49:49 +01:00
}
2025-09-20 23:09:22 +01:00
let TicketData = { tickets: db.getAllTickets() };
2024-07-03 13:46:57 +01:00
const { getRole, getChannel } = interaction.client;
if (
supportbot.Ticket.TicketsPerUser &&
2026-03-11 18:58:15 +00:00
TicketData.tickets.filter(
(t) => t.user === interaction.user.id && t.open,
).length >= supportbot.Ticket.TicketsPerUser
2024-07-03 13:46:57 +01:00
) {
2026-04-21 13:37:39 +01:00
return interaction.editReply({
2024-07-03 13:46:57 +01:00
embeds: [
{
title: "Too Many Tickets!",
description: `You can't have more than ${supportbot.Ticket.TicketsPerUser} open tickets!`,
color: supportbot.Embed.Colours.Warn,
},
],
2026-03-10 20:02:43 +00:00
flags: MessageFlags.Ephemeral,
2021-12-04 20:57:23 -06:00
});
2024-07-03 13:46:57 +01:00
}
2021-10-24 22:46:36 +01:00
2026-03-10 20:02:43 +00:00
const usingThreads = supportbot.Ticket.TicketType === "threads";
const departmentsEnabled = areDepartmentsEnabled();
const prioritiesEnabled = arePrioritiesEnabled();
2026-03-11 18:58:15 +00:00
const aiModeEnabled = isAIModeEnabledGlobally();
2026-03-10 20:02:43 +00:00
2026-03-11 18:58:15 +00:00
const departmentKey = departmentsEnabled
? getSelectedDepartmentKey(interaction)
: null;
const departmentConfig = departmentsEnabled
? getDepartmentConfig(departmentKey)
: null;
2026-03-10 20:02:43 +00:00
2026-03-11 18:58:15 +00:00
const priorityKey = prioritiesEnabled
? getPriorityKey(interaction, departmentKey)
: null;
const priorityConfig = prioritiesEnabled
? getPriorityConfig(priorityKey)
: null;
2026-03-10 20:02:43 +00:00
2024-07-03 13:46:57 +01:00
let ticketNumberID = await TicketNumberID.pad();
2024-07-09 22:49:49 +01:00
const TicketSubject = TicketReason || msgconfig.Ticket.InvalidSubject;
2026-03-10 20:02:43 +00:00
const ticketChannelName = buildTicketChannelName(
ticketNumberID,
priorityKey,
departmentConfig,
);
2024-07-03 13:46:57 +01:00
const TicketExists = new EmbedBuilder()
.setTitle("Ticket Exists!")
.setDescription(msgconfig.Ticket.TicketExists);
if (
await interaction.guild.channels.cache.find(
2026-03-10 20:02:43 +00:00
(ticketChannel) => ticketChannel.name === ticketChannelName,
2024-07-03 13:46:57 +01:00
)
) {
2026-04-21 13:37:39 +01:00
return await interaction.editReply({
2024-07-03 13:46:57 +01:00
embeds: [TicketExists],
2026-03-10 20:02:43 +00:00
flags: MessageFlags.Ephemeral,
2024-07-03 13:46:57 +01:00
});
}
2026-03-11 18:58:15 +00:00
const Staff = await getRole(
supportbot.Roles.StaffMember.Staff,
interaction.guild,
);
const Admin = await getRole(
supportbot.Roles.StaffMember.Admin,
interaction.guild,
);
2026-03-10 20:02:43 +00:00
2026-03-11 18:58:15 +00:00
const departmentRoleId = departmentsEnabled
? departmentConfig?.Role || null
: null;
2026-03-10 20:02:43 +00:00
const DepartmentRole = departmentRoleId
? await getRole(departmentRoleId, interaction.guild)
: null;
if (!Staff || !Admin) {
2026-04-21 13:37:39 +01:00
return interaction.editReply({
2026-03-11 18:58:15 +00:00
content:
"Some roles seem to be missing!\nPlease check for errors when starting the bot.",
2026-03-10 20:02:43 +00:00
flags: MessageFlags.Ephemeral,
2024-07-03 13:46:57 +01:00
});
2026-03-10 20:02:43 +00:00
}
2024-07-03 13:46:57 +01:00
2024-10-10 19:14:45 +01:00
let ticketChannel;
2024-07-03 13:46:57 +01:00
2026-03-10 20:02:43 +00:00
if (usingThreads) {
2024-10-10 19:14:45 +01:00
const TicketHome = interaction.guild.channels.cache.find(
2026-03-11 18:58:15 +00:00
(c) =>
c.name === supportbot.Ticket.TicketHome ||
c.id === supportbot.Ticket.TicketHome,
2024-10-10 19:14:45 +01:00
);
2026-03-11 18:58:15 +00:00
const channel = await getChannel(
supportbot.Ticket.TicketHome,
interaction.guild,
);
2026-03-10 20:02:43 +00:00
2024-10-10 19:14:45 +01:00
ticketChannel = await channel.threads.create({
2026-03-10 20:02:43 +00:00
name: ticketChannelName,
2024-10-10 19:14:45 +01:00
type: ChannelType.PrivateThread,
parent: TicketHome,
autoArchiveDuration: 60,
2026-03-10 20:02:43 +00:00
reason: "support ticket",
2024-10-10 19:14:45 +01:00
});
2026-03-11 18:58:15 +00:00
} else {
2026-03-10 20:02:43 +00:00
let targetCategoryId = supportbot.Ticket.TicketChannelsCategory;
if (departmentsEnabled && departmentConfig?.Category) {
targetCategoryId = departmentConfig.Category;
}
2024-10-10 19:14:45 +01:00
const category = interaction.guild.channels.cache.find(
2026-03-10 20:02:43 +00:00
(c) => c.name === targetCategoryId || c.id === targetCategoryId,
2024-10-10 19:14:45 +01:00
);
if (!category) {
2026-04-21 13:37:39 +01:00
return interaction.editReply({
2024-10-10 19:14:45 +01:00
content: "The ticket category does not exist!",
2026-03-10 20:02:43 +00:00
flags: MessageFlags.Ephemeral,
2024-10-10 19:14:45 +01:00
});
}
ticketChannel = await interaction.guild.channels.create({
2026-03-10 20:02:43 +00:00
name: ticketChannelName,
2024-10-10 19:14:45 +01:00
type: ChannelType.GuildText,
parent: category.id,
2026-03-10 20:02:43 +00:00
reason: "support ticket",
2024-10-10 19:14:45 +01:00
});
2026-03-11 18:58:15 +00:00
if (
!departmentsEnabled &&
category.children.cache &&
category.children.cache.size >= 50
) {
2024-10-10 19:14:45 +01:00
const secondaryCategory = interaction.guild.channels.cache.find(
2026-03-10 20:02:43 +00:00
(c) =>
c.name === supportbot.Ticket.TicketChannelsCategory2 ||
c.id === supportbot.Ticket.TicketChannelsCategory2,
2024-10-10 19:14:45 +01:00
);
if (secondaryCategory) {
await ticketChannel.setParent(secondaryCategory.id);
} else {
2026-04-21 13:37:39 +01:00
return interaction.editReply({
2024-10-10 19:14:45 +01:00
content: "The secondary ticket category does not exist!",
2026-03-10 20:02:43 +00:00
flags: MessageFlags.Ephemeral,
2024-10-10 19:14:45 +01:00
});
}
}
}
2024-07-03 13:46:57 +01:00
if (supportbot.Ticket.ClaimTickets.Enabled) {
2026-03-11 18:58:15 +00:00
await assignTicketToUser(
ticketChannel,
Staff,
Admin,
interaction,
DepartmentRole || null,
);
2024-06-12 12:54:31 +01:00
}
2024-07-03 13:46:57 +01:00
2026-03-10 20:02:43 +00:00
if (usingThreads) {
await ticketChannel.members.add(interaction.user.id);
2026-03-11 18:58:15 +00:00
} else {
2024-10-10 19:14:45 +01:00
await ticketChannel.permissionOverwrites.create(interaction.user.id, {
2026-03-10 20:02:43 +00:00
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
2024-07-03 13:46:57 +01:00
2026-03-10 20:02:43 +00:00
await ticketChannel.permissionOverwrites.create(Admin.id, {
2024-10-10 19:14:45 +01:00
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
2026-03-10 20:02:43 +00:00
});
2024-10-10 19:14:45 +01:00
2026-03-10 20:02:43 +00:00
await ticketChannel.permissionOverwrites.create(Staff.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
if (DepartmentRole) {
await ticketChannel.permissionOverwrites.create(DepartmentRole.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
}
2026-03-11 18:58:15 +00:00
await ticketChannel.permissionOverwrites.create(
interaction.guild.roles.everyone.id,
{
ViewChannel: false,
},
);
2026-03-10 20:02:43 +00:00
}
2024-11-09 00:37:51 +00:00
2025-09-20 23:09:22 +01:00
db.addTicket({
2024-07-03 13:46:57 +01:00
id: ticketChannel.id,
name: ticketChannel.name,
user: interaction.user.id,
number: ticketNumberID,
reason: TicketSubject,
open: true,
subUsers: [],
createdAt: new Date().toISOString(),
claimedAt: null,
2026-03-10 20:02:43 +00:00
department: departmentKey,
priority: priorityKey,
2026-03-11 18:58:15 +00:00
aiModeEnabled: !usingThreads && aiModeEnabled,
aiChannelId: null,
questionAnswers: [],
2024-07-03 13:46:57 +01:00
});
2026-03-10 20:02:43 +00:00
2025-09-20 23:09:22 +01:00
TicketData.tickets = db.getAllTickets();
2024-07-03 13:46:57 +01:00
const CreatedTicket = new EmbedBuilder()
.setDescription(
2026-03-11 18:58:15 +00:00
msgconfig.Ticket.TicketCreatedAlert.replace(
/%ticketauthor%/g,
interaction.user.id,
)
2024-07-03 13:46:57 +01:00
.replace(/%ticketid%/g, ticketChannel.id)
.replace(/%ticketusername%/g, interaction.user.username)
2026-03-10 20:02:43 +00:00
.replace(/%ticketreason%/g, TicketSubject),
2024-07-03 13:46:57 +01:00
)
.setColor(supportbot.Embed.Colours.General);
2026-04-21 13:37:39 +01:00
await interaction.editReply({
2026-03-10 20:02:43 +00:00
embeds: [CreatedTicket],
flags: MessageFlags.Ephemeral,
});
const ticketMsgContainer = new ContainerBuilder();
2025-09-23 23:23:19 +01:00
if (supportbot.Embed.Colours.General) {
ticketMsgContainer.setAccentColor(
2026-03-10 20:02:43 +00:00
parseInt(supportbot.Embed.Colours.General.replace("#", ""), 16),
);
2024-07-09 22:49:49 +01:00
}
2025-09-23 23:23:19 +01:00
const ticketAuthor = new TextDisplayBuilder().setContent(
2026-03-11 18:58:15 +00:00
msgconfig.Ticket.TicketAuthorTitle.replace(
/%ticketauthor%/g,
interaction.user.id,
)
2025-09-23 23:23:19 +01:00
.replace(/%ticketid%/g, ticketChannel.id)
.replace(/%ticketusername%/g, interaction.user.username),
);
2026-03-10 20:02:43 +00:00
ticketMsgContainer.addTextDisplayComponents(ticketAuthor);
2025-09-23 23:23:19 +01:00
const ticketTitle = new TextDisplayBuilder().setContent(
2026-03-11 18:58:15 +00:00
msgconfig.Ticket.TicketTitle.replace(
/%ticketauthor%/g,
interaction.user.id,
)
2025-09-23 23:23:19 +01:00
.replace(/%ticketid%/g, ticketChannel.id)
2026-03-10 20:02:43 +00:00
.replace(/%ticketusername%/g, interaction.user.username),
2025-09-23 23:23:19 +01:00
);
2026-03-10 20:02:43 +00:00
ticketMsgContainer.addTextDisplayComponents(ticketTitle);
2025-09-23 23:23:19 +01:00
2026-03-10 20:02:43 +00:00
const seperator2 = new SeparatorBuilder().setDivider(true);
ticketMsgContainer.addSeparatorComponents(seperator2);
2025-09-23 23:23:19 +01:00
const ticketDescription = new TextDisplayBuilder().setContent(
2026-03-11 18:58:15 +00:00
msgconfig.Ticket.TicketMessage.replace(
/%ticketauthor%/g,
interaction.user.id,
)
2025-09-23 23:23:19 +01:00
.replace(/%ticketid%/g, ticketChannel.id)
.replace(/%ticketusername%/g, interaction.user.username)
2026-03-10 20:02:43 +00:00
.replace(/%ticketreason%/g, TicketSubject),
2025-09-23 23:23:19 +01:00
);
2026-03-10 20:02:43 +00:00
ticketMsgContainer.addTextDisplayComponents(ticketDescription);
2025-09-23 23:23:19 +01:00
2026-03-10 20:02:43 +00:00
const seperator3 = new SeparatorBuilder().setDivider(true);
ticketMsgContainer.addSeparatorComponents(seperator3);
2025-09-23 23:23:19 +01:00
2026-03-10 20:02:43 +00:00
if (supportbot.Ticket.TicketReason && TicketReason) {
2026-03-11 18:58:15 +00:00
const ticketReason = new TextDisplayBuilder().setContent(
`**Reason:**\n${TicketReason}`,
);
2026-03-10 20:02:43 +00:00
ticketMsgContainer.addTextDisplayComponents(ticketReason);
}
2025-09-23 23:23:19 +01:00
2026-03-10 20:02:43 +00:00
if (departmentsEnabled && departmentConfig) {
const deptLabel = departmentConfig?.Name || departmentKey || "General";
const deptEmoji = departmentConfig?.Emoji || "🎫";
2025-09-23 23:23:19 +01:00
2026-03-10 20:02:43 +00:00
const departmentText = new TextDisplayBuilder().setContent(
`**Department:** ${deptEmoji} ${deptLabel}`,
);
ticketMsgContainer.addTextDisplayComponents(departmentText);
}
if (prioritiesEnabled && priorityConfig && priorityKey) {
const priorityLabel = priorityConfig?.Name || priorityKey || "Medium";
const priorityEmoji = priorityConfig?.Emoji || "🟡";
const priorityText = new TextDisplayBuilder().setContent(
`**Priority:** ${priorityEmoji} ${priorityLabel}`,
);
ticketMsgContainer.addTextDisplayComponents(priorityText);
2025-09-25 13:09:00 +01:00
}
2026-03-11 18:58:15 +00:00
if (usingThreads && aiModeEnabled) {
const aiSeparator = new SeparatorBuilder().setDivider(true);
ticketMsgContainer.addSeparatorComponents(aiSeparator);
const aiTitleText = new TextDisplayBuilder().setContent(
`**${msgconfig.Ticket.AIMode.Title}**`,
);
ticketMsgContainer.addTextDisplayComponents(aiTitleText);
const aiBodyText = new TextDisplayBuilder().setContent(
msgconfig.Ticket.AIMode.Description,
);
ticketMsgContainer.addTextDisplayComponents(aiBodyText);
const aiRow = buildAIModeButtons(ticketChannel.id, false);
ticketMsgContainer.addActionRowComponents(aiRow);
}
2025-09-23 23:23:19 +01:00
const selectMenuRow = new ActionRowBuilder();
2024-07-03 13:46:57 +01:00
const SelectMenus = new StringSelectMenuBuilder()
2026-03-10 20:02:43 +00:00
.setCustomId("ticketcontrolpanel")
.setPlaceholder("Ticket Control Panel")
.addOptions(
2024-07-30 17:45:35 +01:00
new StringSelectMenuOptionBuilder()
2026-03-10 20:02:43 +00:00
.setLabel("Close")
.setDescription("Close the ticket.")
.setEmoji(supportbot.SelectMenus.Tickets.CloseEmoji)
.setValue("ticketclose"),
new StringSelectMenuOptionBuilder()
.setLabel("Voice Channel")
.setDescription("Create a support voice channel.")
.setEmoji(supportbot.SelectMenus.Tickets.VCEmoji)
.setValue("supportvc"),
new StringSelectMenuOptionBuilder()
.setLabel("Archive")
.setDescription("Archive the ticket.")
.setEmoji(supportbot.SelectMenus.Tickets.ArchiveEmoji)
.setValue("archiveticket"),
...(supportbot.Ticket.TicketType === "threads"
? [
new StringSelectMenuOptionBuilder()
.setLabel("Lock Ticket")
.setDescription("Lock the ticket.")
.setEmoji(supportbot.SelectMenus.Tickets.LockEmoji)
.setValue("lockticket"),
]
: []),
new StringSelectMenuOptionBuilder()
.setLabel("Rename Ticket")
.setDescription("Rename the ticket.")
.setEmoji(supportbot.SelectMenus.Tickets.RenameEmoji)
.setValue("renameticket"),
);
2024-07-03 13:46:57 +01:00
2026-03-10 20:02:43 +00:00
if (supportbot.Ticket.TicketType === "threads") {
SelectMenus.addOptions(
new StringSelectMenuOptionBuilder()
.setLabel("Enable Invites")
.setDescription("Enable invites.")
.setEmoji(supportbot.SelectMenus.Tickets.EnableInvitesEmoji)
.setValue("enableinvites"),
new StringSelectMenuOptionBuilder()
.setLabel("Disable Invites")
.setDescription("Disable invites.")
.setEmoji(supportbot.SelectMenus.Tickets.DisableInvitesEmoji)
.setValue("disableinvites"),
);
}
2025-09-23 23:23:19 +01:00
2026-03-10 20:02:43 +00:00
selectMenuRow.addComponents(SelectMenus);
ticketMsgContainer.addActionRowComponents(selectMenuRow);
2025-09-23 23:23:19 +01:00
2022-02-11 23:34:38 -06:00
await ticketChannel.send({
2025-09-23 23:23:19 +01:00
flags: MessageFlags.IsComponentsV2,
components: [ticketMsgContainer],
2022-02-11 23:34:38 -06:00
});
2024-06-12 12:54:31 +01:00
2026-03-10 20:02:43 +00:00
const departmentQuestions =
2026-03-11 18:58:15 +00:00
departmentsEnabled && departmentConfig?.Questions
? departmentConfig.Questions
: [];
2026-03-10 20:02:43 +00:00
const globalQuestionsEnabled = supportbot.Ticket.Questions?.Enabled;
const globalQuestions = supportbot.Ticket.Questions?.List || [];
let questionsToAsk = [];
if (departmentQuestions.length > 0) {
questionsToAsk = departmentQuestions;
} else if (globalQuestionsEnabled && globalQuestions.length > 0) {
questionsToAsk = globalQuestions;
}
2026-03-11 18:58:15 +00:00
let ticketQuestionsAndAnswers = [];
2026-03-10 20:02:43 +00:00
if (questionsToAsk.length > 0) {
2026-03-11 18:58:15 +00:00
ticketQuestionsAndAnswers = await askTicketQuestions(
ticketChannel,
interaction,
questionsToAsk,
);
if (typeof db.updateTicketQuestionAnswers === "function") {
db.updateTicketQuestionAnswers(
ticketChannel.id,
ticketQuestionsAndAnswers,
);
}
}
if (!usingThreads && aiModeEnabled) {
const aiThread = await createAITicketThread(
ticketChannel,
interaction,
).catch((error) => {
console.error("Error creating AI mode thread:", error);
return null;
});
2026-03-10 20:02:43 +00:00
}
2024-07-03 13:46:57 +01:00
} catch (error) {
console.error("Error in run method:", error);
2026-03-10 20:02:43 +00:00
if (!interaction.replied && !interaction.deferred) {
2026-04-21 13:37:39 +01:00
return interaction.editReply({
2026-03-10 20:02:43 +00:00
content: "An error occurred while creating the ticket.",
flags: MessageFlags.Ephemeral,
});
}
2021-10-24 22:46:36 +01:00
}
},
});