Dynamic overflow categories
This commit is contained in:
@@ -16,7 +16,7 @@ const {
|
||||
} = require('discord.js');
|
||||
const { mongoose } = require('../db-connection');
|
||||
const { CONFIG } = require('../config');
|
||||
const { canRename, makeTicketName, minutesFromMs, pickTicketCategoryId, createDiscordTicketAsThread, checkTicketCreationRateLimit } = require('../services/tickets');
|
||||
const { canRename, makeTicketName, minutesFromMs, getOrCreateTicketCategory, cleanupEmptyOverflowCategory, createDiscordTicketAsThread, checkTicketCreationRateLimit } = require('../services/tickets');
|
||||
const { sendTicketClosedEmail } = require('../services/gmail');
|
||||
const { getTicketActionRow } = require('../utils/ticketComponents');
|
||||
const { setEmailRouting } = require('../services/guildSettings');
|
||||
@@ -174,10 +174,13 @@ async function handleButton(interaction) {
|
||||
return interaction.reply({ content: 'Tier 2 (ESCALATED2) is not configured for this ticket type.', ephemeral: true });
|
||||
}
|
||||
try {
|
||||
await interaction.deferReply();
|
||||
await runEscalation(interaction, ticket, 1, 'Escalated via button (Tier 2)');
|
||||
} catch (err) {
|
||||
trackError('escalate-button-tier2', err, interaction);
|
||||
await interaction.reply({ content: 'Failed to escalate to tier 2.', ephemeral: true }).catch(() => {});
|
||||
await interaction.editReply({ content: 'Failed to escalate to tier 2.' }).catch(() =>
|
||||
interaction.followUp({ content: 'Failed to escalate to tier 2.', ephemeral: true }).catch(() => {})
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -194,10 +197,13 @@ async function handleButton(interaction) {
|
||||
return interaction.reply({ content: 'Tier 3 (ESCALATED3) is not configured for this ticket type.', ephemeral: true });
|
||||
}
|
||||
try {
|
||||
await interaction.deferReply();
|
||||
await runEscalation(interaction, ticket, 2, 'Escalated via button (Tier 3)');
|
||||
} catch (err) {
|
||||
trackError('escalate-button-tier3', err, interaction);
|
||||
await interaction.reply({ content: 'Failed to escalate to tier 3.', ephemeral: true }).catch(() => {});
|
||||
await interaction.editReply({ content: 'Failed to escalate to tier 3.' }).catch(() =>
|
||||
interaction.followUp({ content: 'Failed to escalate to tier 3.', ephemeral: true }).catch(() => {})
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -209,10 +215,13 @@ async function handleButton(interaction) {
|
||||
return interaction.reply({ content: 'This ticket is not escalated.', ephemeral: true });
|
||||
}
|
||||
try {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
await runDeescalation(interaction, ticket);
|
||||
} catch (err) {
|
||||
trackError('deescalate-button', err, interaction);
|
||||
await interaction.reply({ content: 'Failed to deescalate this ticket.', ephemeral: true }).catch(() => {});
|
||||
await interaction.editReply({ content: 'Failed to deescalate this ticket.' }).catch(() =>
|
||||
interaction.followUp({ content: 'Failed to deescalate this ticket.', ephemeral: true }).catch(() => {})
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -569,10 +578,20 @@ async function handleConfirmClose(interaction, ticket) {
|
||||
});
|
||||
}
|
||||
|
||||
const parentCatId = ticket.parentCategoryId;
|
||||
const guildRef = interaction.guild;
|
||||
|
||||
setTimeout(
|
||||
() => interaction.channel.delete().catch(() => {}),
|
||||
5000
|
||||
);
|
||||
setTimeout(() => {
|
||||
(async () => {
|
||||
if (parentCatId && guildRef) {
|
||||
await cleanupEmptyOverflowCategory(guildRef, parentCatId, CONFIG.TICKET_CATEGORY_NAME);
|
||||
}
|
||||
})();
|
||||
}, 6000);
|
||||
} catch (e) {
|
||||
console.error('Close ticket error:', e);
|
||||
}
|
||||
@@ -606,9 +625,11 @@ async function handleTicketModal(interaction) {
|
||||
const ticketNumber = (lastTicket?.ticketNumber || 0) + 1;
|
||||
|
||||
let channel;
|
||||
let parentCategoryIdForTicket = null;
|
||||
if (useThread && CONFIG.DISCORD_THREAD_CHANNEL_ID) {
|
||||
try {
|
||||
channel = await createDiscordTicketAsThread(guild, ticketNumber, interaction.user.id);
|
||||
parentCategoryIdForTicket = channel.parent?.parentId ?? null;
|
||||
} catch (err) {
|
||||
console.error('Discord ticket thread create failed:', err.message);
|
||||
return interaction.editReply('Could not create ticket thread. Check DISCORD_THREAD_CHANNEL_ID and try again.');
|
||||
@@ -616,27 +637,39 @@ async function handleTicketModal(interaction) {
|
||||
} else if (useThread && !CONFIG.DISCORD_THREAD_CHANNEL_ID) {
|
||||
return interaction.editReply('Thread tickets are not configured (DISCORD_THREAD_CHANNEL_ID is not set). Use a channel panel or set the env variable.');
|
||||
} 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 (ticket modal):', 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: interaction.user.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 (ticket modal):', 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: interaction.user.id,
|
||||
allow: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.SendMessages, PermissionFlagsBits.ReadMessageHistory]
|
||||
},
|
||||
{
|
||||
id: CONFIG.ROLE_ID_TO_PING,
|
||||
allow: [PermissionFlagsBits.ViewChannel, PermissionFlagsBits.SendMessages, PermissionFlagsBits.ReadMessageHistory]
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
const gmailThreadId = `discord-${Date.now()}-${interaction.user.id}`;
|
||||
@@ -651,7 +684,8 @@ async function handleTicketModal(interaction) {
|
||||
status: 'open',
|
||||
ticketNumber,
|
||||
priority,
|
||||
lastActivity: now
|
||||
lastActivity: now,
|
||||
parentCategoryId: parentCategoryIdForTicket
|
||||
});
|
||||
|
||||
const displayName = interaction.member?.displayName || interaction.user.username;
|
||||
|
||||
Reference in New Issue
Block a user