Dynamic overflow categories

This commit is contained in:
indifferentketchup
2026-03-28 20:55:36 -05:00
parent 6b4fd65d4b
commit 1496a96274
10 changed files with 679 additions and 584 deletions

View File

@@ -13,7 +13,7 @@ const {
const { mongoose } = require('../db-connection');
const { CONFIG, TICKET_TAGS } = require('../config');
const { getPriorityEmoji, getPriorityColor, replaceVariables, escapeRegex } = require('../utils');
const { canRename, makeTicketName, pickTicketCategoryId, createDiscordTicketAsThread, checkTicketCreationRateLimit } = require('../services/tickets');
const { canRename, makeTicketName, getOrCreateTicketCategory, createDiscordTicketAsThread, checkTicketCreationRateLimit } = require('../services/tickets');
const { sendTicketNotificationEmail } = require('../services/gmail');
const { getTicketActionRow } = require('../utils/ticketComponents');
const { getEmailRouting } = require('../services/guildSettings');
@@ -118,7 +118,7 @@ async function runEscalation(interaction, ticket, nextTier, reason) {
const pendingEmbed = new EmbedBuilder()
.setDescription('Ticket will be escalated in a few seconds.')
.setColor(CONFIG.EMBED_COLOR_INFO);
await interaction.reply({ content: null, embeds: [pendingEmbed] });
await interaction.editReply({ embeds: [pendingEmbed] });
const creatorId = isDiscordTicket
? (ticket.gmailThreadId.split('-').pop() || '').trim()
@@ -228,10 +228,7 @@ async function runDeescalation(interaction, ticket) {
.setColor(0x00BFFF)
.setTitle(`✅ De-escalated to ${tierLabel} Support`)
.setFooter({ text: interaction.member?.displayName || interaction.user.username });
await interaction.reply({
embeds: [deescalateEmbed],
ephemeral: true
});
await interaction.editReply({ embeds: [deescalateEmbed] });
const logChan = await interaction.client.channels.fetch(CONFIG.LOG_CHAN).catch(() => null);
if (logChan) {
@@ -316,6 +313,7 @@ async function handleCommand(interaction) {
}
try {
await interaction.deferReply();
await runEscalation(interaction, ticket, nextTier, reason);
if (action === 'unclaim') {
await Ticket.updateOne(
@@ -325,7 +323,9 @@ async function handleCommand(interaction) {
}
} catch (err) {
console.error('Escalate error:', err);
await interaction.reply({ content: 'Failed to escalate this ticket.', ephemeral: true });
await interaction.editReply({ content: 'Failed to escalate this ticket.' }).catch(() =>
interaction.followUp({ content: 'Failed to escalate this ticket.', ephemeral: true }).catch(() => {})
);
}
}
@@ -357,10 +357,13 @@ async function handleCommand(interaction) {
}
try {
await interaction.deferReply({ ephemeral: true });
await runDeescalation(interaction, ticket);
} catch (err) {
console.error('Deescalate error:', err);
await interaction.reply({ content: 'Failed to deescalate this ticket.', ephemeral: true });
await interaction.editReply({ content: 'Failed to deescalate this ticket.' }).catch(() =>
interaction.followUp({ content: 'Failed to deescalate this ticket.', ephemeral: true }).catch(() => {})
);
}
}
@@ -1044,35 +1047,49 @@ async function handleContextMenu(interaction) {
const ticketNumber = (lastTicket?.ticketNumber || 0) + 1;
let channel;
let parentCategoryIdForTicket = null;
if (CONFIG.DISCORD_THREAD_CHANNEL_ID) {
try {
channel = await createDiscordTicketAsThread(guild, ticketNumber, message.author.id);
parentCategoryIdForTicket = channel.parent?.parentId ?? null;
} catch (err) {
console.error('Discord ticket thread create (from message) failed:', err.message);
return interaction.editReply('❌ Could not create ticket thread. Check DISCORD_THREAD_CHANNEL_ID.');
}
} else {
const categoryIds = [CONFIG.DISCORD_TICKET_CATEGORY_ID, ...(CONFIG.DISCORD_TICKET_OVERFLOW_CATEGORY_IDS || [])];
const parentId = pickTicketCategoryId(guild, categoryIds);
if (!parentId) {
return interaction.editReply('❌ Discord ticket category not found or all categories full (50 channels max). Contact an administrator.');
let parentId;
try {
parentId = await getOrCreateTicketCategory(
guild,
CONFIG.DISCORD_TICKET_CATEGORY_ID,
CONFIG.TICKET_CATEGORY_NAME
);
} catch (err) {
console.error('getOrCreateTicketCategory (context menu ticket):', err);
return interaction.editReply('❌ Discord ticket category could not be resolved. Contact an administrator.');
}
parentCategoryIdForTicket = parentId;
try {
channel = await guild.channels.create({
name: `ticket-${ticketNumber}`,
type: ChannelType.GuildText,
parent: parentId,
permissionOverwrites: [
{ id: guild.id, deny: [PermissionFlagsBits.ViewChannel] },
{
id: message.author.id,
allow: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.SendMessages, PermissionFlagsBits.ReadMessageHistory]
},
{
id: CONFIG.ROLE_ID_TO_PING,
allow: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.SendMessages, PermissionFlagsBits.ReadMessageHistory]
}
]
});
} catch (err) {
console.error('guild.channels.create (context menu ticket):', err);
return interaction.editReply('❌ Failed to create ticket channel. Contact an administrator.');
}
channel = await guild.channels.create({
name: `ticket-${ticketNumber}`,
type: ChannelType.GuildText,
parent: parentId,
permissionOverwrites: [
{ id: guild.id, deny: [PermissionFlagsBits.ViewChannel] },
{
id: message.author.id,
allow: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.SendMessages, PermissionFlagsBits.ReadMessageHistory]
},
{
id: CONFIG.ROLE_ID_TO_PING,
allow: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.SendMessages, PermissionFlagsBits.ReadMessageHistory]
}
]
});
}
const gmailThreadId = `discord-msg-${Date.now()}-${message.id}`;
@@ -1086,7 +1103,8 @@ async function handleContextMenu(interaction) {
status: 'open',
ticketNumber,
priority: 'normal',
lastActivity: now
lastActivity: now,
parentCategoryId: parentCategoryIdForTicket
});
const welcomeEmbed = new EmbedBuilder()