smesser 0.0.1

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/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in smesser.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 JJ Buckley
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # SMeSser
2
+
3
+ For using your provider's WebText programatically.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'smesser'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install smesser
18
+
19
+ ## Usage
20
+
21
+ Smesser is generally supposed to be used as part of a more useful application,
22
+ for sending (free!) SMSs.
23
+
24
+ It does, however, come with a little command-line application (`smesser`). Try
25
+ `smesser -h` when it's installed.
26
+
27
+ ## Configuration
28
+
29
+ You can program Smesser programatically, but it will also
30
+ have a look for some configuration files, and determine the
31
+ username/password/provider from there. These config files are loaded, in order,
32
+ if they exist:
33
+
34
+ * /etc/smesserrc
35
+ * /usr/local/etc/smesserrc
36
+ * ~/.smesserrc
37
+
38
+ *HINT*: If you're a command-line type of person (which I presume yuo are, as
39
+ you're reading this), then add a "contacts" hash to your configuration file.
40
+ These can be used as aliases instead of phone number.
41
+
42
+ Here's a sample configuration file, which you'd keep as ~/.smesserrc
43
+
44
+ ```yaml
45
+ provider: o2.ie
46
+ username: "0861234567" # <-- Ensure a string, or the leading 0 could vanish!
47
+ password: secret
48
+ contacts:
49
+ mom: "+3538712345678"
50
+ dad: "+1123456788"
51
+ lisa: "08517171717"
52
+ ```
53
+
54
+ ## Providers
55
+
56
+ The core of Smesser is done by little [Mechanize][mechanize] agents, called
57
+ Providers, that know how to log in (as you) to a specific website, fill in a
58
+ form, and submit it.
59
+
60
+ The only requirements for a Provider is that it responds to `login` and `send`.
61
+ By subclassing Smesser::Provider, you get a couple of convenience methods to
62
+ help you create your own.
63
+
64
+ Have a look at bundled providers for info on how to write your own provider.
65
+
66
+ Currently, there's
67
+
68
+ * vodafone.ie
69
+ * o2.ie
70
+
71
+ ## Contributing
72
+
73
+ 1. Fork it
74
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
75
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
76
+ 4. Push to the branch (`git push origin my-new-feature`)
77
+ 5. Create new Pull Request
78
+
79
+ [mechanize]: http://mechanize.rubyforge.org/
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require "rake/testtask"
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "test"
7
+ t.test_files = FileList["test/test*.rb"]
8
+ end
9
+
10
+ task :default => :test
11
+
data/bin/smesser ADDED
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env ruby
2
+ require File.expand_path(File.join(File.dirname(__FILE__), '../lib/smesser'))
3
+ require 'optparse'
4
+
5
+ $options = {}
6
+
7
+ OptionParser.new do |opts|
8
+ opts.banner = "Usage: smesser [options]"
9
+
10
+ opts.on('-c', '--config FILE', String, 'Use a specific config file') do |f|
11
+ if File.exists?
12
+ Smesser.config_files << f
13
+ else
14
+ puts "Can't open file #{f}"
15
+ exit 3
16
+ end
17
+ end
18
+
19
+ opts.on('-m', '--message MESSAGE', String, 'Your message. If blank, reads from STDIN.') do |m|
20
+ $options[:message] = m
21
+ end
22
+
23
+ opts.on('-P', '--provider PROVIDER', String, 'Choose a provider (list with --list)') do |p|
24
+ $options[:provider] = p
25
+ end
26
+
27
+ opts.on('-u', '--username USERNAME', String, 'Specify a username to log in with') do |u|
28
+ $options[:username] = u
29
+ end
30
+
31
+ opts.on('-p', '--password PASSWORD', String, 'Specify a password to log in with') do |p|
32
+ $options[:password] = p
33
+ end
34
+
35
+ opts.on('-L', '--list', 'Show all known providers, and exit') do
36
+ Smesser.print_providers
37
+ exit
38
+ end
39
+
40
+ opts.on('-v', '--verbose', 'Be more verbose') do
41
+ Smesser.log.level = Logger::INFO
42
+ end
43
+
44
+ opts.on('-d', '--debug', 'Debug the application') do
45
+ Smesser.log.level = Logger::DEBUG
46
+ end
47
+
48
+ opts.on('-n', '--dry-run', "Don't actually send the message") do
49
+ Smesser.configuration[:dryrun] = true
50
+ end
51
+
52
+ opts.on_tail('-h', '--help', 'Show this message') do
53
+ puts opts.to_s
54
+ exit
55
+ end
56
+ end.parse!(ARGV)
57
+
58
+ if ARGV.empty?
59
+ puts "You didn't enter any recipients!"
60
+ exit 1
61
+ end
62
+ $options[:recipients] = []
63
+ $options[:recipients] << ARGV.shift until ARGV.empty?
64
+
65
+ if $options[:message].nil?
66
+ print "Type your message below, and finish with ^D:\n> "
67
+ $options[:message] = ARGF.read.chomp
68
+ end
69
+
70
+ print "Sending... "
71
+ result = Smesser.send_message($options)
72
+
73
+ puts "#{result[:remaining]} remaining" if result[:remaining]
74
+ puts result[:message] if result[:message]
75
+ puts result[:status]
76
+ exit -1 unless result[:status] == "OK"
@@ -0,0 +1,81 @@
1
+ require 'mechanize'
2
+ require 'smesser'
3
+
4
+ module Smesser
5
+ class Provider
6
+ class << self
7
+ attr_accessor :description
8
+ end
9
+
10
+ attr_accessor :username, :password
11
+
12
+ def self.paths
13
+ @paths ||= [File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'providers'))]
14
+ end
15
+
16
+ def self.load_all
17
+ Smesser.log.debug("Loading all provider files in #{paths.inspect}")
18
+ paths.each do |path|
19
+ Dir["#{path}/*.rb"].each do |f|
20
+ require f
21
+ end
22
+ end
23
+ end
24
+
25
+ def initialize(username, password)
26
+ @username = username
27
+ @password = password
28
+ end
29
+
30
+ def get(address)
31
+ log.debug("Getting #{address}")
32
+ agent.get(address) unless agent.page and agent.page.uri == URI.parse(address)
33
+ log.debug("Got #{address} (#{page.code})")
34
+ page.code
35
+ end
36
+
37
+ def post_form(spec, &block)
38
+ spec = { :name => spec } if spec.is_a? String
39
+ log.debug("Looking for form with #{spec.inspect}")
40
+ form = page.form_with(spec)
41
+ raise "Form not found: #{spec.inspect} on #{page.uri} (forms: #{page.forms.map(&:name).inspect})" unless form
42
+ yield form
43
+ log.debug("Submitting form: #{form.action} #{form.request_data}")
44
+ form.submit
45
+ log.debug("Submitted #{form.action} (#{page.code})")
46
+ page.code
47
+ end
48
+
49
+ def click(spec)
50
+ spec = { :text => spec } if spec.is_a? String
51
+ log.debug("Looking for a form with #{spec.inspect}")
52
+ link = page.link_with(spec)
53
+ raise "Link not found: #{spec.inspect} on #{page.uri} (links: #{page.links.map(&:text).inspect})" unless link
54
+ log.debug("Clicking link #{link.text} => #{link.href}")
55
+ link.click
56
+ log.debug("Clicked link #{link.href} (#{page.code})")
57
+ page.code
58
+ end
59
+
60
+ def login
61
+ raise "You need to override #{this}#login"
62
+ end
63
+
64
+ def send(message, *recipients)
65
+ raise "You need to override #{this}#send"
66
+ end
67
+
68
+ def agent
69
+ @agent ||= Mechanize.new
70
+ end
71
+
72
+ def page
73
+ agent.page
74
+ end
75
+
76
+ def log
77
+ Smesser.log
78
+ end
79
+
80
+ end
81
+ end
@@ -0,0 +1,3 @@
1
+ module Smesser
2
+ VERSION = "0.0.1"
3
+ end
data/lib/smesser.rb ADDED
@@ -0,0 +1,116 @@
1
+ require 'yaml'
2
+ require 'ostruct'
3
+ require 'logger'
4
+ require "smesser/version"
5
+
6
+ module Smesser
7
+ autoload :Provider, 'smesser/provider'
8
+
9
+ def self.config_files
10
+ @configfiles ||= [
11
+ "/etc/smesserrc",
12
+ "/usr/local/etc/smesserrc",
13
+ "~/.smesserrc"
14
+ ]
15
+ end
16
+
17
+ # The easiest way to send an SMS.
18
+ #
19
+ # Accepts the following options:
20
+ #
21
+ # provider:: The name of the provider to use (see `providers`)
22
+ # username:: The username to log in with
23
+ # password:: The password to log in with
24
+ # message:: The message
25
+ # recipients:: An array of recipients. If Smesser has `contacts` configured,
26
+ # then you can use aliases here.
27
+ # retry:: The number of times to attempt sending. Default is 1.
28
+ #
29
+ # If any are missing, Smesser's configuration will be checked for a suitable
30
+ # value.
31
+ #
32
+ # Returns a hash, with (at least)
33
+ #
34
+ # status:: 'OK' if all went well, 'Failed' otherwise
35
+ #
36
+ # ...and any additional provider specific return values. A common one might be
37
+ # `:remaining`, to indicate the number of free SMSs left with the provider.
38
+ def self.send_message(options = {})
39
+ options.dup.each { |k, v| options[k.to_sym] = options.delete(k) if k.is_a?(String) }
40
+
41
+ args = configuration.merge(options)
42
+ log.debug("Sending message: args = #{args.inspect}")
43
+
44
+ provider = providers[args[:provider]].new(args[:username], args[:password])
45
+ log.debug("Provider: #{provider.inspect}")
46
+
47
+ recipients = lookup_contacts(args[:recipients])
48
+ log.debug("Recipients: #{recipients.inspect}")
49
+
50
+ unless provider.logged_in?
51
+ log.debug("Logging in... (#{args[:username]})")
52
+ provider.login unless args[:dryrun]
53
+ end
54
+
55
+ log.info("Message: #{args[:message].inspect}")
56
+
57
+ result = {}
58
+
59
+ if args[:dryrun] or provider.send(args[:message], *recipients)
60
+ log.info "Message sent."
61
+ result[:code] = "OK"
62
+ result[:remaining] = provider.remaining if provider.respond_to?(:remaining)
63
+ elsif args[:retry] and args[:retry] > 0
64
+ log.info "Failed, trying again... (#{args[:retry]})"
65
+ args[:retry] -= 1
66
+ return send_message(args)
67
+ else
68
+ log.info "Failed!"
69
+ result[:code] = "Failed"
70
+ end
71
+
72
+ result
73
+ end
74
+
75
+ def self.log
76
+ return @log if @log
77
+ @log = Logger.new(STDOUT)
78
+ @log.level = Logger::WARN
79
+ @log
80
+ end
81
+
82
+ def self.configuration
83
+ @configuration ||= load_config!
84
+ end
85
+
86
+ def self.providers
87
+ Provider.load_all
88
+ @providers ||= {}
89
+ end
90
+
91
+ def self.load_config!
92
+ config = {}
93
+ config_files.each do |f|
94
+ if File.exists?(path = File.expand_path(f))
95
+ config.merge!(YAML.load(File.read(path)))
96
+ end
97
+ end
98
+ config.dup.each { |k, v| config[k.to_sym] = v if k.is_a?(String) }
99
+ config
100
+ end
101
+
102
+ def self.lookup_contacts(recipients)
103
+ contacts = configuration[:contacts]
104
+ return recipients unless contacts
105
+ recipients.map do |r|
106
+ log.debug "#{r} => #{contacts[r]}" if contacts[r]
107
+ contacts[r] || r
108
+ end
109
+ end
110
+
111
+ def self.print_providers(io = STDOUT)
112
+ providers.each do |k, v|
113
+ io.puts "#{k}: #{v.description || 'No description'}"
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,50 @@
1
+ require 'smesser/provider'
2
+
3
+ class O2Ie < Smesser::Provider
4
+ @@start_url = "http://www.o2online.ie/o2/"
5
+ @@send_action = "http://messaging.o2online.ie/smscenter_send.osp"
6
+ @@send_delay = 2
7
+
8
+ self.description = "www.o2.ie (400 free texts per month)"
9
+
10
+ def logged_in?
11
+ get @@start_url
12
+ !!page.link_with(:text => 'Logout of o2.ie')
13
+ end
14
+
15
+ def login
16
+ get(@@start_url)
17
+ click('Login to o2.ie')
18
+ post_form('o2login_form') do |form|
19
+ form["IDToken1"] = @username
20
+ form["IDToken2"] = @password
21
+ end
22
+ end
23
+
24
+ def send(message, *recipients)
25
+ get_webtext_page
26
+ post_form('form_WebtextSend') do |form|
27
+ form["SMSTo"] = recipients.join(", ")
28
+ form["SMSText"] = message
29
+ form.action = @@send_action
30
+ sleep(@@send_delay)
31
+ end
32
+ end
33
+
34
+ def remaining
35
+ get_webtext_page
36
+ if span = page.at("#spn_WebtextFree")
37
+ span.inner_text
38
+ else
39
+ nil
40
+ end
41
+ end
42
+
43
+ def get_webtext_page
44
+ login unless logged_in?
45
+ get(@@start_url)
46
+ click('Send a webtext')
47
+ get(page.frames.first.href)
48
+ end
49
+ end
50
+ Smesser.providers['o2.ie'] = O2Ie
@@ -0,0 +1,65 @@
1
+ require 'smesser/provider'
2
+
3
+ class VodafoneIe < Smesser::Provider
4
+ @@start_url = "https://www.vodafone.ie/myv/index.jsp"
5
+ @@login_url = "https://www.vodafone.ie/myv/services/login/login.jsp"
6
+ @@webtext_url = "https://www.vodafone.ie/myv/messaging/webtext/index.jsp"
7
+ @@send_delay = 3 # Vodafone's servers don't like instantaneousness.
8
+
9
+ self.description = "www.vodafone.ie"
10
+
11
+ def logged_in?
12
+ log.debug("Checking if logged in")
13
+ get(@@start_url)
14
+ if page.uri == URI.parse(@@start_url)
15
+ log.debug("Logged in")
16
+ true
17
+ else
18
+ log.debug("Not logged in")
19
+ false
20
+ end
21
+ end
22
+
23
+ def login
24
+ get(@@login_url)
25
+ post_form('Login') do |form|
26
+ form.username = @username
27
+ form.password = @password
28
+ end
29
+ end
30
+
31
+ def send(message, *recipients)
32
+ get(@@webtext_url)
33
+
34
+ post_form('WebText') do |form|
35
+ form.message = message
36
+ recipients.each_with_index do |recipient, index|
37
+ form["recipients[#{index}]"] = recipient
38
+ end
39
+ sleep(@@send_delay)
40
+ end
41
+
42
+ log.debug("Looking for div#webtext-thankyou")
43
+ if !!page.at('div#webtext-thankyou')
44
+ log.debug("Found!")
45
+ true
46
+ else
47
+ log.warn("No matching element found!")
48
+ false
49
+ end
50
+ end
51
+
52
+ def remaining
53
+ log.debug("Checking remaining texts...")
54
+ login unless logged_in?
55
+ get @@webtext_url unless page.uri == URI::parse(@@webtext_url)
56
+ if div = page.at("div.info-row.text-remaining strong")
57
+ log.debug("Found element")
58
+ div.inner_text
59
+ else
60
+ log.warn("No matching element found!")
61
+ nil
62
+ end
63
+ end
64
+ end
65
+ Smesser.providers['vodafone.ie'] = VodafoneIe
data/smesser.gemspec ADDED
@@ -0,0 +1,19 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/smesser/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["JJ Buckley"]
6
+ gem.email = ["jj@bjjb.org"]
7
+ gem.description = %q{Send free text messages!}
8
+ gem.summary = %q{SMeSser helps you send SMSs using various mobile provider's websites. The gem comes with a binary, which helps you set up the details for your own provider, and with a library which you can integrate into nicer front-ends.}
9
+ gem.homepage = "http://jjbuckley.github.com/smesser"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "smesser"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Smesser::VERSION
17
+ gem.add_development_dependency 'rake'
18
+ gem.add_dependency 'mechanize', '~> 2.3'
19
+ end
@@ -0,0 +1,16 @@
1
+ require 'test/unit'
2
+ require 'smesser'
3
+
4
+ class TestSmesser < Test::Unit::TestCase
5
+ def test_provider_paths
6
+ assert Smesser::Provider.paths.include?(File.expand_path(File.join(File.dirname(__FILE__), '..', 'providers')))
7
+ end
8
+
9
+ def test_config_files
10
+ assert Smesser.config_files.include?("/usr/local/etc/smesserrc")
11
+ assert Smesser.config_files.include?("/etc/smesserrc")
12
+ assert Smesser.config_files.include?("~/.smesserrc")
13
+ end
14
+
15
+ # TODO - loads more tests!
16
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: smesser
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - JJ Buckley
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-10 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: &76809390 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *76809390
25
+ - !ruby/object:Gem::Dependency
26
+ name: mechanize
27
+ requirement: &76788750 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: '2.3'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *76788750
36
+ description: Send free text messages!
37
+ email:
38
+ - jj@bjjb.org
39
+ executables:
40
+ - smesser
41
+ extensions: []
42
+ extra_rdoc_files: []
43
+ files:
44
+ - .gitignore
45
+ - Gemfile
46
+ - LICENSE
47
+ - README.md
48
+ - Rakefile
49
+ - bin/smesser
50
+ - lib/smesser.rb
51
+ - lib/smesser/provider.rb
52
+ - lib/smesser/version.rb
53
+ - providers/o2_ie.rb
54
+ - providers/vodafone_ie.rb
55
+ - smesser.gemspec
56
+ - test/test_smesser.rb
57
+ homepage: http://jjbuckley.github.com/smesser
58
+ licenses: []
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ segments:
70
+ - 0
71
+ hash: 251797839
72
+ required_rubygems_version: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ segments:
79
+ - 0
80
+ hash: 251797839
81
+ requirements: []
82
+ rubyforge_project:
83
+ rubygems_version: 1.8.10
84
+ signing_key:
85
+ specification_version: 3
86
+ summary: SMeSser helps you send SMSs using various mobile provider's websites. The
87
+ gem comes with a binary, which helps you set up the details for your own provider,
88
+ and with a library which you can integrate into nicer front-ends.
89
+ test_files:
90
+ - test/test_smesser.rb