oncourse 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4ff62a2167ee5451cf1f72b9b3ca9ff65ba210ae
4
+ data.tar.gz: e1c80d3865f1f2adb24fd4687e29cabdf7a25e1d
5
+ SHA512:
6
+ metadata.gz: 22bffbdf58374929970a8f62735ffadbc2c15bb418d0d0745e8b25e04dc37c708d6431919e031e6b3a69ed4642d7908ca769c33d755af7c0ce46481d8fca0709
7
+ data.tar.gz: a7e8f792a83abf9e48e74934948d69ba60578f60e679914f6046a7fe78984d3c6a6c955fa6b4c35af193d7c11869ae046a4ee673f25b5a3a13c3fa2ada836357
data/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ *.swp
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in oncourse.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 steve <stevecd123@gmail.com>
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,55 @@
1
+ # Oncourse
2
+
3
+ Oncoursesystems.com is a web app written in ext3js that handles managing teacher
4
+ lesson plans amongst many other things. It has proven to be incredibly tedious to
5
+ work with so I spent a day parsing its html and json requests involved with lessonplan
6
+ editing. This gem is the result.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'oncourse'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install oncourse
21
+
22
+ ## Usage
23
+
24
+ require 'oncourse'
25
+ client = Oncourse::Client.new('testuser', 'password')
26
+
27
+ # parse the weekly planner for a specific date and number of weeks.
28
+ # in the school I'm helping, the weeks start on monday, so this date should
29
+ # be a monday. The week is that monday and the following 4 days.
30
+ # This method only parses the html, so it does not read the homework field.
31
+ # You'll only get the lesson. Use client.read_lesson if you want the homework.
32
+ lessonplan = client.read_planner(Date.parse("2014-03-10"), 1)
33
+
34
+ # After a client.read_planner call, the planner is stored in an instance variable.
35
+ # We can now read all of the standards for each lesson we've read so far:
36
+ lessonplan_with_standards = client.read_planner_standards()
37
+
38
+
39
+ # Save the lesson plan so far.
40
+ client.save_planner("./lessonplan_20140310.json")
41
+
42
+ # read a single lesson given a date and period.
43
+ # this method also returns homework
44
+ puts client.read_lesson(Date.today, 2)
45
+
46
+ # post a lesson
47
+ client.post_lesson("2 + 2 = 4", "homework: 3+3= ?", Date.today, 2)
48
+
49
+ ## Contributing
50
+
51
+ 1. Fork it ( http://github.com/stevecd/ruby-oncourse/fork )
52
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
53
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
54
+ 4. Push to the branch (`git push origin my-new-feature`)
55
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ desc "Open a pry session preloaded with this library"
4
+ task :console do
5
+ sh "pry -r rubygems -I lib -r oncourse.rb"
6
+ end
data/lib/oncourse.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "oncourse/version"
2
+ require "oncourse/client"
3
+
4
+ module Oncourse
5
+ # Your code goes here...
6
+ end
@@ -0,0 +1,239 @@
1
+ require 'mechanize'
2
+ module Oncourse
3
+ URLS = {
4
+ main: "http://www.oncoursesystems.com/",
5
+ set_planner: "http://www.oncoursesystems.com/planner/planner_frame.aspx", #date=20140310&user_id=user_id&template=N
6
+ get_planner: "http://www.oncoursesystems.com/planner/planner.aspx",
7
+ close_planner: "http://www.oncoursesystems.com/planner/planner_navigation_bar.aspx", #action=C&tabid=337113074
8
+ lesson_standards: "http://www.oncoursesystems.com/json.axd/lessonplan/references_linked", #post request, {id: <standards_id>}
9
+ link_standards: "http://www.oncoursesystems.com/json.axd/standards/link_standards",
10
+ read_standard_areas: "http://www.oncoursesystems.com/json.axd/standards/lesson_standard_areas", #post request, {setId: <standard_setId}
11
+ lesson_standard_filters: "http://www.oncoursesystems.com/json.axd/standards/lesson_standard_filters", #post request, {setId: <standard_setId>, subject: {standard_subject}}
12
+ lesson_standards_tree: "http://www.oncoursesystems.com/json.axd/standards/lesson_standards_tree",
13
+ read_lesson: "http://www.oncoursesystems.com/json.axd/LessonPlan/lesson_record",
14
+ post_lesson: "http://www.oncoursesystems.com/json.axd/LessonPlan/lesson_record_save",
15
+ lesson_tree: "http://www.oncoursesystems.com/json.axd/LessonPlan/lesson_tree" #{userId: <user_id>}
16
+ }
17
+ class Client
18
+ attr_accessor :mech, :user_id, :weeks, :request_rate, :lesson_tree
19
+
20
+ # create new mechanize object and go through login steps
21
+ def initialize(username,password,request_rate=1)
22
+ @request_rate = request_rate
23
+ @mech = Mechanize.new()
24
+ login_page = @mech.get(URLS[:main])
25
+ login_form = login_page.forms.first
26
+ login_form.Username = username
27
+ login_form.Password = password.strip
28
+ home_page = login_form.submit
29
+ script_tag = home_page.parser.search("script").select{|s| s.text.index('user_id')}.first
30
+ raise "Login failed, script_tag not present." unless script_tag
31
+ @user_id = /\"user_id\":\"([0-9]+)\"/.match(script_tag.text).captures.first
32
+ raise "Login failed, unable to find user_id" unless @user_id
33
+ end
34
+
35
+ # read lesson planner for current user starting at start_Date: start_date
36
+ # and going each week up to num_weeks
37
+ def read_planner(start_date, num_weeks)
38
+ @weeks = []
39
+ start = start_date
40
+ cell_ids = 0
41
+ itr = 0
42
+ num_weeks.times do
43
+ start += itr * 7
44
+ week = {start: start.strftime("%Y%m%d")}
45
+ week[:columns] = []
46
+
47
+ # a get request to this set_planner url sets the current viewed planner
48
+ @mech.get(URLS[:set_planner], {date: week[:start], user_id: @user_id, template:'N'})
49
+ rate_pause()
50
+
51
+ planner_frame = @mech.get(URLS[:get_planner])
52
+
53
+ # parse out column headers
54
+ columns = planner_frame.parser.search("tr.sheetRowHeader th:not(.sheetRow1stCell)").each_with_index.map{|cell, idx| {label: cell.text.strip, period: idx + 1}}
55
+
56
+ # parse out each cell, ids give date and period.
57
+ # Period corresponds to column number.
58
+ # .lessonPreview classes contain the markup and content created by ext3's text editor
59
+ # that teachers use to input lessons.
60
+ cells = planner_frame.parser.search(".sheetCell")
61
+ cells = cells.map do |cell|
62
+ cell_hash = {
63
+ date: cell['id'][0...-2],
64
+ period: cell['id'][-2..-1].to_i,
65
+ html: cell.search('.lessonPreview').first.inner_html
66
+ }
67
+ flag_element = cell.search('.sheetCellIcons img[src="/images/icons/flag_blue.png"]').first
68
+ if(flag_element)
69
+ match = /showStandard\(\'([0-9]+)\'\)/.match(flag_element['onclick'])
70
+ if(match && match.captures.first)
71
+ cell_hash[:standards_id] = match.captures.first
72
+ end
73
+ end
74
+ cell_hash
75
+ end
76
+ columns.each do |column|
77
+ column[:cells] = cells.select{|cell| cell[:period] == column[:period]}
78
+ end
79
+
80
+ week[:columns] = columns
81
+
82
+ @weeks << week
83
+ itr += 1
84
+ rate_pause()
85
+ end
86
+
87
+ return @weeks
88
+ end
89
+
90
+ def rate_pause
91
+ sleep((1 + rand(0.1)) * @request_rate)
92
+ end
93
+
94
+ def read_planner_standards()
95
+ raise "planner weeks empty" if !@weeks || @weeks.length == 0
96
+ keys = ["id", "header_name", "detail_name"]
97
+ @weeks.each do |week|
98
+ week[:columns].each do |column|
99
+ column[:cells].each do |cell|
100
+ next unless cell[:standards_id]
101
+
102
+ json_response = @mech.post(URLS[:lesson_standards], {
103
+ id: cell[:standards_id]
104
+ })
105
+
106
+ cell[:standards] = JSON.parse(json_response.content).map{|f| f.reject{|k,v| !keys.include?(k)}}
107
+ rate_pause()
108
+ end
109
+ end
110
+ end
111
+ return @weeks
112
+ end
113
+
114
+ def save_planner(filename)
115
+ File.open(filename, "w") { |f| f.puts JSON.pretty_generate(@weeks)}
116
+ end
117
+
118
+ # method to read one lesson through the json interface
119
+ # This is required before posting a lesson since it's the only way to get the
120
+ # homework field that I've found. If you post an empty homework field, any previous
121
+ # homework entry will be lost
122
+ def read_lesson(date, period)
123
+ response = @mech.post(URLS[:read_lesson], {
124
+ userId: @user_id,
125
+ date: date.strftime("%m/%d/%Y"),
126
+ period: period
127
+ })
128
+ return JSON.parse(response.content)["ReturnValue"]
129
+ end
130
+
131
+ def read_lesson_tree()
132
+ return @lesson_tree if @lesson_tree
133
+ response = @mech.post(URLS[:lesson_tree], {userId: @user_id})
134
+
135
+ # oncourse returns invalid JSON, using this ugly regex for now.
136
+ fixed_json = fix_malformed_json(response.content)
137
+
138
+ @lesson_tree = JSON.parse(fixed_json)
139
+
140
+ return @lesson_tree
141
+ end
142
+
143
+ # oncourse produces a lot of malformed json. It seems to strip the quotes off strings
144
+ # on deeply nested objects. Not sure what they're doing wrong, since I don't think
145
+ # the problem is with ext3
146
+ def fix_malformed_json(malformed_json)
147
+ return malformed_json.gsub(/([a-z][a-zA-Z0-9_]+):(?!\s)/, '"\1":')
148
+ end
149
+
150
+ # read parent groups for standards
151
+ def read_standard_groups()
152
+ read_lesson_tree unless @lesson_tree
153
+
154
+ #have to traverse a big json tree here
155
+ standards_branch = @lesson_tree.find{|branch| branch["text"] == "Standards"}
156
+ return standards_branch["children"].map do |child|
157
+ {
158
+ label: child["text"],
159
+ set_id: child["xconfig"]["setId"]
160
+ }
161
+ end
162
+ end
163
+
164
+ # reads first child group of standards
165
+ def read_standard_areas(set_id)
166
+ response = @mech.post(URLS[:standard_areas], {setId: set_id})
167
+
168
+ return JSON.parse(fix_malformed_json(response.content))
169
+ end
170
+
171
+ # reads filters that must be applied after selecting first child group
172
+ def read_lesson_standard_filters(set_id, subject)
173
+ response = @mech.post(URLS[:lesson_standard_filters], {setId: set_id, subject: subject})
174
+
175
+ return JSON.parse(response.content)
176
+ end
177
+
178
+ # Reads a full list of standards for a given SetID, subject, grade, and year
179
+ def read_lesson_standards_tree(set_id, subject, grade, year)
180
+ response = @mech.post(URLS[:lesson_standards_tree], {
181
+ userId: @user_id,
182
+ setId: set_id,
183
+ subject: subject,
184
+ yearName: year,
185
+ searchText1: "",
186
+ searchOperator: "",
187
+ searchText2: "",
188
+ powerSetID: "",
189
+ mapID: "",
190
+ grade: grade,
191
+ showOnlyPowerSet: false,
192
+ activityDate: Date.today.strftime("%m/%d/%Y"), # seems arbitrary
193
+ activityPeriod: 1 # arbitrary
194
+ })
195
+
196
+ return JSON.parse(response.content)
197
+ end
198
+
199
+ def link_standard(standard_id, date, period)
200
+ response = @mech.post(URLS[:link_standards], {
201
+ objectType: "L",
202
+ id: standard_id,
203
+ date: date.strftime("%m/%d/%Y"),
204
+ period: period,
205
+ link: true
206
+ })
207
+
208
+ return JSON.parse(response.content)
209
+ end
210
+
211
+ def unlink_standard(standard_id, date, period)
212
+ response = @mech.post(URLS[:link_standards], {
213
+ objectType: "L",
214
+ id: standard_id,
215
+ date: date.strftime("%m/%d/%Y"),
216
+ period: period,
217
+ link: false
218
+ })
219
+
220
+ return JSON.parse(response.content)
221
+ end
222
+
223
+ # This method will absolutely overwrite anything that occupies this place
224
+ # in the lesson plan. To play it safe, one should call read_lesson and store
225
+ # that response somewhere before using post lesson. This way there is a backup
226
+ # of previous work. Oncourse does not appear to keep a revision history on
227
+ # lesson plans.
228
+ def post_lesson(lesson, homework, date, period)
229
+ response = @mech.post(URLS[:post_lesson], {
230
+ userId: @user_id,
231
+ date: date.strftime("%m/%d/%Y"),
232
+ period: period,
233
+ notes: lesson,
234
+ homework: homework
235
+ })
236
+ return response
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,3 @@
1
+ module Oncourse
2
+ VERSION = "0.0.1"
3
+ end
data/oncourse.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'oncourse/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "oncourse"
8
+ spec.version = Oncourse::VERSION
9
+ spec.authors = ["steve"]
10
+ spec.email = ["stevecd123@gmail.com"]
11
+ spec.summary = %q{Provides a basic lessonplan API}
12
+ spec.description = %q{oncoursesystems.com's interface is very tedious to work with in a lot of cases, this gem uses mechanize to scrape and post lessonplan data}
13
+ spec.homepage = "https://github.com/stevecd/ruby-oncourse"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler", "~> 1.5"
22
+ spec.add_development_dependency "pry"
23
+ spec.add_development_dependency "rake"
24
+
25
+ spec.add_runtime_dependency "mechanize"
26
+ end
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: oncourse
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - steve
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-03-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: mechanize
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: oncoursesystems.com's interface is very tedious to work with in a lot
70
+ of cases, this gem uses mechanize to scrape and post lessonplan data
71
+ email:
72
+ - stevecd123@gmail.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - lib/oncourse.rb
83
+ - lib/oncourse/client.rb
84
+ - lib/oncourse/version.rb
85
+ - oncourse.gemspec
86
+ homepage: https://github.com/stevecd/ruby-oncourse
87
+ licenses:
88
+ - MIT
89
+ metadata: {}
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubyforge_project:
106
+ rubygems_version: 2.0.3
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: Provides a basic lessonplan API
110
+ test_files: []