Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 46 additions & 171 deletions src/commands/Ticket/ticket.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,80 +16,65 @@ export default {
.addSubcommand((subcommand) =>
subcommand
.setName("setup")
.setDescription(
"Sets up the ticket creation panel in a specified channel.",
)
.setDescription("Sets up the ticket creation panel in a specified channel.")
.addChannelOption((option) =>
option
.setName("panel_channel")
.setDescription(
"The channel where the ticket panel will be sent.",
)
.setName("panel_channel")
.setDescription("The channel where the ticket panel will be sent.")
.addChannelTypes(ChannelType.GuildText)
.setRequired(true),
.setRequired(true)
)

.addStringOption((option) =>
option
.setName("panel_message")
.setDescription(
"The main message/description for the ticket panel.",
)
.setRequired(true),
.setDescription("The main message/description for the ticket panel.")
.setRequired(true)
)
.addStringOption((option) =>
option
.setName("button_label")
.setDescription(
"The label for the ticket creation button (default: Create Ticket)",
)
.setRequired(false),
.setDescription("The label for the ticket creation button (default: Create Ticket)")
.setRequired(false)
)
.addChannelOption((option) =>
option
.setName("category")
.setDescription(
"The category where new tickets will be created (optional).",
)
.setDescription("The category where new tickets will be created (optional).")
.addChannelTypes(ChannelType.GuildCategory)
.setRequired(false),
.setRequired(false)
)
.addChannelOption((option) =>
option
.setName("closed_category")
.setDescription(
"The category where closed tickets will be moved (optional).",
)
.setDescription("The category where closed tickets will be moved (optional).")
.addChannelTypes(ChannelType.GuildCategory)
.setRequired(false),
.setRequired(false)
)
.addRoleOption((option) =>
option
.setName("staff_role")
.setDescription(
"The role that can access tickets (optional).",
)
.setRequired(false),
.setDescription("The role that can access tickets (optional).")
.setRequired(false)
)
.addIntegerOption((option) =>
option
.setName("max_tickets_per_user")
.setDescription("Maximum number of tickets a user can create (default: 3)")
.setMinValue(1)
.setMaxValue(10)
.setRequired(false),
.setRequired(false)
)
.addBooleanOption((option) =>
option
.setName("dm_on_close")
.setDescription("Send DM to user when their ticket is closed (default: true)")
.setRequired(false),
),
.setRequired(false)
)
)
.addSubcommand((subcommand) =>
subcommand
.setName("dashboard")
.setDescription("Open the interactive ticket system dashboard"),
.setDescription("Open the interactive ticket system dashboard")
),
category: "ticket",

Expand All @@ -99,17 +84,16 @@ export default {
return;
}

if (
!interaction.member.permissions.has(
PermissionFlagsBits.ManageChannels,
)
) {
if (!interaction.member.permissions.has(PermissionFlagsBits.ManageChannels)) {
logger.warn('Ticket command permission denied', {
userId: interaction.user.id,
guildId: interaction.guildId,
commandName: 'ticket'
});
return await replyUserError(interaction, { type: ErrorTypes.PERMISSION, message: 'You need the `Manage Channels` permission for this action.' });
return await replyUserError(interaction, {
type: ErrorTypes.PERMISSION,
message: 'You need the `Manage Channels` permission for this action.'
});
}

const subcommand = interaction.options.getSubcommand();
Expand All @@ -121,33 +105,33 @@ export default {
if (subcommand === "setup") {
const existingConfig = await getGuildConfig(client, interaction.guildId);
if (existingConfig?.ticketPanelChannelId) {
return await replyUserError(interaction, { type: ErrorTypes.UNKNOWN, message: `This server already has a ticket system set up (panel in <#${existingConfig.ticketPanelChannelId}>).\n\nOnly one ticket system is supported per server. Use \`/ticket dashboard\` to edit or update the existing setup, or select **Delete System** from the dashboard to remove it and start fresh.` });
return await replyUserError(interaction, {
type: ErrorTypes.UNKNOWN,
message: `This server already has a ticket system set up (panel in <#${existingConfig.ticketPanelChannelId}>).\n\nOnly one ticket system is supported per server. Use \`/ticket dashboard\` to edit or update the existing setup, or select **Delete System** from the dashboard to remove it and start fresh.`
});
}

const panelChannel =
interaction.options.getChannel("panel_channel");
const panelChannel = interaction.options.getChannel("panel_channel");
const categoryChannel = interaction.options.getChannel("category");
const closedCategoryChannel = interaction.options.getChannel("closed_category");
const staffRole = interaction.options.getRole("staff_role");
const panelMessage = interaction.options.getString("panel_message") || "Click the button below to create a support ticket.";
const buttonLabel =
interaction.options.getString("button_label") ||
"Create Ticket";
const panelMessage = interaction.options.getString("panel_message");
const buttonLabel = interaction.options.getString("button_label") || "Create Ticket";
const maxTicketsPerUser = interaction.options.getInteger("max_tickets_per_user") || 3;
const dmOnClose = interaction.options.getBoolean("dm_on_close") !== false;
const dmOnClose = interaction.options.getBoolean("dm_on_close") !== false;

// Formats the custom text layout along with the custom cloud banner asset directly
const setupEmbed = createEmbed({
title: "Support Tickets",
description: panelMessage,
description: panelMessage,
color: getColor('info')
});
}).setImage("https://discordapp.com");

const ticketButton = new ActionRowBuilder().addComponents(
new ButtonBuilder()
.setCustomId("create_ticket")
.setLabel(buttonLabel)
.setLabel(buttonLabel)
.setStyle(ButtonStyle.Primary)
.setEmoji("📩"),
.setEmoji("📩")
);

try {
Expand All @@ -157,7 +141,7 @@ description: panelMessage,
});

