Feature: Twilio Whatsapp Integration (#779)

Twilio Whatsapp Integration

Co-authored-by: Sojan <sojan@pepalo.com>
This commit is contained in:
Pranav Raj S
2020-04-30 01:41:13 +05:30
committed by GitHub
parent 168042f9a4
commit 0cb7333977
23 changed files with 238 additions and 81 deletions

View File

@@ -17,6 +17,7 @@ gem 'jbuilder'
gem 'kaminari' gem 'kaminari'
gem 'responders' gem 'responders'
gem 'rest-client' gem 'rest-client'
gem 'telephone_number'
gem 'time_diff' gem 'time_diff'
gem 'tzinfo-data' gem 'tzinfo-data'
gem 'valid_email2' gem 'valid_email2'

View File

@@ -448,6 +448,7 @@ GEM
faraday faraday
inflecto inflecto
virtus virtus
telephone_number (1.4.6)
thor (0.20.3) thor (0.20.3)
thread_safe (0.3.6) thread_safe (0.3.6)
time_diff (0.3.0) time_diff (0.3.0)
@@ -560,6 +561,7 @@ DEPENDENCIES
spring spring
spring-watcher-listen spring-watcher-listen
telegram-bot-ruby telegram-bot-ruby
telephone_number
time_diff time_diff
twilio-ruby (~> 5.32.0) twilio-ruby (~> 5.32.0)
twitty! twitty!

View File

@@ -21,13 +21,14 @@ class ContactBuilder
phone_number: contact_attributes[:phone_number], phone_number: contact_attributes[:phone_number],
email: contact_attributes[:email], email: contact_attributes[:email],
identifier: contact_attributes[:identifier], identifier: contact_attributes[:identifier],
additional_attributes: contact_attributes[:identifier] additional_attributes: contact_attributes[:additional_attributes]
) )
contact_inbox = ::ContactInbox.create!( contact_inbox = ::ContactInbox.create!(
contact_id: contact.id, contact_id: contact.id,
inbox_id: inbox.id, inbox_id: inbox.id,
source_id: source_id source_id: source_id
) )
::ContactAvatarJob.perform_later(contact, contact_attributes[:avatar_url]) if contact_attributes[:avatar_url] ::ContactAvatarJob.perform_later(contact, contact_attributes[:avatar_url]) if contact_attributes[:avatar_url]
contact_inbox contact_inbox
rescue StandardError => e rescue StandardError => e

View File

@@ -15,7 +15,6 @@ class Messages::Outgoing::NormalBuilder
def perform def perform
@message = @conversation.messages.build(message_params) @message = @conversation.messages.build(message_params)
@message.save
if @attachments.present? if @attachments.present?
@attachments.each do |uploaded_attachment| @attachments.each do |uploaded_attachment|
attachment = @message.attachments.new( attachment = @message.attachments.new(
@@ -24,8 +23,8 @@ class Messages::Outgoing::NormalBuilder
) )
attachment.file.attach(uploaded_attachment) attachment.file.attach(uploaded_attachment)
end end
@message.save
end end
@message.save
@message @message
end end

View File

