Feature: Twilio Whatsapp Integration (#779)
Twilio Whatsapp Integration Co-authored-by: Sojan <sojan@pepalo.com>
This commit is contained in:
1
Gemfile
1
Gemfile
@@ -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'
|
||||||
|
|||||||
@@ -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!
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -2,14 +2,16 @@ class Api::V1::Accounts::Channels::TwilioChannelsController < Api::BaseControlle
|
|||||||
before_action :authorize_request
|
before_action :authorize_request
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
ActiveRecord::Base.transaction do
|
||||||
authenticate_twilio
|
authenticate_twilio
|
||||||
build_inbox
|
build_inbox
|
||||||
setup_webhooks
|
setup_webhooks if @twilio_channel.sms?
|
||||||
rescue Twilio::REST::TwilioError => e
|
rescue Twilio::REST::TwilioError => e
|
||||||
render_could_not_create_error(e.message)
|
render_could_not_create_error(e.message)
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
render_could_not_create_error(e.message)
|
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: permitted_params[:phone_number]
|
phone_number: 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
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ class Twilio::CallbackController < ApplicationController
|
|||||||
:FromZip,
|
:FromZip,
|
||||||
:Body,
|
:Body,
|
||||||
:ToCountry,
|
:ToCountry,
|
||||||
:FromState
|
:FromState,
|
||||||
|
:MediaUrl0,
|
||||||
|
:MediaContentType0
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
BIN
app/javascript/dashboard/assets/images/channels/whatsapp.png
Normal file
BIN
app/javascript/dashboard/assets/images/channels/whatsapp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
@@ -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>
|
||||||
|
|||||||
@@ -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 }])
|
||||||
|
|||||||
@@ -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": {
|
||||||
@@ -35,10 +51,9 @@
|
|||||||
},
|
},
|
||||||
"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",
|
||||||
|
|||||||
@@ -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": {
|
||||||
@@ -49,13 +65,17 @@
|
|||||||
"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",
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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_zip_code: params[:FromZip],
|
||||||
from_country: params[:FromCountry],
|
from_country: params[:FromCountry],
|
||||||
from_state: params[:FromState]
|
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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
5
db/migrate/20200429082655_add_medium_to_twilio_sms.rb
Normal file
5
db/migrate/20200429082655_add_medium_to_twilio_sms.rb
Normal 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
|
||||||
@@ -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|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user