/** * 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} 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 };