twilio-rails 1.0.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 (90) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +0 -0
  3. data/LICENSE +21 -0
  4. data/README.md +413 -0
  5. data/Rakefile +8 -0
  6. data/app/assets/config/twilio_rails_manifest.js +1 -0
  7. data/app/assets/stylesheets/twilio/rails/application.css +15 -0
  8. data/app/controllers/twilio/rails/application_controller.rb +6 -0
  9. data/app/controllers/twilio/rails/phone_controller.rb +112 -0
  10. data/app/controllers/twilio/rails/sms_controller.rb +64 -0
  11. data/app/helpers/twilio/rails/application_helper.rb +6 -0
  12. data/app/jobs/twilio/rails/application_job.rb +6 -0
  13. data/app/jobs/twilio/rails/phone/attach_recording_job.rb +15 -0
  14. data/app/jobs/twilio/rails/phone/finished_call_job.rb +15 -0
  15. data/app/jobs/twilio/rails/phone/unanswered_call_job.rb +15 -0
  16. data/app/mailers/twilio/rails/application_mailer.rb +8 -0
  17. data/app/models/twilio/rails/application_record.rb +7 -0
  18. data/app/operations/twilio/rails/application_operation.rb +21 -0
  19. data/app/operations/twilio/rails/find_or_create_phone_caller_operation.rb +29 -0
  20. data/app/operations/twilio/rails/phone/attach_recording_operation.rb +31 -0
  21. data/app/operations/twilio/rails/phone/base_operation.rb +21 -0
  22. data/app/operations/twilio/rails/phone/create_operation.rb +49 -0
  23. data/app/operations/twilio/rails/phone/find_operation.rb +14 -0
  24. data/app/operations/twilio/rails/phone/finished_call_operation.rb +17 -0
  25. data/app/operations/twilio/rails/phone/receive_recording_operation.rb +35 -0
  26. data/app/operations/twilio/rails/phone/start_call_operation.rb +53 -0
  27. data/app/operations/twilio/rails/phone/twiml/after_operation.rb +37 -0
  28. data/app/operations/twilio/rails/phone/twiml/base_operation.rb +50 -0
  29. data/app/operations/twilio/rails/phone/twiml/error_operation.rb +22 -0
  30. data/app/operations/twilio/rails/phone/twiml/greeting_operation.rb +22 -0
  31. data/app/operations/twilio/rails/phone/twiml/prompt_operation.rb +109 -0
  32. data/app/operations/twilio/rails/phone/twiml/prompt_response_operation.rb +29 -0
  33. data/app/operations/twilio/rails/phone/twiml/request_validation_failure_operation.rb +16 -0
  34. data/app/operations/twilio/rails/phone/twiml/timeout_operation.rb +48 -0
  35. data/app/operations/twilio/rails/phone/unanswered_call_operation.rb +22 -0
  36. data/app/operations/twilio/rails/phone/update_operation.rb +26 -0
  37. data/app/operations/twilio/rails/phone/update_response_operation.rb +38 -0
  38. data/app/operations/twilio/rails/sms/base_operation.rb +17 -0
  39. data/app/operations/twilio/rails/sms/create_operation.rb +23 -0
  40. data/app/operations/twilio/rails/sms/find_message_operation.rb +15 -0
  41. data/app/operations/twilio/rails/sms/find_operation.rb +15 -0
  42. data/app/operations/twilio/rails/sms/send_operation.rb +102 -0
  43. data/app/operations/twilio/rails/sms/twiml/base_operation.rb +11 -0
  44. data/app/operations/twilio/rails/sms/twiml/error_operation.rb +15 -0
  45. data/app/operations/twilio/rails/sms/twiml/message_operation.rb +49 -0
  46. data/app/operations/twilio/rails/sms/update_message_operation.rb +27 -0
  47. data/app/views/layouts/twilio/rails/application.html.erb +15 -0
  48. data/config/routes.rb +16 -0
  49. data/lib/generators/twilio/rails/install/USAGE +15 -0
  50. data/lib/generators/twilio/rails/install/install_generator.rb +34 -0
  51. data/lib/generators/twilio/rails/install/templates/initializer.rb +83 -0
  52. data/lib/generators/twilio/rails/install/templates/message.rb +4 -0
  53. data/lib/generators/twilio/rails/install/templates/migration.rb +89 -0
  54. data/lib/generators/twilio/rails/install/templates/phone_call.rb +4 -0
  55. data/lib/generators/twilio/rails/install/templates/phone_caller.rb +4 -0
  56. data/lib/generators/twilio/rails/install/templates/recording.rb +4 -0
  57. data/lib/generators/twilio/rails/install/templates/response.rb +4 -0
  58. data/lib/generators/twilio/rails/install/templates/sms_conversation.rb +4 -0
  59. data/lib/generators/twilio/rails/phone_tree/USAGE +8 -0
  60. data/lib/generators/twilio/rails/phone_tree/phone_tree_generator.rb +12 -0
  61. data/lib/generators/twilio/rails/phone_tree/templates/tree.rb.erb +13 -0
  62. data/lib/generators/twilio/rails/sms_responder/USAGE +8 -0
  63. data/lib/generators/twilio/rails/sms_responder/sms_responder_generator.rb +12 -0
  64. data/lib/generators/twilio/rails/sms_responder/templates/responder.rb.erb +10 -0
  65. data/lib/tasks/rails_tasks.rake +45 -0
  66. data/lib/twilio/rails/client.rb +75 -0
  67. data/lib/twilio/rails/concerns/has_direction.rb +25 -0
  68. data/lib/twilio/rails/concerns/has_phone_number.rb +27 -0
  69. data/lib/twilio/rails/concerns/has_time_scopes.rb +19 -0
  70. data/lib/twilio/rails/configuration.rb +380 -0
  71. data/lib/twilio/rails/engine.rb +11 -0
  72. data/lib/twilio/rails/formatter.rb +93 -0
  73. data/lib/twilio/rails/models/message.rb +21 -0
  74. data/lib/twilio/rails/models/phone_call.rb +132 -0
  75. data/lib/twilio/rails/models/phone_caller.rb +100 -0
  76. data/lib/twilio/rails/models/recording.rb +27 -0
  77. data/lib/twilio/rails/models/response.rb +153 -0
  78. data/lib/twilio/rails/models/sms_conversation.rb +29 -0
  79. data/lib/twilio/rails/phone/base_tree.rb +229 -0
  80. data/lib/twilio/rails/phone/tree.rb +229 -0
  81. data/lib/twilio/rails/phone/tree_macros.rb +147 -0
  82. data/lib/twilio/rails/phone.rb +12 -0
  83. data/lib/twilio/rails/phone_number.rb +29 -0
  84. data/lib/twilio/rails/railtie.rb +17 -0
  85. data/lib/twilio/rails/sms/delegated_responder.rb +97 -0
  86. data/lib/twilio/rails/sms/responder.rb +33 -0
  87. data/lib/twilio/rails/sms.rb +12 -0
  88. data/lib/twilio/rails/version.rb +5 -0
  89. data/lib/twilio/rails.rb +89 -0
  90. metadata +289 -0
