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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +0 -0
- data/LICENSE +21 -0
- data/README.md +413 -0
- data/Rakefile +8 -0
- data/app/assets/config/twilio_rails_manifest.js +1 -0
- data/app/assets/stylesheets/twilio/rails/application.css +15 -0
- data/app/controllers/twilio/rails/application_controller.rb +6 -0
- data/app/controllers/twilio/rails/phone_controller.rb +112 -0
- data/app/controllers/twilio/rails/sms_controller.rb +64 -0
- data/app/helpers/twilio/rails/application_helper.rb +6 -0
- data/app/jobs/twilio/rails/application_job.rb +6 -0
- data/app/jobs/twilio/rails/phone/attach_recording_job.rb +15 -0
- data/app/jobs/twilio/rails/phone/finished_call_job.rb +15 -0
- data/app/jobs/twilio/rails/phone/unanswered_call_job.rb +15 -0
- data/app/mailers/twilio/rails/application_mailer.rb +8 -0
- data/app/models/twilio/rails/application_record.rb +7 -0
- data/app/operations/twilio/rails/application_operation.rb +21 -0
- data/app/operations/twilio/rails/find_or_create_phone_caller_operation.rb +29 -0
- data/app/operations/twilio/rails/phone/attach_recording_operation.rb +31 -0
- data/app/operations/twilio/rails/phone/base_operation.rb +21 -0
- data/app/operations/twilio/rails/phone/create_operation.rb +49 -0
- data/app/operations/twilio/rails/phone/find_operation.rb +14 -0
- data/app/operations/twilio/rails/phone/finished_call_operation.rb +17 -0
- data/app/operations/twilio/rails/phone/receive_recording_operation.rb +35 -0
- data/app/operations/twilio/rails/phone/start_call_operation.rb +53 -0
- data/app/operations/twilio/rails/phone/twiml/after_operation.rb +37 -0
- data/app/operations/twilio/rails/phone/twiml/base_operation.rb +50 -0
- data/app/operations/twilio/rails/phone/twiml/error_operation.rb +22 -0
- data/app/operations/twilio/rails/phone/twiml/greeting_operation.rb +22 -0
- data/app/operations/twilio/rails/phone/twiml/prompt_operation.rb +109 -0
- data/app/operations/twilio/rails/phone/twiml/prompt_response_operation.rb +29 -0
- data/app/operations/twilio/rails/phone/twiml/request_validation_failure_operation.rb +16 -0
- data/app/operations/twilio/rails/phone/twiml/timeout_operation.rb +48 -0
- data/app/operations/twilio/rails/phone/unanswered_call_operation.rb +22 -0
- data/app/operations/twilio/rails/phone/update_operation.rb +26 -0
- data/app/operations/twilio/rails/phone/update_response_operation.rb +38 -0
- data/app/operations/twilio/rails/sms/base_operation.rb +17 -0
- data/app/operations/twilio/rails/sms/create_operation.rb +23 -0
- data/app/operations/twilio/rails/sms/find_message_operation.rb +15 -0
- data/app/operations/twilio/rails/sms/find_operation.rb +15 -0
- data/app/operations/twilio/rails/sms/send_operation.rb +102 -0
- data/app/operations/twilio/rails/sms/twiml/base_operation.rb +11 -0
- data/app/operations/twilio/rails/sms/twiml/error_operation.rb +15 -0
- data/app/operations/twilio/rails/sms/twiml/message_operation.rb +49 -0
- data/app/operations/twilio/rails/sms/update_message_operation.rb +27 -0
- data/app/views/layouts/twilio/rails/application.html.erb +15 -0
- data/config/routes.rb +16 -0
- data/lib/generators/twilio/rails/install/USAGE +15 -0
- data/lib/generators/twilio/rails/install/install_generator.rb +34 -0
- data/lib/generators/twilio/rails/install/templates/initializer.rb +83 -0
- data/lib/generators/twilio/rails/install/templates/message.rb +4 -0
- data/lib/generators/twilio/rails/install/templates/migration.rb +89 -0
- data/lib/generators/twilio/rails/install/templates/phone_call.rb +4 -0
- data/lib/generators/twilio/rails/install/templates/phone_caller.rb +4 -0
- data/lib/generators/twilio/rails/install/templates/recording.rb +4 -0
- data/lib/generators/twilio/rails/install/templates/response.rb +4 -0
- data/lib/generators/twilio/rails/install/templates/sms_conversation.rb +4 -0
- data/lib/generators/twilio/rails/phone_tree/USAGE +8 -0
- data/lib/generators/twilio/rails/phone_tree/phone_tree_generator.rb +12 -0
- data/lib/generators/twilio/rails/phone_tree/templates/tree.rb.erb +13 -0
- data/lib/generators/twilio/rails/sms_responder/USAGE +8 -0
- data/lib/generators/twilio/rails/sms_responder/sms_responder_generator.rb +12 -0
- data/lib/generators/twilio/rails/sms_responder/templates/responder.rb.erb +10 -0
- data/lib/tasks/rails_tasks.rake +45 -0
- data/lib/twilio/rails/client.rb +75 -0
- data/lib/twilio/rails/concerns/has_direction.rb +25 -0
- data/lib/twilio/rails/concerns/has_phone_number.rb +27 -0
- data/lib/twilio/rails/concerns/has_time_scopes.rb +19 -0
- data/lib/twilio/rails/configuration.rb +380 -0
- data/lib/twilio/rails/engine.rb +11 -0
- data/lib/twilio/rails/formatter.rb +93 -0
- data/lib/twilio/rails/models/message.rb +21 -0
- data/lib/twilio/rails/models/phone_call.rb +132 -0
- data/lib/twilio/rails/models/phone_caller.rb +100 -0
- data/lib/twilio/rails/models/recording.rb +27 -0
- data/lib/twilio/rails/models/response.rb +153 -0
- data/lib/twilio/rails/models/sms_conversation.rb +29 -0
- data/lib/twilio/rails/phone/base_tree.rb +229 -0
- data/lib/twilio/rails/phone/tree.rb +229 -0
- data/lib/twilio/rails/phone/tree_macros.rb +147 -0
- data/lib/twilio/rails/phone.rb +12 -0
- data/lib/twilio/rails/phone_number.rb +29 -0
- data/lib/twilio/rails/railtie.rb +17 -0
- data/lib/twilio/rails/sms/delegated_responder.rb +97 -0
- data/lib/twilio/rails/sms/responder.rb +33 -0
- data/lib/twilio/rails/sms.rb +12 -0
- data/lib/twilio/rails/version.rb +5 -0
- data/lib/twilio/rails.rb +89 -0
- 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
|