twilio-rails 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|