aspsms 0.97
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/LICENSE +20 -0
- data/README +28 -0
- data/bin/aspsms +186 -0
- data/lib/aspsms.rb +410 -0
- metadata +67 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (C) 2005-2011, Daniel Roethlisberger <daniel@roe.ch>
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use, with or without modification, are permitted
|
5
|
+
provided that the following conditions are met:
|
6
|
+
1. Redistributions must retain the above copyright notice, this list of
|
7
|
+
conditions and the following disclaimer.
|
8
|
+
2. The name of the author may not be used to endorse or promote products
|
9
|
+
derived from this software without specific prior written permission.
|
10
|
+
|
11
|
+
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
12
|
+
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
13
|
+
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
14
|
+
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
15
|
+
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
16
|
+
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
17
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
18
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
19
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
20
|
+
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
Ruby ASPSMS -- aspsms.com short message service gateway library and client
|
2
|
+
http://www.roe.ch/ASPSMS
|
3
|
+
|
4
|
+
Copyright (C) 2005-2011, Daniel Roethlisberger <daniel@roe.ch>
|
5
|
+
All rights reserved.
|
6
|
+
|
7
|
+
Ruby ASPSMS is both a ruby library and a command line client for painfree,
|
8
|
+
UNIX-style interaction with the aspsms.com short message gateways.
|
9
|
+
|
10
|
+
Written to conform with the ASPSMS XML Interface Specs 1.91, 2007-12-05.
|
11
|
+
- Support for text SMS, show credits, and originator unlocking/testing.
|
12
|
+
- Conforms to ASPSMS failure safety recommendations.
|
13
|
+
- Delivery status notification is supported by the low-level library
|
14
|
+
but not exposed in the easy API nor the command line client.
|
15
|
+
|
16
|
+
Configuration is read from ~/.aspsms or [/usr/local]/etc/aspsms by default:
|
17
|
+
|
18
|
+
# ASPSMS configuration
|
19
|
+
# mandatory options:
|
20
|
+
userkey XYZXYZXYZXYZ
|
21
|
+
password y0UrPasSw0rD
|
22
|
+
# optional default originator:
|
23
|
+
originator +41XXXXXXXXX
|
24
|
+
# optional gateway override:
|
25
|
+
gateway othergateway:port
|
26
|
+
|
27
|
+
Make sure to set sensible permissions (0600 is a good start).
|
28
|
+
|
data/bin/aspsms
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# vim: set et ts=2 sw=2 ft=ruby:
|
3
|
+
#
|
4
|
+
# Ruby ASPSMS -- aspsms.com short message service gateway library and client
|
5
|
+
# http://www.roe.ch/ASPSMS
|
6
|
+
#
|
7
|
+
# Copyright (C) 2005-2011, Daniel Roethlisberger <daniel@roe.ch>
|
8
|
+
# All rights reserved.
|
9
|
+
#
|
10
|
+
# Redistribution and use, with or without modification, are permitted
|
11
|
+
# provided that the following conditions are met:
|
12
|
+
# 1. Redistributions must retain the above copyright notice, this list of
|
13
|
+
# conditions and the following disclaimer.
|
14
|
+
# 2. The name of the author may not be used to endorse or promote products
|
15
|
+
# derived from this software without specific prior written permission.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
18
|
+
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
19
|
+
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
20
|
+
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
21
|
+
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
22
|
+
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
23
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
24
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
25
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
26
|
+
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
27
|
+
|
28
|
+
require 'aspsms'
|
29
|
+
require 'getoptlong'
|
30
|
+
|
31
|
+
class String
|
32
|
+
def is_phone_number?
|
33
|
+
self.match(/^\+[0-9]{2,20}$/)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
$bn = File.basename($0)
|
38
|
+
|
39
|
+
def usage
|
40
|
+
puts <<EOF
|
41
|
+
Usage:
|
42
|
+
#{$bn} [-M] [-v] [-c file] [-o orig] [-t [n]] [-f] [-b] rcpt [...]
|
43
|
+
#{$bn} -C [-v] [-c file]
|
44
|
+
#{$bn} -O [-v] [-c file] [-o orig]
|
45
|
+
#{$bn} -U [-v] [-c file] [-o orig] [-u code]
|
46
|
+
Actions:
|
47
|
+
-M send text message read from stdin (implied if recipients are given)
|
48
|
+
-C retrieve credit balance
|
49
|
+
-O check originator authorization
|
50
|
+
-U send originator unlock code; unlock if used with -u
|
51
|
+
Parameters:
|
52
|
+
-o specify originator (phone number or up to 11 characters)
|
53
|
+
-t truncate message to n bytes, or 160 if used without argument
|
54
|
+
-f/-b flashing/blinking text SMS
|
55
|
+
-u specify unlock code
|
56
|
+
-c specify config file (default: ~/.aspsms, [/usr/local]/etc/aspsms)
|
57
|
+
-v verbose
|
58
|
+
-h/-V show help/version and exit
|
59
|
+
Recipient phone number format: international format, e.g. +41XXXXXXXXX
|
60
|
+
EOF
|
61
|
+
end
|
62
|
+
|
63
|
+
def die(reason, detail = nil)
|
64
|
+
STDERR.print "#{$0}: #{reason}"
|
65
|
+
STDERR.puts detail.nil? ? '' : " -- #{detail}"
|
66
|
+
exit 1
|
67
|
+
end
|
68
|
+
|
69
|
+
actions = []
|
70
|
+
truncate = -1
|
71
|
+
code = nil
|
72
|
+
cf = nil
|
73
|
+
verbose = false
|
74
|
+
opts = {}
|
75
|
+
begin
|
76
|
+
GetoptLong.new(
|
77
|
+
[ "-M", GetoptLong::NO_ARGUMENT ],
|
78
|
+
[ "-C", GetoptLong::NO_ARGUMENT ],
|
79
|
+
[ "-O", GetoptLong::NO_ARGUMENT ],
|
80
|
+
[ "-U", GetoptLong::NO_ARGUMENT ],
|
81
|
+
[ "-o", GetoptLong::REQUIRED_ARGUMENT ],
|
82
|
+
[ "-t", GetoptLong::OPTIONAL_ARGUMENT ],
|
83
|
+
[ "-f", GetoptLong::NO_ARGUMENT ],
|
84
|
+
[ "-b", GetoptLong::NO_ARGUMENT ],
|
85
|
+
[ "-u", GetoptLong::REQUIRED_ARGUMENT ],
|
86
|
+
[ "-c", GetoptLong::REQUIRED_ARGUMENT ],
|
87
|
+
[ "-v", GetoptLong::NO_ARGUMENT ],
|
88
|
+
[ "-h", GetoptLong::NO_ARGUMENT ],
|
89
|
+
[ "-V", GetoptLong::NO_ARGUMENT ]
|
90
|
+
).each do |opt, arg|
|
91
|
+
case opt
|
92
|
+
when '-h'
|
93
|
+
usage
|
94
|
+
exit 0
|
95
|
+
when '-V'
|
96
|
+
puts "#{$bn}: Ruby ASPSMS"
|
97
|
+
puts "Copyright (C) 2005-2011, Daniel Roethlisberger <daniel@roe.ch>"
|
98
|
+
puts "http://www.roe.ch/ASPSMS"
|
99
|
+
exit 0
|
100
|
+
when '-M'
|
101
|
+
actions << :textsms
|
102
|
+
when '-C'
|
103
|
+
actions << :credits
|
104
|
+
when '-O'
|
105
|
+
actions << :auth
|
106
|
+
when '-U'
|
107
|
+
actions << :unlock
|
108
|
+
when '-o'
|
109
|
+
opts[:originator] = arg
|
110
|
+
when '-t'
|
111
|
+
unless arg.empty? || arg.to_i > 0
|
112
|
+
die('invalid option argument', '-t expects positive integer')
|
113
|
+
end
|
114
|
+
truncate = arg.empty? ? 160 : arg.to_i
|
115
|
+
when '-f'
|
116
|
+
opts[:flashing] = true
|
117
|
+
when '-b'
|
118
|
+
opts[:blinking] = true
|
119
|
+
when '-u'
|
120
|
+
code = arg
|
121
|
+
when '-c'
|
122
|
+
cf = arg
|
123
|
+
when '-v'
|
124
|
+
verbose = true
|
125
|
+
end
|
126
|
+
end
|
127
|
+
rescue GetoptLong::MissingArgument, GetoptLong::InvalidOption
|
128
|
+
exit 1
|
129
|
+
end
|
130
|
+
actions << :textsms unless ARGV.empty? || actions.include?(:textsms)
|
131
|
+
|
132
|
+
if actions.length > 1
|
133
|
+
die('incompatible options', '-M, -C, -O and -U mutually exclusive')
|
134
|
+
end
|
135
|
+
if actions[0] != :textsms && (truncate > 0 ||
|
136
|
+
opts.has_key?(:flashing) ||
|
137
|
+
opts.has_key?(:blinking))
|
138
|
+
die('incompatible options', '-t, -f and -b require -M')
|
139
|
+
end
|
140
|
+
if actions[0] != :unlock && !code.nil?
|
141
|
+
die('incompatible options', '-c requires -U')
|
142
|
+
end
|
143
|
+
if actions.include?(:credits) && opts.has_key?(:originator)
|
144
|
+
die('incompatible options', '-o requires -M, -O or -U')
|
145
|
+
end
|
146
|
+
|
147
|
+
# XXX catch exceptions and print more sensible error messages;
|
148
|
+
# add custom exception classes to the ASPSMS:: namespace.
|
149
|
+
aspsms = ASPSMS::Easy.new(cf)
|
150
|
+
actions.each do |action|
|
151
|
+
case action
|
152
|
+
when :credits
|
153
|
+
credits = aspsms.show_credits
|
154
|
+
print credits
|
155
|
+
puts verbose ? ' credits' : ''
|
156
|
+
exit credits.to_i > 0 ? 0 : 1
|
157
|
+
when :auth
|
158
|
+
res = aspsms.check_originator_authorization(opts)
|
159
|
+
puts res ? 'authorized' : 'not authorized' if verbose
|
160
|
+
exit res ? 0 : 1
|
161
|
+
when :unlock
|
162
|
+
if code.nil?
|
163
|
+
res = aspsms.send_originator_unlock_code(opts)
|
164
|
+
puts 'sent unlock code' if verbose
|
165
|
+
else
|
166
|
+
res = aspsms.unlock_originator(code, opts)
|
167
|
+
puts 'unlocked originator' if verbose
|
168
|
+
end
|
169
|
+
exit 0
|
170
|
+
when :textsms
|
171
|
+
rcpt = ARGV.dup
|
172
|
+
rcpt.each do |rcpt|
|
173
|
+
unless rcpt.is_phone_number?
|
174
|
+
die('invalid phone number', rcpt)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
text = STDIN.read.gsub(/\s+/, ' ').gsub(/^\s+|\s+$/, '')
|
178
|
+
text = text[0..(truncate - 1)] if truncate > 0
|
179
|
+
credits_used = aspsms.send_text_sms(text, rcpt, opts)
|
180
|
+
puts "message delivered (#{credits_used} credits used)" if verbose
|
181
|
+
exit 0
|
182
|
+
end
|
183
|
+
end
|
184
|
+
usage
|
185
|
+
exit 1
|
186
|
+
|
data/lib/aspsms.rb
ADDED
@@ -0,0 +1,410 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# vim: set et ts=2 sw=2:
|
3
|
+
#
|
4
|
+
# Ruby ASPSMS -- aspsms.com short message service gateway library and client
|
5
|
+
# http://www.roe.ch/ASPSMS
|
6
|
+
#
|
7
|
+
# Copyright (C) 2005-2011, Daniel Roethlisberger <daniel@roe.ch>
|
8
|
+
# All rights reserved.
|
9
|
+
#
|
10
|
+
# Redistribution and use, with or without modification, are permitted
|
11
|
+
# provided that the following conditions are met:
|
12
|
+
# 1. Redistributions must retain the above copyright notice, this list of
|
13
|
+
# conditions and the following disclaimer.
|
14
|
+
# 2. The name of the author may not be used to endorse or promote products
|
15
|
+
# derived from this software without specific prior written permission.
|
16
|
+
#
|
17
|
+
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
18
|
+
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
19
|
+
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
20
|
+
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
21
|
+
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
22
|
+
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
23
|
+
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
24
|
+
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
25
|
+
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
26
|
+
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
27
|
+
|
28
|
+
require 'rexml/document'
|
29
|
+
require 'net/http'
|
30
|
+
require 'uri'
|
31
|
+
require 'iconv'
|
32
|
+
|
33
|
+
module ASPSMS
|
34
|
+
FILES = [ "#{ENV['HOME']}/.aspsms", '/etc/aspsms', '/usr/local/etc/aspsms' ]
|
35
|
+
CHARSET = 'latin1'
|
36
|
+
|
37
|
+
# Represents a configuration, file based or hash based.
|
38
|
+
class Config
|
39
|
+
def self.charset_from_environment
|
40
|
+
['LC_ALL', 'LC_CTYPE', 'LANG'].each do |key|
|
41
|
+
if ENV.has_key?(key)
|
42
|
+
return ENV[key].match(/\./) ? ENV[key].sub(/^.*\./, '') : CHARSET
|
43
|
+
end
|
44
|
+
end
|
45
|
+
return CHARSET
|
46
|
+
end
|
47
|
+
|
48
|
+
@password, @userkey, @originator, @gateway, @charset = nil,nil,nil,nil,nil
|
49
|
+
|
50
|
+
def initialize(confdata)
|
51
|
+
if confdata.kind_of?(Hash)
|
52
|
+
set(confdata)
|
53
|
+
elsif confdata.kind_of?(String)
|
54
|
+
load(confdata)
|
55
|
+
else
|
56
|
+
autoload
|
57
|
+
end
|
58
|
+
@charset = ASPSMS::Config.charset_from_environment
|
59
|
+
if ENV.has_key?('http_proxy')
|
60
|
+
uri = URI.parse(ENV['http_proxy'])
|
61
|
+
user, pass = uri.userinfo.split(/:/) if uri.userinfo
|
62
|
+
@http_class = Net::HTTP::Proxy(uri.host, uri.port, user, pass)
|
63
|
+
else
|
64
|
+
@http_class = Net::HTTP
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def autoload
|
69
|
+
FILES.each do |fn|
|
70
|
+
begin
|
71
|
+
load(fn)
|
72
|
+
return
|
73
|
+
rescue Errno::ENOENT
|
74
|
+
# ignore if not found
|
75
|
+
end
|
76
|
+
raise "No configuration file found (#{FILES.join(' ')})"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def load(fn)
|
81
|
+
conf = {}
|
82
|
+
File.open(fn).each do |line|
|
83
|
+
line.chomp.scan(/^\s*([a-zA-Z0-9]+)\s+([^\s#]+)/) do |k,v|
|
84
|
+
case k.downcase
|
85
|
+
when 'password'
|
86
|
+
conf[:password] = v
|
87
|
+
when 'userkey'
|
88
|
+
conf[:userkey] = v
|
89
|
+
when 'sender' # backwards compat
|
90
|
+
conf[:originator] = v
|
91
|
+
when 'originator'
|
92
|
+
conf[:originator] = v
|
93
|
+
when 'gateway'
|
94
|
+
conf[:gateway] = v
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
set(conf)
|
99
|
+
end
|
100
|
+
|
101
|
+
def set(conf)
|
102
|
+
@password = conf[:password] if conf.has_key?(:password)
|
103
|
+
@userkey = conf[:userkey] if conf.has_key?(:userkey)
|
104
|
+
@originator = conf[:originator] if conf.has_key?(:originator)
|
105
|
+
@gateway = conf[:gateway] if conf.has_key?(:gateway)
|
106
|
+
|
107
|
+
raise "#{fn}: 'userkey' missing!" if @userkey.nil?
|
108
|
+
raise "#{fn}: 'password' missing!" if @password.nil?
|
109
|
+
if !@gateway.nil? && !@gateway.match(/^[a-zA-Z0-9\[\]:._-]+:[0-9]{1,5}$/)
|
110
|
+
raise "#{fn}: 'gateway' not in format 'host:port'!"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def userkey
|
115
|
+
@userkey
|
116
|
+
end
|
117
|
+
|
118
|
+
def password
|
119
|
+
@password
|
120
|
+
end
|
121
|
+
|
122
|
+
def originator
|
123
|
+
@originator.nil? ? 'aspsms' : @originator
|
124
|
+
end
|
125
|
+
|
126
|
+
def gateways
|
127
|
+
if @gateway.nil?
|
128
|
+
return [ 'xml1.aspsms.com:5061', 'xml2.aspsms.com:5098',
|
129
|
+
'xml1.aspsms.com:5098', 'xml2.aspsms.com:5061' ]
|
130
|
+
else
|
131
|
+
return [ @gateway ]
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def useragent
|
136
|
+
"ASPSMS::Gateway (http://www.roe.ch/ASPSMS)"
|
137
|
+
end
|
138
|
+
|
139
|
+
def http_class
|
140
|
+
@http_class
|
141
|
+
end
|
142
|
+
|
143
|
+
def utf8(str)
|
144
|
+
unless @charset.match(/utf-?8/i)
|
145
|
+
return Iconv::iconv('utf-8', @charset, str)[0]
|
146
|
+
else
|
147
|
+
return str
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Represents a request of specific types.
|
153
|
+
# Handles construction of the request XML document.
|
154
|
+
class Request
|
155
|
+
class Abstract
|
156
|
+
include REXML
|
157
|
+
|
158
|
+
attr_accessor :userkey, :password
|
159
|
+
|
160
|
+
def initialize(cfg)
|
161
|
+
@cfg = cfg
|
162
|
+
@userkey = @cfg.userkey
|
163
|
+
@password = @cfg.password
|
164
|
+
end
|
165
|
+
|
166
|
+
def to_s
|
167
|
+
doc = Document.new
|
168
|
+
doc << REXML::XMLDecl.new('1.0', 'ISO-8859-1')
|
169
|
+
root = Element.new('aspsms')
|
170
|
+
root.elements.add(Element.new('Userkey').
|
171
|
+
add_text(@cfg.utf8(userkey)))
|
172
|
+
root.elements.add(Element.new('Password').
|
173
|
+
add_text(@cfg.utf8(password)))
|
174
|
+
each_element do |element|
|
175
|
+
root.elements.add(element)
|
176
|
+
end
|
177
|
+
root.elements.add(Element.new('Action').
|
178
|
+
add_text(self.class.name.gsub(/^.*::/, '')))
|
179
|
+
doc << root
|
180
|
+
doc.to_s
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
class SendTextSMS < Abstract
|
185
|
+
attr_accessor :originator, :recipients, :text, :flashing, :blinking,
|
186
|
+
:tx_refs, :url_buffered, :url_nondelivery, :url_delivery
|
187
|
+
def initialize(cfg)
|
188
|
+
super(cfg)
|
189
|
+
@originator = @cfg.originator
|
190
|
+
@recipients = []
|
191
|
+
@text = ''
|
192
|
+
@flashing = false
|
193
|
+
@blinking = false
|
194
|
+
@tx_refs = []
|
195
|
+
@url_buffered = ''
|
196
|
+
@url_delivery = ''
|
197
|
+
@url_nondelivery = ''
|
198
|
+
end
|
199
|
+
def each_element
|
200
|
+
yield REXML::Element.new('Originator').add_text(@cfg.utf8(originator))
|
201
|
+
@recipients = [ @recipients ] unless @recipients.kind_of?(Array)
|
202
|
+
txr = @tx_refs.dup
|
203
|
+
recipients.each do |recipient|
|
204
|
+
rcpt = REXML::Element.new('Recipient')
|
205
|
+
rcpt.elements.add(Element.new('PhoneNumber').
|
206
|
+
add_text(@cfg.utf8(recipient)))
|
207
|
+
rcpt.elements.add(Element.new('TransRefNumber').
|
208
|
+
add_text(txr.shift)) unless txr.empty?
|
209
|
+
yield rcpt
|
210
|
+
end
|
211
|
+
yield REXML::Element.new('MessageData').add_text(@cfg.utf8(text))
|
212
|
+
yield REXML::Element.new('UsedCredits').add_text('1')
|
213
|
+
yield REXML::Element.new('FlashingSMS').add_text('1') if flashing
|
214
|
+
yield REXML::Element.new('BlinkingSMS').add_text('1') if blinking
|
215
|
+
yield REXML::Element.new('URLBufferedMessageNotification').
|
216
|
+
add_text(@cfg.utf8(url_buffered)) unless url_buffered.empty?
|
217
|
+
yield REXML::Element.new('URLDeliveryNotification').
|
218
|
+
add_text(@cfg.utf8(url_delivery)) unless url_delivery.empty?
|
219
|
+
yield REXML::Element.new('URLNonDeliveryNotification').
|
220
|
+
add_text(@cfg.utf8(url_nondelivery)) unless url_nondelivery.empty?
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
class SendOriginatorUnlockCode < Abstract
|
225
|
+
attr_accessor :originator
|
226
|
+
def initialize(cfg)
|
227
|
+
super(cfg)
|
228
|
+
@originator = @cfg.originator
|
229
|
+
end
|
230
|
+
|
231
|
+
def each_element
|
232
|
+
yield REXML::Element.new('Originator').add_text(@cfg.utf8(originator))
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
class UnlockOriginator < Abstract
|
237
|
+
attr_accessor :originator, :code
|
238
|
+
def initialize(cfg)
|
239
|
+
super(cfg)
|
240
|
+
@originator = @cfg.originator
|
241
|
+
@code = ''
|
242
|
+
end
|
243
|
+
|
244
|
+
def each_element
|
245
|
+
yield REXML::Element.new('Originator').
|
246
|
+
add_text(@cfg.utf8(originator))
|
247
|
+
yield REXML::Element.new('OriginatorUnlockCode').
|
248
|
+
add_text(@cfg.utf8(code))
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
class CheckOriginatorAuthorization < Abstract
|
253
|
+
attr_accessor :originator
|
254
|
+
def initialize(cfg)
|
255
|
+
super(cfg)
|
256
|
+
@originator = @cfg.originator
|
257
|
+
end
|
258
|
+
|
259
|
+
def each_element
|
260
|
+
yield REXML::Element.new('Originator').add_text(@cfg.utf8(originator))
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
class ShowCredits < Abstract
|
265
|
+
def initialize(cfg)
|
266
|
+
super(cfg)
|
267
|
+
end
|
268
|
+
|
269
|
+
def each_element
|
270
|
+
# no additional elements
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
# Represents a response to a request of any type.
|
276
|
+
# Handles parsing of the response XML document and
|
277
|
+
# provides easy access to data fields therein.
|
278
|
+
class Response
|
279
|
+
def self.parse(cfg, xmlstr)
|
280
|
+
args = {}
|
281
|
+
doc = REXML::Document.new(xmlstr)
|
282
|
+
doc.root.each_element('*') do |element|
|
283
|
+
args[element.name] = element.text.chomp
|
284
|
+
end
|
285
|
+
raise "'ErrorCode' missing!" if args['ErrorCode'].nil?
|
286
|
+
raise "'ErrorDescription' missing!" if args['ErrorDescription'].nil?
|
287
|
+
Response.new(cfg, args)
|
288
|
+
end
|
289
|
+
|
290
|
+
def initialize(cfg, args)
|
291
|
+
@cfg = cfg
|
292
|
+
@args = args
|
293
|
+
end
|
294
|
+
# 1 Ok
|
295
|
+
# 30 Originator not Authorized
|
296
|
+
# 31 Originator already Authorized
|
297
|
+
def success?
|
298
|
+
['1', '30', '31'].include?(errno)
|
299
|
+
end
|
300
|
+
|
301
|
+
def authorized?
|
302
|
+
errno == '31'
|
303
|
+
end
|
304
|
+
|
305
|
+
def errno
|
306
|
+
@args['ErrorCode']
|
307
|
+
end
|
308
|
+
|
309
|
+
def errdesc
|
310
|
+
@args['ErrorDescription']
|
311
|
+
end
|
312
|
+
|
313
|
+
def credits
|
314
|
+
@args['Credits']
|
315
|
+
end
|
316
|
+
|
317
|
+
def credits_used
|
318
|
+
@args['CreditsUsed']
|
319
|
+
end
|
320
|
+
|
321
|
+
def to_s
|
322
|
+
"#{@args['ErrorCode']}: #{@args['ErrorDescription']} #{@args.inspect}"
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# Handles network communication with the ASPSMS gateways.
|
327
|
+
class Gateway
|
328
|
+
attr_reader :host, :port
|
329
|
+
def self.send(cfg, req)
|
330
|
+
cfg.gateways.each do |spec|
|
331
|
+
begin
|
332
|
+
resp = Gateway.new(cfg, spec).send(req)
|
333
|
+
return resp
|
334
|
+
rescue
|
335
|
+
# continue with next gateway in list
|
336
|
+
# XXX collect exception objects and add to raise below
|
337
|
+
end
|
338
|
+
end
|
339
|
+
raise 'Failed to send request to gateways!'
|
340
|
+
end
|
341
|
+
|
342
|
+
def initialize(cfg, gw)
|
343
|
+
@cfg = cfg
|
344
|
+
if gw.match(/^(.*):([0-9]{1,5})$/)
|
345
|
+
@host, @port = $1, $2
|
346
|
+
end
|
347
|
+
@http = @cfg.http_class.new(host, port)
|
348
|
+
end
|
349
|
+
|
350
|
+
def send(req)
|
351
|
+
headers = { 'User-Agent' => @cfg.useragent,
|
352
|
+
'Content-Type' => 'text/xml' }
|
353
|
+
resp = @http.post('/xmlsvr.asp', req.to_s, headers)
|
354
|
+
ASPSMS::Response.parse(@cfg, resp.body)
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
# Easy straightforward API class; for most use cases,
|
359
|
+
# this should be all that is needed.
|
360
|
+
class Easy
|
361
|
+
def initialize(conf = nil)
|
362
|
+
@cfg = ASPSMS::Config.new(conf)
|
363
|
+
end
|
364
|
+
|
365
|
+
def show_credits
|
366
|
+
request = ASPSMS::Request::ShowCredits.new(@cfg)
|
367
|
+
response = ASPSMS::Gateway.send(@cfg, request)
|
368
|
+
raise 'Error status from server!' unless response.success?
|
369
|
+
response.credits
|
370
|
+
end
|
371
|
+
|
372
|
+
def send_text_sms(text, recipients, opts = {})
|
373
|
+
request = ASPSMS::Request::SendTextSMS.new(@cfg)
|
374
|
+
request.text = text
|
375
|
+
request.recipients = recipients
|
376
|
+
request.originator = opts[:originator] if opts.has_key?(:originator)
|
377
|
+
request.flashing = opts[:flashing] if opts.has_key?(:flashing)
|
378
|
+
request.blinking = opts[:blinking] if opts.has_key?(:blinking)
|
379
|
+
response = ASPSMS::Gateway.send(@cfg, request)
|
380
|
+
raise 'Error status from server!' unless response.success?
|
381
|
+
response.credits_used
|
382
|
+
end
|
383
|
+
|
384
|
+
def send_originator_unlock_code(opts = {})
|
385
|
+
request = ASPSMS::Request::SendOriginatorUnlockCode.new(@cfg)
|
386
|
+
request.originator = opts[:originator] if opts.has_key?(:originator)
|
387
|
+
response = ASPSMS::Gateway.send(@cfg, request)
|
388
|
+
raise 'Error status from server!' unless response.success?
|
389
|
+
response
|
390
|
+
end
|
391
|
+
|
392
|
+
def unlock_originator(code, opts = {})
|
393
|
+
request = ASPSMS::Request::UnlockOriginator.new(@cfg)
|
394
|
+
request.code = code
|
395
|
+
request.originator = opts[:originator] if opts.has_key?(:originator)
|
396
|
+
response = ASPSMS::Gateway.send(@cfg, request)
|
397
|
+
raise 'Error status from server!' unless response.success?
|
398
|
+
response
|
399
|
+
end
|
400
|
+
|
401
|
+
def check_originator_authorization(opts = {})
|
402
|
+
request = ASPSMS::Request::CheckOriginatorAuthorization.new(@cfg)
|
403
|
+
request.originator = opts[:originator] if opts.has_key?(:originator)
|
404
|
+
response = ASPSMS::Gateway.send(@cfg, request)
|
405
|
+
raise 'Error status from server!' unless response.success?
|
406
|
+
response.authorized?
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: aspsms
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 201
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 97
|
9
|
+
version: "0.97"
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Daniel Roethlisberger
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-08-04 00:00:00 Z
|
18
|
+
dependencies: []
|
19
|
+
|
20
|
+
description: aspsms.com SMS gateway library and client
|
21
|
+
email: daniel@roe.ch
|
22
|
+
executables:
|
23
|
+
- aspsms
|
24
|
+
extensions: []
|
25
|
+
|
26
|
+
extra_rdoc_files: []
|
27
|
+
|
28
|
+
files:
|
29
|
+
- README
|
30
|
+
- LICENSE
|
31
|
+
- lib/aspsms.rb
|
32
|
+
- bin/aspsms
|
33
|
+
homepage: http://www.roe.ch/ASPSMS
|
34
|
+
licenses: []
|
35
|
+
|
36
|
+
post_install_message:
|
37
|
+
rdoc_options: []
|
38
|
+
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
hash: 3
|
47
|
+
segments:
|
48
|
+
- 0
|
49
|
+
version: "0"
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
hash: 3
|
56
|
+
segments:
|
57
|
+
- 0
|
58
|
+
version: "0"
|
59
|
+
requirements: []
|
60
|
+
|
61
|
+
rubyforge_project:
|
62
|
+
rubygems_version: 1.7.2
|
63
|
+
signing_key:
|
64
|
+
specification_version: 3
|
65
|
+
summary: aspsms.com SMS gateway library and client
|
66
|
+
test_files: []
|
67
|
+
|