Each customId now maps to a named handler in one of two tables:
FREE_BUTTON_HANDLERS (open-ticket panel, tag-delete cancel — no ticket
lookup) or TICKET_BUTTON_HANDLERS (anything fired inside a ticket channel
— the dispatcher does the lookup once before delegating). The dynamic
`confirm_delete_tag::*` id is matched by prefix.
To find a button's logic, search handle<Name>Button or handleTagDelete*.
Other cleanups in the same pass:
- Move findTicketForChannel and runDeferred from handlers/commands.js to
the new handlers/sharedHelpers.js so both files share one source of
truth. runDeferred now also calls logError(verb, ...) — was logged ad
hoc in buttons.js, missing in commands.js. Strictly additive.
- Hoist three inline `require('../services/...')` calls (staffThread,
pinMessage, debugLog) to top imports.
- Collapse escalate_to_tier2 and escalate_to_tier3 into one
handleEscalateButton(interaction, ticket) that derives the tier from
customId. Same for confirm_close / confirm_close_with_email /
confirm_close_no_email — one handleConfirmCloseRequest deriving
sendEmail from customId.
- Decompose the 156-line handleConfirmClose into runFinalClose +
buildTranscriptText + formatDateForTranscript + renderTranscriptHeader
+ dmTranscriptToCreator + postCloseLogEntry. Each piece is testable in
isolation.
- Decompose handleClaim into applyClaim + applyUnclaim.
- Extract buildOpenTicketModal() and postTicketWelcomeEmbeds() so the
ticket-creation modal flow is readable top-to-bottom.
No behavior change. handleButton + handleTicketModal exports preserved;
24/24 modules load clean (sharedHelpers.js is the new one).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
54 lines
1.9 KiB
JavaScript
54 lines
1.9 KiB
JavaScript
/**
|
|
* Shared helpers for slash-command and button handlers.
|
|
*
|
|
* Both handlers/commands.js and handlers/buttons.js use these to avoid
|
|
* repeating the lookup-and-defer-and-try-catch pattern across 30+ branches.
|
|
*/
|
|
const { mongoose } = require('../db-connection');
|
|
const { logError } = require('../services/debugLog');
|
|
|
|
const Ticket = mongoose.model('Ticket');
|
|
|
|
/**
|
|
* Look up the ticket linked to this channel; reply with `missingMessage`
|
|
* (default: "This channel is not linked to a ticket.") and return null if
|
|
* the channel is not a ticket. Returns the ticket on success.
|
|
*
|
|
* @param {import('discord.js').Interaction} interaction
|
|
* @param {string} [missingMessage]
|
|
*/
|
|
async function findTicketForChannel(interaction, missingMessage = 'This channel is not linked to a ticket.') {
|
|
const ticket = await Ticket.findOne({ discordThreadId: interaction.channel.id }).lean();
|
|
if (!ticket) {
|
|
await interaction.reply({ content: missingMessage, ephemeral: true });
|
|
return null;
|
|
}
|
|
return ticket;
|
|
}
|
|
|
|
/**
|
|
* Defer + run + log + reply on error. `verb` is the user-facing verb
|
|
* (e.g. "escalate"); error messages render as "Failed to <verb> this ticket."
|
|
* Errors are logged to console + DEBUGGING_CHANNEL_ID via logError(verb, ...).
|
|
*
|
|
* @param {import('discord.js').Interaction} interaction
|
|
* @param {string} verb
|
|
* @param {() => Promise<void>} fn
|
|
* @param {{ ephemeral?: boolean }} [opts]
|
|
*/
|
|
async function runDeferred(interaction, verb, fn, { ephemeral = false } = {}) {
|
|
try {
|
|
await interaction.deferReply({ ephemeral });
|
|
await fn();
|
|
} catch (err) {
|
|
console.error(`${verb} error:`, err);
|
|
logError(verb, err, interaction).catch(() => {});
|
|
const msg = `Failed to ${verb} this ticket.`;
|
|
await interaction.editReply({ content: msg }).catch(() =>
|
|
interaction.followUp({ content: msg, ephemeral: true }).catch(() => {})
|
|
);
|
|
}
|
|
}
|
|
|
|
module.exports = { findTicketForChannel, runDeferred };
|