Class: LiveCallRouting::Twilio::V3

Inherits:
Integration
  • Object
show all
Defined in:
app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb

Constant Summary collapse

OPTIONS =
[
  {key: :account_sid, type: :string, default: nil},
  {key: :api_key, type: :string, default: nil},
  {key: :api_secret, type: :string, default: nil},
  {key: :api_region, type: :string, default: "ashburn.us1"},
  {key: :force_input, type: :boolean, default: false},
  {key: :record, type: :boolean, default: false},
  {key: :send_straight_to_voicemail, type: :boolean, default: false},
  {key: :record_email, type: :string, default: ""},
  {key: :banned_phone, type: :string, default: ""},
  {key: :dial_pause, type: :integer},
  {key: :max_wait_time, type: :integer, default: nil},
  {key: :skip_resolve_on_hangup, type: :boolean, default: false}
]
API_REGIONS =
["ashburn.us1", "dublin.ie1", "sydney.au1"]
SPEAK_OPTIONS =
{
  language: "en",
  voice: "man"
}

Instance Method Summary collapse

Instance Method Details

#adapter_actionObject



166
167
168
169
170
171
172
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 166

def adapter_action
  if is_banned?
    :other
  else
    :create
  end
end

#adapter_incoming_can_defer?Boolean

Returns:

  • (Boolean)


150
151
152
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 150

def adapter_incoming_can_defer?
  false
end

#adapter_outgoing_interest?(event_name) ⇒ Boolean

Returns:

  • (Boolean)


146
147
148
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 146

def adapter_outgoing_interest?(event_name)
  ["alert_acknowledged", "alert_dropped"].include?(event_name)
end

#adapter_process_call_status_deferredObject



452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 452

def adapter_process_call_status_deferred
  call_status = adapter_incoming_request_params.dig("CallStatus")
  adapter_source_log&.sublog("Processing call status #{call_status}")

  if ["completed"].include?(call_status)
    self.adapter_alert = alerts.find_by(thirdparty_id: _thirdparty_id)

    if adapter_alert.present? && adapter_alert.meta["live_call_send_straight_to_voicemail"] == true && !adapter_alert.additional_data.any? { |x| x["label"] == "Voicemail" }
      if option_skip_resolve_on_hangup
        adapter_alert.logs.create!(message: "Caller hung up without leaving a message.")
      else
        adapter_alert.logs.create!(message: "Caller hung up without leaving a message. Marking alert as resolved.")
        adapter_alert.resolve!(self, force: true)
      end
    elsif adapter_alert.present? && adapter_alert.meta["live_call_send_straight_to_voicemail"] != true && !adapter_alert.meta["live_call_queue_sid"].present?
      adapter_alert.logs.create!(message: "Caller hungup before being put in a queue. Marking alert as resolved.")
      adapter_alert.resolve!(self, force: true)
    end
  end
end

#adapter_process_createObject



178
179
180
181
182
183
184
185
186
187
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 178

def adapter_process_create
  Alert.new(
    title: _title,
    urgency: urgency,

    thirdparty_id: _thirdparty_id,
    dedup_keys: [],
    additional_data: _additional_datums
  )
end

#adapter_process_outgoingObject



473
474
475
476
477
478
479
480
481
482
483
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 473

def adapter_process_outgoing
  return unless adapter_alert.source_id == id
  return if adapter_alert.meta["live_call_send_straight_to_voicemail"] == true

  event = adapter_outgoing_event.event_name.to_s
  if event == "alert_acknowledged"
    _on_acknowledge
  elsif event == "alert_dropped"
    _on_drop
  end
end

#adapter_process_queue_status_deferredObject



438
439
440
441
442
443
444
445
446
447
448
449
450
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 438

def adapter_process_queue_status_deferred
  queue_result = adapter_incoming_request_params.dig("QueueResult")
  adapter_source_log&.sublog("Processing queue status #{queue_result}")

  if queue_result == "hangup"
    self.adapter_alert = alerts.find_by(thirdparty_id: _thirdparty_id)
    adapter_alert.logs.create!(message: "Caller hungup while waiting in queue.")
    adapter_alert.resolve!(self, force: true) unless option_skip_resolve_on_hangup
    queue_destroy
  end

  adapter_source_log&.save!
