import { Channel, NewMessage } from './types.js'; import { formatLocalTime } from './timezone.js'; export function escapeXml(s: string): string { if (!s) return ''; return s .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } export function formatMessages( messages: NewMessage[], timezone: string, ): string { const lines = messages.map((m) => { const displayTime = formatLocalTime(m.timestamp, timezone); return `${escapeXml(m.content)}`; }); const header = `\n`; return `${header}\n${lines.join('\n')}\n`; } export function stripInternalTags(text: string): string { return text.replace(/[\s\S]*?<\/internal>/g, '').trim(); } /** * Patterns that match common API keys / tokens. * Matched strings are replaced with `[REDACTED]`. */ const SECRET_PATTERNS: RegExp[] = [ /sk-ant-[A-Za-z0-9_-]{20,}/g, // Anthropic /sk-[A-Za-z0-9_-]{20,}/g, // OpenAI /gsk_[A-Za-z0-9_-]{20,}/g, // Groq /xai-[A-Za-z0-9_-]{20,}/g, // xAI /ghp_[A-Za-z0-9_]{36,}/g, // GitHub PAT classic /github_pat_[A-Za-z0-9_]{20,}/g, // GitHub PAT fine-grained /glpat-[A-Za-z0-9_-]{20,}/g, // GitLab PAT /AKIA[A-Z0-9]{16}/g, // AWS Access Key /Bearer\s+eyJ[A-Za-z0-9_-]{40,}/g, // Bearer JWT ]; function redactSecrets(text: string): string { let result = text; for (const pattern of SECRET_PATTERNS) { pattern.lastIndex = 0; result = result.replace(pattern, '[REDACTED]'); } return result; } export function formatOutbound(rawText: string): string { const text = stripInternalTags(rawText); if (!text) return ''; return redactSecrets(text); } export function findChannel( channels: Channel[], jid: string, ): Channel | undefined { return channels.find((c) => c.ownsJid(jid)); }