From b1b026870583424e26f66adb8eeba3be9548d090 Mon Sep 17 00:00:00 2001 From: Tejaswini Chile Date: Sat, 18 Sep 2021 00:49:01 +0530 Subject: [PATCH] feat: Support sending and receiving attachments in Slack Integration (#3022) - Process incoming slack attachments - Send attachments from chatwoot to slack --- app/models/attachment.rb | 1 + .../slack/incoming_message_builder.rb | 38 ++++++++++++++++++- .../slack/send_on_slack_service.rb | 35 ++++++++++++++--- .../slack/incoming_message_builder_spec.rb | 12 ++++++ .../slack/send_on_slack_service_spec.rb | 31 +++++++++++++++ spec/support/slack_stubs.rb | 28 ++++++++++++++ 6 files changed, 139 insertions(+), 6 deletions(-) diff --git a/app/models/attachment.rb b/app/models/attachment.rb index 64637163d..0db94b9ba 100644 --- a/app/models/attachment.rb +++ b/app/models/attachment.rb @@ -25,6 +25,7 @@ class Attachment < ApplicationRecord enum file_type: [:image, :audio, :video, :file, :location, :fallback] def push_event_data + return unless file_type return base_data.merge(location_metadata) if file_type.to_sym == :location return base_data.merge(fallback_data) if file_type.to_sym == :fallback diff --git a/lib/integrations/slack/incoming_message_builder.rb b/lib/integrations/slack/incoming_message_builder.rb index 93f23d3a4..e655eef45 100644 --- a/lib/integrations/slack/incoming_message_builder.rb +++ b/lib/integrations/slack/incoming_message_builder.rb @@ -79,7 +79,7 @@ class Integrations::Slack::IncomingMessageBuilder def create_message return unless conversation - conversation.messages.create( + @message = conversation.messages.create( message_type: :outgoing, account_id: conversation.account_id, inbox_id: conversation.inbox_id, @@ -89,10 +89,46 @@ class Integrations::Slack::IncomingMessageBuilder sender: sender ) + process_attachments(params[:event][:files]) if params[:event][:files].present? + { status: 'success' } end def slack_client @slack_client ||= Slack::Web::Client.new(token: @integration_hook.access_token) end + + # TODO: move process attachment for facebook instagram and slack in one place + # https://api.slack.com/messaging/files + def process_attachments(attachments) + attachments.each do |attachment| + tempfile = Down::NetHttp.download(attachment[:url_private], headers: { 'Authorization' => "Bearer #{integration_hook.access_token}" }) + + attachment_params = { + file_type: file_type(attachment), + account_id: @message.account_id, + external_url: attachment[:url_private], + file: { + io: tempfile, + filename: tempfile.original_filename, + content_type: tempfile.content_type + } + } + + attachment_obj = @message.attachments.new(attachment_params) + attachment_obj.file.content_type = attachment[:mimetype] + attachment_obj.save! + end + end + + def file_type(attachment) + return if attachment[:mimetype] == 'text/plain' + + case attachment[:filetype] + when 'png', 'jpeg', 'gif', 'bmp', 'tiff', 'jpg' + :image + when 'pdf' + :file + end + end end diff --git a/lib/integrations/slack/send_on_slack_service.rb b/lib/integrations/slack/send_on_slack_service.rb index ba3e68669..5d8b52e24 100644 --- a/lib/integrations/slack/send_on_slack_service.rb +++ b/lib/integrations/slack/send_on_slack_service.rb @@ -45,8 +45,15 @@ class Integrations::Slack::SendOnSlackService < Base::SendOnChannelService end def send_message - return if message_content.blank? + post_message if message_content.present? + upload_file if message.attachments.any? + rescue Slack::Web::Api::Errors::AccountInactive => e + Rails.logger.info e + hook.authorization_error! + hook.disable if hook.enabled? + end + def post_message @slack_message = slack_client.chat_postMessage( channel: hook.reference_id, text: message_content, @@ -54,10 +61,28 @@ class Integrations::Slack::SendOnSlackService < Base::SendOnChannelService thread_ts: conversation.identifier, icon_url: avatar_url(message.sender) ) - rescue Slack::Web::Api::Errors::AccountInactive => e - Rails.logger.info e - hook.authorization_error! - hook.disable if hook.enabled? + end + + def upload_file + result = slack_client.files_upload({ + channels: hook.reference_id, + initial_comment: 'Attached File!', + thread_ts: conversation.identifier + }.merge(file_information)) + Rails.logger.info(result) + end + + def file_type + File.extname(message.attachments.first.file_url).strip.downcase[1..] + end + + def file_information + { + filename: message.attachments.first.file.filename, + filetype: file_type, + content: message.attachments.first.file.download, + title: message.attachments.first.file.filename + } end def sender_name(sender) diff --git a/spec/lib/integrations/slack/incoming_message_builder_spec.rb b/spec/lib/integrations/slack/incoming_message_builder_spec.rb index ac89434c7..98ae9cdd1 100644 --- a/spec/lib/integrations/slack/incoming_message_builder_spec.rb +++ b/spec/lib/integrations/slack/incoming_message_builder_spec.rb @@ -3,6 +3,7 @@ require 'rails_helper' describe Integrations::Slack::IncomingMessageBuilder do let(:account) { create(:account) } let(:message_params) { slack_message_stub } + let(:message_with_attachments) { slack_attachment_stub } let(:message_without_thread_ts) { slack_message_stub_without_thread_ts } let(:verification_params) { slack_url_verification_stub } @@ -51,6 +52,17 @@ describe Integrations::Slack::IncomingMessageBuilder do builder.perform expect(conversation.messages.count).to eql(messages_count) end + + it 'saves attachment if params files present' do + expect(hook).not_to eq nil + messages_count = conversation.messages.count + builder = described_class.new(message_with_attachments) + allow(builder).to receive(:sender).and_return(nil) + builder.perform + expect(conversation.messages.count).to eql(messages_count + 1) + expect(conversation.messages.last.content).to eql('this is test https://chatwoot.com Hey @Sojan Test again') + expect(conversation.messages.last.attachments).to be_any + end end end end diff --git a/spec/lib/integrations/slack/send_on_slack_service_spec.rb b/spec/lib/integrations/slack/send_on_slack_service_spec.rb index 234a2180b..ef662ae15 100644 --- a/spec/lib/integrations/slack/send_on_slack_service_spec.rb +++ b/spec/lib/integrations/slack/send_on_slack_service_spec.rb @@ -9,6 +9,7 @@ describe Integrations::Slack::SendOnSlackService do create(:message, account: conversation.account, inbox: conversation.inbox, conversation: conversation) end let(:slack_message) { double } + let(:file_attachment) { double } let(:slack_message_content) { double } let(:slack_client) { double } let(:builder) { described_class.new(message: message, hook: hook) } @@ -58,6 +59,36 @@ describe Integrations::Slack::SendOnSlackService do expect(message.external_source_id_slack).to eq 'cw-origin-6789.12345' end + it 'sent attachment on slack' do + expect(slack_client).to receive(:chat_postMessage).with( + channel: hook.reference_id, + text: message.content, + username: "Contact: #{message.sender.name}", + thread_ts: conversation.identifier, + icon_url: anything + ).and_return(slack_message) + + attachment = message.attachments.new(account_id: message.account_id, file_type: :image) + attachment.file.attach(io: File.open(Rails.root.join('spec/assets/avatar.png')), filename: 'avatar.png', content_type: 'image/png') + + expect(slack_client).to receive(:files_upload).with( + channels: hook.reference_id, + initial_comment: 'Attached File!', + content: anything, + filename: attachment.file.filename, + filetype: 'png', + thread_ts: conversation.identifier, + title: anything + ).and_return(file_attachment) + + message.save! + + builder.perform + + expect(message.external_source_id_slack).to eq 'cw-origin-6789.12345' + expect(message.attachments).to be_any + end + it 'disables hook on Slack AccountInactive error' do expect(slack_client).to receive(:chat_postMessage).with( channel: hook.reference_id, diff --git a/spec/support/slack_stubs.rb b/spec/support/slack_stubs.rb index d0e89221b..650a7cf2b 100644 --- a/spec/support/slack_stubs.rb +++ b/spec/support/slack_stubs.rb @@ -21,6 +21,20 @@ module SlackStubs } end + def slack_attachment_stub + { + token: '[FILTERED]', + team_id: 'TLST3048H', + api_app_id: 'A012S5UETV4', + event: message_event, + type: 'event_callback', + event_id: 'Ev013QUX3WV6', + event_time: 1_588_623_033, + authed_users: '[FILTERED]', + webhook: {} + } + end + def slack_message_stub_without_thread_ts { token: '[FILTERED]', @@ -52,6 +66,7 @@ module SlackStubs ts: '1588623033.006000', team: 'TLST3048H', blocks: message_blocks, + files: file_stub, thread_ts: '1588623023.005900', channel: 'G01354F6A6Q', event_ts: '1588623033.006000', @@ -59,6 +74,19 @@ module SlackStubs } end + def file_stub + [ + { + mimetype: 'image/png', + url_private: 'https://via.placeholder.com/250x250.png', + name: 'name_of_the_file', + title: 'title_of_the_file', + filetype: 'png', + url_private_download: 'https://via.placeholder.com/250x250.png' + } + ] + end + def message_blocks [ {