end

#adapter_response_disabledObject



353
354
355
356
357
358
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 353

def adapter_response_disabled
  _twiml.say(message: "This integration is currently disabled. Goodbye!", **SPEAK_OPTIONS)
  _twiml.hangup

  adapter_controller&.render(xml: _twiml.to_xml)
end

#adapter_response_droppedObject



379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 379

def adapter_response_dropped
  recording_url = adapter_incoming_request_params.dig("RecordingUrl")

  if recording_url
    _twiml.play(url: option_no_answer_thank_you_media_url)
    _twiml.hangup

    adapter_alert.additional_data.push(AdditionalDatum.new(format: "link", label: "Voicemail", value: recording_url).to_h)
    adapter_alert.save!

    adapter_alert.logs.create!(message: "Caller left a voicemail.")

    if option_record_emails.any?
      emails = option_record_emails.map do |x|
        if x == "team"
          Array(adapter_alert.destination_teams.map(&:admin_users).flatten&.map(&:email)) + Array(adapter_alert.destination_teams.map(&:member_users).flatten&.map(&:email))
        elsif x == "team-admin"
          Array(adapter_alert.destination_teams.map(&:admin_users).flatten&.map(&:email))
        elsif x == "on-call"
          adapter_alert.destination_teams.map do |t|
            Array(t.schedule.current_oncall_event_occurrences.map(&:attendees).flatten.map(&:attendee).uniq.map(&:email))
          end
        else
          x
        end
      end.flatten.compact_blank.uniq

      adapter_alert.logs.create!(message: "Sending voicemail recording to #{emails.size} emails.")
      emails.each do |email|
        LiveCallRouting::Twilio::V3Mailer.with(email: email, alert: adapter_alert, from: adapter_incoming_request_params.dig("From"), recording_url: recording_url).call_recording.deliver_later
      end
    end

    if adapter_alert.meta["live_call_send_straight_to_voicemail"] == true
      adapter_alert.logs.create!(message: "Call was sent straight to voicemail and caller left a message. Routing the alert.")

      # kick off the alert workflow
      adapter_alert.route_later
      adapter_alert.logs.create!(message: "Successfully enqueued alert team workflow.")
    end
  elsif option_record
    adapter_alert.logs.create!(message: "No one is available to answer this call. Requesting voicemail recording.")
    _twiml.play(url: option_no_answer_media_url)
    _twiml.record(max_length: 60)
  else
    # A friendly goodbye (when the integration has been configured to not record)
    if option_no_answer_no_record_media_url.present?
      adapter_alert.logs.create!(message: "No one is available to answer this call. Play media. Hangup on caller.")
      _twiml.play(url: option_no_answer_no_record_media_url)
    else
      adapter_alert.logs.create!(message: "No one is available to answer this call. Say message. Hangup on caller.")
      _twiml.say(message: "No one is available to answer this call. Goodbye.", **SPEAK_OPTIONS)
    end
    _twiml.hangup
  end

  adapter_controller.render(xml: _twiml.to_xml)
end

#adapter_response_incomingObject



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 189

