babysms 0.5.1
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/.gitignore +12 -0
- data/.rspec +3 -0
- data/.rubocop-airbnb.yml +1716 -0
- data/.rubocop.yml +75 -0
- data/.travis.yml +7 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +184 -0
- data/Rakefile +6 -0
- data/babysms.gemspec +51 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/config.ru +2 -0
- data/lib/babysms.rb +52 -0
- data/lib/babysms/adapter.rb +60 -0
- data/lib/babysms/adapters/bandwidth_adapter.rb +41 -0
- data/lib/babysms/adapters/nexmo_adapter.rb +39 -0
- data/lib/babysms/adapters/plivo_adapter.rb +34 -0
- data/lib/babysms/adapters/signalwire_adapter.rb +38 -0
- data/lib/babysms/adapters/test_adapter.rb +61 -0
- data/lib/babysms/adapters/twilio_adapter.rb +47 -0
- data/lib/babysms/errors.rb +43 -0
- data/lib/babysms/mail_man.rb +77 -0
- data/lib/babysms/message.rb +58 -0
- data/lib/babysms/receipt.rb +19 -0
- data/lib/babysms/report.rb +15 -0
- data/lib/babysms/version.rb +3 -0
- data/lib/babysms/web_application.rb +87 -0
- data/lib/babysms/web_hook.rb +21 -0
- metadata +243 -0
data/.rubocop.yml
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
inherit_from:
|
2
|
+
- .rubocop-airbnb.yml
|
3
|
+
|
4
|
+
AllCops:
|
5
|
+
TargetRubyVersion: 2.6.3
|
6
|
+
|
7
|
+
Rails:
|
8
|
+
Enabled: true
|
9
|
+
|
10
|
+
Layout/EmptyLines:
|
11
|
+
Enabled: false
|
12
|
+
|
13
|
+
# Restore Rubocop default
|
14
|
+
Layout/DotPosition:
|
15
|
+
EnforcedStyle: leading
|
16
|
+
|
17
|
+
# The inbuilt style does not accommodate for:
|
18
|
+
#
|
19
|
+
# var = begin
|
20
|
+
# something
|
21
|
+
# rescue
|
22
|
+
# something
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# without indenting the entire block and looking dumb.
|
26
|
+
|
27
|
+
Layout/RescueEnsureAlignment:
|
28
|
+
Enabled: false
|
29
|
+
|
30
|
+
# In every instance where I've removed these extra lines, the result was less readable.
|
31
|
+
Layout/EmptyLinesAroundBlockBody:
|
32
|
+
Enabled: false
|
33
|
+
|
34
|
+
Layout/EmptyLinesAroundModuleBody:
|
35
|
+
Enabled: false
|
36
|
+
|
37
|
+
# Restore Rubocop default
|
38
|
+
Layout/MultilineMethodCallIndentation:
|
39
|
+
EnforcedStyle: aligned
|
40
|
+
|
41
|
+
Layout/CaseIndentation:
|
42
|
+
EnforcedStyle: end
|
43
|
+
|
44
|
+
Layout/EndAlignment:
|
45
|
+
EnforcedStyleAlignWith: variable
|
46
|
+
|
47
|
+
# Allow both %x() and backticks
|
48
|
+
Style/CommandLiteral:
|
49
|
+
Enabled: false
|
50
|
+
|
51
|
+
# $? and $: are more idiomatic than importing English and using $MAGIC_GLOBALS.
|
52
|
+
# If it's more esoteric, comment.
|
53
|
+
Style/SpecialGlobalVars:
|
54
|
+
Enabled: false
|
55
|
+
|
56
|
+
# Direct opposite of Prettier, allow either.
|
57
|
+
Style/TrailingCommaInHashLiteral:
|
58
|
+
Enabled: false
|
59
|
+
|
60
|
+
# Direct opposite of Prettier, allow either
|
61
|
+
Style/TrailingCommaInArrayLiteral:
|
62
|
+
Enabled: false
|
63
|
+
|
64
|
+
# Allow top-level mixins. Rightfully used in stubs/tasks
|
65
|
+
Style/MixinUsage:
|
66
|
+
Enabled: false
|
67
|
+
|
68
|
+
# Generated code/stubs as well as the rest of the code use what's
|
69
|
+
# appropriate according to the context
|
70
|
+
Style/PercentLiteralDelimiters:
|
71
|
+
Enabled: false
|
72
|
+
|
73
|
+
# "warn" and $stderr.puts have different connotations
|
74
|
+
Style/StderrPuts:
|
75
|
+
Enabled: false
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Mike A. Owens
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,184 @@
|
|
1
|
+
# BabySMS
|
2
|
+
|
3
|
+
BabySMS is a Ruby interface to a decent collection of SMS service providers. With it, you can
|
4
|
+
fire and forget SMS messages, or script entire two-way SMS conversations. All without tying
|
5
|
+
yourself to a single provider.
|
6
|
+
|
7
|
+
The interface stays the same, So you can comparison-shop for prices or reliability.
|
8
|
+
|
9
|
+
BabySMS allows multiple services to be used at once for failover, or random selection of a
|
10
|
+
provider for load-balancing (against throttling.)
|
11
|
+
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
Add these lines to your application's `Gemfile`:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
gem 'YourAPIProviderGem' # see below
|
19
|
+
gem 'babysms', '~> 0.5'
|
20
|
+
```
|
21
|
+
|
22
|
+
## Usage
|
23
|
+
|
24
|
+
### Basic
|
25
|
+
```ruby
|
26
|
+
BabySMS.adapter = BabySMS::Adapters::SomeAdapter.new(...)
|
27
|
+
|
28
|
+
BabySMS::Message.new(to: '+1 555-555-5555', contents: 'Hello, World!').deliver
|
29
|
+
```
|
30
|
+
|
31
|
+
If you need just basic outgoing functionality, that's all there is to it.
|
32
|
+
|
33
|
+
### Multiple Adapters for Reliability
|
34
|
+
```ruby
|
35
|
+
BabySMS.adapters = [
|
36
|
+
BabySMS::Adapters::HooliAdapter.new(...),
|
37
|
+
BabySMS::Adapters::PiedPiperAdapter.new(...)
|
38
|
+
]
|
39
|
+
|
40
|
+
BabySMS.strategy = :in_order
|
41
|
+
BabySMS::Message.new(...).deliver
|
42
|
+
```
|
43
|
+
|
44
|
+
This will try `HooliAdapter` first, but if it fails, `PiedPiperAdapter`. Changing the `strategy`
|
45
|
+
to `:random` will go through the list randomly, providing something akin to load-balancing.
|
46
|
+
|
47
|
+
## Sending Messages
|
48
|
+
|
49
|
+
`BabySMS::Message#deliver` can directly accept an `adapters` keyword argument that overrides the
|
50
|
+
global adapters list in `BabySMS.adapter[s]`.
|
51
|
+
|
52
|
+
BabySMS will attempt delivery with each adapter until one succeeds, or will raise
|
53
|
+
`BabySMS::FailedDelivery` once exhausted.
|
54
|
+
|
55
|
+
`#deliver` returns a `BabySMS::Receipt`. This allows you to inspect (and log) adapters that failed
|
56
|
+
along the way to delivery. Use `#exceptions?` to discover if something is failing, and
|
57
|
+
`#exceptions` to see the list of failed deliveries before the successful one.
|
58
|
+
|
59
|
+
## Receiving Messages
|
60
|
+
|
61
|
+
*Note: This functionality is currently being implemented.*
|
62
|
+
|
63
|
+
The API providers report incoming messages to you by accessing a publicly-available URL. This means
|
64
|
+
you have to have a web server running on the open internet to receive and process incoming messages
|
65
|
+
|
66
|
+
`BabySMS::WebApplication` is a Rack (Sinatra) application that does just that.
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
# config.ru
|
70
|
+
require 'babysms'
|
71
|
+
require 'babysms/web_application'
|
72
|
+
|
73
|
+
# Replace this with your service provider as listed below
|
74
|
+
BabySMS.adapter = BabySMS::TestAdapter.new
|
75
|
+
|
76
|
+
# Publicly-accessible URL
|
77
|
+
BabySMS.web_hook_root = "https://example.com/_babysms"
|
78
|
+
|
79
|
+
BabySMS.inbox = BabySMS::Inbox.new do
|
80
|
+
receive do |message|
|
81
|
+
puts "Incoming message from #{message.from}:\n"
|
82
|
+
puts " > #{message.contents}"
|
83
|
+
|
84
|
+
# Replies with same number/adapter, regardless of strategy
|
85
|
+
message.reply(contents: "Thank you for your message.")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
run BabySMS::WebApplication.new
|
90
|
+
```
|
91
|
+
|
92
|
+
You'll need to secure the web-application against the general public, allowing only your SMS
|
93
|
+
providers to access them.
|
94
|
+
|
95
|
+
*TODO: Explain exactly how to do that with Rack*
|
96
|
+
|
97
|
+
## Rails Integration
|
98
|
+
|
99
|
+
*TODO: Write about mounting BabySMS::WebApplication in routes.rb, sending messages via ActiveJob,
|
100
|
+
etc*
|
101
|
+
|
102
|
+
## Adapters
|
103
|
+
|
104
|
+
Each adapter uses the terminology of the service provider for configuration. E.g., `api_key`
|
105
|
+
vs. `auth_token`, etc. Other than `TestAdapter`, they all share a common `from` initialization
|
106
|
+
parameter to specify the number you're sending from.
|
107
|
+
|
108
|
+
You can configure multiple adapters (even instances of the same adapter class with different
|
109
|
+
configurations) by assigning `BabySMS.adapters` or using the `adapters:` argument to
|
110
|
+
`Message#deliver`.
|
111
|
+
|
112
|
+
Adapters require external gems to interface with their respective services. You will have to
|
113
|
+
require these gems yourself, and *before* `babysms`: BabySMS enables functionality by feature-
|
114
|
+
detecting these gems.
|
115
|
+
|
116
|
+
Putting it before `babysms` in your `Gemfile` should work.
|
117
|
+
|
118
|
+
|
119
|
+
### TestAdapter
|
120
|
+
|
121
|
+
The built-in testing adapter. It stores outgoing texts in its `outbox`. It prints colorful lines
|
122
|
+
to stderr when a message is sent for development purposes. It is the default adapter.
|
123
|
+
|
124
|
+
`verbose:` controls if it displays the outgoing SMS to stderr. `fails:` determines if deliveries
|
125
|
+
will fail.
|
126
|
+
|
127
|
+
```ruby
|
128
|
+
BabySMS::Adapters::TestAdapter.new(verbose: true, fails: false, from: '15555555555')
|
129
|
+
```
|
130
|
+
|
131
|
+
|
132
|
+
### BandwidthAdapter
|
133
|
+
|
134
|
+
Requires the `ruby-bandwidth` gem.
|
135
|
+
|
136
|
+
```ruby
|
137
|
+
BabySMS::Adapters::BandwidthAdapter.new(user_id:, api_token:, api_secret:, from:)
|
138
|
+
```
|
139
|
+
|
140
|
+
|
141
|
+
### TwilioAdapter
|
142
|
+
|
143
|
+
Requires the `twilio-ruby` gem.
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
BabySMS::Adapters::TwilioAdapter.new(account_sid:, auth_token:, from:)
|
147
|
+
```
|
148
|
+
|
149
|
+
|
150
|
+
### NexmoAdapter
|
151
|
+
|
152
|
+
Requires the `nexmo` gem.
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
BabySMS::Adapters::NexmoAdapter.new(api_key:, api_secret:, from:)
|
156
|
+
```
|
157
|
+
|
158
|
+
|
159
|
+
### PlivoAdapter
|
160
|
+
|
161
|
+
Requires the `plivo-ruby` gem.
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
BabySMS::Adapters::PlivoAdapter.new(auth_id:, auth_token:, from:)
|
165
|
+
```
|
166
|
+
|
167
|
+
|
168
|
+
### SignalwireAdapter
|
169
|
+
|
170
|
+
Requires the `signalwire` gem.
|
171
|
+
|
172
|
+
```ruby
|
173
|
+
# Notice the capitalization, to be consistent with their SDK naming
|
174
|
+
BabySMS::Adapters::SignalwireAdapter.new(from:, project:, token:, space_url:)
|
175
|
+
```
|
176
|
+
|
177
|
+
## Credits
|
178
|
+
|
179
|
+
BabySMS was written by Mike A. Owens <mike@filespanker.com>
|
180
|
+
|
181
|
+
## License
|
182
|
+
|
183
|
+
The gem is available as open source under the terms of the
|
184
|
+
[MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/babysms.gemspec
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "babysms/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "babysms"
|
7
|
+
spec.version = BabySMS::VERSION
|
8
|
+
spec.authors = ["Mike A. Owens"]
|
9
|
+
spec.email = ["mike@filespanker.com"]
|
10
|
+
|
11
|
+
spec.summary = "Simple Rails interface to a SMS APIs"
|
12
|
+
spec.description = "Functionality to send SMS via Twilio, Nexmo, etc."
|
13
|
+
spec.homepage = "https://github.com/mieko/babysms"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
17
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
18
|
+
if spec.respond_to?(:metadata)
|
19
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
20
|
+
spec.metadata["source_code_uri"] = "https://github.com/mieko/babysms"
|
21
|
+
spec.metadata["changelog_uri"] = "https://github.com/mieko/babysms/CHANGELOG.md"
|
22
|
+
else
|
23
|
+
raise "RubyGems 2.0 or newer is required to protect against " \
|
24
|
+
"public gem pushes."
|
25
|
+
end
|
26
|
+
|
27
|
+
# Specify which files should be added to the gem when it is released.
|
28
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
29
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
30
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
31
|
+
end
|
32
|
+
|
33
|
+
spec.bindir = "exe"
|
34
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
35
|
+
spec.require_paths = ["lib"]
|
36
|
+
|
37
|
+
spec.add_dependency "activesupport", ">= 5.2.1"
|
38
|
+
spec.add_dependency "rainbow", ">= 3.0"
|
39
|
+
spec.add_dependency "phony", ">= 0.14"
|
40
|
+
spec.add_dependency "sinatra", "~> 2.0"
|
41
|
+
|
42
|
+
spec.add_development_dependency "bundler", ">= 1.17"
|
43
|
+
spec.add_development_dependency "rake", ">= 10.0"
|
44
|
+
spec.add_development_dependency "rspec", ">= 3.0"
|
45
|
+
|
46
|
+
spec.add_development_dependency "rack-test", "~> 1.1.0"
|
47
|
+
spec.add_development_dependency "twilio-ruby", ">= 5.18.0"
|
48
|
+
spec.add_development_dependency "nexmo", ">= 5.5.0"
|
49
|
+
spec.add_development_dependency "ruby-bandwidth", ">= 1.0.20"
|
50
|
+
spec.add_development_dependency "plivo", ">= 4.1.6"
|
51
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "babysms"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/config.ru
ADDED
data/lib/babysms.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'babysms/version'
|
2
|
+
require 'babysms/adapter'
|
3
|
+
require 'babysms/receipt'
|
4
|
+
require 'babysms/errors'
|
5
|
+
require 'babysms/mail_man'
|
6
|
+
require 'babysms/message'
|
7
|
+
|
8
|
+
require 'babysms/adapters/test_adapter'
|
9
|
+
require 'babysms/adapters/bandwidth_adapter' if Object.const_defined?(:Bandwidth)
|
10
|
+
require 'babysms/adapters/nexmo_adapter' if Object.const_defined?(:Nexmo)
|
11
|
+
require 'babysms/adapters/plivo_adapter' if Object.const_defined?(:Plivo)
|
12
|
+
require 'babysms/adapters/twilio_adapter' if Object.const_defined?(:Twilio)
|
13
|
+
require 'babysms/adapters/signalwire_adapter' if Object.const_defined?(:Signalwire)
|
14
|
+
|
15
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
16
|
+
require 'phony'
|
17
|
+
|
18
|
+
module BabySMS
|
19
|
+
mattr_accessor :web_hook_root, default: "http://example.com/web_hooks/"
|
20
|
+
mattr_accessor :strategy, default: :in_order
|
21
|
+
mattr_accessor :adapters, default: [BabySMS::Adapters::TestAdapter.new(verbose: true)]
|
22
|
+
|
23
|
+
# Shorthand to set a list of one adapter in the simple case
|
24
|
+
def self.adapter=(adapter)
|
25
|
+
self.adapters = [adapter]
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.adapter
|
29
|
+
fail "can't use #adapters= with multiple adapters" unless adapters.size == 1
|
30
|
+
adapters.first
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.available_adapters(test: false, cache: true)
|
34
|
+
# Allow cache-busting
|
35
|
+
@found_adapters = nil if !cache
|
36
|
+
|
37
|
+
@found_adapters ||= begin
|
38
|
+
BabySMS::Adapters.constants.map do |constant|
|
39
|
+
cls = BabySMS::Adapters.const_get(constant)
|
40
|
+
if cls.is_a?(Class) && cls.ancestors.include?(BabySMS::Adapter)
|
41
|
+
cls
|
42
|
+
end
|
43
|
+
end.compact
|
44
|
+
end
|
45
|
+
|
46
|
+
if test
|
47
|
+
@found_adapters
|
48
|
+
else
|
49
|
+
@found_adapters - [BabySMS::Adapters::TestAdapter]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module BabySMS
|
2
|
+
class Adapter
|
3
|
+
attr_reader :from
|
4
|
+
attr_reader :client
|
5
|
+
|
6
|
+
def initialize(from:)
|
7
|
+
@from = Phony.normalize(from)
|
8
|
+
end
|
9
|
+
|
10
|
+
# Locates an adapter instance associated with a phone number
|
11
|
+
def self.for_number(number, pool: BabySMS.adapters)
|
12
|
+
number = Phony.normalize(number)
|
13
|
+
pool.find { |adapter| adapter.from == number }
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.for_adapter_id(adapter_id, pool: BabySMS.adapters)
|
17
|
+
pool.find { |adapter| adapter.adapter_id == adapter_id }
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.adapter_name
|
21
|
+
bare_name = name.split('::').last
|
22
|
+
bare_name.gsub(/Adapter\z/, '').downcase
|
23
|
+
end
|
24
|
+
|
25
|
+
# e.g., "twilio"
|
26
|
+
def adapter_name
|
27
|
+
self.class.adapter_name
|
28
|
+
end
|
29
|
+
|
30
|
+
# e.g., "15555555555@twilio"
|
31
|
+
def adapter_id
|
32
|
+
"#{from}@#{adapter_name}"
|
33
|
+
end
|
34
|
+
|
35
|
+
# if the adapter supports web hooks, it'll have a nested class
|
36
|
+
# called "WebHook". This returns the class
|
37
|
+
def web_hook_class
|
38
|
+
self.class.const_get(:WebHook)
|
39
|
+
end
|
40
|
+
|
41
|
+
def web_hook?
|
42
|
+
!!web_hook_class
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns an instance of the web hook handler, if it exists
|
46
|
+
def web_hook
|
47
|
+
unless instance_variable_defined?(:@web_hook)
|
48
|
+
@web_hook = if (cls = web_hook_class)
|
49
|
+
cls.new(self)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
@web_hook
|
54
|
+
end
|
55
|
+
|
56
|
+
protected
|
57
|
+
|
58
|
+
attr_writer :client
|
59
|
+
end
|
60
|
+
end
|