Files
SupportBot/Commands/Tickets/ticket.js
T
2026-08-10 20:15:00 +01:00

1046 lines
31 KiB
JavaScript

const fs = require("fs");
const {
EmbedBuilder,
ApplicationCommandOptionType,
ApplicationCommandType,
ChannelType,
ActionRowBuilder,
ButtonBuilder,
StringSelectMenuBuilder,
StringSelectMenuOptionBuilder,
ButtonStyle,
MessageCollector,
MessageFlags,
SeparatorBuilder,
ContainerBuilder,
TextDisplayBuilder,
} = require("discord.js");
const yaml = require("js-yaml");
const db = require("../../Structures/Database.js");
const panelconfig = require("../../Structures/ConfigStore").ticketPanel;
const supportbot = require("../../Structures/ConfigStore").supportbot;
const cmdconfig = require("../../Structures/ConfigStore").commands;
const msgconfig = require("../../Structures/ConfigStore").messages;
const Command = require("../../Structures/Command.js");
const TicketNumberID = require("../../Structures/TicketID.js");
async function isBlacklisted(userId) {
return typeof db.isUserBlacklisted === "function" ? db.isUserBlacklisted(userId) : false;
}
async function getClockedInUsers(guild) {
const clockedInUsers = new Set();
if (typeof db.getProfile !== "function") {
console.error(
"db.getProfile is not a function. Check Structures/Database.js",
);
return clockedInUsers;
}
const rows = guild.members.cache.map((m) => m.id);
for (const id of rows) {
try {
const profile = db.getProfile(id);
if (profile && profile.clockedIn) {
clockedInUsers.add(id);
}
} catch (error) {
console.error(`Error getting profile for ${id}:`, error);
}
}
return clockedInUsers;
}
function getDepartmentSystem() {
return supportbot.Ticket?.DepartmentSystem || {};
}
function getPrioritySystem() {
return supportbot.Ticket?.PrioritySystem || {};
}
function getAIModeConfig() {
return supportbot.Ticket?.AIMode || {};
}
function isAIModeEnabledGlobally() {
return getAIModeConfig().Enabled === true;
}
function areDepartmentsEnabled() {
return (
supportbot.Ticket?.TicketType !== "threads" &&
supportbot.Ticket?.DepartmentSystem?.Enabled === true
);
}
function arePrioritiesEnabled() {
return (
supportbot.Ticket?.TicketType !== "threads" &&
supportbot.Ticket?.PrioritySystem?.Enabled === true
);
}
function getSelectedDepartmentKey(interaction) {
const departmentSystem = getDepartmentSystem();
const departmentsEnabled = areDepartmentsEnabled();
const departments = departmentSystem.Departments || {};
if (!departmentsEnabled) {
return departmentSystem.DefaultDepartment || "general";
}
const selectedDepartment =
interaction.options?.getString?.("department") ||
interaction.department ||
departmentSystem.DefaultDepartment ||
"general";
if (departments[selectedDepartment]) {
return selectedDepartment;
}
const firstDepartmentKey = Object.keys(departments)[0];
return firstDepartmentKey || departmentSystem.DefaultDepartment || "general";
}
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 (
interaction.options?.getString?.("priority") ||
interaction.priority ||
departmentConfig?.DefaultPriority ||
prioritySystem.DefaultPriority ||
"medium"
);
}
function getPriorityConfig(priorityKey) {
if (!priorityKey) return null;
return getPrioritySystem().Priorities?.[priorityKey] || null;
}
function buildTicketChannelName(ticketNumberID, priorityKey, departmentConfig) {
const defaultPrefix =
supportbot.Ticket?.ChannelPrefix || supportbot.Ticket?.Channel || "ticket-";
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()}`;
}
function buildAIModeButtons(ticketId, aiEnabled = false) {
const enableButton = new ButtonBuilder()
.setCustomId(`ticket_ai_enable:${ticketId}`)
.setLabel(msgconfig.Ticket.AIMode.EnableButton)
.setStyle(ButtonStyle.Success)
.setDisabled(aiEnabled);
const disableButton = new ButtonBuilder()
.setCustomId(`ticket_ai_disable:${ticketId}`)
.setLabel(msgconfig.Ticket.AIMode.DisableButton)
.setStyle(ButtonStyle.Secondary)
.setDisabled(!aiEnabled);
return new ActionRowBuilder().addComponents(enableButton, disableButton);
}
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],
});
const collector = new MessageCollector(ticketChannel, {
filter: (m) => m.author.id === interaction.user.id,
max: 1,
time: 60000,
});
collector.on("collect", async (message) => {
answers.push({
question: questions[questionIndex],
answer: message.content,
});
questionIndex++;
await message.delete().catch(() => {});
await questionMessage.delete().catch(() => {});
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();
});
}
async function assignTicketToUser(
ticketChannel,
Staff,
Admin,
interaction,
departmentRole = null,
attempts = 0,
) {
try {
const clockedInUsers = await getClockedInUsers(interaction.guild);
let eligibleClockedInUsers = Array.from(clockedInUsers);
if (departmentRole) {
eligibleClockedInUsers = eligibleClockedInUsers.filter((userId) => {
const member = interaction.guild.members.cache.get(userId);
return member?.roles?.cache?.has(departmentRole.id);
});
}
const claimChannel = interaction.guild.channels.cache.get(
supportbot.Ticket.ClaimTickets.Channel,
);
if (!claimChannel) {
console.error("Claim channel not found");
return null;
}
const claimPing = departmentRole
? `<@&${departmentRole.id}> <@&${Admin.id}>`
: `<@&${Staff.id}> <@&${Admin.id}>`;
const claimEmbed = new EmbedBuilder()
.setTitle(msgconfig.Ticket.ClaimTickets.ClaimTitle)
.setDescription(
msgconfig.Ticket.ClaimTickets.ClaimMessage.replace(
"%user%",
interaction.user.id,
),
)
.setColor(supportbot.Embed.Colours.General);
const claimButton = new ButtonBuilder()
.setCustomId(`claimticket-${ticketChannel.id}`)
.setLabel(supportbot.Ticket.ClaimTickets.ButtonTitle || "Claim Ticket")
.setEmoji(supportbot.Ticket.ClaimTickets.ButtonEmoji || null)
.setStyle(ButtonStyle.Primary);
const row = new ActionRowBuilder().addComponents(claimButton);
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);
}
await claimMessage.edit({ components: [] }).catch(() => {});
});
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()
.setDescription(
`Ticket is still unclaimed: <#${ticketChannel.id}>`,
)
.setColor(supportbot.Embed.Colours.Warn),
],
});
}
});
return null;
}
const assignedUserId =
eligibleClockedInUsers[
Math.floor(Math.random() * eligibleClockedInUsers.length)
];
const assignedUser = await interaction.guild.members
.fetch(assignedUserId)
.catch(() => null);
if (!assignedUser) {
return null;
}
const claimMessage = await claimChannel.send({
content: `${assignedUser}`,
embeds: [claimEmbed],
components: [row],
});
const filter = (i) => {
if (i.customId !== `claimticket-${ticketChannel.id}`) return false;
return i.user.id === assignedUserId;
};
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: `<@${assignedUserId}> has claimed the ticket.`,
})
.catch(() => {});
if (typeof db.claimTicket === "function") {
db.claimTicket(ticketChannel.id, assignedUserId);
}
await claimMessage.edit({ components: [] }).catch(() => {});
});
collector.on("end", async (collected) => {
if (collected.size === 0) {
await claimMessage.delete().catch(() => {});
if (attempts >= 1) {
await claimChannel.send({
content: `No one claimed the ticket. Tagging roles: ${claimPing}`,
embeds: [
new EmbedBuilder()
.setDescription(
`Ticket is still unclaimed: <#${ticketChannel.id}>`,
)
.setColor(supportbot.Embed.Colours.Warn),
],
});
const retryClaimMessage = await claimChannel.send({
content: claimPing,
embeds: [claimEmbed],
components: [row],
});
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;
};
const retryCollector =
retryClaimMessage.createMessageComponentCollector({
filter: retryFilter,
time: 120000,
});
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.`,
});
if (typeof db.claimTicket === "function") {
db.claimTicket(ticketChannel.id, i.user.id);
}
await retryClaimMessage.edit({ components: [] }).catch(() => {});
});
retryCollector.on("end", async (retryCollected) => {
if (retryCollected.size === 0) {
await retryClaimMessage.delete().catch(() => {});
await claimChannel.send({
content: `No one claimed the ticket. Tagging roles: ${claimPing}`,
});
}
});
} else {
const ReassigningEmbed = new EmbedBuilder()
.setTitle(msgconfig.Ticket.ClaimTickets.ReassigningTitle)
.setDescription(
msgconfig.Ticket.ClaimTickets.ReassigningMessage.replace(
"%user%",
assignedUserId,
),
)
.setColor(supportbot.Embed.Colours.General);
await claimChannel.send({
embeds: [ReassigningEmbed],
components: [],
});
await assignTicketToUser(
ticketChannel,
Staff,
Admin,
interaction,
departmentRole,
attempts + 1,
);
}
}
});
return assignedUserId;
} catch (error) {
console.error("Error in assignTicketToUser function:", error);
return null;
}
}
module.exports = new Command({
name: cmdconfig.OpenTicket.Command,
description: cmdconfig.OpenTicket.Description,
type: ApplicationCommandType.ChatInput,
options: [
...(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,
})),
},
]
: []),
{
name: "reason",
description: "Ticket Reason",
type: ApplicationCommandOptionType.String,
required: false,
},
],
permissions: cmdconfig.OpenTicket.Permission,
async run(interaction) {
try {
if (!interaction.deferred && !interaction.replied) {
await interaction.deferReply({ flags: MessageFlags.Ephemeral }).catch(() => {});
}
if (await isBlacklisted(interaction.user.id)) {
return interaction.editReply({
content: msgconfig.Ticket.Blacklisted,
flags: MessageFlags.Ephemeral,
});
}
let TicketReason = null;
if (supportbot.Ticket.TicketReason) {
TicketReason =
interaction.options?.getString?.("reason") ||
interaction.reason ||
null;
}
let TicketData = { tickets: db.getAllTickets() };
const { getRole, getChannel } = interaction.client;
if (
supportbot.Ticket.TicketsPerUser &&
TicketData.tickets.filter(
(t) => t.user === interaction.user.id && t.open,
).length >= supportbot.Ticket.TicketsPerUser
) {
return interaction.editReply({
embeds: [
{
title: "Too Many Tickets!",
description: `You can't have more than ${supportbot.Ticket.TicketsPerUser} open tickets!`,
color: supportbot.Embed.Colours.Warn,
},
],
flags: MessageFlags.Ephemeral,
});
}
const usingThreads = supportbot.Ticket.TicketType === "threads";
const departmentsEnabled = areDepartmentsEnabled();
const prioritiesEnabled = arePrioritiesEnabled();
const aiModeEnabled = isAIModeEnabledGlobally();
const departmentKey = departmentsEnabled
? getSelectedDepartmentKey(interaction)
: null;
const departmentConfig = departmentsEnabled
? getDepartmentConfig(departmentKey)
: null;
const priorityKey = prioritiesEnabled
? getPriorityKey(interaction, departmentKey)
: null;
const priorityConfig = prioritiesEnabled
? getPriorityConfig(priorityKey)
: null;
let ticketNumberID = await TicketNumberID.pad();
const TicketSubject = TicketReason || msgconfig.Ticket.InvalidSubject;
const ticketChannelName = buildTicketChannelName(
ticketNumberID,
priorityKey,
departmentConfig,
);
const TicketExists = new EmbedBuilder()
.setTitle("Ticket Exists!")
.setDescription(msgconfig.Ticket.TicketExists);
if (
await interaction.guild.channels.cache.find(
(ticketChannel) => ticketChannel.name === ticketChannelName,
)
) {
return await interaction.editReply({
embeds: [TicketExists],
flags: MessageFlags.Ephemeral,
});
}
const Staff = await getRole(
supportbot.Roles.StaffMember.Staff,
interaction.guild,
);
const Admin = await getRole(
supportbot.Roles.StaffMember.Admin,
interaction.guild,
);
const departmentRoleId = departmentsEnabled
? departmentConfig?.Role || null
: null;
const DepartmentRole = departmentRoleId
? await getRole(departmentRoleId, interaction.guild)
: null;
if (!Staff || !Admin) {
return interaction.editReply({
content:
"Some roles seem to be missing!\nPlease check for errors when starting the bot.",
flags: MessageFlags.Ephemeral,
});
}
let ticketChannel;
if (usingThreads) {
const TicketHome = interaction.guild.channels.cache.find(
(c) =>
c.name === supportbot.Ticket.TicketHome ||
c.id === supportbot.Ticket.TicketHome,
);
const channel = await getChannel(
supportbot.Ticket.TicketHome,
interaction.guild,
);
ticketChannel = await channel.threads.create({
name: ticketChannelName,
type: ChannelType.PrivateThread,
parent: TicketHome,
autoArchiveDuration: 60,
reason: "support ticket",
});
} else {
let targetCategoryId = supportbot.Ticket.TicketChannelsCategory;
if (departmentsEnabled && departmentConfig?.Category) {
targetCategoryId = departmentConfig.Category;
}
const category = interaction.guild.channels.cache.find(
(c) => c.name === targetCategoryId || c.id === targetCategoryId,
);
if (!category) {
return interaction.editReply({
content: "The ticket category does not exist!",
flags: MessageFlags.Ephemeral,
});
}
ticketChannel = await interaction.guild.channels.create({
name: ticketChannelName,
type: ChannelType.GuildText,
parent: category.id,
reason: "support ticket",
});
if (
!departmentsEnabled &&
category.children.cache &&
category.children.cache.size >= 50
) {
const secondaryCategory = interaction.guild.channels.cache.find(
(c) =>
c.name === supportbot.Ticket.TicketChannelsCategory2 ||
c.id === supportbot.Ticket.TicketChannelsCategory2,
);
if (secondaryCategory) {
await ticketChannel.setParent(secondaryCategory.id);
} else {
return interaction.editReply({
content: "The secondary ticket category does not exist!",
flags: MessageFlags.Ephemeral,
});
}
}
}
if (supportbot.Ticket.ClaimTickets.Enabled) {
await assignTicketToUser(
ticketChannel,
Staff,
Admin,
interaction,
DepartmentRole || null,
);
}
if (usingThreads) {
await ticketChannel.members.add(interaction.user.id);
} else {
await ticketChannel.permissionOverwrites.create(interaction.user.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
await ticketChannel.permissionOverwrites.create(Admin.id, {
ViewChannel: true,
SendMessages: true,
ReadMessageHistory: true,
});
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,
});
}
await ticketChannel.permissionOverwrites.create(
interaction.guild.roles.everyone.id,
{
ViewChannel: false,
},
);
}
db.addTicket({
id: ticketChannel.id,
name: ticketChannel.name,
user: interaction.user.id,
number: ticketNumberID,
reason: TicketSubject,
open: true,
subUsers: [],
createdAt: new Date().toISOString(),
claimedAt: null,
department: departmentKey,
priority: priorityKey,
aiModeEnabled: !usingThreads && aiModeEnabled,
aiChannelId: null,
questionAnswers: [],
});
TicketData.tickets = db.getAllTickets();
const CreatedTicket = new EmbedBuilder()
.setDescription(
msgconfig.Ticket.TicketCreatedAlert.replace(
/%ticketauthor%/g,
interaction.user.id,
)
.replace(/%ticketid%/g, ticketChannel.id)
.replace(/%ticketusername%/g, interaction.user.username)
.replace(/%ticketreason%/g, TicketSubject),
)
.setColor(supportbot.Embed.Colours.General);
await interaction.editReply({
embeds: [CreatedTicket],
flags: MessageFlags.Ephemeral,
});
const ticketMsgContainer = new ContainerBuilder();
if (supportbot.Embed.Colours.General) {
ticketMsgContainer.setAccentColor(
parseInt(supportbot.Embed.Colours.General.replace("#", ""), 16),
);
}
const ticketAuthor = new TextDisplayBuilder().setContent(
msgconfig.Ticket.TicketAuthorTitle.replace(
/%ticketauthor%/g,
interaction.user.id,
)
.replace(/%ticketid%/g, ticketChannel.id)
.replace(/%ticketusername%/g, interaction.user.username),
);
ticketMsgContainer.addTextDisplayComponents(ticketAuthor);
const ticketTitle = new TextDisplayBuilder().setContent(
msgconfig.Ticket.TicketTitle.replace(
/%ticketauthor%/g,
interaction.user.id,
)
.replace(/%ticketid%/g, ticketChannel.id)
.replace(/%ticketusername%/g, interaction.user.username),
);
ticketMsgContainer.addTextDisplayComponents(ticketTitle);
const seperator2 = new SeparatorBuilder().setDivider(true);
ticketMsgContainer.addSeparatorComponents(seperator2);
const ticketDescription = new TextDisplayBuilder().setContent(
msgconfig.Ticket.TicketMessage.replace(
/%ticketauthor%/g,
interaction.user.id,
)
.replace(/%ticketid%/g, ticketChannel.id)
.replace(/%ticketusername%/g, interaction.user.username)
.replace(/%ticketreason%/g, TicketSubject),
);
ticketMsgContainer.addTextDisplayComponents(ticketDescription);
const seperator3 = new SeparatorBuilder().setDivider(true);
ticketMsgContainer.addSeparatorComponents(seperator3);
if (supportbot.Ticket.TicketReason && TicketReason) {
const ticketReason = new TextDisplayBuilder().setContent(
`**Reason:**\n${TicketReason}`,
);
ticketMsgContainer.addTextDisplayComponents(ticketReason);
}
if (departmentsEnabled && departmentConfig) {
const deptLabel = departmentConfig?.Name || departmentKey || "General";
const deptEmoji = departmentConfig?.Emoji || "🎫";
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);
}
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);
}
const selectMenuRow = new ActionRowBuilder();
const SelectMenus = new StringSelectMenuBuilder()
.setCustomId("ticketcontrolpanel")
.setPlaceholder("Ticket Control Panel")
.addOptions(
new StringSelectMenuOptionBuilder()
.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"),
);
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"),
);
}
selectMenuRow.addComponents(SelectMenus);
ticketMsgContainer.addActionRowComponents(selectMenuRow);
await ticketChannel.send({
flags: MessageFlags.IsComponentsV2,
components: [ticketMsgContainer],
});
const departmentQuestions =
departmentsEnabled && departmentConfig?.Questions
? departmentConfig.Questions
: [];
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;
}
let ticketQuestionsAndAnswers = [];
if (questionsToAsk.length > 0) {
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;
});
}
} catch (error) {
console.error("Error in run method:", error);
if (!interaction.replied && !interaction.deferred) {
return interaction.editReply({
content: "An error occurred while creating the ticket.",
flags: MessageFlags.Ephemeral,
});
}
}
},
});