5 Commits

Author SHA1 Message Date
447800ad73 complete rapla async 2024-04-10 13:10:56 +02:00
ccb088e36d optimize mensa async 2024-04-09 11:04:18 +02:00
0d31d84d48 complete mensa async 2024-04-09 08:50:05 +02:00
490ee7f02f complete dualis async 2024-04-09 00:16:59 +02:00
211ec18887 async test 2024-04-08 20:48:34 +02:00
15 changed files with 613 additions and 255 deletions

View File

@ -1,3 +1,6 @@
import time
import asyncio
import icalendar import icalendar
import datetime import datetime
import recurring_ical_events import recurring_ical_events
@ -10,7 +13,7 @@ months = ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August"
"November", "Dezember"] "November", "Dezember"]
def getWeek(weekstart: datetime, file: str, showsat: bool): async def getWeek(weekstart: datetime, file: str, showsat: bool):
""" """
Liefert alle Events einer Woche zurück. \n Liefert alle Events einer Woche zurück. \n
Wochenstart wird automatisch auf den Montag der Woche gelegt. \n Wochenstart wird automatisch auf den Montag der Woche gelegt. \n
@ -67,10 +70,10 @@ def getWeek(weekstart: datetime, file: str, showsat: bool):
"day": estart.day "day": estart.day
} }
eventl += [eventdict] eventl += [eventdict]
return eventl, daylist(start_date, showsat), prevw, nextw, mon return eventl, await daylist(start_date, showsat), prevw, nextw, mon
def daylist(weekstart: datetime, showsat: bool): async def daylist(weekstart: datetime, showsat: bool):
""" """
Gibt die Essen einer Woche zurück. Gibt die Essen einer Woche zurück.
:param weekstart: :param weekstart:
@ -83,15 +86,19 @@ def daylist(weekstart: datetime, showsat: bool):
r = 6 r = 6
else: else:
r = 5 r = 5
essen = []
for i in range(r): for i in range(r):
essen = getMeals(weekday) essen += [getMeals(weekday)]
dayl += [{ dayl += [{
"day": weekday.day, "day": weekday.day,
"short": shortnames[i], "short": shortnames[i],
"long": longnames[i], "long": longnames[i],
"mensa": essen "mensa": i
}] }]
weekday += datetime.timedelta(days=1) weekday += datetime.timedelta(days=1)
essenl = await asyncio.gather(*essen, return_exceptions=True)
for day in range(r):
dayl[day]["mensa"] = essenl[day]
return dayl return dayl

View File

@ -1,6 +1,2 @@
{ {
"TINF22B3": [
"raplaTINF22B3.ical",
"https://rapla.dhbw-karlsruhe.de/rapla?page=ical&user=vollmer&file=tinf22b3"
]
} }

View File

