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,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module Phone
|
5
|
+
module Twiml
|
6
|
+
class TimeoutOperation < 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
|
+
response.timeout = true
|
15
|
+
response.save!
|
16
|
+
|
17
|
+
if final_timeout?(response, count: tree.config[:final_timeout_attempts])
|
18
|
+
twiml_response = Twilio::TwiML::VoiceResponse.new do |twiml|
|
19
|
+
add_messages(twiml, message_set: tree.config[:final_timeout_message], response: response)
|
20
|
+
twiml.hangup
|
21
|
+
end
|
22
|
+
|
23
|
+
Twilio::Rails.config.logger.info("final timeout on phone_call##{ phone_call.id }")
|
24
|
+
Twilio::Rails.config.logger.info("timeout_twiml: #{twiml_response.to_s}")
|
25
|
+
twiml_response.to_s
|
26
|
+
else
|
27
|
+
prompt = tree.prompts[response.prompt_handle]
|
28
|
+
raise Twilio::Rails::Phone::InvalidTreeError, "cannot find #{ response.prompt_handle } in #{ tree.name }" unless prompt
|
29
|
+
|
30
|
+
after = prompt.after
|
31
|
+
after = Twilio::Rails::Phone::Tree::After.new(after.proc.call(response)) if after.proc
|
32
|
+
|
33
|
+
Twilio::Rails::Phone::Twiml::AfterOperation.call(phone_call_id: phone_call.id, tree: tree, after: after)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def final_timeout?(last_response, count: )
|
40
|
+
responses = phone_call.responses.final_timeout_check(count: count, prompt_handle: last_response.prompt_handle)
|
41
|
+
|
42
|
+
responses.count == count && responses.all? { |r| r.timeout? }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module Phone
|
5
|
+
class UnansweredCallOperation < ::Twilio::Rails::Phone::BaseOperation
|
6
|
+
def execute
|
7
|
+
if !phone_call.outbound?
|
8
|
+
Twilio::Rails.config.logger.tagged(self.class) { |l| l.error("Should never be called on inbound call") }
|
9
|
+
halt
|
10
|
+
end
|
11
|
+
|
12
|
+
if phone_call.unanswered?
|
13
|
+
Twilio::Rails.config.logger.tagged(self.class) { |l| l.warn("Skipping duplicate unanswered call job") }
|
14
|
+
else
|
15
|
+
phone_call.update!(unanswered: true)
|
16
|
+
phone_call.tree.unanswered_call.call(phone_call) if phone_call.tree.unanswered_call
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module Phone
|
5
|
+
class UpdateOperation < ::Twilio::Rails::Phone::BaseOperation
|
6
|
+
input :params, accepts: Hash, type: :keyword, required: true
|
7
|
+
|
8
|
+
def execute
|
9
|
+
if phone_call.outbound?
|
10
|
+
if params["AnsweredBy"].present? && phone_call.answered_by != params["AnsweredBy"]
|
11
|
+
phone_call.answered_by = params["AnsweredBy"]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
if params["CallStatus"].present? && phone_call.call_status != params["CallStatus"]
|
16
|
+
phone_call.call_status = params["CallStatus"]
|
17
|
+
end
|
18
|
+
|
19
|
+
phone_call.save! if phone_call.changed?
|
20
|
+
|
21
|
+
phone_call
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module Phone
|
5
|
+
class UpdateResponseOperation < ::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 params["Digits"].present?
|
13
|
+
response.digits = params["Digits"]
|
14
|
+
end
|
15
|
+
|
16
|
+
if params["TranscriptionText"].present? && params["TranscriptionStatus"] == "completed"
|
17
|
+
response.transcription = params["TranscriptionText"]
|
18
|
+
response.transcribed = true
|
19
|
+
end
|
20
|
+
|
21
|
+
if params["SpeechResult"].present?
|
22
|
+
response.transcription = params["SpeechResult"]
|
23
|
+
response.transcribed = true
|
24
|
+
end
|
25
|
+
|
26
|
+
response.save! if response.changed?
|
27
|
+
|
28
|
+
if params["RecordingSid"]
|
29
|
+
Twilio::Rails::Phone::ReceiveRecordingOperation.call(phone_call_id: phone_call.id, response_id: response.id, params: params)
|
30
|
+
response.reload # This attaches to the other end of the association so this instance doesn't know about it without a reload
|
31
|
+
end
|
32
|
+
|
33
|
+
response
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module SMS
|
5
|
+
# Base class for all SMS operations. Requires the `sms_conversation_id` to be passed in.
|
6
|
+
class BaseOperation < ::Twilio::Rails::ApplicationOperation
|
7
|
+
input :sms_conversation_id, accepts: Integer, type: :keyword, required: true
|
8
|
+
|
9
|
+
protected
|
10
|
+
|
11
|
+
def conversation
|
12
|
+
@conversation ||= ::Twilio::Rails.config.sms_conversation_class.find(sms_conversation_id)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module SMS
|
5
|
+
# Called by {Twilio::Rails::SMSController} with the Twilio params to create a new SMS conversation.
|
6
|
+
class CreateOperation < ApplicationOperation
|
7
|
+
input :params, accepts: Hash, type: :keyword, required: true
|
8
|
+
|
9
|
+
def execute
|
10
|
+
conversation = ::Twilio::Rails.config.sms_conversation_class.new(
|
11
|
+
number: params["Called"].presence || params["To"].presence,
|
12
|
+
from_number: params["From"].presence,
|
13
|
+
from_city: params["FromCity"].presence,
|
14
|
+
from_province: params["FromState"].presence,
|
15
|
+
from_country: params["FromCountry"].presence,
|
16
|
+
)
|
17
|
+
conversation.save!
|
18
|
+
conversation
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module SMS
|
5
|
+
# Called by {Twilio::Rails::SMSController} with the Twilio params to find an existing SMS message.
|
6
|
+
class FindMessageOperation < ApplicationOperation
|
7
|
+
input :params, accepts: Hash, type: :keyword, required: true
|
8
|
+
|
9
|
+
def execute
|
10
|
+
::Twilio::Rails.config.message_class.find_by!(sid: params["SmsSid"])
|
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 SMS
|
5
|
+
# Called by {Twilio::Rails::SMSController} with the Twilio params to find an existing SMS conversation.
|
6
|
+
class FindOperation < ApplicationOperation
|
7
|
+
input :sms_conversation_id, accepts: Integer, type: :keyword, required: true
|
8
|
+
|
9
|
+
def execute
|
10
|
+
::Twilio::Rails.config.sms_conversation_class.find(sms_conversation_id)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module SMS
|
5
|
+
# Public entrypoint used to send an SMS message. This operation will create a new conversation to the phone caller
|
6
|
+
# and send a series of messages to them. The interaction will be stored in the database and sent via Twilio's API.
|
7
|
+
# The operation will raise if the {#from_number} is not a valid phone number.
|
8
|
+
#
|
9
|
+
# @example
|
10
|
+
# Twilio::Rails::SMS::SendOperation.call(
|
11
|
+
# phone_caller_id: a_phone_caller.id,
|
12
|
+
# messages: ["Hello world!"],
|
13
|
+
# from_number: "+1234567890"
|
14
|
+
# )
|
15
|
+
#
|
16
|
+
# *Note:* Operations should be called with `call(params)` and not by calling `new(params).execute` directly.
|
17
|
+
class SendOperation < ApplicationOperation
|
18
|
+
input :phone_caller_id, accepts: Integer, type: :keyword, required: true
|
19
|
+
input :messages, accepts: Array, type: :keyword, required: true
|
20
|
+
input :from_number, accepts: [String, Twilio::Rails::PhoneNumber], type: :keyword, required: false
|
21
|
+
|
22
|
+
TWILIO_UNSUBSCRIBED_ERROR_CODES = [ 21610 ].freeze
|
23
|
+
|
24
|
+
# @param phone_caller_id [Integer] the id of the phone caller to send the message to.
|
25
|
+
# @param messages [Array<String>] the messages to send to the phone caller. It may be empty.
|
26
|
+
# @param from_number [String, Twilio::Rails::PhoneNumber] the phone number to send the message from. If the
|
27
|
+
# number is `nil` then it will attempt to extract the phone number from the last phone call. If that is not found
|
28
|
+
# then it will raise {Twilio::Rails::SMS::Error}.
|
29
|
+
# @return [Twilio::Rails::Models::SMSConversation] the SMS conversation that was created and sent.
|
30
|
+
def execute
|
31
|
+
return nil if messages.blank?
|
32
|
+
raise Twilio::Rails::SMS::Error, "from_number=#{ from_number } is not a valid phone number" if from_number.present? && !Twilio::Rails::Formatter.coerce_to_valid_phone_number(from_number)
|
33
|
+
|
34
|
+
conversation = ::Twilio::Rails.config.sms_conversation_class.new(
|
35
|
+
number: calculated_from_number,
|
36
|
+
from_number: calculated_to_number,
|
37
|
+
from_city: phone_call&.from_city,
|
38
|
+
from_province: phone_call&.from_province,
|
39
|
+
from_country: phone_call&.from_country,
|
40
|
+
)
|
41
|
+
conversation.save!
|
42
|
+
|
43
|
+
messages.each do |body|
|
44
|
+
sid = nil
|
45
|
+
begin
|
46
|
+
sid = Twilio::Rails::Client.send_message(
|
47
|
+
message: body,
|
48
|
+
to: calculated_to_number,
|
49
|
+
from: calculated_from_number,
|
50
|
+
)
|
51
|
+
rescue Twilio::REST::RestError => e
|
52
|
+
if TWILIO_UNSUBSCRIBED_ERROR_CODES.include?(e.code)
|
53
|
+
Twilio::Rails.config.logger.tagged(self.class) { |l| l.warn("tried to send to unsubscribed and got Twilio::REST::RestError code=21610 phone_caller_id=#{ phone_caller.id } phone_number=#{ calculated_to_number } message=#{ body }") }
|
54
|
+
else
|
55
|
+
Twilio::Rails.notify_exception(e,
|
56
|
+
message: "Failed to send Twilio message. Got REST error response.",
|
57
|
+
context: {
|
58
|
+
to: calculated_to_number,
|
59
|
+
from: calculated_from_number,
|
60
|
+
phone_call_id: phone_call&.id,
|
61
|
+
},
|
62
|
+
exception_binding: binding
|
63
|
+
)
|
64
|
+
raise
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
message = conversation.messages.build(body: body, sid: sid, direction: "outbound")
|
69
|
+
|
70
|
+
message.save!
|
71
|
+
end
|
72
|
+
|
73
|
+
conversation
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def phone_caller
|
79
|
+
@phone_caller ||= ::Twilio::Rails.config.phone_caller_class.find(phone_caller_id)
|
80
|
+
end
|
81
|
+
|
82
|
+
def phone_call
|
83
|
+
@phone_call ||= phone_caller.phone_calls.inbound.last
|
84
|
+
end
|
85
|
+
|
86
|
+
def calculated_from_number
|
87
|
+
if from_number.present?
|
88
|
+
Twilio::Rails::Formatter.coerce_to_valid_phone_number(from_number)
|
89
|
+
elsif phone_call
|
90
|
+
phone_call.number
|
91
|
+
else
|
92
|
+
raise Twilio::Rails::SMS::Error, "Cannot find a valid from_number to send from"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def calculated_to_number
|
97
|
+
phone_caller.phone_number
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module SMS
|
5
|
+
module Twiml
|
6
|
+
class ErrorOperation < ::Twilio::Rails::ApplicationOperation
|
7
|
+
def execute
|
8
|
+
twiml = Twilio::TwiML::MessagingResponse.new
|
9
|
+
twiml.to_s
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module SMS
|
5
|
+
module Twiml
|
6
|
+
class MessageOperation < Twilio::Rails::SMS::Twiml::BaseOperation
|
7
|
+
input :params, accepts: Hash, type: :keyword, required: true
|
8
|
+
|
9
|
+
def execute
|
10
|
+
inbound_message = conversation.messages.build(
|
11
|
+
direction: "inbound",
|
12
|
+
body: params["Body"],
|
13
|
+
sid: params["SmsSid"].presence || params["MessageSid"].presence
|
14
|
+
)
|
15
|
+
|
16
|
+
inbound_message.save!
|
17
|
+
|
18
|
+
body = Twilio::Rails::SMS::Responder.new(inbound_message).respond
|
19
|
+
|
20
|
+
if body.present?
|
21
|
+
message = conversation.messages.build(
|
22
|
+
direction: "outbound",
|
23
|
+
body: body,
|
24
|
+
)
|
25
|
+
|
26
|
+
message.save!
|
27
|
+
|
28
|
+
twiml_response = Twilio::TwiML::MessagingResponse.new do |twiml|
|
29
|
+
twiml.message(
|
30
|
+
body: body,
|
31
|
+
action: ::Twilio::Rails::Engine.routes.url_helpers.sms_status_message_path(message_id: message.id)
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
Twilio::Rails.config.logger.info("message_twiml: #{twiml_response.to_s}")
|
36
|
+
twiml_response.to_s
|
37
|
+
else
|
38
|
+
Twilio::Rails.config.logger.info("resply is blank, not sending message in response")
|
39
|
+
Twilio::Rails.config.logger.info("message_twiml: #{twiml_response.to_s}")
|
40
|
+
|
41
|
+
twiml = Twilio::TwiML::MessagingResponse.new
|
42
|
+
twiml.to_s
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module Twilio
|
3
|
+
module Rails
|
4
|
+
module SMS
|
5
|
+
# Called by {Twilio::Rails::SMSController} with the Twilio params to update an existing SMS message with any
|
6
|
+
# status changes or updates that Twilio sends. The save will only happen if there has been a change.
|
7
|
+
class UpdateMessageOperation < ApplicationOperation
|
8
|
+
input :params, accepts: Hash, type: :keyword, required: true
|
9
|
+
input :message_id, accepts: Integer, type: :keyword, required: true
|
10
|
+
|
11
|
+
def execute
|
12
|
+
message = ::Twilio::Rails.config.message_class.find(message_id)
|
13
|
+
|
14
|
+
if params["MessageStatus"].present?
|
15
|
+
message.status = params["MessageStatus"]
|
16
|
+
end
|
17
|
+
|
18
|
+
if message.changed?
|
19
|
+
message.save!
|
20
|
+
end
|
21
|
+
|
22
|
+
message
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/config/routes.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# Implements all the routes that Twillio will call to manage the lifecycle of phone calls and SMS messages. See the
|
2
|
+
# {README.md} for detailed instructions on how to configure the Twilio dashboard to call these routes.
|
3
|
+
Twilio::Rails::Engine.routes.draw do
|
4
|
+
match "phone/receive_recording/:response_id", to: "phone#receive_response_recording", as: :phone_receive_recording, via: ::Twilio::Rails.config.controller_http_methods
|
5
|
+
match "phone/transcribe/:response_id", to: "phone#transcribe", as: :phone_transcribe, via: ::Twilio::Rails.config.controller_http_methods
|
6
|
+
match "phone/status", to: "phone#status", as: :phone_status, via: ::Twilio::Rails.config.controller_http_methods
|
7
|
+
match "phone/:tree_name/inbound", to: "phone#inbound", as: :phone_inbound, via: ::Twilio::Rails.config.controller_http_methods
|
8
|
+
match "phone/:tree_name/outbound", to: "phone#outbound", as: :phone_outbound, via: ::Twilio::Rails.config.controller_http_methods
|
9
|
+
match "phone/:tree_name/prompt/:response_id", to: "phone#prompt", as: :phone_prompt, via: ::Twilio::Rails.config.controller_http_methods
|
10
|
+
match "phone/:tree_name/prompt_response/:response_id", to: "phone#prompt_response", as: :phone_prompt_response, via: ::Twilio::Rails.config.controller_http_methods
|
11
|
+
match "phone/:tree_name/timeout/:response_id", to: "phone#timeout", as: :phone_timeout, via: ::Twilio::Rails.config.controller_http_methods
|
12
|
+
|
13
|
+
match "sms/message", to: "sms#message", as: :sms_message, via: ::Twilio::Rails.config.controller_http_methods
|
14
|
+
match "sms/status", to: "sms#status", as: :sms_status, via: ::Twilio::Rails.config.controller_http_methods
|
15
|
+
match "sms/status/:message_id", to: "sms#status", as: :sms_status_message, via: ::Twilio::Rails.config.controller_http_methods
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
Description:
|
2
|
+
Installs the initializer, routes, models, and migrations for `twilio-rails`.
|
3
|
+
|
4
|
+
Example:
|
5
|
+
bin/rails generate twilio:rails:install
|
6
|
+
|
7
|
+
This will create:
|
8
|
+
config/initializers/twilio_rails.rb
|
9
|
+
db/migrate/install_twilio_rails.rb
|
10
|
+
app/models/message.rb
|
11
|
+
app/models/phone_caller.rb
|
12
|
+
app/models/phone_call.rb
|
13
|
+
app/models/recording.rb
|
14
|
+
app/models/response.rb
|
15
|
+
app/models/sms_conversation.rb
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
class Twilio::Rails::InstallGenerator < Rails::Generators::Base
|
3
|
+
source_root File.expand_path("templates", __dir__)
|
4
|
+
|
5
|
+
include Rails::Generators::Migration
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def next_migration_number(dirname)
|
9
|
+
next_migration_number = current_migration_number(dirname) + 1
|
10
|
+
ActiveRecord::Migration.next_migration_number(next_migration_number)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def setup_initializer
|
15
|
+
copy_file "initializer.rb", "config/initializers/twilio_rails.rb"
|
16
|
+
end
|
17
|
+
|
18
|
+
def setup_routes
|
19
|
+
route "mount Twilio::Rails::Engine => '/twilio'"
|
20
|
+
end
|
21
|
+
|
22
|
+
def setup_migrations
|
23
|
+
migration_template "migration.rb", "db/migrate/install_twilio_rails.rb"
|
24
|
+
end
|
25
|
+
|
26
|
+
def setup_models
|
27
|
+
copy_file "message.rb", "app/models/message.rb"
|
28
|
+
copy_file "phone_caller.rb", "app/models/phone_caller.rb"
|
29
|
+
copy_file "phone_call.rb", "app/models/phone_call.rb"
|
30
|
+
copy_file "recording.rb", "app/models/recording.rb"
|
31
|
+
copy_file "response.rb", "app/models/response.rb"
|
32
|
+
copy_file "sms_conversation.rb", "app/models/sms_conversation.rb"
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
Twilio::Rails.setup do |config|
|
3
|
+
# These are the Twilio account credentials used to access the Twilio API. These should likely be configured in the
|
4
|
+
# encrypted Rails credentials or loaded from an ENV variable.
|
5
|
+
config.account_sid = "TODO: account_sid"
|
6
|
+
config.auth_token = "TODO: auth_token"
|
7
|
+
|
8
|
+
# This is the phone number that will be used to send SMS messages or start Phone Calls. It must be first configured
|
9
|
+
# and purchased in the Twilio dashboard, then entered here. The format must be "+15556667777". In most applications it
|
10
|
+
# is probably the only number, but in more complex applications it is the "main" or default number. It is used when
|
11
|
+
# the phone number is not specified and the number otherwise cannot be intelligently guessed or inferred. This number
|
12
|
+
# should likely be configured in the encrypted Rails credentials or loaded from an ENV variable.
|
13
|
+
config.default_outgoing_phone_number = "TODO: +15556667777"
|
14
|
+
|
15
|
+
# All the following configuration options are optional and have reasonable defaults. Though if no phone trees or SMS
|
16
|
+
# responders are registered then the app will not be able to do much.
|
17
|
+
|
18
|
+
# Registry objects used to store and lookup phone trees and SMS responders. They must be registered here at setup time
|
19
|
+
# in order for the application to be able to use them. They can either be defined as strings that will be
|
20
|
+
# constantized, or as a block that will be evaluated after initialization. It is recommended to use the included
|
21
|
+
# generators to create new phone trees and SMS responders, which will add the lines here to register them.
|
22
|
+
#
|
23
|
+
# See the README documentation for more information on how to define and register phone trees and SMS responders.
|
24
|
+
# config.phone_trees.register { MyPhoneTree }
|
25
|
+
# config.sms_responders.register { MySMSResponder }
|
26
|
+
|
27
|
+
# This is the host that will be used to generate URLs that the Twilio API will use to make requests to the
|
28
|
+
# application. It defaults to what is defined in Rails `default_url_options` but can be overridden here. The format
|
29
|
+
# must be a protocol and domain, without the trailing slash, and no path.
|
30
|
+
# config.host = "https://example.com"
|
31
|
+
|
32
|
+
# The logger used by the framework. Defaults to `Rails.logger`. It cannot be `nil`, so to disable framework
|
33
|
+
# logging explicitly set it to `Logger.new(nil)`.
|
34
|
+
# config.logger = Rails.logger
|
35
|
+
|
36
|
+
# The HTTP methods that Twilio will use to call into the app. Defaults to `[:get, :post]` but can be restricted
|
37
|
+
# to just `[:get]` or `[:post]`. This must match the configuration in the Twilio dashboard.
|
38
|
+
# config.controller_http_methods = [:get, :post]
|
39
|
+
|
40
|
+
# Allows SMS messages to be filtered at source if they appear to be spam. This is an optional callable that is run
|
41
|
+
# with raw params from Twilio on each request. If the callable returns `true` it will prevent the message from being
|
42
|
+
# processed. This is useful for filtering out messages that are obviously spam. Setting this to `nil` will disable
|
43
|
+
# the filter and is the default.
|
44
|
+
# config.spam_filter = ->(params) {
|
45
|
+
# [ "Bad text" ].any? { |regex| regex.match?(params["Body"]) }
|
46
|
+
# }
|
47
|
+
|
48
|
+
# A proc that will be called when an exception is raised in certain key points in the framework. This will never
|
49
|
+
# capture the exception, it will raise regardless, but it is a good spot to send an email or notify in chat
|
50
|
+
# if desired. The proc needs to accept `(exception, message, context, exception_binding)` as arguments. The
|
51
|
+
# default is `nil`, which means no action will be taken.
|
52
|
+
# config.exception_notifier = ->(exception, message, context, exception_binding) {
|
53
|
+
# MyChatClient.send_message("Error: #{ message } #{ exception.message } #{ context }")
|
54
|
+
# }
|
55
|
+
|
56
|
+
# Controls if recordings will be downloaded and attached to the `Recording` model in an ActiveStorage attachment.
|
57
|
+
# This is `true` by default, but can be set to `false` to disable all downloads. It can also be set to a `Proc` or
|
58
|
+
# callable that will receive the `Recording` instance and return a boolean for this specific instance. if reordings will be downloaded.
|
59
|
+
# config.attach_recordings = true
|
60
|
+
|
61
|
+
# A list of strings to be interpreted as yes or acceptance to a question, when the response is transcribed. Used in
|
62
|
+
# the phone macros. Defaults to a list of common responses.
|
63
|
+
# config.yes_responses = ["yes"]
|
64
|
+
|
65
|
+
# A list of strings to be interpreted as no or rejection to a question, when the response is transcribed. Used in
|
66
|
+
# the phone macros. Defaults to a list of common responses.
|
67
|
+
# config.no_responses = ["no"]
|
68
|
+
|
69
|
+
# The name of the model classes, as strings, that this application uses to represent the concepts stored in the DB.
|
70
|
+
# The generators will generate the models with the default names below, but they can be changed as the application
|
71
|
+
# may need.
|
72
|
+
# config.phone_caller_class_name = "PhoneCaller"
|
73
|
+
# config.phone_call_class_name = "PhoneCall"
|
74
|
+
# config.response_class_name = "Response"
|
75
|
+
# config.sms_conversation_class_name = "SMSConversation"
|
76
|
+
# config.message_class_name = "Message"
|
77
|
+
# config.recording_class_name = "Recording"
|
78
|
+
|
79
|
+
# Allows adding a module to be included into the `macros` in the phone tree DSL. This is useful for
|
80
|
+
# adding convenience methods specific to the application. It can be called multiple times to add multiple modules.
|
81
|
+
# Built in macros are defined in `Twilio::Rails::Phone::TreeMacros`.
|
82
|
+
# config.include_phone_macros MyMacrosModule
|
83
|
+
end
|