The 1028-line handlers/commands.js bundled escalation logic + force-close
flow + /response tag CRUD + /panel + /signature + context-menu handlers +
several config-toggle slash commands. After the dispatch-table refactor it
was still a god module. Split into handlers/commands/ with one file per
topic; require('./commands') resolves to handlers/commands/index.js
(handlers/commands.js is removed).
Layout:
helpers.js — requireStaffRole, fetchLoggingChannel
(cross-submodule, kept here to avoid cycles with index.js)
escalation.js — runEscalation, runDeescalation, handleEscalate, handleDeescalate
(run* are still exported via index.js for handlers/buttons.js)
close.js — handleForceClose, handleCancelClose, handleCloseTimer
+ finalizeForceClose / postTranscript (timer callback)
response.js — handleResponse + send/create/edit/delete/list subcommands
+ handleAutocomplete (only /response autocompletes)
panel.js — handlePanel, buildPanelButtonRow, handleSignature
contextMenu.js — handleCreateTicketFromMessage, handleViewUserTickets
index.js — dispatch tables, handleCommand/handleContextMenu, plus the
short-and-not-thematic handlers (notifydm, add, remove,
transfer, move, topic, staffthread, pinmessages, gmailpoll,
help) and the public re-exports.
No behavior change — every imported name, every Discord call, every DB
write, every embed, every reply payload preserved verbatim. Public surface
of require('./commands') is still { handleCommand, handleContextMenu,
handleAutocomplete, runEscalation, runDeescalation }.
Largest single module is now index.js at 299 lines; others are 33–214.
134 lines
4.5 KiB
JavaScript
134 lines
4.5 KiB
JavaScript
/**
|
|
* /panel — create a ticket-creation panel embed in a chosen channel.
|
|
* Also hosts /signature (modal for staff personal email signature) since
|
|
* both are user-facing UX-flow commands without their own dedicated module.
|
|
*/
|
|
const {
|
|
ActionRowBuilder,
|
|
ButtonBuilder,
|
|
ButtonStyle,
|
|
EmbedBuilder,
|
|
MessageFlags,
|
|
ModalBuilder,
|
|
TextInputBuilder,
|
|
TextInputStyle
|
|
} = require('discord.js');
|
|
const { mongoose } = require('../../db-connection');
|
|
const { CONFIG } = require('../../config');
|
|
const { enqueueSend } = require('../../services/channelQueue');
|
|
|
|
const StaffSignature = mongoose.model('StaffSignature');
|
|
|
|
async function handlePanel(interaction) {
|
|
const channel = interaction.options.getChannel('channel');
|
|
const panelType = interaction.options.getString('type') || null; // 'thread' | 'category' | 'both' or null
|
|
const title = interaction.options.getString('title') || 'Indifferent Broccoli Tickets';
|
|
const description = interaction.options.getString('description') ||
|
|
'Need help? Click below to create a ticket. 🎟';
|
|
|
|
const embed = new EmbedBuilder()
|
|
.setTitle(title)
|
|
.setDescription(description)
|
|
.setColor(0x2ecc71)
|
|
.setThumbnail(CONFIG.LOGO_URL || null)
|
|
.setFooter({ text: 'Indifferent Broccoli Tickets' });
|
|
|
|
const row = buildPanelButtonRow(panelType);
|
|
|
|
try {
|
|
await enqueueSend(channel, { embeds: [embed], components: [row] });
|
|
await interaction.reply({ content: `Panel created in ${channel}!`, flags: MessageFlags.Ephemeral });
|
|
} catch (err) {
|
|
console.error('Panel creation error:', err);
|
|
await interaction.reply({ content: 'Failed to create panel.', flags: MessageFlags.Ephemeral });
|
|
}
|
|
}
|
|
|
|
function buildPanelButtonRow(panelType) {
|
|
if (panelType === 'both') {
|
|
return new ActionRowBuilder().addComponents(
|
|
new ButtonBuilder()
|
|
.setCustomId('open_ticket_thread')
|
|
.setLabel('Create ticket (thread)')
|
|
.setStyle(ButtonStyle.Secondary)
|
|
.setEmoji('🧵'),
|
|
new ButtonBuilder()
|
|
.setCustomId('open_ticket_channel')
|
|
.setLabel('Create ticket (channel)')
|
|
.setStyle(ButtonStyle.Secondary)
|
|
.setEmoji('📁')
|
|
);
|
|
}
|
|
if (panelType === 'thread') {
|
|
return new ActionRowBuilder().addComponents(
|
|
new ButtonBuilder()
|
|
.setCustomId('open_ticket_thread')
|
|
.setLabel('Create ticket')
|
|
.setStyle(ButtonStyle.Secondary)
|
|
.setEmoji('🧵')
|
|
);
|
|
}
|
|
if (panelType === 'category') {
|
|
return new ActionRowBuilder().addComponents(
|
|
new ButtonBuilder()
|
|
.setCustomId('open_ticket_channel')
|
|
.setLabel('Create ticket')
|
|
.setStyle(ButtonStyle.Secondary)
|
|
.setEmoji('📁')
|
|
);
|
|
}
|
|
return new ActionRowBuilder().addComponents(
|
|
new ButtonBuilder()
|
|
.setCustomId('open_ticket')
|
|
.setLabel('Create ticket')
|
|
.setStyle(ButtonStyle.Secondary)
|
|
.setEmoji('✅')
|
|
);
|
|
}
|
|
|
|
async function handleSignature(interaction) {
|
|
try {
|
|
const existingSignature = await StaffSignature.findOne({ userId: interaction.user.id }).lean();
|
|
|
|
const modal = new ModalBuilder()
|
|
.setCustomId(`signature_modal_${interaction.user.id}`)
|
|
.setTitle('Staff Signature Settings');
|
|
|
|
const valedictionInput = new TextInputBuilder()
|
|
.setCustomId('valediction')
|
|
.setLabel('Valediction (e.g. "Best regards", "Thanks")')
|
|
.setStyle(TextInputStyle.Short)
|
|
.setRequired(false)
|
|
.setValue(existingSignature?.valediction || '');
|
|
|
|
const displayNameInput = new TextInputBuilder()
|
|
.setCustomId('display_name')
|
|
.setLabel('Display Name (e.g. "Support Team")')
|
|
.setStyle(TextInputStyle.Short)
|
|
.setRequired(false)
|
|
.setValue(existingSignature?.displayName || '');
|
|
|
|
const taglineInput = new TextInputBuilder()
|
|
.setCustomId('tagline')
|
|
.setLabel('Tagline (e.g. "Technical Support Specialist")')
|
|
.setStyle(TextInputStyle.Short)
|
|
.setRequired(false)
|
|
.setValue(existingSignature?.tagline || '');
|
|
|
|
modal.addComponents(
|
|
new ActionRowBuilder().addComponents(valedictionInput),
|
|
new ActionRowBuilder().addComponents(displayNameInput),
|
|
new ActionRowBuilder().addComponents(taglineInput)
|
|
);
|
|
|
|
await interaction.showModal(modal);
|
|
} catch (err) {
|
|
console.error('Signature command error:', err);
|
|
if (!interaction.replied && !interaction.deferred) {
|
|
await interaction.reply({ content: 'Failed to open signature settings.', flags: MessageFlags.Ephemeral }).catch(() => {});
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = { handlePanel, handleSignature };
|