fix(whatsapp): add error handling to messages.upsert handler (#695)

Wrap the inner message processing loop in a try-catch to prevent a
single malformed or edge-case message from crashing the entire handler.
Logs the error with remoteJid for debugging while continuing to process
remaining messages in the batch.

Co-authored-by: Ethan M <ethan@nanoclaw.local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
glifocat
2026-03-04 14:02:21 +01:00
committed by GitHub
parent 58dec06e4c
commit 5e3d8b6c2c

View File

@@ -173,67 +173,74 @@ export class WhatsAppChannel implements Channel {
this.sock.ev.on('messages.upsert', async ({ messages }) => { this.sock.ev.on('messages.upsert', async ({ messages }) => {
for (const msg of messages) { for (const msg of messages) {
if (!msg.message) continue; try {
// Unwrap container types (viewOnceMessageV2, ephemeralMessage, if (!msg.message) continue;
// editedMessage, etc.) so that conversation, extendedTextMessage, // Unwrap container types (viewOnceMessageV2, ephemeralMessage,
// imageMessage, etc. are accessible at the top level. // editedMessage, etc.) so that conversation, extendedTextMessage,
const normalized = normalizeMessageContent(msg.message); // imageMessage, etc. are accessible at the top level.
if (!normalized) continue; const normalized = normalizeMessageContent(msg.message);
const rawJid = msg.key.remoteJid; if (!normalized) continue;
if (!rawJid || rawJid === 'status@broadcast') continue; const rawJid = msg.key.remoteJid;
if (!rawJid || rawJid === 'status@broadcast') continue;
// Translate LID JID to phone JID if applicable // Translate LID JID to phone JID if applicable
const chatJid = await this.translateJid(rawJid); const chatJid = await this.translateJid(rawJid);
const timestamp = new Date( const timestamp = new Date(
Number(msg.messageTimestamp) * 1000, Number(msg.messageTimestamp) * 1000,
).toISOString(); ).toISOString();
// Always notify about chat metadata for group discovery // Always notify about chat metadata for group discovery
const isGroup = chatJid.endsWith('@g.us'); const isGroup = chatJid.endsWith('@g.us');
this.opts.onChatMetadata( this.opts.onChatMetadata(
chatJid, chatJid,
timestamp,
undefined,
'whatsapp',
isGroup,
);
// Only deliver full message for registered groups
const groups = this.opts.registeredGroups();
if (groups[chatJid]) {
const content =
normalized.conversation ||
normalized.extendedTextMessage?.text ||
normalized.imageMessage?.caption ||
normalized.videoMessage?.caption ||
'';
// Skip protocol messages with no text content (encryption keys, read receipts, etc.)
if (!content) continue;
const sender = msg.key.participant || msg.key.remoteJid || '';
const senderName = msg.pushName || sender.split('@')[0];
const fromMe = msg.key.fromMe || false;
// Detect bot messages: with own number, fromMe is reliable
// since only the bot sends from that number.
// With shared number, bot messages carry the assistant name prefix
// (even in DMs/self-chat) so we check for that.
const isBotMessage = ASSISTANT_HAS_OWN_NUMBER
? fromMe
: content.startsWith(`${ASSISTANT_NAME}:`);
this.opts.onMessage(chatJid, {
id: msg.key.id || '',
chat_jid: chatJid,
sender,
sender_name: senderName,
content,
timestamp, timestamp,
is_from_me: fromMe, undefined,
is_bot_message: isBotMessage, 'whatsapp',
}); isGroup,
);
// Only deliver full message for registered groups
const groups = this.opts.registeredGroups();
if (groups[chatJid]) {
const content =
normalized.conversation ||
normalized.extendedTextMessage?.text ||
normalized.imageMessage?.caption ||
normalized.videoMessage?.caption ||
'';
// Skip protocol messages with no text content (encryption keys, read receipts, etc.)
if (!content) continue;
const sender = msg.key.participant || msg.key.remoteJid || '';
const senderName = msg.pushName || sender.split('@')[0];
const fromMe = msg.key.fromMe || false;
// Detect bot messages: with own number, fromMe is reliable
// since only the bot sends from that number.
// With shared number, bot messages carry the assistant name prefix
// (even in DMs/self-chat) so we check for that.
const isBotMessage = ASSISTANT_HAS_OWN_NUMBER
? fromMe
: content.startsWith(`${ASSISTANT_NAME}:`);
this.opts.onMessage(chatJid, {
id: msg.key.id || '',
chat_jid: chatJid,
sender,
sender_name: senderName,
content,
timestamp,
is_from_me: fromMe,
is_bot_message: isBotMessage,
});
}
} catch (err) {
logger.error(
{ err, remoteJid: msg.key?.remoteJid },
'Error processing incoming message',
);
} }
} }
}); });