Chore: Twitter Integration house cleaning (#855)

Co-authored-by: Pranav Raj Sreepuram <pranavrajs@gmail.com>
This commit is contained in:
Sojan Jose 2020-05-14 22:51:07 +05:30 committed by GitHub
parent 1108446974
commit 20f39caa42
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 159 additions and 63 deletions

View file

@ -64,7 +64,8 @@ gem 'facebook-messenger'
gem 'telegram-bot-ruby' gem 'telegram-bot-ruby'
gem 'twilio-ruby', '~> 5.32.0' gem 'twilio-ruby', '~> 5.32.0'
# twitty will handle subscription of twitter account events # twitty will handle subscription of twitter account events
gem 'twitty', git: 'https://github.com/chatwoot/twitty' # gem 'twitty', git: 'https://github.com/chatwoot/twitty'
gem 'twitty'
# facebook client # facebook client
gem 'koala' gem 'koala'
# Random name generator # Random name generator

View file

@ -1,10 +1,3 @@
GIT
remote: https://github.com/chatwoot/twitty
revision: af4f3e45dca55e42c64f7741a1fedfaa94d36419
specs:
twitty (0.1.0)
oauth
GIT GIT
remote: https://github.com/sds/mock_redis remote: https://github.com/sds/mock_redis
revision: 16d00789f0341a3aac35126c0ffe97a596753ff9 revision: 16d00789f0341a3aac35126c0ffe97a596753ff9
@ -489,6 +482,8 @@ GEM
faraday (~> 1.0.0) faraday (~> 1.0.0)
jwt (>= 1.5, <= 2.5) jwt (>= 1.5, <= 2.5)
nokogiri (>= 1.6, < 2.0) nokogiri (>= 1.6, < 2.0)
twitty (0.1.1)
oauth
tzinfo (1.2.7) tzinfo (1.2.7)
thread_safe (~> 0.1) thread_safe (~> 0.1)
tzinfo-data (1.2020.1) tzinfo-data (1.2020.1)
@ -598,7 +593,7 @@ DEPENDENCIES
telephone_number telephone_number
time_diff time_diff
twilio-ruby (~> 5.32.0) twilio-ruby (~> 5.32.0)
twitty! twitty
tzinfo-data tzinfo-data
uglifier uglifier
valid_email2 valid_email2

View file

@ -14,6 +14,18 @@ class ContactBuilder
@account ||= inbox.account @account ||= inbox.account
end end
def create_contact_inbox(contact)
::ContactInbox.create!(
contact_id: contact.id,
inbox_id: inbox.id,
source_id: source_id
)
end
def update_contact_avatar(contact)
::ContactAvatarJob.perform_later(contact, contact_attributes[:avatar_url]) if contact_attributes[:avatar_url]
end
def build_contact def build_contact
ActiveRecord::Base.transaction do ActiveRecord::Base.transaction do
contact = account.contacts.create!( contact = account.contacts.create!(
@ -23,16 +35,12 @@ class ContactBuilder
identifier: contact_attributes[:identifier], identifier: contact_attributes[:identifier],
additional_attributes: contact_attributes[:additional_attributes] additional_attributes: contact_attributes[:additional_attributes]
) )
contact_inbox = ::ContactInbox.create!(
contact_id: contact.id,
inbox_id: inbox.id,
source_id: source_id
)
::ContactAvatarJob.perform_later(contact, contact_attributes[:avatar_url]) if contact_attributes[:avatar_url] contact_inbox = create_contact_inbox(contact)
update_contact_avatar(contact)
contact_inbox contact_inbox
rescue StandardError => e rescue StandardError => e
Rails.logger e Rails.logger.info e
end end
end end
end end

View file

@ -14,7 +14,7 @@ class Api::V1::Accounts::CallbacksController < Api::BaseController
@facebook_inbox = current_account.inboxes.create!(name: inbox_name, channel: facebook_channel) @facebook_inbox = current_account.inboxes.create!(name: inbox_name, channel: facebook_channel)
set_avatar(@facebook_inbox, page_id) set_avatar(@facebook_inbox, page_id)
rescue StandardError => e rescue StandardError => e
Rails.logger e Rails.logger.info e
end end
end end
@ -62,7 +62,7 @@ class Api::V1::Accounts::CallbacksController < Api::BaseController
koala = Koala::Facebook::OAuth.new(ENV['FB_APP_ID'], ENV['FB_APP_SECRET']) koala = Koala::Facebook::OAuth.new(ENV['FB_APP_ID'], ENV['FB_APP_SECRET'])
koala.exchange_access_token_info(omniauth_token)['access_token'] koala.exchange_access_token_info(omniauth_token)['access_token']
rescue StandardError => e rescue StandardError => e
Rails.logger e Rails.logger.info e
end end
def mark_already_existing_facebook_pages(data) def mark_already_existing_facebook_pages(data)

View file

@ -2,18 +2,18 @@ class Twitter::CallbacksController < Twitter::BaseController
def show def show
return redirect_to twitter_app_redirect_url if permitted_params[:denied] return redirect_to twitter_app_redirect_url if permitted_params[:denied]
@response = twitter_client.access_token( @response = ensure_access_token
oauth_token: permitted_params[:oauth_token], return redirect_to twitter_app_redirect_url if @response.status != '200'
oauth_verifier: permitted_params[:oauth_verifier]
) ActiveRecord::Base.transaction do
if @response.status == '200' inbox = create_inbox
inbox = build_inbox
::Redis::Alfred.delete(permitted_params[:oauth_token]) ::Redis::Alfred.delete(permitted_params[:oauth_token])
::Twitter::WebhookSubscribeService.new(inbox_id: inbox.id).perform ::Twitter::WebhookSubscribeService.new(inbox_id: inbox.id).perform
redirect_to app_twitter_inbox_agents_url(account_id: account.id, inbox_id: inbox.id) redirect_to app_twitter_inbox_agents_url(account_id: account.id, inbox_id: inbox.id)
else
redirect_to twitter_app_redirect_url
end end
rescue StandardError => e
Rails.logger.info e
redirect_to twitter_app_redirect_url
end end
private private
@ -34,8 +34,14 @@ class Twitter::CallbacksController < Twitter::BaseController
app_new_twitter_inbox_url(account_id: account.id) app_new_twitter_inbox_url(account_id: account.id)
end end
def build_inbox def ensure_access_token
ActiveRecord::Base.transaction do twitter_client.access_token(
oauth_token: permitted_params[:oauth_token],
oauth_verifier: permitted_params[:oauth_verifier]
)
end
def create_inbox
twitter_profile = account.twitter_profiles.create( twitter_profile = account.twitter_profiles.create(
twitter_access_token: parsed_body['oauth_token'], twitter_access_token: parsed_body['oauth_token'],
twitter_access_token_secret: parsed_body['oauth_token_secret'], twitter_access_token_secret: parsed_body['oauth_token_secret'],
@ -45,9 +51,6 @@ class Twitter::CallbacksController < Twitter::BaseController
name: parsed_body['screen_name'], name: parsed_body['screen_name'],
channel: twitter_profile channel: twitter_profile
) )
rescue StandardError => e
Rails.logger e
end
end end
def permitted_params def permitted_params

View file

@ -35,7 +35,7 @@ class Channel::TwitterProfile < ApplicationRecord
source_id: profile_id source_id: profile_id
) )
rescue StandardError => e rescue StandardError => e
Rails.logger e Rails.logger.info e
end end
end end
@ -53,7 +53,10 @@ class Channel::TwitterProfile < ApplicationRecord
private private
def unsubscribe def unsubscribe
webhooks_list = twitter_client.fetch_webhooks.body ### Fix unsubscription with new endpoint
twitter_client.unsubscribe_webhook(id: webhooks_list.first['id']) if webhooks_list.present? unsubscribe_response = twitter_client.remove_subscription(user_id: profile_id)
Rails.logger.info 'TWITTER_UNSUBSCRIBE: ' + unsubscribe_response.body.to_s
rescue StandardError => e
Rails.logger.info e
end end
end end

