twilio-rails 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +23 -17
  3. data/app/controllers/twilio/rails/phone_controller.rb +6 -2
  4. data/app/controllers/twilio/rails/sms_controller.rb +6 -5
  5. data/app/jobs/twilio/rails/phone/attach_recording_job.rb +1 -0
  6. data/app/jobs/twilio/rails/phone/finished_call_job.rb +1 -0
  7. data/app/jobs/twilio/rails/phone/unanswered_call_job.rb +1 -0
  8. data/app/operations/twilio/rails/application_operation.rb +2 -1
  9. data/app/operations/twilio/rails/find_or_create_phone_caller_operation.rb +2 -1
  10. data/app/operations/twilio/rails/phone/attach_recording_operation.rb +3 -2
  11. data/app/operations/twilio/rails/phone/base_operation.rb +1 -0
  12. data/app/operations/twilio/rails/phone/create_operation.rb +11 -8
  13. data/app/operations/twilio/rails/phone/find_operation.rb +1 -0
  14. data/app/operations/twilio/rails/phone/finished_call_operation.rb +2 -1
  15. data/app/operations/twilio/rails/phone/receive_recording_operation.rb +3 -2
  16. data/app/operations/twilio/rails/phone/start_call_operation.rb +15 -13
  17. data/app/operations/twilio/rails/phone/twiml/after_operation.rb +2 -1
  18. data/app/operations/twilio/rails/phone/twiml/base_operation.rb +11 -5
  19. data/app/operations/twilio/rails/phone/twiml/error_operation.rb +2 -1
  20. data/app/operations/twilio/rails/phone/twiml/greeting_operation.rb +3 -2
  21. data/app/operations/twilio/rails/phone/twiml/invalid_phone_number_operation.rb +25 -0
  22. data/app/operations/twilio/rails/phone/twiml/prompt_operation.rb +6 -6
  23. data/app/operations/twilio/rails/phone/twiml/prompt_response_operation.rb +2 -1
  24. data/app/operations/twilio/rails/phone/twiml/request_validation_failure_operation.rb +1 -0
  25. data/app/operations/twilio/rails/phone/twiml/timeout_operation.rb +5 -4
  26. data/app/operations/twilio/rails/phone/unanswered_call_operation.rb +2 -1
  27. data/app/operations/twilio/rails/phone/update_operation.rb +1 -0
  28. data/app/operations/twilio/rails/phone/update_response_operation.rb +1 -0
  29. data/app/operations/twilio/rails/sms/base_operation.rb +1 -0
  30. data/app/operations/twilio/rails/sms/create_operation.rb +2 -1
  31. data/app/operations/twilio/rails/sms/find_message_operation.rb +1 -0
  32. data/app/operations/twilio/rails/sms/find_operation.rb +1 -0
  33. data/app/operations/twilio/rails/sms/send_operation.rb +12 -12
  34. data/app/operations/twilio/rails/sms/twiml/base_operation.rb +1 -0
  35. data/app/operations/twilio/rails/sms/twiml/error_operation.rb +1 -0
  36. data/app/operations/twilio/rails/sms/twiml/message_operation.rb +4 -3
  37. data/app/operations/twilio/rails/sms/update_message_operation.rb +1 -0
  38. data/lib/generators/twilio/rails/install/install_generator.rb +1 -0
  39. data/lib/generators/twilio/rails/install/templates/initializer.rb +5 -8
  40. data/lib/generators/twilio/rails/install/templates/message.rb +1 -0
  41. data/lib/generators/twilio/rails/install/templates/phone_call.rb +1 -0
  42. data/lib/generators/twilio/rails/install/templates/phone_caller.rb +1 -0
  43. data/lib/generators/twilio/rails/install/templates/recording.rb +1 -0
  44. data/lib/generators/twilio/rails/install/templates/response.rb +1 -0
  45. data/lib/generators/twilio/rails/install/templates/sms_conversation.rb +1 -0
  46. data/lib/generators/twilio/rails/phone_tree/phone_tree_generator.rb +1 -0
  47. data/lib/generators/twilio/rails/sms_responder/sms_responder_generator.rb +1 -0
  48. data/lib/generators/twilio/rails/sms_responder/templates/responder.rb.erb +2 -2
  49. data/lib/tasks/rails_tasks.rake +6 -5
  50. data/lib/twilio/rails/client.rb +9 -8
  51. data/lib/twilio/rails/concerns/has_direction.rb +2 -1
  52. data/lib/twilio/rails/concerns/has_phone_number.rb +13 -4
  53. data/lib/twilio/rails/concerns/has_time_scopes.rb +1 -0
  54. data/lib/twilio/rails/configuration.rb +46 -42
  55. data/lib/twilio/rails/formatter.rb +35 -29
  56. data/lib/twilio/rails/models/message.rb +1 -0
  57. data/lib/twilio/rails/models/phone_call.rb +1 -0
  58. data/lib/twilio/rails/models/phone_caller.rb +4 -3
  59. data/lib/twilio/rails/models/recording.rb +1 -0
  60. data/lib/twilio/rails/models/response.rb +7 -6
  61. data/lib/twilio/rails/models/sms_conversation.rb +1 -0
  62. data/lib/twilio/rails/phone/base_tree.rb +8 -8
  63. data/lib/twilio/rails/phone/tree.rb +8 -8
  64. data/lib/twilio/rails/phone/tree_macros.rb +27 -8
  65. data/lib/twilio/rails/phone.rb +3 -2
  66. data/lib/twilio/rails/phone_number.rb +6 -5
  67. data/lib/twilio/rails/phone_number_formatter/north_america.rb +52 -0
  68. data/lib/twilio/rails/phone_number_formatter.rb +20 -0
  69. data/lib/twilio/rails/railtie.rb +6 -1
  70. data/lib/twilio/rails/sms/delegated_responder.rb +6 -5
  71. data/lib/twilio/rails/sms/responder.rb +3 -2
  72. data/lib/twilio/rails/sms.rb +3 -2
  73. data/lib/twilio/rails/version.rb +1 -1
  74. data/lib/twilio/rails.rb +12 -26
  75. metadata +22 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cf947f0ddeb9ea673be2d9ce93cfcdff8e96290c231655c1ae724343bd699bff
