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.
Files changed (90) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +0 -0
  3. data/LICENSE +21 -0
  4. data/README.md +413 -0
  5. data/Rakefile +8 -0
  6. data/app/assets/config/twilio_rails_manifest.js +1 -0
  7. data/app/assets/stylesheets/twilio/rails/application.css +15 -0
  8. data/app/controllers/twilio/rails/application_controller.rb +6 -0
  9. data/app/controllers/twilio/rails/phone_controller.rb +112 -0
  10. data/app/controllers/twilio/rails/sms_controller.rb +64 -0
  11. data/app/helpers/twilio/rails/application_helper.rb +6 -0
  12. data/app/jobs/twilio/rails/application_job.rb +6 -0
  13. data/app/jobs/twilio/rails/phone/attach_recording_job.rb +15 -0
  14. data/app/jobs/twilio/rails/phone/finished_call_job.rb +15 -0
  15. data/app/jobs/twilio/rails/phone/unanswered_call_job.rb +15 -0
  16. data/app/mailers/twilio/rails/application_mailer.rb +8 -0
  17. data/app/models/twilio/rails/application_record.rb +7 -0
  18. data/app/operations/twilio/rails/application_operation.rb +21 -0
  19. data/app/operations/twilio/rails/find_or_create_phone_caller_operation.rb +29 -0
  20. data/app/operations/twilio/rails/phone/attach_recording_operation.rb +31 -0
  21. data/app/operations/twilio/rails/phone/base_operation.rb +21 -0
  22. data/app/operations/twilio/rails/phone/create_operation.rb +49 -0
  23. data/app/operations/twilio/rails/phone/find_operation.rb +14 -0
  24. data/app/operations/twilio/rails/phone/finished_call_operation.rb +17 -0
  25. data/app/operations/twilio/rails/phone/receive_recording_operation.rb +35 -0
  26. data/app/operations/twilio/rails/phone/start_call_operation.rb +53 -0
  27. data/app/operations/twilio/rails/phone/twiml/after_operation.rb +37 -0
  28. data/app/operations/twilio/rails/phone/twiml/base_operation.rb +50 -0
  29. data/app/operations/twilio/rails/phone/twiml/error_operation.rb +22 -0
  30. data/app/operations/twilio/rails/phone/twiml/greeting_operation.rb +22 -0
  31. data/app/operations/twilio/rails/phone/twiml/prompt_operation.rb +109 -0
  32. data/app/operations/twilio/rails/phone/twiml/prompt_response_operation.rb +29 -0
  33. data/app/operations/twilio/rails/phone/twiml/request_validation_failure_operation.rb +16 -0
  34. data/app/operations/twilio/rails/phone/twiml/timeout_operation.rb +48 -0
  35. data/app/operations/twilio/rails/phone/unanswered_call_operation.rb +22 -0
  36. data/app/operations/twilio/rails/phone/update_operation.rb +26 -0
  37. data/app/operations/twilio/rails/phone/update_response_operation.rb +38 -0
  38. data/app/operations/twilio/rails/sms/base_operation.rb +17 -0
  39. data/app/operations/twilio/rails/sms/create_operation.rb +23 -0
  40. data/app/operations/twilio/rails/sms/find_message_operation.rb +15 -0
  41. data/app/operations/twilio/rails/sms/find_operation.rb +15 -0
  42. data/app/operations/twilio/rails/sms/send_operation.rb +102 -0
  43. data/app/operations/twilio/rails/sms/twiml/base_operation.rb +11 -0
  44. data/app/operations/twilio/rails/sms/twiml/error_operation.rb +15 -0
  45. data/app/operations/twilio/rails/sms/twiml/message_operation.rb +49 -0
  46. data/app/operations/twilio/rails/sms/update_message_operation.rb +27 -0
  47. data/app/views/layouts/twilio/rails/application.html.erb +15 -0
  48. data/config/routes.rb +16 -0
  49. data/lib/generators/twilio/rails/install/USAGE +15 -0
  50. data/lib/generators/twilio/rails/install/install_generator.rb +34 -0
  51. data/lib/generators/twilio/rails/install/templates/initializer.rb +83 -0
  52. data/lib/generators/twilio/rails/install/templates/message.rb +4 -0
  53. data/lib/generators/twilio/rails/install/templates/migration.rb +89 -0
  54. data/lib/generators/twilio/rails/install/templates/phone_call.rb +4 -0
  55. data/lib/generators/twilio/rails/install/templates/phone_caller.rb +4 -0
  56. data/lib/generators/twilio/rails/install/templates/recording.rb +4 -0
  57. data/lib/generators/twilio/rails/install/templates/response.rb +4 -0
  58. data/lib/generators/twilio/rails/install/templates/sms_conversation.rb +4 -0
  59. data/lib/generators/twilio/rails/phone_tree/USAGE +8 -0
  60. data/lib/generators/twilio/rails/phone_tree/phone_tree_generator.rb +12 -0
  61. data/lib/generators/twilio/rails/phone_tree/templates/tree.rb.erb +13 -0
  62. data/lib/generators/twilio/rails/sms_responder/USAGE +8 -0
  63. data/lib/generators/twilio/rails/sms_responder/sms_responder_generator.rb +12 -0
  64. data/lib/generators/twilio/rails/sms_responder/templates/responder.rb.erb +10 -0
  65. data/lib/tasks/rails_tasks.rake +45 -0
  66. data/lib/twilio/rails/client.rb +75 -0
  67. data/lib/twilio/rails/concerns/has_direction.rb +25 -0
  68. data/lib/twilio/rails/concerns/has_phone_number.rb +27 -0
  69. data/lib/twilio/rails/concerns/has_time_scopes.rb +19 -0
  70. data/lib/twilio/rails/configuration.rb +380 -0
  71. data/lib/twilio/rails/engine.rb +11 -0
  72. data/lib/twilio/rails/formatter.rb +93 -0
  73. data/lib/twilio/rails/models/message.rb +21 -0
  74. data/lib/twilio/rails/models/phone_call.rb +132 -0
  75. data/lib/twilio/rails/models/phone_caller.rb +100 -0
  76. data/lib/twilio/rails/models/recording.rb +27 -0
  77. data/lib/twilio/rails/models/response.rb +153 -0
  78. data/lib/twilio/rails/models/sms_conversation.rb +29 -0
  79. data/lib/twilio/rails/phone/base_tree.rb +229 -0
  80. data/lib/twilio/rails/phone/tree.rb +229 -0
  81. data/lib/twilio/rails/phone/tree_macros.rb +147 -0
  82. data/lib/twilio/rails/phone.rb +12 -0
  83. data/lib/twilio/rails/phone_number.rb +29 -0
  84. data/lib/twilio/rails/railtie.rb +17 -0
  85. data/lib/twilio/rails/sms/delegated_responder.rb +97 -0
  86. data/lib/twilio/rails/sms/responder.rb +33 -0
  87. data/lib/twilio/rails/sms.rb +12 -0
  88. data/lib/twilio/rails/version.rb +5 -0
  89. data/lib/twilio/rails.rb +89 -0
  90. metadata +289 -0