View file

@ -55,7 +55,7 @@ class Channel::WebWidget < ApplicationRecord
) )
contact_inbox contact_inbox
rescue StandardError => e rescue StandardError => e
Rails.logger e Rails.logger.info e
end end
end end
end end

View file

@ -47,7 +47,7 @@ class Twitter::SendReplyService
tweet_data = response.body tweet_data = response.body
message.update!(source_id: tweet_data['id_str']) message.update!(source_id: tweet_data['id_str'])
else else
Rails.logger 'TWITTER_TWEET_REPLY_ERROR' + response.body Rails.logger.info 'TWITTER_TWEET_REPLY_ERROR' + response.body
end end
end end

View file

@ -4,9 +4,13 @@ class Twitter::WebhookSubscribeService
pattr_initialize [:inbox_id] pattr_initialize [:inbox_id]
def perform def perform
register_response = twitter_client.register_webhook(url: webhooks_twitter_url(protocol: 'https')) ensure_webhook
twitter_client.subscribe_webhook if register_response.status == '200' unless subscription?
Rails.logger.info 'TWITTER_REGISTER_WEBHOOK_FAILURE: ' + register_response.body.to_s subscribe_response = twitter_client.create_subscription
raise StandardError, 'Twitter Subscription Failed' unless subscribe_response.status == '204'
end
true
end end
private private
@ -17,4 +21,37 @@ class Twitter::WebhookSubscribeService
def inbox def inbox
Inbox.find_by!(id: inbox_id) Inbox.find_by!(id: inbox_id)
end end
def twitter_url
webhooks_twitter_url(protocol: 'https')
end
def ensure_webhook
webhooks = fetch_webhooks
return true if webhooks&.first&.try(:[], 'url') == twitter_url
# twitter supports only one webhook url per environment
# so we will delete the existing one if its not chatwoot
unregister_webhook(webhooks.first) if webhooks&.first
register_webhook
end
def unregister_webhook(webhook)
unregister_response = twitter_client.unregister_webhook(id: webhook.try(:[], 'id'))
Rails.logger.info 'TWITTER_UNREGISTER_WEBHOOK: ' + unregister_response.body.to_s
end
def register_webhook
register_response = twitter_client.register_webhook(url: twitter_url)
Rails.logger.info 'TWITTER_UNREGISTER_WEBHOOK: ' + register_response.body.to_s
end
def subscription?
response = twitter_client.fetch_subscriptions
response.status == '204'
end
def fetch_webhooks
twitter_client.fetch_webhooks.body
end
end end