@@ -2,13 +2,15 @@ class Api::V1::Accounts::Channels::TwilioChannelsController < Api::BaseControlle
before_action :authorize_request before_action :authorize_request
def create def create
authenticate_twilio ActiveRecord::Base.transaction do
build_inbox authenticate_twilio
setup_webhooks build_inbox
rescue Twilio::REST::TwilioError => e setup_webhooks if @twilio_channel.sms?
render_could_not_create_error(e.message) rescue Twilio::REST::TwilioError => e
rescue StandardError => e render_could_not_create_error(e.message)
render_could_not_create_error(e.message) rescue StandardError => e
render_could_not_create_error(e.message)
end
end end
private private
@@ -26,25 +28,30 @@ class Api::V1::Accounts::Channels::TwilioChannelsController < Api::BaseControlle
::Twilio::WebhookSetupService.new(inbox: @inbox).perform ::Twilio::WebhookSetupService.new(inbox: @inbox).perform
end end
def phone_number
medium == 'sms' ? permitted_params[:phone_number] : "whatsapp:#{permitted_params[:phone_number]}"
end
def medium
permitted_params[:medium]
end
def build_inbox def build_inbox
ActiveRecord::Base.transaction do @twilio_channel = current_account.twilio_sms.create!(
twilio_sms = current_account.twilio_sms.create( account_sid: permitted_params[:account_sid],
account_sid: permitted_params[:account_sid], auth_token: permitted_params[:auth_token],
auth_token: permitted_params[:auth_token], phone_number: phone_number,
phone_number: permitted_params[:phone_number] medium: medium
) )
@inbox = current_account.inboxes.create( @inbox = current_account.inboxes.create(
name: permitted_params[:name], name: permitted_params[:name],
channel: twilio_sms channel: @twilio_channel
) )
rescue StandardError => e
render_could_not_create_error(e.message)
end
end end
def permitted_params def permitted_params
params.require(:twilio_channel).permit( params.require(:twilio_channel).permit(
:account_id, :phone_number, :account_sid, :auth_token, :name :account_id, :phone_number, :account_sid, :auth_token, :name, :medium
) )
end end
end end

View File

@@ -23,7 +23,9 @@ class Twilio::CallbackController < ApplicationController
:FromZip, :FromZip,
:Body, :Body,
:ToCountry, :ToCountry,
:FromState :FromState,
:MediaUrl0,
:MediaContentType0
) )
end end
end end

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -33,6 +33,14 @@
:style="badgeStyle" :style="badgeStyle"
src="~dashboard/assets/images/twitter-badge.png" src="~dashboard/assets/images/twitter-badge.png"
/> />
<img
v-if="badge === 'Channel::TwilioSms'"
id="badge"
class="source-badge"
:style="badgeStyle"
src="~dashboard/assets/images/channels/whatsapp.png"
/>
</div> </div>
</template> </template>
<script> <script>

View File

