feat: baileys messages reactions (#33)
* fix: update send_message method to handle baileys-api send-message changes * chore: simplify update of is_unsupported * feat: add support for message reactions in WhatsApp Baileys service * chore: update context description for unsupported message in send_message spec * feat: enhance incoming messages Baileys service to support message reactions and external source ID handling * fix: correct typo in comment for is_reaction attribute in Message model * chore: simplify message handling by removing external source ID checks and unused methods * fix: update comment to reflect necessary change for source_id in set_contact method * fix: refactor message handling for reactions and simplify jid generation * fix: add comment to clarify behavior of find_message_by_source_id method * fix: update unsupported message handling * chore: specs for reaction messages * fix: simplify message update handling by removing redundant validation method * fix: update reaction message handling to use in_reply_to_external_id and fix spec * fix: enhance message handling for unsupported types and update specs * fix: update reaction message handling to set is_reaction flag and enhance specs * fix: update specs for send message in reaction * revert: remove mb0 from input * fix: streamline jid generation in send_message_request * refactor: specs * refactor: move content attributes to helper * test: spec for empty reaction * refactor: streamline references to replied message * test: refactor spec * test: refactor spec --------- Co-authored-by: gabrieljablonski <contact@gabrieljablonski.com>
This commit is contained in:
parent
8dc893d322
commit
8e913236e5
@ -63,7 +63,6 @@ export default {
|
||||
<label class="input-container">
|
||||
<span v-if="label">{{ label }}</span>
|
||||
<input
|
||||
class="!mb-0"
|
||||
:value="modelValue"
|
||||
:type="type"
|
||||
:placeholder="placeholder"
|
||||
|
||||
@ -101,9 +101,10 @@ class Message < ApplicationRecord
|
||||
# [:deleted] : Used to denote whether the message was deleted by the agent
|
||||
# [:external_created_at] : Can specify if the message was created at a different timestamp externally
|
||||
# [:external_error : Can specify if the message creation failed due to an error at external API
|
||||
# [:is_reaction] : Used to denote if the message is a reaction and differentiate it from a simple reply message
|
||||
store :content_attributes, accessors: [:submitted_email, :items, :submitted_values, :email, :in_reply_to, :deleted,
|
||||
:external_created_at, :story_sender, :story_id, :external_error,
|
||||
:translations, :in_reply_to_external_id, :is_unsupported], coder: JSON
|
||||
:translations, :in_reply_to_external_id, :is_unsupported, :is_reaction], coder: JSON
|
||||
|
||||
store :external_source_ids, accessors: [:slack], coder: JSON, prefix: :external_source_id
|
||||
|
||||
|
||||
@ -67,6 +67,7 @@ class Whatsapp::IncomingMessageBaileysService < Whatsapp::IncomingMessageBaseSer
|
||||
def set_contact
|
||||
push_name = contact_name
|
||||
contact_inbox = ::ContactInboxWithContactBuilder.new(
|
||||
# FIXME: update the source_id to complete jid in future
|
||||
source_id: phone_number_from_jid,
|
||||
inbox: inbox,
|
||||
contact_attributes: { name: push_name, phone_number: "+#{phone_number_from_jid}" }
|
||||
@ -100,6 +101,8 @@ class Whatsapp::IncomingMessageBaileysService < Whatsapp::IncomingMessageBaseSer
|
||||
case message_type
|
||||
when 'text'
|
||||
create_message
|
||||
when 'reaction'
|
||||
create_message if message_content.present?
|
||||
when 'image', 'file', 'video', 'audio', 'sticker'
|
||||
create_message
|
||||
attach_media
|
||||
@ -148,6 +151,7 @@ class Whatsapp::IncomingMessageBaileysService < Whatsapp::IncomingMessageBaseSer
|
||||
return 'poll' if msg.key?(:pollCreationMessageV3)
|
||||
return 'event' if msg.key?(:eventMessage)
|
||||
return 'sticker' if msg.key?(:stickerMessage)
|
||||
return 'reaction' if msg.key?(:reactionMessage)
|
||||
|
||||
'unsupported'
|
||||
end
|
||||
@ -165,10 +169,19 @@ class Whatsapp::IncomingMessageBaileysService < Whatsapp::IncomingMessageBaseSer
|
||||
sender: sender,
|
||||
sender_type: sender_type,
|
||||
message_type: message_type,
|
||||
in_reply_to_external_id: nil
|
||||
content_attributes: message_content_attributes
|
||||
)
|
||||
end
|
||||
|
||||
def message_content_attributes
|
||||
return unless message_type == 'reaction'
|
||||
|
||||
{
|
||||
in_reply_to_external_id: @raw_message.dig(:message, :reactionMessage, :key, :id),
|
||||
is_reaction: true
|
||||
}
|
||||
end
|
||||
|
||||
def incoming?
|
||||
!@raw_message[:key][:fromMe]
|
||||
end
|
||||
@ -231,6 +244,8 @@ class Whatsapp::IncomingMessageBaileysService < Whatsapp::IncomingMessageBaseSer
|
||||
@raw_message.dig(:message, :imageMessage, :caption)
|
||||
when 'video'
|
||||
@raw_message.dig(:message, :videoMessage, :caption)
|
||||
when 'reaction'
|
||||
@raw_message.dig(:message, :reactionMessage, :text)
|
||||
end
|
||||
end
|
||||
|
||||
@ -263,17 +278,12 @@ class Whatsapp::IncomingMessageBaileysService < Whatsapp::IncomingMessageBaseSer
|
||||
end
|
||||
|
||||
def handle_update
|
||||
raise MessageNotFoundError unless valid_update_message?
|
||||
raise MessageNotFoundError unless find_message_by_source_id(message_id)
|
||||
|
||||
update_status if @raw_message.dig(:update, :status).present?
|
||||
update_message_content if @raw_message.dig(:update, :message).present?
|
||||
end
|
||||
|
||||
def valid_update_message?
|
||||
@message = find_message_by_source_id(message_id)
|
||||
@message.present?
|
||||
end
|
||||
|
||||
def update_status
|
||||
status = status_mapper
|
||||
@message.update!(status: status) if status.present? && status_transition_allowed?(status)
|
||||
|
||||
@ -33,7 +33,9 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
||||
@message = message
|
||||
@phone_number = phone_number
|
||||
|
||||
if message.attachments.present?
|
||||
if message.content_attributes[:is_reaction]
|
||||
@message_content = reaction_message_content
|
||||
elsif message.attachments.present?
|
||||
@message_content = attachment_message_content
|
||||
elsif message.content.present?
|
||||
@message_content = { text: @message.content }
|
||||
@ -74,6 +76,15 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
||||
whatsapp_channel.provider_config['api_key'].presence || DEFAULT_API_KEY
|
||||
end
|
||||
|
||||
def reaction_message_content
|
||||
{
|
||||
react: { key: { id: @message.in_reply_to_external_id,
|
||||
remoteJid: remote_jid,
|
||||
fromMe: true },
|
||||
text: @message.content }
|
||||
}
|
||||
end
|
||||
|
||||
def attachment_message_content
|
||||
attachment = @message.attachments.first
|
||||
buffer = Base64.strict_encode64(attachment.file.download)
|
||||
@ -103,7 +114,7 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
||||
"#{provider_url}/connections/#{whatsapp_channel.phone_number}/send-message",
|
||||
headers: api_headers,
|
||||
body: {
|
||||
jid: "#{@phone_number.delete('+')}@s.whatsapp.net",
|
||||
jid: remote_jid,
|
||||
messageContent: @message_content
|
||||
}.to_json
|
||||
)
|
||||
@ -118,6 +129,10 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
||||
response.success?
|
||||
end
|
||||
|
||||
def remote_jid
|
||||
"#{@phone_number.delete('+')}@s.whatsapp.net"
|
||||
end
|
||||
|
||||
private_class_method def self.with_error_handling(*method_names)
|
||||
method_names.each do |method_name|
|
||||
original_method = instance_method(method_name)
|
||||
|
||||
@ -164,7 +164,7 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
}
|
||||
end
|
||||
|
||||
it 'creates an alert of unsupported message' do
|
||||
it 'creates an unsupported message' do
|
||||
described_class.new(inbox: inbox, params: params).perform
|
||||
|
||||
conversation = inbox.conversations.last
|
||||
@ -373,6 +373,51 @@ describe Whatsapp::IncomingMessageBaileysService do
|
||||
end
|
||||
end
|
||||
|
||||
context 'when message type is reaction' do
|
||||
let(:phone_number) { '5511912345678' }
|
||||
let(:jid) { "#{phone_number}@s.whatsapp.net" }
|
||||
let(:raw_message) do
|
||||
{
|
||||
key: { id: 'reaction_123', remoteJid: jid, fromMe: false },
|
||||
message: { reactionMessage: { key: { remoteJid: jid, fromMe: true, id: 'msg_123' }, text: '👍' } },
|
||||
pushName: 'John Doe'
|
||||
}
|
||||
end
|
||||
let(:params) do
|
||||
{
|
||||
webhookVerifyToken: webhook_verify_token,
|
||||
event: 'messages.upsert',
|
||||
data: {
|
||||
type: 'notify',
|
||||
messages: [raw_message]
|
||||
}
|
||||
}
|
||||
end
|
||||
let!(:message) do
|
||||
contact = create(:contact, account: inbox.account, name: phone_number)
|
||||
contact_inbox = create(:contact_inbox, inbox: inbox, contact: contact, source_id: phone_number)
|
||||
conversation = create(:conversation, inbox: inbox, contact_inbox: contact_inbox)
|
||||
create(:message, inbox: inbox, conversation: conversation, source_id: 'msg_123')
|
||||
end
|
||||
|
||||
it 'creates the reaction' do
|
||||
described_class.new(inbox: inbox, params: params).perform
|
||||
|
||||
reaction = message.conversation.messages.last
|
||||
expect(reaction.is_reaction).to be(true)
|
||||
expect(reaction.in_reply_to).to eq(message.id)
|
||||
expect(reaction.in_reply_to_external_id).to eq(message.source_id)
|
||||
end
|
||||
|
||||
it 'does not create the reaction if content is empty' do
|
||||
raw_message[:message][:reactionMessage][:text] = ''
|
||||
|
||||
described_class.new(inbox: inbox, params: params).perform
|
||||
|
||||
expect(message.conversation.messages.count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when message type is image' do
|
||||
let(:raw_message) do
|
||||
{
|
||||
|
||||
@ -6,8 +6,8 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
let(:whatsapp_channel) { create(:channel_whatsapp, provider: 'baileys', validate_provider_config: false) }
|
||||
let(:message) { create(:message) }
|
||||
|
||||
let(:test_send_phone_number) { '+5511987654321' }
|
||||
let(:test_send_jid) { '5511987654321@s.whatsapp.net' }
|
||||
let(:test_send_phone_number) { '551187654321' }
|
||||
let(:test_send_jid) { '551187654321@s.whatsapp.net' }
|
||||
|
||||
before do
|
||||
stub_const('Whatsapp::Providers::WhatsappBaileysService::DEFAULT_CLIENT_NAME', 'chatwoot-test')
|
||||
@ -92,6 +92,39 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
end
|
||||
|
||||
describe '#send_message' do
|
||||
context 'when message is a reaction' do
|
||||
let(:inbox) { whatsapp_channel.inbox }
|
||||
let(:contact) { create(:contact, account: inbox.account, name: 'John Doe', phone_number: "+#{test_send_phone_number}") }
|
||||
let(:contact_inbox) { create(:contact_inbox, inbox: inbox, contact: contact, source_id: test_send_phone_number) }
|
||||
let(:conversation) { create(:conversation, inbox: inbox, contact_inbox: contact_inbox) }
|
||||
let!(:message) { create(:message, inbox: inbox, conversation: conversation, sender: contact, source_id: 'msg_123') }
|
||||
let(:reaction) do
|
||||
create(:message, inbox: inbox, conversation: conversation, sender: contact, content: '👍',
|
||||
content_attributes: { is_reaction: true, in_reply_to: message.id })
|
||||
end
|
||||
|
||||
it 'sends the reaction 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: {
|
||||
jid: test_send_jid,
|
||||
messageContent: { react: { key: { id: message.source_id,
|
||||
remoteJid: test_send_jid,
|
||||
fromMe: true },
|
||||
text: '👍' } }
|
||||
}.to_json
|
||||
)
|
||||
.to_return(
|
||||
status: 200,
|
||||
headers: { 'Content-Type' => 'application/json' },
|
||||
body: { 'data' => { 'key' => { 'id' => 'reaction_123' } } }.to_json
|
||||
)
|
||||
|
||||
result = service.send_message(test_send_phone_number, reaction)
|
||||
|
||||
expect(result).to eq('reaction_123')
|
||||
|
||||
context 'when message does not have content nor attachments' do
|
||||
it 'updates the message with content attribute is_unsupported' do
|
||||
message.update!(content: nil)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user