Commit cb50f30d authored by Gabriel Dengler's avatar Gabriel Dengler
Browse files

Added support for multiple university courses

parent d44fa693
#!/usr/bin/env python3
import time
import requests
from credentials import get_credentials, update_credentials
from contextlib import ExitStack
from bs4 import BeautifulSoup
from log import print_info, print_warn
from urllib.parse import urlparse
from colorama import init as colorama_init, Fore, Style
from lxml.html import fromstring
from urllib.parse import unquote
headers = {
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'
+ '(KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36'
}
url_login_form = 'https://www.campus.uni-erlangen.de/qisserver/rds?' \
'state=user&type=1'
url_exams_page = 'https://www.campus.uni-erlangen.de/qisserver/rds?' \
'state=template&template=pruefungen'
AUTH_STATE_URL = 'https://www.campus.uni-erlangen.de/Shibboleth.sso/Login'
#AUTH_STATE_URL = 'https://www.studon.fau.de/studon/saml.php?target=ilias_app_oauth2'
OAUTH_URL = 'https://www.sso.uni-erlangen.de/simplesaml/module.php/core/loginuserpass.php?'
#SAML_URL = 'https://www.studon.fau.de/simplesaml/module.php/saml/sp/saml2-acs.php/default-sp'
SAML_URL = 'https://www.campus.uni-erlangen.de/Shibboleth.sso/SAML2/POST'
#SAML_URL = 'https://www.campus.uni-erlangen.de/Shibboleth.sso/Login'
class Connection:
def __init__(self, username=None):
self.url_exam_results = None
self._print = False
self.username = username
self.password = None
self.form_data = {'submit': 'Anmelden'}
colorama_init(autoreset=True)
def __enter__(self):
self.s = requests.session()
with ExitStack() as stack:
stack.enter_context(self.s)
self._stack = stack.pop_all()
self.username, self.password = get_credentials(self.username)
if not self.__connect():
print_warn('Wrong username or password for user '+self.username)
#self.username, self.password = update_credentials(self.username)
return None
else:
print_info('connection successful')
return self
def get_username(self):
return self.username
def __exit__(self, type, value, traceback):
self._stack.__exit__(type, value, traceback)
def __get(self, url):
print_info(f"get {urlparse(url).netloc}")
r = self.s.get(url, headers=headers, timeout=5)
r.raise_for_status()
return r
def __post(self, url, data):
print_info(f"get {urlparse(url).netloc}")
r = self.s.post(url, data=data, headers=headers, timeout=5)
r.raise_for_status()
return r
def __connect(self):
self.form_data['username'] = self.username
self.form_data['password'] = self.password
use_sso = True
if use_sso:
success = self.do_sso()
if not success:
return False
r = self.__get(url_exams_page)
url_overview_page = find_overview_page_url(r.content)
if not url_overview_page:
return False
r = self.__get(url_overview_page)
self.url_exam_results = find_exam_results_url(r.content)
if self.url_exam_results:
return True
else:
while True:
print_info(f'logging in as {self.username}')
self.__post(url_login_form, self.form_data)
r = self.__get(url_exams_page)
url_overview_page = find_overview_page_url(r.content)
if not url_overview_page:
return False
r = self.__get(url_overview_page)
self.url_exam_results = find_exam_results_url(r.content)
if self.url_exam_results:
return True
time.sleep(1)
def do_sso(self):
print(Style.DIM + 'generate auth state session...', end=' ', flush=True)
self.s = requests.Session()
auth_state_rq = self.s.get(AUTH_STATE_URL)
dom = fromstring(auth_state_rq.text)
auth_state = dom.xpath('//input[@name=\'AuthState\']/@value')
if len(auth_state) == 0:
auth_state_rq = self.s.get(
unquote(dom.xpath('//a[@id=\'redirect\']/@href')[0]))
dom = fromstring(auth_state_rq.text)
auth_state = dom.xpath('//input[@name=\'AuthState\']/@value')[0]
print(Style.DIM, 'done')
data = {
'username': self.username,
'password': self.password,
'AuthState': auth_state
}
oauth_rq = self.s.post(OAUTH_URL, data=data)
if 'AuthState' in oauth_rq.text:
return False
dom = fromstring(oauth_rq.text)
saml_res = dom.xpath('//input[@name=\'SAMLResponse\']/@value')[0]
payload = {'SAMLResponse': saml_res}
saml_rq = self.s.post(SAML_URL, data=payload)
return True
def __get_grades_page(self):
while True:
try:
return self.__get(self.url_exam_results).content
except Exception:
print_warn('connection lost, reconnecting:')
try:
while not self.__connect():
pass
except Exception as e:
print_warn(e)
time.sleep(1)
def get_grades(self):
rows = None
while not rows:
try:
grades_html = self.__get_grades_page()
rows = BeautifulSoup(grades_html, 'html5lib') \
.find('table', attrs={'id': 'notenspiegel'}) \
.find('tbody').find_all('tr')
except Exception as e:
print_warn(e)
self.__connect()
def p(list): return [' '.join(s.get_text().split()) for s in list]
headers = [p(r) for r in [r.find_all('th')
for r in rows] if len(r) > 0]
grades = [p(r) for r in [r.find_all('td') for r in rows] if len(r) > 0]
header = next((h for h in headers if h[0].startswith('#')))
grades = [dict(zip(header, row)) for row in grades]
modules = []
for grade in grades:
if not grade.get('Semester'):
grade['exams'] = []
modules.append(grade)
else:
if len(modules) == 0:
modules.append({ 'exams': [] })
modules[-1]['exams'].append(grade)
return modules
def find_overview_page_url(page):
try:
return BeautifulSoup(page, 'html5lib') \
.find('a', attrs={'id': 'notenspiegelStudent'}) \
.get('href')
except Exception as e:
print_warn(e)
return None
def find_exam_results_url(page):
return BeautifulSoup(page, 'html5lib') \
.find('meta', attrs={'http-equiv': 'refresh'}) \
.get('content')[6:]
if __name__ == '__main__':
with Connection() as c:
grades = c.get_grades()
for grade in grades:
print(f'{grade.get("Prüfungstext")}:'.rjust(
60), f'{grade.get("Note")}')
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment