manual commit 2026-04-10T19:27:53Z

This commit is contained in:
2026-04-10 19:27:53 +00:00
parent a1cd67fd73
commit 1017ef6ae7
6 changed files with 277 additions and 8 deletions

View File

@@ -157,14 +157,63 @@ async function sendTicketClosedEmail(ticket, discordDisplayName) {
}
}
const { mongoose } = require('../db-connection');
const StaffSignature = mongoose.model('StaffSignature');
/**
* Get formatted signature blocks (text and HTML) for a staff member
* @param {string} userId - Discord user ID
* @returns {Promise<{text: string, html: string}>} Signature blocks
*/
async function getStaffSignatureBlocks(userId) {
try {
const signature = await StaffSignature.findOne({ userId }).lean();
if (!signature) {
return {
text: '',
html: ''
};
}
const valediction = signature.valediction || '';
const displayName = signature.displayName || '';
const tagline = signature.tagline || '';
const textSignature = [
valediction,
displayName,
tagline
].filter(Boolean).join('\n');
const htmlSignature = [
valediction ? `<p style="margin: 0;">${escapeHtml(valediction)}</p>` : '',
displayName ? `<p style="margin: 0; font-weight: bold;">${escapeHtml(displayName)}</p>` : '',
tagline ? `<div style="color: #666; font-size: 12px;">${escapeHtml(tagline)}</div>` : ''
].filter(Boolean).join('\n');
return {
text: textSignature,
html: htmlSignature
};
} catch (err) {
console.error('Error fetching staff signature:', err);
return {
text: '',
html: ''
};
}
}
/**
* Send a notification email in the ticket thread (e.g. escalation, high-priority).
* @param {Object} ticket - Ticket with gmailThreadId, senderEmail, subject
* @param {string} subjectLine - Subject line (e.g. "Ticket escalated" or "Priority updated")
* @param {string} messageBody - Plain or HTML message body
* @param {string} [fromLabel] - Label for "From" (e.g. "Support on Discord")
* @param {string} [userId] - Discord user ID for signature (optional)
*/
async function sendTicketNotificationEmail(ticket, subjectLine, messageBody, fromLabel) {
async function sendTicketNotificationEmail(ticket, subjectLine, messageBody, fromLabel, userId = null) {
try {
const gmail = getGmailClient();
const recipientEmail = extractRawEmail(ticket.senderEmail || '').toLowerCase();
@@ -191,6 +240,13 @@ async function sendTicketNotificationEmail(ticket, subjectLine, messageBody, fro
const label = escapeHtml(fromLabel || CONFIG.SUPPORT_NAME || 'Support');
const safeBody = escapeHtml(messageBody || '').replace(/\n/g, '<br>');
const safeLogoUrl = escapeHtml(CONFIG.LOGO_URL || '');
// Get staff signature if userId provided
let signatureBlocks = { text: '', html: '' };
if (userId) {
signatureBlocks = await getStaffSignatureBlocks(userId);
}
const safeSignature = escapeHtml(CONFIG.SIGNATURE || '');
const htmlBody = `
<div style="font-family: sans-serif; font-size: 14px; color: #333;">
@@ -203,8 +259,9 @@ async function sendTicketNotificationEmail(ticket, subjectLine, messageBody, fro
${safeLogoUrl ? `<img src="${safeLogoUrl}" width="65">` : ''}
</td>
<td style="border-left: 1px solid #ddd; padding-left: 12px;">
<p style="margin: 0; font-weight: bold;">${label}</p>
<div style="color: #666; font-size: 12px;">${safeSignature}</div>
${signatureBlocks.html || `<p style="margin: 0; font-weight: bold;">${label}</p>`}
${signatureBlocks.html ? '<hr style="border:none; border-top:1px solid #ddd; margin:10px 0;">' : ''}
${signatureBlocks.html ? signatureBlocks.html : `<div style="color: #666; font-size: 12px;">${safeSignature}</div>`}
</td>
</tr>
</table>
@@ -237,6 +294,84 @@ async function sendTicketNotificationEmail(ticket, subjectLine, messageBody, fro
}
}
/**
* Send a Gmail reply to a ticket
* @param {string} threadId - Gmail thread ID
* @param {string} replyText - Reply text
* @param {string} recipientEmail - Recipient email
* @param {string} subject - Subject line
* @param {string} discordUser - Discord user name
* @param {string} messageId - Message ID (optional)
* @param {string} userId - Discord user ID for signature (optional)
*/
async function sendGmailReply(
threadId,
replyText,
recipientEmail,
subject,
discordUser,
messageId,
userId = null
) {
const gmail = getGmailClient();
const utf8Subject = `=?utf-8?B?${Buffer.from(
`Re: ${subject}`
).toString('base64')}?=`;
const safeUser = escapeHtml(discordUser);
const safeReply = escapeHtml(replyText).replace(/\n/g, '<br>');
const safeLogoUrl = escapeHtml(CONFIG.LOGO_URL || '');
const safeSignature = escapeHtml(CONFIG.SIGNATURE || '');
// Get staff signature if userId provided
let signatureBlocks = { text: '', html: '' };
if (userId) {
signatureBlocks = await getStaffSignatureBlocks(userId);
}
const htmlBody = `
<div style="font-family: sans-serif; font-size: 14px; color: #333;">
<p><strong>From:</strong> ${safeUser} on Discord</p>
<p>${safeReply}</p>
<hr style="border:none; border-top:1px solid #ddd; margin:20px 0;">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td style="padding-right: 12px;">
${safeLogoUrl ? `<img src="${safeLogoUrl}" width="65">` : ''}
</td>
<td style="border-left: 1px solid #ddd; padding-left: 12px;">
${signatureBlocks.html || `<p style="margin: 0; font-weight: bold;">${safeUser}</p>`}
${signatureBlocks.html ? '<hr style="border:none; border-top:1px solid #ddd; margin:10px 0;">' : ''}
${signatureBlocks.html ? signatureBlocks.html : `<div style="color: #666; font-size: 12px;">${safeSignature}</div>`}
</td>
</tr>
</table>
</div>`;
const headers = [
`From: ${CONFIG.MY_EMAIL}`,
`To: ${recipientEmail}`,
`Subject: ${utf8Subject}`,
messageId ? `In-Reply-To: ${messageId}` : '',
messageId ? `References: ${messageId}` : '',
'MIME-Version: 1.0',
'Content-Type: text/html; charset="UTF-8"',
'',
htmlBody
].filter(Boolean);
const raw = Buffer.from(headers.join('\r\n'))
.toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
await gmail.users.messages.send({
userId: 'me',
requestBody: { raw, threadId }
});
}
module.exports = {
getGmailClient,
sendGmailReply,