@@ -0,0 +1,89 @@
1
+ class InstallTwilioRails < ActiveRecord::Migration[7.0]
2
+ def change
3
+ create_table "messages", force: :cascade do |t|
4
+ t.bigint "sms_conversation_id", null: false
5
+ t.string "sid"
6
+ t.text "body"
7
+ t.string "status"
8
+ t.string "direction"
9
+ t.datetime "created_at", null: false
10
+ t.datetime "updated_at", null: false
11
+ t.index ["created_at"], name: "index_messages_on_created_at"
12
+ t.index ["direction"], name: "index_messages_on_direction"
13
+ t.index ["sms_conversation_id"], name: "index_messages_on_sms_conversation_id"
14
+ end
15
+
16
+ create_table "phone_callers", force: :cascade do |t|
17
+ t.string "phone_number"
18
+ t.datetime "created_at", null: false
19
+ t.datetime "updated_at", null: false
20
+ t.index ["phone_number"], name: "index_phone_callers_on_phone_number"
21
+ end
22
+
23
+ create_table "phone_calls", force: :cascade do |t|
24
+ t.integer "phone_caller_id"
25
+ t.string "sid"
26
+ t.string "number"
27
+ t.string "from_number"
28
+ t.string "from_city"
29
+ t.string "from_province"
30
+ t.string "from_country"
31
+ t.string "tree_name"
32
+ t.string "direction"
33
+ t.string "answered_by"
34
+ t.boolean "unanswered", default: false
35
+ t.boolean "finished", default: false
36
+ t.string "call_status"
37
+ t.integer "length_seconds"
38
+ t.datetime "created_at", null: false
39
+ t.datetime "updated_at", null: false
40
+ t.index ["created_at"], name: "index_phone_calls_on_created_at"
41
+ t.index ["direction"], name: "index_phone_calls_on_direction"
42
+ t.index ["phone_caller_id"], name: "index_phone_calls_on_phone_caller_id"
43
+ t.index ["sid"], name: "index_calls_on_sid"
44
+ t.index ["tree_name"], name: "index_phone_calls_on_tree_name"
45
+ end
46
+
47
+ create_table "recordings", force: :cascade do |t|
48
+ t.bigint "phone_call_id", null: false
49
+ t.string "recording_sid"
50
+ t.string "duration"
51
+ t.string "url"
52
+ t.datetime "created_at", null: false
53
+ t.datetime "updated_at", null: false
54
+ t.index ["phone_call_id"], name: "index_recordings_on_call_id"
55
+ end
56
+
57
+ create_table "responses", force: :cascade do |t|
58
+ t.bigint "phone_call_id"
59
+ t.bigint "recording_id"
60
+ t.string "prompt_handle"
61
+ t.string "digits"
62
+ t.text "transcription"
63
+ t.boolean "transcribed", default: false
64
+ t.boolean "timeout", default: false
65
+ t.datetime "created_at", null: false
66
+ t.datetime "updated_at", null: false
67
+ t.index ["created_at"], name: "index_responses_on_created_at"
68
+ t.index ["digits"], name: "index_responses_on_digits"
69
+ t.index ["phone_call_id", "prompt_handle"], name: "index_responses_on_phone_call_id_and_prompt_handle"
70
+ t.index ["prompt_handle"], name: "index_responses_on_prompt_handle"
71
+ t.index ["recording_id"], name: "index_responses_on_recording_id"
72
+ t.index ["timeout"], name: "index_responses_on_timeout"
73
+ t.index ["transcribed"], name: "index_responses_on_transcribed"
74
+ end
75
+
76
+ create_table "sms_conversations", force: :cascade do |t|
77
+ t.string "number"
78
+ t.string "from_number"
79
+ t.string "from_city"
80
+ t.string "from_province"
81
+ t.string "from_country"
82
+ t.datetime "created_at", null: false
83
+ t.datetime "updated_at", null: false
84
+ t.index ["created_at"], name: "index_sms_conversations_on_created_at"
85
+ t.index ["from_number"], name: "index_sms_conversations_on_from_number"
86
+ t.index ["number"], name: "index_sms_conversations_on_number"
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ class PhoneCall < ApplicationRecord
3
+ include Twilio::Rails::Models::PhoneCall
4
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ class PhoneCaller < ApplicationRecord
3
+ include Twilio::Rails::Models::PhoneCaller
4
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ class Recording < ApplicationRecord
3
+ include Twilio::Rails::Models::Recording
4
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ class Response < ApplicationRecord
3
+ include Twilio::Rails::Models::Response
4
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ class SMSConversation < ApplicationRecord
3
+ include Twilio::Rails::Models::SMSConversation
4
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Generates a new phone tree and registers it.
3
+
4
+ Example:
5
+ bin/rails generate twilio:rails:phone_tree SomeExample
6
+
7
+ This will create:
8
+ app/phone_trees/some_example_tree.rb
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ class Twilio::Rails::PhoneTreeGenerator < Rails::Generators::NamedBase
3
+ source_root File.expand_path("templates", __dir__)
4
+
5
+ def copy_template_tree
6
+ template "tree.rb.erb", "app/phone_trees/#{file_name}_tree.rb"
7
+ end
8
+
9
+ def register_tree
10
+ insert_into_file "config/initializers/twilio_rails.rb", "\n config.phone_trees.register { #{class_name}Tree }", before: "\nend\n"
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+ class <%= class_name %>Tree < Twilio::Rails::Phone::BaseTree
3
+ # voice "male"
4
+ # final_timeout_message("Goodbye.")
5
+ # invalid_phone_number("Sorry, we cannot accept your call.")
6
+
7
+ greeting message: "Hello!",
8
+ prompt: :thanks_and_hangup
9
+
10
+ prompt :thanks_and_hangup,
11
+ message: "Thank you for calling!",
12
+ after: { hangup: true }
13
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Generates a new SMS responder and registers it.
3
+
4
+ Example:
5
+ bin/rails generate twilio:rails:sms_responder SomeExample
6
+
7
+ This will create:
8
+ app/sms_responders/some_example_responder.rb
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+ class Twilio::Rails::SmsResponderGenerator < Rails::Generators::NamedBase
3
+ source_root File.expand_path("templates", __dir__)
4
+
5
+ def copy_template_responder
6
+ template "responder.rb.erb", "app/sms_responders/#{file_name}_responder.rb"
7
+ end
8
+
9
+ def register_responder
10
+ insert_into_file "config/initializers/twilio_rails.rb", "\n config.sms_responders.register { #{class_name}Responder }", before: "\nend\n"
11
+ end
12
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ class <%= class_name %>Responder < Twilio::Rails::SMS::DelegatedResponder
3
+ def handle?
4
+ raise NotImplementedError, "Implement #{ self.class }#handle?"
5
+ end
6
+
7
+ def reply
8
+ raise NotImplementedError, "Implement #{ self.class }#reply"
9
+ end
10
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+ namespace :twilio do
3
+ namespace :rails do
4
+ desc "Show the available values to config in Twilio"
5
+ task config: :environment do
6
+ if Twilio::Rails.config.host.blank?
7
+ puts "Twilio::Rails.config.host is not set. Set it in the `config/initializers/twilio_rails.rb` file."
8
+ elsif Twilio::Rails.config.host == "https://example.com"
9
+ puts "Twilio::Rails.config.host is set to a test value. Set it in the `config/initializers/twilio_rails.rb` file."
10
+ else
11
+ http_methods = if Twilio::Rails.config.controller_http_methods.length == 1
12
+ "HTTP #{ Twilio::Rails.config.controller_http_methods.first.to_s.upcase }"
13
+ else
14
+ "HTTP POST or HTTP GET"
15
+ end
16
+
17
+ puts "Log into the Twilio web console: https://console.twilio.com"
18
+ puts "Navigate to Phone Numbers -> Manage -> Active Numbers and find the phone number #{Twilio::Rails.config.default_outgoing_phone_number}."
19
+ puts ""
20
+
21
+ if Twilio::Rails.config.phone_trees.all.length == 0
22
+ puts "You cannot yet configure `Voice & Fax' because There are no phone trees registered in this application."
23
+ puts "Register them in the `config/initializers/twilio_rails.rb` file if you want to handle phone calls, and run this task again to help configure Twilio."
24
+ else
25
+ puts "Under 'Voice & Fax' set 'A CALL COMES IN' to 'Webhook' with #{ http_methods } and one of the following URLs:"
26
+ Twilio::Rails.config.phone_trees.all.each do |name, tree|
27
+ puts " #{tree.inbound_url}"
28
+ end
29
+ puts ""
30
+ puts "Under 'Voice & Fax' set 'CALL STATUS CHANGES' to following URL:"
31
+ puts " #{ ::Twilio::Rails.config.host }#{ ::Twilio::Rails::Engine.routes.url_helpers.phone_status_path(format: :xml) }"
32
+ end
33
+
34
+ puts ""
35
+ puts "Under 'Messaging' set 'A MESSAGE COMES IN' to 'Webhook' with #{ http_methods } and the following URL:"
36
+ puts " #{ ::Twilio::Rails.config.host }#{ ::Twilio::Rails::Engine.routes.url_helpers.sms_message_path(format: :xml) }"
37
+
38
+ if Twilio::Rails.config.sms_responders.all.length == 0
39
+ puts "There are no SMS responders registered so they will not be handled."
40
+ puts "Register them in the `config/initializers/twilio_rails.rb` file if you want to handle SMS messages."
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ # An abstraction over top of the `Twilio::REST` API client. Used to send SMS messages and start calls, as well as
5
+ # return an initialized client if needed.
6
+ module Client
7
+ extend self
8
+
9
+ # @return [Twilio::REST::Client] Twilio client initialized with `account_sid` and `auth_token` from the config.
10
+ def client
11
+ @twilio_client ||= Twilio::REST::Client.new(
12
+ Twilio::Rails.config.account_sid,
13
+ Twilio::Rails.config.auth_token,
14
+ )
15
+ end
16
+
17
+ # Do not call this directly, instead see {Twilio::Rails::SMS::SendOperation}. Send an SMS message to and from the
18
+ # given phone numbers using the Twilio REST API directly. This does not store or manage any interactions in the
19
+ # database.
20
+ #
21
+ # @param message [String] the message to send.
22
+ # @param to [String] the phone number to send the message to.
23
+ # @param from [String] the phone number to send the message from.
24
+ # @return [String] the SID returned from Twilio for the sent SMS message.
25
+ def send_message(message:, to:, from:)
26
+ Twilio::Rails.config.logger.tagged(self) { |l| l.info("[send_message] to=#{ to } from=#{ from } body='#{ message }'") }
27
+ client.messages.create(
28
+ from: from,
29
+ to: to,
30
+ body: message,
31
+ status_callback: "#{ Twilio::Rails.config.host }#{ ::Twilio::Rails::Engine.routes.url_helpers.sms_status_path(format: :xml) }",
32
+ ).sid
33
+ end
34
+
35
+ # Do not call this directly, instead see {Twilio::Rails::SMS::SendOperation}. Sends multiple SMS messages to and
36
+ # from the given phone numbers using the Twilio REST API directly. This does not store or manage any interactions
37
+ # in the database. If a message is blank it will be ignored.
38
+ #
39
+ # @param messages [Array<String>] the messages to send.
40
+ # @param to [String] the phone number to send the messages to.
41
+ # @param from [String] the phone number to send the messages from.
42
+ # @return [Array<String>] the SIDs returned from Twilio for the sent SMS messages.
43
+ def send_messages(messages:, to:, from:)
44
+ Twilio::Rails.config.logger.tagged(self) { |l| l.info("[send_messages] to blank messages") } if messages.blank?
45
+ messages.map { |m| send_message(message: m, to: to, from: from) }
46
+ end
47
+
48
+ # Do not call this directly, instead see {Twilio::Rails::Phone::StartCallOperation}. Starts a phone call to and
49
+ # from the given phone numbers using the Twilio REST API directly. This does not store or manage any interactions
50
+ # in the database. The URL should come from the {Twilio::Rails::Phone::Tree} that is being used to start the call.
51
+ #
52
+ # @param url [String] the URL to use for the Twilio REST API call, probably from the {Twilio::Rails::Phone::Tree}.
53
+ # @param to [String] the phone number to make the call to.
54
+ # @param from [String] the phone number to make the call from.
55
+ # @param answering_machine_detection [true, false] whether or not to enable answering machine detection.
56
+ # @return [String] the SID returned from Twilio for the started call.
57
+ def start_call(url:, to:, from:, answering_machine_detection: true)
58
+ Twilio::Rails.config.logger.tagged(self) { |l| l.info("[start_call] to=#{ to } from=#{ from } url=#{ url } answering_machine_detection=#{ !!answering_machine_detection }") }
59
+ client.calls.create(
60
+ from: from,
61
+ to: to,
62
+ url: url,
63
+ machine_detection: ( answering_machine_detection ? "Enable" : "Disable" ),
64
+ async_amd: true,
65
+ async_amd_status_callback: "#{ Twilio::Rails.config.host }#{ ::Twilio::Rails::Engine.routes.url_helpers.phone_status_path(format: :xml, async_amd: "true") }",
66
+ async_amd_status_callback_method: "POST",
67
+ status_callback: "#{ Twilio::Rails.config.host }#{ ::Twilio::Rails::Engine.routes.url_helpers.phone_status_path(format: :xml) }",
68
+ status_callback_method: "POST",
69
+ status_callback_event: ["completed", "no-answer"],
70
+ # timeout: 30,
71
+ ).sid
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ # Provides scopes, validations, and convenience methods for a model that has an attribute `direction` with
5
+ # values "inbound" or "outbound".
6
+ module HasDirection
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ validates :direction, inclusion: { in: ["outbound", "inbound"] }
11
+
12
+ scope :outbound, -> { where(direction: "outbound") }
13
+ scope :inbound, -> { where(direction: "inbound") }
14
+ end
15
+
16
+ def inbound?
17
+ direction == "inbound"
18
+ end
19
+
20
+ def outbound?
21
+ direction == "outbound"
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ # Provides validations and reformatting on validation for a model that has an attribute `phone_number` that is
5
+ # that is unique and that matches {Twilio::Rails::Formatter.coerce_to_valid_phone_number}.
6
+ module HasPhoneNumber
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ validates :phone_number, uniqueness: { allow_blank: true, message: "already exists" }
11
+
12
+ before_validation :reformat_phone_number
13
+ end
14
+
15
+ def reformat_phone_number
16
+ current = Twilio::Rails::Formatter.coerce_to_valid_phone_number(phone_number)
17
+ self.phone_number = current if current
18
+
19
+ true
20
+ end
21
+
22
+ def valid_north_american_phone_number?
23
+ Twilio::Rails::Formatter.valid_north_american_phone_number?(phone_number)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+ module Twilio
3
+ module Rails
4
+ # Provides convenience scopes for a model that has a `created_at` attribute.
5
+ module HasTimeScopes
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ scope :in_last, ->(time) { where(created_at: (Time.now - time)..(Time.now)) }
10
+ scope :in_previous, ->(time) { where(created_at: (Time.now - time - time)..(Time.now - time)) }
11
+
12
+ scope :in_last_24_hours, -> { in_last(1.day) }
13
+ scope :in_last_2_days, -> { in_last(2.days) }
14
+ scope :in_last_4_hours, -> { in_last(4.hours) }
15
+ scope :in_previous_24_hours, -> { in_previous(1.day) }
16
+ end
17
+ end
18
+ end
19
+ end