-
Notifications
You must be signed in to change notification settings - Fork 6
/
grades.py
187 lines (144 loc) · 7.44 KB
/
grades.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
from pyquery import PyQuery as pq
from auth import authenticate
from datetime import datetime
from urllib import urlencode
from icalendar import Calendar, UTC
import re
import json
def get_final_grades():
''' extracts your grades from CMU's academic audit
returns a json of course -> letter grade (string)
* means you're taking the class and grades haven't been put in yet
AP means you got it through AP credit
P is pass
'''
s = authenticate('https://enr-apps.as.cmu.edu/audit/audit')
# find out the params for main major auditing
mainFrame = s.get('https://enr-apps.as.cmu.edu/audit/audit?call=2').content
d = pq(mainFrame)
params = {'call': 7}
for htmlInput in d('input[type=hidden]'):
name = d(htmlInput).attr('name')
value = d(htmlInput).attr('value')
if name != 'call':
params[name] = value
# get page for given major
classes = s.get('https://enr-apps.as.cmu.edu/audit/audit?' + urlencode(params)).content
# take grades from <pre>s in the page
d = pq(classes)
courses = {}
for pre in d('pre'):
data = d(pre).text()
for line in data.split('\n'):
matches = re.search('(\d+-\d+) \w+\s*\'\d+ ((\w|\*)+)\s*(\d+\.\d)\s*$', line)
if matches is not None:
course = matches.group(1)
grade = matches.group(2)
courses[course] = grade
return courses
def get_autolab_grades():
#Autolab has their SSL certificates misconfigured, so we won't verify them
s = authenticate('https://autolab.cs.cmu.edu/auth/users/auth/shibboleth',{"verify":False})
main = s.get('https://autolab.cs.cmu.edu').content
d = pq(main)
current_courses = d('#content > .rolodex > .course > h1 > a')
grades = {}
for course in current_courses:
page_1 = s.get('https://autolab.cs.cmu.edu%s/assessments' % d(course).attr('href')).content
gradebook = pq(pq(page_1)('.action-links > li > a')[1]).attr('href')
course_page = s.get('https://autolab.cs.cmu.edu%s' % gradebook).content
course_name = d(course).text()
cd = pq(course_page)
grades[course_name] = {}
assignments = cd('.grades tr')
for assgn in assignments:
if d(assgn).attr('class') == 'header': continue
name = cd(assgn).find("td > span > a").text()
score = cd(assgn).find("td > a").text()
total = cd(assgn).find("span.max_score").text()
if name is not None and score is not None and total is not None:
grades[course_name][name] = [float(score), float(total)]
return grades
def get_sio():
''' get information from SIO
TODO: figure out how to parse shit like the finances response
'''
s = authenticate('https://s3.as.cmu.edu/sio/index.html')
s.headers['Content-Type'] = 'text/x-gwt-rpc; charset=UTF-8'
siojs = s.get('https://s3.as.cmu.edu/sio/sio/sio.nocache.js').content
permutation = re.search("Rb='([^']+)'", siojs).group(1)
page_name = 'https://s3.as.cmu.edu/sio/sio/%s.cache.html' % (permutation)
cachehtml = s.get(page_name).content
# to successfully do RPC with SIO, you have to find the correct keys
# for each different kind of RPC you're doing and send them with the request
def get_key(key):
var_name = re.search("'%s',(\w+)," % key, cachehtml).group(1)
return re.search("%s='([^']+)'" % var_name, cachehtml).group(1)
context_key = get_key('userContext.rpc')
content_key = get_key('bioinfo.rpc')
# GWT returns something that's _almost_ JSON but not quite
def parse_gwt(gwt_response):
return json.loads(gwt_response.replace("'", '"').replace("\\", "\\\\")[4:])
return_data = {}
# info in user context: full name, major/school
s.post('https://s3.as.cmu.edu/sio/sio/userContext.rpc',
data=('7|0|4|https://s3.as.cmu.edu/sio/sio/|%s|edu.cmu.s3.ui.common.client.serverproxy.user.UserContextService|initUserContext|1|2|3|4|0|' % context_key))
# get mailbox/smc
gwt_response = s.post('https://s3.as.cmu.edu/sio/sio/bioinfo.rpc',
data=('7|0|4|https://s3.as.cmu.edu/sio/sio/|%s|edu.cmu.s3.ui.sio.student.client.serverproxy.bio.StudentBioService|fetchStudentSMCBoxInfo|1|2|3|4|0|' % content_key)).content
sio_json = parse_gwt(gwt_response)
return_data['smc'] = sio_json[5][2]
return_data['mailbox_combo'] = sio_json[5][1]
# get schedule
now = datetime.now()
currSemester = ('F' if now.month > 6 else 'S') + str(now.year % 100)
cal = Calendar.from_string(s.get('https://s3.as.cmu.edu/sio/secure/export/schedule/%s_semester.ics?semester=%s' % (currSemester, currSemester)).content)
day_map = {'MO': 1, 'TU': 2, 'WE': 3, 'TH': 4, 'FR': 5}
return_data['schedule'] = []
for event in cal.walk():
if event.name != 'VEVENT': continue
return_data['schedule'].append({
'days': map(lambda day: day_map[day], event.get('rrule').get('byday')),
'location': event.get('location').strip(),
'summary': event.get('summary').strip(),
'start_time': event.get('dtstart').dt,
'end_time': event.get('dtend').dt
})
return return_data
def get_blackboard_grades():
''' returns all your grades from the current semester
returns a json of courses mapping to a json of homeworks mapping to an array of [score, total]
raises an Exception if blackboard refuses to respond (which it does sometimes, query until it works)
'''
s = authenticate('https://blackboard.andrew.cmu.edu')
''' As of November 2013, Blackboard loads grades dynamically in these "streams"
so we request a stream containing a list of courses and then request grades for each course
except, because fuck blackboard, they'll give us a list of courses as a JSON but the grades
themselves are placed right in an HTML page (so much for ajax). bbGrades is the JSON output
of the stream we get for a student
'''
bbGrades = json.loads(s.post('https://blackboard.andrew.cmu.edu/webapps/streamViewer/streamViewer',
data={'cmd': 'loadStream', 'streamName': 'mygrades', 'forOverview': False, 'providers': {}}).content)
# Sometimes blackboard fails for unknown reasons, raise exception in this case
if len(bbGrades['sv_extras']['sx_filters']) == 0:
raise Exception('blackboard connection failed')
grades = {}
now = datetime.now()
currSemester = ('F' if now.month > 6 else 'S') + str(now.year % 100)
for course_id, course in bbGrades['sv_extras']['sx_filters'][0]['choices'].iteritems():
# we rely on blackboard's naming convention that all courses are of the pattern [SEASON][YEAR]-[COURSE NAME]
# also, :3
#if course[:3] != currSemester: continue
# get the HTML page for the specific set of grades we want
grades[course] = {}
html = s.get('https://blackboard.andrew.cmu.edu/webapps/bb-mygrades-BBLEARN/myGrades?course_id=%s&stream_name=mygrades' % course_id).content
# use pyquery (like jQuery) to extract grades
d = pq(html)
for homework in d('.graded_item_row'):
hw = d(homework).find('.gradable').text().strip()
grade = d(homework).find('span.grade').text().strip()
if grade is None: continue
possible = d(homework).find('span.pointsPossible').text().strip()[1:]
if possible is None: continue
grades[course][hw] = [float(grade), float(possible)]
return grades