Fixes #3672 - MessageBird integration
This commit is contained in:
parent
0402c755de
commit
1cf8ec8969
15 changed files with 562 additions and 81 deletions
|
@ -284,6 +284,7 @@ RSpec/ExampleLength:
|
||||||
- 'spec/requests/integration/smime_spec.rb'
|
- 'spec/requests/integration/smime_spec.rb'
|
||||||
- 'spec/requests/integration/telegram_spec.rb'
|
- 'spec/requests/integration/telegram_spec.rb'
|
||||||
- 'spec/requests/integration/twilio_sms_spec.rb'
|
- 'spec/requests/integration/twilio_sms_spec.rb'
|
||||||
|
- 'spec/requests/integration/message_bird_sms_spec.rb'
|
||||||
- 'spec/requests/integration/user_device_spec.rb'
|
- 'spec/requests/integration/user_device_spec.rb'
|
||||||
- 'spec/requests/knowledge_base/answer_attachments_cloning_spec.rb'
|
- 'spec/requests/knowledge_base/answer_attachments_cloning_spec.rb'
|
||||||
- 'spec/requests/links_spec.rb'
|
- 'spec/requests/links_spec.rb'
|
||||||
|
@ -315,6 +316,7 @@ RSpec/ExpectActual:
|
||||||
- 'spec/requests/integration/monitoring_spec.rb'
|
- 'spec/requests/integration/monitoring_spec.rb'
|
||||||
- 'spec/requests/integration/object_manager_attributes_spec.rb'
|
- 'spec/requests/integration/object_manager_attributes_spec.rb'
|
||||||
- 'spec/requests/integration/twilio_sms_spec.rb'
|
- 'spec/requests/integration/twilio_sms_spec.rb'
|
||||||
|
- 'spec/requests/integration/message_bird_sms_spec.rb'
|
||||||
- 'spec/requests/integration/user_device_spec.rb'
|
- 'spec/requests/integration/user_device_spec.rb'
|
||||||
- 'spec/requests/organization_spec.rb'
|
- 'spec/requests/organization_spec.rb'
|
||||||
- 'spec/requests/ticket/article_attachments_spec.rb'
|
- 'spec/requests/ticket/article_attachments_spec.rb'
|
||||||
|
@ -565,6 +567,7 @@ RSpec/MultipleExpectations:
|
||||||
- 'spec/requests/integration/smime_spec.rb'
|
- 'spec/requests/integration/smime_spec.rb'
|
||||||
- 'spec/requests/integration/telegram_spec.rb'
|
- 'spec/requests/integration/telegram_spec.rb'
|
||||||
- 'spec/requests/integration/twilio_sms_spec.rb'
|
- 'spec/requests/integration/twilio_sms_spec.rb'
|
||||||
|
- 'spec/requests/integration/message_bird_sms_spec.rb'
|
||||||
- 'spec/requests/integration/user_device_spec.rb'
|
- 'spec/requests/integration/user_device_spec.rb'
|
||||||
- 'spec/requests/links_spec.rb'
|
- 'spec/requests/links_spec.rb'
|
||||||
- 'spec/requests/long_polling_spec.rb'
|
- 'spec/requests/long_polling_spec.rb'
|
||||||
|
|
|
@ -105,8 +105,6 @@ Metrics/AbcSize:
|
||||||
- 'app/models/channel/driver/imap.rb'
|
- 'app/models/channel/driver/imap.rb'
|
||||||
- 'app/models/channel/driver/pop3.rb'
|
- 'app/models/channel/driver/pop3.rb'
|
||||||
- 'app/models/channel/driver/sendmail.rb'
|
- 'app/models/channel/driver/sendmail.rb'
|
||||||
- 'app/models/channel/driver/sms/massenversand.rb'
|
|
||||||
- 'app/models/channel/driver/sms/twilio.rb'
|
|
||||||
- 'app/models/channel/driver/smtp.rb'
|
- 'app/models/channel/driver/smtp.rb'
|
||||||
- 'app/models/channel/driver/twitter.rb'
|
- 'app/models/channel/driver/twitter.rb'
|
||||||
- 'app/models/channel/email_build.rb'
|
- 'app/models/channel/email_build.rb'
|
||||||
|
@ -509,7 +507,6 @@ Metrics/CyclomaticComplexity:
|
||||||
- 'app/models/channel/driver/facebook.rb'
|
- 'app/models/channel/driver/facebook.rb'
|
||||||
- 'app/models/channel/driver/imap.rb'
|
- 'app/models/channel/driver/imap.rb'
|
||||||
- 'app/models/channel/driver/pop3.rb'
|
- 'app/models/channel/driver/pop3.rb'
|
||||||
- 'app/models/channel/driver/sms/twilio.rb'
|
|
||||||
- 'app/models/channel/driver/smtp.rb'
|
- 'app/models/channel/driver/smtp.rb'
|
||||||
- 'app/models/channel/driver/twitter.rb'
|
- 'app/models/channel/driver/twitter.rb'
|
||||||
- 'app/models/channel/email_build.rb'
|
- 'app/models/channel/email_build.rb'
|
||||||
|
@ -743,7 +740,6 @@ Metrics/PerceivedComplexity:
|
||||||
- 'app/models/channel/driver/facebook.rb'
|
- 'app/models/channel/driver/facebook.rb'
|
||||||
- 'app/models/channel/driver/imap.rb'
|
- 'app/models/channel/driver/imap.rb'
|
||||||
- 'app/models/channel/driver/pop3.rb'
|
- 'app/models/channel/driver/pop3.rb'
|
||||||
- 'app/models/channel/driver/sms/twilio.rb'
|
|
||||||
- 'app/models/channel/driver/smtp.rb'
|
- 'app/models/channel/driver/smtp.rb'
|
||||||
- 'app/models/channel/driver/twitter.rb'
|
- 'app/models/channel/driver/twitter.rb'
|
||||||
- 'app/models/channel/email_build.rb'
|
- 'app/models/channel/email_build.rb'
|
||||||
|
@ -921,6 +917,7 @@ Style/OptionalBooleanParameter:
|
||||||
- 'app/models/channel/driver/facebook.rb'
|
- 'app/models/channel/driver/facebook.rb'
|
||||||
- 'app/models/channel/driver/sendmail.rb'
|
- 'app/models/channel/driver/sendmail.rb'
|
||||||
- 'app/models/channel/driver/sms/massenversand.rb'
|
- 'app/models/channel/driver/sms/massenversand.rb'
|
||||||
|
- 'app/models/channel/driver/sms/message_bird.rb'
|
||||||
- 'app/models/channel/driver/sms/twilio.rb'
|
- 'app/models/channel/driver/sms/twilio.rb'
|
||||||
- 'app/models/channel/driver/smtp.rb'
|
- 'app/models/channel/driver/smtp.rb'
|
||||||
- 'app/models/channel/driver/telegram.rb'
|
- 'app/models/channel/driver/telegram.rb'
|
||||||
|
|
1
Gemfile
1
Gemfile
|
@ -127,6 +127,7 @@ gem 'icalendar-recurrence'
|
||||||
gem 'telephone_number'
|
gem 'telephone_number'
|
||||||
|
|
||||||
# feature - SMS
|
# feature - SMS
|
||||||
|
gem 'messagebird-rest'
|
||||||
gem 'twilio-ruby', require: false
|
gem 'twilio-ruby', require: false
|
||||||
|
|
||||||
# feature - ordering
|
# feature - ordering
|
||||||
|
|
|
@ -313,6 +313,7 @@ GEM
|
||||||
marcel (1.0.1)
|
marcel (1.0.1)
|
||||||
memoizable (0.4.2)
|
memoizable (0.4.2)
|
||||||
thread_safe (~> 0.3, >= 0.3.1)
|
thread_safe (~> 0.3, >= 0.3.1)
|
||||||
|
messagebird-rest (2.0.0)
|
||||||
method_source (1.0.0)
|
method_source (1.0.0)
|
||||||
mime-types (3.3.1)
|
mime-types (3.3.1)
|
||||||
mime-types-data (~> 3.2015)
|
mime-types-data (~> 3.2015)
|
||||||
|
@ -692,6 +693,7 @@ DEPENDENCIES
|
||||||
koala
|
koala
|
||||||
libv8
|
libv8
|
||||||
mail!
|
mail!
|
||||||
|
messagebird-rest
|
||||||
mime-types
|
mime-types
|
||||||
mini_racer (= 0.2.9)
|
mini_racer (= 0.2.9)
|
||||||
mysql2
|
mysql2
|
||||||
|
|
|
@ -98,6 +98,8 @@ class ChannelsSmsController < ApplicationController
|
||||||
list = []
|
list = []
|
||||||
Dir.glob(Rails.root.join('app/models/channel/driver/sms/*.rb')).each do |path|
|
Dir.glob(Rails.root.join('app/models/channel/driver/sms/*.rb')).each do |path|
|
||||||
filename = File.basename(path)
|
filename = File.basename(path)
|
||||||
|
next if !Channel.driver_class("sms/#{filename}").const_defined?(:NAME)
|
||||||
|
|
||||||
list.push Channel.driver_class("sms/#{filename}").definition
|
list.push Channel.driver_class("sms/#{filename}").definition
|
||||||
end
|
end
|
||||||
list
|
list
|
||||||
|
|
68
app/models/channel/driver/sms/base.rb
Normal file
68
app/models/channel/driver/sms/base.rb
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||||
|
|
||||||
|
class Channel::Driver::Sms::Base
|
||||||
|
def user_by_cti(mobile)
|
||||||
|
_from_comment, preferences = Cti::CallerId.get_comment_preferences(mobile, 'from')
|
||||||
|
user = nil
|
||||||
|
if preferences && preferences['from'] && preferences['from'][0] && preferences['from'][0]['level'] == 'known' && preferences['from'][0]['object'] == 'User'
|
||||||
|
user = User.find_by(id: preferences['from'][0]['o_id'])
|
||||||
|
end
|
||||||
|
user
|
||||||
|
end
|
||||||
|
|
||||||
|
def user_by_mobile(mobile)
|
||||||
|
User.where(mobile: mobile).order(:updated_at).first || user_by_cti(mobile) || User.create!(
|
||||||
|
firstname: mobile,
|
||||||
|
mobile: mobile,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def article_type_sms
|
||||||
|
Ticket::Article::Type.find_by(name: 'sms')
|
||||||
|
end
|
||||||
|
|
||||||
|
def closed_ids
|
||||||
|
Ticket::State.where(name: %w[closed merged removed]).pluck(:id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def ensure_ticket_followup_state(ticket)
|
||||||
|
return if !ticket
|
||||||
|
|
||||||
|
new_state = Ticket::State.find_by(default_create: true)
|
||||||
|
return if ticket.state_id == new_state.id
|
||||||
|
|
||||||
|
ticket.state = Ticket::State.find_by(default_follow_up: true)
|
||||||
|
ticket.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_open_sms_ticket(user)
|
||||||
|
ticket = Ticket.where(customer_id: user.id, create_article_type_id: article_type_sms.id).where.not(state_id: closed_ids).order(:updated_at).first
|
||||||
|
ensure_ticket_followup_state(ticket)
|
||||||
|
ticket
|
||||||
|
end
|
||||||
|
|
||||||
|
def ensure_group!(channel)
|
||||||
|
raise Exceptions::UnprocessableEntity, 'Group needed in channel definition!' if channel.group_id.blank?
|
||||||
|
|
||||||
|
group = Group.find_by(id: channel.group_id)
|
||||||
|
raise Exceptions::UnprocessableEntity, 'Group is invalid!' if !group
|
||||||
|
end
|
||||||
|
|
||||||
|
def cut_title(title)
|
||||||
|
if title.length > 40
|
||||||
|
title = "#{title[0, 40]}..."
|
||||||
|
end
|
||||||
|
title
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_ticket(attr, channel, user)
|
||||||
|
ticket = find_open_sms_ticket(user)
|
||||||
|
if !ticket
|
||||||
|
ensure_group!(channel)
|
||||||
|
|
||||||
|
ticket = create_ticket(attr, channel, user)
|
||||||
|
end
|
||||||
|
|
||||||
|
create_article(attr, channel, ticket)
|
||||||
|
end
|
||||||
|
end
|
|
@ -10,17 +10,7 @@ class Channel::Driver::Sms::Massenversand
|
||||||
|
|
||||||
Rails.logger.info "Backend sending Massenversand SMS to #{attr[:recipient]}"
|
Rails.logger.info "Backend sending Massenversand SMS to #{attr[:recipient]}"
|
||||||
begin
|
begin
|
||||||
url = build_url(options, attr)
|
send_create(options, attr)
|
||||||
|
|
||||||
if Setting.get('developer_mode') != true
|
|
||||||
response = Faraday.get(url).body
|
|
||||||
|
|
||||||
if !response.match?('OK')
|
|
||||||
message = "Received non-OK response from gateway URL '#{url}'"
|
|
||||||
Rails.logger.error "#{message}: #{response.inspect}"
|
|
||||||
raise message
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
true
|
true
|
||||||
rescue => e
|
rescue => e
|
||||||
|
@ -31,6 +21,19 @@ class Channel::Driver::Sms::Massenversand
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def send_create(options, attr)
|
||||||
|
url = build_url(options, attr)
|
||||||
|
|
||||||
|
return if Setting.get('developer_mode')
|
||||||
|
|
||||||
|
response = Faraday.get(url).body
|
||||||
|
return if response.match?('OK')
|
||||||
|
|
||||||
|
message = "Received non-OK response from gateway URL '#{url}'"
|
||||||
|
Rails.logger.error "#{message}: #{response.inspect}"
|
||||||
|
raise message
|
||||||
|
end
|
||||||
|
|
||||||
def self.definition
|
def self.definition
|
||||||
{
|
{
|
||||||
name: 'Massenversand',
|
name: 'Massenversand',
|
||||||
|
|
110
app/models/channel/driver/sms/message_bird.rb
Normal file
110
app/models/channel/driver/sms/message_bird.rb
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||||
|
|
||||||
|
class Channel::Driver::Sms::MessageBird < Channel::Driver::Sms::Base
|
||||||
|
NAME = 'sms/message_bird'.freeze
|
||||||
|
|
||||||
|
def fetchable?(_channel)
|
||||||
|
false
|
||||||
|
end
|
||||||
|
|
||||||
|
def send(options, attr, _notification = false)
|
||||||
|
Rails.logger.info "Sending SMS to recipient #{attr[:recipient]}"
|
||||||
|
|
||||||
|
return true if Setting.get('import_mode')
|
||||||
|
|
||||||
|
Rails.logger.info "Backend sending MessageBird SMS to #{attr[:recipient]}"
|
||||||
|
begin
|
||||||
|
send_create(options, attr)
|
||||||
|
true
|
||||||
|
rescue => e
|
||||||
|
Rails.logger.debug { "MessageBird error: #{e.inspect}" }
|
||||||
|
raise e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_create(options, attr)
|
||||||
|
return if Setting.get('developer_mode')
|
||||||
|
|
||||||
|
api(options).message_create(options[:sender], attr[:recipient], attr[:message])
|
||||||
|
end
|
||||||
|
|
||||||
|
def process(_options, attr, channel)
|
||||||
|
Rails.logger.info "Receiving SMS frim recipient #{attr['originator']}"
|
||||||
|
|
||||||
|
# prevent already created articles
|
||||||
|
if attr['message_id'].present? && Ticket::Article.exists?(message_id: attr['message_id'])
|
||||||
|
return [:json, {}]
|
||||||
|
end
|
||||||
|
|
||||||
|
# find sender
|
||||||
|
user = user_by_mobile(attr['originator'])
|
||||||
|
UserInfo.current_user_id = user.id
|
||||||
|
|
||||||
|
process_ticket(attr, channel, user)
|
||||||
|
|
||||||
|
[:json, {}]
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_ticket(attr, channel, user)
|
||||||
|
title = cut_title(attr['incomingMessage'])
|
||||||
|
ticket = Ticket.new(
|
||||||
|
group_id: channel.group_id,
|
||||||
|
title: title,
|
||||||
|
state_id: Ticket::State.find_by(default_create: true).id,
|
||||||
|
priority_id: Ticket::Priority.find_by(default_create: true).id,
|
||||||
|
customer_id: user.id,
|
||||||
|
preferences: {
|
||||||
|
channel_id: channel.id,
|
||||||
|
sms: {
|
||||||
|
originator: attr['originator'],
|
||||||
|
recipient: attr['recipient'],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
ticket.save!
|
||||||
|
ticket
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_article(attr, channel, ticket)
|
||||||
|
Ticket::Article.create!(
|
||||||
|
ticket_id: ticket.id,
|
||||||
|
type: article_type_sms,
|
||||||
|
sender: Ticket::Article::Sender.find_by(name: 'Customer'),
|
||||||
|
body: attr['incomingMessage'],
|
||||||
|
from: attr['originator'],
|
||||||
|
to: attr['recipient'],
|
||||||
|
message_id: attr['message_id'],
|
||||||
|
content_type: 'text/plain',
|
||||||
|
preferences: {
|
||||||
|
channel_id: channel.id,
|
||||||
|
sms: {
|
||||||
|
reference: attr['reference'],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.definition
|
||||||
|
{
|
||||||
|
name: 'message_bird',
|
||||||
|
adapter: 'sms/message_bird',
|
||||||
|
account: [
|
||||||
|
{ name: 'options::webhook_token', display: 'Webhook Token', tag: 'input', type: 'text', limit: 200, null: false, default: Digest::MD5.hexdigest(rand(999_999_999_999).to_s), disabled: true, readonly: true },
|
||||||
|
{ name: 'options::token', display: 'Token', tag: 'input', type: 'text', limit: 255, null: false },
|
||||||
|
{ name: 'options::sender', display: 'Sender', tag: 'input', type: 'text', limit: 200, null: false, placeholder: '+491710000000' },
|
||||||
|
{ name: 'group_id', display: 'Destination Group', tag: 'select', null: false, relation: 'Group', nulloption: true, filter: { active: true } },
|
||||||
|
],
|
||||||
|
notification: [
|
||||||
|
{ name: 'options::token', display: 'Token', tag: 'input', type: 'text', limit: 255, null: false },
|
||||||
|
{ name: 'options::sender', display: 'Sender', tag: 'input', type: 'text', limit: 200, null: false, placeholder: '+491710000000' },
|
||||||
|
],
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def api(options)
|
||||||
|
require 'messagebird'
|
||||||
|
@api ||= ::MessageBird::Client.new(options[:token])
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,6 +1,6 @@
|
||||||
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||||
|
|
||||||
class Channel::Driver::Sms::Twilio
|
class Channel::Driver::Sms::Twilio < Channel::Driver::Sms::Base
|
||||||
NAME = 'sms/twilio'.freeze
|
NAME = 'sms/twilio'.freeze
|
||||||
|
|
||||||
def fetchable?(_channel)
|
def fetchable?(_channel)
|
||||||
|
@ -14,7 +14,18 @@ class Channel::Driver::Sms::Twilio
|
||||||
|
|
||||||
Rails.logger.info "Backend sending Twilio SMS to #{attr[:recipient]}"
|
Rails.logger.info "Backend sending Twilio SMS to #{attr[:recipient]}"
|
||||||
begin
|
begin
|
||||||
if Setting.get('developer_mode') != true
|
send_create(options, attr)
|
||||||
|
|
||||||
|
true
|
||||||
|
rescue => e
|
||||||
|
Rails.logger.debug { "Twilio error: #{e.inspect}" }
|
||||||
|
raise e
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def send_create(options, attr)
|
||||||
|
return if Setting.get('developer_mode')
|
||||||
|
|
||||||
result = api(options).messages.create(
|
result = api(options).messages.create(
|
||||||
from: options[:sender],
|
from: options[:sender],
|
||||||
to: attr[:recipient],
|
to: attr[:recipient],
|
||||||
|
@ -24,13 +35,6 @@ class Channel::Driver::Sms::Twilio
|
||||||
raise result.error_message if result&.error_code&.positive?
|
raise result.error_message if result&.error_code&.positive?
|
||||||
end
|
end
|
||||||
|
|
||||||
true
|
|
||||||
rescue => e
|
|
||||||
Rails.logger.debug { "Twilio error: #{e.inspect}" }
|
|
||||||
raise e
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def process(_options, attr, channel)
|
def process(_options, attr, channel)
|
||||||
Rails.logger.info "Receiving SMS frim recipient #{attr[:From]}"
|
Rails.logger.info "Receiving SMS frim recipient #{attr[:From]}"
|
||||||
|
|
||||||
|
@ -40,47 +44,17 @@ class Channel::Driver::Sms::Twilio
|
||||||
return ['application/xml; charset=UTF-8;', Twilio::TwiML::MessagingResponse.new.to_s]
|
return ['application/xml; charset=UTF-8;', Twilio::TwiML::MessagingResponse.new.to_s]
|
||||||
end
|
end
|
||||||
|
|
||||||
# find sender
|
user = user_by_mobile(attr[:From])
|
||||||
user = User.where(mobile: attr[:From]).order(:updated_at).first
|
|
||||||
if !user
|
|
||||||
_from_comment, preferences = Cti::CallerId.get_comment_preferences(attr[:From], 'from')
|
|
||||||
if preferences && preferences['from'] && preferences['from'][0] && preferences['from'][0]['level'] == 'known' && preferences['from'][0]['object'] == 'User'
|
|
||||||
user = User.find_by(id: preferences['from'][0]['o_id'])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if !user
|
|
||||||
user = User.create!(
|
|
||||||
firstname: attr[:From],
|
|
||||||
mobile: attr[:From],
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
UserInfo.current_user_id = user.id
|
UserInfo.current_user_id = user.id
|
||||||
|
|
||||||
# find ticket
|
process_ticket(attr, channel, user)
|
||||||
article_type_sms = Ticket::Article::Type.find_by(name: 'sms')
|
|
||||||
state_ids = Ticket::State.where(name: %w[closed merged removed]).pluck(:id)
|
require 'twilio-ruby' # Only load this gem when it is really used
|
||||||
ticket = Ticket.where(customer_id: user.id, create_article_type_id: article_type_sms.id).where.not(state_id: state_ids).order(:updated_at).first
|
['application/xml; charset=UTF-8;', Twilio::TwiML::MessagingResponse.new.to_s]
|
||||||
if ticket
|
|
||||||
new_state = Ticket::State.find_by(default_create: true)
|
|
||||||
if ticket.state_id != new_state.id
|
|
||||||
ticket.state = Ticket::State.find_by(default_follow_up: true)
|
|
||||||
ticket.save!
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if channel.group_id.blank?
|
|
||||||
raise Exceptions::UnprocessableEntity, 'Group needed in channel definition!'
|
|
||||||
end
|
end
|
||||||
|
|
||||||
group = Group.find_by(id: channel.group_id)
|
def create_ticket(attr, channel, user)
|
||||||
if !group
|
title = cut_title(attr[:Body])
|
||||||
raise Exceptions::UnprocessableEntity, 'Group is invalid!'
|
|
||||||
end
|
|
||||||
|
|
||||||
title = attr[:Body]
|
|
||||||
if title.length > 40
|
|
||||||
title = "#{title[0, 40]}..."
|
|
||||||
end
|
|
||||||
ticket = Ticket.new(
|
ticket = Ticket.new(
|
||||||
group_id: channel.group_id,
|
group_id: channel.group_id,
|
||||||
title: title,
|
title: title,
|
||||||
|
@ -97,8 +71,10 @@ class Channel::Driver::Sms::Twilio
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
ticket.save!
|
ticket.save!
|
||||||
|
ticket
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_article(attr, channel, ticket)
|
||||||
Ticket::Article.create!(
|
Ticket::Article.create!(
|
||||||
ticket_id: ticket.id,
|
ticket_id: ticket.id,
|
||||||
type: article_type_sms,
|
type: article_type_sms,
|
||||||
|
@ -117,9 +93,6 @@ class Channel::Driver::Sms::Twilio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
require 'twilio-ruby' # Only load this gem when it is really used
|
|
||||||
['application/xml; charset=UTF-8;', Twilio::TwiML::MessagingResponse.new.to_s]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.definition
|
def self.definition
|
||||||
|
|
6
script/messagebird.rb
Normal file
6
script/messagebird.rb
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||||
|
|
||||||
|
require 'messagebird'
|
||||||
|
|
||||||
|
client = MessageBird::Client.new('OSR2zUFd14Nd5snb8zmEQYoBx')
|
||||||
|
client.message_create('Zammad GmbH', '+4917670333748', 'This is a test messageNEW', reference: 'Foobar')
|
68
spec/models/channel/driver/sms/message_bird_spec.rb
Normal file
68
spec/models/channel/driver/sms/message_bird_spec.rb
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
require 'messagebird'
|
||||||
|
|
||||||
|
RSpec.describe Channel::Driver::Sms::MessageBird do
|
||||||
|
it 'passes' do
|
||||||
|
channel = create_channel
|
||||||
|
|
||||||
|
stub_request(:post, url_to_mock)
|
||||||
|
.to_return(body: mocked_response_success)
|
||||||
|
|
||||||
|
api = channel.driver_instance.new
|
||||||
|
expect(api.send(channel.options, { recipient: '+37060010000', message: message_body })).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'fails' do
|
||||||
|
channel = create_channel
|
||||||
|
|
||||||
|
stub_request(:post, url_to_mock)
|
||||||
|
.to_return(status: 400, body: mocked_response_failure)
|
||||||
|
|
||||||
|
api = channel.driver_instance.new
|
||||||
|
|
||||||
|
expect { api.send(channel.options, { recipient: 'asd', message: message_body }) }.to raise_exception(::MessageBird::ServerException)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def create_channel
|
||||||
|
FactoryBot.create(:channel,
|
||||||
|
options: {
|
||||||
|
adapter: 'sms/message_bird',
|
||||||
|
sender: sender_number,
|
||||||
|
token: token
|
||||||
|
},
|
||||||
|
created_by_id: 1,
|
||||||
|
updated_by_id: 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
# api parameters
|
||||||
|
|
||||||
|
def url_to_mock
|
||||||
|
'https://rest.messagebird.com/messages'
|
||||||
|
end
|
||||||
|
|
||||||
|
def message_body
|
||||||
|
'Test'
|
||||||
|
end
|
||||||
|
|
||||||
|
def sender_number
|
||||||
|
'+15005550006'
|
||||||
|
end
|
||||||
|
|
||||||
|
def token
|
||||||
|
'2345r4erfdvc4wedxv3efds'
|
||||||
|
end
|
||||||
|
|
||||||
|
# mocked responses
|
||||||
|
|
||||||
|
def mocked_response_success
|
||||||
|
'{"id":"1e8cc35873d14fe4ab18bd97a412121","href":"https://rest.messagebird.com/messages/1e8cc35873d14fe4ab18bd121212f971a","direction":"mt","type":"sms","originator":"Zammad GmbH","body":"This is a test messageNEW","reference":"Foobar","validity":null,"gateway":10,"typeDetails":{},"datacoding":"plain","mclass":1,"scheduledDatetime":null,"createdDatetime":"2021-07-22T13:25:03+00:00","recipients":{"totalCount":1,"totalSentCount":1,"totalDeliveredCount":0,"totalDeliveryFailedCount":0,"items":[{"recipient":491234,"status":"sent","statusDatetime":"2021-07-22T13:25:03+00:00","messagePartCount":1}]}}'
|
||||||
|
end
|
||||||
|
|
||||||
|
def mocked_response_failure
|
||||||
|
'{"errors":[{"code":9,"description":"no (correct) recipients found","parameter":"recipient"}]}'
|
||||||
|
end
|
||||||
|
end
|
188
spec/requests/integration/message_bird_sms_spec.rb
Normal file
188
spec/requests/integration/message_bird_sms_spec.rb
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
# Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Message Bird SMS', type: :request do
|
||||||
|
|
||||||
|
describe 'request handling' do
|
||||||
|
|
||||||
|
let(:agent) do
|
||||||
|
create(:agent, groups: Group.all)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does basic call' do
|
||||||
|
|
||||||
|
# configure twilio channel
|
||||||
|
group_id = Group.find_by(name: 'Users').id
|
||||||
|
|
||||||
|
UserInfo.current_user_id = 1
|
||||||
|
channel = create(
|
||||||
|
:channel,
|
||||||
|
area: 'Sms::Account',
|
||||||
|
options: {
|
||||||
|
adapter: 'sms/message_bird',
|
||||||
|
webhook_token: 'f409460e50f76d331fdac8ba7b7963b6',
|
||||||
|
token: '223',
|
||||||
|
sender: '333',
|
||||||
|
},
|
||||||
|
group_id: nil,
|
||||||
|
)
|
||||||
|
|
||||||
|
# process inbound sms
|
||||||
|
post '/api/v1/sms_webhook', params: read_message('inbound_sms1'), as: :json
|
||||||
|
expect(response).to have_http_status(:not_found)
|
||||||
|
|
||||||
|
post '/api/v1/sms_webhook/not_existing', params: read_message('inbound_sms1'), as: :json
|
||||||
|
expect(response).to have_http_status(:not_found)
|
||||||
|
|
||||||
|
post '/api/v1/sms_webhook/f409460e50f76d331fdac8ba7b7963b6', params: read_message('inbound_sms1'), as: :json
|
||||||
|
expect(response).to have_http_status(:unprocessable_entity)
|
||||||
|
expect(json_response['error']).to eq('Can\'t use Channel::Driver::Sms::MessageBird: #<Exceptions::UnprocessableEntity: Group needed in channel definition!>')
|
||||||
|
|
||||||
|
channel.group_id = Group.first.id
|
||||||
|
channel.save!
|
||||||
|
|
||||||
|
post '/api/v1/sms_webhook/f409460e50f76d331fdac8ba7b7963b6', params: read_message('inbound_sms1'), as: :json
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
|
||||||
|
ticket = Ticket.last
|
||||||
|
article = Ticket::Article.last
|
||||||
|
customer = User.last
|
||||||
|
expect(ticket.articles.count).to eq(1)
|
||||||
|
expect(ticket.title).to eq('Ldfhxhcuffufuf. Fifififig. Fifififiif F...')
|
||||||
|
expect(ticket.state.name).to eq('new')
|
||||||
|
expect(ticket.group_id).to eq(group_id)
|
||||||
|
expect(ticket.customer_id).to eq(customer.id)
|
||||||
|
expect(ticket.created_by_id).to eq(customer.id)
|
||||||
|
expect(article.from).to eq('+491710000000')
|
||||||
|
expect(article.to).to eq('+4915700000000')
|
||||||
|
expect(article.cc).to be_nil
|
||||||
|
expect(article.subject).to be_nil
|
||||||
|
expect(article.body).to eq('Ldfhxhcuffufuf. Fifififig. Fifififiif Fifififiif Fifififiif Fifififiif Fifififiif')
|
||||||
|
expect(article.created_by_id).to eq(customer.id)
|
||||||
|
expect(article.sender.name).to eq('Customer')
|
||||||
|
expect(article.type.name).to eq('sms')
|
||||||
|
|
||||||
|
post '/api/v1/sms_webhook/f409460e50f76d331fdac8ba7b7963b6', params: read_message('inbound_sms2'), as: :json
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
|
||||||
|
ticket.reload
|
||||||
|
expect(ticket.articles.count).to eq(2)
|
||||||
|
expect(ticket.state.name).to eq('new')
|
||||||
|
|
||||||
|
article = Ticket::Article.last
|
||||||
|
expect(article.from).to eq('+491710000000')
|
||||||
|
expect(article.to).to eq('+4915700000000')
|
||||||
|
expect(article.cc).to be_nil
|
||||||
|
expect(article.subject).to be_nil
|
||||||
|
expect(article.body).to eq('Follow-up')
|
||||||
|
expect(article.sender.name).to eq('Customer')
|
||||||
|
expect(article.type.name).to eq('sms')
|
||||||
|
expect(article.created_by_id).to eq(customer.id)
|
||||||
|
|
||||||
|
# check duplicate callbacks
|
||||||
|
post '/api/v1/sms_webhook/f409460e50f76d331fdac8ba7b7963b6', params: read_message('inbound_sms2'), as: :json
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
|
||||||
|
ticket.reload
|
||||||
|
expect(ticket.articles.count).to eq(2)
|
||||||
|
expect(ticket.state.name).to eq('new')
|
||||||
|
expect(article.id).to eq(Ticket::Article.last.id)
|
||||||
|
|
||||||
|
# new ticket need to be create
|
||||||
|
ticket.state = Ticket::State.find_by(name: 'closed')
|
||||||
|
ticket.save!
|
||||||
|
|
||||||
|
post '/api/v1/sms_webhook/f409460e50f76d331fdac8ba7b7963b6', params: read_message('inbound_sms3'), as: :json
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
|
||||||
|
ticket.reload
|
||||||
|
expect(ticket.articles.count).to eq(2)
|
||||||
|
expect(ticket.id).not_to eq(Ticket.last.id)
|
||||||
|
expect(ticket.state.name).to eq('closed')
|
||||||
|
|
||||||
|
ticket = Ticket.last
|
||||||
|
article = Ticket::Article.last
|
||||||
|
customer = User.last
|
||||||
|
expect(ticket.articles.count).to eq(1)
|
||||||
|
expect(ticket.title).to eq('new 2')
|
||||||
|
expect(ticket.group_id).to eq(group_id)
|
||||||
|
expect(ticket.customer_id).to eq(customer.id)
|
||||||
|
expect(ticket.created_by_id).to eq(customer.id)
|
||||||
|
expect(article.from).to eq('+491710000000')
|
||||||
|
expect(article.to).to eq('+4915700000000')
|
||||||
|
expect(article.cc).to be_nil
|
||||||
|
expect(article.subject).to be_nil
|
||||||
|
expect(article.body).to eq('new 2')
|
||||||
|
expect(article.created_by_id).to eq(customer.id)
|
||||||
|
expect(article.sender.name).to eq('Customer')
|
||||||
|
expect(article.type.name).to eq('sms')
|
||||||
|
|
||||||
|
# reply by agent
|
||||||
|
params = {
|
||||||
|
ticket_id: ticket.id,
|
||||||
|
body: 'some test',
|
||||||
|
type: 'sms',
|
||||||
|
}
|
||||||
|
authenticated_as(agent)
|
||||||
|
post '/api/v1/ticket_articles', params: params, as: :json
|
||||||
|
expect(response).to have_http_status(:created)
|
||||||
|
expect(json_response).to be_a_kind_of(Hash)
|
||||||
|
expect(json_response['subject']).to be_nil
|
||||||
|
expect(json_response['body']).to eq('some test')
|
||||||
|
expect(json_response['content_type']).to eq('text/plain')
|
||||||
|
expect(json_response['updated_by_id']).to eq(agent.id)
|
||||||
|
expect(json_response['created_by_id']).to eq(agent.id)
|
||||||
|
|
||||||
|
stub_request(:post, 'https://rest.messagebird.com/messages')
|
||||||
|
.to_return(status: 200, body: mocked_response_success, headers: {})
|
||||||
|
|
||||||
|
expect(article.preferences[:delivery_retry]).to be_nil
|
||||||
|
expect(article.preferences[:delivery_status]).to be_nil
|
||||||
|
|
||||||
|
TransactionDispatcher.commit
|
||||||
|
Scheduler.worker(true)
|
||||||
|
|
||||||
|
article = Ticket::Article.find(json_response['id'])
|
||||||
|
expect(article.preferences[:delivery_retry]).to eq(1)
|
||||||
|
expect(article.preferences[:delivery_status]).to eq('success')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does customer based on already existing mobile attibute' do
|
||||||
|
|
||||||
|
customer = create(
|
||||||
|
:customer,
|
||||||
|
email: 'me@example.com',
|
||||||
|
mobile: '01710000000',
|
||||||
|
)
|
||||||
|
TransactionDispatcher.commit
|
||||||
|
Scheduler.worker(true)
|
||||||
|
|
||||||
|
UserInfo.current_user_id = 1
|
||||||
|
create(
|
||||||
|
:channel,
|
||||||
|
area: 'Sms::Account',
|
||||||
|
options: {
|
||||||
|
adapter: 'sms/twilio',
|
||||||
|
webhook_token: 'f409460e50f76d331fdac8ba7b7963b6',
|
||||||
|
account_id: '111',
|
||||||
|
token: '223',
|
||||||
|
sender: '333',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
post '/api/v1/sms_webhook/f409460e50f76d331fdac8ba7b7963b6', params: read_message('inbound_sms1'), as: :json
|
||||||
|
expect(response).to have_http_status(:ok)
|
||||||
|
|
||||||
|
expect(customer.id).to eq(User.last.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
def read_message(file)
|
||||||
|
JSON.parse(File.read(Rails.root.join('test', 'data', 'message_bird', "#{file}.json")))
|
||||||
|
end
|
||||||
|
|
||||||
|
def mocked_response_success
|
||||||
|
'{"id":"1e8cc35873d14fe4ab18bd97a412121","href":"https://rest.messagebird.com/messages/1e8cc35873d14fe4ab18bd121212f971a","direction":"mt","type":"sms","originator":"Zammad GmbH","body":"some test","reference":"Foobar","validity":null,"gateway":10,"typeDetails":{},"datacoding":"plain","mclass":1,"scheduledDatetime":null,"createdDatetime":"2021-07-22T13:25:03+00:00","recipients":{"totalCount":1,"totalSentCount":1,"totalDeliveredCount":0,"totalDeliveryFailedCount":0,"items":[{"recipient":491234,"status":"sent","statusDatetime":"2021-07-22T13:25:03+00:00","messagePartCount":1}]}}'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
20
test/data/message_bird/inbound_sms1.json
Normal file
20
test/data/message_bird/inbound_sms1.json
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"body":"Ldfhxhcuffufuf. Fifififig. Fifififiif Fifififiif Fifififiif Fifififiif Fifififiif",
|
||||||
|
"createdDatetime":"",
|
||||||
|
"currentTime":"2021-07-22T10:06:00+02:00",
|
||||||
|
"date":"0",
|
||||||
|
"date_utc":"0",
|
||||||
|
"id":"",
|
||||||
|
"incomingMessage":"Ldfhxhcuffufuf. Fifififig. Fifififiif Fifififiif Fifififiif Fifififiif Fifififiif",
|
||||||
|
"message":"",
|
||||||
|
"message_id":"",
|
||||||
|
"originator":"+491710000000",
|
||||||
|
"payload":"Ldfhxhcuffufuf. Fifififig. Fifififiif Fifififiif Fifififiif Fifififiif Fifififiif",
|
||||||
|
"receiver":"",
|
||||||
|
"recipient":"+4915700000000",
|
||||||
|
"reference":"",
|
||||||
|
"sender":"",
|
||||||
|
"controller":"channels_sms",
|
||||||
|
"action":"webhook",
|
||||||
|
"token":"xxx"
|
||||||
|
}
|
20
test/data/message_bird/inbound_sms2.json
Normal file
20
test/data/message_bird/inbound_sms2.json
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"body":"Follow-up",
|
||||||
|
"createdDatetime":"",
|
||||||
|
"currentTime":"2021-07-22T10:06:00+02:00",
|
||||||
|
"date":"0",
|
||||||
|
"date_utc":"0",
|
||||||
|
"id":"",
|
||||||
|
"incomingMessage":"Follow-up",
|
||||||
|
"message":"",
|
||||||
|
"message_id":"123",
|
||||||
|
"originator":"+491710000000",
|
||||||
|
"payload":"Follow-up",
|
||||||
|
"receiver":"",
|
||||||
|
"recipient":"+4915700000000",
|
||||||
|
"reference":"",
|
||||||
|
"sender":"",
|
||||||
|
"controller":"channels_sms",
|
||||||
|
"action":"webhook",
|
||||||
|
"token":"xxx"
|
||||||
|
}
|
20
test/data/message_bird/inbound_sms3.json
Normal file
20
test/data/message_bird/inbound_sms3.json
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"body":"new 2",
|
||||||
|
"createdDatetime":"",
|
||||||
|
"currentTime":"2021-07-22T10:06:00+02:00",
|
||||||
|
"date":"0",
|
||||||
|
"date_utc":"0",
|
||||||
|
"id":"",
|
||||||
|
"incomingMessage":"new 2",
|
||||||
|
"message":"",
|
||||||
|
"message_id":"",
|
||||||
|
"originator":"+491710000000",
|
||||||
|
"payload":"new 2",
|
||||||
|
"receiver":"",
|
||||||
|
"recipient":"+4915700000000",
|
||||||
|
"reference":"",
|
||||||
|
"sender":"",
|
||||||
|
"controller":"channels_sms",
|
||||||
|
"action":"webhook",
|
||||||
|
"token":"xxx"
|
||||||
|
}
|
Loading…
Reference in a new issue