josh-splat 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+ config/vendors.yml
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Gautam Rege
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.rdoc ADDED
@@ -0,0 +1,32 @@
1
+ SPlat - Sms PLATform
2
+
3
+ SPlat is an integration platform to make use of SMS integration really
4
+ easy. Using this platform has the following advantages:
5
+
6
+ * Single point of integration
7
+ * Change vendors without changing code.
8
+ * Send and receive SMS.
9
+ * Generic Exception Handling.
10
+ * Standardized reports.
11
+ * SMS tagged user groups.
12
+ * SMS bogus gateway for testing.
13
+ * Scheduling SMS for delivery.
14
+
15
+ What SPlat isn't!
16
+
17
+ SPlat is not an SMS gateway. You still need to buy a subscription from one
18
+ of the vendors SPlat supports.
19
+
20
+ Note on Patches/Pull Requests
21
+
22
+ * Fork the project.
23
+ * Make your feature addition or bug fix.
24
+ * Add tests for it. This is important so I don't break it in a
25
+ future version unintentionally.
26
+ * Commit, do not mess with rakefile, version, or history.
27
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
28
+ * Send me a pull request. Bonus points for topic branches.
29
+
30
+ == Copyright
31
+
32
+ Copyright (c) 2010 Josh Software Pvt. Ltd. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "splat"
8
+ gem.summary = %Q{TODO: one-line summary of your gem}
9
+ gem.description = %Q{TODO: longer description of your gem}
10
+ gem.email = "gautam@joshsoftware.com"
11
+ gem.homepage = "http://github.com/gautamrege/splat"
12
+ gem.authors = ["Gautam Rege"]
13
+ gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ require 'rake/testtask'
22
+ Rake::TestTask.new(:test) do |test|
23
+ test.libs << 'lib' << 'test'
24
+ test.pattern = 'test/**/test_*.rb'
25
+ test.verbose = true
26
+ end
27
+
28
+ begin
29
+ require 'rcov/rcovtask'
30
+ Rcov::RcovTask.new do |test|
31
+ test.libs << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+ rescue LoadError
36
+ task :rcov do
37
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
38
+ end
39
+ end
40
+
41
+ task :test => :check_dependencies
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "splat #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.2
@@ -0,0 +1,16 @@
1
+ class SplatGenerator < Rails::Generator::Base
2
+
3
+ def manifest
4
+ record do |m|
5
+ m.file 'splat.yml', 'config/splat.yml'
6
+ m.file 'vendors.yml', 'config/vendors.yml'
7
+ end
8
+ end
9
+
10
+ protected
11
+
12
+ def banner
13
+ "Usage: #{$0} splat"
14
+ end
15
+ end
16
+
@@ -0,0 +1,28 @@
1
+
2
+ # Mobile number formats varies from vendor to vendor.
3
+ # To resolve this, SPlat splits country_code and mobile number.
4
+ # The default format is: +<country code><single white space><mobile number.
5
+ #
6
+ # Its quite possible that you may require to use the before_send hook to
7
+ # manipulate the number format.
8
+ #
9
+ # At your own risk, update the regex for default_number_format if you know
10
+ # what you are doing.
11
+ #default_number_format: '^\+\d{1,4}\ \d{8,12}$' # +91 9812345867
12
+
13
+
14
+ # By default SPlat will try 5 times to send an SMS on failure.
15
+ #retries: 5
16
+
17
+ # Default insertion is a means to work-around incomplete string insertions.
18
+ # This can be overridden using the options parameter.
19
+ #use_default_insertion: true
20
+ #default_insertion: ""
21
+
22
+
23
+ default:
24
+ number_format: '^\+\d{1,4}\ \d{8,12}$' # +91 9812345867
25
+ retries: 5
26
+ use_default_insertion: true
27
+ default_insertion: ""
28
+
@@ -0,0 +1,19 @@
1
+
2
+ vmobo:
3
+ provider_url: 'http://www.vMobo.in/bulk_send/v1'
4
+ username:
5
+ api_key:
6
+ keyword:
7
+
8
+ bulksmspune:
9
+ provider_url: 'http://www.bulksmspune.mobi/sendurlcomma.asp'
10
+ username:
11
+ password:
12
+ senderid:
13
+
14
+ clickatell:
15
+ provider_url: 'http://api.clickatell.com'
16
+ api_key:
17
+ username:
18
+ password:
19
+
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+
2
+ require File.dirname(__FILE__) + '/lib/splat'
@@ -0,0 +1,35 @@
1
+ require 'yaml'
2
+
3
+ module Splat
4
+
5
+ #Singleton class
6
+ class Configuration
7
+
8
+ @options = nil
9
+
10
+ def initialize
11
+ @options = YAML.load_file('config/splat.yml')
12
+ @options = @options.merge YAML.load_file('config/vendors.yml')
13
+ end
14
+
15
+ def self.instance
16
+ @@configuration
17
+ end
18
+
19
+ def vendor(vendor = "default")
20
+ @options[vendor.to_s]
21
+ end
22
+
23
+ def all
24
+ @options
25
+ end
26
+
27
+ def vendors
28
+ @options.keys
29
+ end
30
+
31
+ @@configuration = Configuration.new
32
+ private_class_method :new
33
+
34
+ end
35
+ end
@@ -0,0 +1,23 @@
1
+
2
+ module Splat
3
+
4
+ class SplatError < StandardError
5
+ end
6
+
7
+ class SplatGatewayError < SplatError
8
+ end
9
+
10
+ class SplatFormatError < SplatError
11
+ end
12
+
13
+ class SplatNoGatewaySupportError < SplatError
14
+ end
15
+
16
+ class SplatGatewayImplError < SplatError
17
+ end
18
+
19
+ end
20
+
21
+
22
+
23
+
@@ -0,0 +1,118 @@
1
+
2
+ require 'net/http'
3
+ require File.dirname(__FILE__) + '/configuration'
4
+ require File.dirname(__FILE__) + '/utils'
5
+ require File.dirname(__FILE__) + '/insertion'
6
+
7
+ module Splat
8
+
9
+ autoload :Initializer, File.dirname(__FILE__) + '/initializer'
10
+
11
+ class Gateway
12
+
13
+ include Insertion
14
+ include Utils
15
+
16
+ @@vendor_implementations = {}
17
+ @@configuration = nil
18
+
19
+ attr_reader :vendor
20
+
21
+ def initialize
22
+ @vendor = self.class.name.downcase.split("::").last if self.class != Gateway
23
+ end
24
+
25
+ def self.inherited(subclass)
26
+ @@vendor_implementations[subclass.name.downcase.split("::").last] = subclass
27
+ end
28
+
29
+ def self.[](name)
30
+ @@vendor_implementations[name.to_s]
31
+ end
32
+
33
+ def self.default_configuration
34
+ Configuration.instance.vendor
35
+ end
36
+
37
+ def configuration
38
+ Configuration.instance.vendor(@vendor)
39
+ end
40
+
41
+ def config_option(option)
42
+ Configuration.instance.vendor(@vendor)[option.to_s]
43
+ end
44
+
45
+ # Send an SMS.
46
+ # message:: The message. This will be automatically sanitized
47
+ # and url-encoded.
48
+ # number:: The number in Config::Splat.default_number_format
49
+ # options:: The various options specified configure_options
50
+ #
51
+ # Return Values
52
+ # Success:: Return the id if the message was sent to the gateway.
53
+ # This not imply that the messages were delivered.
54
+ # Failure:: Raise SplatGatewayError on failure with the relevant
55
+ # error message and error code.
56
+ #
57
+ # Example:
58
+ # message => "Hello Smith, welcome to splat"
59
+ # number => '+91 9812345678'
60
+ #
61
+ # This returns and id, for which you can track the status.
62
+ def send_sms(message, number, options = {})
63
+ raise SplatGatewayImplError.new("Implement send_sms(message, number, options = {}) method in #{self.class}")
64
+ end
65
+
66
+ # Send bulk SMS
67
+ # message:: The message. This will be automatically sanitized
68
+ # and url-encoded. This message can contain string
69
+ # insertions.
70
+ # number:: Array of numbers in the Config::Splat.default_number_format
71
+ # options:: The various options specified configure_options
72
+ #
73
+ # In case the numbers do not match with string insertions, the
74
+ # default_insertion will be used. If use_default_insertion is
75
+ # set to false, then those numbers will be skipped.
76
+ #
77
+ # Return Values
78
+ # Success:: Return the id if the message was sent to the gateway.
79
+ # This not imply that the messages were delivered.
80
+ # Failure:: Raise SplatGatewayError on failure with the relevant
81
+ # error message and error code.
82
+ #
83
+ # Example:
84
+ # message => "Hello $1, hold on to your $2"
85
+ # numbers => [ '+91 9812345678', '+1 4034832933', '+44 123783218']
86
+ # options =>
87
+ # :insertions => { 1 => [ 'Smith', 'John Doe', 'Jane'],
88
+ # 2 => [ 'stocks', 'socks', 'skirt']
89
+ # },
90
+ # :default_insertion => { 1 => 'User', 2 => '' }
91
+ # REVIEW:
92
+ # :insertions => { '+91 981234565' => [ 'Smith', 'stocks'],
93
+ # '+1 4034832933' => ['John Doe', 'socks'],
94
+ # '+44 123783218' => ['Jane', 'skirt']
95
+ # '+44 122342418' => ['Jane' ]
96
+ # :default_insertion => { 1 => 'User', 2 => '' }
97
+ # }
98
+ def send_bulk_sms(message, numbers, options = {})
99
+ raise SplatGatewayImplError.new("Implement send_bulk_sms(message, numbers, options = {}) in #{self.class}")
100
+ end
101
+
102
+ def send_bulk_sms_with_insertion(message, insertions ={}, options = {})
103
+ raise SplatGatewayImplError.new("Implement send_bulk_sms_with_insertion(message, insertions ={}, options = {}) in #{self.class}")
104
+ end
105
+
106
+ def required_config(options = [])
107
+ options.each do |option|
108
+ raise SplatError.new("'#{option}' not define for '#{@vendor}' in config file") if self.configuration[option.to_s].blank?
109
+ end
110
+ end
111
+
112
+ end
113
+
114
+ unless Splat.included_modules.include?(Splat::Initializer)
115
+ include Initializer
116
+ end
117
+
118
+ end
@@ -0,0 +1,13 @@
1
+ module Splat
2
+
3
+ module Initializer
4
+
5
+ Dir[File.dirname(__FILE__) + '/vendors/**/*_gateway.rb'].each do |file|
6
+ require file
7
+ #vendor_implemetation = File.basename(file, '.rb').camelize
8
+ #autoload vendor_implemetation, file
9
+ end
10
+
11
+ end
12
+
13
+ end
@@ -0,0 +1,17 @@
1
+ module Splat
2
+
3
+ module Insertion
4
+
5
+ def insert_values(message,insertions = {}, default_insertions = {})
6
+ number_messsage_map = {}
7
+
8
+ insertions.each do |key, value|
9
+ i = -1
10
+ insertions[key] = message.gsub(/\$\d/) {|match| value[i += 1] }
11
+ end
12
+ insertions
13
+ end
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,32 @@
1
+ module Splat
2
+
3
+ class Request
4
+
5
+ SINGLE = 0
6
+ BULK = 1
7
+ BULK_INSERTION = 2
8
+
9
+ INVALID_NUMBER_FORMAT = "Invalid phone number format"
10
+
11
+ attr_accessor :type
12
+ attr_accessor :message
13
+ attr_accessor :numbers
14
+ attr_accessor :options
15
+
16
+ def sms(message, number, options = {})
17
+ @type, @message, @numbers, @options = SINGLE, message, number, options
18
+ end
19
+
20
+
21
+ def bulk_sms(message, numbers, options = {}))
22
+ @type, @message, @numbers, @options = BULK, message, numbers, options
23
+ end
24
+
25
+ def bulk_sms_with_insertion(message, numbers, options = {}))
26
+ @type, @message, @numbers, @options = BULK_INSERTION, message, numbers, options
27
+ end
28
+
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,29 @@
1
+ module Splat
2
+
3
+ class Response
4
+
5
+ INVALID_NUMBER_FORMAT = "Invalid phone number format"
6
+ SUCCESSFUL = 'Message sent successfully'
7
+ FAIL = "Fail to send message"
8
+
9
+ attr_accessor :phone_numbers, :message
10
+
11
+ def initialize
12
+ @phone_numbers = {}
13
+ end
14
+
15
+ def invalid_phone_format(number)
16
+ @phone_numbers[number] = INVALID_NUMBER_FORMAT
17
+ end
18
+
19
+ def add(number, message)
20
+ @phone_numbers[number] = message
21
+ end
22
+
23
+ def invalid_phone_numbers
24
+ @phone_numbers.keys
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,9 @@
1
+ module Splat
2
+ module Utils
3
+
4
+ def url_escape(data)
5
+ URI.escape(data, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,14 @@
1
+ module Spalt
2
+
3
+ module Validator
4
+
5
+ def valid_phone?(number)
6
+ number.to_s =~ number_format_regx
7
+ end
8
+
9
+ def valid_phones?(numbers)
10
+ end
11
+
12
+ end
13
+
14
+ end
@@ -0,0 +1,51 @@
1
+
2
+ require 'net/http'
3
+
4
+ module Splat
5
+
6
+ class Bulksmspune < Gateway
7
+
8
+ def initialize()
9
+ super
10
+ self.required_config([:provider_url, :username, :password, :senderid])
11
+ @url = "#{self.config_option(:provider_url)}?user=#{self.config_option(:username)}&pwd=#{self.config_option(:password)}&senderid=#{self.config_option(:senderid)}"
12
+
13
+ #if options[:callbacks]
14
+ # before_send = options[:callback][:before_send]
15
+ #end
16
+ end
17
+
18
+ def send_sms(message, number, options = {})
19
+ options[:response].message = call_service(message, parse_number(number))
20
+ options[:response]
21
+ end
22
+
23
+ def send_bulk_sms(message, numbers, options = {})
24
+ numbers = numbers.collect{ |x| parse_number(x) }.join(',')
25
+ options[:response].message = call_service(message, numbers)
26
+ options[:response]
27
+ end
28
+
29
+ def send_bulk_sms_with_insertion(message, insertions = {}, options = {})
30
+
31
+ number_message_map = insert_values(message, insertions)
32
+ number_message_map.each do |number, message|
33
+ options[:response].add(number, call_service(message, parse_number(number)))
34
+ end
35
+ options[:response]
36
+ end
37
+
38
+ private
39
+
40
+ def call_service(message, number)
41
+ url = "#{@url}&mobileno=#{number}&msgtext=#{url_escape(message)}&priority=High"
42
+ Net::HTTP.get URI.parse(url)
43
+ end
44
+
45
+ def parse_number(number)
46
+ number.split(' ').last
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,119 @@
1
+
2
+ module Splat
3
+
4
+ class Clickatell < Gateway
5
+
6
+ PARSE_REGEX = /[A-Za-z0-9]+:.*?(?:(?=[A-Za-z0-9]+:)|$)/
7
+
8
+ def initialize()
9
+ super
10
+ self.required_config([:provider_url, :api_key, :username, :password])
11
+ @url = self.config_option(:provider_url)
12
+ @api_key = self.config_option(:api_key)
13
+ @username = self.config_option(:username)
14
+ @password = self.config_option(:password)
15
+
16
+ @last_call_time = Time.now - 30*60
17
+ @session_id = nil
18
+ end
19
+
20
+ def send_sms(message, number, options = {})
21
+ options[:response].add(number, parse_response(call_service(message, parse_number(number))))
22
+ options[:response]
23
+ end
24
+
25
+ def send_bulk_sms(message, numbers, options = {})
26
+ numbers = numbers.collect{ |x| parse_number(x) }.join(',')
27
+ response = parse_response(call_batch_service(message, numbers))
28
+ response.each do |result|
29
+ options[:response].add(result[1].split(': ').last, result[0])
30
+ end
31
+ options[:response]
32
+ end
33
+
34
+ def send_bulk_sms_with_insertion(message, insertions = {}, options = {})
35
+ call_batch_service_with_insertion(message, insertions, options[:response])
36
+ end
37
+
38
+ private
39
+
40
+ def get_session_id
41
+ if (Time.now - @last_call_time) > 800
42
+ response = http_get("#{@url}/http/auth?api_id=#{@api_key}&user=#{@username}&password=#{@password}").split(": ")
43
+
44
+ unless response[0] == 'OK'
45
+ raise SplatGatewayError.new('Cliclatell :' + response[1])
46
+ end
47
+
48
+ @session_id = response[1]
49
+ end
50
+ @last_call_time = Time.now
51
+ @session_id
52
+ end
53
+
54
+ def get_batch_id(message)
55
+ i = 0
56
+ message = message.gsub(/\$\d/) {|match| "#field#{i += 1}#" }
57
+ response = http_get("#{@url}/http_batch/startbatch?session_id=#{get_session_id}&template=#{url_escape(message)}").split(': ')
58
+
59
+ unless response[0] == 'ID'
60
+ raise SplatGatewayError.new('Cliclatell :' + response[1])
61
+ end
62
+ response[1]
63
+ end
64
+
65
+ def call_service(message, numbers)
66
+ http_get "#{@url}/http/sendmsg?session_id=#{get_session_id}&to=#{numbers}&text=#{url_escape(message)}"
67
+ #response = http_get "#{@url}/http/sendmsg?user=#{@username}&password=#{@password}&api_id=#{@api_key}&to=#{numbers}&text=#{url_escape(message)}"
68
+ end
69
+
70
+
71
+ def call_batch_service(message, numbers)
72
+ batch_id = get_batch_id(message)
73
+ response = http_get "#{@url}/http_batch/quicksend?session_id=#{get_session_id}&batch_id=#{batch_id}&to=#{numbers}"
74
+ end_batch(batch_id)
75
+ response
76
+ end
77
+
78
+ def call_batch_service_with_insertion(message, insertions, response)
79
+ batch_id = get_batch_id(message)
80
+
81
+ url = "#{@url}/http_batch/senditem?session_id=#{get_session_id}&batch_id=#{batch_id}"
82
+
83
+ insertions.each do |number,fields|
84
+ i = 0
85
+ fields = fields.collect do |field|
86
+ "field#{i += 1}=#{field}"
87
+ end
88
+ response.add(number , http_get("#{url}&to=#{parse_number(number)}&#{fields.join('&')}"))
89
+ end
90
+ end_batch(batch_id)
91
+ response
92
+ end
93
+
94
+ def end_batch(batch_id)
95
+ http_get "#{@url}/http_batch/endbatch?session_id=#{get_session_id}&batch_id=#{batch_id}"
96
+ end
97
+
98
+ def http_get(url)
99
+ response = Net::HTTP.get URI.parse(url)
100
+ response
101
+ end
102
+
103
+ def parse_number(number)
104
+ number.tr('+ ', '')
105
+ end
106
+
107
+ def parse_response(http_response)
108
+ if http_response.scan(/ERR/).any?
109
+ return http_response
110
+ end
111
+ results = http_response.split("\n").map do |line|
112
+ response_fields = line.scan(PARSE_REGEX)
113
+ end
114
+ results.length == 1 ? results.first : results
115
+ end
116
+
117
+ end
118
+
119
+ end
@@ -0,0 +1,108 @@
1
+
2
+ require 'net/http'
3
+ require File.dirname(__FILE__) + '/xml/schema'
4
+
5
+ module Splat
6
+
7
+ class Vmobo < Gateway
8
+ include VmoboSchema
9
+
10
+ SUCCESSFUL = "200"
11
+ BAD_REQUEST = "400"
12
+ FORBIDDEN = "403"
13
+ SERVICE_UNAVAILABLE = "503"
14
+ INTERNAL_ERROR = "500"
15
+
16
+ SUPPORTED_COUNTRY_CODES = ['+91']
17
+
18
+ def initialize()
19
+ super
20
+ self.required_config([:provider_url, :username, :api_key, :keyword])
21
+ @url = URI.parse(self.config_option(:provider_url))
22
+ @username = self.config_option(:username)
23
+ @api_key = self.config_option(:api_key)
24
+ @keyword = self.config_option(:keyword)
25
+ @custom_message = SOAP::SOAPRawString.new("<%= custom_message %>")
26
+ end
27
+
28
+ def supported_country_code
29
+ SUPPORTED_COUNTRY_CODE
30
+ end
31
+
32
+ def send_sms(message, number, options = {})
33
+ call_service make_request(message, [number]), options[:response]
34
+ end
35
+
36
+ def send_bulk_sms(message, numbers, options = {})
37
+ call_service make_request(message, numbers), options[:response]
38
+ end
39
+
40
+ def send_bulk_sms_with_insertion(message, insertions = {}, options = {})
41
+ call_service make_request_with_insertion(insert_values(message, insertions)), options[:response]
42
+ end
43
+
44
+ private
45
+
46
+ def call_service(request, response = Response.new)
47
+ request_xml = XSD::Mapping.obj2xml(request, 'request')
48
+ http_response = Net::HTTP.post_form(@url, {'xml_request' => request_xml})
49
+
50
+ case http_response.code
51
+ when SUCCESSFUL
52
+ response = parse_response(http_response.body, response)
53
+ when BAD_REQUEST
54
+ response.message = 'Bad Request'
55
+ when FORBIDDEN
56
+ response.message = 'Forbidden. You do not have permission to access this resource.'
57
+ when SERVICE_UNAVAILABLE
58
+ response.message = 'Service unavailable. An internal problem prevented us from returning data to you.'
59
+ when INTERNAL_ERROR
60
+ response.message = 'Internal error.'
61
+ end
62
+ response
63
+ end
64
+
65
+ def make_request(message, numbers)
66
+ recipients = Recipients.new()
67
+ numbers.each do |number|
68
+ recipients << Recipient.new(parse_number(number))
69
+ end
70
+
71
+ Request.new(@username, @api_key, message, @keyword, recipients)
72
+ end
73
+
74
+ def make_request_with_insertion(number_message_map)
75
+ recipients = Recipients.new()
76
+ number_message_map.each do |number, message|
77
+ recipients << Recipient.new(parse_number(number), Attributes.new(message))
78
+ end
79
+ Request.new(@username, @api_key, @custom_message, @keyword, recipients)
80
+ end
81
+
82
+ def parse_response(response_xml, response)
83
+ res_obj = XSD::Mapping.xml2obj(response_xml)
84
+ recipients = res_obj.successful_recipients.recipient
85
+ if recipients.instance_of? Array
86
+ recipients.each do |recipient|
87
+ response.add(recipient.xmlattr_phone_number, Response::SUCCESSFUL)
88
+ end
89
+ else
90
+ response.add(recipients.xmlattr_phone_number, Response::SUCCESSFUL)
91
+ end
92
+ #For failed recipients
93
+ if res_obj.failed_recipients.instance_of? String
94
+ failed_recipients = res_obj.failed_recipients.split(',')
95
+ failed_recipients.each do |recipient|
96
+ response.add(recipient, Response::FAIL)
97
+ end
98
+ end
99
+ response
100
+ end
101
+
102
+ def parse_number(number)
103
+ number.split(' ').last
104
+ end
105
+
106
+ end
107
+
108
+ end
@@ -0,0 +1,32 @@
1
+ <?xml version="1.0" encoding="UTF-8" ?>
2
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
3
+ <xs:complexType name="attributes">
4
+ <xs:sequence>
5
+ <xs:element name="custom_message" type="xs:string"/>
6
+ </xs:sequence>
7
+ </xs:complexType>
8
+
9
+ <xs:complexType name="recipient">
10
+ <xs:sequence>
11
+ <xs:element name="phone_number" type="xs:string"/>
12
+ <xs:element name="attributes" type="attributes"/>
13
+ </xs:sequence>
14
+ </xs:complexType>
15
+
16
+ <xs:complexType name="recipients">
17
+ <xs:sequence>
18
+ <xs:element name="recipient" type="recipient" maxOccurs="unbounded"/>
19
+ </xs:sequence>
20
+ </xs:complexType>
21
+
22
+ <xs:complexType name="request">
23
+ <xs:sequence>
24
+ <xs:element name="user" type="xs:string"/>
25
+ <xs:element name="app_id" type="xs:string"/>
26
+ <xs:element name="message" type="xs:string"/>
27
+ <xs:element name="keyword" type="xs:string"/>
28
+ <xs:element name="recipients" type="recipients" />
29
+ </xs:sequence>
30
+ </xs:complexType>
31
+
32
+ </xs:schema>
@@ -0,0 +1,60 @@
1
+ require 'xsd/qname'
2
+ require "xsd/mapping"
3
+
4
+ module VmoboSchema
5
+ # {}attributes
6
+ class Attributes
7
+ @@schema_type = "attributes"
8
+ @@schema_ns = nil
9
+ @@schema_element = [["custom_message", "SOAP::SOAPString"]]
10
+
11
+ attr_accessor :custom_message
12
+
13
+ def initialize(custom_message = nil)
14
+ @custom_message = custom_message
15
+ end
16
+ end
17
+
18
+ # {}recipient
19
+ class Recipient
20
+ @@schema_type = "recipient"
21
+ @@schema_ns = nil
22
+ @@schema_element = [["phone_number", "SOAP::SOAPString"], ["attributes", "Attributes"]]
23
+
24
+ attr_accessor :phone_number
25
+ attr_accessor :attributes
26
+
27
+ def initialize(phone_number = nil, attributes = nil)
28
+ @phone_number = phone_number
29
+ @attributes = attributes
30
+ end
31
+ end
32
+
33
+ # {}recipients
34
+ class Recipients < ::Array
35
+ @@schema_type = "recipient"
36
+ @@schema_ns = nil
37
+ @@schema_element = [["recipient", ["Recipient[]", XSD::QName.new(nil, "recipient")]]]
38
+ end
39
+
40
+ # {}request
41
+ class Request
42
+ @@schema_type = "request"
43
+ @@schema_ns = nil
44
+ @@schema_element = [["user", "SOAP::SOAPString"], ["app_id", "SOAP::SOAPString"], ["message", "SOAP::SOAPString"], ["keyword", "SOAP::SOAPString"], ["recipients", "Recipients"]]
45
+
46
+ attr_accessor :user
47
+ attr_accessor :app_id
48
+ attr_accessor :message
49
+ attr_accessor :keyword
50
+ attr_accessor :recipients
51
+
52
+ def initialize(user = nil, app_id = nil, message = nil, keyword = nil, recipients = nil)
53
+ @user = user
54
+ @app_id = app_id
55
+ @message = message
56
+ @keyword = keyword
57
+ @recipients = recipients
58
+ end
59
+ end
60
+ end
data/lib/splat.rb ADDED
@@ -0,0 +1,89 @@
1
+
2
+ $:.unshift File.dirname(__FILE__)
3
+
4
+ require 'active_support'
5
+ require File.dirname(__FILE__) + '/splat/gateway'
6
+ require File.dirname(__FILE__) + '/splat/error'
7
+ require File.dirname(__FILE__) + '/splat/response'
8
+
9
+
10
+ module Splat
11
+
12
+ class Base
13
+
14
+ @@gateways = {}
15
+ @vendor = nil
16
+ attr_reader :number_format_regx
17
+
18
+ def initialize(vendor_name)
19
+ self.vendor = vendor_name
20
+
21
+ if number_format = Gateway.default_configuration['number_format']
22
+ @number_format_regx = Regexp.new(number_format)
23
+ else
24
+ raise SplatError.new("default: number_format not define in splat.yml") unless number_format
25
+ end
26
+
27
+ end
28
+
29
+ def send_sms(message, number, options = {})
30
+ options[:response] = Response.new
31
+ unless validate_phone?(number)
32
+ return options[:response].invalid_phone_format(number)
33
+ end
34
+ get_vender_object(vendor).send_sms(message, number, options)
35
+ end
36
+
37
+ def send_bulk_sms(message, numbers = [], options = {})
38
+ options[:response] = validate_phones(numbers)
39
+ get_vender_object(vendor).send_bulk_sms(message, numbers - options[:response].invalid_phone_numbers, options)
40
+ end
41
+
42
+ def send_bulk_sms_with_insertion(message, insertions ={}, options = {})
43
+ options[:response] = validate_phones(insertions.keys)
44
+
45
+ insertions.delete_if do |key, value|
46
+ !options[:response].phone_numbers[key].nil?
47
+ end
48
+
49
+ get_vender_object(vendor).send_bulk_sms_with_insertion(message, insertions, options)
50
+ end
51
+
52
+ def vendor
53
+ raise SplatError.new('Vendor is nil.Set vendor name.') unless @vendor
54
+ @vendor
55
+ end
56
+
57
+ private
58
+
59
+ def vendor=(vendor_name)
60
+ raise SplatNoGatewaySupportError.new("#{vendor_name} not supported") unless get_vender_object(vendor_name)
61
+ @vendor = vendor_name
62
+ end
63
+
64
+ def get_vender_object(name)
65
+ unless @@gateways[name.to_s]
66
+ @@gateways[name.to_s] = Gateway[name].new if Gateway[name]
67
+ end
68
+ @@gateways[name.to_s]
69
+ end
70
+
71
+ def validate_phone?(number)
72
+ number.to_s =~ number_format_regx
73
+ end
74
+
75
+ def validate_phones(numbers)
76
+ response = Response.new
77
+ numbers.each do |number|
78
+ response.invalid_phone_format(number) unless validate_phone?(number)
79
+ end
80
+ response
81
+ end
82
+
83
+ end
84
+
85
+ end
86
+
87
+
88
+
89
+
data/test/helper.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
7
+ require 'splat'
8
+
9
+ class Test::Unit::TestCase
10
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestSplat < Test::Unit::TestCase
4
+ should "probably rename this file and start testing for real" do
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
data/test.rb ADDED
@@ -0,0 +1,44 @@
1
+
2
+ require 'rubygems'
3
+ require 'lib/splat'
4
+
5
+ number = '+91 9850888082'
6
+
7
+ gateways = {}
8
+ #gateways['bulksms'] = Splat::Base.new(:bulksmspune)
9
+ #gateways['vmobo'] = Splat::Base.new(:vmobo)
10
+ gateways['clickatell'] = Splat::Base.new(:clickatell)
11
+
12
+
13
+ =begin
14
+ #number = '+1 5599726538'
15
+ number = '+91 9881395656'
16
+ msg = "Testing for splat"
17
+ gateways.each do |provider, obj|
18
+ p "Sending SMS via #{provider}"
19
+ p obj.send_sms(msg, number)
20
+ p "SMS Sent successfully via #{provider}"
21
+ end
22
+ p "========================"
23
+
24
+ msg = "Testing for splat"
25
+ numbers = ['+91 9960054954', '+91 9850888082']
26
+ gateways.each do |provider, obj|
27
+ p obj.send_bulk_sms(msg, numbers)
28
+ p "Bulk Send successfully via #{provider}"
29
+ end
30
+ p "========================"
31
+
32
+ =end
33
+ custom_msg = "Hi $1, hold on to your $2."
34
+ options = {'+91 9960054954' => ['Sagar', 'coffee'],
35
+ '+91 9850888082' => ['Gautam' , 'stocks']}
36
+ #'+91 9880397111' => ['Jane' , 'skirt']}
37
+
38
+ gateways.each do |provider, obj|
39
+ p obj.send_bulk_sms_with_insertion(custom_msg, options)
40
+ p "Bulk Send successfully via #{provider}"
41
+ end
42
+
43
+
44
+
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: josh-splat
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 2
9
+ version: 0.0.2
10
+ platform: ruby
11
+ authors:
12
+ - Gautam Rege
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-07-13 00:00:00 +05:30
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: thoughtbot-shoulda
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :development
31
+ version_requirements: *id001
32
+ description: "\n SPlat is an integration platform to make use of SMS integration really easy. Using this platform has the following advantages:<br/>\n\n <br/>\n * Single point of integration <br/>\n * Change vendors without changing code. <br/>\n * Send and receive SMS. <br/>\n * Generic Exception Handling. <br/>\n * Standardized reports. <br/>\n * SMS tagged user groups. <br/>\n * SMS bogus gateway for testing. <br/>\n * Scheduling SMS for delivery. <br/>\n "
33
+ email: info@joshsoftware.com
34
+ executables: []
35
+
36
+ extensions: []
37
+
38
+ extra_rdoc_files:
39
+ - LICENSE
40
+ - README.rdoc
41
+ files:
42
+ - .document
43
+ - .gitignore
44
+ - LICENSE
45
+ - README.rdoc
46
+ - Rakefile
47
+ - VERSION
48
+ - test/helper.rb
49
+ - test/test_splat.rb
50
+ - generators/splat_config/splat_generator.rb
51
+ - generators/splat_config/templates/splat.yml
52
+ - generators/splat_config/templates/vendors.yml
53
+ - init.rb
54
+ - lib/splat/configuration.rb
55
+ - lib/splat/error.rb
56
+ - lib/splat/gateway.rb
57
+ - lib/splat/initializer.rb
58
+ - lib/splat/insertion.rb
59
+ - lib/splat/request.rb
60
+ - lib/splat/response.rb
61
+ - lib/splat/utils.rb
62
+ - lib/splat/validator.rb
63
+ - lib/splat/vendors/bulksmspune/bulksmspune_gateway.rb
64
+ - lib/splat/vendors/clickatell/clickatell_gateway.rb
65
+ - lib/splat/vendors/vmobo/vmobo_gateway.rb
66
+ - lib/splat/vendors/vmobo/xml/schema/vmobo.xsd
67
+ - lib/splat/vendors/vmobo/xml/schema.rb
68
+ - lib/splat.rb
69
+ - test.rb
70
+ has_rdoc: true
71
+ homepage: http://github.com/gautamrege/splat
72
+ licenses: []
73
+
74
+ post_install_message:
75
+ rdoc_options:
76
+ - --charset=UTF-8
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ segments:
84
+ - 0
85
+ version: "0"
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ segments:
91
+ - 0
92
+ version: "0"
93
+ requirements: []
94
+
95
+ rubyforge_project:
96
+ rubygems_version: 1.3.6
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: SPlat is an integration platform to make use of SMS integration really easy
100
+ test_files:
101
+ - test/helper.rb
102
+ - test/test_splat.rb