txter 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 [name of plugin creator]
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,125 @@
1
+ 4info
2
+ =====
3
+
4
+ Connect to the 4info SMS gateway
5
+
6
+ If you're using 4info.com as your SMS gateway this gem will give you a painless API for both sending and receiving messages.
7
+
8
+ Setting Up Your Model
9
+ =====
10
+
11
+ Include Txter::Contactable into your User class or whatever you're using to represent an entity with a phone number.
12
+
13
+ class User < ActiveRecord::Base
14
+ include Txter::Contactable
15
+ end
16
+
17
+ You can also specify which attributes you'd like to use instead of the defaults
18
+
19
+ class User < ActiveRecord::Base
20
+ include Txter::Contactable
21
+
22
+ sms_phone_number_column :mobile_number
23
+ sms_blocked_column :is_sms_blocked
24
+ sms_confirmation_code_column :the_sms_confirmation_code
25
+ sms_confirmation_attempted_column :when_was_the_sms_confirmation_attempted
26
+ sms_confirmed_phone_number_column :the_mobile_number_thats_been_confirmed
27
+
28
+ # Defaults to the name on the left (minus the '_column' at the end)
29
+ end
30
+
31
+ Turning the thing on
32
+ ---
33
+
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
+
36
+ Txter.mode = :live
37
+
38
+ Skipping this step (or adding any other value) will prevent TXTs from actually being sent.
39
+
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
+
42
+ Txter.configure do |config|
43
+ # these two are required:
44
+ # (replace them with your actual account info)
45
+ config.client_id = 12345
46
+ config.client_key = 'ABC123'
47
+
48
+ # the rest are optional:
49
+ config.short_code = 00001 # if you have a custom short code
50
+ config.proxy_address = 'my.proxy.com'
51
+ config.proxy_port = '80'
52
+ config.proxy_username = 'user'
53
+ config.proxy_password = 'password'
54
+ end
55
+
56
+ Phone number formatting
57
+ ---
58
+
59
+ Whatever is stored in the sms_phone_number_column will be subject to normalized formatting:
60
+
61
+ user = User.create :sms_phone_number => '(206) 555-1234'
62
+ user.sms_phone_number # => 2065551234
63
+
64
+ If you want to preserve the format of the number exactly as the user entered it you'll want
65
+ to save that in a different attribute.
66
+
67
+
68
+ Confirming Phone Number And Sending Messages
69
+ ====
70
+
71
+ You can manage the user's SMS state like so:
72
+
73
+ @user = User.create(:sms_phone_number => '5552223333')
74
+ @user.send_sms_confirmation!
75
+ @user.sms_confirmed? # => false
76
+
77
+ then ask the user for the confirmation code off their phone and pass it in to sms_confirm_with:
78
+
79
+ @user.sms_confirm_with(user_provided_code)
80
+
81
+ If the code is right then the user's current phone number will be automatically marked as confirmed. You can check this at any time with:
82
+
83
+ @user.sms_confirmed? # => true
84
+ @user.send_sms!("Hi! This is a text message.")
85
+
86
+ Then maybe the user will reply with 'BLOCK' by accident and @user.sms_blocked? will be true.
87
+ You can fix this by calling:
88
+
89
+ @user.unblock_sms!
90
+
91
+
92
+ Receiving Messages From 4info.com
93
+ ====
94
+
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 Txter::Controller into it. Then specify which Ruby class you're using as a contactable user model (likely User)
97
+
98
+
99
+ class SMSController < ApplicationController
100
+ include Txter::Controller
101
+
102
+ sms_contactable User # or whichever class you included Txter::Contactable into
103
+ end
104
+
105
+ And hook this up in your routes.rb file like so:
106
+
107
+ ActionController::Routing::Routes.draw do |map|
108
+ map.route '4info', :controller => 'txter', :action => :index
109
+ end
110
+
111
+ Now just tell 4info.com to POST messages and block notices to you at:
112
+
113
+ http://myrubyapp.com/4info
114
+
115
+ Now if your users reply to an SMS with 'STOP' your database will be updated to reflect this.
116
+
117
+ Incoming messages from a user will automatically be sent to that user's record:
118
+
119
+ # If "I love you!" is sent to you from a user with the phone
120
+ # number "555-111-9999" then the following will be executed:
121
+ User.find_by_sms_phone_number('5551119999').receive_sms("I love you!")
122
+
123
+ That's it. Patches welcome, forks celebrated.
124
+
125
+ Copyright (c) 2010 [Jack Danger Canty](http://jåck.com/), released under the MIT license
data/Rakefile ADDED
@@ -0,0 +1,56 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "txter"
8
+ gem.summary = %Q{Send and receive SMS messages simply via the Twilio gateway or the 4info.com gateway}
9
+ gem.description = %Q{Drop-in functionality to let Ruby apps send and receive TXT messages}
10
+ gem.email = "gitcommit@6brand.com"
11
+ gem.homepage = "http://github.com/JackDanger/txter"
12
+ gem.authors = ["Jack Danger Canty"]
13
+ # gem.add_dependency "hpricot", ">= 0"
14
+ # gem.add_dependency "haml", ">= 0"
15
+ gem.add_development_dependency "shoulda", ">= 0"
16
+ gem.add_development_dependency "mocha", ">= 0"
17
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
18
+ end
19
+ Jeweler::GemcutterTasks.new
20
+ rescue LoadError
21
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
22
+ end
23
+
24
+ require 'rake/testtask'
25
+ desc "Test Txter"
26
+ Rake::TestTask.new(:test) do |test|
27
+ test.libs << 'lib' << 'test'
28
+ test.pattern = 'test/**/*_test.rb'
29
+ test.verbose = true
30
+ end
31
+
32
+ begin
33
+ require 'rcov/rcovtask'
34
+ Rcov::RcovTask.new do |test|
35
+ test.libs << 'test'
36
+ test.pattern = 'test/**/test_*.rb'
37
+ test.verbose = true
38
+ end
39
+ rescue LoadError
40
+ task :rcov do
41
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
42
+ end
43
+ end
44
+
45
+ task :test => :check_dependencies
46
+
47
+ task :default => :test
48
+
49
+ require 'rake/rdoctask'
50
+ Rake::RDocTask.new do |rdoc|
51
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
52
+
53
+ rdoc.rdoc_dir = 'rdoc'
54
+ rdoc.title = "inline_styles #{version}"
55
+ rdoc.rdoc_files.include('lib/**/*.rb')
56
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 2.0.2
data/init.rb ADDED
@@ -0,0 +1,3 @@
1
+ # Include hook code here
2
+
3
+ require 'txter'
@@ -0,0 +1,6 @@
1
+ !!! XML
2
+ %request{ :clientId => config.client_id, :clientKey => config.client_key, :type => "VALIDATION" }
3
+ %validation
4
+ %recipient
5
+ %type= 5
6
+ %id= number
@@ -0,0 +1,11 @@
1
+ !!! XML
2
+ %request{ :clientId => config.client_id, :clientKey => config.client_key, :type => "MESSAGE" }
3
+ %message
4
+ - if config.short_code
5
+ %sender
6
+ %type= 6
7
+ %id= config.short_code
8
+ %recipient
9
+ %type= 5
10
+ %id= number
11
+ %text= message.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;").gsub("'", "&apos;").gsub('"', "&quot;")
@@ -0,0 +1,6 @@
1
+ !!! XML
2
+ %request{ :clientId => config.client_id, :clientKey => config.client_key, :type => "UNBLOCK" }
3
+ %unblock
4
+ %recipient
5
+ %type= 5
6
+ %id= number
@@ -0,0 +1,41 @@
1
+ module Txter
2
+ class << self
3
+ def mode
4
+ @@mode ||= :test
5
+ end
6
+
7
+ def mode=(new_mode)
8
+ @@mode = new_mode
9
+ end
10
+
11
+ def configured?
12
+ return false unless configuration
13
+ configuration.client_id && configuration.client_key
14
+ end
15
+
16
+ def configuration
17
+ @@configuration
18
+ end
19
+
20
+ def configure(&block)
21
+ @@configuration = Configuration.new(&block)
22
+ end
23
+ end
24
+
25
+ class Configuration
26
+
27
+ attr_accessor :client_id
28
+ attr_accessor :client_key
29
+ attr_accessor :gateway
30
+ attr_accessor :short_code
31
+ attr_accessor :default_from_phone_number
32
+ attr_accessor :proxy_address
33
+ attr_accessor :proxy_port
34
+ attr_accessor :proxy_username
35
+ attr_accessor :proxy_password
36
+
37
+ def initialize
38
+ yield self
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,150 @@
1
+ module Txter
2
+ module Contactable
3
+
4
+ Attributes = [ :sms_phone_number,
5
+ :sms_blocked,
6
+ :sms_confirmation_code,
7
+ :sms_confirmation_attempted,
8
+ :sms_confirmed_phone_number ]
9
+
10
+ def self.included(model)
11
+ gem 'haml'
12
+ require 'haml'
13
+ require 'net/http'
14
+
15
+ Attributes.each do |attribute|
16
+ # add a method in the class for setting or retrieving
17
+ # which column should be used for which attribute
18
+ #
19
+ # :sms_phone_number_column defaults to :sms_phone_number, etc.
20
+ model.instance_eval "
21
+ def #{attribute}_column(value = nil)
22
+ @#{attribute}_column ||= :#{attribute}
23
+ @#{attribute}_column = value if value
24
+ @#{attribute}_column
25
+ end
26
+ "
27
+ # provide helper methods to access the right value
28
+ # no matter which column it's stored in.
29
+ #
30
+ # e.g.: @user.txter_sms_confirmation_code
31
+ # == @user.send(User.sms_confirmation_code_column)
32
+ model.class_eval "
33
+ def txter_#{attribute}
34
+ send self.class.#{attribute}_column
35
+ end
36
+ alias_method :txter_#{attribute}?, :txter_#{attribute}
37
+ def txter_#{attribute}=(value)
38
+ send self.class.#{attribute}_column.to_s+'=', value
39
+ end
40
+ "
41
+ end
42
+
43
+ # normalize the phone number before it's saved in the database
44
+ # (only for model classes using callbacks a la ActiveRecord,
45
+ # other folks will have to do this by hand)
46
+ if model.respond_to?(:before_save)
47
+ model.before_save :normalize_sms_phone_number
48
+ model.class_eval do
49
+ def normalize_sms_phone_number
50
+ self.txter_sms_phone_number = Txter.numerize(txter_sms_phone_number)
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ # Sends one or more TXT messages to the contactable record's
57
+ # mobile number (if the number has been confirmed).
58
+ # Any messages longer than 160 characters will need to be accompanied
59
+ # by a second argument <tt>true</tt> to clarify that sending
60
+ # multiple messages is intentional.
61
+ def send_sms!(msg, allow_multiple = false)
62
+ if msg.to_s.size > 160 && !allow_multiple
63
+ raise ArgumentError, "SMS Message is too long. Either specify that you want multiple messages or shorten the string."
64
+ end
65
+ return false if msg.to_s.strip.blank? || txter_sms_blocked?
66
+ return false unless sms_confirmed?
67
+
68
+ # split into pieces that fit as individual messages.
69
+ msg.to_s.scan(/.{1,160}/m).map do |text|
70
+ if Txter.deliver(text, txter_sms_phone_number).success?
71
+ text.size
72
+ else
73
+ false
74
+ end
75
+ end
76
+ end
77
+
78
+ # Sends an SMS validation request through the gateway
79
+ def send_sms_confirmation!
80
+ return false if txter_sms_blocked?
81
+ return true if sms_confirmed?
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
95
+
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
103
+ end
104
+
105
+
106
+ # Sends an unblock request via xml to the 4info gateway.
107
+ # If request succeeds, changes the contactable record's
108
+ # sms_blocked_column to false.
109
+ def unblock_sms!
110
+ return false unless txter_sms_blocked?
111
+
112
+ response = Txter.unblock(txter_sms_phone_number)
113
+ if response.success?
114
+ self.txter_sms_blocked = 'false'
115
+ save
116
+ else
117
+ false
118
+ end
119
+ end
120
+
121
+ # Compares user-provided code with the stored confirmation
122
+ # code. If they match then the current phone number is set
123
+ # as confirmed by the user.
124
+ def sms_confirm_with(code)
125
+ if txter_sms_confirmation_code.to_s.downcase == code.downcase
126
+ # save the phone number into the 'confirmed phone number' attribute
127
+ self.txter_sms_confirmed_phone_number = txter_sms_phone_number
128
+ save
129
+ else
130
+ false
131
+ end
132
+ end
133
+
134
+ # Returns true if the current phone number has been confirmed by
135
+ # the user for recieving TXT messages.
136
+ def sms_confirmed?
137
+ return false if txter_sms_confirmed_phone_number.blank?
138
+ txter_sms_confirmed_phone_number == txter_sms_phone_number
139
+ end
140
+
141
+ protected
142
+
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
147
+ save
148
+ end
149
+ end
150
+ end
data/lib/controller.rb ADDED
@@ -0,0 +1,62 @@
1
+ module Txter
2
+ module Controller
3
+
4
+ def self.included(controller)
5
+ controller.instance_eval do
6
+ # the user should specify which class gets contacted
7
+ def sms_contactable(klass)
8
+ @@contactable_class = klass
9
+ end
10
+ end
11
+ end
12
+
13
+ # the likely default
14
+ def index
15
+ recieve_xml
16
+ end
17
+
18
+ # in case this is hooked up as a RESTful route
19
+ def create
20
+ recieve_xml
21
+ end
22
+
23
+ protected
24
+
25
+ def recieve_xml
26
+
27
+ unless defined?(@@contactable_class)
28
+ raise RuntimeError, "Please define your user class in the Txter controller via the 'sms_contactable' method"
29
+ end
30
+
31
+ request = params[:request]
32
+ render :text => 'unknown format', :status => 500 and return unless request
33
+ case request['type']
34
+ when 'BLOCK'
35
+ @contactable = find_contactable(request[:block][:recipient][:id])
36
+ @contactable.txter_sms_blocked = true
37
+ @contactable.save!
38
+ when 'MESSAGE'
39
+ @contactable = find_contactable(request[:message][:sender][:id])
40
+ if @contactable.respond_to?(:receive_sms)
41
+ @contactable.receive_sms(request[:message][:text])
42
+ else
43
+ warn "An SMS message was received but #{@@contactable_class.name.inspect} doesn't have a receive_sms method!"
44
+ end
45
+ end
46
+ render :text => 'OK', :status => 200
47
+ end
48
+
49
+ def find_contactable(id)
50
+ [id, id.sub(/^\+/,''), id.sub(/^\+1/,'')].uniq.compact.each do |possible_phone_number|
51
+ found = @@contactable_class.find(
52
+ :first,
53
+ :conditions =>
54
+ { @@contactable_class.sms_phone_number_column => possible_phone_number }
55
+ )
56
+ return found if found
57
+ end
58
+ # rescue => error
59
+ # render :text => error.inspect
60
+ end
61
+ end
62
+ end