feat(whatsapp): get contact profile picture when using Baileys service (#98)
* feat(whatsapp): add profile picture fetching for Baileys provider - Add `get_profile_pic` method to `WhatsappBaileysService` for fetching contact avatars - Include `avatar_url` in contact attributes during contact creation - Update `MessagesUpsert` handler to fetch and set profile pictures - Add proper error handling and graceful degradation when avatar fetching fails * test(whatsapp): add test coverage for Baileys profile picture fetching - Add test suite for `get_profile_pic` method - Include test scenarios for successful responses, missing avatars, and error handling - Add profile picture HTTP stubbing and avatar job expectations to message processing tests * feat(whatsapp): optimize Baileys avatar fetching and update avatar for existing inboxes Previously, the profile picture was fetched on every incoming message and avatar updates were only attempted for new inboxes. This change optimizes the process by: - Only fetching the WhatsApp profile picture if the contact does not already have an avatar. - Attempting to update the avatar for both new and existing inboxes. This logic is based on Telegram's version (app/services/telegram/incoming_message_service.rb). * refactor(whatsapp): remove unnecessary safe navigation when accessing contact This reverts an unnecessary change introduced in b918a92. * refactor(whatsapp): manually rollback change introduced in b918a92 * refactor(whatsapp): rename endpoint to profile-picture-url * refactor(whatsapp): simplify profile picture response handling Replaced manual JSON parsing with HTTParty's parsed_response and removed redundant JSON parsing tests. * test(whatsapp): separate avatar processing test from message creation test * test(whatsapp): update profile picture test to match actual API behavior Change test to expect a 404 response when no profile picture exists, instead of a 200 response with partial data. * refactor: move new functions to helper module * test: more specs --------- Co-authored-by: gabrieljablonski <contact@gabrieljablonski.com>
This commit is contained in:
parent
29a2474d05
commit
d2a0d53fab
@ -143,6 +143,23 @@ module Whatsapp::BaileysHandlers::Helpers # rubocop:disable Metrics/ModuleLength
|
||||
(message_type == 'reaction' && message_content.blank?)
|
||||
end
|
||||
|
||||
def fetch_profile_picture_url(phone_number)
|
||||
jid = "#{phone_number}@s.whatsapp.net"
|
||||
response = inbox.channel.provider_service.get_profile_pic(jid)
|
||||
response&.dig('data', 'profilePictureUrl')
|
||||
rescue StandardError => e
|
||||
Rails.logger.error "Failed to fetch profile picture for #{phone_number}: #{e.message}"
|
||||
nil
|
||||
end
|
||||
|
||||
def try_update_contact_avatar
|
||||
# TODO: Current logic will never update the contact avatar if their profile picture changes on WhatsApp.
|
||||
return if @contact.avatar.attached?
|
||||
|
||||
profile_pic_url = fetch_profile_picture_url(phone_number_from_jid)
|
||||
::Avatar::AvatarFromUrlJob.perform_later(@contact, profile_pic_url) if profile_pic_url
|
||||
end
|
||||
|
||||
def message_under_process?
|
||||
key = format(Redis::RedisKeys::MESSAGE_SOURCE_KEY, id: raw_message_id)
|
||||
Redis::Alfred.get(key)
|
||||
|
||||
@ -55,6 +55,7 @@ module Whatsapp::BaileysHandlers::MessagesUpsert
|
||||
@contact = contact_inbox.contact
|
||||
|
||||
@contact.update!(name: push_name) if @contact.name == source_id
|
||||
try_update_contact_avatar
|
||||
end
|
||||
|
||||
def handle_create_message
|
||||
|
||||
@ -189,6 +189,19 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
||||
true
|
||||
end
|
||||
|
||||
def get_profile_pic(jid)
|
||||
response = HTTParty.get(
|
||||
"#{provider_url}/connections/#{whatsapp_channel.phone_number}/profile-picture-url",
|
||||
headers: api_headers,
|
||||
query: { jid: jid },
|
||||
format: :json
|
||||
)
|
||||
|
||||
return nil unless process_response(response)
|
||||
|
||||
response.parsed_response
|
||||
end
|
||||
|
||||
def on_whatsapp(phone_number)
|
||||
@phone_number = phone_number
|
||||
|
||||
|
||||
@ -146,6 +146,9 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
end
|
||||
|
||||
context 'when processing messages.upsert event' do
|
||||
# NOTE: We always try fetching profile picture for new contacts on messages.upsert, so we must stub this request.
|
||||
before { stub_profile_pic_fetch }
|
||||
|
||||
let(:timestamp) { Time.current.to_i }
|
||||
let(:raw_message) do
|
||||
{
|
||||
@ -176,6 +179,46 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
expect(message.content_attributes[:external_created_at]).to eq(timestamp)
|
||||
end
|
||||
|
||||
context 'when updating contact avatar' do
|
||||
it 'enqueues avatar job when profile picture is available' do
|
||||
stub_profile_pic_fetch('https://example.com/avatar.jpg')
|
||||
|
||||
described_class.new(inbox: inbox, params: params).perform
|
||||
|
||||
conversation = inbox.conversations.last
|
||||
contact = conversation.contact
|
||||
|
||||
expect(Avatar::AvatarFromUrlJob).to have_been_enqueued.with(contact, 'https://example.com/avatar.jpg')
|
||||
end
|
||||
|
||||
it 'does not enqueue avatar job when contact already has avatar attached' do
|
||||
stub_profile_pic_fetch('https://example.com/avatar.jpg')
|
||||
contact = create(:contact, account: inbox.account, name: 'John Doe', phone_number: '+5511912345678')
|
||||
contact.avatar.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'avatar.png', content_type: 'image/png')
|
||||
|
||||
described_class.new(inbox: inbox, params: params).perform
|
||||
|
||||
expect(Avatar::AvatarFromUrlJob).not_to have_been_enqueued
|
||||
end
|
||||
|
||||
it 'does not enqueue avatar job when profile picture is not available' do
|
||||
described_class.new(inbox: inbox, params: params).perform
|
||||
|
||||
expect(Avatar::AvatarFromUrlJob).not_to have_been_enqueued
|
||||
end
|
||||
|
||||
it 'logs error and does not enqueue avatar job when profile picture request fails' do
|
||||
allow(Rails.logger).to receive(:error)
|
||||
stub_request(:get, /profile-picture-url/)
|
||||
.to_raise(StandardError.new('Profile picture request failed'))
|
||||
|
||||
described_class.new(inbox: inbox, params: params).perform
|
||||
|
||||
expect(Avatar::AvatarFromUrlJob).not_to have_been_enqueued
|
||||
expect(Rails.logger).to have_received(:error).with('Failed to fetch profile picture for 5511912345678: Profile picture request failed')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when message type is unsupported' do
|
||||
it 'creates message with is_unsupported' do
|
||||
raw_message[:message] = { unsupported: 'message' }
|
||||
@ -829,4 +872,12 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
.with('https://baileys.api/media/msg_123', headers: inbox.channel.api_headers)
|
||||
.and_return(StringIO.new('Media data'))
|
||||
end
|
||||
|
||||
def stub_profile_pic_fetch(url = nil)
|
||||
stub_request(:get, /profile-picture-url/)
|
||||
.to_return(
|
||||
status: 200,
|
||||
body: { data: { profilePictureUrl: url } }.to_json
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@ -850,6 +850,51 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
end
|
||||
end
|
||||
|
||||
describe '#get_profile_pic' do
|
||||
let(:test_jid) { '551187654321@s.whatsapp.net' }
|
||||
|
||||
context 'when response is successful' do
|
||||
it 'returns the profile picture URL data' do
|
||||
stub_request(:get, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/profile-picture-url")
|
||||
.with(
|
||||
headers: stub_headers(whatsapp_channel),
|
||||
query: { jid: test_jid }
|
||||
)
|
||||
.to_return(
|
||||
status: 200,
|
||||
body: { data: { profilePictureUrl: 'https://pps.whatsapp.net/v/t61.24694-24/avatar.jpg', jid: test_jid } }.to_json
|
||||
)
|
||||
|
||||
result = service.get_profile_pic(test_jid)
|
||||
|
||||
expect(result).to eq({
|
||||
'data' => {
|
||||
'profilePictureUrl' => 'https://pps.whatsapp.net/v/t61.24694-24/avatar.jpg',
|
||||
'jid' => test_jid
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
context 'when response fails' do
|
||||
it 'returns nil when profile picture not found (404)' do
|
||||
stub_request(:get, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/profile-picture-url")
|
||||
.with(
|
||||
headers: stub_headers(whatsapp_channel),
|
||||
query: { jid: test_jid }
|
||||
)
|
||||
.to_return(
|
||||
status: 404,
|
||||
body: { error: 'Profile picture not found' }.to_json
|
||||
)
|
||||
|
||||
result = service.get_profile_pic(test_jid)
|
||||
|
||||
expect(result).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def stub_headers(channel)
|
||||
{
|
||||
'Content-Type' => 'application/json',
|
||||
|
||||
Loading…
Reference in New Issue
Block a user