How I Hacked My University's Grading System Using Python

📅March 17, 2020

🕒9 minute read

🏷

python

scripting

university

During the final stretch of each semester at a University, everyone is eager to know what their final grade will be. My University's grading system doesn't have a way of notifying you when any of your grades have been updated, so we have to navigate through the University's exceptionally slow, old and unresponsive website and wait for the grades to load up every single time. It's a real P.I.T.A.

My laziness decided this should be automated with Python.

General flow

My basic thought process for this was:

  • Login with University credentials .
  • Go into the grades section/page of the University dashboard .
  • Get grades by subject name (e.g. Calc 1, Algebra, etc).
    • Only first time => Save them as empty strings '' in a simple store (database)
  • Check subject by subject and compare current grade to last stored grade (e.g. 85 == '' ?)
  • If the current grade is different from last stored grade, send me an email telling me my grade has been updated.
  • Repeat every 10 minutes

My first solution involved using the requests library for getting each web page and beautifulsoup to scrape and obtain the necessary links and data.

After some clicks and skimming through the xhr requests on Chrome's devtools (thanks Google), I realized the website was using cookie-based authentication while making calls to an API served by the University itself for data like student info, classes and grades.

Authentication

Cookie-based authentication meant I had to keep a session open with my University credentials while making any API calls. For this I used requests's very own Session object.

with requests.Session() as session:
    session.post(LOGIN_URL, data=form_data_login)

Any requests made after successfully logging in, will carry the corresponding credentials in cookies. So requests to authenticated endpoints can be made:

student_info = session.get(STUDENT_INFO_URL)

student_id = student_info.json().get('data').get('studentID')

Getting and storing grades

After opening a session with my credentials, I was able to make a request to a super secret API endpoint that returned my grades. The returned data included a subject (class) name, an id and the grade for each subject (e.g. Calc 1, 12345, 94). I decided to use the subject id as the identifier for each subject grade.

subjects = res.json().get('data')  # Retrieved data from uni api
for subject in subjects:
    subject_id = subject.get('subjectID')
    subject_grade = subject.get('subjectGrade')

For the store I used a redis-inspired Python package named pickledb that works as a store/database for simple operations and provides convenient methods for storing/getting objects.

store = pickledb.load(PICKLE_PATH, True)  # Load store

# ... for subject in subjects

if store.exists(subject_id):
    # this subject has already been stored, check if it has changed

else:
    # Register in store for first time
    store.set(subject_id, subject_grade)

Getting updates via email

I decided for the notifications to be delivered via email since its the most straight-forward and accessible method. The first thing I did was write a quick email helper class for sending email with SendGrid, which features an effortless setup.

# update_email.py

class GradeUpdateEmail:
    def __init__(self, to_email):
        self.to_email = Email(to_email)
        self.sg = sendgrid.SendGridAPIClient(apikey='SENDGRID_API_KEY')

    def send_update(self, grade_name, grade):
        subject = f'Grade updated for {grade_name}!'
        content = Content(
            'text/plain',
            f'Your grade for {grade_name} has been updated to {grade}.'
        )
        from_email = Email("noreply@erickdelfin.com")
        mail = Mail(from_email, subject, self.to_email, content)

        response = self.sg.client.mail.send.post(request_body=mail.get())

An email for each update would be sent anytime the send_update function invokes. This was to be done anytime a new grade was updated.

subjects = res.json().get('data')  # Retrieved data from uni api
for subject in subjects:
    subject_id = subject.get('subjectID')
    subject_grade = subject.get('subjectGrade')

    # Grade has changed
    if not subject_grade == old_grade:

        # Save new grade to store
        store.set(subject_id, subject_grade)

        # Send the email!
        email = GradeUpdateEmail(USER_EMAIL)
        email.send_update(subject_name, subject_grade)

Since the subjects are being iterated one by one, it doesn't matter if only one of grade is updated or 3 are updated simultaneously, an email will be sent for each one.

Running the script every 10 minutes

I needed a way of running this script every few minutes to check if any updates had been made. Luckily, Cron Jobs exist. They're used for scheduling tasks to run on a server. One thing I've always liked about Cron Jobs is the setup is very straight-forward. Just define any task (script) in the crontab file.


$ crontab -e

Adding my grade notifier script to the end of the crontab file that includes the time/sequence to wait between each execution (10 minutes in my case).

*/10 * * * * sh /home/nifled/uni-grade-notifier/sample-script.sh

Done! 🍻 I got it working by the end of the semester (right before professors started uploading final grades to the University's platform). I shared the code with some of my uni buddies and it has saved us a considerate amount of time. Time that we now can spend on being lazy doing something else.