DUALIS Noten POC

This commit is contained in:
2023-12-08 11:20:00 +01:00
parent 1b69dc7085
commit 081cb4aaa7
10 changed files with 437 additions and 299 deletions

View File

@ -1,62 +0,0 @@
import requests
import urllib.parse
import time
from bs4 import BeautifulSoup
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'
}
url = "https://dualis.dhbw.de/scripts/mgrqispi.dll"
def checkUser(email: str, password: str):
s = requests.Session()
fpw = urllib.parse.quote(password, safe='', encoding=None, errors=None)
fmail = urllib.parse.quote(email, safe='', encoding=None, errors=None)
payload = 'usrname=' + fmail + '&pass=' + fpw + '&ARGUMENTS=clino%2Cusrname%2Cpass%2Cmenuno%2Cmenu_type%2Cbrowser%2Cplatform&APPNAME=CampusNet&PRGNAME=LOGINCHECK'
response = s.post(url, headers=headers, data=payload)
header = response.headers
try:
refresh = header["REFRESH"]
arg = refresh.find("=-N") + 3
komma = refresh[arg:].find(",")
except KeyError:
return -2, s
token = refresh[arg:komma + arg]
return token, s
def getKurs(token: int, cookie: str):
try:
headers["Cookie"] = "cnsc=" + cookie
token = str(token)
response = requests.request("GET",
url + "?APPNAME=CampusNet&PRGNAME=COURSERESULTS&ARGUMENTS=-N" + token + ",-N000307,",
headers=headers, data={})
html = BeautifulSoup(response.text, 'lxml')
link = html.body.find('a', attrs={'id': "Popup_details0001"})['href']
response = requests.request("GET", url + link[21:], headers=headers, data={})
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:
kurs = 0
return kurs
def logOut(token: int, cookie: str):
headers["Cookie"] = "cnsc=" + cookie
response = requests.request("GET", url + "?APPNAME=CampusNet&PRGNAME=LOGOUT&ARGUMENTS=-N" + str(token) + ", -N001",
headers=headers, data={})
def checkLifetime(timecode: float):
if time.time() - timecode > 1800:
return False
else:
return True

117
fetchDUALIS.py Normal file
View File

@ -0,0 +1,117 @@
import requests
import urllib.parse
import time
from bs4 import BeautifulSoup
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"
def checkUser(email: str, password: str):
s = requests.Session()
fpw = urllib.parse.quote(password, safe='', encoding=None, errors=None)
fmail = urllib.parse.quote(email, safe='', encoding=None, errors=None)
payload = 'usrname=' + fmail + '&pass=' + fpw + ('&ARGUMENTS=clino%2Cusrname%2Cpass%2Cmenuno%2Cmenu_type%2Cbrowser'
'%2Cplatform&APPNAME=CampusNet&PRGNAME=LOGINCHECK')
response = s.post(url, headers=headers, data=payload)
header = response.headers
try:
refresh = header["REFRESH"]
arg = refresh.find("=-N") + 3
komma = refresh[arg:].find(",")
except KeyError:
return -2, s
token = refresh[arg:komma + arg]
return token, s
def getKurs(token: int, cookie: str):
try:
headers["Cookie"] = "cnsc=" + cookie
token = str(token)
response = requests.request("GET", url +
"?APPNAME=CampusNet&PRGNAME=COURSERESULTS&ARGUMENTS=-N" + token + ",-N000307,",
headers=headers, data={})
html = BeautifulSoup(response.text, 'lxml')
link = html.body.find('a', attrs={'id': "Popup_details0001"})['href']
response = requests.request("GET", url + link[21:], headers=headers, data={})
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:
kurs = 0
return kurs
def logOut(token: int, cookie: str):
headers["Cookie"] = "cnsc=" + cookie
requests.request("GET", url + "?APPNAME=CampusNet&PRGNAME=LOGOUT&ARGUMENTS=-N" + str(token)
+ ", -N001", headers=headers, data={})
def getSem(token: int, cookie: str):
headers["Cookie"] = "cnsc=" + cookie
token = str(token)
response = requests.request("GET", url +
"?APPNAME=CampusNet&PRGNAME=COURSERESULTS&ARGUMENTS=-N" + token + ",-N000307,",
headers=headers, data={})
html = BeautifulSoup(response.text, 'lxml')
select = html.find('select')
select = select.find_all(value=True)
optlist = []
for i in select:
t = i.text.replace("Wi", "Winter").replace("So", "Sommer")
t = t.replace("Se", "semester")
optlist += [[t, i['value']]]
return optlist
def getResults(token, cookie: str, resl: str):
headers["Cookie"] = "cnsc=" + cookie
response = requests.request("GET", url + "?APPNAME=CampusNet&PRGNAME=COURSERESULTS&ARGUMENTS=-N" + token +
",-N000307," + ",-N" + resl, headers=headers, data={})
html = BeautifulSoup(response.content.decode("utf-8"), 'lxml')
table = html.find('table', attrs={"class": "nb list"})
body = table.find("tbody")
vorl = body.find_all("tr")
vorlist = []
for row in vorl:
cols = row.find_all("td")
col = [e.text.strip() for e in cols]
if len(col) != 0:
if len(col[4]) == 0:
col[2] = getPruefung(row.find("a")["href"])
vorlist += [col[1:4]]
return vorlist
def getPruefung(url):
response = requests.request("GET", "https://dualis.dhbw.de" + url, headers=headers, data={})
html = BeautifulSoup(response.content.decode("utf-8"), 'lxml')
table = html.find('table')
pruefung = table.find_all("tr")
ret = " "
for row in pruefung:
cols = row.find_all("td")
col = [e.text.strip() for e in cols]
if len(col) == 6:
ret = col[3][:3]
if ret[1] != ",":
ret = "noch nicht gesetzt"
return ret
def checkLifetime(timecode: float):
if time.time() - timecode > 1800:
return False
else:
return True

