mitchellh-rubyuw 0.2.0

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 @@
1
+ pkg/*
data/README.markdown ADDED
@@ -0,0 +1,49 @@
1
+ # RubyUW: Ruby Interface to MyUW
2
+
3
+ __RubyUW is NOT supported in any way by the University of Washington__
4
+
5
+ RubyUW provides a programmable interface to MyUW, University
6
+ of Washington's student web portal.
7
+
8
+ ## Why?
9
+
10
+ Proof of Concept.
11
+
12
+ It's a fun project and it was really a proof of concept more than
13
+ anything. I don't plan on officially supporting this library or
14
+ promising updates in case any features break. Its a good example
15
+ for any Ruby developer on the uses of mechanize and the ability
16
+ to scrape content or simulate a human at a browser.
17
+
18
+ It is also a useful library to create MyUW automation tools
19
+ with. I do not support this.
20
+
21
+ ## How does it work?
22
+
23
+ RubyUW functions by emulating a human in an actual browser. It hunts
24
+ down buttons to click, fields to fill in, etc. It is programmed
25
+ using the Ruby Mechanize library to achieve this level of human
26
+ simulation.
27
+
28
+ Of course this also means that even minor tweaks to the MyUW layout
29
+ could potentially "break" the RubyUW library.
30
+
31
+ ## Installing
32
+
33
+ # Install the gem
34
+ sudo gem sources -a http://gems.github.com
35
+ sudo gem install mitchellh-rubyuw
36
+
37
+ ## Using RubyUW
38
+
39
+ It's easy to get started with RubyUW. Officially RDoc documentation
40
+ is planned but is __not up yet__. Sorry!
41
+
42
+ The following is a quick and simple example:
43
+
44
+ require 'myuw'
45
+ myuw = MyUW.new
46
+ myuw.login("netid", "password") or raise("Login Failed!")
47
+
48
+ # Get SLN information
49
+ sln_info = myuw.sln(14153)
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gemspec|
4
+ gemspec.name = "rubyuw"
5
+ gemspec.summary = "Library which provides a ruby interface to the University of Washington student portal."
6
+ gemspec.email = "mitchell.hashimoto@gmail.com"
7
+ gemspec.homepage = "http://github.com/mitchellh/rubyuw"
8
+ gemspec.description = "TODO"
9
+ gemspec.authors = ["Mitchell Hashimoto"]
10
+ end
11
+ rescue LoadError
12
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
13
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
data/lib/myuw.rb ADDED
@@ -0,0 +1,49 @@
1
+ require 'rubygems'
2
+ require 'mechanize'
3
+
4
+ require 'lib/myuw/session'
5
+ require 'lib/myuw/sln'
6
+
7
+ class MyUW
8
+ extend Forwardable
9
+
10
+ ##
11
+ # The version of the ruby MyUW automation class
12
+ VERSION = '0.1'
13
+
14
+ attr_accessor :browser
15
+ attr_accessor :session
16
+
17
+ def initialize
18
+ # Initialize the browser instance and mascarade
19
+ # around as Safari on Mac
20
+ @browser = WWW::Mechanize.new do |browser|
21
+ browser.user_agent_alias = 'Mac Safari'
22
+ browser.follow_meta_refresh = true
23
+
24
+ # Workaround to avoid frozen object error SSL pages
25
+ browser.keep_alive = false
26
+ end
27
+
28
+ # Initialize other members
29
+ @session = Session.new(self)
30
+ end
31
+
32
+ # Forward the session methods
33
+ def_delegator :session, :logged_in?
34
+ def_delegator :session, :logout
35
+
36
+ # Log into MyUW with a given username and password
37
+ def login(user, pass)
38
+ @session.email = user
39
+ @session.password = pass
40
+ @session.login
41
+ end
42
+
43
+ # Creates a new SLN object and returns it
44
+ def sln(sln_number)
45
+ sln_obj = SLNInfo.new(self)
46
+ sln_obj.sln = sln_number
47
+ return sln_obj
48
+ end
49
+ end
@@ -0,0 +1,87 @@
1
+ class MyUW
2
+ # = Synopsis
3
+ # This class encapsulates a MyUW session. It handles
4
+ # the login/logout of the MyUW portal.
5
+ class Session
6
+ attr_accessor :myuw
7
+ attr_accessor :email
8
+ attr_accessor :password
9
+
10
+ def initialize(myuw=nil)
11
+ @myuw ||= myuw
12
+
13
+ @email = @password = nil
14
+ end
15
+
16
+ # Checks if a user is logged in. This method is actually
17
+ # pretty expensive in terms of time since it actually
18
+ # verifies by going to the MyUW homepage and seeing if the
19
+ # dashboard loads.
20
+ def logged_in?
21
+ home = @myuw.browser.get("http://myuw.washington.edu")
22
+
23
+ # Click the login button to hopefully get to the main MyUW
24
+ # page
25
+ entry_button = home.search("//input[@type='submit' and @value='Log in with your UW NetID']")
26
+ raise("Failed to find the log in button") if entry_button.empty?
27
+
28
+ relay_page = home.form_with(:name => 'f').submit()
29
+ welcome_msg = relay_page.search("span.greetingw")
30
+ return !welcome_msg.empty?
31
+ end
32
+
33
+ # Logs a user in using the given email and password
34
+ def login
35
+ # Make sure that email and password are filled out or this is
36
+ # useless
37
+ if @email.nil? || @password.nil?
38
+ raise("Email and password weren't specified for MyUW session")
39
+ end
40
+
41
+ # Log out first
42
+ logout
43
+
44
+ # Go to the MyUW page and get to the login form
45
+ login_page = get_login_page
46
+ login_form = login_page.form_with(:name => 'query')
47
+ raise("Login form was not found on the MyUW page") if login_form.nil?
48
+ login_form.user = @email
49
+ login_form.pass = @password
50
+ relay_page = login_form.submit()
51
+
52
+ # Check if the login failed
53
+ unless relay_page.form_with(:name => 'query').nil?
54
+ return false
55
+ end
56
+
57
+ # Follow the relay
58
+ follow_relay_page(relay_page)
59
+ true
60
+ end
61
+
62
+ # Log out of the MyUW site
63
+ def logout
64
+ @myuw.browser.cookie_jar.clear!
65
+ end
66
+
67
+ private
68
+
69
+ def get_login_page
70
+ home = @myuw.browser.get("http://myuw.washington.edu")
71
+
72
+ # Click the login button to get to the forum
73
+ entry_button = home.search("//input[@type='submit' and @value='Log in with your UW NetID']")
74
+ raise("Failed to find the log in button") if entry_button.empty?
75
+
76
+ entry_form = home.form_with(:name => 'f')
77
+ relay_page = entry_form.submit()
78
+
79
+ return follow_relay_page(relay_page)
80
+ end
81
+
82
+ def follow_relay_page(relay_page)
83
+ relay_form = relay_page.form_with(:name => 'relay')
84
+ relay_form.submit()
85
+ end
86
+ end
87
+ end
data/lib/myuw/sln.rb ADDED
@@ -0,0 +1,89 @@
1
+ class MyUW
2
+ # = Synopsis
3
+ # Gets information regarding a specific SLN, returning
4
+ # information in a SLN object.
5
+ class SLNInfo
6
+ attr_accessor :term
7
+ attr_reader :sln
8
+ attr_accessor :myuw
9
+ attr_reader :data
10
+
11
+ def initialize(myuw)
12
+ @myuw = myuw
13
+ @term = "AUT+2009"
14
+ @data = nil
15
+ @sln = nil
16
+ end
17
+
18
+ # Custom setter for SLN to reset data
19
+ def sln=(value)
20
+ @sln = value
21
+ @data = nil
22
+ end
23
+
24
+ # Fetches the information for the given SLN.
25
+ def fetch_data
26
+ raise("SLN not set.") if @sln.nil?
27
+ raise("User must be logged in to fetch SLN data") unless @myuw.logged_in?
28
+
29
+ page = @myuw.browser.get("https://sdb.admin.washington.edu/timeschd/uwnetid/sln.asp?QTRYR=#{@term}&SLN=#{@sln}")
30
+ if page.uri == "http://www.washington.edu/students/timeschd/badrequest.html" then
31
+ raise("Attempted to fetch SLN data too soon.")
32
+ end
33
+
34
+ # Get the SLN information. I use a pretty sneaky xpath query
35
+ # here which is VERY LIKELY to break given any changes to the
36
+ # design of UW time schedules
37
+ info_nodes = page.search("//table[@border=1 and @cellpadding=3]//tr[@rowspan=1]//td")
38
+
39
+ data_order = [nil, :course, :section, :type, :credits, :title]
40
+ info_nodes.each_with_index do |node, i|
41
+ if i < data_order.length then
42
+ data_key = data_order[i]
43
+
44
+ unless data_key.nil?
45
+ @data ||= {}
46
+ @data[data_key] = node.inner_text.strip
47
+ end
48
+ end
49
+ end
50
+
51
+ # Get the enrollment information. Uses a pretty complicated
52
+ # xpath query once again which is likely to break given any
53
+ # changes to the design of the UW time schedules.
54
+ info_nodes = page.search("//table[@border=1 and @cellpadding=3]//tr[count(td)=5]//td")
55
+
56
+ data_order = [:current_enrollment, :limit_enrollment, :room_capacity, :space_available, :status]
57
+ info_nodes.each_with_index do |node, i|
58
+ if i < data_order.length then
59
+ data_key = data_order[i]
60
+
61
+ unless data_key.nil?
62
+ @data ||= {}
63
+ @data[data_key] = node.inner_text.strip
64
+ end
65
+ end
66
+ end
67
+
68
+ # And finally getting the notes of an SLN, accompanied by
69
+ # by far the ugliest xpath query yet.
70
+ info_nodes = page.search("//table[@border=1 and @cellpadding=3]//tr[count(td)=1]//preceding-sibling::tr[count(th)=1]//following-sibling::tr//td")
71
+ @data ||= {}
72
+ @data[:notes] = info_nodes[0].inner_text.strip
73
+ end
74
+
75
+ # The methods to extract various info out of the SLN. While
76
+ # I tend to try to avoid metaprogramming, this case seemed
77
+ # simple enough. Nothing tricky happening here!
78
+ [:course, :section, :type, :credits, :title, :current_enrollment,
79
+ :limit_enrollment, :room_capacity, :space_available, :status,
80
+ :notes].each do |info|
81
+ eval(<<-eomethod)
82
+ def #{info}
83
+ fetch_data if @data.nil?
84
+ @data[:#{info}]
85
+ end
86
+ eomethod
87
+ end
88
+ end
89
+ end
data/rubyuw.gemspec ADDED
@@ -0,0 +1,41 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{rubyuw}
5
+ s.version = "0.2.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Mitchell Hashimoto"]
9
+ s.date = %q{2009-06-21}
10
+ s.description = %q{TODO}
11
+ s.email = %q{mitchell.hashimoto@gmail.com}
12
+ s.extra_rdoc_files = [
13
+ "README.markdown"
14
+ ]
15
+ s.files = [
16
+ ".gitignore",
17
+ "README.markdown",
18
+ "Rakefile",
19
+ "VERSION",
20
+ "lib/myuw.rb",
21
+ "lib/myuw/session.rb",
22
+ "lib/myuw/sln.rb",
23
+ "rubyuw.gemspec"
24
+ ]
25
+ s.has_rdoc = true
26
+ s.homepage = %q{http://github.com/mitchellh/rubyuw}
27
+ s.rdoc_options = ["--charset=UTF-8"]
28
+ s.require_paths = ["lib"]
29
+ s.rubygems_version = %q{1.3.1}
30
+ s.summary = %q{Library which provides a ruby interface to the University of Washington student portal.}
31
+
32
+ if s.respond_to? :specification_version then
33
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
34
+ s.specification_version = 2
35
+
36
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
37
+ else
38
+ end
39
+ else
40
+ end
41
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mitchellh-rubyuw
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Mitchell Hashimoto
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-06-21 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: TODO
17
+ email: mitchell.hashimoto@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.markdown
24
+ files:
25
+ - .gitignore
26
+ - README.markdown
27
+ - Rakefile
28
+ - VERSION
29
+ - lib/myuw.rb
30
+ - lib/myuw/session.rb
31
+ - lib/myuw/sln.rb
32
+ - rubyuw.gemspec
33
+ has_rdoc: true
34
+ homepage: http://github.com/mitchellh/rubyuw
35
+ post_install_message:
36
+ rdoc_options:
37
+ - --charset=UTF-8
38
+ require_paths:
39
+ - lib
40
+ required_ruby_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: "0"
45
+ version:
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: "0"
51
+ version:
52
+ requirements: []
53
+
54
+ rubyforge_project:
55
+ rubygems_version: 1.2.0
56
+ signing_key:
57
+ specification_version: 2
58
+ summary: Library which provides a ruby interface to the University of Washington student portal.
59
+ test_files: []
60
+