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,100 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ module Models
5
+ # The core identity object, uniquely identifying an individual by their phone number. All ingoing or outgoing
6
+ # phone calls or SMS messages are associated to a phone caller. A phone caller is automatically created when any
7
+ # phone call or SMS message is sent or received.
8
+ module PhoneCaller
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ include Twilio::Rails::HasPhoneNumber
13
+
14
+ has_many :phone_calls, -> { order(created_at: :asc) }, class_name: Twilio::Rails.config.phone_call_class_name
15
+ has_many :responses, -> { order(created_at: :asc) }, through: :phone_calls, class_name: Twilio::Rails.config.response_class_name
16
+ end
17
+
18
+ class_methods do
19
+ # Finds a phone caller by phone number string or object, regardless of formatting. Returns `nil` if not found.
20
+ #
21
+ # @param phone_number_string [String, Twilio::Rails::PhoneNumber] The phone number to find the record.
22
+ # @return [Twilio::Rails::Models::PhoneCaller, nil] The phone caller record or `nil` if not found.
23
+ def for(phone_number_string)
24
+ phone_number = Twilio::Rails::Formatter.coerce_to_valid_phone_number(phone_number_string)
25
+ find_by(phone_number: phone_number) if phone_number.present?
26
+ end
27
+ end
28
+
29
+ # @return [String] A well formatted string with the city/state/country of the phone caller, if available.
30
+ def location
31
+ phone_calls.inbound.last&.location
32
+ end
33
+
34
+ # @return [Array<Twilio::Rails::Models::PhoneCall>] All inbound phone calls for the given phone tree or tree name.
35
+ def inbound_calls_for(tree)
36
+ tree = tree.is_a?(Twilio::Rails::Phone::Tree) ? tree.name : tree
37
+ phone_calls.inbound.tree(tree)
38
+ end
39
+
40
+ # @return [Array<Twilio::Rails::Models::PhoneCall>] All outbound phone calls for the given phone tree or tree name.
41
+ def outbound_calls_for(tree)
42
+ tree = tree.is_a?(Twilio::Rails::Phone::Tree) ? tree.name : tree
43
+ phone_calls.outbound.tree(tree)
44
+ end
45
+
46
+ # @return [Array<Twilio::Rails::Models::SmsConversation>] All SMS conversations for the phone caller.
47
+ def sms_conversations
48
+ Twilio::Rails.config.sms_conversation_class.phone_number(self.phone_number)
49
+ end
50
+
51
+ # Returns the digits as a `String` as entered through the keypad during a phone call as `gather:`. Returns
52
+ #`nil` if the response is not found, if the response has no digits, or if the response was a timeout. Can
53
+ # include both `*` and `#` characters if the caller pressed them.
54
+ #
55
+ # @param prompt [String, Symbol] The prompt handle to query.
56
+ # @param tree [String, Symbol, Twilio::Rails::Phone::Tree] The tree or name of the tree to query.
57
+ # @return [String, nil] The digits as entered by the caller or `nil` if not found or not present.
58
+ def response_digits(prompt:, tree:)
59
+ response = responses.tree(tree).where(prompt_handle: prompt, timeout: false).last
60
+ return nil unless response
61
+ response.digits
62
+ end
63
+
64
+ # Returns the digits as an `Integer` entered through the keypad during a phone call as `gather:`. Returns `nil`
65
+ # if the response is not found, if the response has no digits, if the response was a timeout, or if the response
66
+ # contains `*` or `#` characters. Useful for doing branching logic within a phone tree, such as "Press 2 for
67
+ # sales..." etc..
68
+ #
69
+ # @param prompt [String, Symbol] The prompt handle to query.
70
+ # @param tree [String, Symbol, Twilio::Rails::Phone::Tree] The tree or name of the tree to query.
71
+ # @return [Integer, nil] The digits as entered by the caller or `nil` if not found or not present.
72
+ def response_integer_digits(prompt:, tree:)
73
+ response = responses.tree(tree).where(prompt_handle: prompt, timeout: false).last
74
+ return nil unless response
75
+ response.integer_digits
76
+ end
77
+
78
+ # Checks if this phone caller has ever reached a response in a given phone tree. This is useful for building
79
+ # phone trees and determining if a phone caller has reached a certain point in the tree before or not.
80
+ #
81
+ # @param prompt [String, Symbol] The prompt handle to query.
82
+ # @param tree [String, Symbol, Twilio::Rails::Phone::Tree] The tree or name of the tree to query.
83
+ # @return [true, false] If the response has been reached or not.
84
+ def response_reached?(prompt:, tree:)
85
+ response_for(prompt: prompt, tree: tree).present?
86
+ end
87
+
88
+ # Finds the most recent {Twilio::Rails::Models::Response} for the given prompt and tree. This is useful for
89
+ # building phone trees and finding previous responses to prompts. Returns `nil` if no response is found.
90
+ #
91
+ # @param prompt [String, Symbol] The prompt handle to find the response for.
92
+ # @param tree [String, Symbol, Twilio::Rails::Phone::Tree] The tree or name of the tree in which to find the response.
93
+ # @return [Twilio::Rails::Models::Response, nil] The response or `nil` if not found.
94
+ def response_for(prompt:, tree:)
95
+ responses.tree(tree).where(prompt_handle: prompt).last
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ module Models
5
+ # A recording of a fragment of a phone call gathered from Twilio. See `gather: { type: :voice }` in the
6
+ # documentation for {Twilio::Rails::Phone::BaseTree}. Is associated to one {Twilio::Rails::Models::Response}.
7
+ # Attaches the audio file as an ActiveStorage attachment.
8
+ module Recording
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ belongs_to :phone_call, class_name: Twilio::Rails.config.phone_call_class_name
13
+
14
+ has_one :response, class_name: Twilio::Rails.config.response_class_name
15
+ has_one_attached :audio
16
+
17
+ scope :sid, ->(sid) { where(recording_sid: sid) }
18
+ end
19
+
20
+ # @return [Integer, nil] The length of the recording in seconds, or nil if unavailable.
21
+ def length_seconds
22
+ duration.to_i if duration.present?
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ module Models
5
+ # A response object is created for every prompt in a phone call. It is associated to a
6
+ # {Twilio::Rails::Models::PhoneCall} in order, and contains transcriptions, digits, recordings, timestamps, and
7
+ # all other metadata.
8
+ module Response
9
+ extend ActiveSupport::Concern
10
+
11
+ included do
12
+ include Twilio::Rails::HasTimeScopes
13
+
14
+ validates :prompt_handle, presence: true
15
+
16
+ belongs_to :phone_call, class_name: Twilio::Rails.config.phone_call_class_name
17
+ belongs_to :recording, required: false, class_name: Twilio::Rails.config.recording_class_name
18
+
19
+ delegate :phone_caller, to: :phone_call
20
+
21
+ scope :completed, -> { where(timeout: false) }
22
+ scope :recent_transcriptions, ->(number=5) { completed.order(created_at: :desc).where.not(transcription: nil).limit(number) }
23
+ scope :final_timeout_check, ->(count:, prompt_handle:) {
24
+ prompt(prompt_handle).order(created_at: :desc).limit(count)
25
+ }
26
+ scope :tree, ->(name) { joins(:phone_call).where(phone_calls: { tree_name: name }) }
27
+ scope :prompt, ->(prompt_handle) { where(prompt_handle: prompt_handle) }
28
+ scope :in_order, -> { reorder(created_at: :asc) }
29
+ scope :transcribed, -> { where(transcribed: true) }
30
+
31
+ after_commit :recalculate_phone_call_length, on: :create
32
+ end
33
+
34
+ # Checks if the response is for a given prompt or promts, and given tree or trees or tree names.
35
+ #
36
+ # @param tree [Twilio::Rails::Phone::Tree, String, Symbol, Array] The tree or tree name or an array of them.
37
+ # @param prompt [String, Symbol, Array] The prompt handle or an array of them.
38
+ # @return [true, false] true if the response is for the given prompt and tree.
39
+ def is?(tree:, prompt:)
40
+ trees = Array(tree).map { |t| t.is_a?(Twilio::Rails::Phone::Tree) ? t.name : t.to_s }
41
+
42
+ from?(tree: tree) && Array(prompt).map(&:to_s).reject(&:blank?).include?(self.prompt_handle)
43
+ end
44
+
45
+ # Checks if the response is for a given tree or trees or tree names.
46
+ #
47
+ # @param tree [Twilio::Rails::Phone::Tree, String, Symbol, Array] The tree or tree name or an array of them.
48
+ # @return [true, false] true if the response is for the given tree.
49
+ def from?(tree:)
50
+ trees = Array(tree).map { |t| t.is_a?(Twilio::Rails::Phone::Tree) ? t.name : t.to_s }
51
+
52
+ trees.include?(self.phone_call.tree_name)
53
+ end
54
+
55
+ # Returns the digits as an `Integer` entered through the keypad during a phone call as `gather:`. Returns `nil`
56
+ # if the response has no digits, or if the response contains `*` or `#` characters. Useful for doing branching
57
+ # logic within a phone tree, such as "Press 2 for sales..." etc..
58
+ #
59
+ # @return [Integer, nil] The digits as entered by the caller or `nil` if not found or not present.
60
+ def integer_digits
61
+ return nil unless digits.present?
62
+ return nil unless digits =~ /\A[0-9]+\Z/
63
+ digits.to_i
64
+ end
65
+
66
+ # Returns true if the digits entered through the keypad during a phone call as `gather:` contain only `*` or `#`
67
+ #
68
+ # @return [true, false] true if the digits are only `*` or `#`.
69
+ def pound_star?
70
+ !!(digits =~ /\A[#*]+\Z/)
71
+ end
72
+ alias_method :star_pound?, :pound_star?
73
+
74
+ # Checks if any of the passed in patterns match the transcription. Will always return false if the
75
+ # transcription is blank. Patterns can be a `String`, `Symbol`, `Regexp`, or an `Array` of any of those. Will
76
+ # raise `ArgumentError` if no transcriptions are passed in.
77
+ #
78
+ # @param patterns [String, Symbol, Regexp, Array] The patterns to match against.
79
+ # @return [true, false] true if any of the patterns match the transcription.
80
+ def transcription_matches?(*patterns)
81
+ patterns = Array(patterns).flatten
82
+ raise ArgumentError, "transcription must match against at least one pattern" if patterns.blank?
83
+
84
+ return false if transcription.blank?
85
+
86
+ patterns.each do |pattern|
87
+ case pattern
88
+ when Regexp
89
+ return true if pattern.match?(transcription)
90
+ when String, Symbol
91
+ return true if transcription.downcase.include?(pattern.to_s.downcase)
92
+ else
93
+ raise ArgumentError, "can only match a String or Regexp"
94
+ end
95
+ end
96
+
97
+ false
98
+ end
99
+
100
+ # Returns true if the transcription matches any of the configured "yes". Will return false if the transcription
101
+ # is blank. See {Twilio::Rails::Configuration#yes_responses} for the default values. It is possible for
102
+ # {#answer_yes?} and {#answer_no?} to both be false.
103
+ #
104
+ # @return [true, false] true if the transcription matches any of the configured "yes" responses.
105
+ def answer_yes?
106
+ transcription_matches?(Twilio::Rails.config.yes_responses)
107
+ end
108
+
109
+ # Returns true if the transcription matches any of the configured "no". Will return false if the transcription
110
+ # is blank. See {Twilio::Rails::Configuration#yes_responses} for the default values. It is possible for
111
+ # {#answer_yes?} and {#answer_no?} to both be false.
112
+ #
113
+ # @return [true, false] true if the transcription matches any of the configured "no" responses.
114
+ def answer_no?
115
+ transcription_matches?(Twilio::Rails.config.no_responses)
116
+ end
117
+
118
+ # Returns true if this response is the first time the caller has encountered the given prompt for this phone
119
+ # call. The parameter `include_timeouts` defaults to true and flags whether or not to include responses that
120
+ # are timeouts. If the response is unsaved it will always return false.
121
+ #
122
+ # @param include_timeouts [true, false] Whether or not to include timeouts responses.
123
+ # @return [true, false] if this is the first time the caller has encountered this prompt in this phone call.
124
+ def first_for_phone_call?(include_timeouts: true)
125
+ return false unless id
126
+ finder = phone_call.responses.prompt(prompt_handle).order(id: :asc)
127
+ finder = finder.where(timeout: false) if !include_timeouts
128
+ finder.first&.id == id
129
+ end
130
+
131
+ # Returns true if this response is the first time the caller has encountered the given prompt across *any* phone
132
+ # call. The parameter `include_timeouts` defaults to true and flags whether or not to include responses that
133
+ # are timeouts. If the response is unsaved it will always return false.
134
+ #
135
+ # @param include_timeouts [true, false] Whether or not to include timeouts responses.
136
+ # @return [true, false] if this is the first time the caller has encountered this prompt in any phone call.
137
+ def first_for_phone_caller?(include_timeouts: true)
138
+ return false unless id
139
+ finder = phone_caller.responses.prompt(prompt_handle).tree(phone_call.tree_name).order(id: :asc)
140
+ finder = finder.where(timeout: false) if !include_timeouts
141
+ finder.first&.id == id
142
+ end
143
+
144
+ private
145
+
146
+ def recalculate_phone_call_length
147
+ phone_call.recalculate_length
148
+ true
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ module Models
5
+ # A conversation via SMS. Has many {Twilio::Rails::Models::Message}s. Each message has a direction and can be
6
+ # unrolled into a full conversation.
7
+ module SMSConversation
8
+ extend ActiveSupport::Concern
9
+
10
+ included do
11
+ has_many :messages, -> { order(created_at: :asc) }, dependent: :destroy, class_name: Twilio::Rails.config.message_class_name
12
+
13
+ scope :recent, -> { reorder(created_at: :desc).limit(10) }
14
+ scope :phone_number, ->(number) { where(from_number: number) }
15
+ end
16
+
17
+ # @return [Twilio::Rails::Models::PhoneCaller] The phone caller associated with this conversation.
18
+ def phone_caller
19
+ Twilio::Rails.config.phone_caller_class.for(from_number)
20
+ end
21
+
22
+ # @return [String] A well formatted string with the city/state/country of the phone number if available.
23
+ def location
24
+ Twilio::Rails::Formatter.location(city: from_city, country: from_country, province: from_province)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,229 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ module Phone
5
+ # Base class for all phone trees which provides the DSL to define a tree. To define a phone tree start by
6
+ # generating a sublcass.
7
+ #
8
+ # rails generate twilio:rails:phone_tree LeaveFeedback
9
+ #
10
+ # This will create a new class in `app/phone_trees/leave_feedback_tree.rb` which will subclass this class. It must be
11
+ # registered with the framework in the initializer for it to be available. The generator does this.
12
+ #
13
+ # # config/initializers/twilio_rails.rb
14
+ # config.phone_trees.register { LeaveFeedbackTree }
15
+ #
16
+ # Then define the tree using the DSL methods provided by this class. For example:
17
+ #
18
+ # class LeaveFeedbackTree < Twilio::Rails::Phone::BaseTree
19
+ # voice "Polly.Matthew-Neural"
20
+ # final_timeout_message "Sorry, you don't appear to be there. Goodbye."
21
+ # unanswered_call ->(phone_call) { MyMailer.send_followup(phone_call).deliver_later }
22
+ # finished_call ->(phone_call) { MyMailer.send_followup(phone_call).deliver_later }
23
+ # invalid_phone_number "Sorry, we can only accept calls from North America. Goodbye."
24
+ #
25
+ # greeting message: "Hello, and thank you for calling.",
26
+ # prompt: :leave_feedback
27
+ #
28
+ # prompt :leave_feedback,
29
+ # message: "Please leave your feedback after the tone, and press pound when you are finished.",
30
+ # gather: {
31
+ # type: :voice,
32
+ # timeout: 30,
33
+ # transcribe: true,
34
+ # },
35
+ # after: ->(response) {
36
+ # if MyServiceObject.new(response.phone_caller).has_followup_message?
37
+ # { prompt: :followup_message }
38
+ # else
39
+ # {
40
+ # message: "Thank you for your feedback. Have a nice day.",
41
+ # hangup: true,
42
+ # }
43
+ # end
44
+ # }
45
+ #
46
+ # prompt :followup_message,
47
+ # message: ->(response) {
48
+ # [
49
+ # { play: "http://example.com/followup_message_sound.mp3" },
50
+ # { say: MyServiceObject.new(response.phone_caller).followup_message_text },
51
+ # ]
52
+ # },
53
+ # after: {
54
+ # message: "Thank you. Have a nice day.",
55
+ # hangup: true,
56
+ # }
57
+ # end
58
+ class BaseTree
59
+ class << self
60
+ # Accepts a string with the voice parameter to be used throughout the phone tree, unless overridden for a
61
+ # given message. See the Twilio documentation for a list of available voices. The voices are dependent on
62
+ # locale, and can also accept Amazon Polly voices. The default is "male".
63
+ # https://www.twilio.com/docs/voice/twiml/say/text-speech
64
+ #
65
+ # @param voice_name [String] the name of the voice to use.
66
+ # @return [nil]
67
+ def voice(voice_name)
68
+ tree.config[:voice] = voice_name
69
+ nil
70
+ end
71
+
72
+ # The `message:` object that is played to the caller if the phone tree is expecting input but none is
73
+ # received. The default number of attempts before this is called is 3, configured in `final_timeout_attempts`.
74
+ # The default value is a simple "Goodbye."
75
+ def final_timeout_message(message)
76
+ tree.config[:final_timeout_message] = message
77
+ nil
78
+ end
79
+
80
+ # The entrypoint and first call for any incoming or outgoing phone call. It should only be called once as
81
+ # there is only one greeting. Subsequent calls will overwrite the earlier ones. It accepts an optional
82
+ # `message:` object which will be played to the caller. See the documentation for {.prompt} for what a message
83
+ # can contain. It then accepts a required `prompt:` which is the next prompt in the flow of the call.
84
+ #
85
+ # @param message [String, Hash, Array, Proc] The message to play to the caller.
86
+ # @param prompt [Symbol, Hash, Proc] The name of the next prompt.
87
+ # @return [nil]
88
+ def greeting(message: nil, prompt:)
89
+ tree.greeting = Twilio::Rails::Phone::Tree::After.new(message: message, prompt: prompt)
90
+ nil
91
+ end
92
+
93
+ # Defines a prompt in the phone tree. It accepts a required `name:` which must be unique within the tree.
94
+ #
95
+ # It accepts an optional `message:` object which will be played to the caller. A message must be one of:
96
+ # * `nil`: No message will be played.
97
+ # * `String`: A string that will be read to the caller using text-to-speech. This is the equivalent of
98
+ # `{ say: "a string" }`.
99
+ # * `Hash`: A hash that contain only the following keys:
100
+ # * `{ say: "hello" }`: A string that will be read to the caller using text-to-speech. Optionally also
101
+ # accepts a `voice:` key which will override the default voice for this message.
102
+ # * `{ play: "https://example.com/sound.mp3" }`: A URL to a "wav" or "mp3" file that will be played to the
103
+ # caller via Twilio.
104
+ # * `{ pause: 1 }`: Pause in silence for the given number of seconds.
105
+ # * `Array`: An array that contains any number of the above.
106
+ # * `Proc`: A proc that will be called when the prompt is reached. The prompt will receive the previous
107
+ # {Twilio::Rails::Models::Response} instance as an argument. The proc must return one of the above.
108
+ #
109
+ # It accepts an optional `gather:` object which, if present, will be used to gather input from the caller.
110
+ # After the optional message completes, the gather will collect the input. The gather object must be a hash
111
+ # with one of the following types:
112
+ # * `{ type: :digits }`: Collects one or more integer digits from the caller's keypad. Those digits will be
113
+ # stored in the `digits` field of the {Twilio::Rails::Models::Response} instance. Digits accepts the
114
+ # following configuration keys:
115
+ # * `:timeout`: The number of seconds to wait for input before timing out and falling through to the
116
+ # `after:`. The default is 5.
117
+ # * `:number`: The number of digits to collect. The default is 1.
118
+ # * `:interrupt`: Weather pressing a key will interrupt the message, or if the gather will not start
119
+ # until the message is complete. The default is `false`.
120
+ # * `{ type: :voice }`: Records and collects the phone caller's voice as audio. The framework handles
121
+ # updating the `url` and fetching the audio file as a {Twilio::Rails::Models::Recording} attached to the
122
+ # response instance. However, this all happens asynchronously with no guarantee of time or success. Voice
123
+ # accepts the following configuration keys:
124
+ # * `:length`: The number of seconds to record. The default is 10.
125
+ # * `:beep`: A boolean if the gather is preceeded by a beep. The default is `true`.
126
+ # * `:transcribe`: A boolean if Twilio should attempt to transcribe the audio and send it back as text. The
127
+ # framework handles this all asynchronously and will update the `transcription` field. Default is `false`.
128
+ # * `:profanity_filter`: Replaces any profanity in the transcription with ***. Default is `false`.
129
+ # * `{ type: :speech }`: Collects speech from the caller as text using a specialzed model designed to better
130
+ # identify utterances of digits, commands, conversations, etc.. This does not collect audio files, and is
131
+ # more expensive, but returns the `response.transcription` in realtime which can be immediately used in
132
+ # the call flow. Speech accepts the following configuration keys:
133
+ # * `:language`: The language of the caller. The default is "en-US".
134
+ # * `:speech_model`: The model to use for the speech recognition. Accepts "default", "numbers_and_commands",
135
+ # "phone_call", "experimental_conversations", and "experimental_utterances". The default is "default".
136
+ # See the Twilio documentation for details. https://www.twilio.com/docs/voice/twiml/gather#speechmodel
137
+ # * `:enhanced`: A boolean if the enhanced model should be used. Results are better but the cost is higher.
138
+ # The default is `false`.
139
+ # * `:timeout`: The number of seconds to wait for input before timing out and falling through to the
140
+ # `after:`. The default is 5.
141
+ # * `:speech_timeout`: Accepts an interger or "auto". If both this and `timeout` is set, Twilio will use
142
+ # `timeout` for digits and this value for voice or speech.
143
+ # * `:profanity_filter`: Replaces any profanity in the transcription with ***. Default is `false`.
144
+ #
145
+ # It accepts an required `after:` object which, will be used to determine the next prompt in the call flow.
146
+ # The after object must be one of:
147
+ # * `Symbol`: The name of the next prompt in the flow.
148
+ # * `Hash`: A hash that contains:
149
+ # * `:message`: An optional message to play before the next prompt. See the above documentation for what
150
+ # a message can contain.
151
+ # * `:prompt`: The name of the next prompt in the flow.
152
+ # * `:hangup`: A boolean if the call should be hung up after the message. Only one of prompt and hangup can
153
+ # be present.
154
+ # * `Proc`: A proc that will be called after the message and gather have been called. The proc will receive
155
+ # the current {Twilio::Rails::Models::Response} instance as an argument. The proc must return one of the
156
+ # above.
157
+ def prompt(prompt_name, message: nil, gather: nil, after:)
158
+ tree.prompts[prompt_name] = Twilio::Rails::Phone::Tree::Prompt.new(name: prompt_name, message: message, gather: gather, after: after)
159
+ nil
160
+ end
161
+
162
+ # Accepts a proc which will be called when a call goes unanswered, or is answered by an answering machine.
163
+ # The proc will be called asynchronously in a job. The proc will be passed the
164
+ # {Twilio::Rails::Models::PhoneCall} instance for the call. It is called after the call has been completed
165
+ # so cannot control the flow of the call. It is intended to be used as a hook to handle application logic for
166
+ # unanswered calls. The default is `nil` and no action is taken.
167
+ #
168
+ # @param proc [Proc] the proc to call when a call goes unanswered, must accept a phone call instance.
169
+ # @return [nil]
170
+ def unanswered_call(proc)
171
+ tree.unanswered_call = proc
172
+ nil
173
+ end
174
+
175
+ # Accepts a proc which will be called when a call is completed, unanswered, or any state not in progress.
176
+ # The proc will be called asynchronously in a job. The proc will be passed the
177
+ # {Twilio::Rails::Models::PhoneCall} instance for the call. It is called after the call has been completed
178
+ # so cannot control the flow of the call. It is intended to be used as a hook to handle application logic for
179
+ # when a call finishes and is no longer in progress. The default is `nil` and no action is taken.
180
+ #
181
+ # @param proc [Proc] the proc to call when a call is finished, must accept a phone call instance.
182
+ # @return [nil]
183
+ def finished_call(proc)
184
+ tree.finished_call = proc
185
+ nil
186
+ end
187
+
188
+ # The `message:` object that played to the caller if a call from an invalid phone number is received. The
189
+ # important case here is a number from outside of North America. This is currently a limitation of the
190
+ # framework. The default is `nil` and no action is taken. See the documentation for {.prompt} for what a
191
+ # message object can contain.
192
+ #
193
+ # @param message [String, Hash, Array, Proc] The message to play to the caller.
194
+ def invalid_phone_number(message)
195
+ tree.config[:invalid_phone_number] = message
196
+ nil
197
+ end
198
+
199
+ # The string name of the tree used to look it up and identify it in the registry and used in the routes. It
200
+ # must be unique and use URL safe characters. It defaults to the class name but can be overridden here.
201
+ #
202
+ # @return [String] the name of the tree.
203
+ def tree_name
204
+ self.name.demodulize.underscore.sub(/_tree\z/, "")
205
+ end
206
+
207
+ # The instance of {Twilio::Rails::Phone::Tree} built from the DSL. Should be treated as read-only. Used
208
+ # mostly internally by the framework. It is named according to {.tree_name}.
209
+ #
210
+ # @return [Twilio::Rails::Phone::Tree] the tree instance.
211
+ def tree
212
+ @tree ||= Twilio::Rails::Phone::Tree.new(tree_name)
213
+ end
214
+
215
+ # A module of convenience macros used in prompts to prevent repetition or wordy tasks. See
216
+ # {Twilio::Rails::Phone::TreeMacros} for the available methods. The macros do not have access to any instance
217
+ # information and must be passed any context they require.
218
+ #
219
+ # Additional macros can be added through the application config. See {Twilio::Rails::Configuration#include_phone_macros}.
220
+ #
221
+ # @return [Twilio::Rails::Phone::TreeMacros] the module of macros.
222
+ def macros
223
+ Twilio::Rails::Phone::TreeMacros
224
+ end
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end