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.
@@ -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
@@ -0,0 +1,11 @@
1
+ Authoritative changelog in README.md.
2
+
3
+ ## Version next
4
+ - Interceptors and observers
5
+ - Support for Twilio
6
+ - Fixed Nexmo number formatting
7
+
8
+ ## Version 0.1.0 (Apr 5, 2013)
9
+ - Initial version
10
+ - Support for Nexmo
11
+ - Support for testing
data/Gemfile CHANGED
@@ -1,5 +1,5 @@
1
1
  # encoding: UTF-8
2
- # Copyright © 2012, 2013, Watu
2
+ # Copyright © 2012, 2013, 2014, 2015, Carousel Apps
3
3
 
4
4
  source "https://rubygems.org"
5
5
 
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 Watu
1
+ Copyright (c) 2012, 2013, 2014, 2015, Carousel Apps
2
2
 
3
3
  MIT License
4
4
 
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 'action_texter'
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
 
@@ -1,19 +1,24 @@
1
1
  # encoding: UTF-8
2
- # Copyright © 2013, Watu
2
+ # Copyright © 2013, 2014, 2015, Carousel Apps
3
3
 
4
- require File.expand_path('../lib/action_texter/version', __FILE__)
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 |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 = ""
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
- 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
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
@@ -1,6 +1,7 @@
1
1
  # encoding: UTF-8
2
- # Copyright © 2012, 2013, Watu
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
@@ -1,5 +1,5 @@
1
1
  # encoding: UTF-8
2
- # Copyright © 2012, 2013, Watu
2
+ # Copyright © 2012, 2013, 2014, 2015, Carousel Apps
3
3
 
4
4
  # Parent class for all SMS clients.
5
5
  #
@@ -1,5 +1,5 @@
1
1
  # encoding: UTF-8
2
- # Copyright © 2012, 2013, Watu
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
- client.deliver(self)
34
+
35
+ response = client.deliver(message)
36
+ ActionTexter.inform_observers(message, response)
37
+ response
32
38
  end
33
39
 
34
40
  # @private
@@ -1,5 +1,5 @@
1
1
  # encoding: UTF-8
2
- # Copyright © 2012, 2013, Watu
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" => message.from,
77
- "to" => message.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"})
@@ -1,5 +1,5 @@
1
1
  # encoding: UTF-8
2
- # Copyright © 2012, 2013, Watu
2
+ # Copyright © 2012, 2013, 2014, 2015, Carousel Apps
3
3
 
4
4
  # A response as sent from the provider.
5
5
  #
@@ -1,5 +1,5 @@
1
1
  # encoding: UTF-8
2
- # Copyright © 2012, 2013, Watu
2
+ # Copyright © 2012, 2013, 2014, 2015, Carousel Apps
3
3
 
4
4
  require "bigdecimal"
5
5
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  # encoding: UTF-8
2
- # Copyright © 2012, 2013, Watu
2
+ # Copyright © 2012, 2013, 2014, 2015, Carousel Apps
3
3
 
4
4
  module ActionTexter
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.0"
6
6
  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.1.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: 2013-04-05 00:00:00.000000000 Z
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@watuhq.com
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: '0'
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: 1.8.24
57
+ rubygems_version: 2.4.3
55
58
  signing_key:
56
- specification_version: 3
59
+ specification_version: 4
57
60
  summary: Generic interface to send SMS with Ruby
58
61
  test_files: []