feat: support reply to for outgoing message in WhatsApp (#8107)

- This PR enables replies to WhatsApp.
This commit is contained in:
Shivam Mishra
2023-10-20 01:54:46 +05:30
committed by GitHub
parent b94c89ebf1
commit 7416bbb25e
8 changed files with 95 additions and 29 deletions

View File

@@ -964,6 +964,19 @@ export default {
(item, index) => itemIndex !== index
);
},
setReplyToInPayload(payload) {
if (this.inReplyTo?.id) {
return {
...payload,
contentAttributes: {
...payload.contentAttributes,
in_reply_to: this.inReplyTo.id,
},
};
}
return payload;
},
getMessagePayloadForWhatsapp(message) {
const multipleMessagePayload = [];
@@ -973,41 +986,41 @@ export default {
const attachedFile = this.globalConfig.directUploadsEnabled
? attachment.blobSignedId
: attachment.resource.file;
const attachmentPayload = {
let attachmentPayload = {
conversationId: this.currentChat.id,
files: [attachedFile],
private: false,
message: caption,
sender: this.sender,
};
attachmentPayload = this.setReplyToInPayload(attachmentPayload);
multipleMessagePayload.push(attachmentPayload);
caption = '';
});
} else {
const messagePayload = {
let messagePayload = {
conversationId: this.currentChat.id,
message,
private: false,
sender: this.sender,
};
messagePayload = this.setReplyToInPayload(messagePayload);
multipleMessagePayload.push(messagePayload);
}
return multipleMessagePayload;
},
getMessagePayload(message) {
const messagePayload = {
let messagePayload = {
conversationId: this.currentChat.id,
message,
private: this.isPrivate,
sender: this.sender,
};
if (this.inReplyTo?.id) {
messagePayload.contentAttributes = {
in_reply_to: this.inReplyTo.id,
};
}
messagePayload = this.setReplyToInPayload(messagePayload);
if (this.attachedFiles && this.attachedFiles.length) {
messagePayload.files = [];

View File

@@ -1,10 +1,11 @@
<template>
<div
class="px-2 py-1.5 -mx-2 rounded-sm min-w-[15rem] mb-2"
class="px-2 py-1.5 rounded-sm min-w-[10rem] mb-2"
:class="{
'bg-slate-100 dark:bg-slate-600 dark:text-slate-50':
messageType === MESSAGE_TYPE.INCOMING,
'bg-woot-600 text-woot-50': messageType === MESSAGE_TYPE.OUTGOING,
'-mx-2': message.content,
}"
>
<message-preview

View File

@@ -239,13 +239,11 @@ class Message < ApplicationRecord
in_reply_to = content_attributes[:in_reply_to]
in_reply_to_external_id = content_attributes[:in_reply_to_external_id]
if in_reply_to.present? && in_reply_to_external_id.blank?
message = conversation.messages.find_by(id: in_reply_to)
content_attributes[:in_reply_to_external_id] = message.try(:source_id)
elsif in_reply_to_external_id.present? && in_reply_to.blank?
message = conversation.messages.find_by(source_id: in_reply_to_external_id)
content_attributes[:in_reply_to] = message.try(:id)
end
Messages::InReplyToMessageBuilder.new(
message: self,
in_reply_to: in_reply_to,
in_reply_to_external_id: in_reply_to_external_id
).perform
end
def ensure_content_type

View File

@@ -0,0 +1,24 @@
class Messages::InReplyToMessageBuilder
pattr_initialize [:message!, :in_reply_to!, :in_reply_to_external_id!]
delegate :conversation, to: :message
def perform
set_in_reply_to_attribute if @in_reply_to.present? || @in_reply_to_external_id.present?
end
private
def set_in_reply_to_attribute
@message.content_attributes[:in_reply_to_external_id] = in_reply_to_message.try(:source_id)
@message.content_attributes[:in_reply_to] = in_reply_to_message.try(:id)
end
def in_reply_to_message
return conversation.messages.find_by(id: @in_reply_to) if @in_reply_to.present?
return conversation.messages.find_by(source_id: @in_reply_to_external_id) if @in_reply_to_external_id
nil
end
end

View File

@@ -144,8 +144,7 @@ class Whatsapp::IncomingMessageBaseService
message_type: :incoming,
sender: @contact,
source_id: message[:id].to_s,
in_reply_to_external_id: @in_reply_to_external_id,
in_reply_to: @in_reply_to
in_reply_to_external_id: @in_reply_to_external_id
)
end

View File

@@ -88,15 +88,7 @@ module Whatsapp::IncomingMessageServiceHelpers
end
def process_in_reply_to(message)
return if message['context'].blank?
@in_reply_to_external_id = message['context']['id']
return if @in_reply_to_external_id.blank?
in_reply_to_message = Message.find_by(source_id: @in_reply_to_external_id)
@in_reply_to = in_reply_to_message.id if in_reply_to_message.present?
@in_reply_to_external_id = message['context']&.[]('id')
end
def find_message_by_source_id(source_id)

View File

@@ -78,6 +78,7 @@ class Whatsapp::Providers::WhatsappCloudService < Whatsapp::Providers::BaseServi
headers: api_headers,
body: {
messaging_product: 'whatsapp',
context: whatsapp_reply_context(message),
to: phone_number,
text: { body: message.content },
type: 'text'
@@ -100,6 +101,7 @@ class Whatsapp::Providers::WhatsappCloudService < Whatsapp::Providers::BaseServi
headers: api_headers,
body: {
:messaging_product => 'whatsapp',
:context => whatsapp_reply_context(message),
'to' => phone_number,
'type' => type,
type.to_s => type_content
@@ -132,6 +134,15 @@ class Whatsapp::Providers::WhatsappCloudService < Whatsapp::Providers::BaseServi
}
end
def whatsapp_reply_context(message)
reply_to = message.content_attributes[:in_reply_to_external_id]
return nil if reply_to.blank?
{
message_id: reply_to
}
end
def send_interactive_text_message(phone_number, message)
payload = create_payload_based_on_items(message)

View File

@@ -3,8 +3,18 @@ require 'rails_helper'
describe Whatsapp::Providers::WhatsappCloudService do
subject(:service) { described_class.new(whatsapp_channel: whatsapp_channel) }
let(:conversation) { create(:conversation, inbox: whatsapp_channel.inbox) }
let(:whatsapp_channel) { create(:channel_whatsapp, provider: 'whatsapp_cloud', validate_provider_config: false, sync_templates: false) }
let(:message) { create(:message, message_type: :outgoing, content: 'test', inbox: whatsapp_channel.inbox) }
let(:message) do
create(:message, conversation: conversation, message_type: :outgoing, content: 'test', inbox: whatsapp_channel.inbox, source_id: 'external_id')
end
let(:message_with_reply) do
create(:message, conversation: conversation, message_type: :outgoing, content: 'reply', inbox: whatsapp_channel.inbox,
content_attributes: { in_reply_to: message.id })
end
let(:response_headers) { { 'Content-Type' => 'application/json' } }
let(:whatsapp_response) { { messages: [{ id: 'message_id' }] } }
@@ -19,6 +29,7 @@ describe Whatsapp::Providers::WhatsappCloudService do
.with(
body: {
messaging_product: 'whatsapp',
context: nil,
to: '+123456789',
text: { body: message.content },
type: 'text'
@@ -28,6 +39,23 @@ describe Whatsapp::Providers::WhatsappCloudService do
expect(service.send_message('+123456789', message)).to eq 'message_id'
end
it 'calls message endpoints for a reply to messages' do
stub_request(:post, 'https://graph.facebook.com/v13.0/123456789/messages')
.with(
body: {
messaging_product: 'whatsapp',
context: {
message_id: message.source_id
},
to: '+123456789',
text: { body: message_with_reply.content },
type: 'text'
}.to_json
)
.to_return(status: 200, body: whatsapp_response.to_json, headers: response_headers)
expect(service.send_message('+123456789', message_with_reply)).to eq 'message_id'
end
it 'calls message endpoints for image attachment message messages' do
attachment = message.attachments.new(account_id: message.account_id, file_type: :image)
attachment.file.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'avatar.png', content_type: 'image/png')