@@ -0,0 +1,6 @@
1
+ module Twilio
2
+ module Rails
3
+ class ApplicationJob < ActiveJob::Base
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ module Phone
5
+ # Performs the {Twilio::Rails::Phone::AttachRecordingOperation}.
6
+ class AttachRecordingJob < ::Twilio::Rails::ApplicationJob
7
+ queue_as :default
8
+
9
+ def perform(recording_id:)
10
+ Twilio::Rails::Phone::AttachRecordingOperation.call(recording_id: recording_id)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ module Phone
5
+ # Performs the {Twilio::Rails::Phone::FinishedCallOperation}.
6
+ class FinishedCallJob < ::Twilio::Rails::ApplicationJob
7
+ queue_as :default
8
+
9
+ def perform(phone_call_id:)
10
+ Twilio::Rails::Phone::FinishedCallOperation.call(phone_call_id: phone_call_id)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ module Phone
5
+ # Performs the {Twilio::Rails::Phone::UnansweredCallOperation}.
6
+ class UnansweredCallJob < ::Twilio::Rails::ApplicationJob
7
+ queue_as :default
8
+
9
+ def perform(phone_call_id:)
10
+ Twilio::Rails::Phone::UnansweredCallOperation.call(phone_call_id: phone_call_id)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,8 @@
1
+ module Twilio
2
+ module Rails
3
+ class ApplicationMailer < ActionMailer::Base
4
+ default from: "from@example.com"
5
+ layout "mailer"
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ module Twilio
2
+ module Rails
3
+ class ApplicationRecord < ActiveRecord::Base
4
+ self.abstract_class = true
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ class ApplicationOperation < ActiveOperation::Base
5
+ # Annotates the log output for every operation with the execution time.
6
+ around do |instance, executable|
7
+ @operation_runtime_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
8
+ executable.call
9
+ @operation_runtime_stop = Process.clock_gettime(Process::CLOCK_MONOTONIC)
10
+
11
+ ::Twilio::Rails.config.logger.tagged(self.class) { |l| l.info("execution time #{ instance.operation_runtime_seconds } seconds")}
12
+ end
13
+
14
+ protected
15
+
16
+ def operation_runtime_seconds
17
+ (@operation_runtime_stop || Process.clock_gettime(Process::CLOCK_MONOTONIC)) - @operation_runtime_start
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ # Finds and returns a {Twilio::Rails::Models::PhoneCaller} by phone number, or creates a new one if it does not
5
+ # exist. The operation will `halt` if the phone number is not valid or blank.
6
+ #
7
+ # *Note:* Operations should be called with `call(params)` and not by calling `new(params).execute` directly.
8
+ class FindOrCreatePhoneCallerOperation < ::Twilio::Rails::ApplicationOperation
9
+ input :phone_number, accepts: String, type: :keyword, required: false
10
+
11
+ # @param phone_number [String] The phone number to find or create the phone caller.
12
+ # @return [Twilio::Rails::Models::PhoneCaller] The found or newly created phone caller.
13
+ def execute
14
+ halt nil unless valid_phone_number
15
+
16
+ phone_caller = ::Twilio::Rails.config.phone_caller_class.find_or_initialize_by(phone_number: valid_phone_number)
17
+ phone_caller.save! if phone_caller.new_record?
18
+
19
+ phone_caller
20
+ end
21
+
22
+ private
23
+
24
+ def valid_phone_number
25
+ Twilio::Rails::Formatter.coerce_to_valid_phone_number(phone_number)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ module Phone
5
+ # Called by {Twilio::Rails::Phone::ReceiveRecordingOperation} to download and store the audio file from the URL
6
+ # provided by Twilio.
7
+ class AttachRecordingOperation < ::Twilio::Rails::ApplicationOperation
8
+ input :recording_id, accepts: Integer, type: :keyword, required: true
9
+
10
+ def execute
11
+ recording = ::Twilio::Rails.config.recording_class.find(recording_id)
12
+
13
+ if !recording.audio.attached?
14
+ if recording.url.blank?
15
+ raise Twilio::Rails::Phone::Error, "[AttachRecordingOperation] Has a blank URL and cannot be fetched recording_id=#{ recording.id }"
16
+ end
17
+
18
+ response = Faraday.get(recording.url)
19
+
20
+ if response.success?
21
+ recording.audio.attach(io: StringIO.new(response.body), filename: "recording.wav", content_type: "audio/wav")
22
+ recording.save!
23
+ else
24
+ raise Twilio::Rails::Phone::Error, "[AttachRecordingOperation] Failed to fetch recording recording_id=#{ recording.id } HTTP#{ response.status } from #{ recording.url }"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ module Phone
5
+ # Base class for all SMS operations. Requires the `phone_call_id` to be passed in.
6
+ class BaseOperation < ::Twilio::Rails::ApplicationOperation
7
+ input :phone_call_id, accepts: Integer, type: :keyword, required: true
8
+
9
+ protected
10
+
11
+ def phone_call
12
+ @phone_call ||= ::Twilio::Rails.config.phone_call_class.find(phone_call_id)
13
+ end
14
+
15
+ def phone_caller
16
+ phone_call.phone_caller
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ module Phone
5
+ class CreateOperation < ::Twilio::Rails::ApplicationOperation
6
+ input :params, accepts: Hash, type: :keyword, required: true
7
+ input :tree, accepts: Twilio::Rails::Phone::Tree, type: :keyword, required: true
8
+
9
+ def execute
10
+ phone_call = ::Twilio::Rails.config.phone_call_class.new(
11
+ sid: params["CallSid"],
12
+ direction: params["direction"].presence || "inbound",
13
+ call_status: params["CallStatus"],
14
+ tree_name: tree.name,
15
+ number: params["Called"].presence || params["To"].presence,
16
+ from_number: params["Caller"].presence || params["From"].presence,
17
+ from_city: params["CallerCity"].presence || params["FromCity"].presence,
18
+ from_province: params["CallerState"].presence || params["FromState"].presence,
19
+ from_country: params["CallerCountry"].presence || params["FromCountry"].presence,
20
+ )
21
+
22
+ phone_caller = Twilio::Rails::FindOrCreatePhoneCallerOperation.call(phone_number: phone_call.from_number)
23
+
24
+ if !phone_caller
25
+ error_message = if !Twilio::Rails::Formatter.valid_north_american_phone_number?(phone_call.from_number)
26
+ "The phone number is invalid."
27
+ else
28
+ "The phone caller could not be persisted or retrieved."
29
+ end
30
+
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
+ end
33
+
34
+ phone_call.phone_caller = phone_caller
35
+ phone_call.save!
36
+
37
+ phone_call
38
+ 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
+ )
44
+ raise
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ module Phone
5
+ class FindOperation < ::Twilio::Rails::ApplicationOperation
6
+ input :params, accepts: Hash, type: :keyword, required: true
7
+
8
+ def execute
9
+ ::Twilio::Rails.config.phone_call_class.find_by!(sid: params["CallSid"])
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ module Phone
5
+ class FinishedCallOperation < ::Twilio::Rails::Phone::BaseOperation
6
+ def execute
7
+ if phone_call.finished?
8
+ Twilio::Rails.config.logger.tagged(self.class) { |l| l.warn("Skipping duplicate finished call job") }
9
+ else
10
+ phone_call.update!(finished: true)
11
+ phone_call.tree.finished_call.call(phone_call) if phone_call.tree.finished_call
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ module Phone
5
+ class ReceiveRecordingOperation < ::Twilio::Rails::Phone::BaseOperation
6
+ input :params, accepts: Hash, type: :keyword, required: true
7
+ input :response_id, accepts: Integer, type: :keyword, required: true
8
+
9
+ def execute
10
+ response = phone_call.responses.find(response_id)
11
+
12
+ 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
+ else
15
+ recording = phone_call.recordings.build(
16
+ recording_sid: params["RecordingSid"],
17
+ url: params["RecordingUrl"],
18
+ duration: params["RecordingDuration"].presence,
19
+ )
20
+ recording.save!
21
+
22
+ response.recording = recording
23
+ response.save!
24
+
25
+ if Twilio::Rails.config.attach_recording?(recording)
26
+ Twilio::Rails::Phone::AttachRecordingJob.set(wait: 5.seconds).perform_later(recording_id: recording.id)
27
+ end
28
+
29
+ recording
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ module Phone
5
+ class StartCallOperation < ::Twilio::Rails::ApplicationOperation
6
+ input :tree, accepts: Twilio::Rails::Phone::Tree, type: :keyword, required: true
7
+ input :to, accepts: String, type: :keyword, required: true
8
+ input :from, accepts: [String, Twilio::Rails::PhoneNumber], type: :keyword, required: false
9
+ input :answering_machine_detection, accepts: [true, false], default: true, type: :keyword, required: false
10
+
11
+ def execute
12
+ from = if self.from.is_a?(Twilio::Rails::PhoneNumber)
13
+ self.from.number
14
+ elsif self.from.present?
15
+ self.from
16
+ else
17
+ Twilio::Rails.config.default_outgoing_phone_number
18
+ end
19
+
20
+ params = {
21
+ "CallSid" => nil,
22
+ "direction" => "outbound",
23
+ "To" => from,
24
+ "From" => to,
25
+ }
26
+
27
+ begin
28
+ sid = Twilio::Rails::Client.start_call(url: tree.outbound_url, to: to, from: from, answering_machine_detection: answering_machine_detection)
29
+ params["CallSid"] = sid
30
+ 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
+ )
36
+ raise
37
+ 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
+ )
43
+ raise
44
+ end
45
+
46
+ # TODO: I think this may be a race condition
47
+ phone_call = Twilio::Rails::Phone::CreateOperation.call(params: params, tree: tree)
48
+ phone_call
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ module Phone
5
+ module Twiml
6
+ class AfterOperation < Twilio::Rails::Phone::Twiml::BaseOperation
7
+ input :tree, accepts: Twilio::Rails::Phone::Tree, type: :keyword, required: true
8
+ input :after, accepts: Twilio::Rails::Phone::Tree::After, type: :keyword, required: true
9
+
10
+ def execute
11
+ unless after.hangup?
12
+ next_response = phone_call.responses.build(prompt_handle: after.prompt)
13
+ next_response.save!
14
+ end
15
+
16
+ twiml_response = Twilio::TwiML::VoiceResponse.new do |twiml|
17
+ add_messages(twiml, message_set: after.messages, response: next_response)
18
+
19
+ if after.hangup?
20
+ twiml.hangup
21
+ else
22
+ twiml.redirect(::Twilio::Rails::Engine.routes.url_helpers.phone_prompt_path(
23
+ format: :xml,
24
+ tree_name: tree.name,
25
+ response_id: next_response.id
26
+ ))
27
+ end
28
+ end
29
+
30
+ Twilio::Rails.config.logger.info("after_twiml: #{twiml_response.to_s}")
31
+ twiml_response.to_s
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ module Phone
5
+ module Twiml
6
+ class BaseOperation < Twilio::Rails::Phone::BaseOperation
7
+ protected
8
+
9
+ # Adds messages to the TwiML response from the passed in message set. This mutates the passed in TwiML object,
10
+ # which isn't ideal.
11
+ #
12
+ # @param twiml [Twilio::TwiML::VoiceResponse] the TwiML response object to add messages to.
13
+ # @param message_set [Twilio::Rails::Phone::Tree::MessageSet, Hash, Proc, Array, String] the message or messages to add to the TwiML response.
14
+ # @param response [Twilio::Rails::Phone::Response] the response passed to the proc when they are called.
15
+ def add_messages(twiml, message_set:, response:)
16
+ message_set = message_set.call(response) if message_set.is_a?(Proc)
17
+ message_set = Twilio::Rails::Phone::Tree::MessageSet.new(message: message_set) unless message_set.is_a?(Twilio::Rails::Phone::Tree::MessageSet)
18
+
19
+ message_set.each do |message|
20
+ message = message.call(response) if message.is_a?(Proc)
21
+ message = Twilio::Rails::Phone::Tree::Message.new(**message) if message.is_a?(Hash)
22
+ next if message.blank?
23
+ message = Twilio::Rails::Phone::Tree::Message.new(say: message, voice: tree.config[:voice]) if message.is_a?(String)
24
+
25
+ raise Twilio::Rails::Phone::InvalidTreeError "unknown message #{ message } is a #{ message.class }" unless message.is_a?(Twilio::Rails::Phone::Tree::Message)
26
+
27
+ # TODO: if we want to make a transcript of sent messages this is where we would record outgoing messages
28
+
29
+ if message.say?
30
+ value = message.value
31
+ 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
34
+ end
35
+ elsif message.play?
36
+ value = message.value
37
+ value = message.value.call(response) if message.value.is_a?(Proc)
38
+ twiml.play(url: value)
39
+ elsif message.pause?
40
+ twiml.pause(length: message.value)
41
+ else
42
+ raise Twilio::Rails::Phone::InvalidTreeError, "unknown message #{ message }"
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ module Phone
5
+ module Twiml
6
+ class ErrorOperation < Twilio::Rails::Phone::Twiml::BaseOperation
7
+ input :tree, accepts: Twilio::Rails::Phone::Tree, type: :keyword, required: true
8
+ input :messages, accepts: Object, type: :keyword, required: false
9
+
10
+ def execute
11
+ twiml = Twilio::TwiML::VoiceResponse.new
12
+ add_messages(twiml, message_set: messages, response: phone_call.responses.build) if messages
13
+ twiml.hangup
14
+
15
+ Twilio::Rails.config.logger.info("error_twiml: #{twiml.to_s}")
16
+ twiml.to_s
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ module Phone
5
+ module Twiml
6
+ class GreetingOperation < Twilio::Rails::Phone::Twiml::BaseOperation
7
+ input :tree, accepts: Twilio::Rails::Phone::Tree, type: :keyword, required: true
8
+
9
+ 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])
12
+ else
13
+ after = tree.greeting
14
+ after = Twilio::Rails::Phone::Tree::After.new(after.proc.call(phone_call.responses.build)) if after.proc
15
+ Twilio::Rails::Phone::Twiml::AfterOperation.call(phone_call_id: phone_call.id, tree: tree, after: after)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ module Phone
5
+ module Twiml
6
+ class PromptOperation < Twilio::Rails::Phone::Twiml::BaseOperation
7
+ input :tree, accepts: Twilio::Rails::Phone::Tree, type: :keyword, required: true
8
+ input :response_id, accepts: Integer, type: :keyword, required: true
9
+
10
+ def execute
11
+ return Twilio::Rails::Phone::Twiml::ErrorOperation.call(phone_call_id: phone_call.id, tree: tree) if phone_call.answering_machine?
12
+
13
+ response = phone_call.responses.find(response_id)
14
+ prompt = tree.prompts[response.prompt_handle]
15
+ raise Twilio::Rails::Phone::InvalidTreeError, "cannot find #{ response.prompt_handle } in #{ tree.name }" unless prompt
16
+
17
+ twiml_response = Twilio::TwiML::VoiceResponse.new do |twiml|
18
+ unless prompt.gather&.interrupt?
19
+ add_messages(twiml, message_set: prompt.messages, response: response)
20
+ end
21
+
22
+ case prompt.gather&.type
23
+ when :digits
24
+ args = {
25
+ action: ::Twilio::Rails::Engine.routes.url_helpers.phone_prompt_response_path(
26
+ format: :xml,
27
+ tree_name: tree.name,
28
+ response_id: response.id
29
+ ),
30
+ input: "dtmf",
31
+ num_digits: prompt.gather.args[:number],
32
+ timeout: prompt.gather.args[:timeout],
33
+ action_on_empty_result: false,
34
+ }
35
+
36
+ args[:finish_on_key] = prompt.gather.args[:finish_on_key] if prompt.gather.args[:finish_on_key]
37
+
38
+ twiml.gather(**args) do |twiml|
39
+ if prompt.gather&.interrupt?
40
+ add_messages(twiml, message_set: prompt.messages, response: response)
41
+ end
42
+ end
43
+ twiml.redirect(::Twilio::Rails::Engine.routes.url_helpers.phone_timeout_path(
44
+ format: :xml,
45
+ tree_name: tree.name,
46
+ response_id: response.id
47
+ ))
48
+ when :voice
49
+ args = {
50
+ max_length: prompt.gather.args[:length],
51
+ play_beep: prompt.gather.args[:beep],
52
+ # trim: "trim-silence",
53
+ timeout: prompt.gather.args[:timeout],
54
+ action: ::Twilio::Rails::Engine.routes.url_helpers.phone_prompt_response_path(
55
+ format: :xml,
56
+ tree_name: tree.name,
57
+ response_id: response.id
58
+ ),
59
+ recording_status_callback: ::Twilio::Rails::Engine.routes.url_helpers.phone_receive_recording_path(
60
+ response_id: response.id
61
+ ),
62
+ }
63
+
64
+ if prompt.gather.args[:transcribe]
65
+ args[:transcribe] = true
66
+ args[:transcribe_callback] = ::Twilio::Rails::Engine.routes.url_helpers.phone_transcribe_path(response_id: response.id)
67
+ end
68
+
69
+ args[:profanity_filter] = true if prompt.gather.args[:profanity_filter]
70
+
71
+ twiml.record(**args)
72
+ when :speech
73
+ args = {
74
+ action: ::Twilio::Rails::Engine.routes.url_helpers.phone_prompt_response_path(
75
+ format: :xml,
76
+ tree_name: tree.name,
77
+ response_id: response.id
78
+ ),
79
+ input: "speech",
80
+ timeout: prompt.gather.args[:timeout],
81
+ action_on_empty_result: true,
82
+ language: prompt.gather.args[:language].presence || "en-US",
83
+ enhanced: !!prompt.gather.args[:enhanced],
84
+ }
85
+
86
+ args[:speech_timeout] = prompt.gather.args[:speech_timeout] if prompt.gather.args[:speech_timeout]
87
+ args[:speech_model] = prompt.gather.args[:speech_model] if prompt.gather.args[:speech_model].present?
88
+ args[:profanity_filter] = true if prompt.gather.args[:profanity_filter]
89
+
90
+ twiml.gather(**args)
91
+ when nil
92
+ twiml.redirect(::Twilio::Rails::Engine.routes.url_helpers.phone_prompt_response_path(
93
+ format: :xml,
94
+ tree_name: tree.name,
95
+ response_id: response.id
96
+ ))
97
+ else
98
+ raise Twilio::Rails::Phone::InvalidTreeError, "unknown gather type #{prompt.gather.type.inspect}"
99
+ end
100
+ end
101
+
102
+ Twilio::Rails.config.logger.info("prompt_twiml: #{twiml_response.to_s}")
103
+ twiml_response.to_s
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ module Phone
5
+ module Twiml
6
+ class PromptResponseOperation < Twilio::Rails::Phone::Twiml::BaseOperation
7
+ input :tree, accepts: Twilio::Rails::Phone::Tree, type: :keyword, required: true
8
+ input :response_id, accepts: Integer, type: :keyword, required: true
9
+ input :params, accepts: Hash, type: :keyword, required: true
10
+
11
+ def execute
12
+ return Twilio::Rails::Phone::Twiml::ErrorOperation.call(phone_call_id: phone_call.id, tree: tree) if phone_call.answering_machine?
13
+
14
+ response = phone_call.responses.find(response_id)
15
+ response = Twilio::Rails::Phone::UpdateResponseOperation.call(params: params, response_id: response.id, phone_call_id: phone_call.id)
16
+
17
+ prompt = tree.prompts[response.prompt_handle]
18
+ raise Twilio::Rails::Phone::InvalidTreeError, "cannot find #{ response.prompt_handle } in #{ tree.name }" unless prompt
19
+
20
+ after = prompt.after
21
+ after = Twilio::Rails::Phone::Tree::After.new(after.proc.call(response)) if after.proc
22
+
23
+ Twilio::Rails::Phone::Twiml::AfterOperation.call(phone_call_id: phone_call.id, tree: tree, after: after)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ module Phone
5
+ module Twiml
6
+ class RequestValidationFailureOperation < ::Twilio::Rails::ApplicationOperation
7
+ def execute
8
+ twiml = Twilio::TwiML::VoiceResponse.new
9
+ twiml.hangup
10
+ twiml.to_s
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end