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.
@@ -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: []