diff --git a/.gitignore b/.gitignore index 0d20b64..94f8f2f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ *.pyc +src/lib/data/*.csv +src/lib/shadow +TODO.md + diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/Makefile b/Makefile old mode 100644 new mode 100755 index cc5a2df..233ae4e --- a/Makefile +++ b/Makefile @@ -1,5 +1,16 @@ +SHELL=/bin/bash + install: - pip install -r requirements.txt + python3 -m pip install -r requirements.txt + + ./.config/mkdirs.sh + + cp ./src/main.py /bin/solen + chmod 774 /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: - python3 "src/main.py" \ No newline at end of file + ./.config/launch.sh diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 5e76f6b..924da1e --- a/README.md +++ b/README.md @@ -15,20 +15,31 @@ Insiration was taken from [@JakubAndrysek](https://github.com/JakubAndrysek)'s [ + Shows all grades and their arithmetic average (not accounting for weight) + Works offline (when the internet has been accessed at least once before) -# Usage +# Installation -Clone the repo +**Clone the repo** -`git clone git@github.com:mnjx/sol-enhanced.git` +`git clone https://github.com/mnjx/sol-enhanced.git` -Navigate into the repo directory +**Navigate into the repo directory** `cd sol-enhanced` -Install the requirements (required to run only once) +**Install** *(superuser privileges required)* -`make install` +`sudo make install` -Run the program +**Run the program** -`make run` +From the repo folder: `make run` +From anywhere on the computer: `solen` + +# Usage + +Upon startup, the user os prompted to enter their ŠkolaOnLine credentials. +If the login proccess is successfull, the preconfigured functions in `/bin/solen` +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. diff --git a/requirements.txt b/requirements.txt old mode 100644 new mode 100755 diff --git a/src/lib/__init__.py b/src/lib/__init__.py old mode 100644 new mode 100755 diff --git a/src/lib/grades.py b/src/lib/grades.py old mode 100644 new mode 100755 index fe7afb7..72eafdf --- a/src/lib/grades.py +++ b/src/lib/grades.py @@ -1,5 +1,6 @@ -from lib import networking as nw +from solenlib import networking as nw from bs4 import BeautifulSoup +import os not_login = 0 @@ -88,8 +89,8 @@ def show_grades(): print("N/A\n") import os - if os.path.exists("src/lib/data/grades.csv"): - os.remove("src/lib/data/grades.csv") + 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)): @@ -111,7 +112,7 @@ def grades_to_file(grades=None): # !!! make sure the format of the grades list i data = [] - with open("src/lib/data/grades.csv", mode="w", newline="") as f: + 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"] @@ -134,7 +135,7 @@ def grades_from_file() -> list: grades = [] try: - with open("src/lib/data/grades.csv", mode="r") as f: + with open(f"{os.environ['HOME']}/.local/lib/solendata/grades.csv", mode="r") as f: reader = csv.reader(f) rows = [] for row in reader: @@ -154,4 +155,4 @@ def grades_from_file() -> list: except FileNotFoundError: print("No grades.csv file found\n") - return grades \ No newline at end of file + return grades diff --git a/src/lib/networking.py b/src/lib/networking.py old mode 100644 new mode 100755 index 068392d..8d06cd0 --- a/src/lib/networking.py +++ b/src/lib/networking.py @@ -1,63 +1,102 @@ import requests +import hashlib from bs4 import BeautifulSoup +import os not_login = 0 F_not_anymore = 0 session = requests.Session() -def login(user: str, pwd: str) -> bool: - if is_connected() == 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": "" - } +def _read_shadow() -> str: + result = "" + with open(f"{os.environ['HOME']}/.local/lib/solendata/shadow", "r") as f: + for row in f: + result += row+";" - 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 + return result + +def _save_creds(user: str, pwd: str): + creds = _get_creds_hash(user, pwd) + + try: + with open(f"{os.environ['HOME']}/.local/lib/solendata/shadow", "a+") as f: + if not creds in _read_shadow(): + f.write(creds) + except FileNotFoundError: + print("No shadow file found") + +def _eval_creds(creds: str) -> bool: + shadow = _read_shadow() + + if creds in shadow: + return True + else: + return False + +def _get_creds_hash(user: str, pwd: str) -> str: + user_hash = hashlib.sha512(bytes(user, 'utf-8')).hexdigest() + pwd_hash = hashlib.sha512(bytes(pwd, 'utf-8')).hexdigest() + creds = user_hash+":"+pwd_hash + + return creds + +def login(user: str, pwd: str) -> bool: + if is_connected() == 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") + + if _eval_creds(_get_creds_hash(user, pwd)): + return True + else: + return False def is_connected() -> bool: - import socket + import socket - try: - host = socket.gethostbyname("one.one.one.one") - s = socket.create_connection((host, 80), 2) - s.close() - return True - except Exception: - pass - return False + try: + host = socket.gethostbyname("one.one.one.one") + 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" + base = "https://aplikace.skolaonline.cz" - if params is not None: - base += params + 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 "" + response = session.get(base) + if response.status_code == 200: + #print("Download successful") + return response + else: + print("Download unsuccessful") + return "" diff --git a/src/lib/schedule_tdy_tmr.py b/src/lib/schedule_tdy_tmr.py old mode 100644 new mode 100755 index 3d2d41e..ef452dc --- a/src/lib/schedule_tdy_tmr.py +++ b/src/lib/schedule_tdy_tmr.py @@ -1,5 +1,6 @@ -from lib import networking as nw +from solenlib import networking as nw from bs4 import BeautifulSoup +import os not_login = 0 @@ -10,10 +11,10 @@ def fetch_locally() -> list: not_login = 0 #print("Could not sign in (you may have entered invalid login credentials)") #print("Fetching the schedule for this week locally\n") - schedule_tdy_tmr = schedule_tdy_tmr_from_file() - return schedule_tdy_tmr + schedule = schedule_from_file() + return schedule -def get_schedule_tdy_tmr(url: str): +def get_schedule(url: str): try: html = nw.get_html_from(url) @@ -25,7 +26,7 @@ def get_schedule_tdy_tmr(url: str): rows_even = table.find_all("tr", class_="RowEven") rows = rows_odd + rows_even - schedule_tdy_tmr = [] + schedule = [] 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 and rows[i].find("td", class_="KuvSkolniAkceHodina") is None and rows[i].find("td", class_="DctInnerTableType10DataTD") is None: @@ -105,16 +106,16 @@ def get_schedule_tdy_tmr(url: str): break if F_continue == False: - schedule_tdy_tmr_row = { + schedule_row = { "DAY": day, "DATE": date, "SUBJECTS": subjects, "WHERES": wheres } - schedule_tdy_tmr.append(schedule_tdy_tmr_row) + schedule.append(schedule_row) - return schedule_tdy_tmr + return schedule except AttributeError: global not_login @@ -122,12 +123,12 @@ def get_schedule_tdy_tmr(url: str): schedule = fetch_locally() return schedule -def schedule_tdy_tmr_from_file_to_list(): +def schedule_from_file_to_list(): schedule = [] schedule_ = [] try: - with open("src/lib/data/schedule_tdy_tmr.csv") as f: + with open(f"{os.environ['HOME']}/.local/lib/solendata/schedule_tdy_tmr.csv") as f: import csv reader = csv.reader(f) for row in reader: @@ -160,7 +161,7 @@ def schedule_tdy_tmr_from_file_to_list(): return schedule def get_whitespace(key: str, idx=0) -> int: - schedule = schedule_tdy_tmr_from_file_to_list() + schedule = schedule_from_file_to_list() max_len = 0 if key == "DATE": @@ -176,27 +177,27 @@ def get_whitespace(key: str, idx=0) -> int: return max_len -def show_schedule_tdy_tmr(): +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_tdy_tmr("/SOL/App/Spolecne/KZZ010_RychlyPrehled.aspx") - schedule_tdy_tmr_to_file(schedule) + schedule = get_schedule("/SOL/App/Spolecne/KZZ010_RychlyPrehled.aspx") + schedule_to_file(schedule) else: - schedule = schedule_tdy_tmr_from_file() + schedule = schedule_from_file() print("") print("--------------------------") - print(" Dnesni a zitrejsi rozvrh ") + print(" Dnešní a zítřejší rozvrh ") print("--------------------------\n") if schedule == []: print("N/A\n") import os - if os.path.exists("src/lib/data/schedule_tdy_tmr.csv"): - os.remove("src/lib/data/schedule_tdy_tmr.csv") + if os.path.exists(f"{os.environ['HOME']}/.local/lib/solendata/schedule_tdy_tmr.csv"): + os.remove(f"{os.environ['HOME']}/.local/lib/solendata/schedule_tdy_tmr.csv") return for i in range(len(schedule)): @@ -223,16 +224,16 @@ def show_schedule_tdy_tmr(): print("") -def schedule_tdy_tmr_to_file(schedule=None): +def schedule_to_file(schedule=None): import csv if schedule is None: try: - schedule = get_schedule_tdy_tmr("/SOL/App/Spolecne/KZZ010_RychlyPrehled.aspx") + schedule = get_schedule("/SOL/App/Spolecne/KZZ010_RychlyPrehled.aspx") except Exception: print("An error occured, you probably can't access the internet right now") return - with open("src/lib/data/schedule_tdy_tmr.csv", mode="w", newline="") as f: + with open(f"{os.environ['HOME']}/.local/lib/solendata/schedule_tdy_tmr.csv", mode="w", newline="") as f: data = [] for i in range(len(schedule)): @@ -264,12 +265,12 @@ def schedule_tdy_tmr_to_file(schedule=None): for row in data: writer.writerow(row) -def schedule_tdy_tmr_from_file() -> list: +def schedule_from_file() -> list: import csv schedule = [] try: - with open("src/lib/data/schedule_tdy_tmr.csv", mode="r") as f: + with open(f"{os.environ['HOME']}/.local/lib/solendata/schedule_tdy_tmr.csv", mode="r") as f: reader = csv.reader(f) rows = [] for row in reader: diff --git a/src/lib/schedule_week.py b/src/lib/schedule_week.py old mode 100644 new mode 100755 index d0ac8ff..ff47b7f --- a/src/lib/schedule_week.py +++ b/src/lib/schedule_week.py @@ -1,5 +1,6 @@ -from lib import networking as nw +from solenlib import networking as nw from bs4 import BeautifulSoup +import os not_login = 0 @@ -10,28 +11,28 @@ def fetch_locally() -> list: not_login = 0 #print("Could not sign in (you may have entered invalid login credentials)") #print("Fetching the schedule for this week locally\n") - schedule_week = schedule_week_from_file() - return schedule_week + schedule = schedule_from_file() + return schedule -def sort_schedule_week(schedule_week: list[dict]) -> list[dict]: - sorted_schedule_week: list[dict] = [{}, {}, {}, {}, {}] +def sort_schedule(schedule: list[dict]) -> list[dict]: + sorted_schedule: list[dict] = [{}, {}, {}, {}, {}] - for i in range(len(schedule_week)): - if schedule_week[i]["DAY"] == "Po": - sorted_schedule_week[0] = schedule_week[i] - if schedule_week[i]["DAY"] == "Út": - sorted_schedule_week[1] = schedule_week[i] - if schedule_week[i]["DAY"] == "St": - sorted_schedule_week[2] = schedule_week[i] - if schedule_week[i]["DAY"] == "Čt": - sorted_schedule_week[3] = schedule_week[i] - if schedule_week[i]["DAY"] == "Pá": - sorted_schedule_week[4] = schedule_week[i] + for i in range(len(schedule)): + if schedule[i]["DAY"] == "Po": + sorted_schedule[0] = schedule[i] + if schedule[i]["DAY"] == "Út": + sorted_schedule[1] = schedule[i] + if schedule[i]["DAY"] == "St": + sorted_schedule[2] = schedule[i] + if schedule[i]["DAY"] == "Čt": + sorted_schedule[3] = schedule[i] + if schedule[i]["DAY"] == "Pá": + sorted_schedule[4] = schedule[i] - return sorted_schedule_week + return sorted_schedule -def get_schedule_week(url: str): +def get_schedule(url: str): try: html = nw.get_html_from(url) @@ -43,7 +44,7 @@ def get_schedule_week(url: str): rows_even = table.find_all("tr", class_="RowEven") rows = rows_odd + rows_even - schedule_week = [] + schedule = [] 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 and rows[i].find("td", class_="KuvSkolniAkceHodina") is None and rows[i].find("td", class_="DctInnerTableType10DataTD") is None: @@ -126,18 +127,18 @@ def get_schedule_week(url: str): break if F_continue == False: - schedule_week_row = { + schedule_row = { "DAY": day, "DATE": date, "SUBJECTS": subjects, "WHERES": wheres } - schedule_week.append(schedule_week_row) + schedule.append(schedule_row) - sorted_schedule_week = sort_schedule_week(schedule_week) + sorted_schedule = sort_schedule(schedule) - return sorted_schedule_week + return sorted_schedule except AttributeError: global not_login @@ -145,12 +146,12 @@ def get_schedule_week(url: str): schedule = fetch_locally() return schedule -def schedule_week_from_file_to_list(): +def schedule_from_file_to_list(): schedule = [] schedule_ = [] try: - with open("src/lib/data/schedule_week.csv") as f: + with open(f"{os.environ['HOME']}/.local/lib/solendata/schedule_week.csv") as f: import csv reader = csv.reader(f) for row in reader: @@ -183,7 +184,7 @@ def schedule_week_from_file_to_list(): return schedule def get_whitespace(key: str, idx=0) -> int: - schedule = schedule_week_from_file_to_list() + schedule = schedule_from_file_to_list() max_len = 0 if key == "DATE": @@ -199,14 +200,14 @@ def get_whitespace(key: str, idx=0) -> int: return max_len -def show_schedule_week(): +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_week("/SOL/App/Kalendar/KZK001_KalendarTyden.aspx") - schedule_week_to_file(schedule) + schedule = get_schedule("/SOL/App/Kalendar/KZK001_KalendarTyden.aspx") + schedule_to_file(schedule) else: - schedule = schedule_week_from_file() + schedule = schedule_from_file() print("") @@ -218,8 +219,8 @@ def show_schedule_week(): print("N/A\n") import os - if os.path.exists("src/lib/data/schedule_week.csv"): - os.remove("src/lib/data/schedule_week.csv") + if os.path.exists(f"{os.environ['HOME']}/.local/lib/solendata/schedule_week.csv"): + os.remove(f"{os.environ['HOME']}/.local/lib/solendata/schedule_week.csv") return for i in range(len(schedule)): @@ -246,16 +247,16 @@ def show_schedule_week(): print("") -def schedule_week_to_file(schedule=None): +def schedule_to_file(schedule=None): import csv if schedule is None: try: - schedule = get_schedule_week("/SOL/App/Kalendar/KZK001_KalendarTyden.aspx") + 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("src/lib/data/schedule_week.csv", mode="w", newline="") as f: + with open(f"{os.environ['HOME']}/.local/lib/solendata/schedule_week.csv", mode="w", newline="") as f: data = [] for i in range(len(schedule)): @@ -287,12 +288,12 @@ def schedule_week_to_file(schedule=None): for row in data: writer.writerow(row) -def schedule_week_from_file() -> list: +def schedule_from_file() -> list: import csv schedule = [] try: - with open("src/lib/data/schedule_week.csv", mode="r") as f: + with open(f"{os.environ['HOME']}/.local/lib/solendata/schedule_week.csv", mode="r") as f: reader = csv.reader(f) rows = [] for row in reader: diff --git a/src/main.py b/src/main.py old mode 100644 new mode 100755 index a514845..59f9f54 --- a/src/main.py +++ b/src/main.py @@ -1,30 +1,23 @@ -from lib import grades as gr -from lib import networking as nw -from lib import schedule_week as sw -from lib import schedule_tdy_tmr as st +#!/bin/python3 + +from solenlib import grades as gr +from solenlib import networking as nw +from solenlib import schedule_week as sw +from solenlib import schedule_tdy_tmr as st +import sys +import getpass if __name__ == "__main__": user = str(input("Zadejte přihlašovací jméno: ")) - pwd = str(input("Zadejte heslo: ")) + try: + pwd = str(getpass.getpass("Zadejte heslo: ")) + except: + pwd = str(input("Zadejte heslo: ")) - - if nw.is_connected() == True: - nw.login(user, pwd) - - - #sw.get_schedule_week("/SOL/App/Kalendar/KZK001_KalendarTyden.aspx") - #sw.schedule_week_to_file() - #sw.schedule_week_from_file() - sw.show_schedule_week() - - - #gr.get_grades("/SOL/App/Hodnoceni/KZH001_HodnVypisStud.aspx") - #gr.grades_to_file() - #gr.grades_from_file() - gr.show_grades() - - - #st.get_schedule("/SOL/App/Spolecne/KZZ010_RychlyPrehled.aspx") - #st.schedule_tdy_tmr_to_file() - #st.schedule_tdy_tmr_from_file() - st.show_schedule_tdy_tmr() + if not nw.login(user, pwd): + print("LOGIN UNSUCCESSFULL") + sys.exit(1) + else: + sw.show_schedule() + st.show_schedule() + gr.show_grades()