def adapter_response_incoming
  if is_banned?
    _twiml.reject

    adapter_source_log&.sublog("Caller #{adapter_incoming_request_params.dig("From")} on blocked list. Rejected call.")
    adapter_source_log&.save

    return adapter_controller&.render(xml: _twiml.to_xml)
  end

  if adapter_alert.meta["live_call_status_callback_set"] != true
    begin
      # give us status updates on the call, so we can clean up if they hang up before leaving a message
      _call.update(
        status_callback: PagerTree::Integrations::Engine.routes.url_helpers.call_status_live_call_routing_twilio_v3_url(id, thirdparty_id: _thirdparty_id),
        status_callback_method: "POST",
        url: adapter_controller&.url_for || endpoint
      )

      adapter_alert.meta["live_call_status_callback_set"] = true
      adapter_alert.save!
    rescue ::Twilio::REST::RestError => e
      if e.code == 21220
        # 21220 - Unable to update record. Call is not in-progress. Cannot redirect.
        adapter_alert.logs.create!(message: "Updating the call for status callbacks failed. The caller has already hung up.")
      else
        adapter_alert.logs.create!(message: "Updating the call for status callbacks failed. #{e.message}")
      end

      adapter_alert.logs.create!(message: "Marking alert as resolved due to call update failure.")
      adapter_alert.resolve!(self, force: true)
    end

    return adapter_controller&.render(xml: _twiml.to_xml)
  end

  # if this was attached to a router
  if !adapter_alert.meta["live_call_router_team_prefix_ids"].present? && routers.size > 0 && .subscription_feature_routers?
    adapter_alert.logs.create!(message: "Routed to router. Attempting to get a list of teams...")
    team_ids = []
    default_receiver_team_ids = []
    v3 = adapter_alert.v3_format
    routers.each do |router|
      if router.enabled? && router.kept?
        actions = router.rules_eval({
          always: true,
          alert: v3
        })

        actions.flatten!

        actions.each do |action|
          team_ids << Array(action["receiver"]) if action["type"] == "assign"
        end

        # hold on to the default destination team ids in case we need them later
        default_receiver_team_ids |= [router.default_receiver.prefix_id] if router.default_receiver.present?
      end
    end # end routers.each

    team_ids.flatten!
    team_ids.uniq!

    # if the router didn't return any teams, use the default receiver teams
    team_ids = default_receiver_team_ids if team_ids.size == 0

    if team_ids.size > 0
      adapter_alert.logs.create!(message: "Router provided #{team_ids.size} teams: #{team_ids}")
      adapter_alert.meta["live_call_router_team_prefix_ids"] = team_ids
      adapter_alert.save!
    else
      adapter_alert.logs.create!(message: "Router provided no teams.")
    end
  end # end if routers

  if _teams_size == 0
    adapter_alert.logs.create!(message: "This integration is not configured to route to any teams. Hang up.")
    _twiml.say(message: "This integration is not configured to route to any teams. Goodbye", **SPEAK_OPTIONS)
    _twiml.hangup
    return adapter_controller&.render(xml: _twiml.to_xml)
  end

  if option_record && option_send_straight_to_voicemail && adapter_alert.meta["live_call_send_straight_to_voicemail"].nil?
    # flag this call to send straight to voicemail
    adapter_alert.meta["live_call_send_straight_to_voicemail"] = true
    adapter_alert.save!
  end

  if !adapter_alert.meta["live_call_welcome"] && option_welcome_media.present?
    adapter_alert.logs.create!(message: "Play welcome media to caller.")
    _twiml.play(url: option_welcome_media.url)
    adapter_alert.meta["live_call_welcome"] = true
    adapter_alert.save!
  end

  if selected_team
    adapter_alert.logs.create!(message: "Caller selected team '#{selected_team.name}'.") if _teams_size > 1 || option_force_input

    if adapter_alert.meta["live_call_send_straight_to_voicemail"] == true
      adapter_alert.destination_teams = [selected_team]
      adapter_alert.save!

      adapter_alert.logs.create!(message: "Send caller straight to voicemail (integration option).")

      _twiml.redirect(PagerTree::Integrations::Engine.routes.url_helpers.dropped_live_call_routing_twilio_v3_url(id, thirdparty_id: _thirdparty_id), method: "POST")

      return adapter_controller&.render(xml: _twiml.to_xml)
    end

    adapter_alert.logs.create!(message: "Play please wait media to caller.")
    _twiml.play(url: option_please_wait_media_url)
    friendly_name = adapter_alert.id

    # create the queue and save it off
    queue = _client.queues.create(friendly_name: friendly_name)
    adapter_alert.meta["live_call_queue_sid"] = queue.sid

    adapter_alert.destination_teams = [selected_team]

    # save the alert
    adapter_alert.save!

    _twiml.enqueue(
      name: friendly_name,
      action: PagerTree::Integrations::Engine.routes.url_helpers.queue_status_live_call_routing_twilio_v3_path(id, thirdparty_id: _thirdparty_id),
      method: "POST",
      wait_url: PagerTree::Integrations::Engine.routes.url_helpers.music_live_call_routing_twilio_v3_path(id, thirdparty_id: _thirdparty_id),
      wait_url_method: "GET"
    )
    adapter_alert.logs.create!(message: "Enqueue caller in Twilio queue '#{friendly_name}'.")

    # kick off the alert workflow
    adapter_alert.route_later
    adapter_alert.logs.create!(message: "Successfully enqueued alert team workflow.")

    if option_max_wait_time.present?
      # set the max wait time for the queue
      PagerTree::Integrations::LiveCallRouting::Twilio::V3::MaxWaitTimeJob.set(wait: option_max_wait_time.seconds).perform_later(id, adapter_alert.id)
      adapter_alert.logs.create!(message: "Max wait time set to #{option_max_wait_time} seconds.")
    end
  else
    adapter_alert.meta["live_call_repeat_count"] ||= 0
    adapter_alert.meta["live_call_repeat_count"] += 1
    adapter_alert.save!

    if adapter_alert.meta["live_call_repeat_count"] <= 3
      adapter_alert.logs.create!(message: "Caller has not selected a team. Playing team options.")
      _twiml.gather numDigits: _teams_size.to_s.size, timeout: 30 do |g|
        3.times do
          g.say(message: _teams_message, **SPEAK_OPTIONS)
          g.pause(length: 1)
        end
      end
    else
      adapter_alert.logs.create!(message: "Caller input: bad input (too many times). Hangup.")
      adapter_alert.resolve!(self)
      _twiml.say(message: "Too much invalid input. Goodbye.", **SPEAK_OPTIONS)
      _twiml.hangup
    end
  end

  adapter_controller&.render(xml: _twiml.to_xml)
