fix: Handle Facebook reel attachment type (#13691)
Fixes https://linear.app/chatwoot/issue/PLA-96/argumenterror-reel-is-not-a-valid-file-type-argumenterror Fixes a crash in `when a user shares a Facebook reel via Messenger. Facebook sends these attachments with `type: "reel"`, which isn't a valid `file_type` enum value on the Attachment model (only `ig_reel` exists, matching Instagram's format). --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Sojan Jose <sojan@pepalo.com>
This commit is contained in:
@@ -2,12 +2,17 @@ class Messages::Messenger::MessageBuilder
|
|||||||
include ::FileTypeHelper
|
include ::FileTypeHelper
|
||||||
|
|
||||||
def process_attachment(attachment)
|
def process_attachment(attachment)
|
||||||
# This check handles very rare case if there are multiple files to attach with only one usupported file
|
# This check handles very rare case if there are multiple files to attach with only one unsupported file
|
||||||
return if unsupported_file_type?(attachment['type'])
|
return if unsupported_file_type?(attachment['type'])
|
||||||
|
|
||||||
attachment_obj = @message.attachments.new(attachment_params(attachment).except(:remote_file_url))
|
params = attachment_params(attachment)
|
||||||
|
attachment_obj = @message.attachments.new(params.except(:remote_file_url))
|
||||||
attachment_obj.save!
|
attachment_obj.save!
|
||||||
attach_file(attachment_obj, attachment_params(attachment)[:remote_file_url]) if attachment_params(attachment)[:remote_file_url]
|
if facebook_reel?(attachment)
|
||||||
|
update_facebook_reel_content(attachment)
|
||||||
|
elsif params[:remote_file_url]
|
||||||
|
attach_file(attachment_obj, params[:remote_file_url])
|
||||||
|
end
|
||||||
fetch_story_link(attachment_obj) if attachment_obj.file_type == 'story_mention'
|
fetch_story_link(attachment_obj) if attachment_obj.file_type == 'story_mention'
|
||||||
fetch_ig_story_link(attachment_obj) if attachment_obj.file_type == 'ig_story'
|
fetch_ig_story_link(attachment_obj) if attachment_obj.file_type == 'ig_story'
|
||||||
fetch_ig_post_link(attachment_obj) if attachment_obj.file_type == 'ig_post'
|
fetch_ig_post_link(attachment_obj) if attachment_obj.file_type == 'ig_post'
|
||||||
@@ -26,7 +31,7 @@ class Messages::Messenger::MessageBuilder
|
|||||||
end
|
end
|
||||||
|
|
||||||
def attachment_params(attachment)
|
def attachment_params(attachment)
|
||||||
file_type = attachment['type'].to_sym
|
file_type = normalize_file_type(attachment['type'])
|
||||||
params = { file_type: file_type, account_id: @message.account_id }
|
params = { file_type: file_type, account_id: @message.account_id }
|
||||||
|
|
||||||
if [:image, :file, :audio, :video, :share, :story_mention, :ig_reel, :ig_post, :ig_story].include? file_type
|
if [:image, :file, :audio, :video, :share, :story_mention, :ig_reel, :ig_post, :ig_story].include? file_type
|
||||||
@@ -100,6 +105,28 @@ class Messages::Messenger::MessageBuilder
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
# Facebook may send attachment types that don't directly match our file_type enum.
|
||||||
|
# Map known aliases to their canonical enum values.
|
||||||
|
FACEBOOK_FILE_TYPE_MAP = { reel: :ig_reel }.freeze
|
||||||
|
|
||||||
|
def normalize_file_type(type)
|
||||||
|
sym = type.to_sym
|
||||||
|
FACEBOOK_FILE_TYPE_MAP.fetch(sym, sym)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Facebook sends reel URLs as webpage links (facebook.com/reel/...) rather than
|
||||||
|
# direct video URLs. Downloading these yields HTML, not video content.
|
||||||
|
def facebook_reel?(attachment)
|
||||||
|
attachment['type'].to_sym == :reel
|
||||||
|
end
|
||||||
|
|
||||||
|
def update_facebook_reel_content(attachment)
|
||||||
|
url = attachment.dig('payload', 'url')
|
||||||
|
return if url.blank?
|
||||||
|
|
||||||
|
@message.update!(content: url) if @message.content.blank?
|
||||||
|
end
|
||||||
|
|
||||||
def unsupported_file_type?(attachment_type)
|
def unsupported_file_type?(attachment_type)
|
||||||
[:template, :unsupported_type, :ephemeral].include? attachment_type.to_sym
|
[:template, :unsupported_type, :ephemeral].include? attachment_type.to_sym
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ class Attachment < ApplicationRecord
|
|||||||
when :embed
|
when :embed
|
||||||
embed_data
|
embed_data
|
||||||
else
|
else
|
||||||
file_metadata
|
file.attached? ? file_metadata : { data_url: external_url, thumb_url: '' }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -89,6 +89,57 @@ describe Messages::Facebook::MessageBuilder do
|
|||||||
expect(message.content_attributes['external_echo']).to be true
|
expect(message.content_attributes['external_echo']).to be true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when message contains a reel attachment' do
|
||||||
|
let(:reel_message_object) do
|
||||||
|
{
|
||||||
|
messaging: {
|
||||||
|
sender: { id: '3383290475046708' },
|
||||||
|
recipient: { id: facebook_channel.page_id },
|
||||||
|
timestamp: 1_772_452_164_516,
|
||||||
|
message: {
|
||||||
|
mid: 'm_reel_test',
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
type: 'reel',
|
||||||
|
payload: {
|
||||||
|
url: 'https://www.facebook.com/reel/123456',
|
||||||
|
title: 'Test Reel Title',
|
||||||
|
reel_video_id: 123_456
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.to_json
|
||||||
|
end
|
||||||
|
let(:reel_message) { Integrations::Facebook::MessageParser.new(reel_message_object) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(Koala::Facebook::API).to receive(:new).and_return(fb_object)
|
||||||
|
allow(fb_object).to receive(:get_object).and_return(
|
||||||
|
{ first_name: 'Jane', last_name: 'Dae', profile_pic: 'https://chatwoot-assets.local/sample.png' }.with_indifferent_access
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates an ig_reel attachment without downloading the file' do
|
||||||
|
expect(Down).not_to receive(:download)
|
||||||
|
described_class.new(reel_message, facebook_channel.inbox).perform
|
||||||
|
|
||||||
|
message = facebook_channel.inbox.messages.find_by(source_id: 'm_reel_test')
|
||||||
|
expect(message).to be_present
|
||||||
|
expect(message.attachments.first.file_type).to eq('ig_reel')
|
||||||
|
expect(message.attachments.first.external_url).to eq('https://www.facebook.com/reel/123456')
|
||||||
|
expect(message.attachments.first.file.attached?).to be false
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sets the reel URL as message content' do
|
||||||
|
described_class.new(reel_message, facebook_channel.inbox).perform
|
||||||
|
|
||||||
|
message = facebook_channel.inbox.messages.find_by(source_id: 'm_reel_test')
|
||||||
|
expect(message.content).to eq('https://www.facebook.com/reel/123456')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when lock to single conversation' do
|
context 'when lock to single conversation' do
|
||||||
subject(:mocked_message_builder) do
|
subject(:mocked_message_builder) do
|
||||||
described_class.new(mocked_incoming_fb_text_message, facebook_channel.inbox).perform
|
described_class.new(mocked_incoming_fb_text_message, facebook_channel.inbox).perform
|
||||||
|
|||||||
@@ -155,6 +155,30 @@ RSpec.describe Attachment do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'push_event_data for ig_reel attachments' do
|
||||||
|
it 'returns external_url as data_url when no file is attached' do
|
||||||
|
attachment = message.attachments.create!(
|
||||||
|
account_id: message.account_id,
|
||||||
|
file_type: :ig_reel,
|
||||||
|
external_url: 'https://www.facebook.com/reel/123456'
|
||||||
|
)
|
||||||
|
|
||||||
|
event_data = attachment.push_event_data
|
||||||
|
expect(event_data[:data_url]).to eq('https://www.facebook.com/reel/123456')
|
||||||
|
expect(event_data[:thumb_url]).to eq('')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns file_url as data_url when file is attached' do
|
||||||
|
attachment = message.attachments.new(account_id: message.account_id, file_type: :ig_reel,
|
||||||
|
external_url: 'https://www.instagram.com/reel/123')
|
||||||
|
attachment.file.attach(io: Rails.root.join('spec/assets/avatar.png').open, filename: 'avatar.png', content_type: 'image/png')
|
||||||
|
attachment.save!
|
||||||
|
|
||||||
|
event_data = attachment.push_event_data
|
||||||
|
expect(event_data[:data_url]).to be_present
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'push_event_data for embed attachments' do
|
describe 'push_event_data for embed attachments' do
|
||||||
it 'returns external url as data_url' do
|
it 'returns external url as data_url' do
|
||||||
attachment = message.attachments.create!(account_id: message.account_id, file_type: :embed, external_url: 'https://example.com/embed')
|
attachment = message.attachments.create!(account_id: message.account_id, file_type: :embed, external_url: 'https://example.com/embed')
|
||||||
|
|||||||
Reference in New Issue
Block a user