@ -1,9 +1,10 @@
import requests
import urllib.parse import urllib.parse
import time import time
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from flask import redirect, url_for from flask import redirect, url_for
from init import Dualis from init import Dualis
import asyncio
import httpx
headers = { headers = {
'Cookie': 'cnsc=0', 'Cookie': 'cnsc=0',
@ -16,31 +17,32 @@ headers = {
url = "https://dualis.dhbw.de/scripts/mgrqispi.dll" url = "https://dualis.dhbw.de/scripts/mgrqispi.dll"
def checkUser(email: str, password: str): async def checkUser(email: str, password: str):
""" """
Erhält von Dualis den Token und Cookie für User. Erhält von Dualis den Token und Cookie für User.
:param email: :param email:
:param password: :param password:
:return (Token, Session): :return (Token, Session):
""" """
s = requests.Session() async with httpx.AsyncClient() as s:
fpw = urllib.parse.quote(password, safe='', encoding=None, errors=None) fpw = urllib.parse.quote(password, safe='', encoding=None, errors=None)
fmail = urllib.parse.quote(email, 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' content = (f'usrname={fmail}&pass={fpw}&ARGUMENTS=clino%2Cusrname%2Cpass%2Cmenuno%2Cmenu_type%2Cbrowser'
'%2Cplatform&APPNAME=CampusNet&PRGNAME=LOGINCHECK') f'%2Cplatform&APPNAME=CampusNet&PRGNAME=LOGINCHECK')
response = s.post(url, headers=headers, data=payload) response = await s.post(url=url, headers=headers, content=content)
header = response.headers header = response.headers
try: try:
refresh = header["REFRESH"] refresh = header["REFRESH"]
arg = refresh.find("=-N") + 3 arg = refresh.find("=-N") + 3
komma = refresh[arg:].find(",") komma = refresh[arg:].find(",")
cookie = s.cookies.get("cnsc")
except KeyError: except KeyError:
return -2, s return -2, s
token = refresh[arg:komma + arg] token = refresh[arg:komma + arg]
return token, s return token, cookie
def getKurs(token: int, cookie: str): async def getKurs(token: int, cookie: str):
""" """
Bestimmt aus der ersten Prüfung den Kursbezeichner des Users. Bestimmt aus der ersten Prüfung den Kursbezeichner des Users.
TODO: Umstellen auf Bezeichner INKL. Standort TODO: Umstellen auf Bezeichner INKL. Standort
@ -51,12 +53,12 @@ def getKurs(token: int, cookie: str):
try: try:
headers["Cookie"] = "cnsc=" + cookie headers["Cookie"] = "cnsc=" + cookie
token = str(token) token = str(token)
response = requests.request("GET", url + async with httpx.AsyncClient as s:
"?APPNAME=CampusNet&PRGNAME=COURSERESULTS&ARGUMENTS=-N" + token + ",-N000307,", response = await s.get(url=f"{url}?APPNAME=CampusNet&PRGNAME=COURSERESULTS&ARGUMENTS=-N{token},-N000307,",
headers=headers, data={}) headers=headers)
html = BeautifulSoup(response.text, 'lxml') html = BeautifulSoup(response.text, 'lxml')
link = html.body.find('a', attrs={'id': "Popup_details0001"})['href'] link = html.body.find('a', attrs={'id': "Popup_details0001"})['href']
response = requests.request("GET", url + link[21:], headers=headers, data={}) response = await s.get(url=f"{url}{link[21:]}", headers=headers)
html = BeautifulSoup(response.text, 'lxml') html = BeautifulSoup(response.text, 'lxml')
content = html.body.find('td', attrs={'class': 'level02'}).text content = html.body.find('td', attrs={'class': 'level02'}).text
start = content.find(" ") + 4 start = content.find(" ") + 4
@ -67,18 +69,18 @@ def getKurs(token: int, cookie: str):
return kurs return kurs
def logOut(token: int, cookie: str): async def logOut(token: int, cookie: str):
""" """
Invalidiert Token und Cookie bei Dualis. Invalidiert Token und Cookie bei Dualis.
:param token: :param token:
:param cookie: :param cookie:
""" """
headers["Cookie"] = "cnsc=" + cookie headers["Cookie"] = "cnsc=" + cookie
requests.request("GET", url + "?APPNAME=CampusNet&PRGNAME=LOGOUT&ARGUMENTS=-N" + str(token) async with httpx.AsyncClient() as s:
+ ", -N001", headers=headers, data={}) await s.get(url=f"{url}?APPNAME=CampusNet&PRGNAME=LOGOUT&ARGUMENTS=-N{token}, -N001", headers=headers)
def getSem(token: int, cookie: str): async def getSem(token: int, cookie: str):
""" """
Liefert die Liste aller auf Dualis verfügbaren Semester. Liefert die Liste aller auf Dualis verfügbaren Semester.
:param token: :param token:
@ -87,9 +89,9 @@ def getSem(token: int, cookie: str):
""" """
headers["Cookie"] = "cnsc=" + cookie headers["Cookie"] = "cnsc=" + cookie
token = str(token) token = str(token)
response = requests.request("GET", url + async with httpx.AsyncClient() as s:
"?APPNAME=CampusNet&PRGNAME=COURSERESULTS&ARGUMENTS=-N" + token + ",-N000307,", response = await s.get(url=f"{url}?APPNAME=CampusNet&PRGNAME=COURSERESULTS&ARGUMENTS=-N{token},-N000307,",
headers=headers, data={}) headers=headers)
html = BeautifulSoup(response.text, 'lxml') html = BeautifulSoup(response.text, 'lxml')
select = html.find('select') select = html.find('select')
select = select.find_all(value=True) select = select.find_all(value=True)
@ -101,7 +103,7 @@ def getSem(token: int, cookie: str):
return optlist return optlist
def getResults(token, cookie: str, resl: str): async def getResults(token, cookie: str, resl: str):
""" """
Liefert die Liste aller Prüfungsergebnisse eines Semesters. Liefert die Liste aller Prüfungsergebnisse eines Semesters.
:param token: :param token:
@ -110,31 +112,43 @@ def getResults(token, cookie: str, resl: str):
:return [[Name, Note, Credits], ...]: :return [[Name, Note, Credits], ...]:
""" """
headers["Cookie"] = "cnsc=" + cookie headers["Cookie"] = "cnsc=" + cookie
response = requests.request("GET", url + "?APPNAME=CampusNet&PRGNAME=COURSERESULTS&ARGUMENTS=-N" + token + async with httpx.AsyncClient() as s:
",-N000307," + ",-N" + resl, headers=headers, data={}) 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') html = BeautifulSoup(response.content.decode("utf-8"), 'lxml')
table = html.find('table', attrs={"class": "nb list"}) table = html.find('table', attrs={"class": "nb list"})
body = table.find("tbody") body = table.find("tbody")
vorl = body.find_all("tr") vorl = body.find_all("tr")
vorlist = [] vorlist = []
tasks = []
i = 0
for row in vorl: for row in vorl:
cols = row.find_all("td") cols = row.find_all("td")
col = [[e.text.strip()] for e in cols] col = [[e.text.strip()] for e in cols]
if len(col) != 0: if len(col) != 0:
if len(col[4][0]) == 0: if len(col[4][0]) == 0:
col[2] = getPruefung(row.find("a")["href"]) tasks += [getPruefung(s, row.find("a")["href"])]
col[2] = i
i += 1
vorlist += [col[1:4]] vorlist += [col[1:4]]
return vorlist notlisted = await asyncio.gather(*tasks, return_exceptions=True)
for i in vorlist:
for e in range(0, len(i)):
if isinstance(i[e], int):
i[e] = notlisted[i[e]]
return vorlist[:-1]
def getPruefung(url): async def getPruefung(s, url):
""" """
Ermittelt Noten "geschachtelter" Prüfungen, die nicht auf der Hauptseite angezeigt werden. 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. TODO: Namen der spezifischen Prüfungen auch zurückgeben, um Zusammensetzung zu spezifizieren.
:param s:
:param url: :param url:
:return list: :return list:
""" """
response = requests.request("GET", "https://dualis.dhbw.de" + url, headers=headers, data={}) response = await s.get("https://dualis.dhbw.de" + url, headers=headers)
html = BeautifulSoup(response.content.decode("utf-8"), 'lxml') html = BeautifulSoup(response.content.decode("utf-8"), 'lxml')
table = html.find('table') table = html.find('table')
pruefung = table.find_all("tr") pruefung = table.find_all("tr")

View File

@ -1,13 +1,16 @@
import json import json
from init import db, Meals, scheduler
import asyncio
from init import db, Meals, scheduler, app
import datetime import datetime
import requests
import time import time
import httpx
nomeal = 'Essen nicht (mehr) verfügbar' nomeal = 'Essen nicht (mehr) verfügbar'
def getMeals(day: datetime): async def getMeals(day: datetime):
""" """
Liefert alle Mahlzeiten eines Tages. \n Liefert alle Mahlzeiten eines Tages. \n
Befinden sie sich schon in der Datenbank, werden diese zurückgegeben. \n Befinden sie sich schon in der Datenbank, werden diese zurückgegeben. \n
@ -23,10 +26,10 @@ def getMeals(day: datetime):
essen += [i.name] essen += [i.name]
essen.sort(key=len, reverse=True) essen.sort(key=len, reverse=True)
return essen return essen
return getMealsFromAPI(day, dbentry=True) return await getMealsFromAPI(day, dbentry=True)
def getMealsFromAPI(day: str, dbentry: bool = False): async def getMealsFromAPI(day: str, dbentry: bool = False):
""" """
Fragt die Mensa-API nach den Mahlzeiten eines Tages ab. \n Fragt die Mensa-API nach den Mahlzeiten eines Tages ab. \n
Wenn dbentry: Schreibt die Ergebnisse in die Datenbank. \n Wenn dbentry: Schreibt die Ergebnisse in die Datenbank. \n
@ -35,8 +38,8 @@ def getMealsFromAPI(day: str, dbentry: bool = False):
:param dbentry: :param dbentry:
:return [Name1, Name2, ...]: :return [Name1, Name2, ...]:
""" """
url = "https://dh-api.paulmartin.cloud/plans/" + day + "?canteens=erzberger" async with httpx.AsyncClient() as s:
response = requests.request("GET", url) response = await s.get(url=f"https://dh-api.paulmartin.cloud/plans/{day}?canteens=erzberger")
response = response.content response = response.content
jres = json.loads(response.decode("utf-8")) jres = json.loads(response.decode("utf-8"))
essen = [] essen = []
@ -110,14 +113,13 @@ def formatDay(day: datetime):
return day return day
@scheduler.task('cron', id="refreshMeals", hour='8-11', day_of_week='*', minute='15', week='*', second='30') async def refreshMeals():
def refreshMeals():
""" """
Aktualisiert immer vormittags alle Mahlzeiten in der Datenbank. \n Aktualisiert immer vormittags alle Mahlzeiten in der Datenbank. \n
Datenbankeinträge werden ersetzt, wenn die API andere Mahlzeiten liefert. Datenbankeinträge werden ersetzt, wenn die API andere Mahlzeiten liefert.
""" """
print("Aktualisiere Essenspläne...\n") print("Aktualisiere Essenspläne...\n")
with scheduler.app.app_context():
table = Meals.query.all() table = Meals.query.all()
dates = [] dates = []
for i in table: for i in table:
@ -126,7 +128,8 @@ def refreshMeals():
for i in range(len(dates)): for i in range(len(dates)):
dates[i] = formatDay(dates[i]) dates[i] = formatDay(dates[i])
for i in dates: for i in dates:
apinames = getMealsFromAPI(i) apinames = await getMealsFromAPI(i)
apinames = await getMealsFromAPI(i)
dbmeals = Meals.query.filter_by(date=i).all() dbmeals = Meals.query.filter_by(date=i).all()
dbnames = [] dbnames = []
for m in dbmeals: for m in dbmeals:
@ -135,4 +138,10 @@ def refreshMeals():
for n in dbnames: for n in dbnames:
db.session.delete(Meals.query.filter_by(date=i, name=n).first()) db.session.delete(Meals.query.filter_by(date=i, name=n).first())
db.session.commit() db.session.commit()
getMealsFromAPI(i, True) await getMealsFromAPI(i, True)
@scheduler.task('cron', id="mensaschedule", hour='8-11', day_of_week='*', minute='*/15', week='*', second='5')
def mensaschedule():
with app.app_context():
asyncio.run(refreshMeals())

View File

@ -1,9 +1,24 @@
import urllib.error import urllib.error
from urllib.request import urlretrieve from urllib.request import urlretrieve
import asyncio
import httpx
import icalendar import icalendar
import json import json
import recurring_ical_events import recurring_ical_events
from init import scheduler
from init import scheduler, app
async def fetchPlan(session, url):
return await session.get(url=url)
def writeToFile(filename, data):
with open(filename, 'w+') as f:
f.write(data.text)
f.close()
def parseURL(url: str): def parseURL(url: str):
@ -34,7 +49,7 @@ def parseURL(url: str):
return 0 return 0
def getNewRapla(url: str): async def getNewRapla(url: str):
""" """
Speichert den iCal eines Raplas auf dem Server. \n Speichert den iCal eines Raplas auf dem Server. \n
Gibt Namen der Datei zurück. \n Gibt Namen der Datei zurück. \n
@ -50,16 +65,18 @@ def getNewRapla(url: str):
try: try:
urlretrieve(url, "calendars/rapla" + kurs + ".ical") urlretrieve(url, "calendars/rapla" + kurs + ".ical")
async with httpx.AsyncClient() as s:
response = await fetchPlan(s, url)
writeToFile(f"calendars/rapla{kurs}.ical", response)
except urllib.error.URLError: except urllib.error.URLError:
return -1 return -1
file = open("calendars/list.json", "r+") file = open("calendars/list.json", "r+")
jsoncal = json.load(file) jsoncal = json.load(file)
jsoncal.update({kurs: ["rapla" + kurs + ".ical", url]}) jsoncal.update({kurs: [f"rapla{kurs}.ical", url]})
file.close() file.close()
file = open("calendars/list.json", "w") file = open("calendars/list.json", "w")
json.dump(jsoncal, file, indent=4) json.dump(jsoncal, file, indent=4)
return "rapla" + kurs + ".ical" return f"rapla{kurs}.ical"
def getIcal(kurs: str): def getIcal(kurs: str):
@ -93,13 +110,23 @@ def getRaplas():
return sorted(kursl), sorted(filel), sorted(urll) return sorted(kursl), sorted(filel), sorted(urll)
@scheduler.task("interval", id="refreshRapla", minutes=5) async def refreshRapla():
def refreshRapla():
""" """
Aktualisiert alle 5 Minuten alle gespeicherten Raplas. Aktualisiert alle 5 Minuten alle gespeicherten Raplas.
""" """
filel = getRaplas()[1] filel = getRaplas()[1]
urll = getRaplas()[2] urll = getRaplas()[2]
jobl = []
async with httpx.AsyncClient() as s:
for i in range(len(filel)): for i in range(len(filel)):
print("Update Rapla: " + filel[i][:-5]) print(f"Update Rapla: {filel[i][:-5]}")
urlretrieve(urll[i], "calendars/" + filel[i]) jobl += [fetchPlan(s, urll[i])]
callist = await asyncio.gather(*jobl, return_exceptions=True)
for cal in range(len(callist)):
writeToFile(f"calendars/{filel[cal]}", callist[cal])
@scheduler.task('cron', id="raplaschedule", hour='*', day_of_week='*', minute='*/15', week='*')
def raplaschedule():
with app.app_context():
asyncio.run(refreshRapla())

22
init.py
View File

@ -1,11 +1,12 @@
import apscheduler
from flask import Flask from flask import Flask
from flask_login import LoginManager, UserMixin from flask_login import LoginManager, UserMixin
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from flask_talisman import Talisman from flask_talisman import Talisman
from get_mysql import get_mysql from get_mysql import get_mysql
import atexit import atexit
from flask_apscheduler import APScheduler from flask_apscheduler import APScheduler
from turbo_flask import Turbo
def create(): def create():
@ -14,7 +15,6 @@ def create():
:return app: :return app:
""" """
app = Flask(__name__) app = Flask(__name__)
turbo.init_app(app)
dbpw = get_mysql()[1] dbpw = get_mysql()[1]
dbun = get_mysql()[0] dbun = get_mysql()[0]
@ -56,7 +56,17 @@ 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_list = db.Column(db.String(15)) semester = db.Column(db.String(15))
class Semesterlist(db.Model):
"""
Datenbank-Modell für Semester-Liste.
"""
uid = db.Column(db.Integer)
semestername = db.Column(db.String(25))
semesterid = db.Column(db.String(15))
itemid = db.Column(db.Integer, primary_key=True)
class Meals(db.Model): class Meals(db.Model):
@ -71,11 +81,11 @@ class Meals(db.Model):
schwein = db.Column(db.Boolean) schwein = db.Column(db.Boolean)
scheduler = APScheduler() scheduler = APScheduler ()
turbo = Turbo()
app = create() app = create()
def_src = ["*.paulmartin.cloud", '\'self\''] def_src = ["*.paulmartin.cloud", '\'self\'']
# Talisman(app, content_security_policy={"default-src": def_src, "script-src": def_src # + ["'unsafe-inline'"]}) Talisman(app, content_security_policy={"default-src": def_src, "script-src": def_src # + ["'unsafe-inline'"]
})
scheduler.init_app(app) scheduler.init_app(app)
scheduler.start() scheduler.start()
scheduler.api_enabled = True scheduler.api_enabled = True

