feat(bridge): WhatsApp forwarded message detection, startup guard, and contact handling

Three improvements to the WhatsApp Baileys bridge:

1. Forwarded message detection: Extracts contextInfo.isForwarded from
   all message types (text, image, video, audio, document) and passes
   it as isForwarded in the InboundMessage. Allows the agent to
   distinguish forwarded content from direct messages — useful for
   different handling (e.g., transcribe-only vs execute as instruction).

2. Startup timestamp guard: Records the timestamp when the bridge
   starts and drops any messages with messageTimestamp older than
   startup time. Prevents replaying message history on reconnect,
   which caused duplicate processing and stale command execution.

3. Contact message handling: Adds support for contactMessage and
   contactsArrayMessage types, extracting displayName and vcard
   data instead of silently dropping shared contacts.

Changes:
- Add isForwarded field to InboundMessage interface
- Add startupTimestamp guard in message processing loop
- Add contactMessage/contactsArrayMessage extraction
- Extract contextInfo.isForwarded from all media message types
This commit is contained in:
comadreja 2026-06-06 12:25:24 -05:00
parent ab9f49970d
commit eec59c05de

View File

@ -29,6 +29,7 @@ export interface InboundMessage {
content: string; content: string;
timestamp: number; timestamp: number;
isGroup: boolean; isGroup: boolean;
isForwarded?: boolean;
wasMentioned?: boolean; wasMentioned?: boolean;
media?: string[]; media?: string[];
} }
@ -81,6 +82,10 @@ export class WhatsAppClient {
console.log(`Using Baileys version: ${version.join('.')}`); console.log(`Using Baileys version: ${version.join('.')}`);
// Record startup time — messages older than this will be ignored
// to avoid replaying history on reconnect
const startupTimestamp = Math.floor(Date.now() / 1000);
// Create socket following OpenClaw's pattern // Create socket following OpenClaw's pattern
this.sock = makeWASocket({ this.sock = makeWASocket({
auth: { auth: {
@ -145,6 +150,10 @@ export class WhatsAppClient {
if (msg.key.fromMe) continue; if (msg.key.fromMe) continue;
if (msg.key.remoteJid === 'status@broadcast') continue; if (msg.key.remoteJid === 'status@broadcast') continue;
// Drop messages older than startup time (avoid replaying history on reconnect)
const msgTimestamp = msg.messageTimestamp as number;
if (msgTimestamp && msgTimestamp < startupTimestamp) continue;
const unwrapped = baileysExtractMessageContent(msg.message); const unwrapped = baileysExtractMessageContent(msg.message);
if (!unwrapped) continue; if (!unwrapped) continue;
@ -169,8 +178,30 @@ export class WhatsAppClient {
fallbackContent = '[Voice Message]'; fallbackContent = '[Voice Message]';
const path = await this.downloadMedia(msg, unwrapped.audioMessage.mimetype ?? undefined); const path = await this.downloadMedia(msg, unwrapped.audioMessage.mimetype ?? undefined);
if (path) mediaPaths.push(path); if (path) mediaPaths.push(path);
} else if (unwrapped.contactMessage) {
// Single shared contact
const displayName = unwrapped.contactMessage.displayName || '';
const vcard = unwrapped.contactMessage.vcard || '';
fallbackContent = `[Contact: ${displayName}]\n${vcard}`;
} else if (unwrapped.contactsArrayMessage) {
// Multiple shared contacts
const vcards = unwrapped.contactsArrayMessage.contacts || [];
const parts = vcards.map((c: any) => {
const name = c.displayName || '';
const vc = c.vcard || '';
return `[Contact: ${name}]\n${vc}`;
});
fallbackContent = parts.join('\n\n');
} }
// Detect forwarded messages
const contextInfo = msg.message?.extendedTextMessage?.contextInfo
|| msg.message?.imageMessage?.contextInfo
|| msg.message?.videoMessage?.contextInfo
|| msg.message?.audioMessage?.contextInfo
|| msg.message?.documentMessage?.contextInfo;
const isForwarded = contextInfo?.isForwarded || false;
const finalContent = content || (mediaPaths.length === 0 ? fallbackContent : '') || ''; const finalContent = content || (mediaPaths.length === 0 ? fallbackContent : '') || '';
if (!finalContent && mediaPaths.length === 0) continue; if (!finalContent && mediaPaths.length === 0) continue;
@ -184,6 +215,7 @@ export class WhatsAppClient {
content: finalContent, content: finalContent,
timestamp: msg.messageTimestamp as number, timestamp: msg.messageTimestamp as number,
isGroup, isGroup,
...(isForwarded ? { isForwarded } : {}),
...(isGroup ? { wasMentioned } : {}), ...(isGroup ? { wasMentioned } : {}),
...(mediaPaths.length > 0 ? { media: mediaPaths } : {}), ...(mediaPaths.length > 0 ? { media: mediaPaths } : {}),
}); });