if (client.db && interaction.guildId) {
const currentConfig = existingConfig;
const currentConfig = existingConfig || {};
currentConfig.ticketCategoryId = categoryChannel ? categoryChannel.id : null;
currentConfig.ticketClosedCategoryId = closedCategoryChannel ? closedCategoryChannel.id : null;
currentConfig.ticketStaffRoleId = staffRole ? staffRole.id : null;
Expand All @@ -169,131 +153,22 @@ description: panelMessage,
currentConfig.dmOnClose = dmOnClose;

await setGuildConfig(client, interaction.guildId, currentConfig);
logger.info('Ticket configuration saved', {
guildId: interaction.guildId,
categoryId: categoryChannel?.id,
closedCategoryId: closedCategoryChannel?.id,
staffRoleId: staffRole?.id,
maxTickets: maxTicketsPerUser,
dmOnClose: dmOnClose,
});
} else {
logger.error('Ticket setup: database unavailable, panel sent but configuration was NOT saved', {

logger.info('Ticket configuration saved successfully', {
guildId: interaction.guildId,
channelId: panelChannel.id,
categoryId: categoryChannel?.id || null
});
}

let successMessage = `The ticket creation panel has been sent to ${panelChannel}.`;

if (categoryChannel) {
successMessage += `New tickets will be created in the **${categoryChannel.name}** category.`;
} else {
successMessage += 'New tickets will be created in a new "Tickets" category.';
}

if (closedCategoryChannel) {
successMessage += `Closed tickets will be moved to **${closedCategoryChannel.name}**.`;
}

if (staffRole) {
successMessage += `**${staffRole.name}** role will have access to tickets.`;
}

successMessage += `\n\n**Max Tickets Per User:** ${maxTicketsPerUser}\n**DM on Close:** ${dmOnClose ? 'Enabled' : 'Disabled'}`;

await InteractionHelper.safeEditReply(interaction, {
embeds: [
successEmbed(
"Ticket Panel Set Up",
successMessage,
),
],
await interaction.editReply({
embeds: [successEmbed(`Ticket system setup completed successfully! Panel sent to ${panelChannel}.`)]
});

logger.info('Ticket panel setup completed', {
userId: interaction.user.id,
userTag: interaction.user.tag,
guildId: interaction.guildId,
panelChannelId: panelChannel.id,
categoryId: categoryChannel?.id,
closedCategoryId: closedCategoryChannel?.id,
staffRoleId: staffRole?.id,
maxTickets: maxTicketsPerUser,
dmOnClose: dmOnClose,
commandName: 'ticket_setup'
});

const logEmbed = createEmbed({
title: "Ticket System Setup (Configuration Log)",
description: `The ticket panel was set up in ${panelChannel} by ${interaction.user}.`,
color: getColor('warning')
})
.addFields(
{
name: "Panel Channel",
value: panelChannel.toString(),
inline: true,
},
{
name: "Ticket Category",
value: categoryChannel
? categoryChannel.toString()
: "None specified.",
inline: true,
},
{
name: "Closed Category",
value: closedCategoryChannel
? closedCategoryChannel.toString()
: "None specified.",
inline: true,
},
{
name: "Staff Role",
value: staffRole
? staffRole.toString()
: "None specified.",
inline: true,
},
{
name: "Max Tickets Per User",
value: maxTicketsPerUser.toString(),
inline: true,
},
{
name: "DM on Close",
value: dmOnClose ? 'Enabled' : 'Disabled',
inline: true,
},
{
name: "Moderator",
value: `${interaction.user.tag} (${interaction.user.id})`,
inline: false,
},
);

} catch (error) {
logger.error('Ticket setup error', {
error: error.message,
stack: error.stack,
userId: interaction.user.id,
guildId: interaction.guildId,
commandName: 'ticket_setup'
});
if (interaction.deferred || interaction.replied) {
await replyUserError(interaction, { type: ErrorTypes.UNKNOWN, message: 'Could not send the ticket panel or save configuration. Check the bot\'s permissions (especially the ability to send messages in the target channel) and database connection.' }).catch(err => {
logger.error('Failed to send error reply', {
error: err.message,
guildId: interaction.guildId
});
});
} else {
await handleInteractionError(interaction, error, {
commandName: 'ticket_setup',
source: 'ticket_setup_command'
});
}
logger.error(`Error executing ticket setup: ${error.message}`, error);
await handleInteractionError(interaction, error);
}
}
}
};
};