feat: process Baileys attachments, fix contact name creation, and handle unsupported messages (#26)

* fix: return not_found status for missing messages in WhatsApp webhook

* feat: enhance message handling to support image attachments

* chore: Handle incoming whatsapp baileys service attachments and implement specs for images and videos

* fix: add file_content_type method to incoming baileys messages

* chore: specs for stickers, audio and files

* fix: handle media attachment errors

* chore: convert file_content_type to string when attaching media

* fix: add handling for attachment not found error in incoming message service

* chore: refactor file_content_type method and simplify filename method

* chore: update media attachment tests

* feat: baileys unsupported message alert (#27)

* feat: create alert message for unsupported message types

* chore: fail message when try send not supported type in baileys

* chore: add tests for unsupported message handling in Baileys service

* chore: correct spelling

* chore: NIT in spec name

* chore: remove unnecessary message reload in unsupported message type test

* feat: baileys support to send attachments (#28)

* feat: enhance message sending logic with support for attachments and interactive messages

* fix: update message format to use messageContent for text messages

* feat: attachment message sending

* fix: use strict encoding for attachment file download

* chore: streamline message sending logic and remove unused error classes

* chore: remove type from message sending logic

* chore: update message sending specs

* chore: raise MessageNotSentError instead of updating message status to failed

* chore: change baileys contact name preferences (#30)

* refactor: improve contact name handling and extraction from JID

* test: enhance message handling to update contact names based on pushName and verifiedBizName

* chore: update contact name condition to match phone number from JID

* chore: correct method name typo

* chore: correct variable names for phone number consistency in tests

* chore: update message payload structure and improve error handling in send_message

* test: re-add testing for error handling

* feat: enhance contact name handling and add self-message detection

* fix: ensure presence check for verified business name in contact name retrieval

* test: improve specs

---------

Co-authored-by: gabrieljablonski <contact@gabrieljablonski.com>

---------

Co-authored-by: gabrieljablonski <contact@gabrieljablonski.com>
Co-authored-by: Gabriel Jablonski <gabriel@fazer.ai>

---------

Co-authored-by: gabrieljablonski <contact@gabrieljablonski.com>
Co-authored-by: Gabriel Jablonski <gabriel@fazer.ai>

* fix: try to disconnect baileys before destroy inbox (#29)

* fix: ensure proper disconnection of Baileys provider on channel destruction

* test: add callback tests for disconnecting channel provider in Whatsapp spec

* refactor: simplify condition for disconnecting Baileys provider on channel destruction

* refactor: enhance disconnect_channel_provider specs for Baileys provider

* test: verify channel destruction does not call disconnect_channel_provider

---------

Co-authored-by: gabrieljablonski <contact@gabrieljablonski.com>

---------

Co-authored-by: gabrieljablonski <contact@gabrieljablonski.com>
Co-authored-by: Gabriel Jablonski <gabriel@fazer.ai>
This commit is contained in:
Cayo P. R. Oliveira 2025-04-25 16:39:17 -03:00 committed by GitHub
parent d0eb84dbed
commit 8fc14186e0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 595 additions and 94 deletions

View File

@ -26,7 +26,9 @@ class Webhooks::WhatsappController < ActionController::API
rescue Whatsapp::IncomingMessageBaileysService::InvalidWebhookVerifyToken rescue Whatsapp::IncomingMessageBaileysService::InvalidWebhookVerifyToken
head :unauthorized head :unauthorized
rescue Whatsapp::IncomingMessageBaileysService::MessageNotFoundError rescue Whatsapp::IncomingMessageBaileysService::MessageNotFoundError
head :bad_request head :not_found
rescue Whatsapp::IncomingMessageBaileysService::AttachmentNotFoundError
head :unprocessable_entity
end end
def valid_token?(token) def valid_token?(token)

View File

@ -37,6 +37,8 @@ class Channel::Whatsapp < ApplicationRecord
after_create :sync_templates after_create :sync_templates
before_destroy :disconnect_channel_provider, if: -> { provider == 'baileys' }
def name def name
'Whatsapp' 'Whatsapp'
end end

View File

@ -1,6 +1,7 @@
class Whatsapp::IncomingMessageBaileysService < Whatsapp::IncomingMessageBaseService # rubocop:disable Metrics/ClassLength class Whatsapp::IncomingMessageBaileysService < Whatsapp::IncomingMessageBaseService # rubocop:disable Metrics/ClassLength
class InvalidWebhookVerifyToken < StandardError; end class InvalidWebhookVerifyToken < StandardError; end
class MessageNotFoundError < StandardError; end class MessageNotFoundError < StandardError; end
class AttachmentNotFoundError < StandardError; end
def perform def perform
raise InvalidWebhookVerifyToken if processed_params[:webhookVerifyToken] != inbox.channel.provider_config['webhook_verify_token'] raise InvalidWebhookVerifyToken if processed_params[:webhookVerifyToken] != inbox.channel.provider_config['webhook_verify_token']
@ -64,12 +65,7 @@ class Whatsapp::IncomingMessageBaileysService < Whatsapp::IncomingMessageBaseSer
end end
def set_contact def set_contact
# NOTE: jid shape is `<user>_<agent>:<device>@<server>` push_name = contact_name
# 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
# 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_from_jid : @raw_message[:pushName].to_s
contact_inbox = ::ContactInboxWithContactBuilder.new( contact_inbox = ::ContactInboxWithContactBuilder.new(
source_id: phone_number_from_jid, source_id: phone_number_from_jid,
inbox: inbox, inbox: inbox,
@ -79,14 +75,36 @@ class Whatsapp::IncomingMessageBaileysService < Whatsapp::IncomingMessageBaseSer
@contact_inbox = contact_inbox @contact_inbox = contact_inbox
@contact = contact_inbox.contact @contact = contact_inbox.contact
@contact.update!(name: push_name) if @contact.name == phone_number_from_jid && !@raw_message[:key][:fromMe] @contact.update!(name: push_name) if @contact.name == phone_number_from_jid
end
def phone_number_from_jid
# 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
end
def contact_name
# NOTE: `verifiedBizName` is only available for business accounts and has a higher priority than `pushName`.
name = @raw_message[:verifiedBizName].presence || @raw_message[:pushName]
return name if self_message? || incoming?
phone_number_from_jid
end
def self_message?
phone_number_from_jid == inbox.channel.phone_number.delete('+')
end end
def handle_create_message def handle_create_message
case message_type case message_type
when 'text' when 'text'
create_text_message create_message
when 'image', 'file', 'video', 'audio', 'sticker'
create_message
attach_media
else else
create_unsupported_message
Rails.logger.warn "Baileys unsupported message type: #{message_type}" Rails.logger.warn "Baileys unsupported message type: #{message_type}"
end end
end end
@ -126,7 +144,7 @@ class Whatsapp::IncomingMessageBaileysService < Whatsapp::IncomingMessageBaseSer
return 'video_note' if msg.key?(:ptvMessage) return 'video_note' if msg.key?(:ptvMessage)
return 'location' if msg.key?(:locationMessage) return 'location' if msg.key?(:locationMessage)
return 'live_location' if msg.key?(:liveLocationMessage) return 'live_location' if msg.key?(:liveLocationMessage)
return 'document' if msg.key?(:documentMessage) return 'file' if msg.key?(:documentMessage)
return 'poll' if msg.key?(:pollCreationMessageV3) return 'poll' if msg.key?(:pollCreationMessageV3)
return 'event' if msg.key?(:eventMessage) return 'event' if msg.key?(:eventMessage)
return 'sticker' if msg.key?(:stickerMessage) return 'sticker' if msg.key?(:stickerMessage)
@ -134,15 +152,13 @@ class Whatsapp::IncomingMessageBaileysService < Whatsapp::IncomingMessageBaseSer
'unsupported' 'unsupported'
end end
def create_text_message def create_message
is_outgoing = @raw_message[:key][:fromMe] sender = incoming? ? @contact : @inbox.account.account_users.first.user
sender = is_outgoing ? @inbox.account.account_users.first.user : @contact sender_type = incoming? ? 'Contact' : 'User'
sender_type = is_outgoing ? 'User' : 'Contact' message_type = incoming? ? :incoming : :outgoing
message_type = is_outgoing ? :outgoing : :incoming
content = @raw_message.dig(:message, :conversation) || @raw_message.dig(:message, :extendedTextMessage, :text)
@message = @conversation.messages.create!( @message = @conversation.messages.create!(
content: content, content: message_content,
account_id: @inbox.account_id, account_id: @inbox.account_id,
inbox_id: @inbox.id, inbox_id: @inbox.id,
source_id: message_id, source_id: message_id,
@ -153,6 +169,71 @@ class Whatsapp::IncomingMessageBaileysService < Whatsapp::IncomingMessageBaseSer
) )
end end
def incoming?
!@raw_message[:key][:fromMe]
end
def create_unsupported_message
create_message
@message.update!(
content: I18n.t('errors.messages.unsupported'),
message_type: 'template',
status: 'failed'
)
end
def attach_media
media = processed_params.dig(:extra, :media)
return if media.blank?
attachment_payload = media[message_id]
if attachment_payload.blank?
Rails.logger.error "Attachment not found for message: #{message_id}"
raise AttachmentNotFoundError
end
begin
decoded_data = Base64.decode64(attachment_payload)
io = StringIO.new(decoded_data)
@message.attachments.new(
account_id: @message.account_id,
file_type: file_content_type.to_s,
file: { io: io, filename: filename }
)
@message.save!
rescue StandardError => e
Rails.logger.error "Failed to attach media for message #{message_id} (#{e.message}) payload: #{attachment_payload}"
end
end
def file_content_type
return :image if message_type.in?(%w[image sticker])
return :video if message_type.in?(%w[video video_note])
return :audio if message_type == 'audio'
:file
end
def filename
filename = @raw_message.dig(:message, :documentMessage, :fileName)
return filename if filename.present?
"#{file_content_type}_#{@message[:id]}_#{Time.current.strftime('%Y%m%d')}"
end
def message_content
case message_type
when 'text'
@raw_message.dig(:message, :conversation) || @raw_message.dig(:message, :extendedTextMessage, :text)
when 'image'
@raw_message.dig(:message, :imageMessage, :caption)
when 'video'
@raw_message.dig(:message, :videoMessage, :caption)
end
end
def message_id def message_id
@raw_message[:key][:id] @raw_message[:key][:id]
end end

View File

@ -1,5 +1,6 @@
class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseService class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseService
class MessageContentTypeNotSupported < StandardError; end class MessageContentTypeNotSupported < StandardError; end
class MessageNotSentError < StandardError; end
DEFAULT_CLIENT_NAME = ENV.fetch('BAILEYS_PROVIDER_DEFAULT_CLIENT_NAME', nil) DEFAULT_CLIENT_NAME = ENV.fetch('BAILEYS_PROVIDER_DEFAULT_CLIENT_NAME', nil)
DEFAULT_URL = ENV.fetch('BAILEYS_PROVIDER_DEFAULT_URL', nil) DEFAULT_URL = ENV.fetch('BAILEYS_PROVIDER_DEFAULT_URL', nil)
@ -29,21 +30,15 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
end end
def send_message(phone_number, message) def send_message(phone_number, message)
raise MessageContentTypeNotSupported unless message.content_type == 'text' @message = message
@phone_number = phone_number
response = HTTParty.post( if message.attachments.present?
"#{provider_url}/connections/#{whatsapp_channel.phone_number}/send-message", send_attachment_message
headers: api_headers, elsif message.content.present?
body: { send_text_message
type: 'text', else
recipient: phone_number, message.update!(content: I18n.t('errors.messages.send.unsupported'), status: 'failed')
message: message.content end
}.to_json
)
return unless process_response(response)
response.parsed_response.dig('data', 'key', 'id')
end end
def send_template(phone_number, template_info); end def send_template(phone_number, template_info); end
@ -75,6 +70,61 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
whatsapp_channel.provider_config['api_key'].presence || DEFAULT_API_KEY whatsapp_channel.provider_config['api_key'].presence || DEFAULT_API_KEY
end end
def send_attachment_message
@attachment = @message.attachments.first
response = HTTParty.post(
"#{provider_url}/connections/#{whatsapp_channel.phone_number}/send-message",
headers: api_headers,
body: {
recipient: @phone_number,
messageContent: message_content
}.to_json
)
return response.parsed_response.dig('data', 'key', 'id') if process_response(response)
raise MessageNotSentError
end
def message_content
buffer = Base64.strict_encode64(@attachment.file.download)
content = {
fileName: @attachment.file.filename,
caption: @message.content
}
case @attachment.file_type
when 'image'
content[:image] = buffer
when 'audio'
content[:audio] = buffer
when 'file'
content[:document] = buffer
when 'sticker'
content[:sticker] = buffer
when 'video'
content[:video] = buffer
end
content
end
def send_text_message
response = HTTParty.post(
"#{provider_url}/connections/#{whatsapp_channel.phone_number}/send-message",
headers: api_headers,
body: {
recipient: @phone_number,
messageContent: { text: @message.content }
}.to_json
)
return response.parsed_response.dig('data', 'key', 'id') if process_response(response)
raise MessageNotSentError
end
def process_response(response) def process_response(response)
Rails.logger.error response.body unless response.success? Rails.logger.error response.body unless response.success?
response.success? response.success?

View File

@ -37,6 +37,10 @@ en:
inbox_deletetion_response: Your inbox deletion request will be processed in some time. inbox_deletetion_response: Your inbox deletion request will be processed in some time.
errors: errors:
messages:
unsupported: ❌ _This message can't be displayed here. Please open WhatsApp on your phone to view the message._
send:
unsupported: ❌ _This message cannot be sent. Please open WhatsApp on your phone to properly send the message._
validations: validations:
presence: must not be blank presence: must not be blank
webhook: webhook:

View File

@ -23,6 +23,10 @@ pt_BR:
reset_password_failure: Uh ho! Não conseguimos encontrar nenhum usuário com o e-mail especificado. reset_password_failure: Uh ho! Não conseguimos encontrar nenhum usuário com o e-mail especificado.
inbox_deletetion_response: Seu pedido de exclusão da caixa de entrada será processado dentro de algum tempo. inbox_deletetion_response: Seu pedido de exclusão da caixa de entrada será processado dentro de algum tempo.
errors: errors:
messages:
unsupported: ❌ _Esta mensagem não pode ser exibida aqui. Por favor, abra o WhatsApp em seu telefone para visualizar a mensagem._
send:
unsupported: ❌ _Esta mensagem não pode ser enviada. Por favor, abra o WhatsApp em seu telefone para enviar corretamente a mensagem._
validations: validations:
presence: não pode ficar em branco presence: não pode ficar em branco
webhook: webhook:
@ -206,7 +210,7 @@ pt_BR:
meeting_name: '%{agent_name} começou a reunião' meeting_name: '%{agent_name} começou a reunião'
slack: slack:
name: 'Slack' name: 'Slack'
description: "Integre Chatwoot com Slack para manter sua equipe em sincronia. Essa integração permite que você receba notificações de novas conversas e as responda diretamente na interface do Slack." description: 'Integre Chatwoot com Slack para manter sua equipe em sincronia. Essa integração permite que você receba notificações de novas conversas e as responda diretamente na interface do Slack.'
webhooks: webhooks:
name: 'Webhooks' name: 'Webhooks'
description: 'Eventos webhook fornecem atualizações sobre atividades em tempo real na sua conta Chatwoot. Você pode se inscrever em seus eventos preferidos, e o Chatwoot enviará as chamadas HTTP com as atualizações.' description: 'Eventos webhook fornecem atualizações sobre atividades em tempo real na sua conta Chatwoot. Você pode se inscrever em seus eventos preferidos, e o Chatwoot enviará as chamadas HTTP com as atualizações.'
@ -215,7 +219,7 @@ pt_BR:
description: 'Construa chatbots com o Dialogflow e integre-os facilmente na sua caixa de entrada. Esses bots podem lidar com as consultas iniciais antes de transferi-las para um agente de atendimento ao cliente.' description: 'Construa chatbots com o Dialogflow e integre-os facilmente na sua caixa de entrada. Esses bots podem lidar com as consultas iniciais antes de transferi-las para um agente de atendimento ao cliente.'
google_translate: google_translate:
name: 'Tradutor do Google' name: 'Tradutor do Google'
description: "Integre o Google Tradutor para ajudar os agentes a traduzir facilmente as mensagens dos clientes. Esta integração detecta automaticamente o idioma e o converte para o idioma preferido do agente ou do administrador." description: 'Integre o Google Tradutor para ajudar os agentes a traduzir facilmente as mensagens dos clientes. Esta integração detecta automaticamente o idioma e o converte para o idioma preferido do agente ou do administrador.'
openai: openai:
name: 'OpenAI' name: 'OpenAI'
description: 'Aproveite o poder dos grandes modelos de linguagem do OpenAI com recursos como sugestões de resposta, resumo, reformulação de mensagens, verificação ortográfica e classificação de rótulos.' description: 'Aproveite o poder dos grandes modelos de linguagem do OpenAI com recursos como sugestões de resposta, resumo, reformulação de mensagens, verificação ortográfica e classificação de rótulos.'

View File

@ -125,6 +125,8 @@ RSpec.describe Attachment do
it 'preserves meta data with file attachments' do it 'preserves meta data with file attachments' do
expect(image_attachment.meta['description']).to eq('Test image') expect(image_attachment.meta['description']).to eq('Test image')
expect(image_attachment.file.filename.to_s).to eq('avatar.png')
expect(image_attachment.file.content_type).to eq('image/png')
end end
end end
end end

View File

@ -61,4 +61,41 @@ RSpec.describe Channel::Whatsapp do
expect(channel.provider_config['webhook_verify_token']).to eq '123' expect(channel.provider_config['webhook_verify_token']).to eq '123'
end end
end end
describe 'callbacks' do
describe '#disconnect_channel_provider' do
context 'when provider is baileys' do
let(:channel) { create(:channel_whatsapp, provider: 'baileys', validate_provider_config: false, sync_templates: false) }
let(:disconnect_url) { "#{channel.provider_config['provider_url']}/connections/#{channel.phone_number}" }
it 'destroys the channel on successful disconnect' do
stub_request(:delete, disconnect_url).to_return(status: 200)
channel.destroy!
expect(channel).to be_destroyed
end
it 'destroys the channel on failure to disconnect' do
stub_request(:delete, disconnect_url).to_return(status: 404, body: 'error message')
channel.destroy!
expect(channel).to be_destroyed
end
end
context 'when provider is not baileys' do
let(:channel) { create(:channel_whatsapp, provider: 'whatsapp_cloud', validate_provider_config: false, sync_templates: false) }
it 'does not invoke callback' do
expect(channel).not_to receive(:disconnect_channel_provider)
channel.destroy!
expect(channel).to be_destroyed
end
end
end
end
end end

View File

@ -164,6 +164,18 @@ describe Whatsapp::IncomingMessageBaileysService do
} }
end end
it 'creates an alert of unsupported message' do
described_class.new(inbox: inbox, params: params).perform
conversation = inbox.conversations.last
message = conversation.messages.last
expect(message).to be_present
expect(message.content).to eq(I18n.t('errors.messages.unsupported'))
expect(message.message_type).to eq('template')
expect(message.status).to eq('failed')
end
it 'logs a warning message' do it 'logs a warning message' do
allow(Rails.logger).to receive(:warn).with('Baileys unsupported message type: unsupported') allow(Rails.logger).to receive(:warn).with('Baileys unsupported message type: unsupported')
@ -200,11 +212,14 @@ describe Whatsapp::IncomingMessageBaileysService do
end end
context 'when message type is text' do context 'when message type is text' do
let(:phone_number) { '5511912345678' }
let(:jid) { "#{phone_number}@s.whatsapp.net" }
let(:content_message) { 'Hello from Baileys' }
let(:raw_message) do let(:raw_message) do
{ {
key: { id: 'msg_123', remoteJid: '5511912345678@s.whatsapp.net', fromMe: false }, key: { id: 'msg_123', remoteJid: jid, fromMe: false },
message: { conversation: 'Hello from Baileys' }, pushName: 'John Doe',
pushName: 'John Doe' message: { conversation: content_message }
} }
end end
let(:params) do let(:params) do
@ -224,18 +239,16 @@ describe Whatsapp::IncomingMessageBaileysService do
conversation = inbox.conversations.last conversation = inbox.conversations.last
message = conversation.messages.last message = conversation.messages.last
expect(message).to be_present expect(message).to be_present
expect(message.content).to eq('Hello from Baileys') expect(message.content).to eq(content_message)
expect(message.message_type).to eq('incoming')
expect(message.sender).to be_present expect(message.sender).to be_present
expect(message.sender.name).to eq('John Doe') expect(message.sender.name).to eq('John Doe')
expect(message.message_type).to eq('incoming')
end end
it 'creates an outgoing message' do it 'creates an outgoing message' do
number = '5511912345678' raw_message_outgoing = raw_message.merge(key: { id: 'msg_123', remoteJid: jid, fromMe: true })
raw_message_outgoing = raw_message.merge(
key: { id: 'msg_123', remoteJid: "#{number}@s.whatsapp.net", fromMe: true }
)
params_outgoing = params.merge(data: { type: 'notify', messages: [raw_message_outgoing] }) params_outgoing = params.merge(data: { type: 'notify', messages: [raw_message_outgoing] })
create(:account_user, account: inbox.account) create(:account_user, account: inbox.account)
@ -243,20 +256,60 @@ describe Whatsapp::IncomingMessageBaileysService do
conversation = inbox.conversations.last conversation = inbox.conversations.last
message = conversation.messages.last message = conversation.messages.last
expect(message).to be_present expect(message).to be_present
expect(message.content).to eq('Hello from Baileys') expect(message.content).to eq(content_message)
expect(conversation.contact.name).to eq(phone_number)
expect(message.message_type).to eq('outgoing') expect(message.message_type).to eq('outgoing')
expect(conversation.contact.name).to eq(number)
end end
it 'updates the contact name if the current name is a phone number when a incoming message is received' do it 'creates an outgoing self message' do
create(:contact, account: inbox.account, name: '5511912345678') self_jid = "#{whatsapp_channel.phone_number.delete('+')}@s.whatsapp.net"
raw_message_outgoing = raw_message.merge(key: { id: 'msg_123', remoteJid: self_jid, fromMe: true })
params_outgoing = params.merge(data: { type: 'notify', messages: [raw_message_outgoing] })
create(:account_user, account: inbox.account)
described_class.new(inbox: inbox, params: params_outgoing).perform
conversation = inbox.conversations.last
message = conversation.messages.last
expect(message).to be_present
expect(message.content).to eq(content_message)
expect(conversation.contact.name).to eq('John Doe')
expect(message.message_type).to eq('outgoing')
end
it 'updates the contact name when name is the phone number and message has a pushName' do
create(:contact, account: inbox.account, name: phone_number)
described_class.new(inbox: inbox, params: params).perform described_class.new(inbox: inbox, params: params).perform
conversation = inbox.conversations.last conversation = inbox.conversations.last
expect(conversation.contact.name).to eq('John Doe') expect(conversation.contact.name).to eq('John Doe')
end end
it 'updates the contact name when name is the phone number and message has a verifiedBizName' do
raw_message[:verifiedBizName] = 'Verified John'
create(:contact, account: inbox.account, name: phone_number)
described_class.new(inbox: inbox, params: params).perform
conversation = inbox.conversations.last
expect(conversation.contact.name).to eq('Verified John')
end
it 'creates contact with phone number as name on outgoing message' do
raw_message_outgoing = raw_message.merge(key: { id: 'msg_123', remoteJid: jid, fromMe: true })
params_outgoing = params.merge(data: { type: 'notify', messages: [raw_message_outgoing] })
create(:account_user, account: inbox.account)
described_class.new(inbox: inbox, params: params_outgoing).perform
conversation = inbox.conversations.last
expect(conversation.contact.name).to eq(phone_number)
end
it 'creates a message on an existing conversation' do it 'creates a message on an existing conversation' do
contact = create(:contact, account: inbox.account, name: 'John Doe') contact = create(:contact, account: inbox.account, name: 'John Doe')
contact_inbox = create(:contact_inbox, inbox: inbox, contact: contact, source_id: '5511912345678') contact_inbox = create(:contact_inbox, inbox: inbox, contact: contact, source_id: '5511912345678')
@ -319,6 +372,233 @@ describe Whatsapp::IncomingMessageBaileysService do
end end
end end
end end
context 'when message type is image' do
let(:raw_message) do
{
key: { id: 'msg_123', remoteJid: '5511912345678@s.whatsapp.net', fromMe: false },
message: { imageMessage: { caption: 'Hello from Baileys' } },
pushName: 'John Doe'
}
end
let(:params) do
{
webhookVerifyToken: webhook_verify_token,
event: 'messages.upsert',
data: {
type: 'notify',
messages: [raw_message]
},
extra: {
media: {
'msg_123' => 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII='
}
}
}
end
it 'creates the message with caption' do
described_class.new(inbox: inbox, params: params).perform
conversation = inbox.conversations.last
message = conversation.messages.last
expect(message).to be_present
expect(message.content).to eq('Hello from Baileys')
end
it 'creates message attachment' do
freeze_time
described_class.new(inbox: inbox, params: params).perform
conversation = inbox.conversations.last
message = conversation.messages.last
attachment = message.attachments.last
expect(attachment).to be_present
expect(attachment.file).to be_present
expect(attachment.file_type).to eq('image')
expect(attachment.file.filename.to_s).to eq("image_#{message.id}_#{Time.current.strftime('%Y%m%d')}")
expect(attachment.file.content_type).to eq('image/png')
end
end
context 'when message type is video' do
let(:raw_message) do
{
key: { id: 'msg_123', remoteJid: '5511912345678@s.whatsapp.net', fromMe: false },
message: { videoMessage: { caption: 'Hello from Baileys' } },
pushName: 'John Doe'
}
end
let(:params) do
{
webhookVerifyToken: webhook_verify_token,
event: 'messages.upsert',
data: {
type: 'notify',
messages: [raw_message]
},
extra: {
media: {
'msg_123' => 'AAAAHGZ0eXBpc29tAAACAGlzb21pc28ybXA0MQAAAAhmcmVlAAABL21kYXQAAAGzABAHAAABthGBxgj238bbfxtt/G238bbfxtt/G238bbfxtt/G238bbfxtt/G238bbfxtt/G237wAAAbMAEAcAAAG2E4HGCkbckbbfxtt/G238bbfxtt/G238bbfxtt/G238bbfxtt/G238bbfxtt/G237AAABswAQBwAAAbYVgcYLltyRtt/G238bbfxtt/G238bbfxtt/G238bbfxtt/G238bbfxtt/G238bbfsAAAGzABAHAAABtheBxhPbckbbfxtt/G238bbfxtt/G238bbfxtt/G238bbfxtt/G238bbfxtt/G237wAAAbMAEAcAAAG2GYHGJG3JG238bbfxtt/G238bbfxtt/G238bbfxtt/G238bbfxtt/G238bbfxtt+/AAADHW1vb3YAAABsbXZoZAAAAAAAAAAAAAAAAAAAA+gAAAPoAAEAAAEAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAJHdHJhawAAAFx0a2hkAAAAAwAAAAAAAAAAAAAAAQAAAAAAAAPoAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAQAAAAABAAAAAQAAAAAAAJGVkdHMAAAAcZWxzdAAAAAAAAAABAAAD6AAAAAAAAQAAAAABv21kaWEAAAAgbWRoZAAAAAAAAAAAAAAAAAAAKAAAACgAVcQAAAAAAC1oZGxyAAAAAAAAAAB2aWRlAAAAAAAAAAAAAAAAVmlkZW9IYW5kbGVyAAAAAWptaW5mAAAAFHZtaGQAAAABAAAAAAAAAAAAAAAkZGluZgAAABxkcmVmAAAAAAAAAAEAAAAMdXJsIAAAAAEAAAEqc3RibAAAAGJzdHNkAAAAAAAAACVhdmMxAAAAAAAAAAAAAAAAAACFc3R0cwAAAAAAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA' # rubocop:disable Layout/LineLength
}
}
}
end
it 'creates the message with caption' do
described_class.new(inbox: inbox, params: params).perform
conversation = inbox.conversations.last
message = conversation.messages.last
expect(message).to be_present
expect(message.content).to eq('Hello from Baileys')
end
it 'creates message attachment' do
freeze_time
described_class.new(inbox: inbox, params: params).perform
conversation = inbox.conversations.last
message = conversation.messages.last
attachment = message.attachments.last
expect(attachment).to be_present
expect(attachment.file).to be_present
expect(attachment.file_type).to eq('video')
expect(attachment.file.filename.to_s).to eq("video_#{message.id}_#{Time.current.strftime('%Y%m%d')}")
expect(attachment.file.content_type).to eq('video/mp4')
end
end
context 'when message type is file' do
let(:filename) { 'file.pdf' }
let(:raw_message) do
{
key: { id: 'msg_123', remoteJid: '5511912345678@s.whatsapp.net', fromMe: false },
message: { documentMessage: { fileName: filename } },
pushName: 'John Doe'
}
end
let(:params) do
{
webhookVerifyToken: webhook_verify_token,
event: 'messages.upsert',
data: {
type: 'notify',
messages: [raw_message]
},
extra: {
media: {
'msg_123' => 'JVBERi0xLjQKJVRlc3QgUERGCjEgMCBvYmoKPDwgL1R5cGUgL0NhdGFsb2cgL091dGxpbmVzIDIgMCBSIC9QYWdlcyAzIDAgUiA+PgplbmRvYmoKMiAwIG9iago8PCAvVHlwZSAvT3V0bGluZXMgL0NvdW50IDAgPj4KZW5kb2JqCjMgMCBvYmoKPDwgL1R5cGUgL1BhZ2VzIC9LaWRzIFs0IDAgUl0gL0NvdW50IDEgPj4KZW5kb2JqCjQgMCBvYmoKPDwgL1R5cGUgL1BhZ2UgCiAgIC9QYXJlbnQgMyAwIFIgCiAgIC9NZWRpYUJveCBbMCAwIDMwMCAyMDBdIAogICAvQ29udGVudHMgNSAwIFIgCiAgIC9SZXNvdXJjZXMgPDwgL0ZvbnQgPDwgL0YxIDw8IC9UeXBlIC9Gb250IC9TdWJ0eXBlIC9UeXBlMSAvQmFzZUZvbnQgL0hlbHZldGljYSA+PiA+PiA+PgplbmRvYmoKNSAwIG9iago8PCAvTGVuZ3RoIDUzID4+CnN0cmVhbQpCVAovRjEgMTIgVGYKNzIgMTUwIFRkCihIZWxsbyBQREYhKSBUagpFVAplbmRzdHJlYW0KZW5kb2JqCnhyZWYKMCA2CjAwMDAwMDAwMDAgNjU1MzUgZiAKMDAwMDAwMDAxNyAwMDAwMCBuIAowMDAwMDAwMDg0IDAwMDAwIG4gCjAwMDAwMDAxMzQgMDAwMDAgbiAKMDAwMDAwMDE5MyAwMDAwMCBuIAowMDAwMDAwMzkxIDAwMDAwIG4gCnRyYWlsZXIKPDwgL1NpemUgNiAvUm9vdCAxIDAgUiA+PgpzdGFydHhyZWYKNDk0CiUlRU9GCg==' # rubocop:disable Layout/LineLength
}
}
}
end
it 'creates message attachment' do
described_class.new(inbox: inbox, params: params).perform
conversation = inbox.conversations.last
message = conversation.messages.last
attachment = message.attachments.last
expect(attachment).to be_present
expect(attachment.file_type).to eq('file')
expect(attachment.file).to be_present
expect(attachment.file.filename.to_s).to eq(filename)
expect(attachment.file.content_type).to eq('application/pdf')
end
end
context 'when message type is audio' do
let(:raw_message) do
{
key: { id: 'msg_123', remoteJid: '5511912345678@s.whatsapp.net', fromMe: false },
message: { audioMessage: {} },
pushName: 'John Doe'
}
end
let(:params) do
{
webhookVerifyToken: webhook_verify_token,
event: 'messages.upsert',
data: {
type: 'notify',
messages: [raw_message]
},
extra: {
media: {
'msg_123' => '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' # rubocop:disable Layout/LineLength
}
}
}
end
it 'creates message attachment' do
freeze_time
described_class.new(inbox: inbox, params: params).perform
conversation = inbox.conversations.last
message = conversation.messages.last
attachment = message.attachments.last
expect(attachment).to be_present
expect(attachment.file_type).to eq('audio')
expect(attachment.file).to be_present
expect(attachment.file.filename.to_s).to eq("audio_#{message.id}_#{Time.current.strftime('%Y%m%d')}")
expect(attachment.file.content_type).to eq('audio/opus')
end
end
context 'when message type is sticker' do
let(:raw_message) do
{
key: { id: 'msg_123', remoteJid: '5511912345678@s.whatsapp.net', fromMe: false },
message: { stickerMessage: {} },
pushName: 'John Doe'
}
end
let(:params) do
{
webhookVerifyToken: webhook_verify_token,
event: 'messages.upsert',
data: {
type: 'notify',
messages: [raw_message]
},
extra: {
media: {
'msg_123' => 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII='
}
}
}
end
it 'creates message attachment' do
freeze_time
described_class.new(inbox: inbox, params: params).perform
conversation = inbox.conversations.last
message = conversation.messages.last
attachment = message.attachments.last
expect(attachment).to be_present
expect(attachment.file_type).to eq('image')
expect(attachment.file).to be_present
expect(attachment.file.filename.to_s).to eq("image_#{message.id}_#{Time.current.strftime('%Y%m%d')}")
expect(attachment.file.content_type).to eq('image/png')
end
end
end end
context 'when processing messages.update event' do context 'when processing messages.update event' do

View File

@ -91,63 +91,103 @@ describe Whatsapp::Providers::WhatsappBaileysService do
end end
describe '#send_message' do describe '#send_message' do
context 'when response is successful' do context 'when message has attachment' do
it 'returns true' do let(:base64_image) { 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=' }
before do
message.attachments.new(
account_id: message.account_id,
file_type: 'image',
file: {
io: StringIO.new(Base64.decode64(base64_image)),
filename: 'image.png'
}
)
message.save!
end
it 'sends the attachment message' do
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/send-message") stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/send-message")
.with( .with(
headers: stub_headers(whatsapp_channel), headers: stub_headers(whatsapp_channel),
body: { body: {
type: 'text',
recipient: test_send_phone_number, recipient: test_send_phone_number,
message: message.content messageContent: { fileName: 'image.png', caption: message.content, image: base64_image }
}.to_json }.to_json
) )
.to_return( .to_return(
status: 200, status: 200,
headers: { 'Content-Type' => 'application/json' }, headers: { 'Content-Type' => 'application/json' },
body: { 'data' => { 'key' => { 'id' => message.id } } }.to_json body: { 'data' => { 'key' => { 'id' => 'msg_123' } } }.to_json
) )
result = service.send_message(test_send_phone_number, message) result = service.send_message(test_send_phone_number, message)
expect(result).to be message.id expect(result).to eq('msg_123')
end
end end
context 'when response is unsuccessful' do context 'when request is unsuccessful' do
it 'logs the error and returns false' do it 'raises MessageNotSentError' do
with_modified_env BAILEYS_PROVIDER_DEFAULT_URL: 'http://test.com' do
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/send-message") stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/send-message")
.with(
headers: stub_headers(whatsapp_channel),
body: {
type: 'text',
recipient: test_send_phone_number,
message: message.content
}.to_json
)
.to_return( .to_return(
status: 400, status: 400,
body: 'error message', headers: { 'Content-Type' => 'application/json' },
headers: {} body: { 'data' => { 'key' => { 'id' => 'msg_123' } } }.to_json
) )
allow(Rails.logger).to receive(:error).with('error message')
result = service.send_message(test_send_phone_number, message)
expect(result).to be_nil
expect(Rails.logger).to have_received(:error)
end
end
end
context 'when message content type is not supported' do
it 'raises an error' do
message.update!(content_type: 'sticker')
expect do expect do
service.send_message(test_send_phone_number, message) service.send_message(test_send_phone_number, message)
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::MessageContentTypeNotSupported) end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::MessageNotSentError)
end
end
end
context 'when message does not have content nor attachments' do
it 'updates the message status to failed' do
message.update!(content: nil)
service.send_message(test_send_phone_number, message)
expect(message.status).to eq('failed')
end
end
context 'when message is a text' do
it 'sends the message' do
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/send-message")
.to_return(
status: 200,
headers: { 'Content-Type' => 'application/json' },
body: { 'data' => { 'key' => { 'id' => 'msg_123' } } }.to_json
)
result = service.send_message(test_send_phone_number, message)
expect(result).to eq('msg_123')
end
it 'raises MessageNotSentError' do
stub_request(:post, "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/send-message")
.to_return(
status: 400,
headers: { 'Content-Type' => 'application/json' },
body: { 'data' => { 'key' => { 'id' => 'msg_123' } } }.to_json
)
expect do
service.send_message(test_send_phone_number, message)
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::MessageNotSentError)
end
end
context "when message doesn't have attachment and content" do
it 'does not send the message' do
message.update!(content: nil)
service.send_message(test_send_phone_number, message)
expect(message.status).to eq('failed')
expect(message.content).to eq(I18n.t('errors.messages.send.unsupported'))
end end
end end
end end
@ -181,8 +221,6 @@ describe Whatsapp::Providers::WhatsappBaileysService do
expect(service.validate_provider_config?).to be false expect(service.validate_provider_config?).to be false
expect(Rails.logger).to have_received(:error) expect(Rails.logger).to have_received(:error)
end end
end
end
context 'when provider responds with 5XX' do context 'when provider responds with 5XX' do
it 'updated provider connection to close' do it 'updated provider connection to close' do
@ -191,9 +229,8 @@ describe Whatsapp::Providers::WhatsappBaileysService do
"#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/send-message", "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/send-message",
headers: stub_headers(whatsapp_channel), headers: stub_headers(whatsapp_channel),
body: { body: {
type: 'text',
recipient: test_send_phone_number, recipient: test_send_phone_number,
message: message.content messageContent: { text: message.content }
}.to_json }.to_json
).and_raise(HTTParty::ResponseError.new(OpenStruct.new(status_code: 500))) ).and_raise(HTTParty::ResponseError.new(OpenStruct.new(status_code: 500)))
@ -204,6 +241,8 @@ describe Whatsapp::Providers::WhatsappBaileysService do
expect(whatsapp_channel.provider_connection['connection']).to eq('close') expect(whatsapp_channel.provider_connection['connection']).to eq('close')
end end
end end
end
end
context 'when environment variable BAILEYS_PROVIDER_DEFAULT_URL is set' do context 'when environment variable BAILEYS_PROVIDER_DEFAULT_URL is set' do
it 'uses the base url from the environment variable' do it 'uses the base url from the environment variable' do