action_texter 0.1.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.
- data/.gitignore +18 -0
- data/Gemfile +7 -0
- data/LICENSE +22 -0
- data/README.md +29 -0
- data/Rakefile +3 -0
- data/action_texter.gemspec +19 -0
- data/lib/action_texter.rb +11 -0
- data/lib/action_texter/client.rb +37 -0
- data/lib/action_texter/message.rb +38 -0
- data/lib/action_texter/nexmo.rb +89 -0
- data/lib/action_texter/response.rb +41 -0
- data/lib/action_texter/test.rb +52 -0
- data/lib/action_texter/version.rb +6 -0
- metadata +58 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Watu
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# ActionTexter
|
2
|
+
|
3
|
+
Generic interface to send SMSs with Ruby.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'action_texter'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install action_texter
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# Copyright © 2013, Watu
|
3
|
+
|
4
|
+
require File.expand_path('../lib/action_texter/version', __FILE__)
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.authors = ["J. Pablo Fernández"]
|
8
|
+
gem.email = ["info@watuhq.com"]
|
9
|
+
gem.description = %q{Generic interface to send SMS with Ruby}
|
10
|
+
gem.summary = %q{Generic interface to send SMS with Ruby}
|
11
|
+
gem.homepage = ""
|
12
|
+
|
13
|
+
gem.files = `git ls-files`.split($\)
|
14
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
15
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
16
|
+
gem.name = "action_texter"
|
17
|
+
gem.require_paths = ["lib"]
|
18
|
+
gem.version = ActionTexter::VERSION
|
19
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# Copyright © 2012, 2013, Watu
|
3
|
+
|
4
|
+
require "action_texter/version"
|
5
|
+
require "action_texter/client"
|
6
|
+
require "action_texter/message"
|
7
|
+
require "action_texter/response"
|
8
|
+
require "action_texter/response"
|
9
|
+
require "action_texter/test"
|
10
|
+
require "action_texter/nexmo"
|
11
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# Copyright © 2012, 2013, Watu
|
3
|
+
|
4
|
+
# Parent class for all SMS clients.
|
5
|
+
#
|
6
|
+
# @abstract
|
7
|
+
class ActionTexter::Client
|
8
|
+
def self.default
|
9
|
+
@default
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.default=(client)
|
13
|
+
@default = client
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.setup(provider, *attrs)
|
17
|
+
provider_client =
|
18
|
+
begin
|
19
|
+
ActionTexter.const_get(provider + "Client")
|
20
|
+
rescue NameError
|
21
|
+
raise "Provider #{provider} doesn't exist."
|
22
|
+
end
|
23
|
+
self.default = provider_client.new(*attrs)
|
24
|
+
end
|
25
|
+
|
26
|
+
# Deliver a message
|
27
|
+
# @param [Message] message message to be delivered
|
28
|
+
# @returns [Response] a response
|
29
|
+
def deliver(message)
|
30
|
+
raise NotImplementedError.new("should be implemented by subclasses")
|
31
|
+
end
|
32
|
+
|
33
|
+
# @private
|
34
|
+
def to_s
|
35
|
+
"#<#{self.class.name}:#{object_id}>"
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# Copyright © 2012, 2013, Watu
|
3
|
+
|
4
|
+
# Representation of a message
|
5
|
+
#
|
6
|
+
# TODO: Implement these fields that Nexmo can use: :status_report_req, :network_code, :vcard, :vcal, :ttl
|
7
|
+
#
|
8
|
+
# @!attribute from
|
9
|
+
# @return [String] name or phone number of the author of the message.
|
10
|
+
# @!attribute to
|
11
|
+
# @return [String] phone number of the author of the message.
|
12
|
+
# @!attribute text
|
13
|
+
# @return [String] actual message to send.
|
14
|
+
# @!attribute reference
|
15
|
+
# @return [String] a reference that can be used later on to track responses. Implemented for: Nexmo.
|
16
|
+
class ActionTexter::Message
|
17
|
+
attr_accessor :from, :to, :text, :reference
|
18
|
+
|
19
|
+
def initialize(message = {})
|
20
|
+
self.from = message[:from] || raise("A message must contain from")
|
21
|
+
self.to = message[:to] || raise("A message must contain to")
|
22
|
+
self.text = message[:text] || raise("A message must contain text")
|
23
|
+
self.reference = message[:reference]
|
24
|
+
end
|
25
|
+
|
26
|
+
def deliver(client = nil)
|
27
|
+
client ||= ActionTexter::Client.default
|
28
|
+
if client.nil?
|
29
|
+
raise "To deliver a message you need to specify a client by parameter to deliver or by ActionTexter::Client.dafault="
|
30
|
+
end
|
31
|
+
client.deliver(self)
|
32
|
+
end
|
33
|
+
|
34
|
+
# @private
|
35
|
+
def to_s
|
36
|
+
"#<#{self.class.name}:#{from}:#{to}:#{text}>"
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# Copyright © 2012, 2013, Watu
|
3
|
+
|
4
|
+
require "bigdecimal"
|
5
|
+
require "net/http"
|
6
|
+
require "net/https"
|
7
|
+
require "json"
|
8
|
+
require "uri"
|
9
|
+
|
10
|
+
# Nexmo response
|
11
|
+
class ActionTexter::NexmoResponse < ActionTexter::Response
|
12
|
+
SUCCESS_RESPONSE_CODE = "0"
|
13
|
+
|
14
|
+
# TODO: Some of these should be moved to Response if they are common enough.
|
15
|
+
attr_reader :original, :parts_count, :parts, :cost, :remaining_balance, :reference
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def process_response(raw)
|
20
|
+
@success = true
|
21
|
+
@original = JSON.parse(raw)
|
22
|
+
@parts_count = @original["message-count"].to_i
|
23
|
+
@cost = BigDecimal.new("0")
|
24
|
+
@reference = @original["messages"].first["client-ref"] # They should all be the same, we only record it the first time.
|
25
|
+
@remaining_balance = @original["messages"].last["remaining-balance"] # I hope the last one is the lowest one, the cost of a single message shouldn't make that big of a difference anyway.
|
26
|
+
@parts = []
|
27
|
+
error_messages = []
|
28
|
+
@original["messages"].each do |raw_part|
|
29
|
+
if @success # Update the contents of success to status of this part unless @succes is already failed.
|
30
|
+
@success = raw_part["status"] == SUCCESS_RESPONSE_CODE
|
31
|
+
end
|
32
|
+
part = {:id => raw_part["message-id"],
|
33
|
+
:to => raw_part["to"],
|
34
|
+
:success => raw_part["status"] == SUCCESS_RESPONSE_CODE}
|
35
|
+
part[:reference] = raw_part["client-ref"] if raw_part.has_key? "client-ref"
|
36
|
+
if raw_part.has_key? "message-price"
|
37
|
+
part[:cost] = raw_part["message-price"]
|
38
|
+
@cost += BigDecimal.new(raw_part["message-price"])
|
39
|
+
end
|
40
|
+
if raw_part.has_key? "remaining-balance"
|
41
|
+
part[:remaining_balance] = BigDecimal.new(raw_part["remaining-balance"])
|
42
|
+
end
|
43
|
+
if raw_part.has_key? "error-text"
|
44
|
+
part[:error_message] = raw_part["error-text"]
|
45
|
+
error_messages << part[:error_message]
|
46
|
+
end
|
47
|
+
@parts << part
|
48
|
+
end
|
49
|
+
if error_messages.any?
|
50
|
+
@error_message = error_messages.uniq.join(", ")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Implementation of client for Nexmo: http://nexmo.com
|
56
|
+
class ActionTexter::NexmoClient < ActionTexter::Client
|
57
|
+
attr_accessor :key, :secret
|
58
|
+
|
59
|
+
# Create a new Nexmo client with key and secret.
|
60
|
+
#
|
61
|
+
# @param [String] key key as specified by Nexmo for authenticating.
|
62
|
+
# @param [String] secret secret as specified by Nexmo for authenticating.
|
63
|
+
def initialize(key, secret)
|
64
|
+
super()
|
65
|
+
self.key = key
|
66
|
+
self.secret = secret
|
67
|
+
end
|
68
|
+
|
69
|
+
def deliver(message)
|
70
|
+
client = Net::HTTP.new("rest.nexmo.com", 443)
|
71
|
+
client.use_ssl = true
|
72
|
+
response = client.post(
|
73
|
+
"/sms/json",
|
74
|
+
URI.encode_www_form("username" => @key,
|
75
|
+
"password" => @secret,
|
76
|
+
"from" => message.from,
|
77
|
+
"to" => message.to,
|
78
|
+
"text" => message.text,
|
79
|
+
"client-ref" => message.reference),
|
80
|
+
{"Content-Type" => "application/x-www-form-urlencoded"})
|
81
|
+
|
82
|
+
return ActionTexter::NexmoResponse.new(response.body)
|
83
|
+
end
|
84
|
+
|
85
|
+
# @private
|
86
|
+
def to_s
|
87
|
+
"#<#{self.class.name}:#{key}>"
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# Copyright © 2012, 2013, Watu
|
3
|
+
|
4
|
+
# A response as sent from the provider.
|
5
|
+
#
|
6
|
+
# @abstract
|
7
|
+
# @!attribute [r] raw
|
8
|
+
# @return [String] the raw response as returned by the provider
|
9
|
+
# @!attribute success
|
10
|
+
# @return [Boolean] weather sending the message succeeded or not. See #success? and #failed?
|
11
|
+
# @!attribute error_message
|
12
|
+
# @return [String] a descriptive message of the error when an error happened.
|
13
|
+
class ActionTexter::Response
|
14
|
+
attr_reader :raw, :success, :error_message
|
15
|
+
|
16
|
+
def initialize(raw)
|
17
|
+
@raw = raw
|
18
|
+
process_response(raw)
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Boolean] true when sending the message succeeded, false otherwise.
|
22
|
+
def success?
|
23
|
+
!!@success
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [Boolean] false when sending the message failed, true otherwise.
|
27
|
+
def failed?
|
28
|
+
!@success
|
29
|
+
end
|
30
|
+
|
31
|
+
# @private
|
32
|
+
def to_s
|
33
|
+
"#<#{self.class.name}:#{object_id}:#{@success ? "success" : "fail"}>"
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def process_response(raw)
|
39
|
+
raise NotImplementedError.new("should be implemented by subclasses")
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
# Copyright © 2012, 2013, Watu
|
3
|
+
|
4
|
+
require "bigdecimal"
|
5
|
+
|
6
|
+
# Responses sent by #TestClient
|
7
|
+
class ActionTexter::TestResponse < ActionTexter::Response
|
8
|
+
attr_reader :parts_count, :parts, :cost, :remaining_balance, :reference, :error
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def process_response(raw)
|
13
|
+
cost_per_message = BigDecimal("0.058")
|
14
|
+
@success = true
|
15
|
+
@original = raw
|
16
|
+
@reference = @original.reference if !@original.reference.nil?
|
17
|
+
@remaining_balance = BigDecimal.new("15.10")
|
18
|
+
@cost = BigDecimal.new("0")
|
19
|
+
@parts = []
|
20
|
+
(@original.text.length.to_f / 140).ceil.times do
|
21
|
+
@remaining_balance = @remaining_balance - cost_per_message
|
22
|
+
part = {:id => "test-response-#{Time.now.to_i}",
|
23
|
+
:to => @original.to,
|
24
|
+
:remaining_balance => @remaining_balance,
|
25
|
+
:cost => cost_per_message,
|
26
|
+
:success => true}
|
27
|
+
part[:reference] = @original.reference if !@original.reference.nil?
|
28
|
+
@cost += part[:cost]
|
29
|
+
@parts << part
|
30
|
+
end
|
31
|
+
@parts_count = @parts.count
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# A client that doesn't send any message but instead stores them on an array.
|
36
|
+
class ActionTexter::TestClient < ActionTexter::Client
|
37
|
+
def initialize
|
38
|
+
@@deliveries = []
|
39
|
+
end
|
40
|
+
|
41
|
+
def deliver(message)
|
42
|
+
@@deliveries << message
|
43
|
+
ActionTexter::TestResponse.new(message)
|
44
|
+
end
|
45
|
+
|
46
|
+
# All the delivered messages so far.
|
47
|
+
#
|
48
|
+
# @return [Array<Message>] delivered messages.
|
49
|
+
def deliveries
|
50
|
+
@@deliveries
|
51
|
+
end
|
52
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: action_texter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- J. Pablo Fernández
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-04-05 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Generic interface to send SMS with Ruby
|
15
|
+
email:
|
16
|
+
- info@watuhq.com
|
17
|
+
executables: []
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- .gitignore
|
22
|
+
- Gemfile
|
23
|
+
- LICENSE
|
24
|
+
- README.md
|
25
|
+
- Rakefile
|
26
|
+
- action_texter.gemspec
|
27
|
+
- lib/action_texter.rb
|
28
|
+
- lib/action_texter/client.rb
|
29
|
+
- lib/action_texter/message.rb
|
30
|
+
- lib/action_texter/nexmo.rb
|
31
|
+
- lib/action_texter/response.rb
|
32
|
+
- lib/action_texter/test.rb
|
33
|
+
- lib/action_texter/version.rb
|
34
|
+
homepage: ''
|
35
|
+
licenses: []
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
require_paths:
|
39
|
+
- lib
|
40
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
47
|
+
none: false
|
48
|
+
requirements:
|
49
|
+
- - ! '>='
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
requirements: []
|
53
|
+
rubyforge_project:
|
54
|
+
rubygems_version: 1.8.24
|
55
|
+
signing_key:
|
56
|
+
specification_version: 3
|
57
|
+
summary: Generic interface to send SMS with Ruby
|
58
|
+
test_files: []
|