diff --git a/.config/launch.sh b/.config/launch.sh new file mode 100755 index 0000000..48543b5 --- /dev/null +++ b/.config/launch.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +if command -v solen > /dev/null +then + solen +else + python3 "./src/main.py" +fi diff --git a/.config/mkdirs.sh b/.config/mkdirs.sh new file mode 100755 index 0000000..ad5dbd4 --- /dev/null +++ b/.config/mkdirs.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +if [ ! -d "$HOME/.local/lib/python3.11/site-packages/solenlib" ]; then + sudo mkdir --parents "$HOME/.local/lib/python3.11/site-packages/solenlib" +fi + +if [ ! -d "$HOME/.local/lib/solendata" ]; then + sudo mkdir --parents "$HOME/.local/lib/solendata" +fi diff --git a/Makefile b/Makefile index 233ae4e..6fe5d49 100755 --- a/Makefile +++ b/Makefile @@ -1,16 +1,15 @@ SHELL=/bin/bash install: - python3 -m pip install -r requirements.txt + python3 -m pip install -r requirements.txt --break-system-packages ./.config/mkdirs.sh cp ./src/main.py /bin/solen chmod 774 /bin/solen + chmod 774 /usr/bin/solen cp ./src/lib/*.py $(HOME)/.local/lib/python3.11/site-packages/solenlib - cp ./src/lib/shadow $(HOME)/.local/lib/solendata - cp ./src/lib/data/*.csv $(HOME)/.local/lib/solendata run: ./.config/launch.sh diff --git a/README.md b/README.md index 924da1e..9bc0909 100755 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# SOL Enhanced +# Solen (ŠOL Enhanced) An enhanced version of the ŠkolaOnLine (ŠOL) online school system. @@ -11,15 +11,15 @@ Insiration was taken from [@JakubAndrysek](https://github.com/JakubAndrysek)'s [ # Features -+ Shows the schedule for the current week -+ Shows all grades and their arithmetic average (not accounting for weight) -+ Works offline (when the internet has been accessed at least once before) ++ Shows the schedule for the current week ++ Shows all grades and their weighted average ++ Works offline (when the internet has been accessed at least once before) # Installation **Clone the repo** -`git clone https://github.com/mnjx/sol-enhanced.git` +`git clone https://github.com/mnjx/sol-enhanced` **Navigate into the repo directory** @@ -31,8 +31,9 @@ Insiration was taken from [@JakubAndrysek](https://github.com/JakubAndrysek)'s [ **Run the program** -From the repo folder: `make run` -From anywhere on the computer: `solen` +| ------------------------------ | ---------- | +| From the repo folder: | `make run` | +| From anywhere on the computer: | `solen` | # Usage @@ -42,4 +43,15 @@ will execute. By default, the functions are: showing the schedule for this week, showing the schedule for today and tomorrow and showing the grades. You can edit that file if you want to change what is being displayed. Alternatively, you can edit the `src/main.py` and then do the install step (`sudo make install`). -An option for command-line arguments will be added in the future. +There are optional command-line arguments for filtering what should be displayed. + +Here they are: +| ----------------------------------- | --------------------------------------------------------------------------------------- | +| -t, --today-tomorrow | show the schedule for today and tomorrow | +| -w, --week | show the schedule for this week | +| -g, --grades | show the grades | +| -u, --username | provide the username for login | +| -p, --password | provide the password for login | +| -l, --local | only show the local data without attempting to connect to the internet | +| -d, --delete-local-files | deletes all locally stored user data, including schedules, grades and login credentials | +| -b, --backup PASSPHRASE DESTINATION | securly backs up all local user data into a .tar.gz archive | diff --git a/src/lib/data/grades.csv b/src/lib/data/grades.csv deleted file mode 100644 index 8b13789..0000000 --- a/src/lib/data/grades.csv +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/lib/data/schedule_tdy_tmr.csv b/src/lib/data/schedule_tdy_tmr.csv deleted file mode 100644 index 8b13789..0000000 --- a/src/lib/data/schedule_tdy_tmr.csv +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/lib/data/schedule_week.csv b/src/lib/data/schedule_week.csv deleted file mode 100644 index 8b13789..0000000 --- a/src/lib/data/schedule_week.csv +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/lib/grades.py b/src/lib/grades.py index 72eafdf..2a4763f 100755 --- a/src/lib/grades.py +++ b/src/lib/grades.py @@ -5,154 +5,166 @@ import os not_login = 0 def fetch_locally() -> list: - global not_login + global not_login - if not_login == 1: - not_login = 0 - #print("Could not sign in (you may have entered invalid login credentials)") - #print("Fetching grades locally\n") - grades = grades_from_file() - return grades + if not_login == 1: + not_login = 0 + #print("Could not sign in (you may have entered invalid login credentials)") + #print("Fetching grades locally\n") + grades = grades_from_file() + return grades +""" +# deprecated def get_avg(grades: list) -> str: - if len(grades) < 1: - return "" + if len(grades) < 1: + return "" - sum_ = 0 - for i in grades: - sum_ += int(i) - avg = round(sum_ / len(grades), 2) + sum_ = 0 + for i in grades: + sum_ += int(i) + avg = round(sum_ / len(grades), 2) - avg = str(avg) - if len(avg) == 3: - avg += "0" + avg = str(avg) + if len(avg) == 3: + avg += "0" - return avg + return avg +""" def get_grades(url: str): - try: - html = nw.get_html_from(url) + try: + html = nw.get_html_from(url) - soup = BeautifulSoup(html.text, "lxml") + 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 + 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) + avg = rows[i].find("td", class_="VHCelkPrumer").text + temp_dict["AVG"] = avg - return grades - except AttributeError: - global not_login + grades.append(temp_dict) - not_login = 1 - grades = fetch_locally() - return grades + return grades + except AttributeError: + global not_login -def show_grades(): - if nw.is_connected() == True: - grades = get_grades("/SOL/App/Hodnoceni/KZH001_HodnVypisStud.aspx") - grades_to_file(grades) - else: - grades = grades_from_file() + not_login = 1 + grades = fetch_locally() + return grades - 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 +def show_grades(local=False): + if local == False: + if nw.is_connected() == True: + grades = get_grades("/SOL/App/Hodnoceni/KZH001_HodnVypisStud.aspx") + grades_to_file(grades) + else: + grades = grades_from_file() + else: + grades = grades_from_file() - print("") + 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(" Známky ") - print("--------\n") + print("") - if grades == []: - print("N/A\n") - import os + print("--------") + print(" Známky ") + print("--------\n") - if os.path.exists(f"{os.environ['HOME']}/.local/lib/solendata/grades.csv"): - os.remove(f"{os.environ['HOME']}/.local/lib/solendata/grades.csv") - return + if grades == []: + print("N/A\n") + import os - for i in range(len(grades)): - if grades[i]["GRADES"] != []: - whitespace = " " * (max_len_of_subject - len(grades[i]["SUBJECT"])) - print(grades[i]["SUBJECT"] + whitespace + "|", end=" ") - print(get_avg(grades[i]["GRADES"]), "|", end=" ") - print("".join([grades[i]["GRADES"][j]+" " for j in range(len(grades[i]["GRADES"]))]), end=" ") - print("") + if os.path.exists(f"{os.environ['HOME']}/.local/lib/solendata/grades.csv"): + os.remove(f"{os.environ['HOME']}/.local/lib/solendata/grades.csv") + return + + for i in range(len(grades)): + if grades[i]["GRADES"] != []: + whitespace = " " * (max_len_of_subject - len(grades[i]["SUBJECT"])) + print(grades[i]["SUBJECT"] + whitespace + "|", end=" ") + print(grades[i]["AVG"], "|", end=" ") + print("".join([grades[i]["GRADES"][j]+" " for j in range(len(grades[i]["GRADES"]))]), end=" ") + print("") 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 + 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 = [] + data = [] - with open(f"{os.environ['HOME']}/.local/lib/solendata/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) + with open(f"{os.environ['HOME']}/.local/lib/solendata/grades.csv", mode="w", newline="") as f: + for i in range(len(grades)): + temp = {} + temp["SUBJECT"] = grades[i]["SUBJECT"] + temp["AVG"] = grades[i]["AVG"] + 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) + writer = csv.writer(f) + for row in data: + _row = [] + _row.append(row["SUBJECT"]) + _row.append(row["AVG"]) + for i in row["GRADES"]: + _row.append(i) + writer.writerow(_row) def grades_from_file() -> list: - import csv - grades = [] + import csv + grades = [] - try: - with open(f"{os.environ['HOME']}/.local/lib/solendata/grades.csv", mode="r") as f: - reader = csv.reader(f) - rows = [] - for row in reader: - rows.append(row) + try: + with open(f"{os.environ['HOME']}/.local/lib/solendata/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 = {} + 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 + temp["SUBJECT"] = rows[row][0] + temp["AVG"] = rows[row][1] + + temp_lst = [] + for i in range(2, 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") + grades.append(temp) + except FileNotFoundError: + print("No grades.csv file found\n") - return grades + return grades diff --git a/src/lib/networking.py b/src/lib/networking.py index 8d06cd0..810e793 100755 --- a/src/lib/networking.py +++ b/src/lib/networking.py @@ -41,7 +41,7 @@ def _get_creds_hash(user: str, pwd: str) -> str: return creds def login(user: str, pwd: str) -> bool: - if is_connected() == True: + if is_connected(): url: str = "https://aplikace.skolaonline.cz/SOL/Prihlaseni.aspx" data = { "__EVENTTARGET": "dnn$ctr994$SOLLogin$btnODeslat", @@ -83,7 +83,7 @@ def is_connected() -> bool: s = socket.create_connection((host, 80), 2) s.close() return True - except Exception: + except: pass return False diff --git a/src/lib/schedule_tdy_tmr.py b/src/lib/schedule_tdy_tmr.py index ef452dc..f6943b9 100755 --- a/src/lib/schedule_tdy_tmr.py +++ b/src/lib/schedule_tdy_tmr.py @@ -85,7 +85,7 @@ def get_schedule(url: str): temp.append(" - ") wheres = temp - for j in range(len(cells)): + for j in range(len(cells)): if cells[j].get("colspan") is not None: if int(cells[j].get("colspan")) == 2: try: @@ -177,12 +177,15 @@ def get_whitespace(key: str, idx=0) -> int: return max_len -def show_schedule(): - # if there is internet access, load stuff from the web - # if not, load the schedule.csv file - if nw.is_connected() == True: - schedule = get_schedule("/SOL/App/Spolecne/KZZ010_RychlyPrehled.aspx") - schedule_to_file(schedule) +def show_schedule(local=False): + if local == False: + # if there is internet access, load stuff from the web + # if not, load the schedule.csv file + if nw.is_connected() == True: + schedule = get_schedule("/SOL/App/Spolecne/KZZ010_RychlyPrehled.aspx") + schedule_to_file(schedule) + else: + schedule = schedule_from_file() else: schedule = schedule_from_file() diff --git a/src/lib/schedule_week.py b/src/lib/schedule_week.py index ff47b7f..319b83c 100755 --- a/src/lib/schedule_week.py +++ b/src/lib/schedule_week.py @@ -200,12 +200,15 @@ def get_whitespace(key: str, idx=0) -> int: return max_len -def show_schedule(): - # if there is internet access, load stuff from the web - # if not, load the schedule.csv file - if nw.is_connected() == True: - schedule = get_schedule("/SOL/App/Kalendar/KZK001_KalendarTyden.aspx") - schedule_to_file(schedule) +def show_schedule(local=False): + if local == False: + # if there is internet access, load stuff from the web + # if not, load the schedule.csv file + if nw.is_connected() == True: + schedule = get_schedule("/SOL/App/Kalendar/KZK001_KalendarTyden.aspx") + schedule_to_file(schedule) + else: + schedule = schedule_from_file() else: schedule = schedule_from_file() diff --git a/src/main.py b/src/main.py index 59f9f54..d0921bf 100755 --- a/src/main.py +++ b/src/main.py @@ -6,18 +6,106 @@ from solenlib import schedule_week as sw from solenlib import schedule_tdy_tmr as st import sys import getpass +import argparse +import datetime if __name__ == "__main__": - user = str(input("Zadejte přihlašovací jméno: ")) - try: - pwd = str(getpass.getpass("Zadejte heslo: ")) - except: - pwd = str(input("Zadejte heslo: ")) + parser = argparse.ArgumentParser(prog="ŠOLEn", description="ŠOLEn, an enhanced version of the ŠkolaOnLine online school system") + + parser.add_argument("-t", "--today-tomorrow", action="store_true", help="Show the schedule for today and tomorrow") + parser.add_argument("-w", "--week", action="store_true", help="Show the schedule for this week") + parser.add_argument("-g", "--grades", action="store_true", help="Show the grades") + parser.add_argument("-u", "--username", action="store", default="", help="Provide the username for login") + parser.add_argument("-p", "--password", action="store", default="", help="Provide the password for login") + parser.add_argument("-l", "--local", action="store_true", help="Only fetch local files, don't connect to the internet") + parser.add_argument("-d", "--delete-local-files", action="store_true", help="Delete all local user data, including schedules, grades and user credentials") + + parser.add_argument("-b", "--backup", action="store_true", help="Securely back up all user data and store them in a .tar.gz archive in the destination directory") + parser.add_argument("-D", "--backup-directory", action="store", help="Backup destination directory") + parser.add_argument("-P", "--backup-password", action="store", help="Backup password to encrypt the archive with") + parser.add_argument("-R", "--backup-restore", action="store", nargs=1, metavar="PATH", help="Specify the path to a .tar.gz archive that was earlier created with ŠOLEn to restore backed up user data") + + args = parser.parse_args() + + if args.delete_local_files: + try: + print("Removing all schedules and grades ...", end=" ") + os.system(f"rm -rf {os.environ['HOME']}/.local/lib/solendata/*.csv") + print("done") + print("Removing all user credentials ...", end=" ") + os.system(f"rm -rf {os.environ['HOME']}/.local/lib/solendata/shadow") + print("done") + print("All user data removed successfully") + sys.exit(0) + except: + print("Could not remove user data") + sys.exit(1) + + elif args.backup: + try: + if not args.backup_directory or not args.backup_password: + print("-D, --backup-directory BACKUP-DIRECTORY and -P, --backup-password BACKUP-PASSWORD are required") + sys.exit(1) + else: + nowtime = str(datetime.now().strftime("%Y-%m-%d_%H-%M-%S")) + try: + print("Creating the backup archive ...", end=" ") + os.system(f"tar cf solen-backup-{nowtime} {os.path.abspath(args.backup-directory)}") + print("done") + print("Backup archive created successfully") + sys.exit(0) + except: + print("The 'tar' utility is not installed") + sys.exit(1) + except: + print("Could not backup user data") + sys.exit(1) + + else: + if args.username: + user = str(args.username) + else: + user = str(input("Zadejte přihlašovací jméno: ")) + + if args.password: + pwd = str(args.password) + else: + try: + pwd = str(getpass.getpass("Zadejte heslo: ")) + except: + pwd = str(input("Zadejte heslo: ")) - if not nw.login(user, pwd): - print("LOGIN UNSUCCESSFULL") - sys.exit(1) - else: - sw.show_schedule() - st.show_schedule() - gr.show_grades() + if not nw.login(user, pwd): + print("Login unsuccessfull") + sys.exit(1) + else: + try: + if args.week: + if args.local: + sw.show_schedule(local=True) + else: + sw.show_schedule() + if args.today_tomorrow: + if args.local: + st.show_schedule(local=True) + else: + st.show_schedule() + if args.grades: + if args.local: + gr.show_grades(local=True) + else: + gr.show_grades() + + if not args.grades and not args.today_tomorrow and not args.week: + if args.local: + sw.show_schedule(local=True) + st.show_schedule(local=True) + gr.show_grades(local=True) + else: + sw.show_schedule() + st.show_schedule() + gr.show_grades() + sys.exit(0) + except Exception as e: + print("Could not execute") + sys.exit(1)