100 lines
3.5 KiB
JavaScript
100 lines
3.5 KiB
JavaScript
/**
|
|
* Syncs Zammad agent replies to Discord (for Discord tickets) and Gmail (for email tickets).
|
|
* Polls Zammad ticket articles and pushes new customer-visible Agent replies to the right channel.
|
|
*/
|
|
const { mongoose } = require('../db-connection');
|
|
const { getZammadTicketArticles } = require('./zammad');
|
|
const { sendGmailReply } = require('./gmail');
|
|
const { htmlToTextWithBlocks } = require('../utils');
|
|
|
|
const Ticket = mongoose.model('Ticket');
|
|
|
|
function bodyToText(body, contentType) {
|
|
if (!body) return '';
|
|
const isHtml = (contentType || '').toLowerCase().includes('html');
|
|
return isHtml ? htmlToTextWithBlocks(body).trim() : String(body).trim();
|
|
}
|
|
|
|
/**
|
|
* Run once: find open tickets with Zammad ID, fetch new agent (customer-visible) articles,
|
|
* post to Discord or send via Gmail, then update lastSyncedZammadArticleId.
|
|
* @param {import('discord.js').Client} client - Discord client (for posting to ticket channels)
|
|
*/
|
|
async function syncZammadReplies(client) {
|
|
if (!client?.channels) return;
|
|
|
|
const tickets = await Ticket.find({
|
|
zammadTicketId: { $exists: true, $ne: null },
|
|
status: 'open'
|
|
})
|
|
.select('gmailThreadId discordThreadId zammadTicketId lastSyncedZammadArticleId senderEmail subject')
|
|
.lean();
|
|
|
|
for (const ticket of tickets) {
|
|
try {
|
|
const articles = await getZammadTicketArticles(ticket.zammadTicketId);
|
|
// Only agent replies that are customer-visible (not internal notes)
|
|
const agentReplies = articles.filter(
|
|
(a) => a.sender === 'Agent' && a.internal === false && a.body
|
|
);
|
|
if (agentReplies.length === 0) continue;
|
|
|
|
const lastSynced = ticket.lastSyncedZammadArticleId || 0;
|
|
const newReplies = agentReplies.filter((a) => a.id > lastSynced);
|
|
const maxId = Math.max(lastSynced, ...agentReplies.map((a) => a.id));
|
|
|
|
// First run: just advance cursor so we don't resend existing articles
|
|
if (newReplies.length === 0) {
|
|
if (maxId > lastSynced) {
|
|
await Ticket.updateOne(
|
|
{ gmailThreadId: ticket.gmailThreadId },
|
|
{ $set: { lastSyncedZammadArticleId: maxId } }
|
|
);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
const isDiscordTicket = ticket.gmailThreadId.startsWith('discord-');
|
|
|
|
for (const article of newReplies) {
|
|
const text = bodyToText(article.body, article.content_type);
|
|
if (!text) continue;
|
|
|
|
const fromLabel = article.created_by || 'Support';
|
|
|
|
if (isDiscordTicket && ticket.discordThreadId) {
|
|
const channel = await client.channels.fetch(ticket.discordThreadId).catch(() => null);
|
|
if (channel) {
|
|
await channel.send(`**${fromLabel}** (via Zammad):\n${text}`).catch((err) => {
|
|
console.error('Zammad sync: Discord send failed:', err.message);
|
|
});
|
|
}
|
|
} else {
|
|
// Email ticket: send reply via Gmail
|
|
try {
|
|
await sendGmailReply(
|
|
ticket.gmailThreadId,
|
|
text,
|
|
ticket.senderEmail,
|
|
ticket.subject || 'Support',
|
|
fromLabel,
|
|
null
|
|
);
|
|
} catch (err) {
|
|
console.error('Zammad sync: Gmail send failed:', err.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
await Ticket.updateOne(
|
|
{ gmailThreadId: ticket.gmailThreadId },
|
|
{ $set: { lastSyncedZammadArticleId: maxId } }
|
|
);
|
|
} catch (err) {
|
|
console.error('Zammad sync error for ticket', ticket.gmailThreadId, err.message);
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = { syncZammadReplies };
|