4info 1.3.4 → 2.0.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/README.markdown +10 -10
- data/VERSION +1 -1
- data/init.rb +1 -1
- data/lib/{templates → 4info_templates}/confirm.haml +0 -0
- data/lib/{templates → 4info_templates}/deliver.haml +0 -0
- data/lib/{templates → 4info_templates}/unblock.haml +0 -0
- data/lib/configuration.rb +3 -1
- data/lib/contactable.rb +45 -60
- data/lib/controller.rb +3 -3
- data/lib/gateway.rb +45 -0
- data/lib/gateway_4info.rb +114 -0
- data/lib/gateway_twilio.rb +45 -0
- data/lib/{four_info.rb → txter.rb} +6 -5
- data/test/gateway_4info_test.rb +75 -0
- data/test/gateway_twilio_test.rb +15 -0
- data/test/test_helper.rb +6 -5
- data/test/{four_info_contactable_test.rb → txter_contactable_test.rb} +51 -73
- data/test/{four_info_controller_test.rb → txter_controller_test.rb} +8 -8
- data/test/txter_module_test.rb +56 -0
- data/{4info.gemspec → txter.gemspec} +7 -7
- metadata +20 -16
- data/lib/4info.rb +0 -3
- data/lib/request.rb +0 -75
- data/lib/response.rb +0 -27
- data/test/four_info_module_test.rb +0 -56
data/README.markdown
CHANGED
@@ -8,16 +8,16 @@ If you're using 4info.com as your SMS gateway this gem will give you a painless
|
|
8
8
|
Setting Up Your Model
|
9
9
|
=====
|
10
10
|
|
11
|
-
Include
|
11
|
+
Include Txter::Contactable into your User class or whatever you're using to represent an entity with a phone number.
|
12
12
|
|
13
13
|
class User < ActiveRecord::Base
|
14
|
-
include
|
14
|
+
include Txter::Contactable
|
15
15
|
end
|
16
16
|
|
17
17
|
You can also specify which attributes you'd like to use instead of the defaults
|
18
18
|
|
19
19
|
class User < ActiveRecord::Base
|
20
|
-
include
|
20
|
+
include Txter::Contactable
|
21
21
|
|
22
22
|
sms_phone_number_column :mobile_number
|
23
23
|
sms_blocked_column :is_sms_blocked
|
@@ -31,15 +31,15 @@ You can also specify which attributes you'd like to use instead of the defaults
|
|
31
31
|
Turning the thing on
|
32
32
|
---
|
33
33
|
|
34
|
-
Because it can be expensive to send TXTs accidentally, it's required that you manually configure
|
34
|
+
Because it can be expensive to send TXTs accidentally, it's required that you manually configure Txter in your app. Put this line in config/environments/production.rb or anything that loads _only_ in your production environment:
|
35
35
|
|
36
|
-
|
36
|
+
Txter.mode = :live
|
37
37
|
|
38
38
|
Skipping this step (or adding any other value) will prevent TXTs from actually being sent.
|
39
39
|
|
40
40
|
You'll also want to configure your setup with your client_id and client_key. Put this in the same file as above or in a separate initializer if you wish:
|
41
41
|
|
42
|
-
|
42
|
+
Txter.configure do |config|
|
43
43
|
# these two are required:
|
44
44
|
# (replace them with your actual account info)
|
45
45
|
config.client_id = 12345
|
@@ -93,19 +93,19 @@ Receiving Messages From 4info.com
|
|
93
93
|
====
|
94
94
|
|
95
95
|
You can also receive data posted to you from 4info.com. This is how you'll receive messages and notices that users have been blocked.
|
96
|
-
All you need is to create a bare controller and include
|
96
|
+
All you need is to create a bare controller and include Txter::Controller into it. Then specify which Ruby class you're using as a contactable user model (likely User)
|
97
97
|
|
98
98
|
|
99
99
|
class SMSController < ApplicationController
|
100
|
-
include
|
100
|
+
include Txter::Controller
|
101
101
|
|
102
|
-
sms_contactable User # or whichever class you included
|
102
|
+
sms_contactable User # or whichever class you included Txter::Contactable into
|
103
103
|
end
|
104
104
|
|
105
105
|
And hook this up in your routes.rb file like so:
|
106
106
|
|
107
107
|
ActionController::Routing::Routes.draw do |map|
|
108
|
-
map.route '4info', :controller => '
|
108
|
+
map.route '4info', :controller => 'txter', :action => :index
|
109
109
|
end
|
110
110
|
|
111
111
|
Now just tell 4info.com to POST messages and block notices to you at:
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
2.0.0
|
data/init.rb
CHANGED
File without changes
|
File without changes
|
File without changes
|
data/lib/configuration.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
module
|
1
|
+
module Txter
|
2
2
|
class << self
|
3
3
|
def mode
|
4
4
|
@@mode ||= :test
|
@@ -26,7 +26,9 @@ module FourInfo
|
|
26
26
|
|
27
27
|
attr_accessor :client_id
|
28
28
|
attr_accessor :client_key
|
29
|
+
attr_accessor :gateway
|
29
30
|
attr_accessor :short_code
|
31
|
+
attr_accessor :default_from_phone_number
|
30
32
|
attr_accessor :proxy_address
|
31
33
|
attr_accessor :proxy_port
|
32
34
|
attr_accessor :proxy_username
|
data/lib/contactable.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
module
|
1
|
+
module Txter
|
2
2
|
module Contactable
|
3
3
|
|
4
4
|
Attributes = [ :sms_phone_number,
|
@@ -27,14 +27,14 @@ module FourInfo
|
|
27
27
|
# provide helper methods to access the right value
|
28
28
|
# no matter which column it's stored in.
|
29
29
|
#
|
30
|
-
# e.g.: @user.
|
30
|
+
# e.g.: @user.txter_sms_confirmation_code
|
31
31
|
# == @user.send(User.sms_confirmation_code_column)
|
32
32
|
model.class_eval "
|
33
|
-
def
|
33
|
+
def txter_#{attribute}
|
34
34
|
send self.class.#{attribute}_column
|
35
35
|
end
|
36
|
-
alias_method :
|
37
|
-
def
|
36
|
+
alias_method :txter_#{attribute}?, :txter_#{attribute}
|
37
|
+
def txter_#{attribute}=(value)
|
38
38
|
send self.class.#{attribute}_column.to_s+'=', value
|
39
39
|
end
|
40
40
|
"
|
@@ -47,7 +47,7 @@ module FourInfo
|
|
47
47
|
model.before_save :normalize_sms_phone_number
|
48
48
|
model.class_eval do
|
49
49
|
def normalize_sms_phone_number
|
50
|
-
self.
|
50
|
+
self.txter_sms_phone_number = Txter.numerize(txter_sms_phone_number)
|
51
51
|
end
|
52
52
|
end
|
53
53
|
end
|
@@ -62,28 +62,44 @@ module FourInfo
|
|
62
62
|
if msg.to_s.size > 160 && !allow_multiple
|
63
63
|
raise ArgumentError, "SMS Message is too long. Either specify that you want multiple messages or shorten the string."
|
64
64
|
end
|
65
|
-
return false if msg.to_s.strip.blank? ||
|
65
|
+
return false if msg.to_s.strip.blank? || txter_sms_blocked?
|
66
66
|
return false unless sms_confirmed?
|
67
67
|
|
68
68
|
# split into pieces that fit as individual messages.
|
69
69
|
msg.to_s.scan(/.{1,160}/m).map do |text|
|
70
|
-
|
70
|
+
if Txter.deliver(text, txter_sms_phone_number).success?
|
71
|
+
text.size
|
72
|
+
else
|
73
|
+
false
|
74
|
+
end
|
71
75
|
end
|
72
76
|
end
|
73
77
|
|
74
|
-
# Sends an SMS validation request
|
75
|
-
# If request succeeds the 4info-generated confirmation code is saved
|
76
|
-
# in the contactable record.
|
78
|
+
# Sends an SMS validation request through the gateway
|
77
79
|
def send_sms_confirmation!
|
78
|
-
return false if
|
80
|
+
return false if txter_sms_blocked?
|
79
81
|
return true if sms_confirmed?
|
80
|
-
return false if
|
82
|
+
return false if txter_sms_phone_number.blank?
|
83
|
+
|
84
|
+
confirmation_code = Txter.generate_confirmation_code
|
85
|
+
|
86
|
+
# Use this class' confirmation_message method if it
|
87
|
+
# exists, otherwise use the generic message
|
88
|
+
message = (self.class.respond_to?(:confirmation_message) ?
|
89
|
+
self.class :
|
90
|
+
Txter).confirmation_message(confirmation_code)
|
91
|
+
|
92
|
+
if message.to_s.size > 160
|
93
|
+
raise ArgumentError, "SMS Confirmation Message is too long. Limit it to 160 characters of unescaped text."
|
94
|
+
end
|
81
95
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
96
|
+
response = Txter.deliver(message, txter_sms_phone_number)
|
97
|
+
|
98
|
+
if response.success?
|
99
|
+
update_txter_sms_confirmation confirmation_code
|
100
|
+
else
|
101
|
+
false
|
102
|
+
end
|
87
103
|
end
|
88
104
|
|
89
105
|
|
@@ -91,11 +107,11 @@ module FourInfo
|
|
91
107
|
# If request succeeds, changes the contactable record's
|
92
108
|
# sms_blocked_column to false.
|
93
109
|
def unblock_sms!
|
94
|
-
return false unless
|
110
|
+
return false unless txter_sms_blocked?
|
95
111
|
|
96
|
-
response =
|
112
|
+
response = Txter.unblock(txter_sms_phone_number)
|
97
113
|
if response.success?
|
98
|
-
self.
|
114
|
+
self.txter_sms_blocked = 'false'
|
99
115
|
save
|
100
116
|
else
|
101
117
|
false
|
@@ -106,9 +122,9 @@ module FourInfo
|
|
106
122
|
# code. If they match then the current phone number is set
|
107
123
|
# as confirmed by the user.
|
108
124
|
def sms_confirm_with(code)
|
109
|
-
if
|
125
|
+
if txter_sms_confirmation_code.to_s.downcase == code.downcase
|
110
126
|
# save the phone number into the 'confirmed phone number' attribute
|
111
|
-
self.
|
127
|
+
self.txter_sms_confirmed_phone_number = txter_sms_phone_number
|
112
128
|
save
|
113
129
|
else
|
114
130
|
false
|
@@ -118,47 +134,16 @@ module FourInfo
|
|
118
134
|
# Returns true if the current phone number has been confirmed by
|
119
135
|
# the user for recieving TXT messages.
|
120
136
|
def sms_confirmed?
|
121
|
-
return false if
|
122
|
-
|
137
|
+
return false if txter_sms_confirmed_phone_number.blank?
|
138
|
+
txter_sms_confirmed_phone_number == txter_sms_phone_number
|
123
139
|
end
|
124
140
|
|
125
141
|
protected
|
126
|
-
def confirm_four_info_sms_with_custom_message
|
127
|
-
confirmation_code = FourInfo.generate_confirmation_code
|
128
|
-
|
129
|
-
# Use this class' confirmation_message method if it
|
130
|
-
# exists, otherwise use the generic message
|
131
|
-
message = (self.class.respond_to?(:confirmation_message) ?
|
132
|
-
self.class :
|
133
|
-
FourInfo).confirmation_message(confirmation_code)
|
134
|
-
|
135
|
-
if message.to_s.size > 160
|
136
|
-
raise ArgumentError, "SMS Confirmation Message is too long. Limit it to 160 characters of unescaped text."
|
137
|
-
end
|
138
|
-
|
139
|
-
response = FourInfo::Request.new.deliver_message(message, four_info_sms_phone_number)
|
140
|
-
|
141
|
-
if response.success?
|
142
|
-
update_four_info_sms_confirmation confirmation_code
|
143
|
-
else
|
144
|
-
false
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
def confirm_four_info_sms_with_default_message
|
149
|
-
response = FourInfo::Request.new.confirm(four_info_sms_phone_number)
|
150
|
-
|
151
|
-
if response.success?
|
152
|
-
update_four_info_sms_confirmation response.confirmation_code
|
153
|
-
else
|
154
|
-
false
|
155
|
-
end
|
156
|
-
end
|
157
142
|
|
158
|
-
def
|
159
|
-
self.
|
160
|
-
self.
|
161
|
-
self.
|
143
|
+
def update_txter_sms_confirmation(new_code)
|
144
|
+
self.txter_sms_confirmation_code = new_code
|
145
|
+
self.txter_sms_confirmation_attempted = Time.now.utc
|
146
|
+
self.txter_sms_confirmed_phone_number = nil
|
162
147
|
save
|
163
148
|
end
|
164
149
|
end
|
data/lib/controller.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
module
|
1
|
+
module Txter
|
2
2
|
module Controller
|
3
3
|
|
4
4
|
def self.included(controller)
|
@@ -25,7 +25,7 @@ module FourInfo
|
|
25
25
|
def recieve_xml
|
26
26
|
|
27
27
|
unless defined?(@@contactable_class)
|
28
|
-
raise RuntimeError, "Please define your user class in the
|
28
|
+
raise RuntimeError, "Please define your user class in the Txter controller via the 'sms_contactable' method"
|
29
29
|
end
|
30
30
|
|
31
31
|
request = params[:request]
|
@@ -33,7 +33,7 @@ module FourInfo
|
|
33
33
|
case request['type']
|
34
34
|
when 'BLOCK'
|
35
35
|
@contactable = find_contactable(request[:block][:recipient][:id])
|
36
|
-
@contactable.
|
36
|
+
@contactable.txter_sms_blocked = true
|
37
37
|
@contactable.save!
|
38
38
|
when 'MESSAGE'
|
39
39
|
@contactable = find_contactable(request[:message][:sender][:id])
|
data/lib/gateway.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module Txter
|
2
|
+
class Gateway
|
3
|
+
class Request
|
4
|
+
end
|
5
|
+
|
6
|
+
class Response
|
7
|
+
def initialize(*args)
|
8
|
+
@options = args.last
|
9
|
+
end
|
10
|
+
|
11
|
+
def success?
|
12
|
+
:success == @options[:status]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
Success = Txter::Gateway::Response.new(:status => :success)
|
16
|
+
Error = Txter::Gateway::Response.new(:status => :error)
|
17
|
+
|
18
|
+
def self.deliver(*args)
|
19
|
+
# subclasses should actually do something here
|
20
|
+
Success
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.unblock(*args)
|
24
|
+
# subclasses should actually do something here
|
25
|
+
Success
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.current
|
29
|
+
case Txter.configuration.gateway
|
30
|
+
when 'twilio'
|
31
|
+
gem 'twiliolib'
|
32
|
+
require 'twiliolib'
|
33
|
+
GatewayTwilio
|
34
|
+
when '4info'
|
35
|
+
Gateway4info
|
36
|
+
when 'test'
|
37
|
+
Txter::Gateway
|
38
|
+
else
|
39
|
+
raise "You need to specify your Txter gateway!"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
require File.join File.dirname(__FILE__), 'gateway_4info'
|
45
|
+
require File.join File.dirname(__FILE__), 'gateway_twilio'
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Txter
|
2
|
+
class Gateway4info < Txter::Gateway
|
3
|
+
|
4
|
+
API = 'http://gateway.4info.net/msg'
|
5
|
+
|
6
|
+
def self.deliver(message, number)
|
7
|
+
Gateway4info.perform Request.new.deliver_message(message, number)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.perform(body)
|
11
|
+
if :live == Txter.mode
|
12
|
+
require 'net/http'
|
13
|
+
uri = URI.parse API
|
14
|
+
body = start do |http|
|
15
|
+
http.post(
|
16
|
+
uri.path,
|
17
|
+
body,
|
18
|
+
{'Content-Type' => 'text/xml'}
|
19
|
+
).read_body
|
20
|
+
end
|
21
|
+
Response.new(body)
|
22
|
+
else
|
23
|
+
Txter.log "Would have sent to 4info.net: #{body}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
protected
|
28
|
+
|
29
|
+
def self.start
|
30
|
+
c = Txter.configuration
|
31
|
+
net = c.proxy_address ?
|
32
|
+
Net::HTTP::Proxy(
|
33
|
+
c.proxy_address,
|
34
|
+
c.proxy_port,
|
35
|
+
c.proxy_username,
|
36
|
+
c.proxy_password) :
|
37
|
+
Net::HTTP
|
38
|
+
uri = URI.parse API
|
39
|
+
net.start(uri.host, uri.port) do |http|
|
40
|
+
yield http
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class Response < Txter::Gateway::Response
|
45
|
+
def initialize(xml)
|
46
|
+
gem 'hpricot'
|
47
|
+
require 'hpricot'
|
48
|
+
@xml = xml
|
49
|
+
@body = Hpricot.parse(xml)
|
50
|
+
end
|
51
|
+
|
52
|
+
def inspect
|
53
|
+
@xml.to_s
|
54
|
+
end
|
55
|
+
|
56
|
+
def [](name)
|
57
|
+
nodes = (@body/name)
|
58
|
+
1 == nodes.size ? nodes.first : nodes
|
59
|
+
end
|
60
|
+
|
61
|
+
def success?
|
62
|
+
'Success' == self['message'].inner_text
|
63
|
+
end
|
64
|
+
|
65
|
+
def confirmation_code
|
66
|
+
self[:confcode].inner_text
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class Request < Txter::Gateway::Request
|
71
|
+
|
72
|
+
attr_accessor :number
|
73
|
+
attr_accessor :message
|
74
|
+
attr_accessor :config
|
75
|
+
|
76
|
+
def initialize
|
77
|
+
unless Txter.configured?
|
78
|
+
raise "You need to configure Txter before using it!"
|
79
|
+
end
|
80
|
+
self.config = Txter.configuration
|
81
|
+
end
|
82
|
+
|
83
|
+
def deliver_message(message, number)
|
84
|
+
self.number = Txter.internationalize(number)
|
85
|
+
self.message = message
|
86
|
+
|
87
|
+
template(:deliver).render(self)
|
88
|
+
end
|
89
|
+
|
90
|
+
def confirm(number)
|
91
|
+
self.number = Txter.internationalize(number)
|
92
|
+
|
93
|
+
template(:confirm).render(self)
|
94
|
+
end
|
95
|
+
|
96
|
+
def unblock(number)
|
97
|
+
self.number = Txter.internationalize(number)
|
98
|
+
|
99
|
+
template(:unblock).render(self)
|
100
|
+
end
|
101
|
+
|
102
|
+
protected
|
103
|
+
|
104
|
+
def template(name)
|
105
|
+
# Haml templates for XML
|
106
|
+
require 'cgi'
|
107
|
+
templates = Dir.glob(File.expand_path(File.join(File.dirname(__FILE__), '4info_templates', '*.haml')))
|
108
|
+
file = templates.detect {|t| File.basename(t).chomp('.haml').to_sym == name.to_sym }
|
109
|
+
raise ArgumentError, "Missing 4Info template: #{name}" unless file
|
110
|
+
Haml::Engine.new(File.read(file))
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Txter
|
2
|
+
class GatewayTwilio < Txter::Gateway
|
3
|
+
|
4
|
+
API_VERSION = '2008-08-01'
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def deliver(message, to, from = nil)
|
9
|
+
|
10
|
+
from ||= Txter.configuration.default_from_phone_number
|
11
|
+
raise "'From' number required for Twilio" unless from
|
12
|
+
|
13
|
+
response = post 'To' => to,
|
14
|
+
'From' => from,
|
15
|
+
'Body' => message
|
16
|
+
|
17
|
+
Net::HTTPCreated == response.code_type ?
|
18
|
+
Txter::Gateway::Success :
|
19
|
+
Txter::Gateway::Error
|
20
|
+
end
|
21
|
+
|
22
|
+
def account
|
23
|
+
@account ||= begin
|
24
|
+
if Txter.configuration.client_id.blank? ||
|
25
|
+
Txter.configuration.client_key.blank?
|
26
|
+
raise "Add your Twilio account id (as client_id) and token (as client_key) to the Txter.configure block"
|
27
|
+
end
|
28
|
+
|
29
|
+
Twilio::RestAccount.new(
|
30
|
+
Txter.configuration.client_id,
|
31
|
+
Txter.configuration.client_key
|
32
|
+
)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
def post(data = {})
|
39
|
+
account.request "/#{API_VERSION}/Accounts/#{Txter.configuration.client_id}/SMS/Messages",
|
40
|
+
"POST",
|
41
|
+
data
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|