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 +5 -0
- data/.gitignore +22 -0
- data/LICENSE +20 -0
- data/README.rdoc +32 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/generators/splat_config/splat_generator.rb +16 -0
- data/generators/splat_config/templates/splat.yml +28 -0
- data/generators/splat_config/templates/vendors.yml +19 -0
- data/init.rb +2 -0
- data/lib/splat/configuration.rb +35 -0
- data/lib/splat/error.rb +23 -0
- data/lib/splat/gateway.rb +118 -0
- data/lib/splat/initializer.rb +13 -0
- data/lib/splat/insertion.rb +17 -0
- data/lib/splat/request.rb +32 -0
- data/lib/splat/response.rb +29 -0
- data/lib/splat/utils.rb +9 -0
- data/lib/splat/validator.rb +14 -0
- data/lib/splat/vendors/bulksmspune/bulksmspune_gateway.rb +51 -0
- data/lib/splat/vendors/clickatell/clickatell_gateway.rb +119 -0
- data/lib/splat/vendors/vmobo/vmobo_gateway.rb +108 -0
- data/lib/splat/vendors/vmobo/xml/schema/vmobo.xsd +32 -0
- data/lib/splat/vendors/vmobo/xml/schema.rb +60 -0
- data/lib/splat.rb +89 -0
- data/test/helper.rb +10 -0
- data/test/test_splat.rb +7 -0
- data/test.rb +44 -0
- metadata +102 -0
data/.document
ADDED
data/.gitignore
ADDED
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,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
|
data/lib/splat/error.rb
ADDED
@@ -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,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
|
data/lib/splat/utils.rb
ADDED
@@ -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
data/test/test_splat.rb
ADDED
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
|