// "text" format: a human-readable conversation. Returns null if the data is not messages // (and the caller falls back to table). const isMessage = (m) => m && typeof m === 'object' && 'stanzaId' in m && 'chatId' in m; function when(ts) { const d = new Date(ts * 1000); const p = (n) => String(n).padStart(2, '0'); return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`; } export function describeContent(m) { const parts = []; if (m.hasMedia) { const name = m.media?.filename ? ` ${m.media.filename}` : ''; parts.push(`[${m.type}${name}]`); } else if (m.type === 'location' && m.location) { parts.push(`[location ${m.location.latitude},${m.location.longitude}${m.location.name ? ` ${m.location.name}` : ''}]`); } else if (m.type === 'vcard' || m.type === 'multi_vcard') { parts.push('[contact]'); } else if (m.type === 'revoked') { parts.push('[deleted message]'); } else if (m.poll) { parts.push(`[poll] ${m.poll.name}: ${m.poll.options.join(' / ')}`); return parts.join(' '); } else if (!['chat', 'text'].includes(m.type) && !m.body) { parts.push(`[${m.type}]`); } if (m.body) parts.push(m.body); return parts.join(' '); } export function messageLine(m, { chatName } = {}) { const who = m.fromMe ? 'me' : (m.senderName || m.sender); const chat = chatName ? `${chatName} | ` : ''; const flags = `${m.isForwarded ? '↪ ' : ''}${m.hasQuotedMsg ? '↩ ' : ''}`; const body = describeContent(m).replace(/\n/g, '\n '); return `[${when(m.timestamp)}] ${chat}${who}: ${flags}${body}`; } export function toText(data, opts = {}) { // +watch event: {event, data} if (data && data.event && data.data !== undefined) { const inner = data.data.message ?? data.data; if (isMessage(inner)) return messageLine(inner, { chatName: inner.chatId }); return null; } const list = Array.isArray(data) ? data : [data]; if (!list.length || !list.every(isMessage)) return null; return list.map((m) => messageLine(m, opts)).join('\n'); }