View File

@ -1,5 +1,5 @@
#!/bin/sh #!/bin/sh
mysql -e "USE paulmrtn_DUALHUB; CREATE TABLE user ( id int NOT NULL, email VARCHAR(255), password VARCHAR(255), name VARCHAR(255), kurs VARCHAR (15), PRIMARY KEY (ID), UNIQUE (ID, EMAIL) );" mysql -e "USE paulmrtn_DUALHUB; CREATE TABLE user ( id int NOT NULL, email VARCHAR(255), password VARCHAR(255), name VARCHAR(255), kurs VARCHAR (15), PRIMARY KEY (ID), UNIQUE (ID, EMAIL) );"
mysql -e "USE paulmrtn_DUALHUB; CREATE TABLE dualis ( uid int NOT NULL, token VARCHAR(255), result_lists VARCHAR(255), token_created INT, PRIMARY KEY (uid));" mysql -e "USE paulmrtn_DUALHUB; CREATE TABLE dualis ( uid int NOT NULL, token VARCHAR(255), result_list VARCHAR(15), token_created INT, PRIMARY KEY (uid));"
mysql -e "USE paulmrtn_DUALHUB; CREATE TABLE meals ( id int NOT NULL, date date, name VARCHAR(200), vegetarian tinyint(1), vegan tinyint(1), schwein tinyint(1), PRIMARY KEY (id));" mysql -e "USE paulmrtn_DUALHUB; CREATE TABLE meals ( id int NOT NULL, date date, name VARCHAR(200), vegetarian tinyint(1), vegan tinyint(1), schwein tinyint(1), PRIMARY KEY (id));"

View File

@ -45,7 +45,7 @@ class Dualis(db.Model):
token = db.Column(db.String(255), unique=True) token = db.Column(db.String(255), unique=True)
uid = db.Column(db.Integer, primary_key=True) uid = db.Column(db.Integer, primary_key=True)
token_created = db.Column(db.Integer) token_created = db.Column(db.Integer)
result_lists = db.Column(db.String(255)) result_list = db.Column(db.String(15))
class Meals(db.Model): class Meals(db.Model):

View File

