From e0708ca6f8f9a98fa25ea486d21e50cd557d5e1e Mon Sep 17 00:00:00 2001 From: "Cayo P. R. Oliveira" Date: Thu, 3 Apr 2025 23:09:49 -0300 Subject: [PATCH] fix: handle new contact without push name (#13) * fix: resolve bug for contact name when user stats a new conversation with a new contact * chore: update specs to cover the bug resolution * chore: correct variable name * chore: simplify update contact name handling * chore: ensure pushName is always a string and leave a comment to keep an eye if always pushName will be present * fix: update contact name access * chore: identify and permit only direct users jid types (#14) * fix: add jid_type method to classify message sender types and prevent processing for non-user jids * chore: update jid parsing to correctly extract phone number and improve message type detection * chore: add spec to ensure no conversation is created for non-user messages * chore: update jid shape comment * chore: refactor jid_type method * chore: update message_type logic for classify text messages when is a extendedTextMessage * refactor: update spec that no conversation is created for non-user messages * fix: use update! method for contact name update * chore: add note on distinguishing broadcast and status JIDs in Baileys * chore: add note on Baileys internal function for JID classification --- .../incoming_message_baileys_service.rb | 41 +++++++++++++++++-- .../incoming_message_baileys_service_spec.rb | 40 +++++++++++++++++- 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/app/services/whatsapp/incoming_message_baileys_service.rb b/app/services/whatsapp/incoming_message_baileys_service.rb index fe4ba67b1..34a0fc296 100644 --- a/app/services/whatsapp/incoming_message_baileys_service.rb +++ b/app/services/whatsapp/incoming_message_baileys_service.rb @@ -46,6 +46,7 @@ class Whatsapp::IncomingMessageBaileysService < Whatsapp::IncomingMessageBaseSer end def handle_message + return if jid_type != 'user' return if find_message_by_source_id(message_id) || message_under_process? cache_message_source_id_in_redis @@ -62,16 +63,23 @@ class Whatsapp::IncomingMessageBaileysService < Whatsapp::IncomingMessageBaseSer end def set_contact - phone_number_from_jid = @raw_message[:key][:remoteJid].split('@').first.split(':').first - + # NOTE: jid shape is `_:@` + # https://github.com/WhiskeySockets/Baileys/blob/v6.7.16/src/WABinary/jid-utils.ts#L19 + phone_number_from_jid = @raw_message[:key][:remoteJid].split('@').first.split(':').first.split('_').first + phone_number_formatted = "+#{phone_number_from_jid}" + # NOTE: We're assuming `pushName` will always be present when `fromMe: false`. + # This assumption might be incorrect, so let's keep an eye out for contacts being created with empty name. + push_name = @raw_message[:key][:fromMe] ? phone_number_formatted : @raw_message[:pushName].to_s contact_inbox = ::ContactInboxWithContactBuilder.new( source_id: phone_number_from_jid, inbox: inbox, - contact_attributes: { name: @raw_message[:pushName], phone_number: "+#{phone_number_from_jid}" } + contact_attributes: { name: push_name, phone_number: phone_number_formatted } ).perform @contact_inbox = contact_inbox @contact = contact_inbox.contact + + @contact.update!(name: push_name) if @contact.name == phone_number_formatted && !@raw_message[:key][:fromMe] end def handle_create_message @@ -83,9 +91,34 @@ class Whatsapp::IncomingMessageBaileysService < Whatsapp::IncomingMessageBaseSer end end + def jid_type # rubocop:disable Metrics/CyclomaticComplexity + jid = @raw_message[:key][:remoteJid] + server = jid.split('@').last + + # NOTE: Based on Baileys internal functions + # https://github.com/WhiskeySockets/Baileys/blob/v6.7.16/src/WABinary/jid-utils.ts#L48-L58 + case server + when 's.whatsapp.net', 'c.us' + 'user' + when 'g.us' + 'group' + when 'lid' + 'lid' + when 'broadcast' + jid.start_with?('status@') ? 'status' : 'broadcast' + when 'newsletter' + 'newsletter' + when 'call' + 'call' + else + 'unknown' + end + end + def message_type # rubocop:disable Metrics/CyclomaticComplexity,Metrics/PerceivedComplexity msg = @raw_message[:message] - return 'text' if msg.key?(:conversation) || msg.dig(:extendedTextMessage, :text) + + return 'text' if msg.key?(:conversation) || msg.dig(:extendedTextMessage, :text).present? return 'contacts' if msg.key?(:contactMessage) return 'image' if msg.key?(:imageMessage) return 'audio' if msg.key?(:audioMessage) diff --git a/spec/services/whatsapp/incoming_message_baileys_service_spec.rb b/spec/services/whatsapp/incoming_message_baileys_service_spec.rb index 88411fff0..4bc7ab5bd 100644 --- a/spec/services/whatsapp/incoming_message_baileys_service_spec.rb +++ b/spec/services/whatsapp/incoming_message_baileys_service_spec.rb @@ -173,6 +173,32 @@ describe Whatsapp::IncomingMessageBaileysService do end end + context 'when message is not from a user' do + let(:raw_message) do + { + key: { id: 'msg_123', remoteJid: 'status@broadcast', participant: '5511912345678@s.whatsapp.net', fromMe: false }, + message: { extendedTextMessage: { text: 'message' } }, + pushName: 'John Doe' + } + end + let(:params) do + { + webhookVerifyToken: webhook_verify_token, + event: 'messages.upsert', + data: { + type: 'notify', + messages: [raw_message] + } + } + end + + it 'does not create a conversation' do + described_class.new(inbox: inbox, params: params).perform + + expect(inbox.conversations).to be_empty + end + end + context 'when message type is text' do let(:raw_message) do { @@ -206,8 +232,9 @@ describe Whatsapp::IncomingMessageBaileysService do end it 'creates an outgoing message' do + number = '5511912345678' raw_message_outgoing = raw_message.merge( - key: { id: 'msg_123', remoteJid: '5511912345678@s.whatsapp.net', fromMe: true } + key: { id: 'msg_123', remoteJid: "#{number}@s.whatsapp.net", fromMe: true } ) params_outgoing = params.merge(data: { type: 'notify', messages: [raw_message_outgoing] }) create(:account_user, account: inbox.account) @@ -219,6 +246,15 @@ describe Whatsapp::IncomingMessageBaileysService do expect(message).to be_present expect(message.content).to eq('Hello from Baileys') expect(message.message_type).to eq('outgoing') + expect(conversation.contact.name).to eq("+#{number}") + end + + it 'updates the contact name if the current name is a phone number when a incoming message is received' do + create(:contact, account: inbox.account, name: '+5511912345678', phone_number: '+5511912345678') + described_class.new(inbox: inbox, params: params).perform + + conversation = inbox.conversations.last + expect(conversation.contact.name).to eq('John Doe') end it 'creates a message on an existing conversation' do @@ -265,7 +301,7 @@ describe Whatsapp::IncomingMessageBaileysService do let(:raw_message) do { key: { id: 'msg_123', remoteJid: '5511912345678@s.whatsapp.net', fromMe: false }, - message: { 'extendedTextMessage': { text: 'Hello from Baileys' } }, + message: { extendedTextMessage: { text: 'Hello from Baileys' } }, pushName: 'John Doe' } end