end

#adapter_response_maintenance_modeObject



367
368
369
370
371
372
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 367

def adapter_response_maintenance_mode
  _twiml.say(message: "This integration is currently in maintenance mode. Goodbye!", **SPEAK_OPTIONS)
  _twiml.hangup

  adapter_controller&.render(xml: _twiml.to_xml)
end

#adapter_response_musicObject



374
375
376
377
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 374

def adapter_response_music
  _twiml.play(url: option_music_media_url, loop: 0)
  adapter_controller&.render(xml: _twiml.to_xml)
end

#adapter_response_upgradeObject



360
361
362
363
364
365
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 360

def adapter_response_upgrade
  _twiml.say(message: "This account must be upgraded to use live call routing. Goodbye!", **SPEAK_OPTIONS)
  _twiml.hangup

  adapter_controller&.render(xml: _twiml.to_xml)
end

#adapter_supports_auto_aggregate?Boolean

Returns:

  • (Boolean)


142
143
144
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 142

def adapter_supports_auto_aggregate?
  false
end

#adapter_supports_incoming?Boolean

Returns:

  • (Boolean)


134
135
136
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 134

def adapter_supports_incoming?
  true
end

#adapter_supports_outgoing?Boolean

Returns:

  • (Boolean)


138
139
140
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 138

def adapter_supports_outgoing?
  true
end

#adapter_thirdparty_idObject



174
175
176
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 174

def adapter_thirdparty_id
  _thirdparty_id
end

#adapter_will_route_alert?Boolean

Returns:

  • (Boolean)


154
155
156
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 154

def adapter_will_route_alert?
  true
end

#is_banned?Boolean

Returns:

  • (Boolean)


158
159
160
161
162
163
164
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 158

def is_banned?
  from_number = adapter_incoming_request_params.dig("From")
  return false unless from_number.present?
  option_banned_phones.any? { |x| from_number.include?(x) }
