Files
broccolini-bot/handlers/messages.js
indifferentketchup fc81ff32ca command 2
2026-03-28 17:53:12 -05:00

148 lines
4.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Discord messageCreate handler prefix commands and forwards staff replies to Gmail.
*/
const { ChannelType, AttachmentBuilder } = require('discord.js');
const { mongoose } = require('../db-connection');
const { CONFIG } = require('../config');
const { extractRawEmail } = require('../utils');
const { getGmailClient, sendGmailReply } = require('../services/gmail');
const { updateTicketActivity } = require('../services/tickets');
const { hasStaffRole } = require('./commands');
const { trackInteraction, trackError } = require('./analytics');
const Ticket = mongoose.model('Ticket');
/**
* `!ids` — list channel and role IDs (same rules as slash staff-gated commands).
* @returns {Promise<boolean>} true if the message was consumed
*/
async function tryHandleIdsCommand(m) {
if (m.content?.trim().toLowerCase() !== '!ids') return false;
if (!m.guild) {
await m.reply('This command can only be used in a server.').catch(() => {});
return true;
}
const staffConfigured =
CONFIG.ROLE_ID_TO_PING || (CONFIG.ADDITIONAL_STAFF_ROLES && CONFIG.ADDITIONAL_STAFF_ROLES.length > 0);
if (staffConfigured && !hasStaffRole(m.member)) {
const roleMention = CONFIG.ROLE_ID_TO_PING ? `<@&${CONFIG.ROLE_ID_TO_PING}>` : 'support';
await m.reply({
content: `This command is only available to the support team (${roleMention}).`,
allowedMentions: { users: [], roles: [], repliedUser: false }
}).catch(() => {});
return true;
}
trackInteraction('commands', 'ids-prefix', m.author.tag);
try {
await m.guild.channels.fetch().catch(() => {});
const channelTypeName = type =>
Object.entries(ChannelType).find(([, v]) => v === type)?.[0] ?? String(type);
const lines = ['**Channels:**'];
const channels = [...m.guild.channels.cache.values()].sort((a, b) => a.rawPosition - b.rawPosition);
for (const ch of channels) {
lines.push(`\`${ch.id}\`${ch.name} (${channelTypeName(ch.type)})`);
}
lines.push('');
lines.push('**Roles:**');
const roles = [...m.guild.roles.cache.values()].sort((a, b) => b.position - a.position);
for (const role of roles) {
lines.push(`\`${role.id}\`${role.name}`);
}
const text = lines.join('\n');
const baseReply = { allowedMentions: { users: [], roles: [], repliedUser: false } };
if (text.length <= 1900) {
await m.reply({ content: text, ...baseReply }).catch(() => {});
} else {
const buf = Buffer.from(text, 'utf8');
await m.reply({
content: 'List is long; sent as a file.',
files: [new AttachmentBuilder(buf, { name: `guild-ids-${m.guild.id}.txt` })],
...baseReply
}).catch(() => {});
}
} catch (err) {
trackError('ids-prefix-command', err, null);
await m.reply({
content: 'Failed to build ID list.',
allowedMentions: { repliedUser: false }
}).catch(() => {});
}
return true;
}
/**
* Handle a Discord message in a ticket channel → relay to Gmail (email tickets only).
*/
async function handleDiscordReply(m) {
if (m.author.bot || m.interaction) return;
if (await tryHandleIdsCommand(m)) return;
const ticket = await Ticket.findOne({ discordThreadId: m.channel.id }).lean();
if (!ticket) return;
const discordUser = m.member?.displayName || m.author.username;
if (ticket.gmailThreadId.startsWith('discord-')) {
return;
}
// Email tickets: send reply via Gmail.
try {
const gmail = getGmailClient();
const thread = await gmail.users.threads.get({
userId: 'me',
id: ticket.gmailThreadId
});
const last = [...thread.data.messages].reverse().find(msg => {
const from =
msg.payload.headers.find(h => h.name === 'From')?.value || '';
return !from.toLowerCase().includes(CONFIG.MY_EMAIL);
});
if (!last) return;
let recipient =
last.payload.headers.find(h => h.name === 'From')?.value || '';
const replyTo =
last.payload.headers.find(h => h.name === 'Reply-To')?.value;
if (replyTo) recipient = replyTo;
const subject =
last.payload.headers.find(h => h.name === 'Subject')?.value ||
'Support';
const msgId =
last.payload.headers.find(h => h.name === 'Message-ID')?.value;
const recipientEmail = extractRawEmail(recipient).toLowerCase();
if (!recipientEmail || recipientEmail === CONFIG.MY_EMAIL) {
console.warn('Bad recipient for reply:', recipientEmail);
return;
}
await sendGmailReply(
ticket.gmailThreadId,
m.content,
recipientEmail,
subject,
discordUser,
msgId
);
await updateTicketActivity(ticket.gmailThreadId);
} catch (e) {
console.error('REPLY ERROR:', e);
}
}
module.exports = { handleDiscordReply };