4
- data.tar.gz: 701c404e5d513947077e189797643694cddf2db51c87356095330295fbda5871
3
+ metadata.gz: e5bf8143247d793c1fde606c166165114365e54f1e5f4f33aa3642ca58563e97
4
+ data.tar.gz: f6b35b2bce17b4240ea2f63705e095dedea6aacf9d321321719462126419d6c1
5
5
  SHA512:
6
- metadata.gz: 5259e6b131007ef2079977e1dc701c0b1636176242f3c935e1c95feee6dfa610890d8793eb0539eccdaad466a032ee8dab067bdccdbbee9ca6f3f22930e8bcf2
7
- data.tar.gz: 5d798cd90d9575e25cbe2fcf67c79c57ad6154f83a834b90fb24daa329f033061305b195af6696f7b2b8d1b0d2c7488eb9151ede45de083cce42949e879bd26a
6
+ metadata.gz: 1cafe99962f6610985140f2f8a553bf7521c67cb449be1ead374245f94d66b6bfca04e7200d7c9421f8d0c522eee1d543ffe41983ba80df3e5a41141021df75b
7
+ data.tar.gz: 345023826a58a7bd9718a5ef1ed5ef06114027f01082a3c5a56b9c50824c4ad028b69f8293794e0d428a1e70c1d062b01367ef48b29aac45f74e8b0135ce35a0
data/README.md CHANGED
@@ -1,18 +1,29 @@
1
1
  # Twilio Rails
2
2
 
