twilio-rails 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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