feat: Facebook delivery reports (#8136)
This commit is contained in:
8
app/jobs/webhooks/facebook_delivery_job.rb
Normal file
8
app/jobs/webhooks/facebook_delivery_job.rb
Normal file
@@ -0,0 +1,8 @@
|
||||
class Webhooks::FacebookDeliveryJob < ApplicationJob
|
||||
queue_as :low
|
||||
|
||||
def perform(message)
|
||||
response = ::Integrations::Facebook::MessageParser.new(message)
|
||||
Integrations::Facebook::DeliveryStatus.new(params: response).perform
|
||||
end
|
||||
end
|
||||
@@ -29,14 +29,13 @@ Rails.application.reloader.to_prepare do
|
||||
end
|
||||
|
||||
Facebook::Messenger::Bot.on :delivery do |delivery|
|
||||
# delivery.ids # => 'mid.1457764197618:41d102a3e1ae206a38'
|
||||
# delivery.sender # => { 'id' => '1008372609250235' }
|
||||
# delivery.recipient # => { 'id' => '2015573629214912' }
|
||||
# delivery.at # => 2016-04-22 21:30:36 +0200
|
||||
# delivery.seq # => 37
|
||||
updater = Integrations::Facebook::DeliveryStatus.new(delivery)
|
||||
updater.perform
|
||||
Rails.logger.info "Human was online at #{delivery.at}"
|
||||
Rails.logger.info "Recieved delivery status #{delivery.to_json}"
|
||||
Webhooks::FacebookDeliveryJob.perform_later(delivery.to_json)
|
||||
end
|
||||
|
||||
Facebook::Messenger::Bot.on :read do |read|
|
||||
Rails.logger.info "Recieved read status #{read.to_json}"
|
||||
Webhooks::FacebookDeliveryJob.perform_later(read.to_json)
|
||||
end
|
||||
|
||||
Facebook::Messenger::Bot.on :message_echo do |message|
|
||||
|
||||
@@ -1,32 +1,37 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Integrations::Facebook::DeliveryStatus
|
||||
def initialize(params)
|
||||
@params = params
|
||||
end
|
||||
pattr_initialize [:params!]
|
||||
|
||||
def perform
|
||||
update_message_status
|
||||
return if facebook_channel.blank?
|
||||
return unless conversation
|
||||
|
||||
process_delivery_status if params.delivery_watermark
|
||||
process_read_status if params.read_watermark
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sender_id
|
||||
@params.sender['id']
|
||||
def process_delivery_status
|
||||
timestamp = Time.zone.at(params.delivery_watermark.to_i).to_datetime.utc
|
||||
::Conversations::UpdateMessageStatusJob.perform_later(conversation.id, timestamp, :delivered)
|
||||
end
|
||||
|
||||
def process_read_status
|
||||
timestamp = Time.zone.at(params.read_watermark.to_i).to_datetime.utc
|
||||
::Conversations::UpdateMessageStatusJob.perform_later(conversation.id, timestamp, :read)
|
||||
end
|
||||
|
||||
def contact
|
||||
::ContactInbox.find_by(source_id: sender_id)&.contact
|
||||
::ContactInbox.find_by(source_id: params.sender_id)&.contact
|
||||
end
|
||||
|
||||
def conversation
|
||||
@conversation ||= ::Conversation.find_by(contact_id: contact.id) if contact.present?
|
||||
end
|
||||
|
||||
def update_message_status
|
||||
return unless conversation
|
||||
|
||||
conversation.contact_last_seen_at = @params.at
|
||||
conversation.save!
|
||||
def facebook_channel
|
||||
@facebook_channel ||= Channel::FacebookPage.find_by(page_id: params.recipient_id)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -34,6 +34,22 @@ class Integrations::Facebook::MessageParser
|
||||
@messaging.dig('message', 'mid')
|
||||
end
|
||||
|
||||
def delivery
|
||||
@messaging['delivery']
|
||||
end
|
||||
|
||||
def read
|
||||
@messaging['read']
|
||||
end
|
||||
|
||||
def read_watermark
|
||||
read&.dig('watermark')
|
||||
end
|
||||
|
||||
def delivery_watermark
|
||||
delivery&.dig('watermark')
|
||||
end
|
||||
|
||||
def echo?
|
||||
@messaging.dig('message', 'is_echo')
|
||||
end
|
||||
|
||||
@@ -10,4 +10,24 @@ FactoryBot.define do
|
||||
|
||||
initialize_with { attributes }
|
||||
end
|
||||
|
||||
factory :message_deliveries, class: Hash do
|
||||
messaging do
|
||||
{ sender: { id: '3383290475046708' },
|
||||
recipient: { id: '117172741761305' },
|
||||
delivery: { watermark: '1648581633369' } }
|
||||
end
|
||||
|
||||
initialize_with { attributes }
|
||||
end
|
||||
|
||||
factory :message_reads, class: Hash do
|
||||
messaging do
|
||||
{ sender: { id: '3383290475046708' },
|
||||
recipient: { id: '117172741761305' },
|
||||
read: { watermark: '1648581633369' } }
|
||||
end
|
||||
|
||||
initialize_with { attributes }
|
||||
end
|
||||
end
|
||||
|
||||
44
spec/jobs/webhooks/facebook_delivery_job_spec.rb
Normal file
44
spec/jobs/webhooks/facebook_delivery_job_spec.rb
Normal file
@@ -0,0 +1,44 @@
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Webhooks::FacebookDeliveryJob do
|
||||
include ActiveJob::TestHelper
|
||||
|
||||
let(:message) { 'test_message' }
|
||||
let(:parsed_message) { instance_double(Integrations::Facebook::MessageParser) }
|
||||
let(:delivery_status) { instance_double(Integrations::Facebook::DeliveryStatus) }
|
||||
|
||||
before do
|
||||
allow(Integrations::Facebook::MessageParser).to receive(:new).with(message).and_return(parsed_message)
|
||||
allow(Integrations::Facebook::DeliveryStatus).to receive(:new).with(params: parsed_message).and_return(delivery_status)
|
||||
allow(delivery_status).to receive(:perform)
|
||||
end
|
||||
|
||||
after do
|
||||
clear_enqueued_jobs
|
||||
end
|
||||
|
||||
describe '#perform_later' do
|
||||
it 'enqueues the job' do
|
||||
expect do
|
||||
described_class.perform_later(message)
|
||||
end.to have_enqueued_job(described_class).with(message).on_queue('low')
|
||||
end
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
it 'calls the MessageParser with the correct argument' do
|
||||
expect(Integrations::Facebook::MessageParser).to receive(:new).with(message)
|
||||
described_class.perform_now(message)
|
||||
end
|
||||
|
||||
it 'calls the DeliveryStatus with the correct argument' do
|
||||
expect(Integrations::Facebook::DeliveryStatus).to receive(:new).with(params: parsed_message)
|
||||
described_class.perform_now(message)
|
||||
end
|
||||
|
||||
it 'executes perform on the DeliveryStatus instance' do
|
||||
expect(delivery_status).to receive(:perform)
|
||||
described_class.perform_now(message)
|
||||
end
|
||||
end
|
||||
end
|
||||
83
spec/lib/integrations/facebook/delivery_status_spec.rb
Normal file
83
spec/lib/integrations/facebook/delivery_status_spec.rb
Normal file
@@ -0,0 +1,83 @@
|
||||
require 'rails_helper'
|
||||
|
||||
describe Integrations::Facebook::DeliveryStatus do
|
||||
subject(:message_builder) { described_class.new(message_deliveries, facebook_channel.inbox).perform }
|
||||
|
||||
before do
|
||||
stub_request(:post, /graph\.facebook\.com/)
|
||||
end
|
||||
|
||||
let!(:account) { create(:account) }
|
||||
let!(:facebook_channel) { create(:channel_facebook_page, page_id: '117172741761305') }
|
||||
let!(:message_delivery_object) { build(:message_deliveries).to_json }
|
||||
let!(:message_deliveries) { Integrations::Facebook::MessageParser.new(message_delivery_object) }
|
||||
|
||||
let!(:contact) { create(:contact, account: account) }
|
||||
let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: facebook_channel.inbox, source_id: '3383290475046708') }
|
||||
let!(:conversation) { create(:conversation, inbox: facebook_channel.inbox, contact: contact, contact_inbox: contact_inbox) }
|
||||
|
||||
let!(:message_read_object) { build(:message_reads).to_json }
|
||||
let!(:message_reads) { Integrations::Facebook::MessageParser.new(message_read_object) }
|
||||
let!(:message1) do
|
||||
create(:message, content: 'facebook message', message_type: 'outgoing', inbox: facebook_channel.inbox, conversation: conversation)
|
||||
end
|
||||
let!(:message2) do
|
||||
create(:message, content: 'facebook message', message_type: 'incoming', inbox: facebook_channel.inbox, conversation: conversation)
|
||||
end
|
||||
|
||||
describe '#perform' do
|
||||
context 'when message_deliveries callback fires' do
|
||||
before do
|
||||
allow(Conversations::UpdateMessageStatusJob).to receive(:perform_later)
|
||||
end
|
||||
|
||||
it 'updates all messages if the status is delivered' do
|
||||
described_class.new(params: message_deliveries).perform
|
||||
expect(Conversations::UpdateMessageStatusJob).to have_received(:perform_later).with(
|
||||
message1.conversation.id,
|
||||
Time.zone.at(message_deliveries.delivery['watermark'].to_i).to_datetime,
|
||||
:delivered
|
||||
)
|
||||
end
|
||||
|
||||
it 'does not update the message status if the message is incoming' do
|
||||
described_class.new(params: message_deliveries).perform
|
||||
expect(message2.reload.status).to eq('sent')
|
||||
end
|
||||
|
||||
it 'does not update the message status if the message was created after the watermark' do
|
||||
message1.update(created_at: 1.day.from_now)
|
||||
message_deliveries.delivery['watermark'] = 1.day.ago.to_i
|
||||
described_class.new(params: message_deliveries).perform
|
||||
expect(message1.reload.status).to eq('sent')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when message_reads callback fires' do
|
||||
before do
|
||||
allow(Conversations::UpdateMessageStatusJob).to receive(:perform_later)
|
||||
end
|
||||
|
||||
it 'updates all messages if the status is read' do
|
||||
described_class.new(params: message_reads).perform
|
||||
expect(Conversations::UpdateMessageStatusJob).to have_received(:perform_later).with(
|
||||
message1.conversation.id,
|
||||
Time.zone.at(message_reads.read['watermark'].to_i).to_datetime,
|
||||
:read
|
||||
)
|
||||
end
|
||||
|
||||
it 'does not update the message status if the message is incoming' do
|
||||
described_class.new(params: message_reads).perform
|
||||
expect(message2.reload.status).to eq('sent')
|
||||
end
|
||||
|
||||
it 'does not update the message status if the message was created after the watermark' do
|
||||
message1.update(created_at: 1.day.from_now)
|
||||
message_reads.read['watermark'] = 1.day.ago.to_i
|
||||
described_class.new(params: message_reads).perform
|
||||
expect(message1.reload.status).to eq('sent')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user