View file

@ -1,27 +1,26 @@
require 'rails_helper' require 'rails_helper'
RSpec.describe 'Twitter::CallbacksController', type: :request do RSpec.describe 'Twitter::CallbacksController', type: :request do
subject(:webhook_subscribe_service) { described_class.new(inbox_id: twitter_inbox.id) }
let(:twitter_client) { instance_double(::Twitty::Facade) } let(:twitter_client) { instance_double(::Twitty::Facade) }
let(:twitter_response) { instance_double(::Twitty::Response, status: '200', body: { message: 'Valid' }) } let(:twitter_response) { instance_double(::Twitty::Response, status: '200', body: { message: 'Valid' }) }
let(:raw_response) do let(:raw_response) do
object_double('raw_response', body: 'oauth_token=1&oauth_token_secret=1&user_id=100&screen_name=chatwoot') object_double('raw_response', body: 'oauth_token=1&oauth_token_secret=1&user_id=100&screen_name=chatwoot')
end end
let(:account) { create(:account) } let(:account) { create(:account) }
let(:webhook_service) { double }
before do before do
allow(::Twitty::Facade).to receive(:new).and_return(twitter_client) allow(::Twitty::Facade).to receive(:new).and_return(twitter_client)
allow(::Redis::Alfred).to receive(:get).and_return(account.id) allow(::Redis::Alfred).to receive(:get).and_return(account.id)
allow(::Redis::Alfred).to receive(:delete).and_return('OK') allow(::Redis::Alfred).to receive(:delete).and_return('OK')
allow(twitter_client).to receive(:access_token).and_return(twitter_response) allow(twitter_client).to receive(:access_token).and_return(twitter_response)
allow(twitter_client).to receive(:register_webhook).and_return(twitter_response)
allow(twitter_client).to receive(:subscribe_webhook).and_return(true)
allow(twitter_response).to receive(:raw_response).and_return(raw_response) allow(twitter_response).to receive(:raw_response).and_return(raw_response)
allow(::Twitter::WebhookSubscribeService).to receive(:new).and_return(webhook_service)
end end
describe 'GET /twitter/callback' do describe 'GET /twitter/callback' do
it 'returns correct response' do it 'creates inboxes if subscription is successful' do
allow(webhook_service).to receive(:perform).and_return true
get twitter_callback_url get twitter_callback_url
account.reload account.reload
expect(response).to redirect_to app_twitter_inbox_agents_url(account_id: account.id, inbox_id: account.inboxes.last.id) expect(response).to redirect_to app_twitter_inbox_agents_url(account_id: account.id, inbox_id: account.inboxes.last.id)
@ -29,5 +28,13 @@ RSpec.describe 'Twitter::CallbacksController', type: :request do
expect(account.twitter_profiles.last.inbox.name).to eq 'chatwoot' expect(account.twitter_profiles.last.inbox.name).to eq 'chatwoot'
expect(account.twitter_profiles.last.profile_id).to eq '100' expect(account.twitter_profiles.last.profile_id).to eq '100'
end end
it 'does not create inbox if subscription fails' do
allow(webhook_service).to receive(:perform).and_raise StandardError
get twitter_callback_url
account.reload
expect(response).to redirect_to app_new_twitter_inbox_url(account_id: account.id)
expect(account.inboxes.count).to be 0
end
end end
end end

