josh-splat 0.0.2

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/.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