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,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module Phone
|
5
|
+
# Performs the {Twilio::Rails::Phone::AttachRecordingOperation}.
|
6
|
+
class AttachRecordingJob < ::Twilio::Rails::ApplicationJob
|
7
|
+
queue_as :default
|
8
|
+
|
9
|
+
def perform(recording_id:)
|
10
|
+
Twilio::Rails::Phone::AttachRecordingOperation.call(recording_id: recording_id)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module Phone
|
5
|
+
# Performs the {Twilio::Rails::Phone::FinishedCallOperation}.
|
6
|
+
class FinishedCallJob < ::Twilio::Rails::ApplicationJob
|
7
|
+
queue_as :default
|
8
|
+
|
9
|
+
def perform(phone_call_id:)
|
10
|
+
Twilio::Rails::Phone::FinishedCallOperation.call(phone_call_id: phone_call_id)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module Phone
|
5
|
+
# Performs the {Twilio::Rails::Phone::UnansweredCallOperation}.
|
6
|
+
class UnansweredCallJob < ::Twilio::Rails::ApplicationJob
|
7
|
+
queue_as :default
|
8
|
+
|
9
|
+
def perform(phone_call_id:)
|
10
|
+
Twilio::Rails::Phone::UnansweredCallOperation.call(phone_call_id: phone_call_id)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
class ApplicationOperation < ActiveOperation::Base
|
5
|
+
# Annotates the log output for every operation with the execution time.
|
6
|
+
around do |instance, executable|
|
7
|
+
@operation_runtime_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
8
|
+
executable.call
|
9
|
+
@operation_runtime_stop = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
10
|
+
|
11
|
+
::Twilio::Rails.config.logger.tagged(self.class) { |l| l.info("execution time #{ instance.operation_runtime_seconds } seconds")}
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def operation_runtime_seconds
|
17
|
+
(@operation_runtime_stop || Process.clock_gettime(Process::CLOCK_MONOTONIC)) - @operation_runtime_start
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
# Finds and returns a {Twilio::Rails::Models::PhoneCaller} by phone number, or creates a new one if it does not
|
5
|
+
# exist. The operation will `halt` if the phone number is not valid or blank.
|
6
|
+
#
|
7
|
+
# *Note:* Operations should be called with `call(params)` and not by calling `new(params).execute` directly.
|
8
|
+
class FindOrCreatePhoneCallerOperation < ::Twilio::Rails::ApplicationOperation
|
9
|
+
input :phone_number, accepts: String, type: :keyword, required: false
|
10
|
+
|
11
|
+
# @param phone_number [String] The phone number to find or create the phone caller.
|
12
|
+
# @return [Twilio::Rails::Models::PhoneCaller] The found or newly created phone caller.
|
13
|
+
def execute
|
14
|
+
halt nil unless valid_phone_number
|
15
|
+
|
16
|
+
phone_caller = ::Twilio::Rails.config.phone_caller_class.find_or_initialize_by(phone_number: valid_phone_number)
|
17
|
+
phone_caller.save! if phone_caller.new_record?
|
18
|
+
|
19
|
+
phone_caller
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def valid_phone_number
|
25
|
+
Twilio::Rails::Formatter.coerce_to_valid_phone_number(phone_number)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module Phone
|
5
|
+
# Called by {Twilio::Rails::Phone::ReceiveRecordingOperation} to download and store the audio file from the URL
|
6
|
+
# provided by Twilio.
|
7
|
+
class AttachRecordingOperation < ::Twilio::Rails::ApplicationOperation
|
8
|
+
input :recording_id, accepts: Integer, type: :keyword, required: true
|
9
|
+
|
10
|
+
def execute
|
11
|
+
recording = ::Twilio::Rails.config.recording_class.find(recording_id)
|
12
|
+
|
13
|
+
if !recording.audio.attached?
|
14
|
+
if recording.url.blank?
|
15
|
+
raise Twilio::Rails::Phone::Error, "[AttachRecordingOperation] Has a blank URL and cannot be fetched recording_id=#{ recording.id }"
|
16
|
+
end
|
17
|
+
|
18
|
+
response = Faraday.get(recording.url)
|
19
|
+
|
20
|
+
if response.success?
|
21
|
+
recording.audio.attach(io: StringIO.new(response.body), filename: "recording.wav", content_type: "audio/wav")
|
22
|
+
recording.save!
|
23
|
+
else
|
24
|
+
raise Twilio::Rails::Phone::Error, "[AttachRecordingOperation] Failed to fetch recording recording_id=#{ recording.id } HTTP#{ response.status } from #{ recording.url }"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module Phone
|
5
|
+
# Base class for all SMS operations. Requires the `phone_call_id` to be passed in.
|
6
|
+
class BaseOperation < ::Twilio::Rails::ApplicationOperation
|
7
|
+
input :phone_call_id, accepts: Integer, type: :keyword, required: true
|
8
|
+
|
9
|
+
protected
|
10
|
+
|
11
|
+
def phone_call
|
12
|
+
@phone_call ||= ::Twilio::Rails.config.phone_call_class.find(phone_call_id)
|
13
|
+
end
|
14
|
+
|
15
|
+
def phone_caller
|
16
|
+
phone_call.phone_caller
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module Phone
|
5
|
+
class CreateOperation < ::Twilio::Rails::ApplicationOperation
|
6
|
+
input :params, accepts: Hash, type: :keyword, required: true
|
7
|
+
input :tree, accepts: Twilio::Rails::Phone::Tree, type: :keyword, required: true
|
8
|
+
|
9
|
+
def execute
|
10
|
+
phone_call = ::Twilio::Rails.config.phone_call_class.new(
|
11
|
+
sid: params["CallSid"],
|
12
|
+
direction: params["direction"].presence || "inbound",
|
13
|
+
call_status: params["CallStatus"],
|
14
|
+
tree_name: tree.name,
|
15
|
+
number: params["Called"].presence || params["To"].presence,
|
16
|
+
from_number: params["Caller"].presence || params["From"].presence,
|
17
|
+
from_city: params["CallerCity"].presence || params["FromCity"].presence,
|
18
|
+
from_province: params["CallerState"].presence || params["FromState"].presence,
|
19
|
+
from_country: params["CallerCountry"].presence || params["FromCountry"].presence,
|
20
|
+
)
|
21
|
+
|
22
|
+
phone_caller = Twilio::Rails::FindOrCreatePhoneCallerOperation.call(phone_number: phone_call.from_number)
|
23
|
+
|
24
|
+
if !phone_caller
|
25
|
+
error_message = if !Twilio::Rails::Formatter.valid_north_american_phone_number?(phone_call.from_number)
|
26
|
+
"The phone number is invalid."
|
27
|
+
else
|
28
|
+
"The phone caller could not be persisted or retrieved."
|
29
|
+
end
|
30
|
+
|
31
|
+
raise Twilio::Rails::Phone::Error, "Failed to handle incoming Twilio phone call. #{ error_message } phone_number=#{ phone_call.from_number } call_sid=#{ params["CallSid"] }"
|
32
|
+
end
|
33
|
+
|
34
|
+
phone_call.phone_caller = phone_caller
|
35
|
+
phone_call.save!
|
36
|
+
|
37
|
+
phone_call
|
38
|
+
rescue => e
|
39
|
+
Twilio::Rails.notify_exception(e,
|
40
|
+
message: "Failed to handle incoming Twilio phone call.",
|
41
|
+
context: { params: params, tree: tree },
|
42
|
+
exception_binding: binding
|
43
|
+
)
|
44
|
+
raise
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module Phone
|
5
|
+
class FindOperation < ::Twilio::Rails::ApplicationOperation
|
6
|
+
input :params, accepts: Hash, type: :keyword, required: true
|
7
|
+
|
8
|
+
def execute
|
9
|
+
::Twilio::Rails.config.phone_call_class.find_by!(sid: params["CallSid"])
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module Phone
|
5
|
+
class FinishedCallOperation < ::Twilio::Rails::Phone::BaseOperation
|
6
|
+
def execute
|
7
|
+
if phone_call.finished?
|
8
|
+
Twilio::Rails.config.logger.tagged(self.class) { |l| l.warn("Skipping duplicate finished call job") }
|
9
|
+
else
|
10
|
+
phone_call.update!(finished: true)
|
11
|
+
phone_call.tree.finished_call.call(phone_call) if phone_call.tree.finished_call
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module Phone
|
5
|
+
class ReceiveRecordingOperation < ::Twilio::Rails::Phone::BaseOperation
|
6
|
+
input :params, accepts: Hash, type: :keyword, required: true
|
7
|
+
input :response_id, accepts: Integer, type: :keyword, required: true
|
8
|
+
|
9
|
+
def execute
|
10
|
+
response = phone_call.responses.find(response_id)
|
11
|
+
|
12
|
+
if phone_call.recordings.sid(params["RecordingSid"]).any?
|
13
|
+
Twilio::Rails.config.logger.tagged(self.class) { |l| l.warn("duplicate recording for response_id=#{ response.id } recording_sid=#{ params["RecordingSid"] }") }
|
14
|
+
else
|
15
|
+
recording = phone_call.recordings.build(
|
16
|
+
recording_sid: params["RecordingSid"],
|
17
|
+
url: params["RecordingUrl"],
|
18
|
+
duration: params["RecordingDuration"].presence,
|
19
|
+
)
|
20
|
+
recording.save!
|
21
|
+
|
22
|
+
response.recording = recording
|
23
|
+
response.save!
|
24
|
+
|
25
|
+
if Twilio::Rails.config.attach_recording?(recording)
|
26
|
+
Twilio::Rails::Phone::AttachRecordingJob.set(wait: 5.seconds).perform_later(recording_id: recording.id)
|
27
|
+
end
|
28
|
+
|
29
|
+
recording
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module Phone
|
5
|
+
class StartCallOperation < ::Twilio::Rails::ApplicationOperation
|
6
|
+
input :tree, accepts: Twilio::Rails::Phone::Tree, type: :keyword, required: true
|
7
|
+
input :to, accepts: String, type: :keyword, required: true
|
8
|
+
input :from, accepts: [String, Twilio::Rails::PhoneNumber], type: :keyword, required: false
|
9
|
+
input :answering_machine_detection, accepts: [true, false], default: true, type: :keyword, required: false
|
10
|
+
|
11
|
+
def execute
|
12
|
+
from = if self.from.is_a?(Twilio::Rails::PhoneNumber)
|
13
|
+
self.from.number
|
14
|
+
elsif self.from.present?
|
15
|
+
self.from
|
16
|
+
else
|
17
|
+
Twilio::Rails.config.default_outgoing_phone_number
|
18
|
+
end
|
19
|
+
|
20
|
+
params = {
|
21
|
+
"CallSid" => nil,
|
22
|
+
"direction" => "outbound",
|
23
|
+
"To" => from,
|
24
|
+
"From" => to,
|
25
|
+
}
|
26
|
+
|
27
|
+
begin
|
28
|
+
sid = Twilio::Rails::Client.start_call(url: tree.outbound_url, to: to, from: from, answering_machine_detection: answering_machine_detection)
|
29
|
+
params["CallSid"] = sid
|
30
|
+
rescue Twilio::REST::TwilioError => e
|
31
|
+
Twilio::Rails.notify_exception(e,
|
32
|
+
message: "Failed to start Twilio phone call. Got REST error response.",
|
33
|
+
context: { params: params },
|
34
|
+
exception_binding: binding
|
35
|
+
)
|
36
|
+
raise
|
37
|
+
rescue => e
|
38
|
+
Twilio::Rails.notify_exception(e,
|
39
|
+
message: "Failed to start Twilio phone call. Got unknown error.",
|
40
|
+
context: { params: params },
|
41
|
+
exception_binding: binding
|
42
|
+
)
|
43
|
+
raise
|
44
|
+
end
|
45
|
+
|
46
|
+
# TODO: I think this may be a race condition
|
47
|
+
phone_call = Twilio::Rails::Phone::CreateOperation.call(params: params, tree: tree)
|
48
|
+
phone_call
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module Phone
|
5
|
+
module Twiml
|
6
|
+
class AfterOperation < Twilio::Rails::Phone::Twiml::BaseOperation
|
7
|
+
input :tree, accepts: Twilio::Rails::Phone::Tree, type: :keyword, required: true
|
8
|
+
input :after, accepts: Twilio::Rails::Phone::Tree::After, type: :keyword, required: true
|
9
|
+
|
10
|
+
def execute
|
11
|
+
unless after.hangup?
|
12
|
+
next_response = phone_call.responses.build(prompt_handle: after.prompt)
|
13
|
+
next_response.save!
|
14
|
+
end
|
15
|
+
|
16
|
+
twiml_response = Twilio::TwiML::VoiceResponse.new do |twiml|
|
17
|
+
add_messages(twiml, message_set: after.messages, response: next_response)
|
18
|
+
|
19
|
+
if after.hangup?
|
20
|
+
twiml.hangup
|
21
|
+
else
|
22
|
+
twiml.redirect(::Twilio::Rails::Engine.routes.url_helpers.phone_prompt_path(
|
23
|
+
format: :xml,
|
24
|
+
tree_name: tree.name,
|
25
|
+
response_id: next_response.id
|
26
|
+
))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
Twilio::Rails.config.logger.info("after_twiml: #{twiml_response.to_s}")
|
31
|
+
twiml_response.to_s
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module Phone
|
5
|
+
module Twiml
|
6
|
+
class BaseOperation < Twilio::Rails::Phone::BaseOperation
|
7
|
+
protected
|
8
|
+
|
9
|
+
# Adds messages to the TwiML response from the passed in message set. This mutates the passed in TwiML object,
|
10
|
+
# which isn't ideal.
|
11
|
+
#
|
12
|
+
# @param twiml [Twilio::TwiML::VoiceResponse] the TwiML response object to add messages to.
|
13
|
+
# @param message_set [Twilio::Rails::Phone::Tree::MessageSet, Hash, Proc, Array, String] the message or messages to add to the TwiML response.
|
14
|
+
# @param response [Twilio::Rails::Phone::Response] the response passed to the proc when they are called.
|
15
|
+
def add_messages(twiml, message_set:, response:)
|
16
|
+
message_set = message_set.call(response) if message_set.is_a?(Proc)
|
17
|
+
message_set = Twilio::Rails::Phone::Tree::MessageSet.new(message: message_set) unless message_set.is_a?(Twilio::Rails::Phone::Tree::MessageSet)
|
18
|
+
|
19
|
+
message_set.each do |message|
|
20
|
+
message = message.call(response) if message.is_a?(Proc)
|
21
|
+
message = Twilio::Rails::Phone::Tree::Message.new(**message) if message.is_a?(Hash)
|
22
|
+
next if message.blank?
|
23
|
+
message = Twilio::Rails::Phone::Tree::Message.new(say: message, voice: tree.config[:voice]) if message.is_a?(String)
|
24
|
+
|
25
|
+
raise Twilio::Rails::Phone::InvalidTreeError "unknown message #{ message } is a #{ message.class }" unless message.is_a?(Twilio::Rails::Phone::Tree::Message)
|
26
|
+
|
27
|
+
# TODO: if we want to make a transcript of sent messages this is where we would record outgoing messages
|
28
|
+
|
29
|
+
if message.say?
|
30
|
+
value = message.value
|
31
|
+
value = message.value.call(response) if message.value.is_a?(Proc)
|
32
|
+
twiml.say(voice: message.voice || tree.config[:voice], message: value) do |say|
|
33
|
+
message.block.call(say) if message.block
|
34
|
+
end
|
35
|
+
elsif message.play?
|
36
|
+
value = message.value
|
37
|
+
value = message.value.call(response) if message.value.is_a?(Proc)
|
38
|
+
twiml.play(url: value)
|
39
|
+
elsif message.pause?
|
40
|
+
twiml.pause(length: message.value)
|
41
|
+
else
|
42
|
+
raise Twilio::Rails::Phone::InvalidTreeError, "unknown message #{ message }"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module Phone
|
5
|
+
module Twiml
|
6
|
+
class ErrorOperation < Twilio::Rails::Phone::Twiml::BaseOperation
|
7
|
+
input :tree, accepts: Twilio::Rails::Phone::Tree, type: :keyword, required: true
|
8
|
+
input :messages, accepts: Object, type: :keyword, required: false
|
9
|
+
|
10
|
+
def execute
|
11
|
+
twiml = Twilio::TwiML::VoiceResponse.new
|
12
|
+
add_messages(twiml, message_set: messages, response: phone_call.responses.build) if messages
|
13
|
+
twiml.hangup
|
14
|
+
|
15
|
+
Twilio::Rails.config.logger.info("error_twiml: #{twiml.to_s}")
|
16
|
+
twiml.to_s
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module Phone
|
5
|
+
module Twiml
|
6
|
+
class GreetingOperation < Twilio::Rails::Phone::Twiml::BaseOperation
|
7
|
+
input :tree, accepts: Twilio::Rails::Phone::Tree, type: :keyword, required: true
|
8
|
+
|
9
|
+
def execute
|
10
|
+
if !phone_caller.valid_north_american_phone_number? && tree.config[:invalid_phone_number]
|
11
|
+
Twilio::Rails::Phone::Twiml::ErrorOperation.call(phone_call_id: phone_call.id, tree: tree, messages: tree.config[:invalid_phone_number])
|
12
|
+
else
|
13
|
+
after = tree.greeting
|
14
|
+
after = Twilio::Rails::Phone::Tree::After.new(after.proc.call(phone_call.responses.build)) if after.proc
|
15
|
+
Twilio::Rails::Phone::Twiml::AfterOperation.call(phone_call_id: phone_call.id, tree: tree, after: after)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module Phone
|
5
|
+
module Twiml
|
6
|
+
class PromptOperation < Twilio::Rails::Phone::Twiml::BaseOperation
|
7
|
+
input :tree, accepts: Twilio::Rails::Phone::Tree, type: :keyword, required: true
|
8
|
+
input :response_id, accepts: Integer, type: :keyword, required: true
|
9
|
+
|
10
|
+
def execute
|
11
|
+
return Twilio::Rails::Phone::Twiml::ErrorOperation.call(phone_call_id: phone_call.id, tree: tree) if phone_call.answering_machine?
|
12
|
+
|
13
|
+
response = phone_call.responses.find(response_id)
|
14
|
+
prompt = tree.prompts[response.prompt_handle]
|
15
|
+
raise Twilio::Rails::Phone::InvalidTreeError, "cannot find #{ response.prompt_handle } in #{ tree.name }" unless prompt
|
16
|
+
|
17
|
+
twiml_response = Twilio::TwiML::VoiceResponse.new do |twiml|
|
18
|
+
unless prompt.gather&.interrupt?
|
19
|
+
add_messages(twiml, message_set: prompt.messages, response: response)
|
20
|
+
end
|
21
|
+
|
22
|
+
case prompt.gather&.type
|
23
|
+
when :digits
|
24
|
+
args = {
|
25
|
+
action: ::Twilio::Rails::Engine.routes.url_helpers.phone_prompt_response_path(
|
26
|
+
format: :xml,
|
27
|
+
tree_name: tree.name,
|
28
|
+
response_id: response.id
|
29
|
+
),
|
30
|
+
input: "dtmf",
|
31
|
+
num_digits: prompt.gather.args[:number],
|
32
|
+
timeout: prompt.gather.args[:timeout],
|
33
|
+
action_on_empty_result: false,
|
34
|
+
}
|
35
|
+
|
36
|
+
args[:finish_on_key] = prompt.gather.args[:finish_on_key] if prompt.gather.args[:finish_on_key]
|
37
|
+
|
38
|
+
twiml.gather(**args) do |twiml|
|
39
|
+
if prompt.gather&.interrupt?
|
40
|
+
add_messages(twiml, message_set: prompt.messages, response: response)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
twiml.redirect(::Twilio::Rails::Engine.routes.url_helpers.phone_timeout_path(
|
44
|
+
format: :xml,
|
45
|
+
tree_name: tree.name,
|
46
|
+
response_id: response.id
|
47
|
+
))
|
48
|
+
when :voice
|
49
|
+
args = {
|
50
|
+
max_length: prompt.gather.args[:length],
|
51
|
+
play_beep: prompt.gather.args[:beep],
|
52
|
+
# trim: "trim-silence",
|
53
|
+
timeout: prompt.gather.args[:timeout],
|
54
|
+
action: ::Twilio::Rails::Engine.routes.url_helpers.phone_prompt_response_path(
|
55
|
+
format: :xml,
|
56
|
+
tree_name: tree.name,
|
57
|
+
response_id: response.id
|
58
|
+
),
|
59
|
+
recording_status_callback: ::Twilio::Rails::Engine.routes.url_helpers.phone_receive_recording_path(
|
60
|
+
response_id: response.id
|
61
|
+
),
|
62
|
+
}
|
63
|
+
|
64
|
+
if prompt.gather.args[:transcribe]
|
65
|
+
args[:transcribe] = true
|
66
|
+
args[:transcribe_callback] = ::Twilio::Rails::Engine.routes.url_helpers.phone_transcribe_path(response_id: response.id)
|
67
|
+
end
|
68
|
+
|
69
|
+
args[:profanity_filter] = true if prompt.gather.args[:profanity_filter]
|
70
|
+
|
71
|
+
twiml.record(**args)
|
72
|
+
when :speech
|
73
|
+
args = {
|
74
|
+
action: ::Twilio::Rails::Engine.routes.url_helpers.phone_prompt_response_path(
|
75
|
+
format: :xml,
|
76
|
+
tree_name: tree.name,
|
77
|
+
response_id: response.id
|
78
|
+
),
|
79
|
+
input: "speech",
|
80
|
+
timeout: prompt.gather.args[:timeout],
|
81
|
+
action_on_empty_result: true,
|
82
|
+
language: prompt.gather.args[:language].presence || "en-US",
|
83
|
+
enhanced: !!prompt.gather.args[:enhanced],
|
84
|
+
}
|
85
|
+
|
86
|
+
args[:speech_timeout] = prompt.gather.args[:speech_timeout] if prompt.gather.args[:speech_timeout]
|
87
|
+
args[:speech_model] = prompt.gather.args[:speech_model] if prompt.gather.args[:speech_model].present?
|
88
|
+
args[:profanity_filter] = true if prompt.gather.args[:profanity_filter]
|
89
|
+
|
90
|
+
twiml.gather(**args)
|
91
|
+
when nil
|
92
|
+
twiml.redirect(::Twilio::Rails::Engine.routes.url_helpers.phone_prompt_response_path(
|
93
|
+
format: :xml,
|
94
|
+
tree_name: tree.name,
|
95
|
+
response_id: response.id
|
96
|
+
))
|
97
|
+
else
|
98
|
+
raise Twilio::Rails::Phone::InvalidTreeError, "unknown gather type #{prompt.gather.type.inspect}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
Twilio::Rails.config.logger.info("prompt_twiml: #{twiml_response.to_s}")
|
103
|
+
twiml_response.to_s
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module Phone
|
5
|
+
module Twiml
|
6
|
+
class PromptResponseOperation < Twilio::Rails::Phone::Twiml::BaseOperation
|
7
|
+
input :tree, accepts: Twilio::Rails::Phone::Tree, type: :keyword, required: true
|
8
|
+
input :response_id, accepts: Integer, type: :keyword, required: true
|
9
|
+
input :params, accepts: Hash, type: :keyword, required: true
|
10
|
+
|
11
|
+
def execute
|
12
|
+
return Twilio::Rails::Phone::Twiml::ErrorOperation.call(phone_call_id: phone_call.id, tree: tree) if phone_call.answering_machine?
|
13
|
+
|
14
|
+
response = phone_call.responses.find(response_id)
|
15
|
+
response = Twilio::Rails::Phone::UpdateResponseOperation.call(params: params, response_id: response.id, phone_call_id: phone_call.id)
|
16
|
+
|
17
|
+
prompt = tree.prompts[response.prompt_handle]
|
18
|
+
raise Twilio::Rails::Phone::InvalidTreeError, "cannot find #{ response.prompt_handle } in #{ tree.name }" unless prompt
|
19
|
+
|
20
|
+
after = prompt.after
|
21
|
+
after = Twilio::Rails::Phone::Tree::After.new(after.proc.call(response)) if after.proc
|
22
|
+
|
23
|
+
Twilio::Rails::Phone::Twiml::AfterOperation.call(phone_call_id: phone_call.id, tree: tree, after: after)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module Phone
|
5
|
+
module Twiml
|
6
|
+
class RequestValidationFailureOperation < ::Twilio::Rails::ApplicationOperation
|
7
|
+
def execute
|
8
|
+
twiml = Twilio::TwiML::VoiceResponse.new
|
9
|
+
twiml.hangup
|
10
|
+
twiml.to_s
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|