Fix/baileys unread message (#50)
* fix: correct structure of lastMessages in message content * fix: update send_message to store external_created_at timestamp with result from request * fix: filter incoming messages for unread conversation logic * fix: improve extraction of message timestamp in send_message method * feat: enhance message timestamp extraction and update logic in send_message method * refactor: send_message to improve error handling and update external_created_at timestamp * chore: move message timestamp extraction to helper * fix: use message timestamp extract on incoming * fix: update unread_conversation to handle last message correctly for Baileys provider --------- Co-authored-by: gabrieljablonski <contact@gabrieljablonski.com>
This commit is contained in:
parent
21133c3383
commit
548c0351ec
13
app/helpers/baileys_helper.rb
Normal file
13
app/helpers/baileys_helper.rb
Normal file
@ -0,0 +1,13 @@
|
||||
module BaileysHelper
|
||||
def extract_baileys_message_timestamp(timestamp)
|
||||
# NOTE: Timestamp might be in this format {"low"=>1748003165, "high"=>0, "unsigned"=>true}
|
||||
if timestamp.is_a?(Hash) && timestamp.key?('low')
|
||||
low = timestamp['low'].to_i
|
||||
high = timestamp.fetch('high', 0).to_i
|
||||
return (high << 32) | low
|
||||
end
|
||||
|
||||
# NOTE: Timestamp might be a string or a number
|
||||
timestamp.to_i
|
||||
end
|
||||
end
|
||||
@ -100,6 +100,7 @@ class Channel::Whatsapp < ApplicationRecord
|
||||
def unread_conversation(conversation)
|
||||
return unless provider_service.respond_to?(:unread_message)
|
||||
|
||||
# NOTE: For the Baileys provider, the last message is required even if it is an outgoing message.
|
||||
last_message = conversation.messages.last
|
||||
provider_service.unread_message(conversation.contact.phone_number, last_message) if last_message
|
||||
end
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
class Whatsapp::IncomingMessageBaileysService < Whatsapp::IncomingMessageBaseService # rubocop:disable Metrics/ClassLength
|
||||
include BaileysHelper
|
||||
|
||||
class InvalidWebhookVerifyToken < StandardError; end
|
||||
class MessageNotFoundError < StandardError; end
|
||||
class AttachmentNotFoundError < StandardError; end
|
||||
@ -171,9 +173,7 @@ class Whatsapp::IncomingMessageBaileysService < Whatsapp::IncomingMessageBaseSer
|
||||
end
|
||||
|
||||
def message_content_attributes
|
||||
content_attributes = {
|
||||
external_created_at: @raw_message[:messageTimestamp]
|
||||
}
|
||||
content_attributes = { external_created_at: extract_baileys_message_timestamp(@raw_message[:messageTimestamp]) }
|
||||
if message_type == 'reaction'
|
||||
content_attributes[:in_reply_to_external_id] = @raw_message.dig(:message, :reactionMessage, :key, :id)
|
||||
content_attributes[:is_reaction] = true
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseService # rubocop:disable Metrics/ClassLength
|
||||
include BaileysHelper
|
||||
|
||||
class MessageContentTypeNotSupported < StandardError; end
|
||||
class MessageNotSentError < StandardError; end
|
||||
|
||||
@ -139,14 +141,14 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
||||
jid: remote_jid,
|
||||
mod: {
|
||||
markRead: false,
|
||||
lastMessages: {
|
||||
lastMessages: [{
|
||||
key: {
|
||||
id: message.source_id,
|
||||
remoteJid: remote_jid,
|
||||
fromMe: message.message_type == 'outgoing'
|
||||
},
|
||||
messageTimestamp: message.content_attributes[:external_created_at]
|
||||
}
|
||||
}]
|
||||
}
|
||||
}.to_json
|
||||
)
|
||||
@ -209,9 +211,10 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
||||
}.to_json
|
||||
)
|
||||
|
||||
return response.parsed_response.dig('data', 'key', 'id') if process_response(response)
|
||||
raise MessageNotSentError unless process_response(response)
|
||||
|
||||
raise MessageNotSentError
|
||||
update_external_created_at(response)
|
||||
response.parsed_response.dig('data', 'key', 'id')
|
||||
end
|
||||
|
||||
def process_response(response)
|
||||
@ -223,6 +226,14 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
||||
"#{@phone_number.delete('+')}@s.whatsapp.net"
|
||||
end
|
||||
|
||||
def update_external_created_at(response)
|
||||
timestamp = response.parsed_response.dig('data', 'messageTimestamp')
|
||||
return unless timestamp
|
||||
|
||||
external_created_at = extract_baileys_message_timestamp(timestamp)
|
||||
@message.update!(external_created_at: external_created_at)
|
||||
end
|
||||
|
||||
private_class_method def self.with_error_handling(*method_names)
|
||||
method_names.each do |method_name|
|
||||
original_method = instance_method(method_name)
|
||||
|
||||
20
spec/helpers/baileys_helper_spec.rb
Normal file
20
spec/helpers/baileys_helper_spec.rb
Normal file
@ -0,0 +1,20 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe BaileysHelper do
|
||||
let(:timestamp) { 1_748_003_165 }
|
||||
let(:timestamp_hash) { { 'low' => timestamp, 'high' => 123, 'unsigned' => true } }
|
||||
|
||||
it 'extracts the timestamp from a string' do
|
||||
expect(extract_baileys_message_timestamp(timestamp.to_s)).to eq(timestamp)
|
||||
end
|
||||
|
||||
it 'extracts the timestamp from an int' do
|
||||
expect(extract_baileys_message_timestamp(timestamp)).to eq(timestamp)
|
||||
end
|
||||
|
||||
it 'extracts the timestamp from a hash' do
|
||||
expected_timestamp = timestamp + (timestamp_hash['high'] << 32)
|
||||
|
||||
expect(extract_baileys_message_timestamp(timestamp_hash)).to eq(expected_timestamp)
|
||||
end
|
||||
end
|
||||
@ -155,7 +155,7 @@ RSpec.describe Channel::Whatsapp do
|
||||
let(:conversation) { create(:conversation) }
|
||||
|
||||
it 'calls provider service method' do
|
||||
message = create(:message, conversation: conversation)
|
||||
message = create(:message, conversation: conversation, message_type: 'incoming')
|
||||
provider_double = instance_double(Whatsapp::Providers::WhatsappBaileysService, unread_message: nil)
|
||||
allow(Whatsapp::Providers::WhatsappBaileysService).to receive(:new).with(whatsapp_channel: channel).and_return(provider_double)
|
||||
allow(provider_double).to receive(:unread_message).with(conversation.contact.phone_number, [message])
|
||||
@ -167,7 +167,7 @@ RSpec.describe Channel::Whatsapp do
|
||||
|
||||
it 'does not call method if provider service does not implement it' do
|
||||
# NOTE: This message ensures that there are messages but the provider does not implement the method.
|
||||
create(:message, conversation: conversation)
|
||||
create(:message, conversation: conversation, message_type: 'incoming')
|
||||
|
||||
provider_double = instance_double(Whatsapp::Providers::WhatsappBaileysService)
|
||||
allow(Whatsapp::Providers::WhatsappBaileysService).to receive(:new).with(whatsapp_channel: channel).and_return(provider_double)
|
||||
|
||||
@ -95,6 +95,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
|
||||
describe '#send_message' do
|
||||
let(:request_path) { "#{whatsapp_channel.provider_config['provider_url']}/connections/#{whatsapp_channel.phone_number}/send-message" }
|
||||
let(:result_body) { { 'data' => { 'key' => { 'id' => 'msg_123' } } } }
|
||||
|
||||
context 'when message is unsupported' do
|
||||
it 'updates the message with content attribute is_unsupported' do
|
||||
@ -132,7 +133,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
.to_return(
|
||||
status: 200,
|
||||
headers: { 'Content-Type' => 'application/json' },
|
||||
body: { 'data' => { 'key' => { 'id' => 'msg_123' } } }.to_json
|
||||
body: result_body.to_json
|
||||
)
|
||||
|
||||
result = service.send_message(test_send_phone_number, message)
|
||||
@ -153,7 +154,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
.to_return(
|
||||
status: 200,
|
||||
headers: { 'Content-Type' => 'application/json' },
|
||||
body: { 'data' => { 'key' => { 'id' => 'msg_123' } } }.to_json
|
||||
body: result_body.to_json
|
||||
)
|
||||
|
||||
result = service.send_message(test_send_phone_number, message)
|
||||
@ -188,7 +189,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
.to_return(
|
||||
status: 200,
|
||||
headers: { 'Content-Type' => 'application/json' },
|
||||
body: { 'data' => { 'key' => { 'id' => 'msg_123' } } }.to_json
|
||||
body: result_body.to_json
|
||||
)
|
||||
|
||||
result = service.send_message(test_send_phone_number, message)
|
||||
@ -210,7 +211,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
.to_return(
|
||||
status: 200,
|
||||
headers: { 'Content-Type' => 'application/json' },
|
||||
body: { 'data' => { 'key' => { 'id' => 'msg_123' } } }.to_json
|
||||
body: result_body.to_json
|
||||
)
|
||||
|
||||
result = service.send_message(test_send_phone_number, message)
|
||||
@ -222,16 +223,43 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
context 'when message is a text' do
|
||||
it 'sends the message' do
|
||||
stub_request(:post, request_path)
|
||||
.with(
|
||||
headers: stub_headers(whatsapp_channel),
|
||||
body: {
|
||||
jid: test_send_jid,
|
||||
messageContent: { text: message.content }
|
||||
}.to_json
|
||||
)
|
||||
.to_return(
|
||||
status: 200,
|
||||
headers: { 'Content-Type' => 'application/json' },
|
||||
body: { 'data' => { 'key' => { 'id' => 'msg_123' } } }.to_json
|
||||
body: result_body.to_json
|
||||
)
|
||||
|
||||
result = service.send_message(test_send_phone_number, message)
|
||||
|
||||
expect(result).to eq('msg_123')
|
||||
end
|
||||
|
||||
it 'updates the message external_created_at' do
|
||||
stub_request(:post, request_path)
|
||||
.with(
|
||||
headers: stub_headers(whatsapp_channel),
|
||||
body: {
|
||||
jid: test_send_jid,
|
||||
messageContent: { text: message.content }
|
||||
}.to_json
|
||||
)
|
||||
.to_return(
|
||||
status: 200,
|
||||
headers: { 'Content-Type' => 'application/json' },
|
||||
body: { 'data' => { 'key' => { 'id' => 'msg_123' }, 'messageTimestamp' => 1_748_003_165 } }.to_json
|
||||
)
|
||||
|
||||
service.send_message(test_send_phone_number, message)
|
||||
|
||||
expect(message.reload.content_attributes['external_created_at']).to eq(1_748_003_165)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when message is a reaction' do
|
||||
@ -302,7 +330,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
.to_return(
|
||||
status: 400,
|
||||
headers: { 'Content-Type' => 'application/json' },
|
||||
body: { 'data' => { 'key' => { 'id' => 'msg_123' } } }.to_json
|
||||
body: result_body.to_json
|
||||
)
|
||||
|
||||
expect do
|
||||
@ -394,10 +422,12 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
||||
jid: test_send_jid,
|
||||
mod: {
|
||||
markRead: false,
|
||||
lastMessages: {
|
||||
key: { id: 'msg_123', remoteJid: test_send_jid, fromMe: false },
|
||||
messageTimestamp: 123
|
||||
}
|
||||
lastMessages: [
|
||||
{
|
||||
key: { id: 'msg_123', remoteJid: test_send_jid, fromMe: false },
|
||||
messageTimestamp: 123
|
||||
}
|
||||
]
|
||||
}
|
||||
}.to_json
|
||||
).to_return(status: 200)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user