Reapply "Merge branch 'main' of https://git.wpkg.ovh/Fuji/Fuji"
This reverts commit c58a24a4c6
.
This commit is contained in:
parent
c58a24a4c6
commit
330d4f694b
32 changed files with 0 additions and 430 deletions
|
@ -1,152 +0,0 @@
|
|||
from datetime import datetime
|
||||
import requests
|
||||
import json
|
||||
from bs4 import BeautifulSoup
|
||||
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()
|
||||
|
||||
def getDebugInfo(data):
|
||||
data = json.loads(data)
|
||||
status = data.get("Status", {})
|
||||
code = status.get("Code")
|
||||
message = status.get("Message")
|
||||
return code, message
|
||||
|
||||
def makeRequest(url):
|
||||
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
|
||||
|
||||
dinfo = getDebugInfo(content)
|
||||
return content, dinfo
|
||||
|
||||
def APILogin(login, password):
|
||||
|
||||
url = "https://eduvulcan.pl/"
|
||||
response1 = session.get(url)
|
||||
|
||||
url = "https://eduvulcan.pl/logowanie"
|
||||
response2 = session.get(url)
|
||||
|
||||
soup = BeautifulSoup(response2.text, 'html.parser')
|
||||
token_input = soup.find('input', {'name': '__RequestVerificationToken'})
|
||||
token = {"__RequestVerificationToken": token_input['value']}
|
||||
|
||||
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 = makeLoginHeader(cookies_str)
|
||||
|
||||
data = {
|
||||
"Alias": login,
|
||||
"Password": password,
|
||||
"captchaUser": "",
|
||||
"__RequestVerificationToken": token_input['value'],
|
||||
}
|
||||
|
||||
response = session.post(url, headers=headers, data=data)
|
||||
content = response.text
|
||||
cookie_jar = response.cookies.get_dict()
|
||||
|
||||
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)
|
||||
|
||||
return token
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
def JWTLogin(token, debug=False):
|
||||
tenant = get_tenant_from_jwt(token)
|
||||
|
||||
url = f"https://lekcjaplus.vulcan.net.pl/{tenant}{JWT}"
|
||||
|
||||
RequestId = getRandomIdentifier()
|
||||
SelfIdentifier = getRandomIdentifier()
|
||||
|
||||
Certificate = certificate
|
||||
CertificateThumbprint = fingerprint
|
||||
Tokens = token
|
||||
|
||||
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": None,
|
||||
"API": 1,
|
||||
"RequestId": str(RequestId),
|
||||
"Timestamp": getTimestamp(),
|
||||
"TimestampFormatted": str(date),
|
||||
"Envelope": {
|
||||
"OS": DEVICE_OS,
|
||||
"Certificate": Certificate,
|
||||
"CertificateType": "X509",
|
||||
"DeviceModel": DEVICE,
|
||||
"SelfIdentifier": str(SelfIdentifier),
|
||||
"CertificateThumbprint": CertificateThumbprint,
|
||||
"Tokens": [Tokens]
|
||||
}
|
||||
}
|
||||
|
||||
body_json = json.dumps(body, indent=4)
|
||||
|
||||
response = session.post(url, headers=headers, data=body_json)
|
||||
content = response.text
|
||||
|
||||
if debug:
|
||||
dinfo = getDebugInfo(content)
|
||||
return content, dinfo
|
||||
|
||||
return content
|
||||
|
||||
def HEBELogin(tenant, debug=False):
|
||||
url = f"https://lekcjaplus.vulcan.net.pl/{tenant}{HEBE}?mode=2&lastSyncDate=1970-01-01%2001%3A00%3A00"
|
||||
content, dinfo = makeRequest(url)
|
||||
return content, dinfo
|
||||
|
||||
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}{LUCKY}?pupilId={pupilid}&constituentId={constituentid}&day={date}"
|
||||
content, dinfo = makeRequest(url)
|
||||
return content, dinfo
|
||||
|
||||
def getGrades(tenant, schoolid, pupilid, unitid, periodid, debug=False):
|
||||
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"
|
||||
|
||||
content, dinfo = makeRequest(url)
|
||||
return content, dinfo
|
||||
|
||||
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"
|
||||
|
||||
content, dinfo = makeRequest(url)
|
||||
return content, dinfo
|
||||
|
||||
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"
|
||||
|
||||
content, dinfo = makeRequest(url)
|
||||
return content, dinfo
|
|
@ -1,52 +0,0 @@
|
|||
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",
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
import requests
|
||||
import json
|
||||
import uuid
|
||||
import hashlib
|
||||
import sqlite3
|
||||
import os
|
||||
import base64
|
||||
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
|
||||
|
||||
def getUserInfo(tenant):
|
||||
content, dinfo = HEBELogin(tenant)
|
||||
|
||||
data = json.loads(content)
|
||||
envelope = data.get("Envelope", [])[0]
|
||||
|
||||
pupil = envelope.get("Pupil", {})
|
||||
unit = envelope.get("Unit", {})
|
||||
links = envelope.get("Links", {})
|
||||
ConstituentUnit = envelope.get("ConstituentUnit", {})
|
||||
periods = envelope.get("Periods", [])
|
||||
|
||||
|
||||
Name = pupil.get("FirstName", {})
|
||||
SecondName = pupil.get("SecondName", {})
|
||||
Surname = pupil.get("Surname", {})
|
||||
Class = envelope.get("ClassDisplay", {})
|
||||
PupilID = pupil.get("Id", {})
|
||||
SchoolID = links.get("Symbol", {})
|
||||
ConstituentID = ConstituentUnit.get("Id", {})
|
||||
UnitID = unit.get("Id", {})
|
||||
PeriodID = next((period.get('Id') for period in periods if period.get('Current')), None)
|
||||
|
||||
return Name, SecondName, Surname, Class, PupilID, SchoolID, ConstituentID, UnitID, PeriodID
|
|
@ -1,54 +0,0 @@
|
|||
import base64
|
||||
import json
|
||||
import uuid
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
def encodebase64(data):
|
||||
return base64.b64encode(data.encode("utf-8")).decode("utf-8")
|
||||
|
||||
def decodebase64(data):
|
||||
return base64.b64decode(data.encode("utf-8")).decode("utf-8")
|
||||
|
||||
def get_tenant_from_jwt(token):
|
||||
try:
|
||||
# Split the JWT into parts
|
||||
header, payload, signature = token.split('.')
|
||||
|
||||
# Decode the payload from Base64
|
||||
# Add padding
|
||||
payload += '=' * (-len(payload) % 4)
|
||||
decoded_payload = base64.urlsafe_b64decode(payload).decode('utf-8')
|
||||
|
||||
# Parse the payload as JSON
|
||||
payload_json = json.loads(decoded_payload)
|
||||
|
||||
# Return the tenant
|
||||
return payload_json.get('tenant')
|
||||
except (ValueError, json.JSONDecodeError, KeyError) as e:
|
||||
print(f"Error decoding JWT: {e}")
|
||||
return None
|
||||
|
||||
def getRandomIdentifier():
|
||||
ruuid = str(uuid.uuid4())
|
||||
|
||||
return ruuid
|
||||
|
||||
def get_current_week():
|
||||
# Get today's date
|
||||
today = datetime.today()
|
||||
# Calculate the start of the week (Monday)
|
||||
start_of_week = today - timedelta(days=today.weekday())
|
||||
# Calculate the end of the week (Sunday)
|
||||
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')
|
||||
|
||||
def getDate():
|
||||
return datetime.now().strftime("%a, %d %b %Y %H:%M:%S GMT")
|
||||
|
||||
def getTimestamp():
|
||||
now = datetime.now()
|
||||
Timestamp = now.timestamp()
|
||||
|
||||
return Timestamp
|
173
src/sdk/src/README.md
Normal file
173
src/sdk/src/README.md
Normal file
|
@ -0,0 +1,173 @@
|
|||
# Fuji SDK
|
||||
|
||||
## Jak używać
|
||||
|
||||
Obecnie działa jedynie prometeusz (eduvulcan).
|
||||
|
||||
### Techniczny wstęp
|
||||
|
||||
Trzeba skorzystać z interfejsu np. `PrometheusInterface`. Dla każdego interfejsu podajemy dwa parametry:
|
||||
|
||||
- `auth_context` - są w nim dane logowania, sesje, certyfikaty (ogółem dane, które pozwalają dostać się do dziennika, **pamiętaj, by trzymać je w bezpiecznym miejscu**),
|
||||
- `student_context` - są w nim dane ucznia, pozwalające na wykonywanie zapytań do API, których używają interfejsy. (nie jest on konieczny, gdy logujesz się albo pobierasz uczniów)
|
||||
|
||||
Typy tych contextów są różne dla każdego dziennika. Jak uzyskać te contexty, dowiesz się niżej.
|
||||
|
||||
### Logowanie
|
||||
|
||||
#### Prometeusz (eduvulcan)
|
||||
|
||||
Musimy stworzyć instancję interfejsu `PrometheusInterface`, na początku musi on zawierać jedynie `auth_context`, który musi zawierać jedynie dane logowania do dziennika.
|
||||
|
||||
**UWAGA: Nie loguj się do dziennika od nowa za każdym razem, o tym jak zapisać zalogowane konto niżej.**
|
||||
|
||||
```py
|
||||
from sdk.src.interfaces.prometheus.context import (
|
||||
PrometheusAuthContext,
|
||||
PrometheusWebCredentials,
|
||||
)
|
||||
from sdk.src.interfaces.prometheus.interface import PrometheusInterface
|
||||
|
||||
# Tworzymy instancję prometeuszowego interfejsu
|
||||
interface = PrometheusInterface(
|
||||
# Auth context
|
||||
auth_context=PrometheusAuthContext(
|
||||
# Dane logowania
|
||||
prometheus_web_credentials=PrometheusWebCredentials(
|
||||
username="<nazwa użytkownika>", password="<hasło użytkownika>"
|
||||
)
|
||||
),
|
||||
student_context=None,
|
||||
)
|
||||
```
|
||||
|
||||
Gdy stworzyliśmy już instancję interfejsu, trzeba się zalogować, jest to bardzo proste. ~~Szkoda tylko, że w środku interfejsu już nie.~~
|
||||
|
||||
```py
|
||||
interface.login()
|
||||
```
|
||||
|
||||
Reszta część będzie wspólna dla wszystkich dzienników.
|
||||
|
||||
### Zapisywanie zalogowanego konta
|
||||
|
||||
Po zalogowaniu trzeba by było zapisać te dane, bo logowanie za każdym razem od nowa jest czasochłonne.
|
||||
|
||||
```py
|
||||
auth_context = interface.get_auth_context()
|
||||
```
|
||||
|
||||
Później, jak będziesz tworzyć instancję interfejsu, to w parametrze `auth_context` daj właśnie to.
|
||||
|
||||
**Pamiętaj, by trzymać to w bezpiecznym miejscu!**
|
||||
|
||||
### Pobieranie i wybieranie uczniów
|
||||
|
||||
Jak jesteśmy zalogowani, to możemy pobrać uczniów, jest to bardzo proste. **UWAGA! Nie rób tego za każdym razem, tylko zapisz sobie gdzieś te dane.**
|
||||
|
||||
```py
|
||||
students = interface.get_students()
|
||||
```
|
||||
|
||||
Jak dokładnie wygląda to co zwraca `get_students()` możesz sprawdzić w kodzie, to co teraz istotne to to, że każdy uczeń ma w sobie pole `context`. Jak możesz się domyślać jest to pole, które będziemy dawać interfejsowi. Możemy to zrobić albo przy tworzeniu instancji interfejsu:
|
||||
|
||||
```py
|
||||
interface = PrometheusInterface(
|
||||
auth_context=...,
|
||||
student_context=<tu_to_dajesz>,
|
||||
)
|
||||
```
|
||||
|
||||
albo, gdy mamy już stworzoną instancję interfejsu:
|
||||
|
||||
```py
|
||||
interface.select_student(<tu_to_dajesz>)
|
||||
```
|
||||
|
||||
### Co dalej?
|
||||
|
||||
No możesz pobrać na przykład oceny, możesz to zrobić (gdy jesteś zalogowany i masz wybranego ucznia) tak:
|
||||
|
||||
```py
|
||||
grades = interface.get_grades(<numer semestru>)
|
||||
```
|
||||
|
||||
## Jak dodawać funkcjonalności:
|
||||
|
||||
1. Zrób model(e) do danych, nie dawaj zbyt dużo danych, bo pamiętaj, że ma być to uniwersalny model dla wielu dzienników.
|
||||
2. Zrób funkcje w odpowiednich api do pobierania tych danych.
|
||||
3. Zrób mappery w modelach dla odpowiednich api.
|
||||
4. Zrób funkcję we wszystkich interfejsach do pobierania tych danych, przy obsłudze api.
|
||||
|
||||
### Przykład dla sprawdzianów (tylko dla Prometeusza)
|
||||
|
||||
1 i 3. `sdk/src/models/exam.py`
|
||||
```py
|
||||
class ExamType(Enum):
|
||||
TEST = 0
|
||||
SHORT_TEST = 1
|
||||
CLASSWORK = 2
|
||||
OTHER = 3
|
||||
|
||||
@staticmethod
|
||||
def from_hebe_type_name(type_name: str):
|
||||
match type_name:
|
||||
case "Sprawdzian":
|
||||
return ExamType.TEST
|
||||
case "Kartkówka":
|
||||
return ExamType.SHORT_TEST
|
||||
case "Praca klasowa":
|
||||
return ExamType.CLASSWORK
|
||||
case _:
|
||||
return ExamType.OTHER
|
||||
|
||||
|
||||
@dataclass
|
||||
class Exam:
|
||||
deadline: date
|
||||
subject: str
|
||||
type: ExamType
|
||||
description: str
|
||||
creator: str
|
||||
created_at: datetime
|
||||
|
||||
@staticmethod
|
||||
def from_hebe_dict(data: dict):
|
||||
return Exam(
|
||||
deadline=datetime.fromtimestamp(data["Deadline"]["Timestamp"] / 1000),
|
||||
subject=data["Subject"]["Name"],
|
||||
type=ExamType.from_hebe_type_name(data["Type"]),
|
||||
description=data["Content"],
|
||||
creator=data["Creator"]["DisplayName"],
|
||||
created_at=datetime.fromtimestamp(data["DateCreated"]["Timestamp"] / 1000),
|
||||
)
|
||||
```
|
||||
2. `sdk/src/apis/hebe/client.py`
|
||||
```py
|
||||
def get_exams(self, student_id: int, from_: date, to: date):
|
||||
envelope = self._send_request(
|
||||
"GET",
|
||||
ENDPOINT_EXAM_BYPUPIL,
|
||||
params={
|
||||
"pupilId": student_id,
|
||||
"dateFrom": from_,
|
||||
"dateTo": to,
|
||||
},
|
||||
)
|
||||
return list(map(Exam.from_hebe_dict, envelope))
|
||||
```
|
||||
|
||||
4.
|
||||
|
||||
4.1. prometheus interface
|
||||
```py
|
||||
def get_exams(self, from_: date, to: date):
|
||||
self._check_is_auth_context_full()
|
||||
self._check_is_student_selected()
|
||||
return self._hebe_client.get_exams(self._student_context.student_id, from_, to)
|
||||
```
|
||||
4.2. core interface
|
||||
```py
|
||||
def get_exams(from_: date, to: date) -> list[Exam]:
|
||||
pass
|
||||
```
|
15
src/sdk/src/apis/common/models.py
Normal file
15
src/sdk/src/apis/common/models.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass
|
||||
class FsLsQuery:
|
||||
wa: str
|
||||
wtrealm: str
|
||||
wctx: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class FsLsResponse:
|
||||
wa: str
|
||||
wresult: str
|
||||
wctx: str
|
12
src/sdk/src/apis/common/utils.py
Normal file
12
src/sdk/src/apis/common/utils.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
from bs4 import BeautifulSoup
|
||||
|
||||
from sdk.src.apis.common.models import FsLsResponse
|
||||
|
||||
|
||||
def parse_fs_ls_response_form(html: str):
|
||||
soup = BeautifulSoup(html, "html.parser")
|
||||
return FsLsResponse(
|
||||
wa=soup.select_one('input[name="wa"]')["value"],
|
||||
wresult=soup.select_one('input[name="wresult"]')["value"],
|
||||
wctx=soup.select_one('input[name="wctx"]')["value"],
|
||||
)
|
56
src/sdk/src/apis/efeb/client.py
Normal file
56
src/sdk/src/apis/efeb/client.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
import dataclasses
|
||||
import requests
|
||||
|
||||
from sdk.src.apis.common.models import FsLsResponse, FsLsQuery
|
||||
from sdk.src.apis.common.utils import parse_fs_ls_response_form
|
||||
from sdk.src.apis.efeb.constants import (
|
||||
ENDPOINT_LOGIN_FS_LS,
|
||||
ENDPOINT_MESSAGES_APP,
|
||||
ENDPOINT_STUDENT_APP,
|
||||
BASE_LOGIN,
|
||||
BASE_MESSAGES,
|
||||
BASE_MESSAGES_CE,
|
||||
BASE_STUDENT,
|
||||
BASE_STUDENT_CE,
|
||||
)
|
||||
from sdk.src.apis.efeb.utils import parse_app_html
|
||||
|
||||
|
||||
class EfebClient:
|
||||
def __init__(self, cookies: dict, symbol: str, is_ce: bool):
|
||||
self._session = requests.Session()
|
||||
self._session.cookies.update(cookies)
|
||||
self._symbol = symbol
|
||||
self._is_ce = is_ce
|
||||
|
||||
def get_cookies(self):
|
||||
return self._session.cookies.get_dict()
|
||||
|
||||
def login_fs_ls(
|
||||
self, query: FsLsQuery, prometheus_response: FsLsResponse | None = None
|
||||
):
|
||||
response = self._session.request(
|
||||
method="POST" if prometheus_response else "GET",
|
||||
url=f"{BASE_LOGIN}/{self._symbol}/{ENDPOINT_LOGIN_FS_LS}",
|
||||
data=(
|
||||
dataclasses.asdict(prometheus_response) if prometheus_response else None
|
||||
),
|
||||
params=dataclasses.asdict(query),
|
||||
)
|
||||
return parse_fs_ls_response_form(response.text)
|
||||
|
||||
def student_app(self, login_response: FsLsResponse | None = None):
|
||||
response = self._session.request(
|
||||
method="POST" if login_response else "GET",
|
||||
url=f"{BASE_STUDENT_CE if self._is_ce else BASE_STUDENT}/{self._symbol}/{ENDPOINT_STUDENT_APP}",
|
||||
data=dataclasses.asdict(login_response) if login_response else None,
|
||||
)
|
||||
return parse_app_html(response.text)
|
||||
|
||||
def messages_app(self, login_response: FsLsResponse | None = None):
|
||||
response = self._session.request(
|
||||
method="POST" if login_response else "GET",
|
||||
url=f"{BASE_MESSAGES_CE if self._is_ce else BASE_MESSAGES}/{self._symbol}/{ENDPOINT_MESSAGES_APP}",
|
||||
data=dataclasses.asdict(login_response) if login_response else None,
|
||||
)
|
||||
return parse_app_html(response.text)
|
9
src/sdk/src/apis/efeb/constants.py
Normal file
9
src/sdk/src/apis/efeb/constants.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
BASE_LOGIN = "https://dziennik-logowanie.vulcan.net.pl"
|
||||
BASE_STUDENT_CE = "https://uczen.eduvulcan.pl"
|
||||
BASE_STUDENT = "https://dziennik-uczen.vulcan.net.pl"
|
||||
BASE_MESSAGES_CE = "https://wiadomosci.eduvulcan.pl"
|
||||
BASE_MESSAGES = "https://dziennik-wiadomosci.vulcan.net.pl"
|
||||
|
||||
ENDPOINT_LOGIN_FS_LS = "fs/ls"
|
||||
ENDPOINT_STUDENT_APP = "App"
|
||||
ENDPOINT_MESSAGES_APP = "App"
|
17
src/sdk/src/apis/efeb/utils.py
Normal file
17
src/sdk/src/apis/efeb/utils.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
from bs4 import BeautifulSoup
|
||||
import re
|
||||
|
||||
|
||||
def parse_fs_ls_form(html: str):
|
||||
soup = BeautifulSoup(html, "html.parser")
|
||||
return {
|
||||
"wa": soup.select_one('input[name="wa"]')["value"],
|
||||
"wresult": soup.select_one('input[name="wresult"]')["value"],
|
||||
"wctx": soup.select_one('input[name="wctx"]')["value"],
|
||||
}
|
||||
|
||||
|
||||
def parse_app_html(html: str):
|
||||
return re.search("appGuid: '(.*?)'", html).group(1), re.search(
|
||||
"antiForgeryToken: '(.*?)'", html
|
||||
).group(1)
|
2
src/sdk/src/apis/hebe/__init__.py
Normal file
2
src/sdk/src/apis/hebe/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
from sdk.src.apis.hebe.certificate import Certificate as HebeCertificate
|
||||
from sdk.src.apis.hebe.client import HebeClient
|
15
src/sdk/src/apis/hebe/certificate.py
Normal file
15
src/sdk/src/apis/hebe/certificate.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from dataclasses import dataclass
|
||||
from sdk.src.apis.hebe.signer import generate_key_pair
|
||||
|
||||
|
||||
@dataclass
|
||||
class Certificate:
|
||||
certificate: str
|
||||
fingerprint: str
|
||||
private_key: str
|
||||
type: str
|
||||
|
||||
@staticmethod
|
||||
def generate():
|
||||
certificate, fingerprint, private_key = generate_key_pair()
|
||||
return Certificate(certificate, fingerprint, private_key, "X509")
|
209
src/sdk/src/apis/hebe/client.py
Normal file
209
src/sdk/src/apis/hebe/client.py
Normal file
|
@ -0,0 +1,209 @@
|
|||
from datetime import date, datetime
|
||||
import json
|
||||
import uuid
|
||||
import requests
|
||||
|
||||
from sdk.src.apis.hebe.certificate import Certificate
|
||||
from sdk.src.apis.hebe.constants import (
|
||||
API_VERSION,
|
||||
APP_HEBE_NAME,
|
||||
APP_HEBECE_NAME,
|
||||
APP_VERSION,
|
||||
APP_VERSION_CODE,
|
||||
DEVICE_NAME,
|
||||
DEVICE_OS,
|
||||
ENDPOINT_EXAM_BYPUPIL,
|
||||
ENDPOINT_GRADE_BYPUPIL,
|
||||
ENDPOINT_HEARTBEAT,
|
||||
ENDPOINT_NOTE_BYPUPIL,
|
||||
ENDPOINT_REGISTER_HEBE,
|
||||
ENDPOINT_REGISTER_JWT,
|
||||
ENDPOINT_REGISTER_TOKEN,
|
||||
ENDPOINT_SCHOOL_LUCKY,
|
||||
USER_AGENT,
|
||||
)
|
||||
from sdk.src.apis.hebe.exceptions import (
|
||||
ExpiredTokenException,
|
||||
HebeClientException,
|
||||
InvalidPINException,
|
||||
InvalidRequestEnvelopeStructure,
|
||||
InvalidRequestHeadersStructure,
|
||||
NoPermissionsException,
|
||||
NoUnitSymbolException,
|
||||
NotFoundEntityException,
|
||||
UnauthorizedCertificateException,
|
||||
UsedTokenException,
|
||||
)
|
||||
from sdk.src.apis.hebe.student import HebeStudent
|
||||
from sdk.src.apis.hebe.signer import get_signature_values
|
||||
from sdk.src.models.exam import Exam
|
||||
from sdk.src.models.grade import Grade
|
||||
from sdk.src.models.note import Note
|
||||
|
||||
|
||||
class HebeClient:
|
||||
def __init__(
|
||||
self, certificate: Certificate, rest_url: str | None = None, is_ce: bool = True
|
||||
):
|
||||
self._session = requests.Session()
|
||||
self._certificate = certificate
|
||||
self._rest_url = rest_url
|
||||
self._is_ce = is_ce
|
||||
|
||||
def set_rest_url(self, new_value: str):
|
||||
self._rest_url = new_value
|
||||
|
||||
def _send_request(self, method: str, endpoint: str, envelope: any = None, **kwargs):
|
||||
if not self._rest_url:
|
||||
raise Exception("No rest_url!")
|
||||
|
||||
date = datetime.now()
|
||||
|
||||
url = f"{self._rest_url}/{endpoint}"
|
||||
body = json.dumps(self._build_body(date, envelope)) if envelope else None
|
||||
headers = self._build_headers(date, url, body)
|
||||
response = self._session.request(
|
||||
method, url, data=body, headers=headers, **kwargs
|
||||
)
|
||||
|
||||
data = response.json()
|
||||
self._check_response_status_code(data["Status"]["Code"])
|
||||
|
||||
return data["Envelope"]
|
||||
|
||||
def _build_headers(self, date: datetime, url: str, body: any):
|
||||
digest, canonical_url, signature = get_signature_values(
|
||||
self._certificate.fingerprint,
|
||||
self._certificate.private_key,
|
||||
body,
|
||||
url,
|
||||
date,
|
||||
)
|
||||
|
||||
headers = {
|
||||
"signature": signature,
|
||||
"vcanonicalurl": canonical_url,
|
||||
"vos": DEVICE_OS,
|
||||
"vdate": date.strftime("%a, %d %b %Y %H:%M:%S GMT"),
|
||||
"vapi": API_VERSION,
|
||||
"vversioncode": APP_VERSION_CODE,
|
||||
"user-agent": USER_AGENT,
|
||||
}
|
||||
if digest:
|
||||
headers["digest"] = digest
|
||||
headers["content-type"] = "application/json"
|
||||
|
||||
return headers
|
||||
|
||||
def _build_body(self, date: datetime, envelope: any):
|
||||
return {
|
||||
"AppName": APP_HEBECE_NAME if self._is_ce else APP_HEBE_NAME,
|
||||
"AppVersion": APP_VERSION,
|
||||
"NotificationToken": "",
|
||||
"API": int(API_VERSION),
|
||||
"RequestId": str(uuid.uuid4()),
|
||||
"Timestamp": int(date.timestamp()),
|
||||
"TimestampFormatted": date.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"Envelope": envelope,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _check_response_status_code(status_code: int):
|
||||
if status_code == 0:
|
||||
return
|
||||
if status_code == 100:
|
||||
raise NoPermissionsException()
|
||||
if status_code == 101:
|
||||
raise InvalidRequestEnvelopeStructure()
|
||||
if status_code == 102:
|
||||
raise InvalidRequestHeadersStructure()
|
||||
if status_code == 104:
|
||||
raise NoUnitSymbolException()
|
||||
if status_code == 108:
|
||||
raise UnauthorizedCertificateException()
|
||||
if status_code == 200:
|
||||
raise NotFoundEntityException()
|
||||
if status_code == 201:
|
||||
raise UsedTokenException()
|
||||
if status_code == 203:
|
||||
raise InvalidPINException()
|
||||
if status_code == 204:
|
||||
raise ExpiredTokenException()
|
||||
raise HebeClientException(status_code)
|
||||
|
||||
def register_jwt(self, tokens: list[str]):
|
||||
envelope = {
|
||||
"OS": DEVICE_OS,
|
||||
"Certificate": self._certificate.certificate,
|
||||
"CertificateType": self._certificate.type,
|
||||
"DeviceModel": DEVICE_NAME,
|
||||
"SelfIdentifier": str(uuid.uuid4()),
|
||||
"CertificateThumbprint": self._certificate.fingerprint,
|
||||
"Tokens": tokens,
|
||||
}
|
||||
return self._send_request("POST", ENDPOINT_REGISTER_JWT, envelope)
|
||||
|
||||
def register_token(self, token: str, pin: str):
|
||||
# For hebe interface
|
||||
envelope = {
|
||||
"OS": DEVICE_OS,
|
||||
"Certificate": self._certificate.certificate,
|
||||
"CertificateType": self._certificate.type,
|
||||
"DeviceModel": DEVICE_NAME,
|
||||
"SelfIdentifier": str(uuid.uuid4()),
|
||||
"CertificateThumbprint": self._certificate.fingerprint,
|
||||
"SecurityToken": token,
|
||||
"PIN": pin,
|
||||
}
|
||||
return self._send_request("POST", ENDPOINT_REGISTER_TOKEN, envelope)
|
||||
|
||||
def get_students(self, mode: int = 2):
|
||||
envelope = self._send_request(
|
||||
"GET", ENDPOINT_REGISTER_HEBE, params={"mode": mode}
|
||||
)
|
||||
return map(HebeStudent.from_dict, envelope)
|
||||
|
||||
def heartbeat(self):
|
||||
self._send_request("GET", ENDPOINT_HEARTBEAT)
|
||||
|
||||
def get_lucky_number(self, student_id: int, constituent_id: int, day: date):
|
||||
envelope = self._send_request(
|
||||
"GET",
|
||||
ENDPOINT_SCHOOL_LUCKY,
|
||||
params={
|
||||
"pupilId": student_id,
|
||||
"constituentId": constituent_id,
|
||||
"day": day.strftime("%Y-%m-%d"),
|
||||
},
|
||||
)
|
||||
return envelope["Number"]
|
||||
|
||||
def get_grades(self, student_id: int, unit_id: int, period_id: int):
|
||||
envelope = self._send_request(
|
||||
"GET",
|
||||
ENDPOINT_GRADE_BYPUPIL,
|
||||
params={
|
||||
"pupilId": student_id,
|
||||
"unitId": unit_id,
|
||||
"periodId": period_id,
|
||||
},
|
||||
)
|
||||
return list(map(Grade.from_hebe_dict, envelope))
|
||||
|
||||
def get_exams(self, student_id: int, from_: date, to: date):
|
||||
envelope = self._send_request(
|
||||
"GET",
|
||||
ENDPOINT_EXAM_BYPUPIL,
|
||||
params={
|
||||
"pupilId": student_id,
|
||||
"dateFrom": from_,
|
||||
"dateTo": to,
|
||||
},
|
||||
)
|
||||
return list(map(Exam.from_hebe_dict, envelope))
|
||||
|
||||
def get_notes(self, student_id: int):
|
||||
envelope = self._send_request(
|
||||
"GET", ENDPOINT_NOTE_BYPUPIL, params={"pupilId": student_id}
|
||||
)
|
||||
return list(map(Note.from_hebe_dict, envelope))
|
22
src/sdk/src/apis/hebe/constants.py
Normal file
22
src/sdk/src/apis/hebe/constants.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
DEVICE_OS = "Android"
|
||||
DEVICE_NAME = "SM-G935F"
|
||||
|
||||
APP_HEBE_NAME = "DzienniczekPlus 2.0"
|
||||
APP_HEBECE_NAME = "DzienniczekPlus 3.0"
|
||||
APP_VERSION = "24.11.07 (G)"
|
||||
APP_VERSION_CODE = "640"
|
||||
|
||||
USER_AGENT = "Dart/3.3 (dart:io)"
|
||||
|
||||
API_VERSION = "1"
|
||||
|
||||
# Endpoints
|
||||
ENDPOINT_REGISTER_JWT = "mobile/register/jwt"
|
||||
ENDPOINT_REGISTER_TOKEN = "mobile/register/new"
|
||||
ENDPOINT_REGISTER_HEBE = "mobile/register/hebe"
|
||||
ENDPOINT_HEARTBEAT = "mobile/heartbeat"
|
||||
ENDPOINT_SCHOOL_LUCKY = "mobile/school/lucky"
|
||||
ENDPOINT_GRADE_BYPUPIL = "mobile/grade/byPupil"
|
||||
ENDPOINT_SCHEDULE_WITHCHANGES_BYPUPIL = "mobile/schedule/withchanges/byPupil"
|
||||
ENDPOINT_EXAM_BYPUPIL = "mobile/exam/byPupil"
|
||||
ENDPOINT_NOTE_BYPUPIL = "mobile/note/byPupil"
|
42
src/sdk/src/apis/hebe/exceptions.py
Normal file
42
src/sdk/src/apis/hebe/exceptions.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
class HebeClientException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class NotFoundEndpointException(HebeClientException):
|
||||
pass
|
||||
|
||||
|
||||
class NoUnitSymbolException(HebeClientException):
|
||||
pass
|
||||
|
||||
|
||||
class NoPermissionsException(HebeClientException):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidRequestEnvelopeStructure(HebeClientException):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidRequestHeadersStructure(HebeClientException):
|
||||
pass
|
||||
|
||||
|
||||
class UnauthorizedCertificateException(HebeClientException):
|
||||
pass
|
||||
|
||||
|
||||
class NotFoundEntityException(HebeClientException):
|
||||
pass
|
||||
|
||||
|
||||
class UsedTokenException(HebeClientException):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidPINException(HebeClientException):
|
||||
pass
|
||||
|
||||
|
||||
class ExpiredTokenException(HebeClientException):
|
||||
pass
|
|
@ -1,4 +1,5 @@
|
|||
import cryptography
|
||||
import hashlib
|
||||
import json
|
||||
import re
|
||||
import urllib
|
||||
import base64
|
||||
|
@ -17,6 +18,7 @@ def get_encoded_path(full_url):
|
|||
)
|
||||
return urllib.parse.quote(path[1], safe="").lower()
|
||||
|
||||
|
||||
def get_digest(body):
|
||||
if not body:
|
||||
return None
|
||||
|
@ -25,6 +27,7 @@ def get_digest(body):
|
|||
m.update(bytes(body, "utf-8"))
|
||||
return base64.b64encode(m.digest()).decode("utf-8")
|
||||
|
||||
|
||||
def get_headers_list(body, digest, canonical_url, timestamp):
|
||||
sign_data = [
|
||||
["vCanonicalUrl", canonical_url],
|
||||
|
@ -37,28 +40,26 @@ def get_headers_list(body, digest, canonical_url, timestamp):
|
|||
"".join(item[1] for item in sign_data if item),
|
||||
)
|
||||
|
||||
|
||||
def get_signature(data, private_key):
|
||||
# Convert data to a string representatio
|
||||
data_str = (
|
||||
json.dumps(data)
|
||||
if isinstance(data, (dict, list))
|
||||
else str(data)
|
||||
)
|
||||
|
||||
data_str = json.dumps(data) if isinstance(data, (dict, list)) else str(data)
|
||||
|
||||
# Decode the base64 private key and load it
|
||||
private_key_bytes = base64.b64decode(private_key)
|
||||
pkcs8_key = load_der_private_key(private_key_bytes, password=None, backend=default_backend())
|
||||
|
||||
pkcs8_key = load_der_private_key(
|
||||
private_key_bytes, password=None, backend=default_backend()
|
||||
)
|
||||
|
||||
# Sign the data
|
||||
signature = pkcs8_key.sign(
|
||||
bytes(data_str, "utf-8"),
|
||||
padding.PKCS1v15(),
|
||||
hashes.SHA256()
|
||||
bytes(data_str, "utf-8"), padding.PKCS1v15(), hashes.SHA256()
|
||||
)
|
||||
|
||||
|
||||
# Encode the signature in base64 and return
|
||||
return base64.b64encode(signature).decode("utf-8")
|
||||
|
||||
|
||||
def get_signature_values(fingerprint, private_key, body, full_url, timestamp):
|
||||
canonical_url = get_encoded_path(full_url)
|
||||
digest = get_digest(body)
|
||||
|
@ -73,9 +74,11 @@ def get_signature_values(fingerprint, private_key, body, full_url, timestamp):
|
|||
),
|
||||
)
|
||||
|
||||
|
||||
def pem_getraw(pem):
|
||||
return pem.decode("utf-8").replace("\n", "").split("-----")[2]
|
||||
|
||||
|
||||
def generate_key_pair():
|
||||
pkcs8 = crypto.PKey()
|
||||
pkcs8.generate_key(crypto.TYPE_RSA, 2048)
|
58
src/sdk/src/apis/hebe/student.py
Normal file
58
src/sdk/src/apis/hebe/student.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
from dataclasses import dataclass
|
||||
from datetime import date
|
||||
|
||||
|
||||
@dataclass
|
||||
class HebePeriod:
|
||||
id: int
|
||||
number: int
|
||||
current: bool
|
||||
from_: date
|
||||
to: date
|
||||
|
||||
@staticmethod
|
||||
def from_dict(data: dict):
|
||||
return HebePeriod(
|
||||
id=data["Id"],
|
||||
number=data["Number"],
|
||||
current=data["Current"],
|
||||
from_=date.fromtimestamp(data["Start"]["Timestamp"] / 1000),
|
||||
to=date.fromtimestamp(data["End"]["Timestamp"] / 1000),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class HebeStudent:
|
||||
id: int
|
||||
full_name: str
|
||||
unit_id: int
|
||||
constituent_id: int
|
||||
capabilities: list[str]
|
||||
register_id: int
|
||||
register_student_number: int | None
|
||||
class_name: str
|
||||
is_parent: bool
|
||||
messagebox_key: str
|
||||
messagebox_name: str
|
||||
periods: list[HebePeriod]
|
||||
rest_url: str
|
||||
symbol: str
|
||||
|
||||
@staticmethod
|
||||
def from_dict(data: dict):
|
||||
return HebeStudent(
|
||||
id=data["Pupil"]["Id"],
|
||||
full_name=data["Pupil"]["FirstName"] + " " + data["Pupil"]["Surname"],
|
||||
unit_id=data["Unit"]["Id"],
|
||||
constituent_id=data["ConstituentUnit"]["Id"],
|
||||
capabilities=data["Capabilities"],
|
||||
register_id=data["Journal"]["Id"],
|
||||
register_student_number=data["Journal"]["PupilNumber"],
|
||||
class_name=data["ClassDisplay"],
|
||||
is_parent=bool(data["CaretakerId"]),
|
||||
messagebox_key=data["MessageBox"]["GlobalKey"],
|
||||
messagebox_name=data["MessageBox"]["Name"],
|
||||
periods=list(map(HebePeriod.from_dict, data["Periods"])),
|
||||
rest_url=data["Unit"]["RestURL"],
|
||||
symbol=data["TopLevelPartition"],
|
||||
)
|
109
src/sdk/src/apis/prometheus_web/client.py
Normal file
109
src/sdk/src/apis/prometheus_web/client.py
Normal file
|
@ -0,0 +1,109 @@
|
|||
import dataclasses
|
||||
import json
|
||||
from bs4 import BeautifulSoup
|
||||
import requests
|
||||
|
||||
from sdk.src.apis.common.models import FsLsQuery
|
||||
from sdk.src.apis.common.utils import parse_fs_ls_response_form
|
||||
from sdk.src.apis.prometheus_web.constants import (
|
||||
ENDPOINT_ACCOUNT_QUERY_USER_INFO,
|
||||
ENDPOINT_API_AP,
|
||||
ENDPOINT_FS_LS,
|
||||
ENDPOINT_LOGIN,
|
||||
HEADERS,
|
||||
PROMETHEUS_WEB_BASE,
|
||||
)
|
||||
from sdk.src.apis.prometheus_web.exceptions import (
|
||||
NoLoggedInException,
|
||||
InvalidCredentialsException,
|
||||
)
|
||||
|
||||
|
||||
class PrometheusWebClient:
|
||||
def __init__(self, cookies: dict[str, str] | None = None):
|
||||
self._session = requests.Session()
|
||||
if cookies:
|
||||
self._session.cookies.update(cookies)
|
||||
|
||||
def is_logged(self):
|
||||
if not self._session.cookies.get_dict():
|
||||
return False
|
||||
home_response = self._session.get(PROMETHEUS_WEB_BASE)
|
||||
soup = BeautifulSoup(home_response.text, "html.parser")
|
||||
return bool(soup.select_one("div.user-avatar"))
|
||||
|
||||
def get_login_data(self):
|
||||
login_page = self._session.get(
|
||||
f"{PROMETHEUS_WEB_BASE}/logowanie",
|
||||
headers=HEADERS,
|
||||
).text
|
||||
|
||||
return self._parse_login_page(login_page)
|
||||
|
||||
def _parse_login_page(self, html: str):
|
||||
soup = BeautifulSoup(html, "html.parser")
|
||||
request_verification_token = soup.select_one(
|
||||
'input[name="__RequestVerificationToken"]'
|
||||
)["value"]
|
||||
|
||||
captcha_text = soup.select_one('label[for="captchaUser"]').text
|
||||
captcha_images = [img["src"] for img in soup.select(".v-captcha-image")]
|
||||
|
||||
return request_verification_token, captcha_text, captcha_images
|
||||
|
||||
def query_user_info(self, username: str, request_verification_token: str):
|
||||
response = self._session.post(
|
||||
f"{PROMETHEUS_WEB_BASE}/{ENDPOINT_ACCOUNT_QUERY_USER_INFO}",
|
||||
{
|
||||
"alias": username,
|
||||
"__RequestVerificationToken": request_verification_token,
|
||||
},
|
||||
)
|
||||
data = response.json()["data"]
|
||||
return data["ExtraMessage"], data["ShowCaptcha"]
|
||||
|
||||
def login(
|
||||
self,
|
||||
username: str,
|
||||
password: str,
|
||||
reqest_verification_token: str,
|
||||
captcha: str | None,
|
||||
):
|
||||
login_result = self._session.post(
|
||||
f"{PROMETHEUS_WEB_BASE}/{ENDPOINT_LOGIN}",
|
||||
data={
|
||||
"Alias": username,
|
||||
"Password": password,
|
||||
"captchaUser": captcha,
|
||||
"__RequestVerificationToken": reqest_verification_token,
|
||||
},
|
||||
headers=HEADERS,
|
||||
)
|
||||
|
||||
if "Zła nazwa użytkownika lub hasło." in login_result.text:
|
||||
raise InvalidCredentialsException()
|
||||
|
||||
def get_mobile_data(self):
|
||||
response = self._session.get(
|
||||
f"{PROMETHEUS_WEB_BASE}/{ENDPOINT_API_AP}", headers=HEADERS
|
||||
)
|
||||
|
||||
if "Logowanie" in response.text:
|
||||
raise NoLoggedInException()
|
||||
|
||||
soup = BeautifulSoup(response.text, "html.parser")
|
||||
return json.loads(soup.select_one("input#ap")["value"])
|
||||
|
||||
def fs_ls(self, query: FsLsQuery):
|
||||
response = self._session.get(
|
||||
f"{PROMETHEUS_WEB_BASE}/{ENDPOINT_FS_LS}",
|
||||
params=dataclasses.asdict(query),
|
||||
)
|
||||
|
||||
if "Logowanie" in response.text:
|
||||
raise NoLoggedInException()
|
||||
|
||||
return parse_fs_ls_response_form(response.text)
|
||||
|
||||
def get_cookies(self):
|
||||
return self._session.cookies.get_dict()
|
11
src/sdk/src/apis/prometheus_web/constants.py
Normal file
11
src/sdk/src/apis/prometheus_web/constants.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
PROMETHEUS_WEB_BASE = "https://eduvulcan.pl"
|
||||
|
||||
HEADERS = {
|
||||
"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",
|
||||
}
|
||||
|
||||
ENDPOINT_LOGIN = "logowanie"
|
||||
ENDPOINT_API_AP = "api/ap"
|
||||
ENDPOINT_ACCOUNT_QUERY_USER_INFO = "Account/QueryUserInfo"
|
||||
ENDPOINT_FS_LS = "fs/ls"
|
10
src/sdk/src/apis/prometheus_web/exceptions.py
Normal file
10
src/sdk/src/apis/prometheus_web/exceptions.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
class PrometheusWebException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidCredentialsException(PrometheusWebException):
|
||||
pass
|
||||
|
||||
|
||||
class NoLoggedInException(PrometheusWebException):
|
||||
pass
|
24
src/sdk/src/interfaces/core/interface.py
Normal file
24
src/sdk/src/interfaces/core/interface.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
from datetime import date
|
||||
from sdk.src.models.exam import Exam
|
||||
from sdk.src.models.grade import Grade
|
||||
from sdk.src.models.student import Student
|
||||
|
||||
|
||||
class CoreInterface:
|
||||
def select_student(self, context) -> None:
|
||||
pass
|
||||
|
||||
def login(self) -> None:
|
||||
pass
|
||||
|
||||
def get_students(self) -> list[Student]:
|
||||
pass
|
||||
|
||||
def get_lucky_number(self) -> int:
|
||||
pass
|
||||
|
||||
def get_grades(period_number: int) -> list[Grade]:
|
||||
pass
|
||||
|
||||
def get_exams(from_: date, to: date) -> list[Exam]:
|
||||
pass
|
47
src/sdk/src/interfaces/prometheus/context.py
Normal file
47
src/sdk/src/interfaces/prometheus/context.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
from sdk.src.apis.hebe import HebeCertificate
|
||||
from sdk.src.apis.hebe.student import HebeStudent
|
||||
from sdk.src.interfaces.prometheus.utils import get_context_periods_from_hebe_periods
|
||||
|
||||
|
||||
@dataclass
|
||||
class PrometheusStudentContext:
|
||||
student_id: int
|
||||
unit_id: int
|
||||
constituent_id: int
|
||||
periods: dict[int, int]
|
||||
register_id: int
|
||||
hebe_url: str
|
||||
symbol: str
|
||||
messagebox_key: str
|
||||
messagebox_name: str
|
||||
|
||||
@staticmethod
|
||||
def create(hebe_student: HebeStudent):
|
||||
return PrometheusStudentContext(
|
||||
student_id=hebe_student.id,
|
||||
unit_id=hebe_student.unit_id,
|
||||
constituent_id=hebe_student.constituent_id,
|
||||
periods=get_context_periods_from_hebe_periods(hebe_student.periods),
|
||||
register_id=hebe_student.register_id,
|
||||
hebe_url=hebe_student.rest_url,
|
||||
symbol=hebe_student.symbol,
|
||||
messagebox_key=hebe_student.messagebox_key,
|
||||
messagebox_name=hebe_student.messagebox_name,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class PrometheusWebCredentials:
|
||||
username: str
|
||||
password: str
|
||||
|
||||
|
||||
@dataclass
|
||||
class PrometheusAuthContext:
|
||||
prometheus_web_credentials: PrometheusWebCredentials
|
||||
symbols: list[str] | None = None
|
||||
hebe_certificate: HebeCertificate | None = None
|
||||
prometheus_web_cookies: dict[str, str] | None = None
|
||||
efeb_web_cookies: dict[str, str | None] | None = None
|
10
src/sdk/src/interfaces/prometheus/exceptions.py
Normal file
10
src/sdk/src/interfaces/prometheus/exceptions.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
class PrometheusInterfaceException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class NoLoggedInException(PrometheusInterfaceException):
|
||||
pass
|
||||
|
||||
|
||||
class NoStudentSelectedException(PrometheusInterfaceException):
|
||||
pass
|
265
src/sdk/src/interfaces/prometheus/interface.py
Normal file
265
src/sdk/src/interfaces/prometheus/interface.py
Normal file
|
@ -0,0 +1,265 @@
|
|||
from datetime import date, datetime
|
||||
from sdk.src.apis.common.models import FsLsQuery
|
||||
from sdk.src.apis.efeb.client import EfebClient
|
||||
from sdk.src.apis.hebe import HebeClient, HebeCertificate
|
||||
from sdk.src.apis.prometheus_web.client import PrometheusWebClient
|
||||
from sdk.src.apis.prometheus_web.exceptions import (
|
||||
InvalidCredentialsException,
|
||||
NoLoggedInException as PrometheusWebNoLoggedInException,
|
||||
)
|
||||
from sdk.src.interfaces.core.interface import CoreInterface
|
||||
from sdk.src.interfaces.prometheus.context import (
|
||||
PrometheusStudentContext,
|
||||
PrometheusAuthContext,
|
||||
)
|
||||
from sdk.src.interfaces.prometheus.exceptions import (
|
||||
NoLoggedInException,
|
||||
NoStudentSelectedException,
|
||||
)
|
||||
from sdk.src.interfaces.prometheus.utils import (
|
||||
flat_map,
|
||||
get_hebe_url,
|
||||
parse_jwt_payload,
|
||||
)
|
||||
from sdk.src.models.student import Student
|
||||
|
||||
|
||||
class PrometheusInterface(CoreInterface):
|
||||
def __init__(
|
||||
self,
|
||||
auth_context: PrometheusAuthContext,
|
||||
student_context: PrometheusStudentContext | None = None,
|
||||
):
|
||||
self._auth_context = auth_context
|
||||
self._student_context = student_context
|
||||
|
||||
self._prometheus_web_client = PrometheusWebClient(
|
||||
self._auth_context.prometheus_web_cookies
|
||||
)
|
||||
self._hebe_client = (
|
||||
HebeClient(
|
||||
certificate=self._auth_context.hebe_certificate,
|
||||
rest_url=(
|
||||
self._student_context.hebe_url if self._student_context else None
|
||||
),
|
||||
is_ce=True,
|
||||
)
|
||||
if self._auth_context.hebe_certificate
|
||||
else None
|
||||
)
|
||||
self._efeb_clients = {}
|
||||
if self._auth_context.symbols:
|
||||
for symbol in self._auth_context.symbols:
|
||||
if (
|
||||
self._auth_context.efeb_web_cookies
|
||||
and self._auth_context.efeb_web_cookies.get(symbol)
|
||||
):
|
||||
self._efeb_clients[symbol] = EfebClient(
|
||||
cookies=self._auth_context.efeb_web_cookies[symbol],
|
||||
symbol=symbol,
|
||||
is_ce=True,
|
||||
)
|
||||
self._efeb_student_vars = {}
|
||||
self._efeb_messages_vars = {}
|
||||
|
||||
def select_student(self, context: PrometheusStudentContext):
|
||||
self._student_context = context
|
||||
self._hebe_client.set_rest_url(context.hebe_url)
|
||||
self._hebe_client.heartbeat()
|
||||
|
||||
def get_auth_context(self):
|
||||
return self._auth_context
|
||||
|
||||
def login(self, captcha: str | None = None):
|
||||
if not self._prometheus_web_client.is_logged():
|
||||
result = self._login_prometheus(captcha)
|
||||
if result:
|
||||
return result
|
||||
self._efeb_clients = {}
|
||||
self._auth_context.efeb_web_cookies = {}
|
||||
self._efeb_student_vars = {}
|
||||
self._efeb_messages_vars = {}
|
||||
|
||||
self._auth_context.prometheus_web_cookies = (
|
||||
self._prometheus_web_client.get_cookies()
|
||||
)
|
||||
|
||||
# TODO: Expired hebe certificate
|
||||
if not self._auth_context.hebe_certificate:
|
||||
self._login_hebe()
|
||||
|
||||
self._login_efeb()
|
||||
|
||||
def _login_prometheus(self, captcha: str | None = None):
|
||||
print("Logging for prometheus...")
|
||||
request_verification_token, captcha_text, captcha_images = (
|
||||
self._prometheus_web_client.get_login_data()
|
||||
)
|
||||
|
||||
_, show_captcha = self._prometheus_web_client.query_user_info(
|
||||
self._auth_context.prometheus_web_credentials.username,
|
||||
request_verification_token,
|
||||
)
|
||||
if show_captcha:
|
||||
return {
|
||||
"status": "captcha",
|
||||
"captcha_text": captcha_text,
|
||||
"captcha_images": captcha_images,
|
||||
}
|
||||
|
||||
try:
|
||||
self._prometheus_web_client.login(
|
||||
self._auth_context.prometheus_web_credentials.username,
|
||||
self._auth_context.prometheus_web_credentials.password,
|
||||
request_verification_token,
|
||||
captcha,
|
||||
)
|
||||
except InvalidCredentialsException:
|
||||
_, show_captcha = self._prometheus_web_client.query_user_info(
|
||||
self._auth_context.prometheus_web_credentials.username,
|
||||
request_verification_token,
|
||||
)
|
||||
return {
|
||||
"status": "invalid_credentials",
|
||||
"captcha_text": captcha_text,
|
||||
"captcha_images": captcha_images,
|
||||
"show_captcha": show_captcha,
|
||||
}
|
||||
|
||||
def _login_hebe(self):
|
||||
print("Logging for hebe...")
|
||||
self._auth_context.hebe_certificate = HebeCertificate.generate()
|
||||
self._hebe_client = HebeClient(
|
||||
certificate=self._auth_context.hebe_certificate, rest_url=None, is_ce=True
|
||||
)
|
||||
|
||||
try:
|
||||
mobile_data = self._prometheus_web_client.get_mobile_data()
|
||||
except PrometheusWebNoLoggedInException:
|
||||
self._auth_context.prometheus_web_cookies = None
|
||||
raise NoLoggedInException()
|
||||
|
||||
symbols: dict[str, list[any]] = {}
|
||||
for token in mobile_data["Tokens"]:
|
||||
payload = parse_jwt_payload(token)
|
||||
if not symbols.get(payload["tenant"]):
|
||||
symbols[payload["tenant"]] = [token]
|
||||
else:
|
||||
symbols[payload["tenant"]].append(token)
|
||||
|
||||
for symbol in symbols:
|
||||
self._hebe_client.set_rest_url(get_hebe_url(symbol))
|
||||
self._hebe_client.register_jwt(symbols[symbol])
|
||||
|
||||
self._auth_context.symbols = list(symbols.keys())
|
||||
|
||||
def _login_efeb(self):
|
||||
if not self._auth_context.efeb_web_cookies:
|
||||
self._auth_context.efeb_web_cookies = dict(
|
||||
[(symbol, None) for symbol in self._auth_context.symbols]
|
||||
)
|
||||
|
||||
for symbol in self._auth_context.efeb_web_cookies:
|
||||
if self._auth_context.efeb_web_cookies[symbol]:
|
||||
continue
|
||||
|
||||
print(f"Logging for efeb at {symbol}")
|
||||
|
||||
try:
|
||||
student_prometheus_response = self._prometheus_web_client.fs_ls(
|
||||
FsLsQuery(
|
||||
wa="wsignin1.0",
|
||||
wtrealm=f"https://dziennik-logowanie.vulcan.net.pl/{symbol}/Fs/Ls?wa=wsignin1.0&wtrealm=https://uczen.eduvulcan.pl/{symbol}/App&wctx=auth=studentEV&nslo=1",
|
||||
wctx="nslo=1",
|
||||
)
|
||||
)
|
||||
except PrometheusWebNoLoggedInException:
|
||||
self._auth_context.prometheus_web_cookies = None
|
||||
raise NoLoggedInException()
|
||||
|
||||
self._efeb_clients[symbol] = EfebClient(
|
||||
cookies=self._auth_context.prometheus_web_cookies,
|
||||
symbol=symbol,
|
||||
is_ce=True,
|
||||
)
|
||||
student_login_response = self._efeb_clients[symbol].login_fs_ls(
|
||||
query=FsLsQuery(
|
||||
wa="wsignin1.0",
|
||||
wtrealm=f"https://uczen.eduvulcan.pl/{symbol}/App",
|
||||
wctx="auth=studentEV&nslo=1",
|
||||
),
|
||||
prometheus_response=student_prometheus_response,
|
||||
)
|
||||
self._efeb_student_vars[symbol] = self._efeb_clients[symbol].student_app(
|
||||
login_response=student_login_response
|
||||
)
|
||||
|
||||
messages_login_response = self._efeb_clients[symbol].login_fs_ls(
|
||||
query=FsLsQuery(
|
||||
wa="wsignin1.0",
|
||||
wtrealm=f"https://wiadomosci.eduvulcan.pl/{symbol}/App",
|
||||
wctx="auth=studentEV&nslo=1",
|
||||
),
|
||||
)
|
||||
self._efeb_messages_vars[symbol] = self._efeb_clients[symbol].messages_app(
|
||||
login_response=messages_login_response
|
||||
)
|
||||
self._auth_context.efeb_web_cookies[symbol] = self._efeb_clients[
|
||||
symbol
|
||||
].get_cookies()
|
||||
|
||||
def _check_is_auth_context_full(self):
|
||||
if (
|
||||
not self._auth_context.hebe_certificate
|
||||
or not self._auth_context.prometheus_web_cookies
|
||||
or not self._auth_context.symbols
|
||||
):
|
||||
raise NoLoggedInException()
|
||||
|
||||
def _check_is_student_selected(self):
|
||||
if not self._student_context:
|
||||
raise NoStudentSelectedException()
|
||||
|
||||
def get_students(self):
|
||||
self._check_is_auth_context_full()
|
||||
return flat_map(
|
||||
[*map(self._get_students_in_symbol, self._auth_context.symbols)]
|
||||
)
|
||||
|
||||
def _get_students_in_symbol(self, symbol: str):
|
||||
self._hebe_client.set_rest_url(get_hebe_url(symbol))
|
||||
hebe_students = self._hebe_client.get_students()
|
||||
return [
|
||||
Student.from_hebe_student(
|
||||
hebe_student, PrometheusStudentContext.create(hebe_student)
|
||||
)
|
||||
for hebe_student in hebe_students
|
||||
]
|
||||
|
||||
def get_lucky_number(self):
|
||||
self._check_is_auth_context_full()
|
||||
self._check_is_student_selected()
|
||||
return self._hebe_client.get_lucky_number(
|
||||
self._student_context.student_id,
|
||||
self._student_context.constituent_id,
|
||||
datetime.now(),
|
||||
)
|
||||
|
||||
def get_grades(self, period_number: int):
|
||||
self._check_is_auth_context_full()
|
||||
self._check_is_student_selected()
|
||||
return self._hebe_client.get_grades(
|
||||
self._student_context.student_id,
|
||||
self._student_context.unit_id,
|
||||
self._student_context.periods[period_number],
|
||||
)
|
||||
|
||||
def get_exams(self, from_: date, to: date):
|
||||
self._check_is_auth_context_full()
|
||||
self._check_is_student_selected()
|
||||
return self._hebe_client.get_exams(self._student_context.student_id, from_, to)
|
||||
|
||||
def get_notes(self):
|
||||
self._check_is_auth_context_full()
|
||||
self._check_is_student_selected()
|
||||
return self._hebe_client.get_notes(self._student_context.student_id)
|
36
src/sdk/src/interfaces/prometheus/utils.py
Normal file
36
src/sdk/src/interfaces/prometheus/utils.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
import base64
|
||||
import json
|
||||
|
||||
from sdk.src.apis.hebe.student import HebePeriod
|
||||
|
||||
|
||||
def parse_jwt_payload(token: str):
|
||||
# Split the JWT into parts
|
||||
_, payload, _ = token.split(".")
|
||||
|
||||
# Decode the payload from Base64
|
||||
# Add padding
|
||||
payload += "=" * (-len(payload) % 4)
|
||||
decoded_payload = base64.urlsafe_b64decode(payload).decode("utf-8")
|
||||
|
||||
# Parse the payload as JSON
|
||||
payload_json = json.loads(decoded_payload)
|
||||
return payload_json
|
||||
|
||||
|
||||
def get_hebe_url(symbol: str, unit_symbol: str | None = None):
|
||||
return f"https://lekcjaplus.vulcan.net.pl/{symbol}{'/' + unit_symbol if unit_symbol else ''}/api"
|
||||
|
||||
|
||||
def get_context_periods_from_hebe_periods(hebe_periods: list[HebePeriod]):
|
||||
periods = {}
|
||||
for period in hebe_periods:
|
||||
periods[period.number] = period.id
|
||||
return periods
|
||||
|
||||
|
||||
def flat_map(list: list[list]):
|
||||
new = []
|
||||
for sublist in list:
|
||||
[new.append(item) for item in sublist]
|
||||
return new
|
43
src/sdk/src/models/exam.py
Normal file
43
src/sdk/src/models/exam.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
from dataclasses import dataclass
|
||||
from datetime import date, datetime
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class ExamType(Enum):
|
||||
TEST = 0
|
||||
SHORT_TEST = 1
|
||||
CLASSWORK = 2
|
||||
OTHER = 3
|
||||
|
||||
@staticmethod
|
||||
def from_hebe_type_name(type_name: str):
|
||||
match type_name:
|
||||
case "Sprawdzian":
|
||||
return ExamType.TEST
|
||||
case "Kartkówka":
|
||||
return ExamType.SHORT_TEST
|
||||
case "Praca klasowa":
|
||||
return ExamType.CLASSWORK
|
||||
case _:
|
||||
return ExamType.OTHER
|
||||
|
||||
|
||||
@dataclass
|
||||
class Exam:
|
||||
deadline: date
|
||||
subject: str
|
||||
type: ExamType
|
||||
description: str
|
||||
creator: str
|
||||
created_at: datetime
|
||||
|
||||
@staticmethod
|
||||
def from_hebe_dict(data: dict):
|
||||
return Exam(
|
||||
deadline=datetime.fromtimestamp(data["Deadline"]["Timestamp"] / 1000),
|
||||
subject=data["Subject"]["Name"],
|
||||
type=ExamType.from_hebe_type_name(data["Type"]),
|
||||
description=data["Content"],
|
||||
creator=data["Creator"]["DisplayName"],
|
||||
created_at=datetime.fromtimestamp(data["DateCreated"]["Timestamp"] / 1000),
|
||||
)
|
29
src/sdk/src/models/grade.py
Normal file
29
src/sdk/src/models/grade.py
Normal file
|
@ -0,0 +1,29 @@
|
|||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class Grade:
|
||||
value: str
|
||||
is_point: bool
|
||||
point_numerator: int
|
||||
point_denominator: int
|
||||
weight: float
|
||||
name: str
|
||||
created_at: datetime
|
||||
subject: str
|
||||
creator: str
|
||||
|
||||
@staticmethod
|
||||
def from_hebe_dict(data: dict[str, any]):
|
||||
return Grade(
|
||||
value=data["Content"],
|
||||
is_point=data["Numerator"] != None,
|
||||
point_numerator=data["Numerator"],
|
||||
point_denominator=data["Denominator"],
|
||||
weight=data["Column"]["Weight"],
|
||||
name=data["Column"]["Name"] or data["Column"]["Code"],
|
||||
created_at=datetime.fromtimestamp(data["DateCreated"]["Timestamp"] / 1000),
|
||||
subject=data["Column"]["Subject"]["Name"],
|
||||
creator=data["Creator"]["DisplayName"],
|
||||
)
|
21
src/sdk/src/models/note.py
Normal file
21
src/sdk/src/models/note.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class Note:
|
||||
name: str | None
|
||||
content: str
|
||||
points: str | None
|
||||
creator: str
|
||||
created_at: datetime
|
||||
|
||||
@staticmethod
|
||||
def from_hebe_dict(data: dict):
|
||||
return Note(
|
||||
name=data["Category"]["Name"] if data["Category"] else None,
|
||||
content=data["Content"],
|
||||
points=str(data["Points"]) if data["Points"] else None,
|
||||
creator=data["Creator"]["DisplayName"],
|
||||
created_at=datetime.fromtimestamp(data["DateValid"]["Timestamp"] / 1000),
|
||||
)
|
48
src/sdk/src/models/student.py
Normal file
48
src/sdk/src/models/student.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
from dataclasses import dataclass
|
||||
from datetime import date
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
from sdk.src.apis.hebe.student import HebePeriod, HebeStudent
|
||||
|
||||
|
||||
@dataclass
|
||||
class Period:
|
||||
id: int
|
||||
number: int
|
||||
current: bool
|
||||
from_: date
|
||||
to: date
|
||||
|
||||
@staticmethod
|
||||
def from_hebe_period(period: HebePeriod):
|
||||
return Period(
|
||||
id=period.id,
|
||||
number=period.number,
|
||||
current=period.current,
|
||||
from_=period.from_,
|
||||
to=period.to,
|
||||
)
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
@dataclass
|
||||
class Student(Generic[T]):
|
||||
full_name: str
|
||||
is_parent: bool
|
||||
class_name: str
|
||||
register_number: int | None
|
||||
periods: list[Period]
|
||||
context: T
|
||||
|
||||
@staticmethod
|
||||
def from_hebe_student(student: HebeStudent, context: T):
|
||||
return Student(
|
||||
full_name=student.full_name,
|
||||
is_parent=student.is_parent,
|
||||
class_name=student.class_name,
|
||||
register_number=student.register_student_number,
|
||||
periods=list(map(Period.from_hebe_period, student.periods)),
|
||||
context=context,
|
||||
)
|
37
src/test.py
37
src/test.py
|
@ -1,37 +0,0 @@
|
|||
from impl.hebece.src.api import *
|
||||
from impl.hebece.src.parser 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]}")
|
Loading…
Add table
Add a link
Reference in a new issue