action_texter 0.1.0 → 0.2.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 +11 -0
- data/Gemfile +1 -1
- data/LICENSE +1 -1
- data/README.md +62 -2
- data/action_texter.gemspec +19 -14
- data/lib/action_texter.rb +3 -1
- data/lib/action_texter/action_texter.rb +75 -0
- data/lib/action_texter/client.rb +1 -1
- data/lib/action_texter/message.rb +8 -2
- data/lib/action_texter/nexmo.rb +10 -3
- data/lib/action_texter/response.rb +1 -1
- data/lib/action_texter/test.rb +1 -1
- data/lib/action_texter/twilio.rb +72 -0
- data/lib/action_texter/version.rb +2 -2
- metadata +17 -14
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3098b20b629f56af9f732f0579ff97f8ff9339a6
|
4
|
+
data.tar.gz: 1e15e8b067a5891ff348a75387e4d74ff44e788d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8f041a3e6a040a488122a937a7adb8a89627332163a117db88d0ac3999cdef6731903255524fb5a52b01825d6a369f2761e9cf92d4b429e97e14da17b0f9994a
|
7
|
+
data.tar.gz: a8d3c4e9228565d8c9a9046bb3032b2bfec1f7c5f6dc0e230f67f872ec317488689f7bae6834c6806aff7ea85d089bbf50e00f1532cbc2206aa79d8578485ec7
|
data/CHANGELOG.md
ADDED
data/Gemfile
CHANGED
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -6,7 +6,7 @@ Generic interface to send SMSs with Ruby.
|
|
6
6
|
|
7
7
|
Add this line to your application's Gemfile:
|
8
8
|
|
9
|
-
gem
|
9
|
+
gem "action_texter"
|
10
10
|
|
11
11
|
And then execute:
|
12
12
|
|
@@ -18,7 +18,67 @@ Or install it yourself as:
|
|
18
18
|
|
19
19
|
## Usage
|
20
20
|
|
21
|
-
TODO: Write usage instructions here
|
21
|
+
TODO: Write better usage instructions here
|
22
|
+
|
23
|
+
1. Configure which client to use in your initializers
|
24
|
+
|
25
|
+
```
|
26
|
+
if Rails.env.test? # || Rails.env.development?
|
27
|
+
ActionTexter::Client.setup("Test")
|
28
|
+
else
|
29
|
+
ActionTexter::Client.setup("Nexmo", "key", "secret")
|
30
|
+
end
|
31
|
+
```
|
32
|
+
|
33
|
+
1. Instantiate an ActionTexter::Message
|
34
|
+
|
35
|
+
```
|
36
|
+
message = ActionTexter::Message.new(from: "phone number",
|
37
|
+
to: "phone number",
|
38
|
+
text: "message contents",
|
39
|
+
reference: "optional reference")
|
40
|
+
```
|
41
|
+
|
42
|
+
1. Deliver the message
|
43
|
+
|
44
|
+
```
|
45
|
+
message.deliver
|
46
|
+
```
|
47
|
+
|
48
|
+
1. **DO NOT** Instantiate the client and call deliver passing in the message. This breaks the beautiful abstraction
|
49
|
+
of the client selector, and it also means observers and interceptors don't work
|
50
|
+
|
51
|
+
1. Set observers or interceptors
|
52
|
+
|
53
|
+
```
|
54
|
+
class MyInterceptor
|
55
|
+
def delivering_sms(message)
|
56
|
+
# Do something with the message. Modify its contents and return the modified object.
|
57
|
+
# Or return nil / false to cancel the sending
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class MyObserver
|
62
|
+
def delivered_sms(message, response)
|
63
|
+
# Log, or do whatever you want with the fact that an SMS has been sent, and that's the response you got
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
ActionTexter.register_interceptor(MyInterceptor.new)
|
68
|
+
ActionTexter.register_observer(MyObserver.new)
|
69
|
+
```
|
70
|
+
|
71
|
+
## Changelog
|
72
|
+
|
73
|
+
### Version next
|
74
|
+
- Interceptors and observers
|
75
|
+
- Support for Twilio
|
76
|
+
- Fixed Nexmo number formatting
|
77
|
+
|
78
|
+
### Version 0.1.0 (Apr 5, 2013)
|
79
|
+
- Initial version
|
80
|
+
- Support for Nexmo
|
81
|
+
- Support for testing
|
22
82
|
|
23
83
|
## Contributing
|
24
84
|
|
data/action_texter.gemspec
CHANGED
@@ -1,19 +1,24 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
-
# Copyright © 2013,
|
2
|
+
# Copyright © 2013, 2014, 2015, Carousel Apps
|
3
3
|
|
4
|
-
|
4
|
+
lib = File.expand_path("../lib", __FILE__)
|
5
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
6
|
+
require "action_texter/version"
|
5
7
|
|
6
|
-
Gem::Specification.new do |
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
Gem::Specification.new do |spec|
|
9
|
+
spec.name = "action_texter"
|
10
|
+
spec.version = ActionTexter::VERSION
|
11
|
+
spec.authors = ["J. Pablo Fernández", "Daniel Magliola"]
|
12
|
+
spec.email = ["info@carouselapps.com"]
|
13
|
+
spec.homepage = "http://carouselapps.com/action_texter"
|
14
|
+
spec.description = %q{Generic interface to send SMS with Ruby}
|
15
|
+
spec.summary = %q{Generic interface to send SMS with Ruby}
|
16
|
+
spec.license = "MIT"
|
12
17
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
18
|
+
spec.files = `git ls-files`.split($/)
|
19
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.required_ruby_version = ">= 1.9.3"
|
19
24
|
end
|
data/lib/action_texter.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
-
# Copyright © 2012, 2013,
|
2
|
+
# Copyright © 2012, 2013, 2014, 2015, Carousel Apps
|
3
3
|
|
4
|
+
require "action_texter/action_texter"
|
4
5
|
require "action_texter/version"
|
5
6
|
require "action_texter/client"
|
6
7
|
require "action_texter/message"
|
@@ -8,4 +9,5 @@ require "action_texter/response"
|
|
8
9
|
require "action_texter/response"
|
9
10
|
require "action_texter/test"
|
10
11
|
require "action_texter/nexmo"
|
12
|
+
require "action_texter/twilio"
|
11
13
|
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# Copyright © 2014, 2015, Carousel Apps
|
3
|
+
|
4
|
+
module ActionTexter
|
5
|
+
@@delivery_observers = []
|
6
|
+
@@delivery_interceptors = []
|
7
|
+
|
8
|
+
# You can register an object to be informed of every SMS that is sent, after it is sent
|
9
|
+
# Your object needs to respond to a single method #delivered_sms(message, response)
|
10
|
+
# message will be of type ActionTexter::Message
|
11
|
+
# response will be of type ActionTexter::Response
|
12
|
+
#
|
13
|
+
# @param observer [Object] with a #delivered_sms method that will be called passing in the ActionTexter::Message
|
14
|
+
# and ActionTexter::Response
|
15
|
+
# @returns the observer added
|
16
|
+
def self.register_observer(observer)
|
17
|
+
unless @@delivery_observers.include?(observer)
|
18
|
+
@@delivery_observers << observer
|
19
|
+
end
|
20
|
+
observer
|
21
|
+
end
|
22
|
+
|
23
|
+
# Unregister the given observer
|
24
|
+
#
|
25
|
+
# @param observer [Object] to unregister
|
26
|
+
# @returns deleted observer
|
27
|
+
def self.unregister_observer(observer)
|
28
|
+
@@delivery_observers.delete(observer)
|
29
|
+
end
|
30
|
+
|
31
|
+
# You can register an object to be given every Message object that will be sent, before it is sent.
|
32
|
+
# This allows you to modify the contents of the message, or even stop it by returning false or nil.
|
33
|
+
#
|
34
|
+
# Your object needs to respond to a single method #delivering_sms(message)
|
35
|
+
# It must return the modified object to be sent instead, or nil
|
36
|
+
#
|
37
|
+
# @param interceptor [Object] with a #delivering_sms method that will be called passing in the ActionTexter::Message
|
38
|
+
# @returns the interceptor added
|
39
|
+
def self.register_interceptor(interceptor)
|
40
|
+
unless @@delivery_interceptors.include?(interceptor)
|
41
|
+
@@delivery_interceptors << interceptor
|
42
|
+
end
|
43
|
+
interceptor
|
44
|
+
end
|
45
|
+
|
46
|
+
# Unregister the given interceptor
|
47
|
+
#
|
48
|
+
# @param interceptor [Object] to unregister
|
49
|
+
# @returns deleted interceptor
|
50
|
+
def self.unregister_interceptor(interceptor)
|
51
|
+
@@delivery_interceptors.delete(interceptor)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Inform all the observers about the SMS being sent
|
55
|
+
#
|
56
|
+
# @param message [ActionTexter::Message] that is being sent
|
57
|
+
# @returns the list of observers
|
58
|
+
def self.inform_observers(message, response)
|
59
|
+
@@delivery_observers.each { |observer| observer.delivered_sms(message, response) }
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
# Inform all the interceptors about the SMS being sent. Any interceptor can modify the message or cancel it
|
64
|
+
#
|
65
|
+
# @param message [ActionTexter::Message] that is being sent
|
66
|
+
# @returns the message that must be sent, returned by the last interceptor. This may be nil or false
|
67
|
+
def self.inform_interceptors(message)
|
68
|
+
@@delivery_interceptors.each do |interceptor|
|
69
|
+
message = interceptor.delivering_sms(message)
|
70
|
+
break if message.blank?
|
71
|
+
end
|
72
|
+
message
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
data/lib/action_texter/client.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
-
# Copyright © 2012, 2013,
|
2
|
+
# Copyright © 2012, 2013, 2014, 2015, Carousel Apps
|
3
3
|
|
4
4
|
# Representation of a message
|
5
5
|
#
|
@@ -24,11 +24,17 @@ class ActionTexter::Message
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def deliver(client = nil)
|
27
|
+
message = ActionTexter.inform_interceptors(self)
|
28
|
+
return nil if message.blank? # Do not send if one of the interceptors cancelled
|
29
|
+
|
27
30
|
client ||= ActionTexter::Client.default
|
28
31
|
if client.nil?
|
29
32
|
raise "To deliver a message you need to specify a client by parameter to deliver or by ActionTexter::Client.dafault="
|
30
33
|
end
|
31
|
-
|
34
|
+
|
35
|
+
response = client.deliver(message)
|
36
|
+
ActionTexter.inform_observers(message, response)
|
37
|
+
response
|
32
38
|
end
|
33
39
|
|
34
40
|
# @private
|
data/lib/action_texter/nexmo.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
-
# Copyright © 2012, 2013,
|
2
|
+
# Copyright © 2012, 2013, 2014, 2015, Carousel Apps
|
3
3
|
|
4
4
|
require "bigdecimal"
|
5
5
|
require "net/http"
|
@@ -69,12 +69,19 @@ class ActionTexter::NexmoClient < ActionTexter::Client
|
|
69
69
|
def deliver(message)
|
70
70
|
client = Net::HTTP.new("rest.nexmo.com", 443)
|
71
71
|
client.use_ssl = true
|
72
|
+
|
73
|
+
# Nexmo doesn't like phone numbers starting with a +
|
74
|
+
# Pattern only matches phones that are pristine phone numbers starting with a +, and leaves everything else alone
|
75
|
+
pattern = /^\+(\d+)$/
|
76
|
+
from = (message.from =~ pattern ? message.from.gsub(pattern, '\1') : message.from )
|
77
|
+
to = (message.to =~ pattern ? message.to.gsub(pattern, '\1') : message.to )
|
78
|
+
|
72
79
|
response = client.post(
|
73
80
|
"/sms/json",
|
74
81
|
URI.encode_www_form("username" => @key,
|
75
82
|
"password" => @secret,
|
76
|
-
"from" =>
|
77
|
-
"to" =>
|
83
|
+
"from" => from,
|
84
|
+
"to" => to,
|
78
85
|
"text" => message.text,
|
79
86
|
"client-ref" => message.reference),
|
80
87
|
{"Content-Type" => "application/x-www-form-urlencoded"})
|
data/lib/action_texter/test.rb
CHANGED
@@ -0,0 +1,72 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# Copyright © 2014, 2015, Carousel Apps
|
3
|
+
|
4
|
+
require "bigdecimal"
|
5
|
+
require "net/http"
|
6
|
+
require "net/https"
|
7
|
+
require "json"
|
8
|
+
require "uri"
|
9
|
+
|
10
|
+
# Twilio response
|
11
|
+
class ActionTexter::TwilioResponse < ActionTexter::Response
|
12
|
+
# TODO: Some of these should be moved to Response if they are common enough.
|
13
|
+
attr_reader :original, :parts_count, :cost, :reference
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def process_response(raw)
|
18
|
+
@original = JSON.parse(raw)
|
19
|
+
|
20
|
+
if @original["error_code"].blank? && @original["code"].blank?
|
21
|
+
@success = true
|
22
|
+
@reference = @original["sid"]
|
23
|
+
@parts_count = @original["num_segments"]
|
24
|
+
|
25
|
+
# The cost is nil because the message is, as of now, queued. To get the cost we need to GET @original["uri"]
|
26
|
+
# for message details, but that assumes that the message is sent by that time.
|
27
|
+
# The proper way, a callback, is way out of the scope of this gem.
|
28
|
+
@cost = nil
|
29
|
+
else
|
30
|
+
@success = false
|
31
|
+
|
32
|
+
# Responses take two shapes. Either they have a status like "queued", and they have an "error_code" field
|
33
|
+
# (which I've never seen filled), or they return a status like "400", with a "code" and a "message"
|
34
|
+
if @original.has_key?("code")
|
35
|
+
@error_message = @original["message"]
|
36
|
+
elsif @original.has_key?("error_message")
|
37
|
+
@error_message = @original["error_message"]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Implementation of client for Twilio: http://twilio.com
|
44
|
+
class ActionTexter::TwilioClient < ActionTexter::Client
|
45
|
+
attr_accessor :account_sid, :auth_token
|
46
|
+
|
47
|
+
# Create a new Twilio client with account sid and auth token
|
48
|
+
#
|
49
|
+
# @param [String] account_sid as specified by Twilio for authenticating.
|
50
|
+
# @param [String] auth_token as specified by Twilio for authenticating.
|
51
|
+
def initialize(account_sid, auth_token)
|
52
|
+
super()
|
53
|
+
self.account_sid = account_sid
|
54
|
+
self.auth_token = auth_token
|
55
|
+
end
|
56
|
+
|
57
|
+
def deliver(message)
|
58
|
+
http = Net::HTTP.new("api.twilio.com", 443)
|
59
|
+
http.use_ssl = true
|
60
|
+
request = Net::HTTP::Post.new("/2010-04-01/Accounts/#{@account_sid}/Messages.json")
|
61
|
+
request.basic_auth(@account_sid, @auth_token)
|
62
|
+
request.set_form_data({"From" => message.from, "To" => message.to, "Body" => message.text})
|
63
|
+
response = http.request(request)
|
64
|
+
|
65
|
+
return ActionTexter::TwilioResponse.new(response.body)
|
66
|
+
end
|
67
|
+
|
68
|
+
# @private
|
69
|
+
def to_s
|
70
|
+
"#<#{self.class.name}:#{key}>"
|
71
|
+
end
|
72
|
+
end
|
metadata
CHANGED
@@ -1,58 +1,61 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: action_texter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.2.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- J. Pablo Fernández
|
8
|
+
- Daniel Magliola
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2015-01-28 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Generic interface to send SMS with Ruby
|
15
15
|
email:
|
16
|
-
- info@
|
16
|
+
- info@carouselapps.com
|
17
17
|
executables: []
|
18
18
|
extensions: []
|
19
19
|
extra_rdoc_files: []
|
20
20
|
files:
|
21
|
-
- .gitignore
|
21
|
+
- ".gitignore"
|
22
|
+
- CHANGELOG.md
|
22
23
|
- Gemfile
|
23
24
|
- LICENSE
|
24
25
|
- README.md
|
25
26
|
- Rakefile
|
26
27
|
- action_texter.gemspec
|
27
28
|
- lib/action_texter.rb
|
29
|
+
- lib/action_texter/action_texter.rb
|
28
30
|
- lib/action_texter/client.rb
|
29
31
|
- lib/action_texter/message.rb
|
30
32
|
- lib/action_texter/nexmo.rb
|
31
33
|
- lib/action_texter/response.rb
|
32
34
|
- lib/action_texter/test.rb
|
35
|
+
- lib/action_texter/twilio.rb
|
33
36
|
- lib/action_texter/version.rb
|
34
|
-
homepage:
|
35
|
-
licenses:
|
37
|
+
homepage: http://carouselapps.com/action_texter
|
38
|
+
licenses:
|
39
|
+
- MIT
|
40
|
+
metadata: {}
|
36
41
|
post_install_message:
|
37
42
|
rdoc_options: []
|
38
43
|
require_paths:
|
39
44
|
- lib
|
40
45
|
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
46
|
requirements:
|
43
|
-
- -
|
47
|
+
- - ">="
|
44
48
|
- !ruby/object:Gem::Version
|
45
|
-
version:
|
49
|
+
version: 1.9.3
|
46
50
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
|
-
none: false
|
48
51
|
requirements:
|
49
|
-
- -
|
52
|
+
- - ">="
|
50
53
|
- !ruby/object:Gem::Version
|
51
54
|
version: '0'
|
52
55
|
requirements: []
|
53
56
|
rubyforge_project:
|
54
|
-
rubygems_version:
|
57
|
+
rubygems_version: 2.4.3
|
55
58
|
signing_key:
|
56
|
-
specification_version:
|
59
|
+
specification_version: 4
|
57
60
|
summary: Generic interface to send SMS with Ruby
|
58
61
|
test_files: []
|