commit 80fbd8b946d111e2eaaf84c9f5a8deec664edc09 Author: mnjx Date: Wed Apr 12 23:25:00 2023 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..afed073 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.csv diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c5332dd --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 mnjx + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..88eae8c --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# SOL Enhanced + +An enhanced version of the SkolaOnLine (SOL) online school system. + +It aims to provide more readable data than the web version and +remove some of the annoyances of the mobile app. + +Insiration was taken from [@JakubAndrysek](https://github.com/JakubAndrysek)'s [skola-online-stahovani-znamek](https://github.com/JakubAndrysek/skola-online-stahovani-znamek) project. + +## Still in development! +#### It is not ready yet. diff --git a/scrape_data.py b/scrape_data.py new file mode 100644 index 0000000..2a0246c --- /dev/null +++ b/scrape_data.py @@ -0,0 +1,510 @@ +import requests +from bs4 import BeautifulSoup +import time + +session = requests.Session() +REMOTE_SERVER = "one.one.one.one" +not_login = 0 +F_not_anymore = 0 + +def fetch_locally(what: str) -> list: + global not_login, F_not_anymore + + if not_login == 1 and F_not_anymore == 0: + F_not_anymore = 1 + not_login = 0 + print("Could not sign in (you may have entered invalid login credentials)") + print("Fetching locally\n") + if what == "schedule": + schedule = schedule_from_file() + return schedule + elif what == "grades": + grades = grades_from_file() + return grades + +def login(user: str, pwd: str) -> bool: + if is_connected(REMOTE_SERVER) == True: + url: str = "https://aplikace.skolaonline.cz/SOL/Prihlaseni.aspx" + data = { + "__EVENTTARGET": "dnn$ctr994$SOLLogin$btnODeslat", + "__EVENTARGUMENT": "", + "__VIEWSTATE": "", + "__VIEWSTATEGENERATOR": "", + "__VIEWSTATEENCRYPTED": "", + "__PREVIOUSPAGE": "", + "__EVENTVALIDATION": "", + "dnn$dnnSearch$txtSearch": "", + "JmenoUzivatele": user, # username + "HesloUzivatele": pwd, # password + "ScrollTop": "", + "__dnnVariable": "", + "__RequestVerificationToken": "" + } + + post_login = session.post(url, data=data) + if post_login.status_code != 200: + #print("Could not sign in") + return False + else: + #print("Sign in successful") + return True + else: + #print("Could not sign in") + #print("Fetching data from local files") + return False + +def is_connected(hostname: str) -> bool: + import socket + + try: + host = socket.gethostbyname(hostname) + s = socket.create_connection((host, 80), 2) + s.close() + return True + except Exception: + pass + return False + +def get_html_from(params=None) -> str: + base = "https://aplikace.skolaonline.cz" + + if params is not None: + base += params + + response = session.get(base) + if response.status_code == 200: + #print("Download successful") + return response + else: + print("Download unsuccessful") + return "" + +def sort_calendar(cal: list[dict]) -> list[dict]: + sorted_cal: list[dict] = [{}, {}, {}, {}, {}] + + for i in range(len(cal)): + if cal[i]["DAY"] == "Po": + sorted_cal[0] = cal[i] + if cal[i]["DAY"] == "Út": + sorted_cal[1] = cal[i] + if cal[i]["DAY"] == "St": + sorted_cal[2] = cal[i] + if cal[i]["DAY"] == "Čt": + sorted_cal[3] = cal[i] + if cal[i]["DAY"] == "Pá": + sorted_cal[4] = cal[i] + + return sorted_cal + +def get_schedule(url: str): + try: + html = get_html_from(url) + + soup = BeautifulSoup(html.text, "lxml") + + table = soup.find("table", id="CCADynamicCalendarTable") + + rows_odd = table.find_all("tr", class_="RowOdd") + rows_even = table.find_all("tr", class_="RowEven") + rows = rows_odd + rows_even + + calendar = [] + + for i in range(len(rows)): + if rows[i].find("td", class_="KuvSuplovanaHodina") is not None and rows[i].find("td", class_="KuvSuplujiciHodina") is None: + continue + + subjects = [] + wheres = [] + cells = rows[i].find_all("td", attrs={"class": "DctCell"}) + for j in range(len(cells)): + if cells[j].text == "": + new_span = soup.new_tag("span", attrs={'class': 'KuvBunkaRozvrhNadpis'}) + new_span.string = "Volno" + cells[j].append(new_span) + + if rows[i].find("td", class_="KuvHeaderNadpis") is not None: + day = rows[i].find("td", class_="KuvHeaderNadpis").text + + date = rows[i].find("td", class_="KuvHeaderText").text + + subjects_ = rows[i].find_all("span", class_="KuvBunkaRozvrhNadpis") + temp = [] + for j in range(len(subjects_)): + temp.append(subjects_[j].text) + subjects = temp + + F_con = False + try: + while subjects[-1] == "Volno": + del subjects[-1] + if subjects == []: + F_con = True + except IndexError: + pass + + wheres_ = rows[i].find_all("span", class_="KuvBunkaRozvrhText") + temp = [] + for j in range(len(wheres_)): + if len(subjects[j]) < 10: + temp.append(wheres_[j].text[4:]) + else: + temp.append(" - ") + wheres = temp + + for j in range(len(cells)): + if cells[j].get("colspan") is not None: + if int(cells[j].get("colspan")) == 2: + subjects.insert(j, subjects[j]) + wheres.insert(j, wheres[j]) + for j in range(len(subjects)): + if subjects[j] == "Volno": + wheres.insert(j, " - ") + + if F_con == False: + calendar_row = { + "DAY": day, + "DATE": date, + "SUBJECTS": subjects, + "WHERES": wheres + } + + calendar.append(calendar_row) + + sorted_calendar = sort_calendar(calendar) + + return sorted_calendar + except AttributeError: + global not_login + + not_login = 1 + schedule = fetch_locally("schedule") + return schedule + +def schedule_from_file_to_list(): + schedule = [] + schedule_ = [] + + try: + with open("schedule.csv") as f: + import csv + reader = csv.reader(f) + for row in reader: + temp = [] + for col in row: + temp.append(col.strip()) + schedule_.append(temp) + + for i in range(0, len(schedule_), 2): + del schedule_[i][0] + + temp = [] + for i in range(1, len(schedule_), 2): + temp.append(schedule_[i][0]) + del schedule_[i][0] + schedule.append(temp) + + for i in range(10): + temp = [] + for j in range(10): + try: + temp.append(schedule_[j][0]) + del schedule_[j][0] + except IndexError: + pass + schedule.append(temp) + except FileNotFoundError: + print("No schedule.csv file found\n") + + return schedule + +def get_whitespace(key: str, idx=0) -> int: + schedule = schedule_from_file_to_list() + max_len = 0 + + if key == "DATE": + try: + max_len = len(max(schedule[0], key=len)) + except ValueError: + pass + elif key == "WHERES": + try: + max_len = len(max(schedule[idx], key=len)) + except ValueError: + pass + + return max_len + +def show_schedule(): + # if there is internet access, load stuff from the web + # if not, load the schedule.csv file + if is_connected(REMOTE_SERVER) == True: + schedule = get_schedule("/SOL/App/Kalendar/KZK001_KalendarTyden.aspx") + schedule_to_file(schedule) + else: + schedule = schedule_from_file() + + print("--------") + print(" Rozvrh") + print("--------\n") + + if schedule == []: + print("N/A\n") + import os + + if os.path.exists("schedule.csv"): + os.remove("schedule.csv") + return + + for i in range(len(schedule)): + max_len = get_whitespace("DATE") + + whitespace = " " * (max_len - len(schedule[i]["DAY"])) + print(schedule[i]["DAY"] + whitespace, end=" ") + + for j in range(len(schedule[i]["SUBJECTS"])): + max_len = get_whitespace("WHERES", j+1) + whitespace = " " * (max_len - len(schedule[i]["SUBJECTS"][j])) + print("| " + schedule[i]["SUBJECTS"][j] + whitespace, end=" ") + + print("") + + max_len = get_whitespace("DATE") + whitespace = " " * (max_len - len(schedule[i]["DATE"])) + print(schedule[i]["DATE"] + whitespace, end=" ") + + for j in range(len(schedule[i]["WHERES"])): + max_len = get_whitespace("WHERES", j+1) + whitespace = " " * (max_len - len(schedule[i]["WHERES"][j])) + print("| " + schedule[i]["WHERES"][j] + whitespace, end=" ") + + print("") + print("") + +def get_avg(grades: list) -> float: + if len(grades) < 1: + return "" + + sum_ = 0 + for i in grades: + sum_ += int(i) + avg = round(sum_ / len(grades), 2) + + return str(avg) + +def get_grades(url: str): + html = get_html_from(url) + + soup = BeautifulSoup(html.text, "lxml") + + rows = soup.find("table", id="G_ctl00xmainxCCADynamicUWGrid").find_all("tr") + for i in range(3): + r = soup.find("table", id="G_ctl00xmainxCCADynamicUWGrid").find("tr") + r.decompose() + + grades = [] + + for i in range(1, len(rows)): + if rows[i].find("td") is not None: + subject = rows[i].find("td").text + temp_dict = {} + temp_dict["SUBJECT"] = subject + cell = rows[i].find_all("span", class_="VHVysl") + temp = [] + for j in range(len(cell)): + temp.append(cell[j].text) + temp_dict["GRADES"] = temp + + grades.append(temp_dict) + + return grades + +def show_grades(): + if is_connected(REMOTE_SERVER) == True: + grades = get_grades("/SOL/App/Hodnoceni/KZH001_HodnVypisStud.aspx") + grades_to_file(grades) + else: + grades = grades_from_file() + + whitespace = "" + max_len_of_subject = 0 + subjects = [grades[j]["SUBJECT"] for j in range(len(grades))] + for i in subjects: + if len(i) > max_len_of_subject: + max_len_of_subject = len(i) + max_len_of_subject += 1 + + print("--------") + print(" Znamky") + print("--------\n") + + if grades == []: + print("N/A\n") + import os + + if os.path.exists("grades.csv"): + os.remove("grades.csv") + return + + for i in range(len(grades)): + whitespace = " " * (max_len_of_subject - len(grades[i]["SUBJECT"])) + print(grades[i]["SUBJECT"] + whitespace + "|", end=" ") + print("".join([grades[i]["GRADES"][j]+" " for j in range(len(grades[i]["GRADES"]))]), end=" ") + print("|", get_avg(grades[i]["GRADES"])) + print("") + print("") + +def schedule_to_file(schedule=None): + import csv + if schedule is None: + try: + schedule = get_schedule("/SOL/App/Kalendar/KZK001_KalendarTyden.aspx") + except Exception: + print("An error occured, you probably can't access the internet right now") + return + + with open("schedule.csv", mode="w", newline="") as f: + data = [] + + for i in range(len(schedule)): + temp = [] + try: + temp.append(schedule[i]["DAY"]) + for j in range(len(schedule[i]["SUBJECTS"])): + temp.append(schedule[i]["SUBJECTS"][j]) + data.append(temp) + + temp = [] + temp.append(schedule[i]["DATE"]) + for j in range(len(schedule[i]["WHERES"])): + temp.append(schedule[i]["WHERES"][j]) + data.append(temp) + + temp = [] + for j in range(len(schedule[i]["WHOS"])): + temp.append(schedule[i]["WHOS"][j]) + data.append(temp) + except KeyError: + pass + + writer = csv.writer(f) + for row in data: + writer.writerow(row) + +def schedule_from_file() -> list: + import csv + schedule = [] + + try: + with open("schedule.csv", mode="r") as f: + reader = csv.reader(f) + rows = [] + for row in reader: + rows.append(row) + + for row in range(0, len(rows), 3): + temp = {} + + temp["DAY"] = rows[row][0] + + try: + temp["DATE"] = rows[row+1][0] + except IndexError: + pass + + subjects = [] + for i in range(1, len(rows[row])): + subjects.append(rows[row][i]) + temp["SUBJECTS"] = subjects + + wheres = [] + try: + for i in range(1, len(rows[row+1])): + wheres.append(rows[row+1][i]) + except IndexError: + pass + temp["WHERES"] = wheres + + whos = [] + try: + for i in range(len(rows[row+2])): + whos.append(rows[row+2][i]) + except IndexError: + pass + temp["WHOS"] = whos + + schedule.append(temp) + except FileNotFoundError: + print("No schedule.csv file found\n") + + return schedule + +def grades_to_file(grades=None): # !!! make sure the format of the grades list is saved correctly !!! + import csv + if grades is None: + try: + grades = get_grades("/SOL/App/Hodnoceni/KZH001_HodnVypisStud.aspx") + except Exception: + print("An error occured, you probably can't access the internet right now") + return + + data = [] + + with open("grades.csv", mode="w", newline="") as f: + for i in range(len(grades)): + temp = {} + temp["SUBJECT"] = grades[i]["SUBJECT"] + temp_grades = [] + for j in range(len(grades[i]["GRADES"])): + temp_grades.append(grades[i]["GRADES"][j]) + temp["GRADES"] = temp_grades + data.append(temp) + + writer = csv.writer(f) + for row in data: + _row = [] + _row.append(row["SUBJECT"]) + for i in row["GRADES"]: + _row.append(i) + writer.writerow(_row) + +def grades_from_file() -> list: + import csv + grades = [] + + try: + with open("grades.csv", mode="r") as f: + reader = csv.reader(f) + rows = [] + for row in reader: + rows.append(row) + + for row in range(len(rows)): + temp = {} + + temp["SUBJECT"] = rows[row][0] + + temp_lst = [] + for i in range(1, len(rows[row])): + temp_lst.append(rows[row][i]) + temp["GRADES"] = temp_lst + + grades.append(temp) + except FileNotFoundError: + print("No grades.csv file found\n") + + return grades + +if __name__ == "__main__": + user = str(input("Zadejte prihlasovaci jmeno: ")) + pwd = str(input("Zadejte heslo: ")) + + if is_connected(REMOTE_SERVER) == True: + login(user, pwd) + #get_schedule("/SOL/App/Kalendar/KZK001_KalendarTyden.aspx") + show_schedule() + #get_grades("/SOL/App/Hodnoceni/KZH001_HodnVypisStud.aspx") + show_grades() + #schedule_to_file() + #grades_to_file() + #schedule_from_file()