diff --git a/.claude/skills/add-whatsapp/add/src/channels/whatsapp.ts b/.claude/skills/add-whatsapp/add/src/channels/whatsapp.ts index 64fcf57..f7f27cb 100644 --- a/.claude/skills/add-whatsapp/add/src/channels/whatsapp.ts +++ b/.claude/skills/add-whatsapp/add/src/channels/whatsapp.ts @@ -173,67 +173,74 @@ export class WhatsAppChannel implements Channel { this.sock.ev.on('messages.upsert', async ({ messages }) => { for (const msg of messages) { - if (!msg.message) continue; - // Unwrap container types (viewOnceMessageV2, ephemeralMessage, - // editedMessage, etc.) so that conversation, extendedTextMessage, - // imageMessage, etc. are accessible at the top level. - const normalized = normalizeMessageContent(msg.message); - if (!normalized) continue; - const rawJid = msg.key.remoteJid; - if (!rawJid || rawJid === 'status@broadcast') continue; + try { + if (!msg.message) continue; + // Unwrap container types (viewOnceMessageV2, ephemeralMessage, + // editedMessage, etc.) so that conversation, extendedTextMessage, + // imageMessage, etc. are accessible at the top level. + const normalized = normalizeMessageContent(msg.message); + if (!normalized) continue; + const rawJid = msg.key.remoteJid; + if (!rawJid || rawJid === 'status@broadcast') continue; - // Translate LID JID to phone JID if applicable - const chatJid = await this.translateJid(rawJid); + // Translate LID JID to phone JID if applicable + const chatJid = await this.translateJid(rawJid); - const timestamp = new Date( - Number(msg.messageTimestamp) * 1000, - ).toISOString(); + const timestamp = new Date( + Number(msg.messageTimestamp) * 1000, + ).toISOString(); - // Always notify about chat metadata for group discovery - const isGroup = chatJid.endsWith('@g.us'); - this.opts.onChatMetadata( - 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, + // Always notify about chat metadata for group discovery + const isGroup = chatJid.endsWith('@g.us'); + this.opts.onChatMetadata( + chatJid, timestamp, - is_from_me: fromMe, - is_bot_message: isBotMessage, - }); + 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, + is_from_me: fromMe, + is_bot_message: isBotMessage, + }); + } + } catch (err) { + logger.error( + { err, remoteJid: msg.key?.remoteJid }, + 'Error processing incoming message', + ); } } });