aboutsummaryrefslogtreecommitdiffstats
path: root/src/serialize.js
diff options
context:
space:
mode:
authorElvis Claros Castro <elvis@claros.ar>2026-09-26 19:26:33 -0300
committerElvis Claros Castro <elvis@claros.ar>2026-09-26 19:26:33 -0300
commitd80640d01eeb84907e36ff4af33add9081eda17c (patch)
tree27698b34e06e28d111c2196700015d9c82f78274 /src/serialize.js
downloadwa-cli-main.tar.gz
wa-cli-main.zip
wa-cli: WhatsApp from the terminal, with a neomutt Maildir bridgeHEADmain
Diffstat (limited to 'src/serialize.js')
-rw-r--r--src/serialize.js158
1 files changed, 158 insertions, 0 deletions
diff --git a/src/serialize.js b/src/serialize.js
new file mode 100644
index 0000000..5f22674
--- /dev/null
+++ b/src/serialize.js
@@ -0,0 +1,158 @@
+// 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),
+ };
+}