action_texter 0.1.0 → 0.2.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 +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: []
|