feat: whatsapp cloud service typing status and read messages (#106)
This commit is contained in:
parent
dbb41df67e
commit
eaf2f99520
@ -84,7 +84,8 @@ class Channel::Whatsapp < ApplicationRecord
|
|||||||
def toggle_typing_status(typing_status, conversation:)
|
def toggle_typing_status(typing_status, conversation:)
|
||||||
return unless provider_service.respond_to?(:toggle_typing_status)
|
return unless provider_service.respond_to?(:toggle_typing_status)
|
||||||
|
|
||||||
provider_service.toggle_typing_status(conversation.contact.phone_number, typing_status)
|
last_message = conversation.messages.last
|
||||||
|
provider_service.toggle_typing_status(typing_status, last_message: last_message, phone_number: conversation.contact.phone_number)
|
||||||
end
|
end
|
||||||
|
|
||||||
def update_presence(status)
|
def update_presence(status)
|
||||||
@ -98,7 +99,7 @@ class Channel::Whatsapp < ApplicationRecord
|
|||||||
# NOTE: This is the default behavior, so `mark_as_read` being `nil` is the same as `true`.
|
# NOTE: This is the default behavior, so `mark_as_read` being `nil` is the same as `true`.
|
||||||
return if provider_config&.dig('mark_as_read') == false
|
return if provider_config&.dig('mark_as_read') == false
|
||||||
|
|
||||||
provider_service.read_messages(conversation.contact.phone_number, messages)
|
provider_service.read_messages(messages, phone_number: conversation.contact.phone_number)
|
||||||
end
|
end
|
||||||
|
|
||||||
def unread_conversation(conversation)
|
def unread_conversation(conversation)
|
||||||
|
|||||||
@ -94,7 +94,7 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
|||||||
process_response(response)
|
process_response(response)
|
||||||
end
|
end
|
||||||
|
|
||||||
def toggle_typing_status(phone_number, typing_status)
|
def toggle_typing_status(typing_status, phone_number:, **)
|
||||||
@phone_number = phone_number
|
@phone_number = phone_number
|
||||||
status_map = {
|
status_map = {
|
||||||
Events::Types::CONVERSATION_TYPING_ON => 'composing',
|
Events::Types::CONVERSATION_TYPING_ON => 'composing',
|
||||||
@ -136,7 +136,7 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
|||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
def read_messages(phone_number, messages)
|
def read_messages(messages, phone_number:, **)
|
||||||
@phone_number = phone_number
|
@phone_number = phone_number
|
||||||
|
|
||||||
response = HTTParty.post(
|
response = HTTParty.post(
|
||||||
@ -319,12 +319,12 @@ class Whatsapp::Providers::WhatsappBaileysService < Whatsapp::Providers::BaseSer
|
|||||||
method_names.each do |method_name|
|
method_names.each do |method_name|
|
||||||
original_method = instance_method(method_name)
|
original_method = instance_method(method_name)
|
||||||
|
|
||||||
define_method("#{method_name}_without_error_handling") do |*args, &block|
|
define_method("#{method_name}_without_error_handling") do |*args, **kwargs, &block|
|
||||||
original_method.bind_call(self, *args, &block)
|
original_method.bind_call(self, *args, **kwargs, &block)
|
||||||
end
|
end
|
||||||
|
|
||||||
define_method(method_name) do |*args, &block|
|
define_method(method_name) do |*args, **kwargs, &block|
|
||||||
original_method.bind_call(self, *args, &block)
|
original_method.bind_call(self, *args, **kwargs, &block)
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
handle_channel_error
|
handle_channel_error
|
||||||
raise e
|
raise e
|
||||||
|
|||||||
@ -71,8 +71,8 @@ class Whatsapp::Providers::WhatsappCloudService < Whatsapp::Providers::BaseServi
|
|||||||
end
|
end
|
||||||
|
|
||||||
# TODO: See if we can unify the API versions and for both paths and make it consistent with out facebook app API versions
|
# TODO: See if we can unify the API versions and for both paths and make it consistent with out facebook app API versions
|
||||||
def phone_id_path
|
def phone_id_path(version = 'v13.0')
|
||||||
"#{api_base_path}/v13.0/#{whatsapp_channel.provider_config['phone_number_id']}"
|
"#{api_base_path}/#{version}/#{whatsapp_channel.provider_config['phone_number_id']}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def business_account_path
|
def business_account_path
|
||||||
@ -181,4 +181,41 @@ class Whatsapp::Providers::WhatsappCloudService < Whatsapp::Providers::BaseServi
|
|||||||
|
|
||||||
process_response(response, message)
|
process_response(response, message)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def toggle_typing_status(typing_status, last_message:, **)
|
||||||
|
return false unless [Events::Types::CONVERSATION_TYPING_ON, Events::Types::CONVERSATION_RECORDING].include?(typing_status)
|
||||||
|
|
||||||
|
response = HTTParty.post(
|
||||||
|
"#{phone_id_path('v23.0')}/messages",
|
||||||
|
headers: api_headers,
|
||||||
|
body: {
|
||||||
|
messaging_product: 'whatsapp',
|
||||||
|
message_id: last_message.source_id,
|
||||||
|
# NOTE: API currently only supports "typing", no "recording" status.
|
||||||
|
typing_indicator: { type: 'text' }
|
||||||
|
}.to_json
|
||||||
|
)
|
||||||
|
|
||||||
|
Rails.logger.error(response.parsed_response) unless response.success?
|
||||||
|
|
||||||
|
response.success?
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_messages(messages, **)
|
||||||
|
# NOTE: Marking the last message as read automatically applies to all previous ones.
|
||||||
|
message = messages.last
|
||||||
|
response = HTTParty.post(
|
||||||
|
"#{phone_id_path('v23.0')}/messages",
|
||||||
|
headers: api_headers,
|
||||||
|
body: {
|
||||||
|
messaging_product: 'whatsapp',
|
||||||
|
message_id: message.source_id,
|
||||||
|
status: 'read'
|
||||||
|
}.to_json
|
||||||
|
)
|
||||||
|
|
||||||
|
Rails.logger.error(response.parsed_response) unless response.success?
|
||||||
|
|
||||||
|
response.success?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -186,7 +186,7 @@ RSpec.describe Channel::Whatsapp do
|
|||||||
it 'calls provider service method' do
|
it 'calls provider service method' do
|
||||||
provider_double = instance_double(Whatsapp::Providers::WhatsappBaileysService, toggle_typing_status: nil)
|
provider_double = instance_double(Whatsapp::Providers::WhatsappBaileysService, toggle_typing_status: nil)
|
||||||
allow(provider_double).to receive(:toggle_typing_status)
|
allow(provider_double).to receive(:toggle_typing_status)
|
||||||
.with(conversation.contact.phone_number, Events::Types::CONVERSATION_TYPING_ON)
|
.with(Events::Types::CONVERSATION_TYPING_ON, phone_number: conversation.contact.phone_number)
|
||||||
allow(Whatsapp::Providers::WhatsappBaileysService).to receive(:new)
|
allow(Whatsapp::Providers::WhatsappBaileysService).to receive(:new)
|
||||||
.with(whatsapp_channel: channel)
|
.with(whatsapp_channel: channel)
|
||||||
.and_return(provider_double)
|
.and_return(provider_double)
|
||||||
@ -246,7 +246,7 @@ RSpec.describe Channel::Whatsapp do
|
|||||||
|
|
||||||
it 'calls provider service method' do
|
it 'calls provider service method' do
|
||||||
provider_double = instance_double(Whatsapp::Providers::WhatsappBaileysService, read_messages: nil)
|
provider_double = instance_double(Whatsapp::Providers::WhatsappBaileysService, read_messages: nil)
|
||||||
allow(provider_double).to receive(:read_messages).with([message], conversation.contact.phone_number)
|
allow(provider_double).to receive(:read_messages).with([message], phone_number: conversation.contact.phone_number)
|
||||||
allow(Whatsapp::Providers::WhatsappBaileysService).to receive(:new)
|
allow(Whatsapp::Providers::WhatsappBaileysService).to receive(:new)
|
||||||
.with(whatsapp_channel: channel)
|
.with(whatsapp_channel: channel)
|
||||||
.and_return(provider_double)
|
.and_return(provider_double)
|
||||||
@ -259,7 +259,7 @@ RSpec.describe Channel::Whatsapp do
|
|||||||
it 'call method when the provider config mark_as_read is nil' do
|
it 'call method when the provider config mark_as_read is nil' do
|
||||||
channel.update!(provider_config: {})
|
channel.update!(provider_config: {})
|
||||||
provider_double = instance_double(Whatsapp::Providers::WhatsappBaileysService, read_messages: nil)
|
provider_double = instance_double(Whatsapp::Providers::WhatsappBaileysService, read_messages: nil)
|
||||||
allow(provider_double).to receive(:read_messages).with([message], conversation.contact.phone_number)
|
allow(provider_double).to receive(:read_messages).with([message], phone_number: conversation.contact.phone_number)
|
||||||
allow(Whatsapp::Providers::WhatsappBaileysService).to receive(:new)
|
allow(Whatsapp::Providers::WhatsappBaileysService).to receive(:new)
|
||||||
.with(whatsapp_channel: channel)
|
.with(whatsapp_channel: channel)
|
||||||
.and_return(provider_double)
|
.and_return(provider_double)
|
||||||
@ -270,7 +270,7 @@ RSpec.describe Channel::Whatsapp do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'does not call method if provider service does not implement it' do
|
it 'does not call method if provider service does not implement it' do
|
||||||
channel.update!(provider: 'whatsapp_cloud')
|
channel.update!(provider: 'default')
|
||||||
|
|
||||||
expect do
|
expect do
|
||||||
channel.read_messages([message], conversation: conversation)
|
channel.read_messages([message], conversation: conversation)
|
||||||
|
|||||||
@ -477,7 +477,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
|||||||
body: { keys: [{ id: message.source_id, remoteJid: test_send_jid, fromMe: false }] }.to_json
|
body: { keys: [{ id: message.source_id, remoteJid: test_send_jid, fromMe: false }] }.to_json
|
||||||
).to_return(status: 200, body: '', headers: {})
|
).to_return(status: 200, body: '', headers: {})
|
||||||
|
|
||||||
result = service.read_messages(test_send_phone_number, [message])
|
result = service.read_messages([message], phone_number: test_send_phone_number)
|
||||||
|
|
||||||
expect(result).to be(true)
|
expect(result).to be(true)
|
||||||
end
|
end
|
||||||
@ -495,7 +495,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
|||||||
.to_return(status: 200)
|
.to_return(status: 200)
|
||||||
|
|
||||||
expect do
|
expect do
|
||||||
service.read_messages(test_send_phone_number, [message])
|
service.read_messages([message], phone_number: test_send_phone_number)
|
||||||
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
|
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -605,7 +605,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
|||||||
)
|
)
|
||||||
.to_return(status: 200)
|
.to_return(status: 200)
|
||||||
|
|
||||||
service.toggle_typing_status(test_send_phone_number, Events::Types::CONVERSATION_TYPING_ON)
|
service.toggle_typing_status(Events::Types::CONVERSATION_TYPING_ON, phone_number: test_send_phone_number)
|
||||||
|
|
||||||
expect(request).to have_been_requested
|
expect(request).to have_been_requested
|
||||||
end
|
end
|
||||||
@ -621,7 +621,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
|||||||
)
|
)
|
||||||
.to_return(status: 200)
|
.to_return(status: 200)
|
||||||
|
|
||||||
service.toggle_typing_status(test_send_phone_number, Events::Types::CONVERSATION_RECORDING)
|
service.toggle_typing_status(Events::Types::CONVERSATION_RECORDING, phone_number: test_send_phone_number)
|
||||||
|
|
||||||
expect(request).to have_been_requested
|
expect(request).to have_been_requested
|
||||||
end
|
end
|
||||||
@ -637,7 +637,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
|||||||
)
|
)
|
||||||
.to_return(status: 200)
|
.to_return(status: 200)
|
||||||
|
|
||||||
service.toggle_typing_status(test_send_phone_number, Events::Types::CONVERSATION_TYPING_OFF)
|
service.toggle_typing_status(Events::Types::CONVERSATION_TYPING_OFF, phone_number: test_send_phone_number)
|
||||||
|
|
||||||
expect(request).to have_been_requested
|
expect(request).to have_been_requested
|
||||||
end
|
end
|
||||||
@ -664,7 +664,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
|||||||
allow(Rails.logger).to receive(:error)
|
allow(Rails.logger).to receive(:error)
|
||||||
|
|
||||||
expect do
|
expect do
|
||||||
service.toggle_typing_status(test_send_phone_number, Events::Types::CONVERSATION_TYPING_ON)
|
service.toggle_typing_status(Events::Types::CONVERSATION_TYPING_ON, phone_number: test_send_phone_number)
|
||||||
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
|
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
|
||||||
|
|
||||||
expect(Rails.logger).to have_received(:error).with('error message')
|
expect(Rails.logger).to have_received(:error).with('error message')
|
||||||
@ -916,7 +916,7 @@ describe Whatsapp::Providers::WhatsappBaileysService do
|
|||||||
whatsapp_channel.update!(provider_connection: { 'connection' => 'open' })
|
whatsapp_channel.update!(provider_connection: { 'connection' => 'open' })
|
||||||
|
|
||||||
expect do
|
expect do
|
||||||
service.toggle_typing_status(test_send_phone_number, Events::Types::CONVERSATION_TYPING_ON)
|
service.toggle_typing_status(Events::Types::CONVERSATION_TYPING_ON, phone_number: test_send_phone_number)
|
||||||
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
|
end.to raise_error(Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError)
|
||||||
|
|
||||||
expect(whatsapp_channel.reload.provider_connection['connection']).to eq('close')
|
expect(whatsapp_channel.reload.provider_connection['connection']).to eq('close')
|
||||||
|
|||||||
@ -312,4 +312,91 @@ describe Whatsapp::Providers::WhatsappCloudService do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#toggle_typing_status' do
|
||||||
|
let(:conversation) { create(:conversation) }
|
||||||
|
|
||||||
|
it 'calls messages endpoint with typing indicator for "conversation.typing_on"' do
|
||||||
|
stub_request(:post, 'https://graph.facebook.com/v23.0/123456789/messages')
|
||||||
|
.with(
|
||||||
|
body: {
|
||||||
|
messaging_product: 'whatsapp',
|
||||||
|
message_id: message.source_id,
|
||||||
|
typing_indicator: { type: 'text' }
|
||||||
|
}.to_json
|
||||||
|
)
|
||||||
|
.to_return(status: 200, body: { success: true }.to_json, headers: response_headers)
|
||||||
|
|
||||||
|
expect(service.toggle_typing_status(Events::Types::CONVERSATION_TYPING_ON, last_message: message)).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'calls messages endpoint with typing indicator for "conversation.recording"' do
|
||||||
|
stub_request(:post, 'https://graph.facebook.com/v23.0/123456789/messages')
|
||||||
|
.with(
|
||||||
|
body: {
|
||||||
|
messaging_product: 'whatsapp',
|
||||||
|
message_id: message.source_id,
|
||||||
|
typing_indicator: { type: 'text' }
|
||||||
|
}.to_json
|
||||||
|
)
|
||||||
|
.to_return(status: 200, body: { success: true }.to_json, headers: response_headers)
|
||||||
|
|
||||||
|
expect(service.toggle_typing_status(Events::Types::CONVERSATION_RECORDING, last_message: message)).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not call messages endpoint with typing indicator for "conversation.typing_off"' do
|
||||||
|
expect(service.toggle_typing_status(Events::Types::CONVERSATION_TYPING_OFF, last_message: message)).to be(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'logs error on failure' do
|
||||||
|
allow(Rails.logger).to receive(:error).with('Request failed')
|
||||||
|
stub_request(:post, 'https://graph.facebook.com/v23.0/123456789/messages')
|
||||||
|
.with(
|
||||||
|
body: {
|
||||||
|
messaging_product: 'whatsapp',
|
||||||
|
message_id: message.source_id,
|
||||||
|
typing_indicator: { type: 'text' }
|
||||||
|
}.to_json
|
||||||
|
)
|
||||||
|
.to_return(status: 500, body: 'Request failed')
|
||||||
|
|
||||||
|
service.toggle_typing_status(Events::Types::CONVERSATION_TYPING_ON, last_message: message)
|
||||||
|
|
||||||
|
expect(Rails.logger).to have_received(:error)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#read_messages' do
|
||||||
|
it 'calls messages endpoint to mark last message as read' do
|
||||||
|
stub_request(:post, 'https://graph.facebook.com/v23.0/123456789/messages')
|
||||||
|
.with(
|
||||||
|
body: {
|
||||||
|
messaging_product: 'whatsapp',
|
||||||
|
message_id: message.source_id,
|
||||||
|
status: 'read'
|
||||||
|
}.to_json
|
||||||
|
)
|
||||||
|
.to_return(status: 200, body: { success: true }.to_json, headers: response_headers)
|
||||||
|
|
||||||
|
messages = [create(:message), message]
|
||||||
|
expect(service.read_messages(messages)).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'logs error on failure' do
|
||||||
|
allow(Rails.logger).to receive(:error).with('Request failed')
|
||||||
|
stub_request(:post, 'https://graph.facebook.com/v23.0/123456789/messages')
|
||||||
|
.with(
|
||||||
|
body: {
|
||||||
|
messaging_product: 'whatsapp',
|
||||||
|
message_id: message.source_id,
|
||||||
|
status: 'read'
|
||||||
|
}.to_json
|
||||||
|
)
|
||||||
|
.to_return(status: 500, body: 'Request failed')
|
||||||
|
|
||||||
|
service.read_messages([message])
|
||||||
|
|
||||||
|
expect(Rails.logger).to have_received(:error)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user