Files
broccolini-bot/services/debugLog.js
indifferentketchup 2ccdbf72aa Email ticketing fixes, comms polish, and .env cleanup
Inbound:
- Gmail poll query is:unread in:inbox (was category:primary, which matched
  nothing on a no-tabs Workspace inbox)

Outbound email:
- Closed/escalation auto-emails editable via TICKET_CLOSE_MESSAGE and new
  TICKET_ESCALATION_EMAIL_MESSAGE; drop the staff signature from closing emails
- Replies quote the customer's latest message (gmail_quote markup so clients
  collapse it), embed custom emoji inline via CID attachment, and strip Discord
  role mentions
- Tagline spacing fix in the company signature

Discord side:
- Suppress all mentions in log + transcript posts (no more pinging on close)
- Drop the staff-role ping from new-ticket and follow-up notifications
- Ticket channels inherit category permissions instead of setting per-channel
  overwrites (removes the Manage Roles requirement)

Gmail folders:
- Folder/label routing (gmailLabels.js) with /folder; close files to Complete

Config:
- Remove ~56 stale .env keys for long-removed features; refresh stale copy

Docs:
- Design specs for folder routing, email-flow toggle, and per-staff metrics
2026-06-04 22:05:20 +00:00

101 lines
3.2 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.
/**
* Structured logging service posts embeds to dedicated Discord channels.
* Call setClient(client) from the main bot on ready so logs can be posted.
*/
const { EmbedBuilder } = require('discord.js');
const { CONFIG } = require('../config');
let client = null;
function setClient(c) {
client = c;
}
// --- PII redaction ---
// Email addresses (loose regex — covers most RFC 5321 local parts that show up
// in support traffic) and Discord snowflakes (1820 digit numeric IDs) get
// redacted before stack/message text reaches the debug channel. Both can land
// in error stacks via senderEmail interpolation, channel IDs in error
// messages, etc. — redacting at the boundary keeps the debug channel useful
// for triage without leaking customer addresses or staff member IDs.
const EMAIL_REDACT_RE = /[\w.+-]+@[\w.-]+\.\w+/g;
const SNOWFLAKE_REDACT_RE = /\b\d{18,20}\b/g;
function redactPII(s) {
if (s == null) return '';
return String(s)
.replace(EMAIL_REDACT_RE, '[EMAIL_REDACTED]')
.replace(SNOWFLAKE_REDACT_RE, '[ID_REDACTED]');
}
// --- Helpers ---
async function sendToChannel(channelId, embed, overrideClient) {
const c = overrideClient || client;
if (!c || !channelId) return;
try {
const channel = await c.channels.fetch(channelId);
if (channel) await channel.send({ embeds: [embed], allowedMentions: { parse: [] } });
} catch (_) {
// ignore send failures
}
}
// --- logError (backwards-compatible) ---
async function logError(context, error, interaction = null, overrideClient = null) {
const c = overrideClient || client;
if (!c || !CONFIG.DEBUGGING_CHANNEL_ID) return;
try {
const channel = await c.channels.fetch(CONFIG.DEBUGGING_CHANNEL_ID);
const userLine = interaction?.user?.tag
? `User: ${interaction.user.tag}\n`
: '';
const commandLine = (interaction?.commandName || interaction?.customId)
? `Command/Button: ${interaction.commandName || interaction.customId}\n`
: '';
const message = redactPII(error.message || String(error));
const stack = redactPII(error.stack || error.message || String(error)).slice(0, 1500);
await channel.send({
content: `\`[${context}]\` ${message}\n${userLine}${commandLine}\n\`\`\`${stack}\`\`\``,
allowedMentions: { parse: [] }
});
} catch (_) {
// ignore send failures
}
}
// --- logWarn ---
async function logWarn(context, message, overrideClient = null) {
const embed = new EmbedBuilder()
.setTitle(`Warning: ${context}`)
.setDescription(redactPII(String(message)).slice(0, 4000))
.setColor(0xFFFF00)
.setTimestamp();
await sendToChannel(CONFIG.DEBUGGING_CHANNEL_ID, embed, overrideClient);
}
// --- logTicketEvent ---
async function logTicketEvent(action, fields, interaction = null) {
const embed = new EmbedBuilder()
.setTitle(action)
.setColor(CONFIG.EMBED_COLOR_INFO || 0x1e2124)
.addFields(fields.map(f => ({ name: f.name, value: String(f.value).slice(0, 1024), inline: f.inline ?? true })))
.setTimestamp();
if (interaction?.user?.tag) {
embed.setFooter({ text: interaction.user.tag });
}
await sendToChannel(CONFIG.LOGGING_CHANNEL_ID, embed, interaction?.client);
}
module.exports = {
setClient,
logError,
logWarn,
logTicketEvent
};