@ -1,19 +1,17 @@
#!/usr/bin/env python3.6 #!/usr/bin/env python3.6
from flask import Flask, make_response from flask import make_response
from flask import render_template, url_for, send_from_directory, redirect, request, send_file from flask import render_template, url_for, redirect, request
from flask_login import login_user, login_required, current_user, LoginManager, UserMixin, logout_user, login_manager from flask_login import login_user, login_required, current_user, logout_user
from flask_sqlalchemy import SQLAlchemy from werkzeug.exceptions import HTTPException
from werkzeug.security import generate_password_hash, check_password_hash from werkzeug.security import generate_password_hash, check_password_hash
from talisman import Talisman
import hashlib import hashlib
import datetime import datetime
import time import time
import dualisauth import fetchDUALIS
import fetchRAPLA import fetchRAPLA
import requesthelpers import requesthelpers
from fetchRAPLA import * from fetchRAPLA import *
from get_mysql import get_mysql
from calendar_generation import getWeek from calendar_generation import getWeek
from init import * from init import *
@ -32,69 +30,15 @@ def welcome():
+ name + " (" + kurs + ")") + name + " (" + kurs + ")")
@app.route("/backendpoc/set-up") @app.route("/backendpoc/noten")
@login_required @login_required
def getKurs(): def displayNoten():
d = Dualis.query.filter_by(uid=current_user.id).first() d = Dualis.query.filter_by(uid=current_user.id).first()
if d: t = d.token
e = False sem = d.result_list
if not current_user.kurs: c = request.cookies.get("cnsc")
cookie = request.cookies.get("cnsc") res = fetchDUALIS.getResults(t, c, sem)
kurs = dualisauth.getKurs(d.token, cookie) return render_template("noten.html", noten=res, semester=fetchDUALIS.getSem(t, c))
if kurs != 0:
current_user.kurs = kurs
db.session.commit()
else:
e = True
else:
kurs = current_user.kurs
current_user.kurs = kurs
db.session.commit()
else:
e = True
kurs = ""
return render_template('kurs.html', detected=(kurs, e))
@app.route("/backendpoc/error<int:ecode>")
def error(ecode):
if ecode == 900:
msg = "Ungültige RAPLA-URL! Sicher, dass der Link zum DHBW-Rapla führt?"
elif ecode == 899:
msg = "Der Kalender wurde nicht gefunden! Sicher, dass der Link korrekt ist?"
else:
msg = "Unbekannter Fehler!"
return render_template('index.html', message=msg, headermessage="DualHub")
@app.route("/backendpoc/rapla")
@login_required
def chooseRaplas():
r = getRaplas()
return render_template("rapla.html", raplas=r)
@login_required
@app.route("/backendpoc/rapla", methods=["POST"])
def getRapla():
file = str(request.form.get("file"))
url = str(request.form.get("url"))
if file == url == "None":
return redirect(url_for("chooseRaplas"))
if file != "None":
User.query.filter_by(id=current_user.id).first().kurs = file[5:-5]
db.session.commit()
#return send_file("calendars/" + file)
elif url != "None":
file = getNewRapla(url)
if type(file) is not int:
User.query.filter_by(id=current_user.id).first().kurs = file[5:-5]
db.session.commit()
#return send_file("calendars/" + file)
else:
return redirect(url_for("error", ecode=file + 900))
return redirect(url_for("welcome"))
@app.route("/backendpoc/plan", methods=["GET"]) @app.route("/backendpoc/plan", methods=["GET"])
@ -134,6 +78,84 @@ def displayPlan(kurs):
else: else:
return redirect(url_for("login")) return redirect(url_for("login"))
@app.route("/backendpoc/set-up")
def redKurs():
return redirect(url_for("getKurs"))
@app.route("/backendpoc/set-up/kurs")
@login_required
def getKurs():
d = Dualis.query.filter_by(uid=current_user.id).first()
if d:
e = False
if not current_user.kurs:
cookie = request.cookies.get("cnsc")
kurs = fetchDUALIS.getKurs(d.token, cookie)
if kurs != 0:
current_user.kurs = kurs
db.session.commit()
else:
e = True
else:
kurs = current_user.kurs
current_user.kurs = kurs
db.session.commit()
else:
e = True
kurs = ""
return render_template('kurs.html', detected=(kurs, e))
@app.route("/backendpoc/set-up/semester")
@login_required
def getSemester():
t = Dualis.query.filter_by(uid=current_user.id).first().token
c = request.cookies.get("cnsc")
return render_template("semester.html", semester=fetchDUALIS.getSem(t, c))
@app.route("/backendpoc/set-up/semester", methods=["POST"])
@login_required
def setSemester():
n = request.args.get("next")
if not n:
n = url_for("welcome")
d = Dualis.query.filter_by(uid=current_user.id).first()
d.result_list = request.form.get("sem")
db.session.commit()
return redirect(n)
@app.route("/backendpoc/set-up/rapla")
@login_required
def chooseRaplas():
r = getRaplas()
return render_template("rapla.html", raplas=r)
@login_required
@app.route("/backendpoc/set-up/rapla", methods=["POST"])
def getRapla():
file = str(request.form.get("file"))
url = str(request.form.get("url"))
if file == url == "None":
return redirect(url_for("chooseRaplas"))
if file != "None":
User.query.filter_by(id=current_user.id).first().kurs = file[5:-5]
db.session.commit()
elif url != "None":
file = getNewRapla(url)
if type(file) is not int:
User.query.filter_by(id=current_user.id).first().kurs = file[5:-5]
db.session.commit()
else:
return redirect(url_for("error", ecode=file + 900))
return redirect(url_for("welcome"))
@app.route("/backendpoc/log-in") @app.route("/backendpoc/log-in")
def login(code: int = None): def login(code: int = None):
if code: if code:
@ -156,14 +178,14 @@ def login_post():
if user: if user:
dualis = Dualis.query.filter_by(uid=user.id).first() dualis = Dualis.query.filter_by(uid=user.id).first()
if check_password_hash(user.password, password): if check_password_hash(user.password, password):
if not dualis.token or not dualisauth.checkLifetime(dualis.token_created): if not dualis.token or not fetchDUALIS.checkLifetime(dualis.token_created):
new_token = dualisauth.checkUser(email, password) new_token = fetchDUALIS.checkUser(email, password)
dualis.token = new_token[0] dualis.token = new_token[0]
newcookie = requesthelpers.getCookie(new_token[1].cookies) newcookie = requesthelpers.getCookie(new_token[1].cookies)
dualis.token_created = time.time() dualis.token_created = time.time()
db.session.commit() db.session.commit()
else: else:
t = dualisauth.checkUser(email, password) t = fetchDUALIS.checkUser(email, password)
if t[0] == -2: if t[0] == -2:
return redirect(url_for("login", code=-2)) return redirect(url_for("login", code=-2))
else: else:
@ -174,11 +196,12 @@ def login_post():
db.session.commit() db.session.commit()
login_user(user) login_user(user)
if user.kurs: if user.kurs:
success = make_response(redirect(url_for("welcome"))) if not n:
success = make_response(redirect(url_for("welcome")))
success.set_cookie("cnsc", value=newcookie, httponly=True, secure=True) success.set_cookie("cnsc", value=newcookie, httponly=True, secure=True)
return success return success
t = dualisauth.checkUser(email, password) t = fetchDUALIS.checkUser(email, password)
if t[0] == -2: if t[0] == -2:
return redirect(url_for("login", code=-2)) return redirect(url_for("login", code=-2))
@ -187,7 +210,6 @@ def login_post():
pname = email.find(".") + 1 pname = email.find(".") + 1
ename = min(email[pname:].find("."), email[pname:].find("@")) ename = min(email[pname:].find("."), email[pname:].find("@"))
name = email[pname:pname + ename].capitalize() name = email[pname:pname + ename].capitalize()
new_user = User(email=email, password=hashpw, name=name, id=hashid) new_user = User(email=email, password=hashpw, name=name, id=hashid)
db.session.add(new_user) db.session.add(new_user)
@ -207,15 +229,32 @@ def login_post():
def logout(): def logout():
cookie = request.cookies.get("cnsc") cookie = request.cookies.get("cnsc")
dualis = Dualis.query.filter_by(uid=current_user.id).first() dualis = Dualis.query.filter_by(uid=current_user.id).first()
dualisauth.logOut(dualis.token, cookie) fetchDUALIS.logOut(dualis.token, cookie)
dualis.token = None dualis.token = None
db.session.commit() db.session.commit()
logout_user() logout_user()
red = make_response(redirect(url_for("login", code=1))) red = make_response(redirect(url_for("login", code=1)))
red.set_cookie("cnsc", value="Logged out! Your temporary token " red.set_cookie("cnsc", value="Logged out! Your temporary token "
"on our server and the cookie on your device have been deleted.", httponly=True, secure=True) "on our server and the cookie on your device have been deleted.", httponly=True,
secure=True)
return red return red
@app.route("/backendpoc/error<int:ecode>")
def error(ecode):
if ecode == 900:
msg = "Ungültige RAPLA-URL! Sicher, dass der Link zum DHBW-Rapla führt?"
elif ecode == 899:
msg = "Der Kalender wurde nicht gefunden! Sicher, dass der Link korrekt ist?"
else:
msg = "Unbekannter Fehler!"
return render_template('index.html', message=msg, headermessage="DualHub")
@app.errorhandler(HTTPException)
def handle(e):
return render_template('index.html', message=e, headermessage="DualHub")
if __name__ == "__main__": if __name__ == "__main__":
app.run(host='0.0.0.0', port=2024, debug=True) app.run(host='0.0.0.0', port=2024, debug=True)