View file

@ -12,24 +12,66 @@ describe ::Twitter::WebhookSubscribeService do
before do before do
allow(::Twitty::Facade).to receive(:new).and_return(twitter_client) allow(::Twitty::Facade).to receive(:new).and_return(twitter_client)
allow(twitter_client).to receive(:register_webhook) allow(twitter_client).to receive(:register_webhook).and_return(twitter_success_response)
allow(twitter_client).to receive(:subscribe_webhook) allow(twitter_client).to receive(:unregister_webhook).and_return(twitter_success_response)
allow(twitter_client).to receive(:fetch_subscriptions).and_return(instance_double(::Twitty::Response, status: '204', body: { message: 'Valid' }))
allow(twitter_client).to receive(:create_subscription).and_return(instance_double(::Twitty::Response, status: '204', body: { message: 'Valid' }))
end end
describe '#perform' do describe '#perform' do
context 'with successful registration' do context 'when webhook is not registered' do
it 'calls subscribe webhook' do it 'calls register_webhook' do
allow(twitter_client).to receive(:register_webhook).and_return(twitter_success_response) allow(twitter_client).to receive(:fetch_webhooks).and_return(
instance_double(::Twitty::Response, status: '200', body: {})
)
webhook_subscribe_service.perform webhook_subscribe_service.perform
expect(twitter_client).to have_received(:subscribe_webhook) expect(twitter_client).not_to have_received(:unregister_webhook)
expect(twitter_client).to have_received(:register_webhook)
end end
end end
context 'with unsuccessful registration' do context 'when valid webhook is registered' do
it 'does not call subscribe webhook' do it 'calls unregister_webhook and then register webhook' do
allow(twitter_client).to receive(:register_webhook).and_return(twitter_error_response) allow(twitter_client).to receive(:fetch_webhooks).and_return(
instance_double(::Twitty::Response, status: '200',
body: [{ 'url' => webhook_subscribe_service.send(:twitter_url) }])
)
webhook_subscribe_service.perform webhook_subscribe_service.perform
expect(twitter_client).not_to have_received(:subscribe_webhook) expect(twitter_client).not_to have_received(:unregister_webhook)
expect(twitter_client).not_to have_received(:register_webhook)
end
end
context 'when invalid webhook is registered' do
it 'calls unregister_webhook and then register webhook' do
allow(twitter_client).to receive(:fetch_webhooks).and_return(
instance_double(::Twitty::Response, status: '200',
body: [{ 'url' => 'invalid_url' }])
)
webhook_subscribe_service.perform
expect(twitter_client).to have_received(:unregister_webhook)
expect(twitter_client).to have_received(:register_webhook)
end
end
context 'when correct webhook is present' do
it 'calls create subscription if subscription is not present' do
allow(twitter_client).to receive(:fetch_webhooks).and_return(
instance_double(::Twitty::Response, status: '200',
body: [{ 'url' => webhook_subscribe_service.send(:twitter_url) }])
)
allow(twitter_client).to receive(:fetch_subscriptions).and_return(instance_double(::Twitty::Response, status: '500'))
webhook_subscribe_service.perform
expect(twitter_client).to have_received(:create_subscription)
end
it 'does not call create subscription if subscription is already present' do
allow(twitter_client).to receive(:fetch_webhooks).and_return(
instance_double(::Twitty::Response, status: '200',
body: [{ 'url' => webhook_subscribe_service.send(:twitter_url) }])
)
webhook_subscribe_service.perform
expect(twitter_client).not_to have_received(:create_subscription)
end end
end end
end end