148 lines
4.7 KiB
JavaScript
148 lines
4.7 KiB
JavaScript
/**
|
||
* 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 };
|