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
This commit is contained in:
Cayo P. R. Oliveira 2025-04-03 23:09:49 -03:00 committed by gabrieljablonski
parent 02732b3db6
commit e0708ca6f8
2 changed files with 75 additions and 6 deletions

View File

@ -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 `<user>_<agent>:<device>@<server>`
# 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)

View File

@ -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