View File

@ -1,3 +1,6 @@
from init import Semesterlist, User
def getCookie(cookies): def getCookie(cookies):
""" """
Liefert (letzten) Cookie der Cookies-Liste zurück. Liefert (letzten) Cookie der Cookies-Liste zurück.
@ -8,3 +11,16 @@ def getCookie(cookies):
for c in cookies: for c in cookies:
cookie = c.value cookie = c.value
return cookie return cookie
def getSemesterList(uid):
semesterlist = Semesterlist.query.filter_by(uid=uid).all()
semester = []
for s in semesterlist:
semester += [[s.semestername, s.semesterid]]
semester.sort(key=lambda x: x[-1], reverse=True)
return semester
def loadUser(uid):
return User.query.filter_by(id=uid).first()

View File

@ -1,13 +1,17 @@
beautifulsoup4 beautifulsoup4~=4.12.2
Flask Flask~=3.0.3
Flask_APScheduler Flask_APScheduler
Flask_Login Flask_Login
flask_sqlalchemy flask_sqlalchemy
icalendar icalendar~=5.0.11
recurring_ical_events recurring_ical_events
Requests Requests~=2.31.0
talisman talisman
Werkzeug Werkzeug~=3.0.0
lxml lxml
bs4 bs4~=0.0.1
pytz pytz~=2023.3.post1
asyncio~=3.4.3
httpx~=1.0.0b0
celery~=5.4.0rc2

View File

@ -10,8 +10,9 @@ import time
import random import random
import fetchDUALIS import fetchDUALIS
import fetchMENSA
import fetchRAPLA import fetchRAPLA
import requesthelpers from requesthelpers import *
from fetchRAPLA import * from fetchRAPLA import *
from calendar_generation import getWeek from calendar_generation import getWeek
from init import * from init import *
@ -23,14 +24,10 @@ def index():
Leitet den normalen Website-Aufruf zum Login weiter. Leitet den normalen Website-Aufruf zum Login weiter.
:return HTML: :return HTML:
""" """
if current_user.is_authenticated: return redirect(url_for("login"))
return welcome()
else:
return login()
@app.route("/dashboard")
@app.route("/dashboard", methods=["GET", "POST"])
@login_required @login_required
def welcome(): def welcome():
""" """
@ -50,40 +47,36 @@ def welcome():
else: else:
t = "hidden" t = "hidden"
p = "" p = ""
if turbo.can_stream():
print ("Streaming...")
return turbo.stream([
turbo.update(
render_template('dashboard.html', kurs=kurs, name=name, theorie=t, praxis=p), target="content")
])
else:
print ("Not streaming!")
return render_template('dashboard.html', kurs=kurs, name=name, theorie=t, praxis=p) return render_template('dashboard.html', kurs=kurs, name=name, theorie=t, praxis=p)
@app.route("/theorie/noten") @app.route("/theorie/noten", methods=["GET", "POST"])
@login_required @login_required
def displayNoten(): async def displayNoten():
""" """
Zeigt die Noten aus Dualis an. Hierfür ist ein aktives Token nötig. Zeigt die Noten aus Dualis an. Hierfür ist ein aktives Token nötig.
:return HTML: :return HTML:
""" """
d = Dualis.query.filter_by(uid=current_user.id).first() d = Dualis.query.filter_by(uid=current_user.id).first()
if not d.result_list: if request.method == "POST":
d.semester = request.form.get("sem")
db.session.commit()
if not d.semester:
return redirect(url_for("getSemester", next=url_for(request.endpoint))) return redirect(url_for("getSemester", next=url_for(request.endpoint)))
t = d.token t = d.token
sem = d.result_list chosensemester = d.semester
c = request.cookies.get("cnsc") c = request.cookies.get("cnsc")
timeout = fetchDUALIS.timeOut(d, c, "displayNoten") timeout = fetchDUALIS.timeOut(d, c, "displayNoten")
if timeout: if timeout:
return timeout return timeout
res = fetchDUALIS.getResults(t, c, sem) semester = getSemesterList(current_user.id)
return render_template("noten.html", noten=res, semester=fetchDUALIS.getSem(t, c), sel=sem, s="n", praxis="hidden") noten = await fetchDUALIS.getResults(t, c, chosensemester)
return render_template("noten.html", noten=noten, semester=semester, sel=chosensemester, s="n", praxis="hidden")
@app.route("/plan", methods=["GET"]) @app.route("/plan", methods=["GET"])
@login_required @login_required
def displayRapla(): async def displayRapla():
""" """
Zeigt den Stundenplan für eingeloggte User an. \n Zeigt den Stundenplan für eingeloggte User an. \n
TODO: Persönliche Filter, Notizen, Essensvorlieben etc. berücksichtigen. TODO: Persönliche Filter, Notizen, Essensvorlieben etc. berücksichtigen.
@ -99,14 +92,14 @@ def displayRapla():
samstag = request.args.get("samstag") samstag = request.args.get("samstag")
if not samstag: if not samstag:
samstag = False samstag = False
events = getWeek(week, fetchRAPLA.getIcal(current_user.kurs), samstag) events = await getWeek(week, fetchRAPLA.getIcal(current_user.kurs), samstag)
return render_template("plan-user.html", events=events[0], eventdays=events[1], return render_template("plan-user.html", events=events[0], eventdays=events[1],
name=current_user.name, prev=str(events[2])[:10], next=str(events[3])[:10], mon=events[4], name=current_user.name, prev=str(events[2])[:10], next=str(events[3])[:10], mon=events[4],
s="p", praxis="hidden") s="p", praxis="hidden")
@app.route("/plan/<string:kurs>") @app.route("/plan/<string:kurs>")
def displayPlan(kurs): async def displayPlan(kurs):
""" """
Zeigt den Stundenplan ohne Login an. \n Zeigt den Stundenplan ohne Login an. \n
Präferenzen werden nicht berücksichtigt. Präferenzen werden nicht berücksichtigt.
@ -129,7 +122,7 @@ def displayPlan(kurs):
samstag = request.args.get("samstag") samstag = request.args.get("samstag")
if not samstag: if not samstag:
samstag = False samstag = False
events = getWeek(week, plan, samstag) events = await getWeek(week, plan, samstag)
return render_template("plan-anon.html", events=events[0], eventdays=events[1], kurs=kurs, return render_template("plan-anon.html", events=events[0], eventdays=events[1], kurs=kurs,
prev=str(events[2])[:10], next=str(events[3])[:10], mon=events[4], praxis="hidden") prev=str(events[2])[:10], next=str(events[3])[:10], mon=events[4], praxis="hidden")
else: else:
@ -147,7 +140,7 @@ def redKurs():
@app.route("/set-up/kurs") @app.route("/set-up/kurs")
@login_required @login_required
def getKurs(): async def getKurs():
""" """
Automatische Kurs-Auswahl. \n Automatische Kurs-Auswahl. \n
Aktives Dualis-Token benötigt. Aktives Dualis-Token benötigt.
@ -161,7 +154,7 @@ def getKurs():
return timeout return timeout
e = False e = False
if not current_user.kurs: if not current_user.kurs:
kurs = fetchDUALIS.getKurs(d.token, cookie) kurs = await fetchDUALIS.getKurs(d.token, cookie)
if kurs != 0: if kurs != 0:
if not fetchRAPLA.getIcal(kurs): if not fetchRAPLA.getIcal(kurs):
return render_template('kurs.html', detected=(kurs, e), s="s", theorie="hidden", praxis="hidden", return render_template('kurs.html', detected=(kurs, e), s="s", theorie="hidden", praxis="hidden",
@ -182,15 +175,23 @@ def getKurs():
@app.route("/set-up/semester") @app.route("/set-up/semester")
@login_required @login_required
def getSemester(): async def getSemester():
""" """
Manuelle Semester-Auswahl. Manuelle Semester-Auswahl.
:return HTML: :return HTML:
""" """
t = Dualis.query.filter_by(uid=current_user.id).first().token t = Dualis.query.filter_by(uid=current_user.id).first().token
c = request.cookies.get("cnsc") c = request.cookies.get("cnsc")
semesterlist = Semesterlist.query.filter_by(uid=current_user.id).all()
return render_template("semester.html", semester=fetchDUALIS.getSem(t, c), s="s", theorie="hidden", praxis="hidden") if not semesterlist:
semester = await fetchDUALIS.getSem(t, c)
for i in semester:
semitem = Semesterlist(semestername=i[0], semesterid=i[1], uid=current_user.id, itemid=current_user.id*int(i[1][-7:])//1000000)
db.session.add(semitem)
db.session.commit()
else:
semester = getSemesterList(current_user.id)
return render_template("semester.html", semester=semester, s="s", theorie="hidden", praxis="hidden")
@app.route("/set-up/semester", methods=["POST"]) @app.route("/set-up/semester", methods=["POST"])
@ -204,7 +205,7 @@ def setSemester():
if not n: if not n:
n = url_for("welcome") n = url_for("welcome")
d = Dualis.query.filter_by(uid=current_user.id).first() d = Dualis.query.filter_by(uid=current_user.id).first()
d.result_list = request.form.get("sem") d.semester = request.form.get("sem")
db.session.commit() db.session.commit()
return redirect(n) return redirect(n)
@ -222,7 +223,7 @@ def chooseRaplas():
@app.route("/set-up/rapla", methods=["POST"]) @app.route("/set-up/rapla", methods=["POST"])
@login_required @login_required
def getRapla(): async def getRapla():
""" """
Verarbeitet die Eingabe von chooseRaplas(). Verarbeitet die Eingabe von chooseRaplas().
:return HTML: :return HTML:
@ -232,10 +233,10 @@ def getRapla():
if file == url == "None": if file == url == "None":
return redirect(url_for("chooseRaplas")) return redirect(url_for("chooseRaplas"))
if file != "None": if file != "None":
User.query.filter_by(id=current_user.id).first().kurs = file[5:-5] loadUser(current_user.id).kurs = file[5:-5]
db.session.commit() db.session.commit()
elif url != "None": elif url != "None":
file = getNewRapla(url) file = await getNewRapla(url)
if type(file) is not int: if type(file) is not int:
User.query.filter_by(id=current_user.id).first().kurs = file[5:-5] User.query.filter_by(id=current_user.id).first().kurs = file[5:-5]
db.session.commit() db.session.commit()
@ -254,7 +255,7 @@ def login():
@app.route("/log-in", methods=["POST"]) @app.route("/log-in", methods=["POST"])
def login_post(): async def login_post():
""" """
Verarbeitet die Eingabe von login(). \n Verarbeitet die Eingabe von login(). \n
Falls der User schon angelegt ist, wird das Passwort verglichen. \n Falls der User schon angelegt ist, wird das Passwort verglichen. \n
@ -270,18 +271,18 @@ def login_post():
success = make_response(redirect(url_for("getKurs"))) success = make_response(redirect(url_for("getKurs")))
user = User.query.filter_by(email=email).first() user = User.query.filter_by(email=email).first()
t = fetchDUALIS.checkUser(email, password) t = await 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))
if user: if user:
dualis = Dualis.query.filter_by(uid=user.id).first() dualis = Dualis.query.filter_by(uid=user.id).first()
dualis.token = t[0] dualis.token = t[0]
newcookie = requesthelpers.getCookie(t[1].cookies) newcookie = t[1]
dualis.token_created = time.time() dualis.token_created = time.time()
db.session.commit() db.session.commit()
login_user(user) login_user(user)
if user.kurs: if user.kurs:
if not dualis.result_list: if not dualis.semester:
success = make_response(redirect(url_for("getSemester"))) success = make_response(redirect(url_for("getSemester")))
elif not n: elif not n:
success = make_response(redirect(url_for("welcome"))) success = make_response(redirect(url_for("welcome")))
@ -307,14 +308,14 @@ def login_post():
@app.route("/log-out") @app.route("/log-out")
@login_required @login_required
def logout(): async def logout():
""" """
Loggt den User aus. Loggt den User aus.
:return Empty Token: :return Empty Token:
""" """
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()
fetchDUALIS.logOut(dualis.token, cookie) await fetchDUALIS.logOut(dualis.token, cookie)
dualis.token = None dualis.token = None
db.session.commit() db.session.commit()
logout_user() logout_user()

View File

@ -5,38 +5,37 @@
<link rel="stylesheet" type="text/css" href={{ url_for("static", filename="style.css") }}> <link rel="stylesheet" type="text/css" href={{ url_for("static", filename="style.css") }}>
<meta http-equiv="refresh" content="510"> <meta http-equiv="refresh" content="510">
<script async src="https://analytics.paulmartin.cloud/script.js" data-website-id="459fa66e-e255-4393-8e89-ead8b1572d0d"></script> <script async src="https://analytics.paulmartin.cloud/script.js" data-website-id="459fa66e-e255-4393-8e89-ead8b1572d0d"></script>
{{ turbo() }}
{% block head %} {% block head %}
{% endblock %} {% endblock %}
</head> </head>
<body> <body>
<nav> <nav>
<ul id = "navbar-items"> <ul>
{% if theorie=="hidden" and praxis=="hidden" %} {% if theorie=="hidden" and praxis=="hidden" %}
<li class="top"><a class="top" href={{ url_for("index", sel="theorie") }}>Theorie</a></li> <li class="top"><a class="top" href={{ url_for("welcome", sel="theorie") }}>Theorie</a></li>
<li class="top"><a class="top" href={{ url_for("index", sel="praxis") }}>Praxis</a></li> <li class="top"><a class="top" href={{ url_for("welcome", sel="praxis") }}>Praxis</a></li>
{% elif theorie == "hidden" %} {% elif theorie == "hidden" %}
<li class="top"><a class="top" href={{ url_for("index", sel="theorie") }}>Theorie</a></li> <li class="top"><a class="top" href={{ url_for("welcome", sel="theorie") }}>Theorie</a></li>
<li class="top selected"><a class="top selected" href={{ url_for("index", sel="praxis") }}>Praxis</a></li> <li class="top selected"><a class="top selected" href={{ url_for("welcome", sel="praxis") }}>Praxis</a></li>
{% else %} {% else %}
<li class="top selected"><a class="top selected" href={{ url_for("index", sel="theorie") }}>Theorie</a></li> <li class="top selected"><a class="top selected" href={{ url_for("welcome", sel="theorie") }}>Theorie</a></li>
<li class="top"><a class="top" href={{ url_for("index", sel="praxis") }}>Praxis</a></li> <li class="top"><a class="top" href={{ url_for("welcome", sel="praxis") }}>Praxis</a></li>
{% endif %} {% endif %}
<li {{ theorie }}><a {% if s == "n" %} class="selected" {% endif %} href={{ url_for("displayNoten") }}>Noten</a></li> <li {{ theorie }}><a {% if s == "n" %} class="selected" {% endif %} href={{ url_for("displayNoten") }}>Noten</a></li>
<li {{ theorie }}><a {% if s == "p" %} class="selected" {% endif %} href={{ url_for("displayRapla") }}>Stundenplan</a></li> <li {{ theorie }}><a {% if s == "p" %} class="selected" {% endif %} href={{ url_for("displayRapla") }}>Stundenplan</a></li>
<li {{ praxis }}><a {% if s == "t" %} class="selected" {% endif %} href="">To Dos</a></li> <li {{ praxis }}><a {% if s == "t" %} class="selected" {% endif %} href="">To Dos</a></li>
<li><a {% if s == "s" %} class="selected" {% endif %} href={{ url_for("redKurs") }}>Konfiguration</a></li> <li><a {% if s == "s" %} class="selected" {% endif %} href={{ url_for("redKurs") }}>Konfiguration</a></li>
<li {% block logout %}{% endblock %}><a class="bottom" href={{ url_for("logout") }}>Log-Out</a></li> {% if request.endpoint %}
{% if request.endpoint[:7] != "login" %}
<li><a class="bottom" href={{ url_for("logout") }}>Log-Out</a></li>
{% endif %}
{% endif %}
</ul> </ul>
</nav> </nav>
<turbo-frame> <div class="cs1">
<div class="cs1" id = 'page'> {% block content %}
<div id="content"> {% endblock %}
{% block content %} </div>
{% endblock %}
</div>
</div>
</turbo-frame>
</body> </body>
</html> </html>

View File

@ -1,8 +1,4 @@
{% extends "index.html" %} {% extends "index.html" %}
{% block logout %}
hidden
{% endblock %}
{% block content %} {% block content %}
<div class="cs"> <div class="cs">
<h1>Einloggen</h1> <h1>Einloggen</h1>

View File

@ -1,21 +0,0 @@
{% for i in range (semester|length) %}
{% if semester[i][1]==sel %}
<h1>Deine Noten im {{ semester[i][0] }}</h1>
{% endif %}
{% endfor %}
<br>
<br>
{% for i in noten %}
{% if i[1]|length == 1 %}
{% if i[1][0][0].isnumeric() or i[1][0][-1].isalpha()%}
<h2>{{ i[0][0] }}: {{ i[1][0] }} (Credits: {{ i[2][0] }})</h2>
{% else %}
<h2>{{ i[0][0] }}: {{ i[1][0][-3:] }} (Credits: {{ i[2][0] }})</h2>
{% endif %}
{% else %}
<h2>{{ i[0][0] }} (Credits: {{ i[2][0] }}):</h2>
{% for e in i[1] %}
<h2>&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;{{ e }}</h2>
{% endfor %}
{% endif %}
{% endfor %}

View File

@ -4,14 +4,30 @@
<meta http-equiv="Refresh" content="600"> <meta http-equiv="Refresh" content="600">
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<turbo-frame> {% for i in range (semester|length) %}
<div id="noten"> {% if semester[i][1]==sel %}
{% include "noten-list.html" %} <h1>Deine Noten im {{ semester[i][0] }}</h1>
</div> {% endif %}
</turbo-frame> {% endfor %}
<br> <br>
<br> <br>
<form id= "dropdown" method="post" action={{ url_for ("setSemester", next=url_for("displayNoten")) }}> {% for i in noten %}
{% if i[1]|length == 1 %}
{% if i[1][0][0].isnumeric() or i[1][0][-1].isalpha()%}
<h2>{{ i[0][0] }}: {{ i[1][0] }} (Credits: {{ i[2][0] }})</h2>
{% else %}
<h2>{{ i[0][0] }}: {{ i[1][0][-3:] }} (Credits: {{ i[2][0] }})</h2>
{% endif %}
{% else %}
<h2>{{ i[0][0] }} (Credits: {{ i[2][0] }}):</h2>
{% for e in i[1] %}
<h2>&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;{{ e }}</h2>
{% endfor %}
{% endif %}
{% endfor %}
<br>
<br>
<form id= "dropdown" method="post" action={{ url_for ("displayNoten") }}>
<label for="sem">Semester wählen! </label> <label for="sem">Semester wählen! </label>
<select name="sem" id="sem"> <select name="sem" id="sem">
{% for i in range (semester|length) %} {% for i in range (semester|length) %}

284
tests_examples/vergleich.py Normal file
View File

@ -0,0 +1,284 @@
import asyncio
import httpx
import time
import urllib.parse
import requests
from flask import Flask
from bs4 import BeautifulSoup
from celery import Celery
import fetchDUALIS
from login_data import passwort, email #CREATE LOCAL login_data.py!!!
app = Flask(__name__)
app.config['CELERY_BROKER_URL'] = 'redis://localhost:6379/0'
app.config['CELERY_RESULT_BACKEND'] = 'redis://localhost:6379/0'
celery = Celery(app.name, broker=app.config['CELERY_BROKER_URL'])
celery.conf.update(app.config)
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"
fpw = urllib.parse.quote(passwort, safe='', encoding=None, errors=None)
fmail = urllib.parse.quote(email, safe='', encoding=None, errors=None)
@celery.task(bind=True)
def celery_requests(self):
with httpx.Client() as s:
content = (f'usrname={fmail}&pass={fpw}&ARGUMENTS=clino%2Cusrname%2Cpass%2Cmenuno%2Cmenu_type%2Cbrowser'
f'%2Cplatform&APPNAME=CampusNet&PRGNAME=LOGINCHECK')
response = s.post(url=url, headers=headers, content=content)
return response, s
# noinspection DuplicatedCode
async def checkUser_celery():
req = celery_requests.apply_async()
response = req[0]
s = req[1]
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
# noinspection DuplicatedCode
def checkUser_normal():
"""
Erhält von Dualis den Token und Cookie für User.
:param email:
:param password:
:return (Token, Session):
"""
s = requests.Session()
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]
cookies = s.cookies
cookie = 0
for c in cookies:
cookie = c.value
return token, cookie
# noinspection DuplicatedCode
async def checkUser_async():
"""
Erhält von Dualis den Token und Cookie für User.
:param email:
:param password:
:return (Token, Session):
"""
# noinspection DuplicatedCode
async with httpx.AsyncClient() as s:
content = (f'usrname={fmail}&pass={fpw}&ARGUMENTS=clino%2Cusrname%2Cpass%2Cmenuno%2Cmenu_type%2Cbrowser'
f'%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
# noinspection DuplicatedCode
def getSem_normal(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)
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
# noinspection DuplicatedCode
async def getSem_async(token, cookie):
"""
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=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:
t = i.text.replace("Wi", "Winter").replace("So", "Sommer")
t = t.replace("Se", "semester")
optlist += [[t, i['value']]]
return optlist
async def getSem_celery(token, cookie):
pass
# noinspection DuplicatedCode
async def getResults_async(token, cookie, resl):
headers["Cookie"] = "cnsc=" + cookie
async with httpx.AsyncClient() as s:
response = await s.get(url=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")
vorl = body.find_all("tr")
vorlist = []
tasks = []
i = 0
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]) == 0:
tasks += [getPruefung_async(s, row.find("a")["href"])]
col[2] = i
i += 1
vorlist += [col[1:4]]
extrakurse = await asyncio.gather(*tasks, return_exceptions=True)
for i in vorlist:
for e in range(0, len(i)):
if isinstance(i[e], int):
i[e] = extrakurse[i[e]]
return vorlist[:-1]
# noinspection DuplicatedCode
async def getPruefung_async(s, url):
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")
ret = []
for row in pruefung:
cols = row.find_all("td")
col = [e.text.strip() for e in cols]
if len(col) == 6 and len(col[3]) <= 13:
if len(col[3]) != 0:
ret += [col[0] + ": " + col[3][:3]]
if ret[-1][0] == ':':
ret[-1] = "Gesamt" + ret[-1]
if len(ret) == 0:
ret = ["Noch nicht gesetzt"]
return ret
def normal():
login = checkUser_normal()
token = login[0]
cookie = str(login[1])
semlist = getSem_normal(token, cookie)
for i in range(1, len(semlist)):
results = fetchDUALIS.getResults(token, cookie, semlist[i][1])
return [token, cookie, results]
async def async_normal():
login = await checkUser_async()
token = login[0]
cookie = str(login[1])
semlist = await getSem_async(token, cookie)
for i in range(1, len(semlist)):
results = await getResults_async(token, cookie, semlist[i][1])
return [token, cookie, results]
@app.route('/c')
async def celery_normal():
login = await checkUser_celery()
token = login[0]
cookie = str(login[1])
#print(token, cookie)
#semlist = await getSem_celery(token, cookie)
#print(semlist)
#for i in range(0, len(semlist)):
# results = await getResults_celery(token, cookie, semlist[i][1])
# print(results)
return [token, cookie]
@app.route('/')
async def tests():
normaltime = 0
asynctime = 0
iter = 20
print("Starting...")
for i in range(iter):
start = time.perf_counter()
n = normal()
end = time.perf_counter()
normaltimeloop = end - start
await fetchDUALIS.logOut(n[0], n[1])
start = time.perf_counter()
a = await async_normal()
end = time.perf_counter()
asynctimeloop = end - start
await fetchDUALIS.logOut(a[0], a[1])
if a[2] == n[2]:
normaltime += normaltimeloop
asynctime += asynctimeloop
print(str(((i + 1) / iter) * 100) + '%')
#vergl = "Gleicher Inhalt!"
#else:
# vergl = "ACHTUNG! Ungleicher Inhalt!"
#return "<br><h2>" + vergl + "</h2><br> <h1> Normal: " + str(normaltime) + "</h2><br><br> <h1> Async: " + str(asynctime) + "</h1>"
delta = normaltime / iter - asynctime / iter
return f"<br><h2> Durchschnitt normal: {normaltime / iter} </h2><br><h2> Durchschnitt asynchron: {asynctime / iter} </h2><br><h1> Durchschnittliches Delta: {delta} </h1><br>"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=1024, debug=True)