@@ -26,6 +26,7 @@
<file-upload <file-upload
v-if="showFileUpload" v-if="showFileUpload"
:size="4096 * 4096" :size="4096 * 4096"
accept="jpg,jpeg,png,mp3,ogg,amr,pdf,mp4"
@input-file="onFileUpload" @input-file="onFileUpload"
> >
<i <i
@@ -142,7 +143,10 @@ export default {
return 10000; return 10000;
}, },
showFileUpload() { showFileUpload() {
return this.channelType === 'Channel::WebWidget'; return (
this.channelType === 'Channel::WebWidget' ||
this.channelType === 'Channel::TwilioSms'
);
}, },
replyButtonLabel() { replyButtonLabel() {
if (this.isPrivate) { if (this.isPrivate) {
@@ -295,6 +299,9 @@ export default {
}, },
onFileUpload(file) { onFileUpload(file) {
if (!file) {
return;
}
this.isUploading.image = true; this.isUploading.image = true;
this.$store this.$store
.dispatch('sendAttachment', [this.currentChat.id, { file: file.file }]) .dispatch('sendAttachment', [this.currentChat.id, { file: file.file }])

View File

@@ -6,10 +6,26 @@
"404": "Diesem Konto sind keine Posteingänge zugeordnet." "404": "Diesem Konto sind keine Posteingänge zugeordnet."
}, },
"CREATE_FLOW": [ "CREATE_FLOW": [
{ "title": "Wählen Sie Kanal", "route": "settings_inbox_new", "body": "Wählen Sie den Anbieter, den Sie in Chatwoot integrieren möchten." }, {
{ "title": "Posteingang erstellen", "route": "settings_inboxes_page_channel", "body": "Authentifizieren Sie Ihr Konto und erstellen Sie einen Posteingang." }, "title": "Wählen Sie Kanal",
{ "title": "Agenten hinzufügen", "route": "settings_inboxes_add_agents", "body": "Fügen Sie dem erstellten Posteingang Agenten hinzu." }, "route": "settings_inbox_new",
{ "title": "Voila!", "route": "settings_inbox_finish", "body": "Sie sind bereit zu gehen!" } "body": "Wählen Sie den Anbieter, den Sie in Chatwoot integrieren möchten."
},
{
"title": "Posteingang erstellen",
"route": "settings_inboxes_page_channel",
"body": "Authentifizieren Sie Ihr Konto und erstellen Sie einen Posteingang."
},
{
"title": "Agenten hinzufügen",
"route": "settings_inboxes_add_agents",
"body": "Fügen Sie dem erstellten Posteingang Agenten hinzu."
},
{
"title": "Voila!",
"route": "settings_inbox_finish",
"body": "Sie sind bereit zu gehen!"
}
], ],
"ADD": { "ADD": {
"FB": { "FB": {
@@ -33,12 +49,11 @@
"LABEL": "Widget Farbe", "LABEL": "Widget Farbe",
"PLACEHOLDER": "Aktualisieren Sie die im Widget verwendete Widget-Farbe" "PLACEHOLDER": "Aktualisieren Sie die im Widget verwendete Widget-Farbe"
}, },
"SUBMIT_BUTTON":"Posteingang erstellen" "SUBMIT_BUTTON": "Posteingang erstellen"
}, },
"TWILIO": { "TWILIO": {
"TITLE": "Twilio SMS Channel", "TITLE": "Twilio SMS/Whatsapp Channel",
"DESC": "Integrieren Sie Twilio und unterstützen Sie Ihre Kunden per SMS.", "DESC": "Integrieren Sie Twilio und unterstützen Sie Ihre Kunden per SMS/Whatsapp.",
"ACCOUNT_SID": { "ACCOUNT_SID": {
"LABEL": "Account SID", "LABEL": "Account SID",
"PLACEHOLDER": "Bitte geben Sie Ihre Twilio Account SID ein", "PLACEHOLDER": "Bitte geben Sie Ihre Twilio Account SID ein",
@@ -76,7 +91,7 @@
"TITLE": "Posteingangsdetails", "TITLE": "Posteingangsdetails",
"DESC": "Wählen Sie aus der Dropdown-Liste die Facebook-Seite aus, zu der Sie eine Verbindung zu Chatwoot herstellen möchten. Sie können Ihrem Posteingang auch einen benutzerdefinierten Namen geben, um ihn besser identifizieren zu können." "DESC": "Wählen Sie aus der Dropdown-Liste die Facebook-Seite aus, zu der Sie eine Verbindung zu Chatwoot herstellen möchten. Sie können Ihrem Posteingang auch einen benutzerdefinierten Namen geben, um ihn besser identifizieren zu können."
}, },
"FINISH":{ "FINISH": {
"TITLE": "Geschafft!", "TITLE": "Geschafft!",
"DESC": "Sie haben die Integration Ihrer Facebook-Seite in Chatwoot erfolgreich abgeschlossen. Wenn ein Kunde das nächste Mal eine Nachricht an Ihre Seite sendet, wird die Konversation automatisch in Ihrem Posteingang angezeigt. <br> Wir stellen Ihnen außerdem ein Widget-Skript zur Verfügung, das Sie ganz einfach zu Ihrer Website hinzufügen können. Sobald dies auf Ihrer Website live ist, können Kunden Ihnen ohne Hilfe eines externen Tools direkt von Ihrer Website aus eine Nachricht senden, und die Konversation wird direkt hier auf Chatwoot angezeigt. <br> Cool, oder? Nun, wir versuchen es auf jeden Fall :)" "DESC": "Sie haben die Integration Ihrer Facebook-Seite in Chatwoot erfolgreich abgeschlossen. Wenn ein Kunde das nächste Mal eine Nachricht an Ihre Seite sendet, wird die Konversation automatisch in Ihrem Posteingang angezeigt. <br> Wir stellen Ihnen außerdem ein Widget-Skript zur Verfügung, das Sie ganz einfach zu Ihrer Website hinzufügen können. Sobald dies auf Ihrer Website live ist, können Kunden Ihnen ohne Hilfe eines externen Tools direkt von Ihrer Website aus eine Nachricht senden, und die Konversation wird direkt hier auf Chatwoot angezeigt. <br> Cool, oder? Nun, wir versuchen es auf jeden Fall :)"
} }

View File

@@ -6,10 +6,26 @@
"404": "There are no inboxes attached to this account." "404": "There are no inboxes attached to this account."
}, },
"CREATE_FLOW": [ "CREATE_FLOW": [
{ "title": "Choose Channel", "route": "settings_inbox_new", "body": "Choose the provider you want to integrate with Chatwoot." }, {
{ "title": "Create Inbox", "route": "settings_inboxes_page_channel", "body": "Authenticate your account and create an inbox." }, "title": "Choose Channel",
{ "title": "Add Agents", "route": "settings_inboxes_add_agents", "body": "Add agents to the created inbox." }, "route": "settings_inbox_new",
{ "title": "Voila!", "route": "settings_inbox_finish", "body": "You are all set to go!" } "body": "Choose the provider you want to integrate with Chatwoot."
},
{
"title": "Create Inbox",
"route": "settings_inboxes_page_channel",
"body": "Authenticate your account and create an inbox."
},
{
"title": "Add Agents",
"route": "settings_inboxes_add_agents",
"body": "Add agents to the created inbox."
},
{
"title": "Voila!",
"route": "settings_inbox_finish",
"body": "You are all set to go!"
}
], ],
"ADD": { "ADD": {
"FB": { "FB": {
@@ -46,16 +62,20 @@
"LABEL": "Widget Color", "LABEL": "Widget Color",
"PLACEHOLDER": "Update the widget color used in widget" "PLACEHOLDER": "Update the widget color used in widget"
}, },
"SUBMIT_BUTTON":"Create inbox" "SUBMIT_BUTTON": "Create inbox"
}, },
"TWILIO": { "TWILIO": {
"TITLE": "Twilio SMS Channel", "TITLE": "Twilio SMS/Whatsapp Channel",
"DESC": "Integrate Twilio and start supporting your customers via SMS.", "DESC": "Integrate Twilio and start supporting your customers via SMS or Whatsapp.",
"ACCOUNT_SID": { "ACCOUNT_SID": {
"LABEL": "Account SID", "LABEL": "Account SID",
"PLACEHOLDER": "Please enter your Twilio Account SID", "PLACEHOLDER": "Please enter your Twilio Account SID",
"ERROR": "This field is required" "ERROR": "This field is required"
}, },
"CHANNEL_TYPE": {
"LABEL": "Channel Type",
"ERROR": "Please select your Channel Type"
},
"AUTH_TOKEN": { "AUTH_TOKEN": {
"LABEL": "Auth Token", "LABEL": "Auth Token",
"PLACEHOLDER": "Please enter your Twilio Auth Token", "PLACEHOLDER": "Please enter your Twilio Auth Token",
@@ -88,7 +108,7 @@
"TITLE": "Inbox Details", "TITLE": "Inbox Details",
"DESC": "From the dropdown below, select the Facebook Page you want to connect to Chatwoot. You can also give a custom name to your inbox for better identification." "DESC": "From the dropdown below, select the Facebook Page you want to connect to Chatwoot. You can also give a custom name to your inbox for better identification."
}, },
"FINISH":{ "FINISH": {
"TITLE": "Nailed It!", "TITLE": "Nailed It!",
"DESC": "You have successfully finished integrating your Facebook Page with Chatwoot. Next time a customer messages your Page, the conversation will automatically appear on your inbox.<br>We are also providing you with a widget script that you can easily add to your website. Once this is live on your website, customers can message you right from your website without the help of any external tool and the conversation will appear right here, on Chatwoot.<br>Cool, huh? Well, we sure try to be :)" "DESC": "You have successfully finished integrating your Facebook Page with Chatwoot. Next time a customer messages your Page, the conversation will automatically appear on your inbox.<br>We are also providing you with a widget script that you can easily add to your website. Once this is live on your website, customers can message you right from your website without the help of any external tool and the conversation will appear right here, on Chatwoot.<br>Cool, huh? Well, we sure try to be :)"
} }

View File

@@ -23,6 +23,13 @@
> >
{{ contact.email }} {{ contact.email }}
</a> </a>
<a
v-if="contact.phone_number"
:href="`tel:${contact.phone_number}`"
class="contact--email"
>
{{ contact.phone_number }}
</a>
<div <div
v-if=" v-if="
@@ -211,7 +218,7 @@ export default {
text-transform: capitalize; text-transform: capitalize;
font-weight: $font-weight-bold; font-weight: $font-weight-bold;
font-size: $font-size-medium; font-size: $font-size-default;
} }
.contact--email { .contact--email {

View File

@@ -14,9 +14,22 @@
:placeholder="$t('INBOX_MGMT.ADD.TWILIO.CHANNEL_NAME.PLACEHOLDER')" :placeholder="$t('INBOX_MGMT.ADD.TWILIO.CHANNEL_NAME.PLACEHOLDER')"
@blur="$v.channelName.$touch" @blur="$v.channelName.$touch"
/> />
<span v-if="$v.channelName.$error" class="message"> <span v-if="$v.channelName.$error" class="message">{{
{{ $t('INBOX_MGMT.ADD.TWILIO.CHANNEL_NAME.ERROR') }} $t('INBOX_MGMT.ADD.TWILIO.CHANNEL_NAME.ERROR')
</span> }}</span>
</label>
</div>
<div class="medium-8 columns">
<label :class="{ error: $v.medium.$error }">
{{ $t('INBOX_MGMT.ADD.TWILIO.CHANNEL_TYPE.LABEL') }}
<select v-model="medium">
<option value="sms">SMS</option>
<option value="whatsapp">Whatsapp</option>
</select>
<span v-if="$v.medium.$error" class="message">{{
$t('INBOX_MGMT.ADD.TWILIO.CHANNEL_TYPE.ERROR')
}}</span>
</label> </label>
</div> </div>
@@ -29,9 +42,9 @@
:placeholder="$t('INBOX_MGMT.ADD.TWILIO.PHONE_NUMBER.PLACEHOLDER')" :placeholder="$t('INBOX_MGMT.ADD.TWILIO.PHONE_NUMBER.PLACEHOLDER')"
@blur="$v.phoneNumber.$touch" @blur="$v.phoneNumber.$touch"
/> />
<span v-if="$v.phoneNumber.$error" class="message"> <span v-if="$v.phoneNumber.$error" class="message">{{
{{ $t('INBOX_MGMT.ADD.TWILIO.PHONE_NUMBER.ERROR') }} $t('INBOX_MGMT.ADD.TWILIO.PHONE_NUMBER.ERROR')
</span> }}</span>
</label> </label>
</div> </div>
@@ -44,9 +57,9 @@
:placeholder="$t('INBOX_MGMT.ADD.TWILIO.ACCOUNT_SID.PLACEHOLDER')" :placeholder="$t('INBOX_MGMT.ADD.TWILIO.ACCOUNT_SID.PLACEHOLDER')"
@blur="$v.accountSID.$touch" @blur="$v.accountSID.$touch"
/> />
<span v-if="$v.accountSID.$error" class="message"> <span v-if="$v.accountSID.$error" class="message">{{
{{ $t('INBOX_MGMT.ADD.TWILIO.ACCOUNT_SID.ERROR') }} $t('INBOX_MGMT.ADD.TWILIO.ACCOUNT_SID.ERROR')
</span> }}</span>
</label> </label>
</div> </div>
<div class="medium-8 columns"> <div class="medium-8 columns">
@@ -58,9 +71,9 @@
:placeholder="$t('INBOX_MGMT.ADD.TWILIO.AUTH_TOKEN.PLACEHOLDER')" :placeholder="$t('INBOX_MGMT.ADD.TWILIO.AUTH_TOKEN.PLACEHOLDER')"
@blur="$v.authToken.$touch" @blur="$v.authToken.$touch"
/> />
<span v-if="$v.authToken.$error" class="message"> <span v-if="$v.authToken.$error" class="message">{{
{{ $t('INBOX_MGMT.ADD.TWILIO.AUTH_TOKEN.ERROR') }} $t('INBOX_MGMT.ADD.TWILIO.AUTH_TOKEN.ERROR')
</span> }}</span>
</label> </label>
</div> </div>
@@ -92,6 +105,7 @@ export default {
return { return {
accountSID: '', accountSID: '',
authToken: '', authToken: '',
medium: '',
channelName: '', channelName: '',
phoneNumber: '', phoneNumber: '',
}; };
@@ -106,6 +120,7 @@ export default {
phoneNumber: { required, shouldStartWithPlusSign }, phoneNumber: { required, shouldStartWithPlusSign },
authToken: { required }, authToken: { required },
accountSID: { required }, accountSID: { required },
medium: { required },
}, },
methods: { methods: {
async createChannel() { async createChannel() {
@@ -120,6 +135,7 @@ export default {
{ {
twilio_channel: { twilio_channel: {
name: this.channelName, name: this.channelName,
medium: this.medium,
account_sid: this.accountSID, account_sid: this.accountSID,
auth_token: this.authToken, auth_token: this.authToken,
phone_number: this.phoneNumber, phone_number: this.phoneNumber,

View File

@@ -5,6 +5,7 @@
# id :bigint not null, primary key # id :bigint not null, primary key
# account_sid :string not null # account_sid :string not null
# auth_token :string not null # auth_token :string not null
# medium :integer default("sms")
# phone_number :string not null # phone_number :string not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
@@ -23,6 +24,8 @@ class Channel::TwilioSms < ApplicationRecord
validates :auth_token, presence: true validates :auth_token, presence: true
validates :phone_number, uniqueness: { scope: :account_id }, presence: true validates :phone_number, uniqueness: { scope: :account_id }, presence: true
enum medium: { sms: 0, whatsapp: 1 }
belongs_to :account belongs_to :account
has_one :inbox, as: :channel, dependent: :destroy has_one :inbox, as: :channel, dependent: :destroy

View File

@@ -70,11 +70,12 @@ class Message < ApplicationRecord
has_many :attachments, dependent: :destroy, autosave: true, before_add: :validate_attachments_limit has_many :attachments, dependent: :destroy, autosave: true, before_add: :validate_attachments_limit
after_create :reopen_conversation, after_create :reopen_conversation,
:dispatch_event,
:send_reply,
:execute_message_template_hooks, :execute_message_template_hooks,
:notify_via_mail :notify_via_mail
# we need to wait for the active storage attachments to be available
after_create_commit :dispatch_event, :send_reply
after_update :dispatch_update_event after_update :dispatch_update_event
def channel_token def channel_token

View File

@@ -1,10 +1,12 @@
class Twilio::IncomingMessageService class Twilio::IncomingMessageService
include ::FileTypeHelper
pattr_initialize [:params!] pattr_initialize [:params!]
def perform def perform
set_contact set_contact
set_conversation set_conversation
@conversation.messages.create( @message = @conversation.messages.create(
content: params[:Body], content: params[:Body],
account_id: @inbox.account_id, account_id: @inbox.account_id,
inbox_id: @inbox.id, inbox_id: @inbox.id,
@@ -12,6 +14,7 @@ class Twilio::IncomingMessageService
contact_id: @contact.id, contact_id: @contact.id,
source_id: params[:SmsSid] source_id: params[:SmsSid]
) )
attach_files
end end
private private
@@ -31,6 +34,14 @@ class Twilio::IncomingMessageService
@account ||= inbox.account @account ||= inbox.account
end end
def phone_number
twilio_inbox.sms? ? params[:From] : params[:From].gsub('whatsapp:', '')
end
def formatted_phone_number
TelephoneNumber.parse(phone_number).international_number
end
def set_contact def set_contact
contact_inbox = ::ContactBuilder.new( contact_inbox = ::ContactBuilder.new(
source_id: params[:From], source_id: params[:From],
@@ -61,17 +72,40 @@ class Twilio::IncomingMessageService
def contact_attributes def contact_attributes
{ {
name: params[:From], name: formatted_phone_number,
phone_number: params[:From], phone_number: phone_number,
contact_attributes: additional_attributes additional_attributes: additional_attributes
} }
end end
def additional_attributes def additional_attributes
{ if twilio_inbox.sms?
from_zip_code: params[:FromZip], {
from_country: params[:FromCountry], from_zip_code: params[:FromZip],
from_state: params[:FromState] from_country: params[:FromCountry],
} from_state: params[:FromState]
}
else
{}
end
end
def attach_files
return if params[:MediaUrl0].blank?
file_resource = LocalResource.new(params[:MediaUrl0], params[:MediaContentType0])
attachment = @message.attachments.new(
account_id: @message.account_id,
file_type: file_type(params[:MediaContentType0])
)
attachment.file.attach(
io: file_resource.file,
filename: file_resource.tmp_filename,
content_type: file_resource.encoding
)
@message.save!
end end
end end

View File

@@ -7,11 +7,7 @@ class Twilio::OutgoingMessageService
return if inbox.channel.class.to_s != 'Channel::TwilioSms' return if inbox.channel.class.to_s != 'Channel::TwilioSms'
return unless message.outgoing? return unless message.outgoing?
twilio_message = client.messages.create( twilio_message = client.messages.create(message_params)
body: message.content,
from: channel.phone_number,
to: contact.phone_number
)
message.update!(source_id: twilio_message.sid) message.update!(source_id: twilio_message.sid)
end end
@@ -19,6 +15,21 @@ class Twilio::OutgoingMessageService
delegate :conversation, to: :message delegate :conversation, to: :message
delegate :contact, to: :conversation delegate :contact, to: :conversation
delegate :contact_inbox, to: :conversation
def message_params
params = {
body: message.content,
from: channel.phone_number,
to: contact_inbox.source_id
}
params[:media_url] = attachments if channel.whatsapp? && message.attachments.present?
params
end
def attachments
message.attachments.map(&:file_url)
end
def inbox def inbox
@inbox ||= message.inbox @inbox ||= message.inbox

View File

@@ -0,0 +1,5 @@
class AddMediumToTwilioSms < ActiveRecord::Migration[6.0]
def change
add_column :channel_twilio_sms, :medium, :integer, index: true, default: 0
end
end

View File

@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_04_17_093432) do ActiveRecord::Schema.define(version: 2020_04_29_082655) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@@ -121,6 +121,7 @@ ActiveRecord::Schema.define(version: 2020_04_17_093432) do
t.integer "account_id", null: false t.integer "account_id", null: false
t.datetime "created_at", precision: 6, null: false t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false
t.integer "medium", default: 0
t.index ["account_id", "phone_number"], name: "index_channel_twilio_sms_on_account_id_and_phone_number", unique: true t.index ["account_id", "phone_number"], name: "index_channel_twilio_sms_on_account_id_and_phone_number", unique: true
end end
@@ -287,11 +288,9 @@ ActiveRecord::Schema.define(version: 2020_04_17_093432) do
t.index ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context" t.index ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context"
t.index ["taggable_id", "taggable_type", "tagger_id", "context"], name: "taggings_idy" t.index ["taggable_id", "taggable_type", "tagger_id", "context"], name: "taggings_idy"
t.index ["taggable_id"], name: "index_taggings_on_taggable_id" t.index ["taggable_id"], name: "index_taggings_on_taggable_id"
t.index ["taggable_type", "taggable_id"], name: "index_taggings_on_taggable_type_and_taggable_id"
t.index ["taggable_type"], name: "index_taggings_on_taggable_type" t.index ["taggable_type"], name: "index_taggings_on_taggable_type"
t.index ["tagger_id", "tagger_type"], name: "index_taggings_on_tagger_id_and_tagger_type" t.index ["tagger_id", "tagger_type"], name: "index_taggings_on_tagger_id_and_tagger_type"
t.index ["tagger_id"], name: "index_taggings_on_tagger_id" t.index ["tagger_id"], name: "index_taggings_on_tagger_id"
t.index ["tagger_type", "tagger_id"], name: "index_taggings_on_tagger_type_and_tagger_id"
end end
create_table "tags", id: :serial, force: :cascade do |t| create_table "tags", id: :serial, force: :cascade do |t|

View File

@@ -1,8 +1,9 @@
class LocalResource class LocalResource
attr_reader :uri attr_reader :uri
def initialize(uri) def initialize(uri, file_type = nil)
@uri = URI(uri) @uri = URI(uri)
@file_type = file_type
end end
def file def file
@@ -23,11 +24,12 @@ class LocalResource
io.read.encoding io.read.encoding
end end
def find_file_type
@file_type ? @file_type.split('/').last : Pathname.new(uri.path).extname
end
def tmp_filename def tmp_filename
[ [Time.now.to_i.to_s, find_file_type].join('.')
Time.now.to_i.to_s,
Pathname.new(uri.path).extname
]
end end
def tmp_folder def tmp_folder

View File

@@ -21,7 +21,8 @@ RSpec.describe '/api/v1/accounts/{account.id}/channels/twilio_channel', type: :r
account_sid: 'sid', account_sid: 'sid',
auth_token: 'token', auth_token: 'token',
phone_number: '+1234567890', phone_number: '+1234567890',
name: 'SMS Channel' name: 'SMS Channel',
medium: 'sms'
} }
} }
end end

View File

@@ -3,6 +3,7 @@ FactoryBot.define do
auth_token { SecureRandom.uuid } auth_token { SecureRandom.uuid }
account_sid { SecureRandom.uuid } account_sid { SecureRandom.uuid }
sequence(:phone_number) { |n| "+123456789#{n}1" } sequence(:phone_number) { |n| "+123456789#{n}1" }
medium { :sms }
inbox inbox
account account
end end

View File

@@ -10,7 +10,9 @@ describe Twilio::OutgoingMessageService do
let!(:account) { create(:account) } let!(:account) { create(:account) }
let!(:widget_inbox) { create(:inbox, account: account) } let!(:widget_inbox) { create(:inbox, account: account) }
let!(:twilio_sms) { create(:channel_twilio_sms, account: account) } let!(:twilio_sms) { create(:channel_twilio_sms, account: account) }
let!(:twilio_whatsapp) { create(:channel_twilio_sms, medium: :whatsapp, account: account) }
let!(:twilio_inbox) { create(:inbox, channel: twilio_sms, account: account) } let!(:twilio_inbox) { create(:inbox, channel: twilio_sms, account: account) }
let!(:twilio_whatsapp_inbox) { create(:inbox, channel: twilio_whatsapp, account: account) }
let!(:contact) { create(:contact, account: account) } let!(:contact) { create(:contact, account: account) }
let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: twilio_inbox) } let(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: twilio_inbox) }
let(:conversation) { create(:conversation, contact: contact, inbox: twilio_inbox, contact_inbox: contact_inbox) } let(:conversation) { create(:conversation, contact: contact, inbox: twilio_inbox, contact_inbox: contact_inbox) }
@@ -55,5 +57,18 @@ describe Twilio::OutgoingMessageService do
expect(outgoing_message.reload.source_id).to eq('1234') expect(outgoing_message.reload.source_id).to eq('1234')
end end
end end
it 'if outgoing message has attachment and is for whatsapp' do
# check for message attachment url
allow(messages_double).to receive(:create).with(hash_including(media_url: [anything])).and_return(message_record_double)
allow(message_record_double).to receive(:sid).and_return('1234')
message = build(
:message, message_type: 'outgoing', inbox: twilio_whatsapp_inbox, account: account, conversation: conversation
)
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')
message.save!
end
end end
end end