Wed Jun 21 15:43:39 CEST 2023

This commit is contained in:
mnjx
2023-06-21 17:52:01 +02:00
12 changed files with 290 additions and 159 deletions
+8
View File
@@ -0,0 +1,8 @@
#!/bin/bash
if command -v solen > /dev/null
then
solen
else
python3 "./src/main.py"
fi
+9
View File
@@ -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
+2 -3
View File
@@ -1,16 +1,15 @@
SHELL=/bin/bash SHELL=/bin/bash
install: install:
python3 -m pip install -r requirements.txt python3 -m pip install -r requirements.txt --break-system-packages
./.config/mkdirs.sh ./.config/mkdirs.sh
cp ./src/main.py /bin/solen cp ./src/main.py /bin/solen
chmod 774 /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/*.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: run:
./.config/launch.sh ./.config/launch.sh
+20 -8
View File
@@ -1,4 +1,4 @@
# SOL Enhanced # Solen (ŠOL Enhanced)
An enhanced version of the ŠkolaOnLine (ŠOL) online school system. 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 # Features
+ Shows the schedule for the current week + Shows the schedule for the current week
+ Shows all grades and their arithmetic average (not accounting for weight) + Shows all grades and their weighted average
+ Works offline (when the internet has been accessed at least once before) + Works offline (when the internet has been accessed at least once before)
# Installation # Installation
**Clone the repo** **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** **Navigate into the repo directory**
@@ -31,8 +31,9 @@ Insiration was taken from [@JakubAndrysek](https://github.com/JakubAndrysek)'s [
**Run the program** **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 # 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 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 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`). 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 |
-1
View File
@@ -1 +0,0 @@
-1
View File
@@ -1 +0,0 @@
-1
View File
@@ -1 +0,0 @@
+130 -118
View File
@@ -5,154 +5,166 @@ import os
not_login = 0 not_login = 0
def fetch_locally() -> list: def fetch_locally() -> list:
global not_login global not_login
if not_login == 1: if not_login == 1:
not_login = 0 not_login = 0
#print("Could not sign in (you may have entered invalid login credentials)") #print("Could not sign in (you may have entered invalid login credentials)")
#print("Fetching grades locally\n") #print("Fetching grades locally\n")
grades = grades_from_file() grades = grades_from_file()
return grades return grades
"""
# deprecated
def get_avg(grades: list) -> str: def get_avg(grades: list) -> str:
if len(grades) < 1: if len(grades) < 1:
return "" return ""
sum_ = 0 sum_ = 0
for i in grades: for i in grades:
sum_ += int(i) sum_ += int(i)
avg = round(sum_ / len(grades), 2) avg = round(sum_ / len(grades), 2)
avg = str(avg) avg = str(avg)
if len(avg) == 3: if len(avg) == 3:
avg += "0" avg += "0"
return avg return avg
"""
def get_grades(url: str): def get_grades(url: str):
try: try:
html = nw.get_html_from(url) 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") rows = soup.find("table", id="G_ctl00xmainxCCADynamicUWGrid").find_all("tr")
for i in range(3): for i in range(3):
r = soup.find("table", id="G_ctl00xmainxCCADynamicUWGrid").find("tr") r = soup.find("table", id="G_ctl00xmainxCCADynamicUWGrid").find("tr")
r.decompose() r.decompose()
grades = [] grades = []
for i in range(1, len(rows)): for i in range(1, len(rows)):
if rows[i].find("td") is not None: if rows[i].find("td") is not None:
subject = rows[i].find("td").text subject = rows[i].find("td").text
temp_dict = {} temp_dict = {}
temp_dict["SUBJECT"] = subject temp_dict["SUBJECT"] = subject
cell = rows[i].find_all("span", class_="VHVysl") cell = rows[i].find_all("span", class_="VHVysl")
temp = [] temp = []
for j in range(len(cell)): for j in range(len(cell)):
temp.append(cell[j].text) temp.append(cell[j].text)
temp_dict["GRADES"] = temp temp_dict["GRADES"] = temp
grades.append(temp_dict) avg = rows[i].find("td", class_="VHCelkPrumer").text
temp_dict["AVG"] = avg
return grades grades.append(temp_dict)
except AttributeError:
global not_login
not_login = 1 return grades
grades = fetch_locally() except AttributeError:
return grades global not_login
def show_grades(): not_login = 1
if nw.is_connected() == True: grades = fetch_locally()
grades = get_grades("/SOL/App/Hodnoceni/KZH001_HodnVypisStud.aspx") return grades
grades_to_file(grades)
else:
grades = grades_from_file()
whitespace = "" def show_grades(local=False):
max_len_of_subject = 0 if local == False:
subjects = [grades[j]["SUBJECT"] for j in range(len(grades))] if nw.is_connected() == True:
for i in subjects: grades = get_grades("/SOL/App/Hodnoceni/KZH001_HodnVypisStud.aspx")
if len(i) > max_len_of_subject: grades_to_file(grades)
max_len_of_subject = len(i) else:
max_len_of_subject += 1 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("")
print(" Známky ")
print("--------\n")
if grades == []: print("--------")
print("N/A\n") print(" Známky ")
import os print("--------\n")
if os.path.exists(f"{os.environ['HOME']}/.local/lib/solendata/grades.csv"): if grades == []:
os.remove(f"{os.environ['HOME']}/.local/lib/solendata/grades.csv") print("N/A\n")
return import os
for i in range(len(grades)): if os.path.exists(f"{os.environ['HOME']}/.local/lib/solendata/grades.csv"):
if grades[i]["GRADES"] != []: os.remove(f"{os.environ['HOME']}/.local/lib/solendata/grades.csv")
whitespace = " " * (max_len_of_subject - len(grades[i]["SUBJECT"])) return
print(grades[i]["SUBJECT"] + whitespace + "|", end=" ")
print(get_avg(grades[i]["GRADES"]), "|", end=" ") for i in range(len(grades)):
print("".join([grades[i]["GRADES"][j]+" " for j in range(len(grades[i]["GRADES"]))]), end=" ") if grades[i]["GRADES"] != []:
print("") 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 !!! def grades_to_file(grades=None): # !!! make sure the format of the grades list is saved correctly !!!
import csv import csv
if grades is None: if grades is None:
try: try:
grades = get_grades("/SOL/App/Hodnoceni/KZH001_HodnVypisStud.aspx") grades = get_grades("/SOL/App/Hodnoceni/KZH001_HodnVypisStud.aspx")
except Exception: except Exception:
print("An error occured, you probably can't access the internet right now") print("An error occured, you probably can't access the internet right now")
return return
data = [] data = []
with open(f"{os.environ['HOME']}/.local/lib/solendata/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)): for i in range(len(grades)):
temp = {} temp = {}
temp["SUBJECT"] = grades[i]["SUBJECT"] temp["SUBJECT"] = grades[i]["SUBJECT"]
temp_grades = [] temp["AVG"] = grades[i]["AVG"]
for j in range(len(grades[i]["GRADES"])): temp_grades = []
temp_grades.append(grades[i]["GRADES"][j]) for j in range(len(grades[i]["GRADES"])):
temp["GRADES"] = temp_grades temp_grades.append(grades[i]["GRADES"][j])
data.append(temp) temp["GRADES"] = temp_grades
data.append(temp)
writer = csv.writer(f) writer = csv.writer(f)
for row in data: for row in data:
_row = [] _row = []
_row.append(row["SUBJECT"]) _row.append(row["SUBJECT"])
for i in row["GRADES"]: _row.append(row["AVG"])
_row.append(i) for i in row["GRADES"]:
writer.writerow(_row) _row.append(i)
writer.writerow(_row)
def grades_from_file() -> list: def grades_from_file() -> list:
import csv import csv
grades = [] grades = []
try: try:
with open(f"{os.environ['HOME']}/.local/lib/solendata/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) reader = csv.reader(f)
rows = [] rows = []
for row in reader: for row in reader:
rows.append(row) rows.append(row)
for row in range(len(rows)): for row in range(len(rows)):
temp = {} temp = {}
temp["SUBJECT"] = rows[row][0] temp["SUBJECT"] = rows[row][0]
temp["AVG"] = rows[row][1]
temp_lst = []
for i in range(1, len(rows[row])): temp_lst = []
temp_lst.append(rows[row][i]) for i in range(2, len(rows[row])):
temp["GRADES"] = temp_lst temp_lst.append(rows[row][i])
temp["GRADES"] = temp_lst
grades.append(temp) grades.append(temp)
except FileNotFoundError: except FileNotFoundError:
print("No grades.csv file found\n") print("No grades.csv file found\n")
return grades return grades
+2 -2
View File
@@ -41,7 +41,7 @@ def _get_creds_hash(user: str, pwd: str) -> str:
return creds return creds
def login(user: str, pwd: str) -> bool: def login(user: str, pwd: str) -> bool:
if is_connected() == True: if is_connected():
url: str = "https://aplikace.skolaonline.cz/SOL/Prihlaseni.aspx" url: str = "https://aplikace.skolaonline.cz/SOL/Prihlaseni.aspx"
data = { data = {
"__EVENTTARGET": "dnn$ctr994$SOLLogin$btnODeslat", "__EVENTTARGET": "dnn$ctr994$SOLLogin$btnODeslat",
@@ -83,7 +83,7 @@ def is_connected() -> bool:
s = socket.create_connection((host, 80), 2) s = socket.create_connection((host, 80), 2)
s.close() s.close()
return True return True
except Exception: except:
pass pass
return False return False
+10 -7
View File
@@ -85,7 +85,7 @@ def get_schedule(url: str):
temp.append(" - ") temp.append(" - ")
wheres = temp wheres = temp
for j in range(len(cells)): for j in range(len(cells)):
if cells[j].get("colspan") is not None: if cells[j].get("colspan") is not None:
if int(cells[j].get("colspan")) == 2: if int(cells[j].get("colspan")) == 2:
try: try:
@@ -177,12 +177,15 @@ def get_whitespace(key: str, idx=0) -> int:
return max_len return max_len
def show_schedule(): def show_schedule(local=False):
# if there is internet access, load stuff from the web if local == False:
# if not, load the schedule.csv file # if there is internet access, load stuff from the web
if nw.is_connected() == True: # if not, load the schedule.csv file
schedule = get_schedule("/SOL/App/Spolecne/KZZ010_RychlyPrehled.aspx") if nw.is_connected() == True:
schedule_to_file(schedule) schedule = get_schedule("/SOL/App/Spolecne/KZZ010_RychlyPrehled.aspx")
schedule_to_file(schedule)
else:
schedule = schedule_from_file()
else: else:
schedule = schedule_from_file() schedule = schedule_from_file()
+9 -6
View File
@@ -200,12 +200,15 @@ def get_whitespace(key: str, idx=0) -> int:
return max_len return max_len
def show_schedule(): def show_schedule(local=False):
# if there is internet access, load stuff from the web if local == False:
# if not, load the schedule.csv file # if there is internet access, load stuff from the web
if nw.is_connected() == True: # if not, load the schedule.csv file
schedule = get_schedule("/SOL/App/Kalendar/KZK001_KalendarTyden.aspx") if nw.is_connected() == True:
schedule_to_file(schedule) schedule = get_schedule("/SOL/App/Kalendar/KZK001_KalendarTyden.aspx")
schedule_to_file(schedule)
else:
schedule = schedule_from_file()
else: else:
schedule = schedule_from_file() schedule = schedule_from_file()
+100 -12
View File
@@ -6,18 +6,106 @@ from solenlib import schedule_week as sw
from solenlib import schedule_tdy_tmr as st from solenlib import schedule_tdy_tmr as st
import sys import sys
import getpass import getpass
import argparse
import datetime
if __name__ == "__main__": if __name__ == "__main__":
user = str(input("Zadejte přihlašovací jméno: ")) parser = argparse.ArgumentParser(prog="ŠOLEn", description="ŠOLEn, an enhanced version of the ŠkolaOnLine online school system")
try:
pwd = str(getpass.getpass("Zadejte heslo: ")) parser.add_argument("-t", "--today-tomorrow", action="store_true", help="Show the schedule for today and tomorrow")
except: parser.add_argument("-w", "--week", action="store_true", help="Show the schedule for this week")
pwd = str(input("Zadejte heslo: ")) 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): if not nw.login(user, pwd):
print("LOGIN UNSUCCESSFULL") print("Login unsuccessfull")
sys.exit(1) sys.exit(1)
else: else:
sw.show_schedule() try:
st.show_schedule() if args.week:
gr.show_grades() 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)