Feature: Create conversations from Tweets (#470)
* Feature: Add tweets to conversations
This commit is contained in:
@@ -2,7 +2,7 @@ require 'open-uri'
|
||||
|
||||
# This class creates both outgoing messages from chatwoot and echo outgoing messages based on the flag `outgoing_echo`
|
||||
# Assumptions
|
||||
# 1. Incase of an outgoing message which is echo, fb_id will NOT be nil,
|
||||
# 1. Incase of an outgoing message which is echo, source_id will NOT be nil,
|
||||
# based on this we are showing "not sent from chatwoot" message in frontend
|
||||
# Hence there is no need to set user_id in message for outgoing echo messages.
|
||||
|
||||
@@ -121,7 +121,7 @@ class Messages::MessageBuilder
|
||||
inbox_id: conversation.inbox_id,
|
||||
message_type: @message_type,
|
||||
content: response.content,
|
||||
fb_id: response.identifier
|
||||
source_id: response.identifier
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ class Messages::Outgoing::NormalBuilder
|
||||
content: @content,
|
||||
private: @private,
|
||||
user_id: @user.id,
|
||||
fb_id: @fb_id
|
||||
source_id: @fb_id
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,7 +13,7 @@ class Api::V1::WebhooksController < ApplicationController
|
||||
end
|
||||
|
||||
def twitter_crc
|
||||
render json: { response_token: "sha256=#{$twitter.generate_crc(params[:crc_token])}" }
|
||||
render json: { response_token: "sha256=#{twitter_client.generate_crc(params[:crc_token])}" }
|
||||
end
|
||||
|
||||
def twitter_events
|
||||
@@ -26,6 +26,12 @@ class Api::V1::WebhooksController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def twitter_client
|
||||
Twitty::Facade.new do |config|
|
||||
config.consumer_secret = ENV.fetch('TWITTER_CONSUMER_SECRET', nil)
|
||||
end
|
||||
end
|
||||
|
||||
def login_from_basic_auth
|
||||
authenticate_or_request_with_http_basic do |username, password|
|
||||
username == ENV['CHARGEBEE_WEBHOOK_USERNAME'] && password == ENV['CHARGEBEE_WEBHOOK_PASSWORD']
|
||||
|
||||
@@ -16,19 +16,19 @@
|
||||
:size="avatarSize"
|
||||
/>
|
||||
<img
|
||||
v-if="badge === 'Channel::FacebookPage' && status !== ''"
|
||||
v-if="badge === 'Channel::FacebookPage'"
|
||||
id="badge"
|
||||
class="source-badge"
|
||||
:style="badgeStyle"
|
||||
src="~dashboard/assets/images/fb-badge.png"
|
||||
/>
|
||||
<div
|
||||
v-else-if="status === 'online'"
|
||||
v-else-if="badge === 'Channel::WebWidget' && status === 'online'"
|
||||
class="source-badge user--online"
|
||||
:style="statusStyle"
|
||||
></div>
|
||||
<img
|
||||
v-if="badge === 'Channel::TwitterProfile' && status !== ''"
|
||||
v-if="badge === 'Channel::TwitterProfile'"
|
||||
id="badge"
|
||||
class="source-badge"
|
||||
:style="badgeStyle"
|
||||
|
||||
@@ -20,6 +20,16 @@
|
||||
>
|
||||
{{ contact.email }}
|
||||
</a>
|
||||
|
||||
<div
|
||||
v-if="
|
||||
contact.additional_attributes &&
|
||||
contact.additional_attributes.screen_name
|
||||
"
|
||||
class="contact--location"
|
||||
>
|
||||
{{ `@${contact.additional_attributes.screen_name}` }}
|
||||
</div>
|
||||
<div class="contact--location">
|
||||
{{ contact.location }}
|
||||
</div>
|
||||
@@ -28,6 +38,15 @@
|
||||
<div v-if="contact.bio" class="contact--bio">
|
||||
{{ contact.bio }}
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
contact.additional_attributes &&
|
||||
contact.additional_attributes.description
|
||||
"
|
||||
class="contact--bio"
|
||||
>
|
||||
{{ contact.additional_attributes.description }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="browser" class="conversation--details">
|
||||
<contact-details-item
|
||||
|
||||
@@ -23,7 +23,7 @@ export default [
|
||||
private: false,
|
||||
user_id: 1,
|
||||
status: 'sent',
|
||||
fb_id: null,
|
||||
source_id: null,
|
||||
content_type: 'text',
|
||||
content_attributes: {},
|
||||
sender: {
|
||||
@@ -63,7 +63,7 @@ export default [
|
||||
private: false,
|
||||
user_id: 2,
|
||||
status: 'sent',
|
||||
fb_id: null,
|
||||
source_id: null,
|
||||
content_type: 'text',
|
||||
content_attributes: {},
|
||||
sender: {
|
||||
|
||||
@@ -28,9 +28,9 @@ class Channel::TwitterProfile < ApplicationRecord
|
||||
'Twitter'
|
||||
end
|
||||
|
||||
def create_contact_inbox(profile_id, name)
|
||||
def create_contact_inbox(profile_id, name, additional_attributes)
|
||||
ActiveRecord::Base.transaction do
|
||||
contact = inbox.account.contacts.create!(name: name)
|
||||
contact = inbox.account.contacts.create!(additional_attributes: additional_attributes, name: name)
|
||||
::ContactInbox.create!(
|
||||
contact_id: contact.id,
|
||||
inbox_id: inbox.id,
|
||||
|
||||
@@ -2,14 +2,15 @@
|
||||
#
|
||||
# Table name: contacts
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# email :string
|
||||
# name :string
|
||||
# phone_number :string
|
||||
# pubsub_token :string
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :integer not null
|
||||
# id :integer not null, primary key
|
||||
# additional_attributes :jsonb
|
||||
# email :string
|
||||
# name :string
|
||||
# phone_number :string
|
||||
# pubsub_token :string
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :integer not null
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
|
||||
@@ -12,14 +12,21 @@
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :integer not null
|
||||
# contact_id :bigint
|
||||
# conversation_id :integer not null
|
||||
# fb_id :string
|
||||
# inbox_id :integer not null
|
||||
# source_id :string
|
||||
# user_id :integer
|
||||
#
|
||||
# Indexes
|
||||
#
|
||||
# index_messages_on_contact_id (contact_id)
|
||||
# index_messages_on_conversation_id (conversation_id)
|
||||
# index_messages_on_source_id (source_id)
|
||||
#
|
||||
# Foreign Keys
|
||||
#
|
||||
# fk_rails_... (contact_id => contacts.id)
|
||||
#
|
||||
|
||||
class Message < ApplicationRecord
|
||||
|
||||
@@ -22,8 +22,8 @@ class Facebook::SendReplyService
|
||||
end
|
||||
|
||||
def outgoing_message_from_chatwoot?
|
||||
# messages sent directly from chatwoot won't have fb_id.
|
||||
message.outgoing? && !message.fb_id
|
||||
# messages sent directly from chatwoot won't have source_id.
|
||||
message.outgoing? && !message.source_id
|
||||
end
|
||||
|
||||
# def reopen_lock
|
||||
|
||||
@@ -3,22 +3,60 @@ class Twitter::SendReplyService
|
||||
|
||||
def perform
|
||||
return if message.private
|
||||
return if message.source_id
|
||||
return if inbox.channel.class.to_s != 'Channel::TwitterProfile'
|
||||
return unless outgoing_message_from_chatwoot?
|
||||
|
||||
$twitter.send_direct_message(
|
||||
send_reply
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def twitter_client
|
||||
Twitty::Facade.new do |config|
|
||||
config.consumer_key = ENV.fetch('TWITTER_CONSUMER_KEY', nil)
|
||||
config.consumer_secret = ENV.fetch('TWITTER_CONSUMER_SECRET', nil)
|
||||
config.access_token = channel.twitter_access_token
|
||||
config.access_token_secret = channel.twitter_access_token_secret
|
||||
config.base_url = 'https://api.twitter.com'
|
||||
config.environment = ENV.fetch('TWITTER_ENVIRONMENT', '')
|
||||
end
|
||||
end
|
||||
|
||||
def conversation_type
|
||||
conversation.additional_attributes['type']
|
||||
end
|
||||
|
||||
def screen_name
|
||||
"@#{additional_attributes ? additional_attributes['screen_name'] : ''} "
|
||||
end
|
||||
|
||||
def send_direct_message
|
||||
twitter_client.send_direct_message(
|
||||
recipient_id: contact_inbox.source_id,
|
||||
message: message.content
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
def send_tweet_reply
|
||||
twitter_client.send_tweet_reply(
|
||||
reply_to_tweet_id: conversation.additional_attributes['tweet_id'],
|
||||
tweet: screen_name + message.content
|
||||
)
|
||||
end
|
||||
|
||||
def send_reply
|
||||
conversation_type == 'tweet' ? send_tweet_reply : send_direct_message
|
||||
end
|
||||
|
||||
def outgoing_message_from_chatwoot?
|
||||
message.outgoing?
|
||||
end
|
||||
|
||||
delegate :additional_attributes, to: :contact
|
||||
delegate :contact, to: :conversation
|
||||
delegate :contact_inbox, to: :conversation
|
||||
delegate :conversation, to: :message
|
||||
delegate :inbox, to: :conversation
|
||||
delegate :channel, to: :inbox
|
||||
end
|
||||
|
||||
69
app/services/twitter/tweet_parser_service.rb
Normal file
69
app/services/twitter/tweet_parser_service.rb
Normal file
@@ -0,0 +1,69 @@
|
||||
class Twitter::TweetParserService < Twitter::WebhooksBaseService
|
||||
pattr_initialize [:payload]
|
||||
|
||||
def perform
|
||||
set_inbox
|
||||
find_or_create_contact(user)
|
||||
set_conversation
|
||||
@conversation.messages.create(
|
||||
account_id: @inbox.account_id,
|
||||
contact_id: @contact.id,
|
||||
content: tweet_text,
|
||||
inbox_id: @inbox.id,
|
||||
message_type: message_type,
|
||||
source_id: tweet_data['id'].to_s
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def message_type
|
||||
user['id'] == profile_id ? :outgoing : :incoming
|
||||
end
|
||||
|
||||
def tweet_text
|
||||
tweet_data['truncated'] ? tweet_data['extended_tweet']['full_text'] : tweet_data['text']
|
||||
end
|
||||
|
||||
def tweet_create_events_params
|
||||
payload['tweet_create_events']
|
||||
end
|
||||
|
||||
def tweet_data
|
||||
tweet_create_events_params.first
|
||||
end
|
||||
|
||||
def user
|
||||
tweet_data['user']
|
||||
end
|
||||
|
||||
def parent_tweet_id
|
||||
tweet_data['in_reply_to_status_id_str'].nil? ? tweet_data['id'].to_s : tweet_data['in_reply_to_status_id_str']
|
||||
end
|
||||
|
||||
def conversation_params
|
||||
{
|
||||
account_id: @inbox.account_id,
|
||||
inbox_id: @inbox.id,
|
||||
contact_id: @contact.id,
|
||||
contact_inbox_id: @contact_inbox.id,
|
||||
additional_attributes: {
|
||||
type: 'tweet',
|
||||
tweet_id: parent_tweet_id,
|
||||
tweet_source: tweet_data['source']
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def set_conversation
|
||||
tweet_conversations = @contact_inbox.conversations.where("additional_attributes ->> 'tweet_id' = ?", parent_tweet_id)
|
||||
@conversation = tweet_conversations.first
|
||||
return if @conversation
|
||||
|
||||
tweet_message = @inbox.messages.find_by(source_id: parent_tweet_id)
|
||||
@conversation = tweet_message.conversation if tweet_message
|
||||
return if @conversation
|
||||
|
||||
@conversation = ::Conversation.create!(conversation_params)
|
||||
end
|
||||
end
|
||||
@@ -5,6 +5,17 @@ class Twitter::WebhooksBaseService
|
||||
payload[:for_user_id]
|
||||
end
|
||||
|
||||
def additional_contact_attributes(user)
|
||||
{
|
||||
screen_name: user['screen_name'],
|
||||
location: user['location'],
|
||||
url: user['url'],
|
||||
description: user['description'],
|
||||
followers_count: user['followers_count'],
|
||||
friends_count: user['friends_count']
|
||||
}
|
||||
end
|
||||
|
||||
def set_inbox
|
||||
twitter_profile = ::Channel::TwitterProfile.find_by(profile_id: profile_id)
|
||||
@inbox = ::Inbox.find_by!(channel: twitter_profile)
|
||||
@@ -15,7 +26,9 @@ class Twitter::WebhooksBaseService
|
||||
@contact = @contact_inbox.contact if @contact_inbox
|
||||
return if @contact
|
||||
|
||||
@contact_inbox = @inbox.channel.create_contact_inbox(user['id'], user['name'])
|
||||
@contact_inbox = @inbox.channel.create_contact_inbox(
|
||||
user['id'], user['name'], additional_contact_attributes(user)
|
||||
)
|
||||
@contact = @contact_inbox.contact
|
||||
avatar_resource = LocalResource.new(user['profile_image_url'])
|
||||
@contact.avatar.attach(
|
||||
|
||||
@@ -5,5 +5,6 @@ json.payload do
|
||||
json.email contact.email
|
||||
json.phone_number contact.phone_number
|
||||
json.thumbnail contact.avatar.profile_thumb.url
|
||||
json.additional_attributes contact.additional_attributes
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,4 +5,5 @@ json.payload do
|
||||
json.name @contact.name
|
||||
json.phone_number @contact.phone_number
|
||||
json.thumbnail @contact.avatar_url
|
||||
json.additional_attributes @contact.additional_attributes
|
||||
end
|
||||
|
||||
@@ -4,4 +4,5 @@ json.payload do
|
||||
json.email @contact.email
|
||||
json.phone_number @contact.phone_number
|
||||
json.thumbnail @contact.avatar.thumb.url
|
||||
json.additional_attributes @contact.additional_attributes
|
||||
end
|
||||
|
||||
@@ -7,8 +7,9 @@ json.payload do
|
||||
json.message_type message.message_type_before_type_cast
|
||||
json.created_at message.created_at.to_i
|
||||
json.private message.private
|
||||
json.fb_id message.fb_id
|
||||
json.source_id message.source_id
|
||||
json.attachment message.attachment.push_event_data if message.attachment
|
||||
json.sender message.user.push_event_data if message.user
|
||||
json.contact message.contact
|
||||
end
|
||||
end
|
||||
|
||||
@@ -13,7 +13,7 @@ json.payload do
|
||||
json.message_type message.message_type_before_type_cast
|
||||
json.created_at message.created_at.to_i
|
||||
json.private message.private
|
||||
json.fb_id message.fb_id
|
||||
json.source_id message.source_id
|
||||
json.attachment message.attachment.push_event_data if message.attachment
|
||||
json.sender message.user.push_event_data if message.user
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user