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:
parent
d0eb84dbed
commit
8fc14186e0
@ -26,7 +26,9 @@ class Webhooks::WhatsappController < ActionController::API
|
||||
rescue Whatsapp::IncomingMessageBaileysService::InvalidWebhookVerifyToken
|
||||
head :unauthorized
|
||||
rescue Whatsapp::IncomingMessageBaileysService::MessageNotFoundError
|
||||
head :bad_request
|
||||
head :not_found
|
||||
rescue Whatsapp::IncomingMessageBaileysService::AttachmentNotFoundError
|
||||
head :unprocessable_entity
|
||||
end
|
||||
|
||||
def valid_token?(token)
|
||||
|
||||
@ -37,6 +37,8 @@ class Channel::Whatsapp < ApplicationRecord
|
||||
|
||||
after_create :sync_templates
|
||||
|
||||
before_destroy :disconnect_channel_provider, if: -> { provider == 'baileys' }
|
||||
|
||||
def name
|
||||
'Whatsapp'
|
||||
end
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
class Whatsapp::IncomingMessageBaileysService < Whatsapp::IncomingMessageBaseService # rubocop:disable Metrics/ClassLength
|
||||
class InvalidWebhookVerifyToken < StandardError; end
|
||||
class MessageNotFoundError < StandardError; end
|
||||
class AttachmentNotFoundError < StandardError; end
|
||||
|
||||
def perform
|
||||
raise InvalidWebhookVerifyToken if processed_params[:webhookVerifyToken] != inbox.channel.provider_config['webhook_verify_token']
|
||||
@ -64,12 +65,7 @@ class Whatsapp::IncomingMessageBaileysService < Whatsapp::IncomingMessageBaseSer
|
||||
end
|
||||
|
||||
def set_contact
|
||||
# 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
|
||||
# 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
|
||||
push_name = contact_name
|
||||
contact_inbox = ::ContactInboxWithContactBuilder.new(
|
||||
source_id: phone_number_from_jid,
|
||||
inbox: inbox,
|
||||
@ -79,14 +75,36 @@ class Whatsapp::IncomingMessageBaileysService < Whatsapp::IncomingMessageBaseSer
|
||||
@contact_inbox = contact_inbox
|
||||
@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
|
||||
|
||||
def handle_create_message
|
||||
case message_type
|
||||
when 'text'
|
||||
create_text_message
|
||||
create_message
|
||||
when 'image', 'file', 'video', 'audio', 'sticker'
|
||||
create_message
|
||||
attach_media
|
||||
else
|
||||
create_unsupported_message
|
||||
Rails.logger.warn "Baileys unsupported message type: #{message_type}"
|
||||
end
|
||||
end
|
||||
@ -126,7 +144,7 @@ class Whatsapp::IncomingMessageBaileysService < Whatsapp::IncomingMessageBaseSer
|
||||
return 'video_note' if msg.key?(:ptvMessage)
|
||||
return 'location' if msg.key?(:locationMessage)
|
||||
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 'event' if msg.key?(:eventMessage)
|
||||
return 'sticker' if msg.key?(:stickerMessage)
|
||||
@ -134,15 +152,13 @@ class Whatsapp::IncomingMessageBaileysService < Whatsapp::IncomingMessageBaseSer
|
||||
'unsupported'
|
||||
end
|
||||
|
||||
def create_text_message
|
||||
is_outgoing = @raw_message[:key][:fromMe]
|
||||
sender = is_outgoing ? @inbox.account.account_users.first.user : @contact
|
||||
sender_type = is_outgoing ? 'User' : 'Contact'
|
||||
message_type = is_outgoing ? :outgoing : :incoming
|
||||
content = @raw_message.dig(:message, :conversation) || @raw_message.dig(:message, :extendedTextMessage, :text)
|
||||
def create_message
|
||||
sender = incoming? ? @contact : @inbox.account.account_users.first.user
|
||||
sender_type = incoming? ? 'Contact' : 'User'
|
||||
message_type = incoming? ? :incoming : :outgoing
|
||||
|
||||
@message = @conversation.messages.create!(
|
||||
content: content,
|
||||
content: message_content,
|
||||
account_id: @inbox.account_id,
|
||||
inbox_id: @inbox.id,
|
||||
source_id: message_id,
|
||||
@ -153,6 +169,71 @@ class Whatsapp::IncomingMessageBaileysService < Whatsapp::IncomingMessageBaseSer
|
||||
)
|
||||
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
|
||||
@raw_message[:key][:id]
|
||||
end
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseService
|
||||
class MessageContentTypeNotSupported < StandardError; end
|
||||
class MessageNotSentError < StandardError; end
|
||||
|
||||
DEFAULT_CLIENT_NAME = ENV.fetch('BAILEYS_PROVIDER_DEFAULT_CLIENT_NAME', nil)
|
||||
DEFAULT_URL = ENV.fetch('BAILEYS_PROVIDER_DEFAULT_URL', nil)
|
||||
@ -29,21 +30,15 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
||||
end
|
||||
|
||||
def send_message(phone_number, message)
|
||||
raise MessageContentTypeNotSupported unless message.content_type == 'text'
|
||||
|
||||
response = HTTParty.post(
|
||||
"#{provider_url}/connections/#{whatsapp_channel.phone_number}/send-message",
|
||||
headers: api_headers,
|
||||
body: {
|
||||
type: 'text',
|
||||
recipient: phone_number,
|
||||
message: message.content
|
||||
}.to_json
|
||||
)
|
||||
|
||||
return unless process_response(response)
|
||||
|
||||
response.parsed_response.dig('data', 'key', 'id')
|
||||
@message = message
|
||||
@phone_number = phone_number
|
||||
if message.attachments.present?
|
||||
send_attachment_message
|
||||
elsif message.content.present?
|
||||
send_text_message
|
||||
else
|
||||
message.update!(content: I18n.t('errors.messages.send.unsupported'), status: 'failed')
|
||||
end
|
||||
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
|
||||
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)
|
||||
Rails.logger.error response.body unless response.success?
|
||||
response.success?
|
||||
|
||||
@ -37,6 +37,10 @@ en:
|
||||
inbox_deletetion_response: Your inbox deletion request will be processed in some time.
|
||||
|
||||
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:
|
||||
presence: must not be blank
|
||||
webhook:
|
||||
|
||||
@ -23,6 +23,10 @@ pt_BR:
|
||||
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.
|
||||
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:
|
||||
presence: não pode ficar em branco
|
||||
webhook:
|
||||
@ -206,7 +210,7 @@ pt_BR:
|
||||
meeting_name: '%{agent_name} começou a reunião'
|
||||
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:
|
||||
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.'
|
||||
@ -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.'
|
||||
google_translate:
|
||||
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:
|
||||
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.'
|
||||
|
||||
@ -125,6 +125,8 @@ RSpec.describe Attachment do
|
||||
|
||||
it 'preserves meta data with file attachments' do
|
||||
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
|
||||
|
||||
@ -61,4 +61,41 @@ RSpec.describe Channel::Whatsapp do
|
||||
expect(channel.provider_config['webhook_verify_token']).to eq '123'
|
||||
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
|
||||
|
||||
@ -164,6 +164,18 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
}
|
||||
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
|
||||
allow(Rails.logger).to receive(:warn).with('Baileys unsupported message type: unsupported')
|
||||
|
||||
@ -200,11 +212,14 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
end
|
||||
|
||||
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
|
||||
{
|
||||
key: { id: 'msg_123', remoteJid: '5511912345678@s.whatsapp.net', fromMe: false },
|
||||
message: { conversation: 'Hello from Baileys' },
|
||||
pushName: 'John Doe'
|
||||
key: { id: 'msg_123', remoteJid: jid, fromMe: false },
|
||||
pushName: 'John Doe',
|
||||
message: { conversation: content_message }
|
||||
}
|
||||
end
|
||||
let(:params) do
|
||||
@ -224,18 +239,16 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
|
||||
conversation = inbox.conversations.last
|
||||
message = conversation.messages.last
|
||||
|
||||
expect(message).to be_present
|
||||
expect(message.content).to eq('Hello from Baileys')
|
||||
expect(message.message_type).to eq('incoming')
|
||||
expect(message.content).to eq(content_message)
|
||||
expect(message.sender).to be_present
|
||||
expect(message.sender.name).to eq('John Doe')
|
||||
expect(message.message_type).to eq('incoming')
|
||||
end
|
||||
|
||||
it 'creates an outgoing message' do
|
||||
number = '5511912345678'
|
||||
raw_message_outgoing = raw_message.merge(
|
||||
key: { id: 'msg_123', remoteJid: "#{number}@s.whatsapp.net", fromMe: true }
|
||||
)
|
||||
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)
|
||||
|
||||
@ -243,20 +256,60 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
|
||||
conversation = inbox.conversations.last
|
||||
message = conversation.messages.last
|
||||
|
||||
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(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')
|
||||
it 'creates an outgoing self message' do
|
||||
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
|
||||
|
||||
conversation = inbox.conversations.last
|
||||
expect(conversation.contact.name).to eq('John Doe')
|
||||
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
|
||||
contact = create(:contact, account: inbox.account, name: 'John Doe')
|
||||
contact_inbox = create(:contact_inbox, inbox: inbox, contact: contact, source_id: '5511912345678')
|
||||
@ -319,6 +372,233 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
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
|
||||
|
||||
context 'when processing messages.update event' do
|
||||
|
||||
@ -91,63 +91,103 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
end
|
||||
|
||||
describe '#send_message' do
|
||||
context 'when response is successful' do
|
||||
it 'returns true' do
|
||||
context 'when message has attachment' 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")
|
||||
.with(
|
||||
headers: stub_headers(whatsapp_channel),
|
||||
body: {
|
||||
type: 'text',
|
||||
recipient: test_send_phone_number,
|
||||
message: message.content
|
||||
messageContent: { fileName: 'image.png', caption: message.content, image: base64_image }
|
||||
}.to_json
|
||||
)
|
||||
.to_return(
|
||||
status: 200,
|
||||
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)
|
||||
|
||||
expect(result).to be message.id
|
||||
expect(result).to eq('msg_123')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when response is unsuccessful' do
|
||||
it 'logs the error and returns false' do
|
||||
with_modified_env BAILEYS_PROVIDER_DEFAULT_URL: 'http://test.com' do
|
||||
context 'when request is unsuccessful' do
|
||||
it 'raises MessageNotSentError' do
|
||||
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(
|
||||
status: 400,
|
||||
body: 'error message',
|
||||
headers: {}
|
||||
headers: { 'Content-Type' => 'application/json' },
|
||||
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)
|
||||
expect do
|
||||
service.send_message(test_send_phone_number, message)
|
||||
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::MessageNotSentError)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when message content type is not supported' do
|
||||
it 'raises an error' do
|
||||
message.update!(content_type: 'sticker')
|
||||
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::MessageContentTypeNotSupported)
|
||||
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
|
||||
@ -181,27 +221,26 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
expect(service.validate_provider_config?).to be false
|
||||
expect(Rails.logger).to have_received(:error)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when provider responds with 5XX' do
|
||||
it 'updated provider connection to close' do
|
||||
whatsapp_channel.update!(provider_connection: { 'connection' => 'open' })
|
||||
allow(HTTParty).to receive(:post).with(
|
||||
"#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/send-message",
|
||||
headers: stub_headers(whatsapp_channel),
|
||||
body: {
|
||||
type: 'text',
|
||||
recipient: test_send_phone_number,
|
||||
message: message.content
|
||||
}.to_json
|
||||
).and_raise(HTTParty::ResponseError.new(OpenStruct.new(status_code: 500)))
|
||||
context 'when provider responds with 5XX' do
|
||||
it 'updated provider connection to close' do
|
||||
whatsapp_channel.update!(provider_connection: { 'connection' => 'open' })
|
||||
allow(HTTParty).to receive(:post).with(
|
||||
"#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/send-message",
|
||||
headers: stub_headers(whatsapp_channel),
|
||||
body: {
|
||||
recipient: test_send_phone_number,
|
||||
messageContent: { text: message.content }
|
||||
}.to_json
|
||||
).and_raise(HTTParty::ResponseError.new(OpenStruct.new(status_code: 500)))
|
||||
|
||||
expect do
|
||||
service.send_message(test_send_phone_number, message)
|
||||
end.to raise_error(HTTParty::ResponseError)
|
||||
expect do
|
||||
service.send_message(test_send_phone_number, message)
|
||||
end.to raise_error(HTTParty::ResponseError)
|
||||
|
||||
expect(whatsapp_channel.provider_connection['connection']).to eq('close')
|
||||
expect(whatsapp_channel.provider_connection['connection']).to eq('close')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user