smsinabox 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ nbproject
2
+ *.log
3
+ *.gem
4
+ *.swp
@@ -0,0 +1,13 @@
1
+ == 0.2.0 (Not released yet)
2
+
3
+ * Upgraded to V5 of mymobileapi.com API
4
+ * Cleaned up internals
5
+ * Better specs
6
+ * New delivery report class
7
+ * Sub-classed message class for supporting VCARD and other formats later
8
+ * Basic support for pulling sent messages and replies
9
+
10
+ == 0.0.1 2008-09-25
11
+
12
+ * 1 major enhancement:
13
+ * Initial release
@@ -0,0 +1,10 @@
1
+
2
+ For more information on smsinabox, see http://www.opensourcery.co.za/smsinabox
3
+
4
+ Setup your account by running sms-setup now. You can then use the following
5
+ commands to interact with SMS in a Box:
6
+
7
+ sms-send: Send SMS messages
8
+ sms-credit: Quick overview of the credit available
9
+ sms-replies: Quick access to your replies
10
+ sms-setup: To change the account details
@@ -0,0 +1,47 @@
1
+ = smsinabox
2
+
3
+ * http://www.opensourcery.co.za/smsinabox
4
+ * http://www.smsinabox.co.za/
5
+
6
+ == DESCRIPTION:
7
+
8
+ A Ruby wrapper for the SMS in a Box HTTP API, as well as command line tools for
9
+ sending SMS's and checking account balances
10
+
11
+ == FEATURES/PROBLEMS:
12
+
13
+ * Ruby module 'Smsinabox' for using in your own code
14
+ * sms-setup, sms-send, sms-credit & sms-replies CLI apps
15
+
16
+ == REQUIREMENTS:
17
+
18
+ * Developed and tested on Ruby 1.8.6
19
+
20
+ == INSTALL:
21
+
22
+ * sudo gem install smsinabox
23
+
24
+ == LICENSE:
25
+
26
+ (The MIT License)
27
+
28
+ Copyright (c) 2008 Kenneth Kalmer & SMS in a Box
29
+
30
+ Permission is hereby granted, free of charge, to any person obtaining
31
+ a copy of this software and associated documentation files (the
32
+ 'Software'), to deal in the Software without restriction, including
33
+ without limitation the rights to use, copy, modify, merge, publish,
34
+ distribute, sublicense, and/or sell copies of the Software, and to
35
+ permit persons to whom the Software is furnished to do so, subject to
36
+ the following conditions:
37
+
38
+ The above copyright notice and this permission notice shall be
39
+ included in all copies or substantial portions of the Software.
40
+
41
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
42
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
43
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
44
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
45
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
46
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
47
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,29 @@
1
+ require './lib/smsinabox'
2
+
3
+ Dir['tasks/**/*.rake'].each { |t| load t }
4
+
5
+ # TODO - want other tests/tasks run by default? Add them to the list
6
+ # remove_task :default
7
+ # task :default => [:spec, :features]
8
+
9
+ begin
10
+ require 'jeweler'
11
+ Jeweler::Tasks.new do |gemspec|
12
+ gemspec.name = 'smsinabox'
13
+ gemspec.version = Smsinabox::VERSION
14
+ gemspec.summary = 'Ruby API for sending text messages via http://www.smsinabox.co.za'
15
+ gemspec.description = 'Ruby API for sending text messages via http://www.smsinabox.co.za'
16
+ gemspec.email = 'kenneth.kalmer@gmail.com'
17
+ gemspec.homepage = 'http://github.com/kennethkalmer/smsinabox'
18
+ gemspec.authors = ['kenneth.kalmer@gmail.com']
19
+ gemspec.post_install_message = IO.read('PostInstall.txt')
20
+ gemspec.extra_rdoc_files.include '*.txt'
21
+
22
+ gemspec.add_dependency 'nokogiri', '>= 1.3.3'
23
+ gemspec.add_development_dependency 'rspec'
24
+ gemspec.add_development_dependency 'cucumber'
25
+ end
26
+ Jeweler::GemcutterTasks.new
27
+ rescue LoadError
28
+ puts "Jeweler not available. Install it with 'gem install jeweler'"
29
+ end
@@ -0,0 +1,14 @@
1
+ SMS in a BOX TODO
2
+ =================
3
+
4
+ Lets call this the builtin LightHouse :)
5
+
6
+ * Send bulk SMS messages
7
+ * Identify and handle invalid credentials
8
+ * Better exception generation and handling throughout the code
9
+ * Smsinabox::Message.to_xml
10
+ * Reply script
11
+ * Highline dependencies for better input
12
+ * SMS body input via STDIN
13
+ * Multiple accounts support
14
+ * Options to stay quite during operation
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created on 2008-9-25.
4
+ # Copyright (c) 2008. All rights reserved.
5
+
6
+ begin
7
+ require 'rubygems'
8
+ rescue LoadError
9
+ # no rubygems to load, so we fail silently
10
+ end
11
+
12
+ require 'smsinabox'
13
+ require 'optparse'
14
+
15
+ OPTIONS = {
16
+ :config => nil
17
+ }
18
+
19
+ parser = OptionParser.new do |opts|
20
+ opts.banner = <<BANNER
21
+ Retrieve the number of credits in the account
22
+
23
+ Usage: #{File.basename($0)} [options]
24
+
25
+ Options are:
26
+ BANNER
27
+ opts.separator ""
28
+ opts.on("-c", "--config=path/to/config", String,
29
+ "Path to alternate config file (defaults to ~/.smsinabox)") { |m| OPTIONS[:config] = m }
30
+ opts.on("-h", "--help",
31
+ "Show this help message.") { puts opts; exit }
32
+ opts.parse!(ARGV)
33
+
34
+ end
35
+
36
+ Smsinabox.configure!( OPTIONS[:config] )
37
+
38
+ puts "Credits left: #{Smsinabox.credits}"
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created on 2008-9-25.
4
+ # Copyright (c) 2008. All rights reserved.
5
+
6
+ begin
7
+ require 'rubygems'
8
+ rescue LoadError
9
+ # no rubygems to load, so we fail silently
10
+ end
11
+
12
+ require 'smsinabox'
13
+ require 'optparse'
14
+
15
+ OPTIONS = {
16
+ :recipient => nil,
17
+ :message => nil,
18
+ :pipe => false,
19
+ :config => nil
20
+ }
21
+ MANDATORY_OPTIONS = %w( recipient )
22
+
23
+ parser = OptionParser.new do |opts|
24
+ opts.banner = <<BANNER
25
+ Send a single SMS message to the specified recipient
26
+
27
+ Usage: #{File.basename($0)} [options]
28
+
29
+ Options are:
30
+ BANNER
31
+ opts.separator ""
32
+ opts.on("-r", "--recipient=RECIPIENT", String,
33
+ "The recipient of the SMS") { |r| OPTIONS[:recipient] = r }
34
+ opts.on("-m", "--message=MESSAGE", String,
35
+ "The body of the message (wrap in quotes)") { |m| OPTIONS[:message] = m }
36
+ opts.on("-c", "--config=path/to/config", String,
37
+ "Path to alternate config file (defaults to ~/.smsinabox)") { |m| OPTIONS[:config] = m }
38
+ opts.on("-p", "--pipe",
39
+ "Read message text from STDIN") { OPTIONS[:pipe] = true }
40
+ opts.on("-h", "--help",
41
+ "Show this help message.") { puts opts; exit }
42
+ opts.parse!(ARGV)
43
+
44
+ if MANDATORY_OPTIONS && MANDATORY_OPTIONS.find { |option| OPTIONS[option.to_sym].nil? }
45
+ puts opts; exit 1
46
+ end
47
+
48
+ if OPTIONS[:message].nil? && !OPTIONS[:pipe]
49
+ puts opts; exit 1
50
+ end
51
+ end
52
+
53
+ # Figure out our message
54
+ message = if !OPTIONS[:message].nil?
55
+ OPTIONS[:message]
56
+ else
57
+ require 'fcntl'
58
+ if STDIN.fcntl(Fcntl::F_GETFL, 0) == 0
59
+ STDIN.read
60
+ else
61
+ puts "Nothing to read from STDIN!"
62
+ exit 1
63
+ end
64
+ end
65
+
66
+ Smsinabox.configure!( OPTIONS[:config] )
67
+ id = Smsinabox.send(
68
+ Smsinabox::Message.new(
69
+ :recipient => OPTIONS[:recipient], :body => message
70
+ )
71
+ )
72
+
73
+ puts "Message sent"
74
+ puts "Reference: #{id}"
75
+ puts "Recipient: #{OPTIONS[:recipient]}"
76
+ puts "Message: #{message}"
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Created on 2008-9-25.
4
+ # Copyright (c) 2008. All rights reserved.
5
+
6
+ begin
7
+ require 'rubygems'
8
+ rescue LoadError
9
+ # no rubygems to load, so we fail silently
10
+ end
11
+
12
+ require 'smsinabox'
13
+ require 'optparse'
14
+
15
+ OPTIONS = {
16
+ :username => nil,
17
+ :password => nil,
18
+ :config => nil
19
+ }
20
+ MANDATORY_OPTIONS = %w( username password )
21
+
22
+ parser = OptionParser.new do |opts|
23
+ opts.banner = <<BANNER
24
+ Configure your local SMS in a Box commands to use the supplied details.
25
+
26
+ The configuration file is located at ~/.smsinabox and will have permissions of
27
+ 0600. It contains your password information in plain text, so be careful.
28
+
29
+ Usage: #{File.basename($0)} [options]
30
+
31
+ Options are:
32
+ BANNER
33
+ opts.separator ""
34
+ opts.on("-u", "--username=USERNAME", String,
35
+ "Username to use") { |u| OPTIONS[:username] = u }
36
+ opts.on("-p", "--password=PASSWORD", String,
37
+ "Password to use") { |p| OPTIONS[:password] = p }
38
+ opts.on("-c", "--config=path/to/config", String,
39
+ "Path to alternate config file (defaults to ~/.smsinabox)") { |m| OPTIONS[:config] = m }
40
+ opts.on("-h", "--help",
41
+ "Show this help message.") { puts opts; exit }
42
+ opts.parse!(ARGV)
43
+
44
+ if MANDATORY_OPTIONS && MANDATORY_OPTIONS.find { |option| OPTIONS[option.to_sym].nil? }
45
+ puts opts; exit
46
+ end
47
+ end
48
+
49
+ config = Smsinabox::Configuration.new( OPTIONS[:config] )
50
+ config[:username] = OPTIONS[:username]
51
+ config[:password] = OPTIONS[:password]
52
+ config.save
53
+
54
+ puts 'Configuration file updated.'
@@ -0,0 +1,143 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'net/http'
5
+ require 'uri'
6
+ begin
7
+ require 'nokogiri'
8
+ rescue LoadError
9
+ require 'rubygems'
10
+ require 'nokogiri'
11
+ end
12
+
13
+ module Smsinabox
14
+
15
+ VERSION = '0.2.0'
16
+
17
+ autoload :Exceptions, 'smsinabox/exceptions'
18
+ autoload :Message, 'smsinabox/message'
19
+ autoload :Configuration, 'smsinabox/configuration'
20
+ autoload :DeliveryReport, 'smsinabox/delivery_report'
21
+ autoload :SMS, 'smsinabox/sms'
22
+ autoload :Reply, 'smsinabox/reply'
23
+ autoload :SentMessage, 'smsinabox/sent_message'
24
+
25
+ class << self
26
+
27
+ # Username for the SMS in a Box service
28
+ attr_accessor :username
29
+
30
+ # Password for the SMS in a Box service
31
+ attr_accessor :password
32
+
33
+ def uri
34
+ @uri ||= URI.parse('http://www.mymobileapi.com/api5/http5.aspx')
35
+ end
36
+
37
+ # Return the number of available credits
38
+ def credit_remaining
39
+ perform_request( 'Type' => 'credits' ) do |xml|
40
+ xml.xpath('/api_result/data/credits/text()').to_s.to_i
41
+ end
42
+ end
43
+
44
+ # Send a #Messages and returns a #DelieryReport
45
+ def deliver( message )
46
+ raise MessageInvalid unless message.valid?
47
+
48
+ perform_request(
49
+ 'Type' => 'sendparam',
50
+ 'return_credits' => 'True',
51
+ 'return_msgs_success_count' => 'True',
52
+ 'return_msgs_failed_count' => 'True',
53
+ 'return_entries_success_status' => 'True',
54
+ 'return_entries_failed_status' => 'True',
55
+ 'numto' => message.recipient,
56
+ 'data1' => message.body
57
+ ) do |response|
58
+ DeliveryReport.from_response( response )
59
+ end
60
+ end
61
+
62
+ # Fetch replies from the gateway and then return a collection of replies or
63
+ # yield each reply if a block is given
64
+ def replies( last_id = 0, &block )
65
+ data = [
66
+ "<reply>",
67
+ "<settings>",
68
+ "<id>#{last_id}</id>",
69
+ "<max_recs>100</max_recs>",
70
+ "<cols_returned>eventid,numfrom,receiveddata,received,sentid,sentdata,sentdatetime,sentcustomerid</cols_returned>",
71
+ "</settings>",
72
+ "</reply>"
73
+ ]
74
+ perform_request(
75
+ 'Type' => 'replies',
76
+ 'XMLData' => data.join
77
+ ) do |response|
78
+ replies = []
79
+ response.xpath('/api_result/data').each do |reply|
80
+ replies << Smsinabox::Reply.from_response( reply )
81
+ end
82
+
83
+ replies.each { |r| yield r } if block_given?
84
+
85
+ replies
86
+ end
87
+ end
88
+
89
+ def sent( last_id = 0, &block )
90
+ data = [
91
+ "<sent>",
92
+ "<settings>",
93
+ "<id>#{last_id}</id>",
94
+ "<max_recs>100</max_recs>",
95
+ "<cols_returned>sentid,eventid,smstype,numto,data,flash,customerid,status,statusdate</cols_returned>",
96
+ "</settings>",
97
+ "</sent>"
98
+ ]
99
+
100
+ perform_request(
101
+ 'Type' => 'sent',
102
+ 'XMLData' => data.join
103
+ ) do |response|
104
+ messages = []
105
+ response.xpath('/api_result/data').each do |msg|
106
+ messages << Smsinabox::SentMessage.from_response( msg )
107
+ end
108
+
109
+ messages.each { |m| yield m } if block_given?
110
+
111
+ messages
112
+ end
113
+ end
114
+
115
+ # Set our username and password from #Smsinabox::Configuration
116
+ def configure!( config_file = nil )
117
+ c = Configuration.new( config_file )
118
+ @username = c["username"]
119
+ @password = c["password"]
120
+ nil
121
+ end
122
+
123
+ private
124
+
125
+ def perform_request( params = {}, &block )
126
+ raise MissingCredentialException if @username.nil? || @password.nil?
127
+ params.merge!( 'Username' => @username, 'Password' => @password )
128
+
129
+ req = Net::HTTP::Post.new( uri.path )
130
+ req.set_form_data( params )
131
+ req['user-agent'] = 'SMS in a Box/' + Smsinabox::VERSION
132
+ res = Net::HTTP.new( uri.host, uri.port ).start { |http| http.request( req ) }
133
+ case res
134
+ when Net::HTTPSuccess
135
+ yield Nokogiri::XML( res.body )
136
+ else
137
+ raise "Could not complete request"
138
+ end
139
+ end
140
+
141
+ end
142
+
143
+ end
@@ -0,0 +1,55 @@
1
+ require 'yaml'
2
+
3
+ module Smsinabox
4
+
5
+ # Wrapper around our YAML configuration file, which lives in ~/.smsinabox by
6
+ # default
7
+ class Configuration
8
+
9
+ def initialize( config_file = nil )
10
+ @config_file = config_file
11
+
12
+ check_file!
13
+ load_config
14
+ end
15
+
16
+ def []( key )
17
+ @configuration[key.to_s]
18
+ end
19
+
20
+ def []=( key, value )
21
+ @configuration[ key.to_s ] = value
22
+ end
23
+
24
+ def save
25
+ File.open( config_name, 'w' ) do |f|
26
+ YAML::dump( @configuration , f )
27
+ end
28
+ nil
29
+ end
30
+
31
+ private
32
+
33
+ def load_config
34
+ begin
35
+ @configuration = File.open( config_name, 'r' ) do |f|
36
+ YAML::load( f )
37
+ end
38
+ rescue
39
+ end
40
+
41
+ @configuration ||= {}
42
+ end
43
+
44
+ def check_file!
45
+ unless File.exist?( config_name )
46
+ File.open( config_name, 'w+' ) { |f| f.write('') }
47
+ File.chmod( 0600, config_name )
48
+ end
49
+ end
50
+
51
+ def config_name
52
+ @config_file || File.expand_path( File.join('~', '.smsinabox') )
53
+ end
54
+ end
55
+ end