twilio-rails 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +20 -14
- data/app/controllers/twilio/rails/phone_controller.rb +6 -2
- data/app/controllers/twilio/rails/sms_controller.rb +6 -5
- data/app/jobs/twilio/rails/phone/attach_recording_job.rb +1 -0
- data/app/jobs/twilio/rails/phone/finished_call_job.rb +1 -0
- data/app/jobs/twilio/rails/phone/unanswered_call_job.rb +1 -0
- data/app/operations/twilio/rails/application_operation.rb +2 -1
- data/app/operations/twilio/rails/find_or_create_phone_caller_operation.rb +2 -1
- data/app/operations/twilio/rails/phone/attach_recording_operation.rb +3 -2
- data/app/operations/twilio/rails/phone/base_operation.rb +1 -0
- data/app/operations/twilio/rails/phone/create_operation.rb +11 -8
- data/app/operations/twilio/rails/phone/find_operation.rb +1 -0
- data/app/operations/twilio/rails/phone/finished_call_operation.rb +2 -1
- data/app/operations/twilio/rails/phone/receive_recording_operation.rb +3 -2
- data/app/operations/twilio/rails/phone/start_call_operation.rb +15 -13
- data/app/operations/twilio/rails/phone/twiml/after_operation.rb +2 -1
- data/app/operations/twilio/rails/phone/twiml/base_operation.rb +11 -5
- data/app/operations/twilio/rails/phone/twiml/error_operation.rb +2 -1
- data/app/operations/twilio/rails/phone/twiml/greeting_operation.rb +3 -2
- data/app/operations/twilio/rails/phone/twiml/invalid_phone_number_operation.rb +25 -0
- data/app/operations/twilio/rails/phone/twiml/prompt_operation.rb +6 -5
- data/app/operations/twilio/rails/phone/twiml/prompt_response_operation.rb +2 -1
- data/app/operations/twilio/rails/phone/twiml/request_validation_failure_operation.rb +1 -0
- data/app/operations/twilio/rails/phone/twiml/timeout_operation.rb +5 -4
- data/app/operations/twilio/rails/phone/unanswered_call_operation.rb +2 -1
- data/app/operations/twilio/rails/phone/update_operation.rb +1 -0
- data/app/operations/twilio/rails/phone/update_response_operation.rb +1 -0
- data/app/operations/twilio/rails/sms/base_operation.rb +1 -0
- data/app/operations/twilio/rails/sms/create_operation.rb +2 -1
- data/app/operations/twilio/rails/sms/find_message_operation.rb +1 -0
- data/app/operations/twilio/rails/sms/find_operation.rb +1 -0
- data/app/operations/twilio/rails/sms/send_operation.rb +12 -12
- data/app/operations/twilio/rails/sms/twiml/base_operation.rb +1 -0
- data/app/operations/twilio/rails/sms/twiml/error_operation.rb +1 -0
- data/app/operations/twilio/rails/sms/twiml/message_operation.rb +4 -3
- data/app/operations/twilio/rails/sms/update_message_operation.rb +1 -0
- data/lib/generators/twilio/rails/install/install_generator.rb +1 -0
- data/lib/generators/twilio/rails/install/templates/initializer.rb +5 -8
- data/lib/generators/twilio/rails/install/templates/message.rb +1 -0
- data/lib/generators/twilio/rails/install/templates/phone_call.rb +1 -0
- data/lib/generators/twilio/rails/install/templates/phone_caller.rb +1 -0
- data/lib/generators/twilio/rails/install/templates/recording.rb +1 -0
- data/lib/generators/twilio/rails/install/templates/response.rb +1 -0
- data/lib/generators/twilio/rails/install/templates/sms_conversation.rb +1 -0
- data/lib/generators/twilio/rails/phone_tree/phone_tree_generator.rb +1 -0
- data/lib/generators/twilio/rails/sms_responder/sms_responder_generator.rb +1 -0
- data/lib/generators/twilio/rails/sms_responder/templates/responder.rb.erb +2 -2
- data/lib/tasks/rails_tasks.rake +6 -5
- data/lib/twilio/rails/client.rb +9 -8
- data/lib/twilio/rails/concerns/has_direction.rb +2 -1
- data/lib/twilio/rails/concerns/has_phone_number.rb +13 -4
- data/lib/twilio/rails/concerns/has_time_scopes.rb +1 -0
- data/lib/twilio/rails/configuration.rb +42 -40
- data/lib/twilio/rails/formatter.rb +35 -29
- data/lib/twilio/rails/models/message.rb +1 -0
- data/lib/twilio/rails/models/phone_call.rb +1 -0
- data/lib/twilio/rails/models/phone_caller.rb +4 -3
- data/lib/twilio/rails/models/recording.rb +1 -0
- data/lib/twilio/rails/models/response.rb +7 -6
- data/lib/twilio/rails/models/sms_conversation.rb +1 -0
- data/lib/twilio/rails/phone/base_tree.rb +8 -7
- data/lib/twilio/rails/phone/tree.rb +8 -7
- data/lib/twilio/rails/phone/tree_macros.rb +9 -9
- data/lib/twilio/rails/phone.rb +3 -2
- data/lib/twilio/rails/phone_number.rb +6 -5
- data/lib/twilio/rails/phone_number_formatter/north_america.rb +52 -0
- data/lib/twilio/rails/phone_number_formatter.rb +20 -0
- data/lib/twilio/rails/railtie.rb +6 -1
- data/lib/twilio/rails/sms/delegated_responder.rb +6 -5
- data/lib/twilio/rails/sms/responder.rb +3 -2
- data/lib/twilio/rails/sms.rb +3 -2
- data/lib/twilio/rails/version.rb +1 -1
- data/lib/twilio/rails.rb +12 -26
- metadata +20 -6
@@ -1,12 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Twilio
|
3
4
|
module Rails
|
4
5
|
module Formatter
|
5
6
|
extend self
|
6
7
|
|
7
|
-
PHONE_NUMBER_REGEX = /\A\+1[0-9]{10}\Z/
|
8
|
-
PHONE_NUMBER_SEGMENTS_REGEX = /\A\+1([0-9]{3})([0-9]{3})([0-9]{4})\Z/
|
9
|
-
|
10
8
|
# Takes in a string or a {Twilio::Rails::PhoneNumber} or something that responds to `to_s` and turns it into a
|
11
9
|
# consistently formatted valid north american 10 digit phone number prefixed with 1 and plus. It uses the format
|
12
10
|
# Twilio expects which is "+15555555555" or returns `nil` if it cannot be coerced.
|
@@ -14,17 +12,13 @@ module Twilio
|
|
14
12
|
# @param string [String, Twilio::Rails::PhoneNumber, nil, Object] the input to turn into a phone number string.
|
15
13
|
# @return [String, nil] the phone number string or nil.
|
16
14
|
def coerce_to_valid_phone_number(string)
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
if string
|
21
|
-
string = string.gsub(/[^0-9]/, "")
|
22
|
-
string = "1#{ string }" unless string.starts_with?("1")
|
23
|
-
string = "+#{ string }"
|
24
|
-
string = nil unless valid_north_american_phone_number?(string)
|
25
|
-
end
|
15
|
+
Twilio::Rails.deprecator.warn(<<~DEPRECATION.strip)
|
16
|
+
Twilio::Rails::Formatter#coerce_to_valid_phone_number(s) is deprecated and will be removed in the next major version.
|
26
17
|
|
27
|
-
|
18
|
+
Set Twilio::Rails.config.phone_number_formatter = Twilio::Rails::PhoneNumberFormatter::NorthAmerica.new
|
19
|
+
and use Twilio::Rails.config.phone_number_formatter.coerce(s) instead.
|
20
|
+
DEPRECATION
|
21
|
+
north_america_formatter.coerce(string)
|
28
22
|
end
|
29
23
|
|
30
24
|
# Takes in a string or a {Twilio::Rails::PhoneNumber} or something that responds to `to_s` and validates it
|
@@ -33,8 +27,13 @@ module Twilio
|
|
33
27
|
# @param phone_number [String, Twilio::Rails::PhoneNumber, nil] the input to validate as a phone number.
|
34
28
|
# @return [true, false]
|
35
29
|
def valid_north_american_phone_number?(phone_number)
|
36
|
-
|
37
|
-
|
30
|
+
Twilio::Rails.deprecator.warn(<<~DEPRECATION.strip)
|
31
|
+
Twilio::Rails::Formatter#valid_north_american_phone_number?(s) is deprecated and will be removed in the next major version.
|
32
|
+
|
33
|
+
Set Twilio::Rails.config.phone_number_formatter = Twilio::Rails::PhoneNumberFormatter::NorthAmerica.new
|
34
|
+
and use Twilio::Rails.config.phone_number_formatter.valid?(s) instead.
|
35
|
+
DEPRECATION
|
36
|
+
north_america_formatter.valid?(phone_number)
|
38
37
|
end
|
39
38
|
|
40
39
|
# Takes in a string or a {Twilio::Rails::PhoneNumber} or something that responds to `to_s` and turns it into
|
@@ -44,11 +43,13 @@ module Twilio
|
|
44
43
|
# @param phone_number [String, Twilio::Rails::PhoneNumber, nil] the input to turn into a phone number string.
|
45
44
|
# @return [String] the phone number string or empty string if invalid.
|
46
45
|
def to_phone_number_url_param(phone_number)
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
46
|
+
Twilio::Rails.deprecator.warn(<<~DEPRECATION.strip)
|
47
|
+
Twilio::Rails::Formatter#to_phone_number_url_param(s) is deprecated and will be removed in the next major version.
|
48
|
+
|
49
|
+
Set Twilio::Rails.config.phone_number_formatter = Twilio::Rails::PhoneNumberFormatter::NorthAmerica.new
|
50
|
+
and use Twilio::Rails.config.phone_number_formatter.to_param(s) instead.
|
51
|
+
DEPRECATION
|
52
|
+
north_america_formatter.to_param(phone_number)
|
52
53
|
end
|
53
54
|
|
54
55
|
# Takes in a string or a {Twilio::Rails::PhoneNumber} or something that responds to `to_s` and turns it into a
|
@@ -58,14 +59,13 @@ module Twilio
|
|
58
59
|
# @param phone_number [String, Twilio::Rails::PhoneNumber, nil] the input to turn into a phone number string.
|
59
60
|
# @return [String, Object] the phone number string or the original object if invalid.
|
60
61
|
def display_phone_number(phone_number)
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
end
|
62
|
+
Twilio::Rails.deprecator.warn(<<~DEPRECATION.strip)
|
63
|
+
Twilio::Rails::Formatter#display_phone_number(s) is deprecated and will be removed in the next major version.
|
64
|
+
|
65
|
+
Set Twilio::Rails.config.phone_number_formatter = Twilio::Rails::PhoneNumberFormatter::NorthAmerica.new
|
66
|
+
and use Twilio::Rails.config.phone_number_formatter.display(s) instead.
|
67
|
+
DEPRECATION
|
68
|
+
north_america_formatter.display(phone_number)
|
69
69
|
end
|
70
70
|
|
71
71
|
# Formats a city, province, and country into a single string, correctly handling blanks, and formatting countries.
|
@@ -85,9 +85,15 @@ module Twilio
|
|
85
85
|
[
|
86
86
|
city.presence&.titleize,
|
87
87
|
province,
|
88
|
-
country_name
|
88
|
+
country_name
|
89
89
|
].reject(&:blank?).join(", ")
|
90
90
|
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def north_america_formatter
|
95
|
+
@north_america_formatter ||= Twilio::Rails::PhoneNumberFormatter::NorthAmerica.new
|
96
|
+
end
|
91
97
|
end
|
92
98
|
end
|
93
99
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Twilio
|
3
4
|
module Rails
|
4
5
|
module Models
|
@@ -21,7 +22,7 @@ module Twilio
|
|
21
22
|
# @param phone_number_string [String, Twilio::Rails::PhoneNumber] The phone number to find the record.
|
22
23
|
# @return [Twilio::Rails::Models::PhoneCaller, nil] The phone caller record or `nil` if not found.
|
23
24
|
def for(phone_number_string)
|
24
|
-
phone_number = Twilio::Rails::
|
25
|
+
phone_number = Twilio::Rails::PhoneNumberFormatter.coerce(phone_number_string)
|
25
26
|
find_by(phone_number: phone_number) if phone_number.present?
|
26
27
|
end
|
27
28
|
end
|
@@ -45,11 +46,11 @@ module Twilio
|
|
45
46
|
|
46
47
|
# @return [Array<Twilio::Rails::Models::SmsConversation>] All SMS conversations for the phone caller.
|
47
48
|
def sms_conversations
|
48
|
-
Twilio::Rails.config.sms_conversation_class.phone_number(
|
49
|
+
Twilio::Rails.config.sms_conversation_class.phone_number(phone_number)
|
49
50
|
end
|
50
51
|
|
51
52
|
# Returns the digits as a `String` as entered through the keypad during a phone call as `gather:`. Returns
|
52
|
-
|
53
|
+
# `nil` if the response is not found, if the response has no digits, or if the response was a timeout. Can
|
53
54
|
# include both `*` and `#` characters if the caller pressed them.
|
54
55
|
#
|
55
56
|
# @param prompt [String, Symbol] The prompt handle to query.
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Twilio
|
3
4
|
module Rails
|
4
5
|
module Models
|
@@ -19,11 +20,11 @@ module Twilio
|
|
19
20
|
delegate :phone_caller, to: :phone_call
|
20
21
|
|
21
22
|
scope :completed, -> { where(timeout: false) }
|
22
|
-
scope :recent_transcriptions, ->(number=5) { completed.order(created_at: :desc).where.not(transcription: nil).limit(number) }
|
23
|
+
scope :recent_transcriptions, ->(number = 5) { completed.order(created_at: :desc).where.not(transcription: nil).limit(number) }
|
23
24
|
scope :final_timeout_check, ->(count:, prompt_handle:) {
|
24
25
|
prompt(prompt_handle).order(created_at: :desc).limit(count)
|
25
26
|
}
|
26
|
-
scope :tree, ->(name) { joins(:phone_call).where(phone_calls: {
|
27
|
+
scope :tree, ->(name) { joins(:phone_call).where(phone_calls: {tree_name: name}) }
|
27
28
|
scope :prompt, ->(prompt_handle) { where(prompt_handle: prompt_handle) }
|
28
29
|
scope :in_order, -> { reorder(created_at: :asc) }
|
29
30
|
scope :transcribed, -> { where(transcribed: true) }
|
@@ -37,9 +38,9 @@ module Twilio
|
|
37
38
|
# @param prompt [String, Symbol, Array] The prompt handle or an array of them.
|
38
39
|
# @return [true, false] true if the response is for the given prompt and tree.
|
39
40
|
def is?(tree:, prompt:)
|
40
|
-
|
41
|
+
Array(tree).map { |t| t.is_a?(Twilio::Rails::Phone::Tree) ? t.name : t.to_s }
|
41
42
|
|
42
|
-
from?(tree: tree) && Array(prompt).map(&:to_s).reject(&:blank?).include?(
|
43
|
+
from?(tree: tree) && Array(prompt).map(&:to_s).reject(&:blank?).include?(prompt_handle)
|
43
44
|
end
|
44
45
|
|
45
46
|
# Checks if the response is for a given tree or trees or tree names.
|
@@ -49,7 +50,7 @@ module Twilio
|
|
49
50
|
def from?(tree:)
|
50
51
|
trees = Array(tree).map { |t| t.is_a?(Twilio::Rails::Phone::Tree) ? t.name : t.to_s }
|
51
52
|
|
52
|
-
trees.include?(
|
53
|
+
trees.include?(phone_call.tree_name)
|
53
54
|
end
|
54
55
|
|
55
56
|
# Returns the digits as an `Integer` entered through the keypad during a phone call as `gather:`. Returns `nil`
|
@@ -59,7 +60,7 @@ module Twilio
|
|
59
60
|
# @return [Integer, nil] The digits as entered by the caller or `nil` if not found or not present.
|
60
61
|
def integer_digits
|
61
62
|
return nil unless digits.present?
|
62
|
-
return nil unless
|
63
|
+
return nil unless /\A[0-9]+\Z/.match?(digits)
|
63
64
|
digits.to_i
|
64
65
|
end
|
65
66
|
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Twilio
|
3
4
|
module Rails
|
4
5
|
module Phone
|
@@ -85,7 +86,7 @@ module Twilio
|
|
85
86
|
# @param message [String, Hash, Array, Proc] The message to play to the caller.
|
86
87
|
# @param prompt [Symbol, Hash, Proc] The name of the next prompt.
|
87
88
|
# @return [nil]
|
88
|
-
def greeting(message: nil
|
89
|
+
def greeting(prompt:, message: nil)
|
89
90
|
tree.greeting = Twilio::Rails::Phone::Tree::After.new(message: message, prompt: prompt)
|
90
91
|
nil
|
91
92
|
end
|
@@ -153,7 +154,7 @@ module Twilio
|
|
153
154
|
# * `Proc`: A proc that will be called after the message and gather have been called. The proc will receive
|
154
155
|
# the current {Twilio::Rails::Models::Response} instance as an argument. The proc must return one of the
|
155
156
|
# above.
|
156
|
-
def prompt(prompt_name, message: nil, gather: nil
|
157
|
+
def prompt(prompt_name, after:, message: nil, gather: nil)
|
157
158
|
tree.prompts[prompt_name] = Twilio::Rails::Phone::Tree::Prompt.new(name: prompt_name, message: message, gather: gather, after: after)
|
158
159
|
nil
|
159
160
|
end
|
@@ -184,10 +185,10 @@ module Twilio
|
|
184
185
|
nil
|
185
186
|
end
|
186
187
|
|
187
|
-
# The `message:` object that played to the caller if a call from an invalid phone number is received.
|
188
|
-
# important case here is a number from outside of North America. This
|
189
|
-
#
|
190
|
-
# message object can contain.
|
188
|
+
# The `message:` object that played to the caller if a call from an invalid phone number is received. This can
|
189
|
+
# be an empty or "Unknown" number, but the important case here is a number from outside of North America. This
|
190
|
+
# is currently a limitation of theframework. The default is `nil` and no action is taken. See the
|
191
|
+
# documentation for {.prompt} for what a message object can contain.
|
191
192
|
#
|
192
193
|
# @param message [String, Hash, Array, Proc] The message to play to the caller.
|
193
194
|
def invalid_phone_number(message)
|
@@ -200,7 +201,7 @@ module Twilio
|
|
200
201
|
#
|
201
202
|
# @return [String] the name of the tree.
|
202
203
|
def tree_name
|
203
|
-
|
204
|
+
name.demodulize.underscore.sub(/_tree\z/, "")
|
204
205
|
end
|
205
206
|
|
206
207
|
# The instance of {Twilio::Rails::Phone::Tree} built from the DSL. Should be treated as read-only. Used
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Twilio
|
3
4
|
module Rails
|
4
5
|
module Phone
|
@@ -24,14 +25,14 @@ module Twilio
|
|
24
25
|
#
|
25
26
|
# @return [String] The outbound URL for the phone tree.
|
26
27
|
def outbound_url
|
27
|
-
"#{
|
28
|
+
"#{::Twilio::Rails.config.host}#{::Twilio::Rails::Engine.routes.url_helpers.phone_outbound_path(tree_name: name, format: :xml)}"
|
28
29
|
end
|
29
30
|
|
30
31
|
# The fully qualified URL for the tree used by Twilio to be configured in the dashboard.
|
31
32
|
#
|
32
33
|
# @return [String] The inbound URL for the phone tree.
|
33
34
|
def inbound_url
|
34
|
-
"#{
|
35
|
+
"#{::Twilio::Rails.config.host}#{::Twilio::Rails::Engine.routes.url_helpers.phone_inbound_path(tree_name: name, format: :xml)}"
|
35
36
|
end
|
36
37
|
|
37
38
|
class Prompt
|
@@ -136,7 +137,7 @@ module Twilio
|
|
136
137
|
end
|
137
138
|
|
138
139
|
class Message
|
139
|
-
attr_reader :
|
140
|
+
attr_reader :voice, :block
|
140
141
|
|
141
142
|
def initialize(say: nil, play: nil, pause: nil, voice: nil, &block)
|
142
143
|
@say = say.presence
|
@@ -149,8 +150,8 @@ module Twilio
|
|
149
150
|
raise Twilio::Rails::Phone::InvalidTreeError, "must only have one of say: play: pause:" if (@say && @play) || (@say && @pause) || (@play && @pause)
|
150
151
|
raise Twilio::Rails::Phone::InvalidTreeError, "say: must be a string or proc" if @say && !(@say.is_a?(String) || @say.is_a?(Proc))
|
151
152
|
raise Twilio::Rails::Phone::InvalidTreeError, "play: must be a string or proc" if @play && !(@play.is_a?(String) || @play.is_a?(Proc))
|
152
|
-
raise Twilio::Rails::Phone::InvalidTreeError, "play: be a valid url but is #{
|
153
|
-
raise Twilio::Rails::Phone::InvalidTreeError, "pause: must be over zero but is #{
|
153
|
+
raise Twilio::Rails::Phone::InvalidTreeError, "play: be a valid url but is #{@play}" if @play&.is_a?(String) && !@play.match(/^https?:\/\/.+/)
|
154
|
+
raise Twilio::Rails::Phone::InvalidTreeError, "pause: must be over zero but is #{@pause}" if @pause && @pause <= 0
|
154
155
|
raise Twilio::Rails::Phone::InvalidTreeError, "block is only valid for say:" if block_given? && (@play || @pause)
|
155
156
|
end
|
156
157
|
|
@@ -183,7 +184,7 @@ module Twilio
|
|
183
184
|
if set.is_a?(Hash)
|
184
185
|
set = set.symbolize_keys
|
185
186
|
if set.key?(:message)
|
186
|
-
raise Twilio::Rails::Phone::InvalidTreeError, "MessageSet should never receive a hash with any key other than :message but received #{
|
187
|
+
raise Twilio::Rails::Phone::InvalidTreeError, "MessageSet should never receive a hash with any key other than :message but received #{set}" if set.keys != [:message]
|
187
188
|
set = set[:message]
|
188
189
|
end
|
189
190
|
end
|
@@ -201,7 +202,7 @@ module Twilio
|
|
201
202
|
elsif message.is_a?(Hash)
|
202
203
|
@messages << Twilio::Rails::Phone::Tree::Message.new(**message.symbolize_keys)
|
203
204
|
else
|
204
|
-
raise Twilio::Rails::Phone::InvalidTreeError, "message value #{
|
205
|
+
raise Twilio::Rails::Phone::InvalidTreeError, "message value #{message} is not valid"
|
205
206
|
end
|
206
207
|
end
|
207
208
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Twilio
|
3
4
|
module Rails
|
4
5
|
module Phone
|
@@ -21,7 +22,7 @@ module Twilio
|
|
21
22
|
timeout: timeout.to_i.presence || 6,
|
22
23
|
number: 1,
|
23
24
|
interrupt: true,
|
24
|
-
finish_on_key: ""
|
25
|
+
finish_on_key: ""
|
25
26
|
}
|
26
27
|
end
|
27
28
|
|
@@ -34,16 +35,16 @@ module Twilio
|
|
34
35
|
# @return [String] the digits joined with commas.
|
35
36
|
def digits(num)
|
36
37
|
return "" if num.blank?
|
37
|
-
num.to_s.
|
38
|
+
num.to_s.chars.join(", ")
|
38
39
|
end
|
39
40
|
|
40
41
|
# Pause for a number of seconds, defaults to 1 second. Useful when putting space between segments of speech.
|
41
42
|
#
|
42
43
|
# @param seconds [Integer] the number of seconds to pause for, defaults to 1 second.
|
43
44
|
# @return [Hash] formatted to pass to `message:`.
|
44
|
-
def pause(seconds=nil)
|
45
|
+
def pause(seconds = nil)
|
45
46
|
{
|
46
|
-
pause:
|
47
|
+
pause: seconds.presence || 1
|
47
48
|
}
|
48
49
|
end
|
49
50
|
|
@@ -66,7 +67,7 @@ module Twilio
|
|
66
67
|
raise Twilio::Rails::Phone::Error, "`numbered_choices` macro got an empty array" if choices.empty?
|
67
68
|
raise Twilio::Rails::Phone::Error, "`numbered_choices` macro cannot be more than 9" if choices.length > 9
|
68
69
|
prefix ||= "For"
|
69
|
-
choices.each_with_index.map { |choice, index| "#{
|
70
|
+
choices.each_with_index.map { |choice, index| "#{prefix} #{choice}, press #{index + 1}." }.join(" ")
|
70
71
|
end
|
71
72
|
|
72
73
|
# Validates if the response object includes a digit that is within the range of the choices array. This pairs
|
@@ -115,7 +116,6 @@ module Twilio
|
|
115
116
|
answers_no.include?((string || "").downcase.strip.gsub(/[.,!?]/, ""))
|
116
117
|
end
|
117
118
|
|
118
|
-
|
119
119
|
# Finds and validates the existence of a file in the `public` folder. Formats that link to include the
|
120
120
|
# configured hose from {Twilio::Rails::Configuration#host}, and returns a fully qualified URL to the file. This
|
121
121
|
# is useful for playing audio files in a `message:` block. If the file is not found
|
@@ -128,9 +128,9 @@ module Twilio
|
|
128
128
|
local_path = ::Rails.public_path.join(filename)
|
129
129
|
|
130
130
|
if File.exist?(local_path)
|
131
|
-
"#{
|
131
|
+
"#{::Twilio::Rails.config.host}/#{filename}"
|
132
132
|
else
|
133
|
-
raise Twilio::Rails::Phone::Error, "Cannot find public file '#{
|
133
|
+
raise Twilio::Rails::Phone::Error, "Cannot find public file '#{filename}' at #{local_path}"
|
134
134
|
end
|
135
135
|
end
|
136
136
|
|
@@ -139,7 +139,7 @@ module Twilio
|
|
139
139
|
# @param filename [String] the filename of the file to play located in the `public` folder.
|
140
140
|
# @return [Hash] formatted to pass to `message:`.
|
141
141
|
def play_public_file(filename)
|
142
|
-
{
|
142
|
+
{play: public_file(filename)}
|
143
143
|
end
|
144
144
|
|
145
145
|
# Expose a {Twilio::TwiML::Say} node to be used in a `message:` block. This can be used to form Speech Synthesis
|
data/lib/twilio/rails/phone.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Twilio
|
3
4
|
module Rails
|
4
5
|
module Phone
|
5
6
|
# Base error class for errors relating to Twilio phone interactions.
|
6
|
-
class Error < ::Twilio::Rails::Error
|
7
|
+
class Error < ::Twilio::Rails::Error; end
|
7
8
|
|
8
9
|
# Error raised when attempting to build a phone tree.
|
9
|
-
class InvalidTreeError < Error
|
10
|
+
class InvalidTreeError < Error; end
|
10
11
|
end
|
11
12
|
end
|
12
13
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Twilio
|
3
4
|
module Rails
|
4
5
|
# A phone number object that includes the country and some optional metadata.
|
@@ -10,8 +11,8 @@ module Twilio
|
|
10
11
|
# @param label [String, nil] an optional label for the phone number, such as its source or purpose.
|
11
12
|
# @param project [String, nil] an optional project identifier for grouping phone numbers.
|
12
13
|
def initialize(number:, country:, label: nil, project: nil)
|
13
|
-
@number = Twilio::Rails::
|
14
|
-
raise Twilio::Rails::Phone::Error, "Invalid phone number '#{
|
14
|
+
@number = Twilio::Rails::PhoneNumberFormatter.coerce(number)
|
15
|
+
raise Twilio::Rails::Phone::Error, "Invalid phone number '#{number}'" unless @number
|
15
16
|
@country = country&.upcase
|
16
17
|
@label = label
|
17
18
|
@project = project.presence&.to_s
|
@@ -19,9 +20,9 @@ module Twilio
|
|
19
20
|
|
20
21
|
# @return [String] a human readable string representation of the phone number and its metadata.
|
21
22
|
def to_s
|
22
|
-
s = "Phone number #{
|
23
|
-
s = "#{
|
24
|
-
s = "#{
|
23
|
+
s = "Phone number #{number} (#{country})"
|
24
|
+
s = "#{s} #{label}" if label.present?
|
25
|
+
s = "#{s} for #{project}" if project.present?
|
25
26
|
s
|
26
27
|
end
|
27
28
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twilio
|
4
|
+
module Rails
|
5
|
+
module PhoneNumberFormatter
|
6
|
+
# Formats phone numbers as North American 10 digit numbers only, and treats any other number as invalid.
|
7
|
+
# This is the legacy behavior from 1.0 which will be the default still in 1.1 as an upgrade path.
|
8
|
+
class NorthAmerica
|
9
|
+
PHONE_NUMBER_REGEX = /\A\+1[0-9]{10}\Z/
|
10
|
+
PHONE_NUMBER_SEGMENTS_REGEX = /\A\+1([0-9]{3})([0-9]{3})([0-9]{4})\Z/
|
11
|
+
|
12
|
+
def coerce(string)
|
13
|
+
string = string.number if string.is_a?(Twilio::Rails::PhoneNumber)
|
14
|
+
string = string.to_s.presence
|
15
|
+
|
16
|
+
if string
|
17
|
+
string = string.gsub(/[^0-9]/, "")
|
18
|
+
string = "1#{string}" unless string.starts_with?("1")
|
19
|
+
string = "+#{string}"
|
20
|
+
string = nil unless valid?(string)
|
21
|
+
end
|
22
|
+
|
23
|
+
string
|
24
|
+
end
|
25
|
+
|
26
|
+
def valid?(string)
|
27
|
+
string = string.number if string.is_a?(Twilio::Rails::PhoneNumber)
|
28
|
+
!!string&.match?(PHONE_NUMBER_REGEX)
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_param(string)
|
32
|
+
string = coerce(string)
|
33
|
+
return "" unless string
|
34
|
+
matches = string.match(PHONE_NUMBER_SEGMENTS_REGEX)
|
35
|
+
raise Twilio::Rails::Error, "[to_param] Phone number marked as valid but could not capture. I made a bad regex: #{string}" unless matches
|
36
|
+
matches.captures.join("-")
|
37
|
+
end
|
38
|
+
|
39
|
+
def display(string)
|
40
|
+
coerced_phone_number = coerce(string)
|
41
|
+
if coerced_phone_number
|
42
|
+
matches = coerced_phone_number.match(PHONE_NUMBER_SEGMENTS_REGEX)
|
43
|
+
raise Twilio::Rails::Error, "[display] Phone number marked as valid but could not capture. I made a bad regex: #{string}" unless matches
|
44
|
+
"(#{matches.captures[0]}) #{matches.captures[1]} #{matches.captures[2]}"
|
45
|
+
else
|
46
|
+
string
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Twilio
|
4
|
+
module Rails
|
5
|
+
# The interface for the phone number validator and formatter that is defined in the `Twilio::Rails.config.phone_number_formatter`
|
6
|
+
# configuration. This delegates the methods to that instance and is used both internally to the gem and by the gem consumer.
|
7
|
+
module PhoneNumberFormatter
|
8
|
+
extend self
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
def_delegators :formatter, :coerce, :valid?, :to_param, :display
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def formatter
|
16
|
+
Twilio::Rails.config.phone_number_formatter
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/twilio/rails/railtie.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Twilio
|
3
4
|
module Rails
|
4
5
|
class Railtie < ::Rails::Railtie
|
5
6
|
config.before_initialize do
|
6
7
|
ActiveSupport::Inflector.inflections(:en) do |inflect|
|
7
|
-
inflect.acronym
|
8
|
+
inflect.acronym "SMS"
|
8
9
|
end
|
9
10
|
end
|
10
11
|
|
@@ -12,6 +13,10 @@ module Twilio
|
|
12
13
|
# TODO: This should work but it does not. I think maybe it happens too late? The same line works if you add it directly to the `application.rb` of the app. It is needed for dev mode.
|
13
14
|
# application.config.hosts << Twilio::Rails.config.host_domain
|
14
15
|
end
|
16
|
+
|
17
|
+
initializer "twilio_rails.deprecator" do |app|
|
18
|
+
app.deprecators[:twilio_rails] = Twilio::Rails.deprecator
|
19
|
+
end
|
15
20
|
end
|
16
21
|
end
|
17
22
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Twilio
|
3
4
|
module Rails
|
4
5
|
module SMS
|
@@ -34,7 +35,7 @@ module Twilio
|
|
34
35
|
#
|
35
36
|
# @return [String] the name of the responder.
|
36
37
|
def responder_name
|
37
|
-
|
38
|
+
name.demodulize.underscore.gsub(/_responder\Z/, "")
|
38
39
|
end
|
39
40
|
end
|
40
41
|
|
@@ -43,22 +44,22 @@ module Twilio
|
|
43
44
|
@sms_conversation = message.sms_conversation
|
44
45
|
end
|
45
46
|
|
46
|
-
# Must be implemented by the subclass otherwise will raise a `
|
47
|
+
# Must be implemented by the subclass otherwise will raise a `NoMethodError`. Returns true if this
|
47
48
|
# responder should handle the given message. If true then the {#reply} method will be called to generate the
|
48
49
|
# body of the response. It has access to the message and the conversation.
|
49
50
|
#
|
50
51
|
# @return [true, false] true if this responder should handle the given message.
|
51
52
|
def handle?
|
52
|
-
raise
|
53
|
+
raise NoMethodError, "#{self.class}#handle? must be implemented."
|
53
54
|
end
|
54
55
|
|
55
|
-
# Must be implemented by the subclass otherwise will raise a `
|
56
|
+
# Must be implemented by the subclass otherwise will raise a `NoMethodError`. Returns the body of the
|
56
57
|
# message to be sent in response. Will only be called if {#handle?} returns true. It has access to the message
|
57
58
|
# and the conversation.
|
58
59
|
#
|
59
60
|
# @return [String, nil] the body of the response to be sent as SMS, or `nil` if no message should be sent.
|
60
61
|
def reply
|
61
|
-
raise
|
62
|
+
raise NoMethodError, "#{self.class}#reply must be implemented."
|
62
63
|
end
|
63
64
|
|
64
65
|
protected
|
@@ -1,5 +1,5 @@
|
|
1
|
-
|
2
1
|
# frozen_string_literal: true
|
2
|
+
|
3
3
|
module Twilio
|
4
4
|
module Rails
|
5
5
|
module SMS
|
@@ -25,7 +25,8 @@ module Twilio
|
|
25
25
|
return responder.reply if responder.handle?
|
26
26
|
end
|
27
27
|
|
28
|
-
raise Twilio::Rails::SMS::InvalidResponderError, "No responder found for message_id=#{
|
28
|
+
raise Twilio::Rails::SMS::InvalidResponderError, "No responder found for SMS. message_id=#{message.id} " \
|
29
|
+
"phone_caller_id=#{sms_conversation.phone_caller&.id} from_number=\"#{sms_conversation.from_number}\" body=\"#{message.body}\""
|
29
30
|
end
|
30
31
|
end
|
31
32
|
end
|
data/lib/twilio/rails/sms.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module Twilio
|
3
4
|
module Rails
|
4
5
|
module SMS
|
5
6
|
# Base error class for errors relating to Twilio phone interactions.
|
6
|
-
class Error < ::Twilio::Rails::Error
|
7
|
+
class Error < ::Twilio::Rails::Error; end
|
7
8
|
|
8
9
|
# Error raised when a responder is unable to handle an SMS message.
|
9
|
-
class InvalidResponderError < Error
|
10
|
+
class InvalidResponderError < Error; end
|
10
11
|
end
|
11
12
|
end
|
12
13
|
end
|
data/lib/twilio/rails/version.rb
CHANGED