diff --git a/app/controllers/twilio/callback_controller.rb b/app/controllers/twilio/callback_controller.rb index ff5db952d..ff16e9386 100644 --- a/app/controllers/twilio/callback_controller.rb +++ b/app/controllers/twilio/callback_controller.rb @@ -24,9 +24,10 @@ class Twilio::CallbackController < ApplicationController :Body, :ToCountry, :FromState, - :MediaUrl0, - :MediaContentType0, - :MessagingServiceSid + *Array.new(10) { |i| :"MediaUrl#{i}" }, + *Array.new(10) { |i| :"MediaContentType#{i}" }, + :MessagingServiceSid, + :NumMedia ) end end diff --git a/app/services/twilio/incoming_message_service.rb b/app/services/twilio/incoming_message_service.rb index 5c5854ee6..7a335c87d 100644 --- a/app/services/twilio/incoming_message_service.rb +++ b/app/services/twilio/incoming_message_service.rb @@ -106,15 +106,22 @@ class Twilio::IncomingMessageService end def attach_files - return if params[:MediaUrl0].blank? + num_media = params[:NumMedia].to_i + return if num_media.zero? - attachment_file = download_attachment_file + num_media.times do |i| + media_url = params[:"MediaUrl#{i}"] + attach_single_file(media_url) if media_url.present? + end + end + def attach_single_file(media_url) + attachment_file = download_attachment_file(media_url) return if attachment_file.blank? @message.attachments.new( account_id: @message.account_id, - file_type: file_type(params[:MediaContentType0]), + file_type: file_type(attachment_file.content_type), file: { io: attachment_file, filename: attachment_file.original_filename, @@ -123,24 +130,22 @@ class Twilio::IncomingMessageService ) end - def download_attachment_file - download_with_auth + def download_attachment_file(media_url) + download_with_auth(media_url) rescue Down::Error, Down::ClientError => e - handle_download_attachment_error(e) + handle_download_attachment_error(e, media_url) end - def download_with_auth + def download_with_auth(media_url) Down.download( - params[:MediaUrl0], - # https://support.twilio.com/hc/en-us/articles/223183748-Protect-Media-Access-with-HTTP-Basic-Authentication-for-Programmable-Messaging + media_url, http_basic_authentication: [twilio_channel.account_sid, twilio_channel.auth_token || twilio_channel.api_key_sid] ) end - # This is just a temporary workaround since some users have not yet enabled media protection. We will remove this in the future. - def handle_download_attachment_error(error) + def handle_download_attachment_error(error, media_url) Rails.logger.info "Error downloading attachment from Twilio: #{error.message}: Retrying" - Down.download(params[:MediaUrl0]) + Down.download(media_url) rescue StandardError => e Rails.logger.info "Error downloading attachment from Twilio: #{e.message}: Skipping" nil diff --git a/spec/services/twilio/incoming_message_service_spec.rb b/spec/services/twilio/incoming_message_service_spec.rb index 5f4dcec69..c8812e45d 100644 --- a/spec/services/twilio/incoming_message_service_spec.rb +++ b/spec/services/twilio/incoming_message_service_spec.rb @@ -173,7 +173,7 @@ describe Twilio::IncomingMessageService do context 'when a message with an attachment is received' do before do stub_request(:get, 'https://chatwoot-assets.local/sample.png') - .to_return(status: 200, body: 'image data', headers: {}) + .to_return(status: 200, body: 'image data', headers: { 'Content-Type' => 'image/png' }) end let(:params_with_attachment) do @@ -203,7 +203,7 @@ describe Twilio::IncomingMessageService do .to_raise(Down::Error.new('Download error')) stub_request(:get, 'https://chatwoot-assets.local/sample.png') - .to_return(status: 200, body: 'image data', headers: {}) + .to_return(status: 200, body: 'image data', headers: { 'Content-Type' => 'image/png' }) end let(:params_with_attachment_error) do @@ -229,5 +229,36 @@ describe Twilio::IncomingMessageService do expect(conversation.reload.messages.last.attachments.first.file_type).to eq('image') end end + + context 'when a message with multiple attachments is received' do + before do + stub_request(:get, 'https://chatwoot-assets.local/sample.png') + .to_return(status: 200, body: 'image data 1', headers: { 'Content-Type' => 'image/png' }) + stub_request(:get, 'https://chatwoot-assets.local/sample.jpg') + .to_return(status: 200, body: 'image data 2', headers: { 'Content-Type' => 'image/jpeg' }) + end + + let(:params_with_multiple_attachments) do + { + SmsSid: 'SMxx', + From: '+12345', + AccountSid: 'ACxxx', + MessagingServiceSid: twilio_channel.messaging_service_sid, + Body: 'testing multiple media', + NumMedia: '2', + MediaContentType0: 'image/png', + MediaUrl0: 'https://chatwoot-assets.local/sample.png', + MediaContentType1: 'image/jpeg', + MediaUrl1: 'https://chatwoot-assets.local/sample.jpg' + } + end + + it 'creates a new message with multiple media attachments in existing conversation' do + described_class.new(params: params_with_multiple_attachments).perform + expect(conversation.reload.messages.last.content).to eq('testing multiple media') + expect(conversation.reload.messages.last.attachments.count).to eq(2) + expect(conversation.reload.messages.last.attachments.map(&:file_type)).to contain_exactly('image', 'image') + end + end end end