3
3
  [![RSpec Tests](https://github.com/kmcphillips/twilio-rails/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/kmcphillips/twilio-rails/actions/workflows/ci.yml)
4
+ [![Gem Version](https://badge.fury.io/rb/twilio-rails.svg)](https://badge.fury.io/rb/twilio-rails)
4
5
 
5
6
  The `twilio-rails` gem is an opinionated Rails engine and a framework for building complex, realtime, stateful phone interactions in Rails without needing to directly interact with the Twilio API or use TwiML. It is not a replacement for the [`twilio-ruby` gem](https://github.com/twilio/twilio-ruby), but is rather built on top of it.
6
7
 
7
8
  The most powerful ability of this engine is to build phone trees (think of calling customer service and pressing 2 for account information or whatever) using a simple Ruby DSL.
8
9
 
9
10
  What does this mean in practice? **Call and find out!**
10
- * In Canada: 📞 **(204) 800-7772**
11
- * In the US: 📞 **(631) 800-7772**
11
+
12
+ The [`twilio-rails-example`](https://github.com/kmcphillips/twilio-rails-example) app is running here:
13
+
14
+ * **🇨🇦 Calling from Canada:** 📞 (204) 800-7772
15
+ * **🇺🇸 Calling from the US:** 📞 (631) 800-7772
16
+ * **Internationally:** [Sorry, not yet supported](https://github.com/kmcphillips/twilio-rails#limitations-and-known-issues)
17
+
18
+ There is also a more fun but less cleanly organized [`dial-a-haiku`](https://github.com/kmcphillips/dial-a-haiku) running here:
19
+ * **🇨🇦 Calling from Canada:** 📞 (249) 444-2458 / (249)44-HAIKU
20
+ * **🇺🇸 Calling from the US:** 📞 (341) 444-2458 / (341)44-HAIKU
21
+ * **Internationally:** [Sorry, not yet supported](https://github.com/kmcphillips/twilio-rails#limitations-and-known-issues)
22
+
12
23
 
13
24
  ## Documentation
14
25
 
15
- On [RubyDoc](https://rubydoc.info/github/kmcphillips/twilio-rails/main) or [Github pages](https://twilio-rails.kev.cool/).
26
+ On [RubyDoc](https://rubydoc.info/gems/twilio-rails) or [Github pages](https://twilio-rails.kev.cool/).
16
27
 
17
28
 
18
29
  ## Getting started
@@ -102,10 +113,11 @@ Both are explained in detail below.
102
113
 
103
114
  ### Example app
104
115
 
105
- An example Rails app demonstrating the framework is available at [`twilio-rails-example`](https://github.com/kmcphillips/twilio-rails-example). It can be run locally with some minimal configuration, or can be reached as a working Twilio app by calling:
116
+ An example Rails app demonstrating the framework is available at [`twilio-rails-example`](https://github.com/kmcphillips/twilio-rails-example). The seteps to go from new Rails app to running Twilio application are broken down into well documented commits. It's a great place to learn and experiment. It can be run locally with some minimal configuration, or can be reached as a working Twilio app by calling:
106
117
 
107
- * In Canada: 📞 **(204) 800-7772**
108
- * In the US: 📞 **(631) 800-7772**
118
+ * **🇨🇦 Calling from Canada:** 📞 (204) 800-7772
119
+ * **🇺🇸 Calling from the US:** 📞 (631) 800-7772
120
+ * **Internationally:** [Sorry, not yet supported](https://github.com/kmcphillips/twilio-rails#limitations-and-known-issues)
109
121
 
110
122
 
111
123
  ## How it works
@@ -262,7 +274,6 @@ prompt :record_your_feedback,
262
274
  gather: {
263
275
  type: :voice,
264
276
  length: 30,
265
- beep: true,
266
277
  transcribe: true,
267
278
  profanity_filter: true
268
279
  },
@@ -272,9 +283,9 @@ prompt :record_your_feedback,
272
283
  }
273
284
  ```
274
285
 
275
- The above `gather:` with `type: :voice` example will finish reading the message, play a beep, and then record the phone caller's speech for 30 seconds or until they press the `#` pound key. The phone tree will then immediately execute the `after:`, while the framework continues to handle the audio recording asynchronously. When Twilio makes it available, the audio file of the recording will be downloaded and stored as an ActiveStorage attachment in a `Recording` model as `response.recording`. If the `transcribe:` option is set to `true`, the voice in the recording will also attempt to be transcribed as text and stored as `response.transcription`. Importantly though, **neither are guaranteed to arrive or will arrive immediately**. In practice they both usually arrive within a few seconds, but can sometimes be blank or missing if the caller is silent or garbled. There is a cost to transcription so it can be disabled, and the `profanity_filter:` defaults to false and will just *** out any profanity in the transcription.
286
+ The above `gather:` with `type: :voice` example will finish reading the message and then record the phone caller's speech for 30 seconds or until they press the `#` pound key. The phone tree will then immediately execute the `after:`, while the framework continues to handle the audio recording asynchronously. When Twilio makes it available, the audio file of the recording will be downloaded and stored as an ActiveStorage attachment in a `Recording` model as `response.recording`. If the `transcribe:` option is set to `true`, the voice in the recording will also attempt to be transcribed as text and stored as `response.transcription`. Importantly though, **neither are guaranteed to arrive or will arrive immediately**. In practice they both usually arrive within a few seconds, but can sometimes be blank or missing if the caller is silent or garbled. There is a cost to transcription so it can be disabled, and the `profanity_filter:` defaults to false and will just *** out any profanity in the transcription.
276
287
 
277
- Finally, the `gather:` can also accept `type: :speech` which is a specialzed model designed to identify voice in realtime. It will provide the `response.transcription` field immediately, making it available in the `after:` proc or in the next prompt. But the tradeoffs are that it does not provide a recording, there is a time gap of a few seconds between prompts, and it is more expensive. See the [Twilio documentation for specifics](https://www.twilio.com/docs/voice/twiml/gather#speechmodel). The keys it expects match the documentation, `speech_model:`, `speech_timeout:`, `language:` (defaults to "en-US"), and `encanced:` (defaults to false).
288
+ Finally, the `gather:` can also accept `type: :speech` which is a specialzed model designed to identify voice in realtime. It will provide the `response.transcription` field immediately, making it available in the `after:` proc or in the next prompt. But the tradeoffs are that it does not provide a recording, there is a time gap of a few seconds between prompts, and it is more expensive. See the [Twilio documentation for specifics](https://www.twilio.com/docs/voice/twiml/gather#speechmodel). The keys it expects match the documentation, `speech_model:`, `speech_timeout:`, `language:` (defaults to "en-US"), and `enhanced:` (defaults to false).
278
289
 
279
290
  ```ruby
280
291
  prompt :what_direction_should_we_go,
@@ -308,7 +319,7 @@ prompt :what_direction_should_we_go,
308
319
  }
309
320
  ```
310
321
 
311
- To inspect the implementation and get further detail, most of the magic happens in [`Twilio::Rails::Phone::Tree`](lib/twilio/rails/phone/tree.rb) and the operations under [`Twilio::Rails::Phone::Twiml`](app/operations/twilio/rails/phone/twiml/) where the DSL is defined and then converted inbot [TwiML](https://www.twilio.com/docs/voice/twiml).
322
+ To inspect the implementation and get further detail, most of the magic happens in [`Twilio::Rails::Phone::Tree`](lib/twilio/rails/phone/tree.rb) and the operations under [`Twilio::Rails::Phone::Twiml`](app/operations/twilio/rails/phone/twiml/) where the DSL is defined and then converted into [TwiML](https://www.twilio.com/docs/voice/twiml).
312
323
 
313
324
 
314
325
  ### Make an outgoing phone call
@@ -326,7 +337,7 @@ Twilio::Phone::StartCallOperation.call(
326
337
 
327
338
  ### SMS responders
328
339
 
329
- > **Warning**
340
+ > [!IMPORTANT]
330
341
  > Due to how Twilio makes API calls into the application for SMS messages, SMS responders require Rails sessions to be enabled and setup in order to handle SMS messages.
331
342
 
332
343
  Twilio provides a hook for incoming SMS messages and can send SMS messages to any phone number. This gem provides a simple method for handling SMS conversations, though it does not provide a full stateful tree structure.
@@ -365,13 +376,8 @@ phone_caller = Twilio::Rails::FindOrCreatePhoneCallerOperation.call(phone_number
365
376
 
366
377
  All errors are subclasses of [`Twilio::Rails::Error`](lib/twilio/rails.rb). They are grouped under [`Twilio::Rails::Phone::Error`](lib/twilio/rails/phone.rb) and [`Twilio::Rails::SMS::Error`](lib/twilio/rails/sms.rb), and then further specialized from there.
367
378
 
368
- There is a configuration option to add an exception notifier in some important places in the framework. It will never catch or handle exceptions.
379
+ There are a few places where exceptions are notified from inside the framework using `::Rails.error.report`. They are never rescued or handled. See the [Rails documentation](https://api.rubyonrails.org/classes/ActiveSupport/ErrorReporter.html) for how to use the error reporter.
369
380
 
370
- ```ruby
371
- config.exception_notifier = ->(exception, message, context, exception_binding) {
372
- # Send an email or use some kind of service etc.
373
- }
374
- ```
375
381
 
376
382
  ### The rest of the documentation
377
383
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  class PhoneController < ApplicationController
@@ -10,6 +11,9 @@ module Twilio
10
11
  respond_to do |format|
11
12
  format.xml do
12
13
  phone_call = Twilio::Rails::Phone::CreateOperation.call(params: params_hash, tree: tree)
14
+ rescue
15
+ render xml: Twilio::Rails::Phone::Twiml::InvalidPhoneNumberOperation.call(tree: tree)
16
+ else
13
17
  render xml: Twilio::Rails::Phone::Twiml::GreetingOperation.call(phone_call_id: phone_call.id, tree: tree)
14
18
  end
15
19
  end
@@ -39,7 +43,7 @@ module Twilio
39
43
  format.xml do
40
44
  phone_call = Twilio::Rails::Phone::FindOperation.call(params: params_hash)
41
45
  phone_call = Twilio::Rails::Phone::UpdateOperation.call(phone_call_id: phone_call.id, params: params_hash)
42
- response = Twilio::Rails::Phone::UpdateResponseOperation.call(phone_call_id: phone_call.id, response_id: params[:response_id].to_i, params: params_hash)
46
+ Twilio::Rails::Phone::UpdateResponseOperation.call(phone_call_id: phone_call.id, response_id: params[:response_id].to_i, params: params_hash)
43
47
  render xml: Twilio::Rails::Phone::Twiml::PromptResponseOperation.call(phone_call_id: phone_call.id, tree: tree, response_id: params[:response_id].to_i, params: params_hash)
44
48
  end
45
49
  end
@@ -70,7 +74,7 @@ module Twilio
70
74
  respond_to do |format|
71
75
  format.xml do
72
76
  phone_call = Twilio::Rails::Phone::FindOperation.call(params: params_hash)
73
- phone_call = Twilio::Rails::Phone::UpdateOperation.call(phone_call_id: phone_call.id, params: params_hash)
77
+ Twilio::Rails::Phone::UpdateOperation.call(phone_call_id: phone_call.id, params: params_hash)
74
78
 
75
79
  head :ok
76
80
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  class SMSController < ::Twilio::Rails::ApplicationController
@@ -10,7 +11,7 @@ module Twilio
10
11
  respond_to do |format|
11
12
  format.xml do
12
13
  if spam?
13
- render xml: Twilio::Rails::SMS::Twiml::ErrorOperation.call()
14
+ render xml: Twilio::Rails::SMS::Twiml::ErrorOperation.call
14
15
  else
15
16
  if session[:sms_conversation_id].present?
16
17
  conversation = Twilio::Rails::SMS::FindOperation.call(sms_conversation_id: session[:sms_conversation_id])
@@ -29,10 +30,10 @@ module Twilio
29
30
  respond_to do |format|
30
31
  format.xml do
31
32
  if params[:message_id].present?
32
- message = Twilio::Rails::SMS::UpdateMessageOperation.call(message_id: params[:message_id].to_i, params: params_hash)
33
+ Twilio::Rails::SMS::UpdateMessageOperation.call(message_id: params[:message_id].to_i, params: params_hash)
33
34
  else
34
35
  message = Twilio::Rails::SMS::FindMessageOperation.call(params: params_hash)
35
- message = Twilio::Rails::SMS::UpdateMessageOperation.call(message_id: message.id, params: params_hash)
36
+ Twilio::Rails::SMS::UpdateMessageOperation.call(message_id: message.id, params: params_hash)
36
37
  end
37
38
 
38
39
  head :ok
@@ -46,14 +47,14 @@ module Twilio
46
47
  if params["AccountSid"] != Twilio::Rails.config.account_sid
47
48
  respond_to do |format|
48
49
  format.xml do
49
- render xml: Twilio::Rails::SMS::Twiml::ErrorOperation.call()
50
+ render xml: Twilio::Rails::SMS::Twiml::ErrorOperation.call
50
51
  end
51
52
  end
52
53
  end
53
54
  end
54
55
 
55
56
  def spam?
56
- Twilio::Rails.config.spam_filter && Twilio::Rails.config.spam_filter.call(params)
57
+ Twilio::Rails.config.spam_filter&.call(params)
57
58
  end
58
59
 
59
60
  def params_hash
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module Phone
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module Phone
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module Phone
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  class ApplicationOperation < ActiveOperation::Base
@@ -8,7 +9,7 @@ module Twilio
8
9
  executable.call
9
10
  @operation_runtime_stop = Process.clock_gettime(Process::CLOCK_MONOTONIC)
10
11
 
11
- ::Twilio::Rails.config.logger.tagged(self.class) { |l| l.info("execution time #{ instance.operation_runtime_seconds } seconds")}
12
+ ::Twilio::Rails.config.logger.tagged(self.class) { |l| l.info("execution time #{instance.operation_runtime_seconds} seconds") }
12
13
  end
13
14
 
14
15
  protected
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  # Finds and returns a {Twilio::Rails::Models::PhoneCaller} by phone number, or creates a new one if it does not
@@ -22,7 +23,7 @@ module Twilio
22
23
  private
23
24
 
24
25
  def valid_phone_number
25
- Twilio::Rails::Formatter.coerce_to_valid_phone_number(phone_number)
26
+ Twilio::Rails::PhoneNumberFormatter.coerce(phone_number)
26
27
  end
27
28
  end
28
29
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module Phone
@@ -12,7 +13,7 @@ module Twilio
12
13
 
13
14
  if !recording.audio.attached?
14
15
  if recording.url.blank?
15
- raise Twilio::Rails::Phone::Error, "[AttachRecordingOperation] Has a blank URL and cannot be fetched recording_id=#{ recording.id }"
16
+ raise Twilio::Rails::Phone::Error, "[AttachRecordingOperation] Has a blank URL and cannot be fetched recording_id=#{recording.id}"
16
17
  end
17
18
 
18
19
  response = Faraday.get(recording.url)
@@ -21,7 +22,7 @@ module Twilio
21
22
  recording.audio.attach(io: StringIO.new(response.body), filename: "recording.wav", content_type: "audio/wav")
22
23
  recording.save!
23
24
  else
24
- raise Twilio::Rails::Phone::Error, "[AttachRecordingOperation] Failed to fetch recording recording_id=#{ recording.id } HTTP#{ response.status } from #{ recording.url }"
25
+ raise Twilio::Rails::Phone::Error, "[AttachRecordingOperation] Failed to fetch recording recording_id=#{recording.id} HTTP#{response.status} from #{recording.url}"
25
26
  end
26
27
  end
27
28
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module Phone
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module Phone
@@ -16,19 +17,19 @@ module Twilio
16
17
  from_number: params["Caller"].presence || params["From"].presence,
17
18
  from_city: params["CallerCity"].presence || params["FromCity"].presence,
18
19
  from_province: params["CallerState"].presence || params["FromState"].presence,
19
- from_country: params["CallerCountry"].presence || params["FromCountry"].presence,
20
+ from_country: params["CallerCountry"].presence || params["FromCountry"].presence
20
21
  )
21
22
 
22
23
  phone_caller = Twilio::Rails::FindOrCreatePhoneCallerOperation.call(phone_number: phone_call.from_number)
23
24
 
24
25
  if !phone_caller
25
- error_message = if !Twilio::Rails::Formatter.valid_north_american_phone_number?(phone_call.from_number)
26
+ error_message = if !Twilio::Rails::PhoneNumberFormatter.valid?(phone_call.from_number)
26
27
  "The phone number is invalid."
27
28
  else
28
29
  "The phone caller could not be persisted or retrieved."
29
30
  end
30
31
 
31
- raise Twilio::Rails::Phone::Error, "Failed to handle incoming Twilio phone call. #{ error_message } phone_number=#{ phone_call.from_number } call_sid=#{ params["CallSid"] }"
32
+ raise Twilio::Rails::Phone::Error, "Failed to handle incoming Twilio phone call. #{error_message} phone_number=#{phone_call.from_number} call_sid=#{params["CallSid"]}"
32
33
  end
33
34
 
34
35
  phone_call.phone_caller = phone_caller
@@ -36,11 +37,13 @@ module Twilio
36
37
 
37
38
  phone_call
38
39
  rescue => e
39
- Twilio::Rails.notify_exception(e,
40
- message: "Failed to handle incoming Twilio phone call.",
41
- context: { params: params, tree: tree },
42
- exception_binding: binding
43
- )
40
+ ::Rails.error.report(e,
41
+ handled: false,
42
+ context: {
43
+ message: "Failed to handle incoming Twilio phone call.",
44
+ params: params,
45
+ tree: tree
46
+ })
44
47
  raise
45
48
  end
46
49
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module Phone
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module Phone
@@ -8,7 +9,7 @@ module Twilio
8
9
  Twilio::Rails.config.logger.tagged(self.class) { |l| l.warn("Skipping duplicate finished call job") }
9
10
  else
10
11
  phone_call.update!(finished: true)
11
- phone_call.tree.finished_call.call(phone_call) if phone_call.tree.finished_call
12
+ phone_call.tree.finished_call&.call(phone_call)
12
13
  end
13
14
  end
14
15
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module Phone
@@ -10,12 +11,12 @@ module Twilio
10
11
  response = phone_call.responses.find(response_id)
11
12
 
12
13
  if phone_call.recordings.sid(params["RecordingSid"]).any?
13
- Twilio::Rails.config.logger.tagged(self.class) { |l| l.warn("duplicate recording for response_id=#{ response.id } recording_sid=#{ params["RecordingSid"] }") }
14
+ Twilio::Rails.config.logger.tagged(self.class) { |l| l.warn("duplicate recording for response_id=#{response.id} recording_sid=#{params["RecordingSid"]}") }
14
15
  else
15
16
  recording = phone_call.recordings.build(
16
17
  recording_sid: params["RecordingSid"],
17
18
  url: params["RecordingUrl"],
18
- duration: params["RecordingDuration"].presence,
19
+ duration: params["RecordingDuration"].presence
19
20
  )
20
21
  recording.save!
21
22
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module Phone
@@ -21,31 +22,32 @@ module Twilio
21
22
  "CallSid" => nil,
22
23
  "direction" => "outbound",
23
24
  "To" => from,
24
- "From" => to,
25
+ "From" => to
25
26
  }
26
27
 
27
28
  begin
28
29
  sid = Twilio::Rails::Client.start_call(url: tree.outbound_url, to: to, from: from, answering_machine_detection: answering_machine_detection)
29
30
  params["CallSid"] = sid
30
31
  rescue Twilio::REST::TwilioError => e
31
- Twilio::Rails.notify_exception(e,
32
- message: "Failed to start Twilio phone call. Got REST error response.",
33
- context: { params: params },
34
- exception_binding: binding
35
- )
32
+ ::Rails.error.report(e,
33
+ handled: false,
34
+ context: {
35
+ message: "Failed to start Twilio phone call. Got REST error response.",
36
+ params: params
37
+ })
36
38
  raise
37
39
  rescue => e
38
- Twilio::Rails.notify_exception(e,
39
- message: "Failed to start Twilio phone call. Got unknown error.",
40
- context: { params: params },
41
- exception_binding: binding
42
- )
40
+ ::Rails.error.report(e,
41
+ handled: false,
42
+ context: {
43
+ message: "Failed to start Twilio phone call. Got unknown error.",
44
+ params: params
45
+ })
43
46
  raise
44
47
  end
45
48
 
46
49
  # TODO: I think this may be a race condition
47
- phone_call = Twilio::Rails::Phone::CreateOperation.call(params: params, tree: tree)
48
- phone_call
50
+ Twilio::Rails::Phone::CreateOperation.call(params: params, tree: tree)
49
51
  end
50
52
  end
51
53
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module Phone
@@ -27,7 +28,7 @@ module Twilio
27
28
  end
28
29
  end
29
30
 
30
- Twilio::Rails.config.logger.info("after_twiml: #{twiml_response.to_s}")
31
+ Twilio::Rails.config.logger.info("after_twiml: #{twiml_response}")
31
32
  twiml_response.to_s
32
33
  end
33
34
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module Phone
@@ -20,17 +21,17 @@ module Twilio
20
21
  message = message.call(response) if message.is_a?(Proc)
21
22
  message = Twilio::Rails::Phone::Tree::Message.new(**message) if message.is_a?(Hash)
22
23
  next if message.blank?
23
- message = Twilio::Rails::Phone::Tree::Message.new(say: message, voice: tree.config[:voice]) if message.is_a?(String)
24
+ message = Twilio::Rails::Phone::Tree::Message.new(say: message, voice: voice) if message.is_a?(String)
24
25
 
25
- raise Twilio::Rails::Phone::InvalidTreeError "unknown message #{ message } is a #{ message.class }" unless message.is_a?(Twilio::Rails::Phone::Tree::Message)
26
+ raise Twilio::Rails::Phone::InvalidTreeError "unknown message #{message} is a #{message.class}" unless message.is_a?(Twilio::Rails::Phone::Tree::Message)
26
27
 
27
28
  # TODO: if we want to make a transcript of sent messages this is where we would record outgoing messages
28
29
 
29
30
  if message.say?
30
31
  value = message.value
31
32
  value = message.value.call(response) if message.value.is_a?(Proc)
32
- twiml.say(voice: message.voice || tree.config[:voice], message: value) do |say|
33
- message.block.call(say) if message.block
33
+ twiml.say(voice: message.voice || voice, message: value) do |say|
34
+ message.block&.call(say)
34
35
  end
35
36
  elsif message.play?
36
37
  value = message.value
@@ -39,10 +40,15 @@ module Twilio
39
40
  elsif message.pause?
40
41
  twiml.pause(length: message.value)
41
42
  else
42
- raise Twilio::Rails::Phone::InvalidTreeError, "unknown message #{ message }"
43
+ raise Twilio::Rails::Phone::InvalidTreeError, "unknown message #{message}"
43
44
  end
44
45
  end
45
46
  end
47
+
48
+ # @return [String] the voice to use for the TwiML response if configured on the tree.
49
+ def voice
50
+ tree.config[:voice] if tree
51
+ end
46
52
  end
47
53
  end
48
54
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module Phone
@@ -12,7 +13,7 @@ module Twilio
12
13
  add_messages(twiml, message_set: messages, response: phone_call.responses.build) if messages
13
14
  twiml.hangup
14
15
 
15
- Twilio::Rails.config.logger.info("error_twiml: #{twiml.to_s}")
16
+ Twilio::Rails.config.logger.info("error_twiml: #{twiml}")
16
17
  twiml.to_s
17
18
  end
18
19
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module Phone
@@ -7,8 +8,8 @@ module Twilio
7
8
  input :tree, accepts: Twilio::Rails::Phone::Tree, type: :keyword, required: true
8
9
 
9
10
  def execute
10
- if !phone_caller.valid_north_american_phone_number? && tree.config[:invalid_phone_number]
11
- Twilio::Rails::Phone::Twiml::ErrorOperation.call(phone_call_id: phone_call.id, tree: tree, messages: tree.config[:invalid_phone_number])
11
+ if !phone_caller.valid_phone_number? && tree.config[:invalid_phone_number]
12
+ Twilio::Rails::Phone::Twiml::InvalidPhoneNumberOperation.call(phone_call_id: phone_call.id, tree: tree)
12
13
  else
13
14
  after = tree.greeting
14
15
  after = Twilio::Rails::Phone::Tree::After.new(after.proc.call(phone_call.responses.build)) if after.proc
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Twilio
4
+ module Rails
5
+ module Phone
6
+ module Twiml
7
+ class InvalidPhoneNumberOperation < Twilio::Rails::Phone::Twiml::BaseOperation
8
+ input :tree, accepts: Twilio::Rails::Phone::Tree, type: :keyword, required: true
9
+ input :phone_call_id, accepts: Integer, type: :keyword, required: false, default: nil
10
+
11
+ def execute
12
+ twiml = Twilio::TwiML::VoiceResponse.new
13
+ messages = tree.config[:invalid_phone_number] || []
14
+ response = phone_call.responses.build if phone_call_id
15
+ add_messages(twiml, message_set: messages, response: response)
16
+ twiml.hangup
17
+
18
+ Twilio::Rails.config.logger.info("error_twiml: #{twiml}")
19
+ twiml.to_s
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module Phone
@@ -12,7 +13,7 @@ module Twilio
12
13
 
13
14
  response = phone_call.responses.find(response_id)
14
15
  prompt = tree.prompts[response.prompt_handle]
15
- raise Twilio::Rails::Phone::InvalidTreeError, "cannot find #{ response.prompt_handle } in #{ tree.name }" unless prompt
16
+ raise Twilio::Rails::Phone::InvalidTreeError, "cannot find #{response.prompt_handle} in #{tree.name}" unless prompt
16
17
 
17
18
  twiml_response = Twilio::TwiML::VoiceResponse.new do |twiml|
18
19
  unless prompt.gather&.interrupt?
@@ -30,7 +31,7 @@ module Twilio
30
31
  input: "dtmf",
31
32
  num_digits: prompt.gather.args[:number],
32
33
  timeout: prompt.gather.args[:timeout],
33
- action_on_empty_result: false,
34
+ action_on_empty_result: false
34
35
  }
35
36
 
36
37
  args[:finish_on_key] = prompt.gather.args[:finish_on_key] if prompt.gather.args[:finish_on_key]
@@ -48,7 +49,6 @@ module Twilio
48
49
  when :voice
49
50
  args = {
50
51
  max_length: prompt.gather.args[:length],
51
- play_beep: prompt.gather.args[:beep],
52
52
  # trim: "trim-silence",
53
53
  timeout: prompt.gather.args[:timeout],
54
54
  action: ::Twilio::Rails::Engine.routes.url_helpers.phone_prompt_response_path(
@@ -58,7 +58,7 @@ module Twilio
58
58
  ),
59
59
  recording_status_callback: ::Twilio::Rails::Engine.routes.url_helpers.phone_receive_recording_path(
60
60
  response_id: response.id
61
- ),
61
+ )
62
62
  }
63
63
 
64
64
  if prompt.gather.args[:transcribe]
@@ -80,7 +80,7 @@ module Twilio
80
80
  timeout: prompt.gather.args[:timeout],
81
81
  action_on_empty_result: true,
82
82
  language: prompt.gather.args[:language].presence || "en-US",
83
- enhanced: !!prompt.gather.args[:enhanced],
83
+ enhanced: !!prompt.gather.args[:enhanced]
84
84
  }
85
85
 
86
86
  args[:speech_timeout] = prompt.gather.args[:speech_timeout] if prompt.gather.args[:speech_timeout]
@@ -99,7 +99,7 @@ module Twilio
99
99
  end
100
100
  end
101
101
 
102
- Twilio::Rails.config.logger.info("prompt_twiml: #{twiml_response.to_s}")
102
+ Twilio::Rails.config.logger.info("prompt_twiml: #{twiml_response}")
103
103
  twiml_response.to_s
104
104
  end
105
105
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module Phone
@@ -15,7 +16,7 @@ module Twilio
15
16
  response = Twilio::Rails::Phone::UpdateResponseOperation.call(params: params, response_id: response.id, phone_call_id: phone_call.id)
16
17
 
17
18
  prompt = tree.prompts[response.prompt_handle]
18
- raise Twilio::Rails::Phone::InvalidTreeError, "cannot find #{ response.prompt_handle } in #{ tree.name }" unless prompt
19
+ raise Twilio::Rails::Phone::InvalidTreeError, "cannot find #{response.prompt_handle} in #{tree.name}" unless prompt
19
20
 
20
21
  after = prompt.after
21
22
  after = Twilio::Rails::Phone::Tree::After.new(after.proc.call(response)) if after.proc
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Twilio
3
4
  module Rails
4
5
  module Phone