diff --git a/Gemfile b/Gemfile index c4fa85bc5..45d54dfe0 100644 --- a/Gemfile +++ b/Gemfile @@ -72,7 +72,7 @@ gem 'wisper', '2.0.0' ##--- gems for channels ---## # TODO: bump up gem to 2.0 -gem 'facebook-messenger', '1.5.0' +gem 'facebook-messenger' gem 'telegram-bot-ruby' gem 'twilio-ruby', '~> 5.32.0' # twitty will handle subscription of twitter account events diff --git a/Gemfile.lock b/Gemfile.lock index e372d1545..2923468cd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -186,7 +186,7 @@ GEM et-orbi (1.2.4) tzinfo execjs (2.7.0) - facebook-messenger (1.5.0) + facebook-messenger (2.0.1) httparty (~> 0.13, >= 0.13.7) rack (>= 1.4.5) factory_bot (6.1.0) @@ -333,7 +333,7 @@ GEM method_source (1.0.0) mime-types (3.3.1) mime-types-data (~> 3.2015) - mime-types-data (3.2020.0512) + mime-types-data (3.2021.0225) mini_magick (4.10.1) mini_mime (1.1.0) mini_portile2 (2.5.1) @@ -631,7 +631,7 @@ DEPENDENCIES devise devise_token_auth dotenv-rails - facebook-messenger (= 1.5.0) + facebook-messenger factory_bot_rails faker fcm diff --git a/app/bot/facebook_bot.rb b/app/bot/facebook_bot.rb index f3f87220d..c13cf12ed 100644 --- a/app/bot/facebook_bot.rb +++ b/app/bot/facebook_bot.rb @@ -1,15 +1,13 @@ require 'facebook/messenger' class FacebookBot - include Facebook::Messenger - - Bot.on :message do |message| + Facebook::Messenger::Bot.on :message do |message| Rails.logger.info "MESSAGE_RECIEVED #{message}" response = ::Integrations::Facebook::MessageParser.new(message) ::Integrations::Facebook::MessageCreator.new(response).perform end - Bot.on :delivery do |delivery| + Facebook::Messenger::Bot.on :delivery do |delivery| # delivery.ids # => 'mid.1457764197618:41d102a3e1ae206a38' # delivery.sender # => { 'id' => '1008372609250235' } # delivery.recipient # => { 'id' => '2015573629214912' } @@ -20,7 +18,7 @@ class FacebookBot Rails.logger.info "Human was online at #{delivery.at}" end - Bot.on :message_echo do |message| + Facebook::Messenger::Bot.on :message_echo do |message| Rails.logger.info "MESSAGE_ECHO #{message}" response = ::Integrations::Facebook::MessageParser.new(message) ::Integrations::Facebook::MessageCreator.new(response).perform diff --git a/app/builders/messages/facebook/message_builder.rb b/app/builders/messages/facebook/message_builder.rb index 447fe3cec..f0fc21f7d 100644 --- a/app/builders/messages/facebook/message_builder.rb +++ b/app/builders/messages/facebook/message_builder.rb @@ -13,6 +13,7 @@ class Messages::Facebook::MessageBuilder @outgoing_echo = outgoing_echo @sender_id = (@outgoing_echo ? @response.recipient_id : @response.sender_id) @message_type = (@outgoing_echo ? :outgoing : :incoming) + @attachments = (@response.attachments || []) end def perform @@ -41,13 +42,19 @@ class Messages::Facebook::MessageBuilder def build_message @message = conversation.messages.create!(message_params) - (response.attachments || []).each do |attachment| - attachment_obj = @message.attachments.new(attachment_params(attachment).except(:remote_file_url)) - attachment_obj.save! - attach_file(attachment_obj, attachment_params(attachment)[:remote_file_url]) if attachment_params(attachment)[:remote_file_url] + @attachments.each do |attachment| + process_attachment(attachment) end end + def process_attachment(attachment) + return if attachment['type'].to_sym == :template + + attachment_obj = @message.attachments.new(attachment_params(attachment).except(:remote_file_url)) + attachment_obj.save! + attach_file(attachment_obj, attachment_params(attachment)[:remote_file_url]) if attachment_params(attachment)[:remote_file_url] + end + def attach_file(attachment, file_url) file_resource = LocalResource.new(file_url) attachment.file.attach(io: file_resource.file, filename: file_resource.filename, content_type: file_resource.encoding) diff --git a/app/controllers/api/v1/accounts/facebook_indicators_controller.rb b/app/controllers/api/v1/accounts/facebook_indicators_controller.rb deleted file mode 100644 index a0e42c040..000000000 --- a/app/controllers/api/v1/accounts/facebook_indicators_controller.rb +++ /dev/null @@ -1,55 +0,0 @@ -class Api::V1::Accounts::FacebookIndicatorsController < Api::V1::Accounts::BaseController - before_action :set_access_token - around_action :handle_with_exception - - def mark_seen - fb_bot.deliver(payload('mark_seen'), access_token: @access_token) - head :ok - end - - def typing_on - fb_bot.deliver(payload('typing_on'), access_token: @access_token) - head :ok - end - - def typing_off - fb_bot.deliver(payload('typing_off'), access_token: @access_token) - head :ok - end - - private - - def fb_bot - ::Facebook::Messenger::Bot - end - - def handle_with_exception - yield - rescue Facebook::Messenger::Error => e - Rails.logger.debug "Rescued: #{e.inspect}" - true - end - - def payload(action) - { - recipient: { id: contact.source_id }, - sender_action: action - } - end - - def inbox - @inbox ||= Current.account.inboxes.find(permitted_params[:inbox_id]) - end - - def set_access_token - @access_token = inbox.channel.page_access_token - end - - def contact - @contact ||= inbox.contact_inboxes.find_by!(contact_id: permitted_params[:contact_id]) - end - - def permitted_params - params.permit(:inbox_id, :contact_id) - end -end diff --git a/app/services/facebook/send_on_facebook_service.rb b/app/services/facebook/send_on_facebook_service.rb index b8102c6fa..a0c75db92 100644 --- a/app/services/facebook/send_on_facebook_service.rb +++ b/app/services/facebook/send_on_facebook_service.rb @@ -14,7 +14,7 @@ class Facebook::SendOnFacebookService < Base::SendOnChannelService end def send_message_to_facebook(delivery_params) - result = FacebookBot::Bot.deliver(delivery_params, access_token: message.channel_token) + result = Facebook::Messenger::Bot.deliver(delivery_params, page_id: channel.page_id) message.update!(source_id: JSON.parse(result)['message_id']) end diff --git a/config/initializers/bot.rb b/config/initializers/bot.rb index c730c8b1b..00ef3a0ac 100644 --- a/config/initializers/bot.rb +++ b/config/initializers/bot.rb @@ -1,3 +1,7 @@ +# Remember that Rails only eager loads everything in its production environment. +# In the development and test environments, it only requires files as you reference constants. +# You'll need to explicitly load app/bot + unless Rails.env.production? bot_files = Dir[Rails.root.join('app', 'bot', '**', '*.rb')] bot_reloader = ActiveSupport::FileUpdateChecker.new(bot_files) do @@ -11,21 +15,7 @@ unless Rails.env.production? bot_files.each { |file| require_dependency file } end -module Facebook - module Messenger - module Incoming - # The Message class represents an incoming Facebook Messenger message. - class Message - include Facebook::Messenger::Incoming::Common - - def app_id - @messaging['message']['app_id'] - end - end - end - end -end - +# ref: https://github.com/jgorset/facebook-messenger#make-a-configuration-provider class ChatwootFbProvider < Facebook::Messenger::Configuration::Providers::Base def valid_verify_token?(_verify_token) ENV['FB_VERIFY_TOKEN'] @@ -36,13 +26,13 @@ class ChatwootFbProvider < Facebook::Messenger::Configuration::Providers::Base end def access_token_for(page_id) - Channel::FacebookPage.where(page_id: page_id).last.access_token + Channel::FacebookPage.where(page_id: page_id).last.page_access_token end private def bot - MyApp::Bot + Chatwoot::Bot end end diff --git a/config/routes.rb b/config/routes.rb index 667346c02..fba20286f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -88,14 +88,6 @@ Rails.application.routes.draw do end end - resources :facebook_indicators, only: [] do - collection do - post :mark_seen - post :typing_on - post :typing_off - end - end - resources :inboxes, only: [:index, :create, :update, :destroy] do get :assignable_agents, on: :member get :campaigns, on: :member diff --git a/spec/controllers/api/v1/accounts/facebook_indicators_controller_spec.rb b/spec/controllers/api/v1/accounts/facebook_indicators_controller_spec.rb deleted file mode 100644 index 0b32705f1..000000000 --- a/spec/controllers/api/v1/accounts/facebook_indicators_controller_spec.rb +++ /dev/null @@ -1,143 +0,0 @@ -require 'rails_helper' - -RSpec.describe 'Facebook Indicators API', type: :request do - let(:account) { create(:account) } - let(:facebook_channel) { create(:channel_facebook_page, account: account) } - let(:inbox) { create(:inbox, account: account, channel: facebook_channel) } - let(:contact) { create(:contact, account: account) } - let(:valid_params) { { contact_id: contact.id, inbox_id: inbox.id } } - - before do - allow(Facebook::Messenger::Bot).to receive(:deliver).and_return(true) - allow(Facebook::Messenger::Subscriptions).to receive(:subscribe).and_return(true) - end - - describe 'POST /api/v1/accounts/{account.id}/facebook_indicators/mark_seen' do - context 'when it is an unauthenticated user' do - it 'returns unauthorized' do - post "/api/v1/accounts/#{account.id}/facebook_indicators/mark_seen" - - expect(response).to have_http_status(:unauthorized) - end - end - - context 'when it is an authenticated user' do - let(:agent) { create(:user, account: account, role: :agent) } - - it 'marks a payload as seen' do - contact_inbox = create(:contact_inbox, contact: contact, inbox: inbox) - - expect(Facebook::Messenger::Bot).to receive(:deliver).with( - { recipient: { id: contact_inbox.source_id }, sender_action: 'mark_seen' }, - access_token: inbox.channel.page_access_token - ) - - post "/api/v1/accounts/#{account.id}/facebook_indicators/mark_seen", - headers: agent.create_new_auth_token, - params: valid_params, - as: :json - - expect(response).to have_http_status(:success) - end - - it 'rescues an error' do - create(:contact_inbox, contact: contact, inbox: inbox) - - allow(Facebook::Messenger::Bot).to receive(:deliver).and_raise(Facebook::Messenger::Error) - - post "/api/v1/accounts/#{account.id}/facebook_indicators/mark_seen", - headers: agent.create_new_auth_token, - params: valid_params, - as: :json - - expect(response).to have_http_status(:success) - end - end - end - - describe 'POST /api/v1/accounts/{account.id}/facebook_indicators/typing_on' do - context 'when it is an unauthenticated user' do - it 'returns unauthorized' do - post "/api/v1/accounts/#{account.id}/facebook_indicators/typing_on" - - expect(response).to have_http_status(:unauthorized) - end - end - - context 'when it is an authenticated user' do - let(:agent) { create(:user, account: account, role: :agent) } - - it 'marks a payload as typing_on' do - contact_inbox = create(:contact_inbox, contact: contact, inbox: inbox) - - expect(Facebook::Messenger::Bot).to receive(:deliver).with( - { recipient: { id: contact_inbox.source_id }, sender_action: 'typing_on' }, - access_token: inbox.channel.page_access_token - ) - - post "/api/v1/accounts/#{account.id}/facebook_indicators/typing_on", - headers: agent.create_new_auth_token, - params: valid_params, - as: :json - - expect(response).to have_http_status(:success) - end - - it 'rescues an error' do - create(:contact_inbox, contact: contact, inbox: inbox) - - allow(Facebook::Messenger::Bot).to receive(:deliver).and_raise(Facebook::Messenger::Error) - - post "/api/v1/accounts/#{account.id}/facebook_indicators/typing_on", - headers: agent.create_new_auth_token, - params: valid_params, - as: :json - - expect(response).to have_http_status(:success) - end - end - end - - describe 'POST /api/v1/accounts/{account.id}/facebook_indicators/typing_off' do - context 'when it is an unauthenticated user' do - it 'returns unauthorized' do - post "/api/v1/accounts/#{account.id}/facebook_indicators/typing_off" - - expect(response).to have_http_status(:unauthorized) - end - end - - context 'when it is an authenticated user' do - let(:agent) { create(:user, account: account, role: :agent) } - - it 'marks a payload as typing_off' do - contact_inbox = create(:contact_inbox, contact: contact, inbox: inbox) - - expect(Facebook::Messenger::Bot).to receive(:deliver).with( - { recipient: { id: contact_inbox.source_id }, sender_action: 'typing_off' }, - access_token: inbox.channel.page_access_token - ) - - post "/api/v1/accounts/#{account.id}/facebook_indicators/typing_off", - headers: agent.create_new_auth_token, - params: valid_params, - as: :json - - expect(response).to have_http_status(:success) - end - - it 'rescues an error' do - create(:contact_inbox, contact: contact, inbox: inbox) - - allow(Facebook::Messenger::Bot).to receive(:deliver).and_raise(Facebook::Messenger::Error) - - post "/api/v1/accounts/#{account.id}/facebook_indicators/typing_off", - headers: agent.create_new_auth_token, - params: valid_params, - as: :json - - expect(response).to have_http_status(:success) - end - end - end -end diff --git a/spec/services/facebook/send_on_facebook_service_spec.rb b/spec/services/facebook/send_on_facebook_service_spec.rb index e84319a26..8ff842591 100644 --- a/spec/services/facebook/send_on_facebook_service_spec.rb +++ b/spec/services/facebook/send_on_facebook_service_spec.rb @@ -10,7 +10,7 @@ describe Facebook::SendOnFacebookService do end let!(:account) { create(:account) } - let(:bot) { class_double('FacebookBot::Bot').as_stubbed_const } + let(:bot) { class_double('Facebook::Messenger::Bot').as_stubbed_const } let!(:widget_inbox) { create(:inbox, account: account) } let!(:facebook_channel) { create(:channel_facebook_page, account: account) } let!(:facebook_inbox) { create(:inbox, channel: facebook_channel, account: account) } @@ -61,7 +61,7 @@ describe Facebook::SendOnFacebookService do expect(bot).to have_received(:deliver).with({ recipient: { id: contact_inbox.source_id }, message: { text: message.content } - }, { access_token: facebook_channel.page_access_token }) + }, { page_id: facebook_channel.page_id }) expect(bot).to have_received(:deliver).with({ recipient: { id: contact_inbox.source_id }, message: { @@ -72,7 +72,7 @@ describe Facebook::SendOnFacebookService do } } } - }, { access_token: facebook_channel.page_access_token }) + }, { page_id: facebook_channel.page_id }) end end end