View File

@ -19,7 +19,7 @@ input[type=url], input[type=email], input[type=password] {
} }
select { select {
width: 150px; width: 200px;
height: 50px height: 50px
} }

View File

@ -9,7 +9,7 @@
<body> <body>
{% if not detected[1] %} {% if not detected[1] %}
<h1>Wir haben {{ detected[0] }} als deinen Kurs ermittelt. Falls er nicht stimmt, kannst du ihn unten auswählen.</h1> <h1>Wir haben {{ detected[0] }} als deinen Kurs ermittelt. Falls er nicht stimmt, kannst du ihn unten auswählen.</h1>
<form action={{ url_for("welcome") }}> <form action={{ url_for("getSemester") }}>
<input type="submit" value="Der Kurs stimmt!"> <input type="submit" value="Der Kurs stimmt!">
</form> </form>
{% else %} {% else %}

24
templates/noten.html Normal file
View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Noten</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
<script async src="https://analytics.paulmartin.cloud/script.js" data-website-id="459fa66e-e255-4393-8e89-ead8b1572d0d"></script>
</head>
<body>
{% for i in noten %}
<h2>{{ i[0] }}: {{ i[1].capitalize() }} (Credits: {{ i[2] }})</h2>
{% endfor %}
<form method="post" action={{ url_for ("setSemester", next=url_for("displayNoten")) }}>
<label for="sem">Semester wählen! </label>
<select name="sem" id="sem">
<option value = "none" selected disabled hidden></option>
{% for i in range (semester|length) %}
<option value= {{ semester [i] [1] }}>{{ semester [i] [0] }} </option>
{% endfor %}
</select>
<input type="submit" value="Semester setzen!">
</form>
</body>
</html>

