mirror of
https://github.com/szymon-jozef/zutui.git
synced 2026-09-07 20:20:42 +02:00
184 lines
7.3 KiB
Python
184 lines
7.3 KiB
Python
import requests
|
|
from bs4 import BeautifulSoup
|
|
import json
|
|
from appdirs import user_config_dir, user_cache_dir
|
|
import os
|
|
|
|
app_name = "zutui"
|
|
app_author = "shv187"
|
|
|
|
CONFIG_DIR = user_config_dir(app_name, app_author)
|
|
CACHE_DIR = user_cache_dir(app_name, app_author)
|
|
os.makedirs(CACHE_DIR, exist_ok=True)
|
|
os.makedirs(CONFIG_DIR, exist_ok=True)
|
|
|
|
CACHE_FILE = os.path.join(CACHE_DIR, "grades_cache.json")
|
|
TIMEOUT = 10
|
|
|
|
class ZUT:
|
|
URLS = {
|
|
"LOGIN": "https://edziekanat.zut.edu.pl/WU/Logowanie2.aspx",
|
|
"FINAL": "https://edziekanat.zut.edu.pl/WU/OcenyP.aspx",
|
|
"PARTIAL": "https://edziekanat.zut.edu.pl/WU/OcenyCzast.aspx",
|
|
"NEWS": "https://edziekanat.zut.edu.pl/WU/News.aspx"
|
|
}
|
|
|
|
def __init__(self, username, password):
|
|
self.username = username
|
|
self.password = password
|
|
self.session = requests.Session()
|
|
self.session.headers.update({
|
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
|
|
"Origin": "https://edziekanat.zut.edu.pl",
|
|
})
|
|
self.is_logged_in = False
|
|
|
|
def load_cache(self):
|
|
if os.path.exists(CACHE_FILE):
|
|
try:
|
|
with open(CACHE_FILE, 'r', encoding='utf-8') as f:
|
|
return json.load(f)
|
|
except:
|
|
return {}
|
|
return {}
|
|
|
|
def save_cache(self, data):
|
|
try:
|
|
with open(CACHE_FILE, 'w', encoding='utf-8') as f:
|
|
json.dump(data, f, ensure_ascii=False, indent=4)
|
|
except Exception as e:
|
|
print(f"Failed to save cache: {e}")
|
|
|
|
def _get_hidden_inputs(self, soup):
|
|
return {inp.get('name'): inp.get('value') for inp in soup.find_all('input', type='hidden') if inp.get('name')}
|
|
|
|
def login(self):
|
|
try:
|
|
self.session.headers.update({"Referer": self.URLS["LOGIN"]})
|
|
r = self.session.get(self.URLS["LOGIN"], timeout=TIMEOUT)
|
|
soup = BeautifulSoup(r.text, 'html.parser')
|
|
payload = self._get_hidden_inputs(soup)
|
|
|
|
payload.update({
|
|
'ctl00$ctl00$ContentPlaceHolder$MiddleContentPlaceHolder$txtIdent': self.username,
|
|
'ctl00$ctl00$ContentPlaceHolder$MiddleContentPlaceHolder$txtHaslo': self.password,
|
|
'ctl00$ctl00$ContentPlaceHolder$MiddleContentPlaceHolder$butLoguj': 'Zaloguj',
|
|
'ctl00$ctl00$ContentPlaceHolder$MiddleContentPlaceHolder$rbKto': 'student'
|
|
})
|
|
|
|
post_response = self.session.post(self.URLS["LOGIN"], data=payload, timeout=TIMEOUT)
|
|
|
|
if ".ASPX" in str(self.session.cookies.get_dict()) or "Wyloguj" in post_response.text:
|
|
self.is_logged_in = True
|
|
return True
|
|
return False
|
|
except Exception as e:
|
|
print(f"Login Error: {e}")
|
|
return False
|
|
|
|
def get_final_grades(self):
|
|
if not self.is_logged_in: return {}
|
|
self.session.headers.update({"Referer": self.URLS["NEWS"]})
|
|
try:
|
|
resp = self.session.get(self.URLS["FINAL"], timeout=TIMEOUT)
|
|
soup = BeautifulSoup(resp.text, 'html.parser')
|
|
|
|
data = {}
|
|
table = soup.find('table', id='ctl00_ctl00_ContentPlaceHolder_RightContentPlaceHolder_dgDane')
|
|
if not table: return data
|
|
|
|
for row in table.find_all('tr', class_='gridDane'):
|
|
cells = row.find_all('td')
|
|
if not cells: continue
|
|
|
|
def parse_cell(c):
|
|
txt = [t.strip() for t in c.stripped_strings]
|
|
if not txt: return None
|
|
return {"grade": txt[0], "date": txt[1] if len(txt)>1 else ""}
|
|
|
|
subject = cells[0].get_text(strip=True)
|
|
ctype = cells[1].get_text(strip=True)
|
|
key = f"{subject}_{ctype}"
|
|
|
|
data[key] = {
|
|
"subject": subject,
|
|
"type": ctype,
|
|
"final_grades": {
|
|
"term_1": parse_cell(cells[5]),
|
|
"retake_1": parse_cell(cells[6]),
|
|
"retake_2": parse_cell(cells[7]),
|
|
"commission": parse_cell(cells[8]),
|
|
},
|
|
"partial_grades": []
|
|
}
|
|
return data
|
|
except Exception:
|
|
return {}
|
|
|
|
def get_partial_grades(self):
|
|
if not self.is_logged_in: return {}
|
|
try:
|
|
resp = self.session.get(self.URLS["PARTIAL"], timeout=TIMEOUT)
|
|
soup = BeautifulSoup(resp.text, 'html.parser')
|
|
payload = self._get_hidden_inputs(soup)
|
|
|
|
payload.update({
|
|
'__EVENTTARGET': 'ctl00$ctl00$ContentPlaceHolder$RightContentPlaceHolder$chb_ExpColAll',
|
|
'__EVENTARGUMENT': '',
|
|
'ctl00$ctl00$ContentPlaceHolder$RightContentPlaceHolder$chb_ExpColAll': 'on'
|
|
})
|
|
|
|
resp_expanded = self.session.post(self.URLS["PARTIAL"], data=payload, timeout=TIMEOUT)
|
|
soup_exp = BeautifulSoup(resp_expanded.text, 'html.parser')
|
|
|
|
partials_map = {}
|
|
main_grid = soup_exp.find('table', id='ctl00_ctl00_ContentPlaceHolder_RightContentPlaceHolder_rg_Przedmioty_ctl00')
|
|
if not main_grid: return {}
|
|
|
|
rows = main_grid.find_all('tr')
|
|
current_key = None
|
|
|
|
for row in rows:
|
|
classes = row.get('class', [])
|
|
if ('rgRow' in classes or 'rgAltRow' in classes) and not row.find('table'):
|
|
cells = row.find_all('td')
|
|
if len(cells) > 2:
|
|
subj = cells[1].get_text(strip=True)
|
|
ctype = cells[2].get_text(strip=True)
|
|
current_key = f"{subj}_{ctype}"
|
|
|
|
nested_table = row.find('table')
|
|
if nested_table and current_key:
|
|
grades_list = []
|
|
for inner_row in nested_table.find_all('tr'):
|
|
icells = inner_row.find_all('td')
|
|
if len(icells) >= 4:
|
|
g_val = icells[2].get_text(strip=True)
|
|
if g_val:
|
|
grades_list.append({
|
|
"grade": g_val,
|
|
"desc": icells[1].get_text(strip=True) if len(icells) > 1 else "",
|
|
"date": icells[3].get_text(strip=True) if len(icells) > 3 else "",
|
|
"teacher": icells[4].get_text(strip=True) if len(icells) > 4 else ""
|
|
})
|
|
partials_map[current_key] = grades_list
|
|
return partials_map
|
|
except Exception:
|
|
return {}
|
|
|
|
def refresh_data(self):
|
|
if not self.is_logged_in:
|
|
if not self.login():
|
|
return None
|
|
try:
|
|
finals = self.get_final_grades()
|
|
partials = self.get_partial_grades()
|
|
for key, p_grades in partials.items():
|
|
if key in finals:
|
|
finals[key]['partial_grades'] = p_grades
|
|
self.save_cache(finals)
|
|
return finals
|
|
except Exception as e:
|
|
print(f"Refresh error: {e}")
|
|
return None
|