Chore: Twitter Integration house cleaning (#855)
Co-authored-by: Pranav Raj Sreepuram <pranavrajs@gmail.com>
This commit is contained in:
parent
1108446974
commit
20f39caa42
11 changed files with 159 additions and 63 deletions
3
Gemfile
3
Gemfile
|
@ -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
|
||||||
|
|
11
Gemfile.lock
11
Gemfile.lock
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue