diff --git a/src/impl/hebece/src/__init__.py b/src/impl/hebece/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/impl/hebece/src/api.py b/src/impl/hebece/src/api.py index f7cd9d4..772cdce 100644 --- a/src/impl/hebece/src/api.py +++ b/src/impl/hebece/src/api.py @@ -2,8 +2,9 @@ from datetime import datetime import requests import json from bs4 import BeautifulSoup -from signer import * -from utils import * +from impl.hebece.src.signer import * +from impl.hebece.src.utils import * +from impl.hebece.src.const import * session = requests.Session() certificate, fingerprint, private_key = generate_key_pair() @@ -27,109 +28,71 @@ def APILogin(login, password): token_input = soup.find('input', {'name': '__RequestVerificationToken'}) token = {"__RequestVerificationToken": token_input['value']} - # Combine all cookies into a single string format for the Cookie header cookies = {**response1.cookies.get_dict(), **response2.cookies.get_dict()} cookies_str = "; ".join([f"{key}={value}" for key, value in cookies.items()]) cookies_str += f"; __RequestVerificationToken={token_input['value']}" # Prometheus url = "https://eduvulcan.pl/logowanie?ReturnUrl=%2fapi%2fap" - headers = { - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", - "Accept-Encoding": "gzip, deflate, br, zstd", - "Accept-Language": "en-US,en;q=0.9", - "Cache-Control": "max-age=0", - "Connection": "keep-alive", - "Content-Type": "application/x-www-form-urlencoded", - "Cookie": cookies_str, # Use the formatted cookies string here - "Host": "eduvulcan.pl", - "Origin": "https://eduvulcan.pl", - "Referer": "https://eduvulcan.pl/logowanie?ReturnUrl=%2fapi%2fap", - "sec-ch-ua": "\"Chromium\";v=\"130\", \"Android WebView\";v=\"130\", \"Not?A_Brand\";v=\"99\"", - "sec-ch-ua-mobile": "?1", - "sec-ch-ua-platform": "\"Android\"", - "Sec-Fetch-Dest": "document", - "Sec-Fetch-Mode": "navigate", - "Sec-Fetch-Site": "same-origin", - "Sec-Fetch-User": "?1", - "Upgrade-Insecure-Requests": "1", - "User-Agent": "Mozilla/5.0 (Linux; Android 13; SM-G935F Build/TQ3A.230901.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.107 Mobile Safari/537.36", - "X-Requested-With": "pl.edu.vulcan.hebe.ce", - } + headers = makeLoginHeader(cookies_str) data = { "Alias": login, "Password": password, "captchaUser": "", - "__RequestVerificationToken": token_input['value'], # Use the actual token value here + "__RequestVerificationToken": token_input['value'], } response = session.post(url, headers=headers, data=data) content = response.text cookie_jar = response.cookies.get_dict() - soup = BeautifulSoup(content, "html.parser") - input_element = soup.find("input", {"id": "ap"}) - value = input_element["value"] - parsed_json = json.loads(value) + try: + soup = BeautifulSoup(content, "html.parser") + input_element = soup.find("input", {"id": "ap"}) + value = input_element["value"] + parsed_json = json.loads(value) - tokens = parsed_json.get("Tokens", []) - token = " ".join(tokens) + tokens = parsed_json.get("Tokens", []) + token = " ".join(tokens) - return token + return token + except TypeError: + pass def JWTLogin(token, debug=False): - - timestamp = datetime.now() - date = timestamp.strftime("%a, %d %b %Y %H:%M:%S GMT") tenant = get_tenant_from_jwt(token) - - url = f"https://lekcjaplus.vulcan.net.pl/{tenant}/api/mobile/register/jwt" - NotificationToken = None - RequestId = getRandomIdentifier() # Ensure this is a value (not a function) + url = f"https://lekcjaplus.vulcan.net.pl/{tenant}{JWT}" + + RequestId = getRandomIdentifier() + SelfIdentifier = getRandomIdentifier() - OS = "Android" Certificate = certificate CertificateThumbprint = fingerprint - SelfIdentifier = getRandomIdentifier() # Ensure this is a value (not a function) Tokens = token - DeviceModel = "SM-G935F" - signerurl = url - signerbody = None - digest, canonical_url, signature = get_signature_values(fingerprint, private_key, signerbody, signerurl, timestamp) - - headers = { - "accept-encoding": "gzip", - "content-type": "application/json", - "host": "lekcjaplus.vulcan.net.pl", - "signature": signature, - "user-agent": "Dart/3.3 (dart:io)", - "vapi": "1", - "vcanonicalurl": "api%2fmobile%2fregister%2fjwt", - "vdate": date, - "vos": "Android", - "vversioncode": "640", - } - - - bodytimestamp = getCurrentTimestamp() + digest, canonical_url, signature = get_signature_values(fingerprint, private_key, body=None, full_url=url, timestamp=datetime.now()) + + headers = makeHeader(signature, canonical_url) + timestamp = datetime.now() + date = getDate() + body = { "AppName": "DzienniczekPlus 3.0", "AppVersion": "24.11.07 (G)", - "NotificationToken": str(NotificationToken) if NotificationToken else None, + "NotificationToken": None, "API": 1, - "RequestId": str(RequestId), # Ensure RequestId is serializable - "Timestamp": bodytimestamp, - "TimestampFormatted": date, + "RequestId": str(RequestId), + "Timestamp": getTimestamp(), + "TimestampFormatted": str(date), "Envelope": { - "OS": OS, + "OS": DEVICE_OS, "Certificate": Certificate, "CertificateType": "X509", - "DeviceModel": DeviceModel, - "SelfIdentifier": str(SelfIdentifier), # Ensure serializability + "DeviceModel": DEVICE, + "SelfIdentifier": str(SelfIdentifier), "CertificateThumbprint": CertificateThumbprint, "Tokens": [Tokens] } @@ -147,25 +110,11 @@ def JWTLogin(token, debug=False): return content def HEBELogin(tenant, debug=False): - url = f"https://lekcjaplus.vulcan.net.pl/{tenant}/api/mobile/register/hebe?mode=2&lastSyncDate=1970-01-01%2001%3A00%3A00" - signerurl = f"https://lekcjaplus.vulcan.net.pl/{tenant}/api/mobile/register/hebe?mode=2&lastSyncDate=1970-01-01%2001%3A00%3A00" - body = None - timestamp1 = datetime.now() - date1 = timestamp1.strftime("%a, %d %b %Y %H:%M:%S GMT") - digest, canonical_url, signature = get_signature_values(fingerprint, private_key, body, signerurl, timestamp=timestamp1) + url = f"https://lekcjaplus.vulcan.net.pl/{tenant}{HEBE}?mode=2&lastSyncDate=1970-01-01%2001%3A00%3A00" + + digest, canonical_url, signature = get_signature_values(fingerprint, private_key, body=None, full_url=url, timestamp=datetime.now()) - headers = { - "accept-encoding": "gzip", - "content-type": "application/json", - "host": "lekcjaplus.vulcan.net.pl", - "signature": signature, - "user-agent": "Dart/3.3 (dart:io)", - "vapi": "1", - "vcanonicalurl": canonical_url, - "vdate": date1, - "vos": "Android", - "vversioncode": "640", - } + headers = makeHeader(signature, canonical_url) response = requests.get(url, headers=headers) content = response.text @@ -178,65 +127,28 @@ def HEBELogin(tenant, debug=False): def getLuckyNumber(tenant, schoolid, pupilid, constituentid, debug=False): timestamp = datetime.now() - date = timestamp.strftime("%Y-%m-%d") - url = f"https://lekcjaplus.vulcan.net.pl/{tenant}/{schoolid}/api/mobile/school/lucky?pupilId={pupilid}&constituentId={constituentid}&day={date}" + date = timestamp.strftime("%Y-%m-%d") - signerurl = url - body = None - date1 = timestamp.strftime("%a, %d %b %Y %H:%M:%S GMT") - digest, canonical_url, signature = get_signature_values(fingerprint, private_key, body, signerurl, timestamp=timestamp) - - headers = { - "accept-encoding": "gzip", - "content-type": "application/json", - "host": "lekcjaplus.vulcan.net.pl", - "signature": signature, - "user-agent": "Dart/3.3 (dart:io)", - "vapi": "1", - "vcanonicalurl": canonical_url, - "vdate": date1, - "vos": "Android", - "vversioncode": "640", - } + url = f"https://lekcjaplus.vulcan.net.pl/{tenant}/{schoolid}{LUCKY}?pupilId={pupilid}&constituentId={constituentid}&day={date}" + + digest, canonical_url, signature = get_signature_values(fingerprint, private_key, body=None, full_url=url, timestamp=datetime.now()) + headers = makeHeader(signature, canonical_url) response = requests.get(url, headers=headers) content = response.text - data = json.loads(content) - Envelope = data.get("Envelope", {}) - - LuckyNumberDay = Envelope.get("Day", {}) - LuckyNumber = Envelope.get("Number", {}) - if debug: dinfo = getDebugInfo(content) - return LuckyNumber, LuckyNumberDay, dinfo + return content, dinfo - return LuckyNumber, LuckyNumberDay - + return content def getGrades(tenant, schoolid, pupilid, unitid, periodid, debug=False): - timestamp = datetime.now() - date = timestamp.strftime("%Y-%m-%d") url = f"https://lekcjaplus.vulcan.net.pl/{tenant}/{schoolid}/api/mobile/grade/byPupil?unitId={unitid}&pupilId={pupilid}&periodId={periodid}&lastSyncDate=1970-01-01%2001%3A00%3A00&lastId=-2147483648&pageSize=500" - signerurl = url - body = None - date1 = timestamp.strftime("%a, %d %b %Y %H:%M:%S GMT") - digest, canonical_url, signature = get_signature_values(fingerprint, private_key, body, signerurl, timestamp=timestamp) + digest, canonical_url, signature = get_signature_values(fingerprint, private_key, body=None, full_url=url, timestamp=datetime.now()) - headers = { - "accept-encoding": "gzip", - "content-type": "application/json", - "host": "lekcjaplus.vulcan.net.pl", - "signature": signature, - "user-agent": "Dart/3.3 (dart:io)", - "vapi": "1", - "vcanonicalurl": canonical_url, - "vdate": date1, - "vos": "Android", - "vversioncode": "640", - } + headers = makeHeader(signature, canonical_url) response = requests.get(url, headers=headers) content = response.text @@ -250,23 +162,10 @@ def getGrades(tenant, schoolid, pupilid, unitid, periodid, debug=False): def getTimetable(tenant, schoolid, pupilid, start_date, end_date, debug=False): url = f"https://lekcjaplus.vulcan.net.pl/{tenant}/{schoolid}/api/mobile/schedule/withchanges/byPupil?pupilId={pupilid}&dateFrom={start_date}&dateTo={end_date}&lastId=-2147483648&pageSize=500&lastSyncDate=1970-01-01%2001%3A00%3A00" - signerurl = url - body = None - date1 = datetime.now().strftime("%a, %d %b %Y %H:%M:%S GMT") - digest, canonical_url, signature = get_signature_values(fingerprint, private_key, body, signerurl, timestamp=datetime.now()) + + digest, canonical_url, signature = get_signature_values(fingerprint, private_key, body=None, full_url=url, timestamp=datetime.now()) - headers = { - "accept-encoding": "gzip", - "content-type": "application/json", - "host": "lekcjaplus.vulcan.net.pl", - "signature": signature, - "user-agent": "Dart/3.3 (dart:io)", - "vapi": "1", - "vcanonicalurl": canonical_url, - "vdate": date1, - "vos": "Android", - "vversioncode": "640", - } + headers = makeHeader(signature, canonical_url) response = requests.get(url, headers=headers) content = response.text @@ -279,24 +178,10 @@ def getTimetable(tenant, schoolid, pupilid, start_date, end_date, debug=False): def getExams(tenant, schoolid, pupilid, start_date, end_date, debug=False): url = f"https://lekcjaplus.vulcan.net.pl/{tenant}/{schoolid}/api/mobile/exam/byPupil?pupilId={pupilid}&dateFrom={start_date}&dateTo={end_date}&lastId=-2147483648&pageSize=500&lastSyncDate=1970-01-01%2001%3A00%3A00" - signerurl = url - body = None - date1 = datetime.now().strftime("%a, %d %b %Y %H:%M:%S GMT") - digest, canonical_url, signature = get_signature_values(fingerprint, private_key, body, signerurl, timestamp=datetime.now()) - - headers = { - "accept-encoding": "gzip", - "content-type": "application/json", - "host": "lekcjaplus.vulcan.net.pl", - "signature": signature, - "user-agent": "Dart/3.3 (dart:io)", - "vapi": "1", - "vcanonicalurl": canonical_url, - "vdate": date1, - "vos": "Android", - "vversioncode": "640", - } - + + digest, canonical_url, signature = get_signature_values(fingerprint, private_key, body=None, full_url=url, timestamp=datetime.now()) + + headers = makeHeader(signature, canonical_url) response = requests.get(url, headers=headers) content = response.text diff --git a/src/impl/hebece/src/const.py b/src/impl/hebece/src/const.py new file mode 100644 index 0000000..750d5cb --- /dev/null +++ b/src/impl/hebece/src/const.py @@ -0,0 +1,52 @@ +from impl.hebece.src.utils import * +# Endpoints + +JWT = "/api/mobile/register/jwt" +HEBE = "/api/mobile/register/hebe" +LUCKY = "/api/mobile/school/lucky" +GRADES = "/api/mobile/grade/byPupil" +TIMETABLE = "/api/mobile/schedule/withchanges/byPupil" +EXAMS = "/api/mobile/exam/byPupil" + +# Header +DEVICE = "SM-G935F" +DEVICE_OS = "Android" +APPVERSION = "24.11.07 (G)" + +def makeHeader(signature, canonical_url): + return { + "accept-encoding": "gzip", + "content-type": "application/json", + "host": "lekcjaplus.vulcan.net.pl", + "signature": signature, + "user-agent": "Dart/3.3 (dart:io)", + "vapi": "1", + "vcanonicalurl": canonical_url, + "vdate": getDate(), + "vos": DEVICE_OS, + "vversioncode": "640", + } + +def makeLoginHeader(cookies): + return { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", + "Accept-Encoding": "gzip, deflate, br, zstd", + "Accept-Language": "en-US,en;q=0.9", + "Cache-Control": "max-age=0", + "Connection": "keep-alive", + "Content-Type": "application/x-www-form-urlencoded", + "Cookie": cookies, + "Host": "eduvulcan.pl", + "Origin": "https://eduvulcan.pl", + "Referer": "https://eduvulcan.pl/logowanie?ReturnUrl=%2fapi%2fap", + "sec-ch-ua": "\"Chromium\";v=\"130\", \"Android WebView\";v=\"130\", \"Not?A_Brand\";v=\"99\"", + "sec-ch-ua-mobile": "?1", + "sec-ch-ua-platform": "\"Android\"", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Site": "same-origin", + "Sec-Fetch-User": "?1", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Mozilla/5.0 (Linux; Android 13; SM-G935F Build/TQ3A.230901.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.107 Mobile Safari/537.36", + "X-Requested-With": "pl.edu.vulcan.hebe.ce", + } \ No newline at end of file diff --git a/src/impl/hebece/src/main.py b/src/impl/hebece/src/sdkmain.py similarity index 92% rename from src/impl/hebece/src/main.py rename to src/impl/hebece/src/sdkmain.py index f48f77a..6671d87 100644 --- a/src/impl/hebece/src/main.py +++ b/src/impl/hebece/src/sdkmain.py @@ -5,10 +5,9 @@ import hashlib import sqlite3 import os import base64 -from signer import * -from utils import * -from sqlite import * -from api import * +from impl.hebece.src.signer import * +from impl.hebece.src.utils import * +from impl.hebece.src.api import * from datetime import datetime, timedelta from bs4 import BeautifulSoup @@ -65,13 +64,9 @@ if __name__ == '__main__': print(f"Lucky number: {LuckyNumber}") content, dinfoGRADE = getGrades(tenant=tenant, schoolid=SchoolID, pupilid=PupilID, unitid=UnitID, periodid=PeriodID, debug=debug) - ImportGradesToSQLite(content) - print("Grades imported to SQLite database") response, dinfoTIME = getTimetable(tenant=tenant, schoolid=SchoolID, pupilid=PupilID, start_date=start_date, end_date=end_date, debug=debug) - ImportTimetableToSQLite(response) - print("Timetable imported to SQLite database") r = getTimetableForDay(day=today) print(f"\nLessons for {today}:") diff --git a/src/impl/hebece/src/sqlite.py b/src/impl/hebece/src/sqlite.py deleted file mode 100644 index b8dcb86..0000000 --- a/src/impl/hebece/src/sqlite.py +++ /dev/null @@ -1,174 +0,0 @@ -import sqlite3 -import json - -def ImportGradesToSQLite(content): - data = json.loads(content) - - conn = sqlite3.connect('grades.db') - cursor = conn.cursor() - - cursor.execute(''' - CREATE TABLE IF NOT EXISTS grades ( - id INTEGER PRIMARY KEY, - pupil_id INTEGER, - content_raw TEXT, - content TEXT, - value INTEGER, - description TEXT, - date_created TEXT, - date_modified TEXT, - creator_name TEXT, - creator_surname TEXT, - lesson_name TEXT, - lesson_code TEXT, - category_name TEXT, - category_code TEXT - ) - ''') - - for entry in data['Envelope']: - column = entry.get('Column') or {} - subject = column.get('Subject') or {} - category = column.get('Category') or {} - creator = entry.get('Creator') or {} - - cursor.execute(''' - INSERT or IGNORE INTO grades ( - id, pupil_id, content_raw, content, value, description, - date_created, date_modified, creator_name, creator_surname, - lesson_name, lesson_code, category_name, category_code - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''', ( - entry.get('Id'), - entry.get('PupilId'), - entry.get('ContentRaw'), - entry.get('Content'), - entry.get('Value'), - entry.get('Comment'), - entry.get('DateCreated', {}).get('DateDisplay'), - entry.get('DateModify', {}).get('DateDisplay'), - creator.get('Name'), - creator.get('Surname'), - subject.get('Name'), - subject.get('Kod'), - category.get('Name'), - category.get('Code') - )) - - conn.commit() - conn.close() - -def ImportTimetableToSQLite(content): - data = json.loads(content) - - conn = sqlite3.connect('timetable.db') - cursor = conn.cursor() - - cursor.execute(''' - CREATE TABLE IF NOT EXISTS timetable ( - id INTEGER PRIMARY KEY, - date TEXT, - start_time TEXT, - end_time TEXT, - subject_name TEXT, - teacher_name TEXT, - teacher_surname TEXT, - room_code TEXT, - class_display TEXT, - position INTEGER - ) - ''') - - for entry in data['Envelope']: - cursor.execute(''' - INSERT OR IGNORE INTO timetable ( - id, date, start_time, end_time, subject_name, teacher_name, - teacher_surname, room_code, class_display, position - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''', ( - entry.get('Id'), - entry.get('Date', {}).get('DateDisplay'), - entry.get('TimeSlot', {}).get('Start'), - entry.get('TimeSlot', {}).get('End'), - entry.get('Subject', {}).get('Name'), - entry.get('TeacherPrimary', {}).get('Name'), - entry.get('TeacherPrimary', {}).get('Surname'), - entry.get('Room', {}).get('Code'), - entry.get('Clazz', {}).get('DisplayName'), - entry.get('TimeSlot', {}).get('Position') or 0 - )) - - conn.commit() - conn.close() - -def getTimetableForDay(day): - conn = sqlite3.connect('timetable.db') - cursor = conn.cursor() - - def get_lessons_for_day_sorted(date): - cursor.execute('SELECT * FROM timetable WHERE date = ? ORDER BY position ASC', (date,)) - lessons = cursor.fetchall() - return lessons - - - day_to_check = day - lessons_for_day_sorted = get_lessons_for_day_sorted(day_to_check) - conn.close() - - return lessons_for_day_sorted - -def ImportExamsToSQLite(content): - data = json.loads(content) - - conn = sqlite3.connect("exams.db") - cursor = conn.cursor() - - cursor.execute(''' - CREATE TABLE IF NOT EXISTS exams ( - id INTEGER PRIMARY KEY, - type TEXT, - content TEXT, - date_created TEXT, - date_modified TEXT, - deadline TEXT, - creator_name TEXT, - creator_surname TEXT, - subject_name TEXT, - pupil_id INTEGER - ) - ''') - - for entry in data['Envelope']: - cursor.execute(''' - INSERT OR IGNORE INTO exams ( - id, type, content, date_created, date_modified, deadline, - creator_name, creator_surname, subject_name, pupil_id - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ''', ( - entry.get('Id'), - entry.get('Type'), - entry.get('Content'), - entry.get('DateCreated', {}).get('DateDisplay'), - entry.get('DateModify', {}).get('DateDisplay'), - entry.get('Deadline', {}).get('DateDisplay'), - entry.get('Creator', {}).get('Name'), - entry.get('Creator', {}).get('Surname'), - entry.get('Subject', {}).get('Name'), - entry.get('PupilId') - )) - - conn.commit() - conn.close() - -def getExamsForWeek(start_date, end_date): - conn = sqlite3.connect("exams.db") - cursor = conn.cursor() - - cursor.execute(''' - SELECT * FROM exams - WHERE deadline BETWEEN ? AND ? - ORDER BY deadline ASC - ''', (start_date, end_date)) - exams = cursor.fetchall() - conn.close() - return exams \ No newline at end of file diff --git a/src/impl/hebece/src/utils.py b/src/impl/hebece/src/utils.py index 7a43b9b..1f240fd 100644 --- a/src/impl/hebece/src/utils.py +++ b/src/impl/hebece/src/utils.py @@ -28,12 +28,6 @@ def get_tenant_from_jwt(token): print(f"Error decoding JWT: {e}") return None -def getCurrentTimestamp(): - now = datetime.now() - Timestamp = int(now.timestamp()) - - return Timestamp - def getRandomIdentifier(): ruuid = str(uuid.uuid4()) @@ -48,4 +42,13 @@ def get_current_week(): end_of_week = start_of_week + timedelta(days=6) # Return the dates as formatted strings - return start_of_week.strftime('%Y-%m-%d'), end_of_week.strftime('%Y-%m-%d') \ No newline at end of file + return start_of_week.strftime('%Y-%m-%d'), end_of_week.strftime('%Y-%m-%d') + +def getDate(): + return datetime.now().strftime("%a, %d %b %Y %H:%M:%S GMT") + +def getTimestamp(): + now = datetime.now() + Timestamp = now.timestamp() + + return Timestamp \ No newline at end of file diff --git a/src/test.py b/src/test.py new file mode 100644 index 0000000..f99a213 --- /dev/null +++ b/src/test.py @@ -0,0 +1,37 @@ +from impl.hebece.src.api import * +from impl.hebece.src.sdkmain import * +from impl.hebece.src.utils import * +from impl.hebece.src.signer import * + +if __name__ == '__main__': + today = datetime.today().strftime('%d-%m-%y') + start_date, end_date = get_current_week() + + token = APILogin(login = input("login: "),password = input("password: ")) + if not token: + print("You entered wrong login, password or VULCAN asked for captcha. Verify your login and password and try to log into eduVULCAN from your browser.") + input("Press Enter to exit...") + exit() + + tenant = get_tenant_from_jwt(token) + + content, dinfoJWT = JWTLogin(token, debug=True) + + content, dinfoHEBE = HEBELogin(tenant, debug=True) + + Name, SecondName, Surname, Class, PupilID, SchoolID, ConstituentID, UnitID, PeriodID = getUserInfo(tenant) + + content, dinfoLUCK = getLuckyNumber(tenant=tenant, schoolid=SchoolID, pupilid=PupilID, constituentid=ConstituentID, debug=True) + + content, dinfoGRADE = getGrades(tenant=tenant, schoolid=SchoolID, pupilid=PupilID, unitid=UnitID, periodid=PeriodID, debug=True) + + content, dinfoTIME = getTimetable(tenant=tenant, schoolid=SchoolID, pupilid=PupilID, start_date=start_date, end_date=end_date, debug=True) + + content, dinfoEXAM = getExams(tenant=tenant, schoolid=SchoolID, pupilid=PupilID, start_date=start_date, end_date=end_date, debug=True) + + print(f"\nJWT Status: {dinfoJWT[0]} {dinfoJWT[1]}") + print(f"HEBE Status: {dinfoHEBE[0]} {dinfoHEBE[1]}") + print(f"Lucky Number Status: {dinfoLUCK[0]} {dinfoLUCK[1]}") + print(f"Grades Status: {dinfoGRADE[0]} {dinfoGRADE[1]}") + print(f"Timetable Status: {dinfoTIME[0]} {dinfoTIME[1]}") + print(f"Exams Status: {dinfoEXAM[0]} {dinfoEXAM[1]}") \ No newline at end of file