// whatsapp-web.js objects -> plain, stable JSON. // // This shape is the "contract" consumed by the formats (table, eml, mbox) and by any // external integration (hooks, the Maildir bridge). Changing it breaks consumers: adding // fields is fine, renaming is not. const iso = (ts) => (ts ? new Date(ts * 1000).toISOString() : null); // names: (id) => display name | undefined export function message(m, { names = () => undefined, me } = {}) { const d = m._data || {}; const chatId = typeof m.id.remote === 'string' ? m.id.remote : m.id.remote?._serialized; const sender = m.fromMe ? (me?.id ?? m.from) : (m.author || m.from); const out = { id: m.id._serialized ?? m.id.$1, stanzaId: m.id.id, chatId, chatName: names(chatId) ?? null, fromMe: m.fromMe, from: m.from, to: m.to, author: m.author || null, sender, senderName: m.fromMe ? (me?.name ?? null) : (names(sender) || d.notifyName || sender?.split('@')[0] || null), timestamp: m.timestamp, date: iso(m.timestamp), type: m.type, body: m.body ?? '', hasMedia: m.hasMedia, media: m.hasMedia ? { mimetype: d.mimetype ?? null, filename: d.filename ?? null, size: d.size ?? null } : null, hasQuotedMsg: m.hasQuotedMsg, quotedStanzaId: d.quotedStanzaID ?? null, quotedParticipant: serializedId(d.quotedParticipant), isForwarded: Boolean(m.isForwarded), isStarred: Boolean(m.isStarred), isStatus: Boolean(m.isStatus), ack: m.ack ?? null, mentionedIds: (m.mentionedIds || []).map(serializedId), links: (m.links || []).map((l) => l.link), }; if (m.location) { out.location = { latitude: Number(m.location.latitude), longitude: Number(m.location.longitude), name: m.location.name ?? m.location.description ?? null, address: m.location.address ?? null, url: m.location.url ?? null, }; } if (m.vCards?.length) out.vCards = m.vCards; if (m.type === 'poll_creation') { out.poll = { name: m.pollName, options: (m.pollOptions || []).map((o) => o.name ?? o) }; } return out; } function serializedId(v) { if (v == null) return null; return typeof v === 'string' ? v : (v._serialized ?? null); } export function chat(c, opts = {}) { const out = { id: c.id._serialized, name: c.name || c.id.user, isGroup: Boolean(c.isGroup), unreadCount: c.unreadCount ?? 0, timestamp: c.timestamp ?? null, date: iso(c.timestamp), archived: Boolean(c.archived), pinned: Boolean(c.pinned), isMuted: Boolean(c.isMuted), muteExpiration: c.muteExpiration ?? 0, isReadOnly: Boolean(c.isReadOnly), lastMessage: c.lastMessage ? message(c.lastMessage, opts) : null, }; if (c.isGroup && c.groupMetadata) { out.participantCount = c.groupMetadata.participants?.length ?? null; } return out; } export function group(c, { names = () => undefined } = {}) { const md = c.groupMetadata || {}; return { id: c.id._serialized, name: c.name, description: md.desc ?? null, owner: serializedId(md.owner), createdAt: md.creation ? iso(md.creation) : null, announce: Boolean(md.announce), restrict: Boolean(md.restrict), participants: (md.participants || []).map((p) => { const id = serializedId(p.id); return { id, name: names(id) ?? null, isAdmin: Boolean(p.isAdmin), isSuperAdmin: Boolean(p.isSuperAdmin) }; }), }; } export function contact(c) { return { id: c.id._serialized, number: c.number ?? c.id.user, name: c.name ?? null, pushname: c.pushname ?? null, shortName: c.shortName ?? null, displayName: displayName(c), isMyContact: Boolean(c.isMyContact), isBusiness: Boolean(c.isBusiness), isGroup: Boolean(c.isGroup), isBlocked: Boolean(c.isBlocked), }; } export function displayName(c) { return c.name || c.pushname || c.shortName || c.verifiedName || c.number || c.id?.user || null; } export function reaction(r) { return { id: serializedId(r.id), messageId: serializedId(r.msgId), chatId: r.msgId?.remote ? serializedId(r.msgId.remote) : null, senderId: r.senderId, reaction: r.reaction, timestamp: r.timestamp, date: iso(r.timestamp), }; } export function groupNotification(n) { return { id: serializedId(n.id), chatId: n.chatId, type: n.type, author: n.author, recipientIds: n.recipientIds, body: n.body, timestamp: n.timestamp, date: iso(n.timestamp), }; } export function call(c) { return { id: c.id, from: c.from ?? null, fromMe: Boolean(c.fromMe), isVideo: Boolean(c.isVideo), isGroup: Boolean(c.isGroup), timestamp: c.timestamp, date: iso(c.timestamp), }; }