Files
DualHub/fetchDUALIS.py
2024-09-29 19:21:20 +02:00

200 lines
6.9 KiB
Python

import urllib.parse
import time
from bs4 import BeautifulSoup
from flask import redirect, url_for
from init import Dualis
import asyncio
import httpx
headers = {
'Cookie': 'cnsc=0',
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/58.0.3029.110 Safari/537.36',
'Accept-Encoding': 'utf-8'
}
url = "https://dualis.dhbw.de/scripts/mgrqispi.dll"
async def checkUser(email: str, password: str):
"""
Erhält von Dualis den Token und Cookie für User.
:param email:
:param password:
:return (Token, Cookie):
"""
async with httpx.AsyncClient() as s:
formattedPassword = urllib.parse.quote(password, safe='', encoding=None, errors=None)
formattedEmail = urllib.parse.quote(email, safe='', encoding=None, errors=None)
content = (f"usrname={formattedEmail}&pass={formattedPassword}&ARGUMENTS=clino%2Cusrname%2Cpass%2C"
f"menuno%2Cmenu_type%2Cbrowser%2Cplatform&APPNAME=CampusNet&PRGNAME=LOGINCHECK")
response = await s.post(url=url, headers=headers, content=content)
header = response.headers
try:
refresh = header["REFRESH"]
arg = refresh.find("=-N") + 3
komma = refresh[arg:].find(",")
cookie = s.cookies.get("cnsc")
except KeyError:
return -2, s
token = refresh[arg:komma + arg]
return token, cookie
async def getKurs(token: int, cookie: str):
"""
Bestimmt aus der ersten Prüfung den Kursbezeichner des Users.
TODO: Umstellen auf Bezeichner INKL. Standort
:param token:
:param cookie:
:return Kurs-Bezeichner ODER 0 bei Fehler:
"""
try:
headers["Cookie"] = "cnsc=" + cookie
token = str(token)
async with httpx.AsyncClient() as s:
response = await s.get(url=f"{url}?APPNAME=CampusNet&PRGNAME=COURSERESULTS&ARGUMENTS=-N{token},-N000307,",
headers=headers)
html = BeautifulSoup(response.text, 'lxml')
try:
link = html.body.find('a', attrs={'id': "Popup_details0001"})['href']
except TypeError:
return 0
response = await s.get(url=f"{url}{link[21:]}", headers=headers)
html = BeautifulSoup(response.text, 'lxml')
content = html.body.find('td', attrs={'class': 'level02'}).text
start = content.find(" ") + 4
end = start + (content[start:].find(" "))
kurs = content[start:end]
except AttributeError:
kurs = 0
return kurs
async def logOut(token: int, cookie: str):
"""
Invalidiert Token und Cookie bei Dualis.
:param token:
:param cookie:
"""
headers["Cookie"] = "cnsc=" + cookie
async with httpx.AsyncClient() as s:
await s.get(url=f"{url}?APPNAME=CampusNet&PRGNAME=LOGOUT&ARGUMENTS=-N{token}, -N001", headers=headers)
async def getSem(token: int, cookie: str):
"""
Liefert die Liste aller auf Dualis verfügbaren Semester.
:param token:
:param cookie:
:return Liste [[Semester, Semester-ID], ...]:
"""
headers["Cookie"] = "cnsc=" + cookie
token = str(token)
async with httpx.AsyncClient() as s:
response = await s.get(url=f"{url}?APPNAME=CampusNet&PRGNAME=COURSERESULTS&ARGUMENTS=-N{token},-N000307,",
headers=headers)
html = BeautifulSoup(response.text, 'lxml')
select = html.find('select')
select = select.find_all(value=True)
optlist = []
for i in select:
text = i.text.replace("Wi", "Winter").replace("So", "Sommer")
text = text.replace("Se", "semester")
optlist += [[text, i['value']]]
return optlist
async def getResults(token, cookie: str, resl: str):
"""
Liefert die Liste aller Prüfungsergebnisse eines Semesters.
:param token:
:param cookie:
:param resl:
:return [[Name, Note, Credits], ...]:
"""
headers["Cookie"] = "cnsc=" + cookie
async with httpx.AsyncClient() as s:
response = await s.get(
url=f"{url}?APPNAME=CampusNet&PRGNAME=COURSERESULTS&ARGUMENTS=-N{token},-N000307,,-N{resl}",
headers=headers)
html = BeautifulSoup(response.content.decode("utf-8"), 'lxml')
table = html.find('table', attrs={"class": "nb list"})
body = table.find("tbody")
vorlesungen = body.find_all("tr")
vorlesungenList = []
tasks = []
i = 0
for row in vorlesungen:
columns = row.find_all("td")
column = [[e.text.strip()] for e in columns]
if len(column) != 0:
if len(column[4][0]) == 0 or len(column[2][0]) == 0:
tasks += [getPruefung(s, row.find("a")["href"])]
column[2] = i
i += 1
vorlesungenList += [column[1:4]]
notlisted = await asyncio.gather(*tasks, return_exceptions=True)
for i in vorlesungenList:
for e in range(0, len(i)):
if isinstance(i[e], int):
i[e] = notlisted[i[e]]
return vorlesungenList[:-1]
async def getPruefung(s, url):
"""
Ermittelt Noten "geschachtelter" Prüfungen, die nicht auf der Hauptseite angezeigt werden.
TODO: Namen der spezifischen Prüfungen auch zurückgeben, um Zusammensetzung zu spezifizieren.
:param s:
:param url:
:return Noten-Liste:
"""
response = await s.get("https://dualis.dhbw.de" + url, headers=headers)
html = BeautifulSoup(response.content.decode("utf-8"), 'lxml')
table = html.find('table')
pruefung = table.find_all("tr")
returnList = []
for row in pruefung:
columns = row.find_all("td")
column = [e.text.strip() for e in columns]
if len(column) == 6 and len(column[3]) <= 13:
if len(column[3]) != 0:
returnList += [column[0] + ": " + column[3].split("\xa0")[0]]
if returnList[-1][0] == ':':
returnList[-1] = "Gesamt" + returnList[-1]
if len(returnList) == 0:
returnList = ["Noch nicht gesetzt"]
return returnList
def checkLifetime(timecode: float):
"""
Dualis-Token laufen nach 30 Minuten ab.
True, wenn Token noch gültig ist.
False wenn ungültig.
:param timecode:
:return:
"""
if time.time() - timecode > 1800:
return False
else:
return True
def timeOut(dualis: Dualis, cookie: str, origin: str):
"""
Checkt, ob Token und Cookie noch valide/vorhanden sind.
False, falls alles richtig ist.
Weiterleitung zum Login, falls nicht.
:param dualis:
:param cookie:
:param origin:
:return:
"""
if not checkLifetime(dualis.token_created) or not cookie:
return redirect(url_for("login", next=url_for(origin), code=10))
else:
return False