Files
broccolini-bot/handlers/commands/forward.js
indifferentketchup 6bae3e79b1 Gmail folder auto-advance + /forward command
- Reply-cycle auto-advance: staff reply files the thread to "Awaiting Reply",
  a customer response files it to "Needs Response" (new GMAIL_LABEL_AWAITING_REPLY
  / GMAIL_LABEL_NEEDS_RESPONSE labels + autoAdvanceFolder, which only moves
  threads still in the auto-cycle and leaves hand-filed folders alone)
- /forward: forward a ticket's email to another address (handlers/commands/forward.js
  + forward composition in services/gmail.js)
- Tests for the auto-advance cycle; label fixtures updated for the new labels
2026-06-05 02:46:50 +00:00

60 lines
2.3 KiB
JavaScript

/**
* /forward — forward this ticket's email thread to a third-party address.
*
* Builds a fresh outbound email to the target only; the original customer is
* never looped in (see services/gmail.js forwardThread).
*/
const { MessageFlags } = require('discord.js');
const { findTicketForChannel } = require('../sharedHelpers');
const { forwardThread } = require('../../services/gmail');
const { logError, logTicketEvent } = require('../../services/debugLog');
const plural = (n, word) => `${n} ${word}${n === 1 ? '' : 's'}`;
async function handleForward(interaction) {
const ticket = await findTicketForChannel(interaction);
if (!ticket) return;
// Discord-origin tickets have no Gmail thread to forward.
if (ticket.gmailThreadId.startsWith('discord-')) {
return interaction.reply({
content: "This ticket has no email thread, so there's nothing to forward.",
flags: MessageFlags.Ephemeral
});
}
const target = interaction.options.getString('email');
const note = interaction.options.getString('note') || '';
// Defer: fetching the thread + downloading attachments can exceed the 3s window.
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
try {
const { messageCount, attachmentCount, skipped } = await forwardThread(
ticket.gmailThreadId, target, note
);
logTicketEvent('Email thread forwarded', [
{ name: 'To', value: target },
{ name: 'Messages', value: String(messageCount) },
{ name: 'Forwarded by', value: interaction.user.tag }
], interaction).catch(() => {});
const skippedNote = skipped ? ` (${plural(skipped, 'attachment')} skipped — over the size limit)` : '';
return interaction.editReply({
content: `Forwarded ${plural(messageCount, 'message')} (${plural(attachmentCount, 'attachment')}) to **${target}**.${skippedNote}`
});
} catch (err) {
if (err.code === 'EBADRECIPIENT') {
return interaction.editReply({ content: "That doesn't look like a valid email address." });
}
if (err.code === 'EEMPTY') {
return interaction.editReply({ content: 'This thread has no messages to forward.' });
}
logError('handleForward', err, interaction).catch(() => {});
return interaction.editReply({ content: `Failed to forward: ${err.message}` });
}
}
module.exports = { handleForward };