View File

@ -7,19 +7,17 @@
</head> </head>
<body> <body>
<h1>Verfügbare Raplas </h1> <h1>Verfügbare Raplas </h1>
{% block content %} <form method="post" action={{ url_for ("getRapla") }}>
<form method="post" action={{ url_for ("getRapla") }}> <label for="file">Vefügbaren RAPLA wählen! </label>
<label for="file">Vefügbaren RAPLA wählen! </label>
<select name="file" id="file"> <select name="file" id="file">
<option value = "none" selected disabled hidden></option> <option value = "none" selected disabled hidden></option>
{% for i in range (raplas[0]|length) %} {% for i in range (raplas[0]|length) %}
<option value= {{ raplas [1] [i] }}>{{ raplas [0] [i] }} </option> <option value= {{ raplas [1] [i] }}>{{ raplas [0] [i] }} </option>
{% endfor %} {% endfor %}
</select> </select>
<input type="submit" value="Importieren!"> <input type="submit" value="Importieren!">
</form> </form>
{% endblock %}
<h1>Eigenen Rapla hinzufügen</h1> <h1>Eigenen Rapla hinzufügen</h1>
<form method="post" action={{ url_for ("getRapla") }}> <form method="post" action={{ url_for ("getRapla") }}>
<label for="url">Rapla-URL eingeben, falls Du deinen Kurs nicht siehst:</label> <label for="url">Rapla-URL eingeben, falls Du deinen Kurs nicht siehst:</label>

22
templates/semester.html Normal file
View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Semester wählen</title>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
<script async src="https://analytics.paulmartin.cloud/script.js" data-website-id="459fa66e-e255-4393-8e89-ead8b1572d0d"></script>
</head>
<body>
<h1>Bitte wähle aus der unten stehenden Liste das Semester, dessen Noten du sehen möchtest.</h1>
<form method="post" action={{ url_for ("setSemester") }}>
<label for="sem">Semester wählen! </label>
<select name="sem" id="sem">
<option value = "none" selected disabled hidden></option>
{% for i in range (semester|length) %}
<option value= {{ semester [i] [1] }}>{{ semester [i] [0] }} </option>
{% endfor %}
</select>
<input type="submit" value="Semester setzen!">
</form>
</body>
</html>