rescue
  false
end

#max_wait_time_reached!(alert_id) ⇒ Object



485
486
487
488
489
490
491
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 485

def max_wait_time_reached!(alert_id)
  # Handle max wait time reached logic
  self.adapter_alert = alerts.find(alert_id)
  return unless adapter_alert&.status_open?

  _on_max_wait_time_reached
end

#option_banned_phonesObject



94
95
96
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 94

def option_banned_phones
  option_banned_phone.split(",")
end

#option_banned_phones=(x) ⇒ Object



90
91
92
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 90

def option_banned_phones=(x)
  self.option_banned_phone = Array(x).join(",")
end

#option_banned_phones_listObject



126
127
128
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 126

def option_banned_phones_list
  option_banned_phones
end

#option_banned_phones_list=(x) ⇒ Object



114
115
116
117
118
119
120
121
122
123
124
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 114

def option_banned_phones_list=(x)
  # what comes in as json, via tagify
  uniq_array = []
  begin
    uniq_array = JSON.parse(x).map { |y| y["value"] }.uniq
  rescue JSON::ParserError => exception
    Rails.logger.debug(exception)
  end

  self.option_banned_phones = uniq_array
end

#option_connect_now_media_urlObject



58
59
60
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 58

def option_connect_now_media_url
  option_connect_now_media.present? ? option_connect_now_media.url : URI.join(Rails.application.routes.url_helpers.root_url, "audios/you-are-now-being-connected.mp3").to_s
end

#option_music_media_urlObject



62
63
64
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 62

def option_music_media_url
  option_music_media.present? ? option_music_media.url : "http://com.twilio.sounds.music.s3.amazonaws.com/oldDog_-_endless_goodbye_%28instr.%29.mp3"
end

#option_no_answer_media_urlObject



70
71
72
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 70

def option_no_answer_media_url
  option_no_answer_media.present? ? option_no_answer_media.url : URI.join(Rails.application.routes.url_helpers.root_url, "audios/no-answer.mp3").to_s
end

#option_no_answer_no_record_media_urlObject



78
79
80
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 78

def option_no_answer_no_record_media_url
  option_no_answer_no_record_media.present? ? option_no_answer_no_record_media.url : nil
end

#option_no_answer_thank_you_media_urlObject



74
75
76
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 74

def option_no_answer_thank_you_media_url
  option_no_answer_thank_you_media.present? ? option_no_answer_thank_you_media.url : URI.join(Rails.application.routes.url_helpers.root_url, "audios/thanks-for-message.mp3").to_s
end

#option_please_wait_media_urlObject



66
67
68
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 66

def option_please_wait_media_url
  option_please_wait_media.present? ? option_please_wait_media.url : URI.join(Rails.application.routes.url_helpers.root_url, "audios/please-wait.mp3").to_s
end

#option_record_emailsObject



86
87
88
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 86

def option_record_emails
  option_record_email.split(",")
end

#option_record_emails=(x) ⇒ Object



82
83
84
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 82

def option_record_emails=(x)
  self.option_record_email = Array(x).join(",")
end

#option_record_emails_listObject



110
111
112
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 110

def option_record_emails_list
  option_record_emails
end

#option_record_emails_list=(x) ⇒ Object



98
99
100
101
102
103
104
105
106
107
108
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 98

def option_record_emails_list=(x)
  # what comes in as json, via tagify
  uniq_array = []
  begin
    uniq_array = JSON.parse(x).map { |y| y["value"] }.uniq
  rescue JSON::ParserError => exception
    Rails.logger.debug(exception)
  end

  self.option_record_emails = uniq_array
end

#validate_record_emailsObject



130
131
132
# File 'app/models/pager_tree/integrations/live_call_routing/twilio/v3.rb', line 130

def validate_record_emails
  errors.add(:record_emails, "must be a valid email") if option_record_emails.any? { |x| !(x.match(URI::MailTo::EMAIL_REGEXP) || ["team", "team-admin", "on-call"].include?(x)) }
end