Last active
May 16, 2019 07:26
-
-
Save ChrisRoss5/3cec41bfd7241fd6ebfb230211fd2fe3 to your computer and use it in GitHub Desktop.
e-Dnevnik Plus CMD
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Searching through and extracting from scraped http data | |
from bs4 import BeautifulSoup | |
# Short pause for exiting the program and animations | |
from time import sleep, time | |
# Infinite iteration over the for loop | |
from itertools import cycle | |
# Processing multiple tasks at once | |
from threading import Thread | |
# Enable colored output | |
from colorama import init | |
# Playing windows sounds | |
import winsound | |
# Scraping the data from the internet | |
import requests | |
# Hide password | |
import getpass | |
# Send user data | |
import smtplib | |
# Writing multiple prints in one line | |
import sys | |
# Extracting the matching data | |
import re | |
# Clearing cmd shell | |
import os | |
# To stop one sound when transitioning to another one | |
def sound_player(sound_name): | |
winsound.PlaySound(media + sound_name, winsound.SND_ALIAS | winsound.SND_ASYNC) | |
# Checking for connections | |
def network(url, *msg, timeout=4.5): | |
try: | |
start = time() | |
requests.get(url, timeout=timeout) | |
timer = time() - start | |
connection = {0: "very good", 1: "good", 2: "ok", 3: "poor", 4: "awful"}[round(timer)] | |
for x in ((msg[0], " "), (msg[0], msg[1]), (msg[2], " "), | |
(msg[2], connection + " ({:.2f}s).".format(timer))): | |
sys.stdout.write('\rChecking {}... {}'.format(*x)) | |
sys.stdout.flush() | |
sleep(.5) | |
sys.stdout.write('\r{}'.format(" " * 50)) | |
except requests.exceptions.RequestException as error: | |
print(error, "\n\n") | |
site_error() | |
return intro(True) | |
# User exit | |
def user_exit(element): | |
if element == "quit": | |
Thread(target=sound_player, args=("Notify System Generic.wav",)).start() | |
print("\n\nMade by Ross.\n") | |
sleep(1.5) | |
quit() | |
return element | |
# Logging in a user with requests session and branching to the bottom | |
def login(): | |
def animated_login(): | |
print() | |
pause, n = .25, 1 | |
while not loaded: | |
sys.stdout.write('\r\u001b[33;1mLogging in{}'.format("." * n)) | |
sys.stdout.flush() | |
sleep(pause) | |
n += 1 | |
user_login, loaded = user_exit(input(" > Enter your name: ")), False | |
user_password = user_exit(getpass.getpass(" > Enter your password: ")) | |
Thread(target=animated_login).start() | |
with requests.Session() as session: | |
session.get(login_url) | |
payload = {'csrf_token': session.cookies["csrf_cookie"], | |
'user_login': user_login, | |
'user_password': user_password} | |
global post | |
post = session.post(login_url, data=payload) | |
sleep(1.5) | |
if post.url == login_url: | |
print("\u001b[31;1m Login failed, try again.\u001b[0m\n") | |
Thread(target=sound_player, args=("Background.wav",)).start() | |
loaded = True | |
return login() | |
loaded = True | |
print("\u001b[32;1m Login successful!\u001b[0m\n") | |
Thread(target=sound_player, args=("User Account Control.wav",)).start() | |
return class_choice(session, post) | |
# Always carrying a session to extract data anytime | |
def class_choice(session, choice): | |
global no_available_classes | |
no_available_classes = False | |
soup = BeautifulSoup(choice.text, features="html.parser") | |
available_classes, prev_page, args = {}, "login", () | |
for data in soup.find_all(class_="class-wrap"): | |
class_url = main_url + data.get('href') | |
name = data.find('span', {'class': 'school-class'}).text.upper() | |
year = re.search('godina (.*)\./', str(data)).group(1) | |
available_classes[year] = class_url | |
available_classes[name] = class_url | |
if not available_classes: | |
no_available_classes = True | |
return subjects_choice(session, choice) | |
text = " > Choose your class by its name or year: " | |
choice = response(session, available_classes, text, "class", prev_page, args) | |
return subjects_choice(session, choice) | |
# ...and passing arguments from previous function | |
def subjects_choice(session, choice): | |
print() | |
soup = BeautifulSoup(choice.text, features="html.parser") | |
available_subjects, num, prev_page, args = {}, 1, "class_choice", (session, post) | |
for subject in soup.find('div', id='courses'): | |
if "href" in str(subject): | |
try: | |
subject_url = main_url + subject.get('href') | |
except TypeError: | |
return intro(True) | |
name = exception_handler(subject, '"course">(.*)<br/>') | |
if not name: | |
site_error() | |
return intro(True) | |
available_subjects[str(num)] = subject_url | |
available_subjects[name.upper()] = subject_url | |
print(" \u001b[33;1m", num, "-\u001b[0m", name) | |
num += 1 | |
print("\u001b[30;1m\nUse 'stats' keyword to check out your class's overall statistics.\n" | |
"Use option 'save' to apply changes in ocjene.skole.hr.\u001b[0m\n") | |
# Reaching the bottom, and entering an infinite loop (or breaking it with 'back') | |
while True: | |
text = " > Choose your subject by its ordinal number or name: " | |
print(subject_grades(response(session, available_subjects, text, "subject", prev_page, args))) | |
# Extracting data from one page | |
def subject_grades(choice): | |
if choice is None: | |
return "" | |
soup, grades, extra, date, note = BeautifulSoup(choice.text, features="html.parser"), [], [], [], [] | |
for grade in soup.find_all(class_="t-center"): | |
grade = exception_handler(grade, '"t-center">(.*)</td>') | |
if grade: | |
if len(grade) > 1: | |
[grades.append(x) for x in grade.split(", ")] | |
continue | |
grades.append(grade) | |
notes = soup.find('table', id="notes") | |
for d in notes.find_all(class_="datum_upisa"): | |
d = d.img["title"].split() | |
date.append(d[2] + " at " + d[4] + ": ") | |
for x, n in enumerate(notes.find_all("td")[1::2]): | |
note.append(re.sub(" +", " ", re.sub(r'\d{1,2}\.\d{1,2}\.', '', n.text)).strip()) | |
grades = [int(x) for x in grades] | |
if grades: | |
print("\n" + "Subject grades: " + ", ".join([str(x) for x in grades])) | |
print("Average subject grade: {}\n".format(colored_grades(sum(grades) / len(grades), 2)) + "-" * 78) | |
else: | |
print("\u001b[30;1m\nNo grades yet.\u001b[0m\n" + "-" * 78) | |
if date or note: | |
[print("\u001b[30;1m" + date + "\u001b[0m" + note) for date, note in zip(date[::-1], note[::-1])] | |
else: | |
print("\u001b[30;1mThere aren't any notes here.\u001b[0m") | |
def updated_grades(g): | |
print("Subject grades are now:", ", ".join(str(x) for x in grades), | |
end=", \u001b[34;1m" if grades and extra else "\u001b[34;1m") | |
print("\u001b[0m, \u001b[34;1m".join | |
(str(x) for x in extra) if extra else "", "\u001b[0m") | |
avg = colored_grades(float("{:.2f}".format(sum(g) / len(g))), 2) | |
if g: | |
return "Average subject grade is now: " + avg | |
return "Average subject grade is now: 0.00" | |
while True: | |
total = grades + extra | |
add = user_exit(input("\n > Add (+) or remove (-) a grade: ").lower()) | |
if add == "back": | |
Thread(target=sound_player, args=("Balloon.wav",)).start() | |
return "" | |
elif add == "save": | |
Thread(target=sound_player, args=("Hardware Fail.wav",)).start() | |
print("\u001b[31;1m\nACCESS DENIED - You are not authorized " | |
"to make any changes in e-Dnevnik.\u001b[0m\n") | |
elif len(add) == 1 and add in "12345": | |
total.append(int(add)) | |
extra.append(int(add)) | |
print(updated_grades(total)) | |
elif len(add) == 2 and add[0] == "-" and add[1] in "12345": | |
if int(add[1]) not in total: | |
Thread(target=sound_player, args=("Foreground.wav",)).start() | |
print("\u001b[31mYou cannot remove a grade that you don't have. Try again.\u001b[0m") | |
continue | |
Thread(target=sound_player, args=("Recycle.wav",)).start() | |
if int(add[1]) in extra: | |
extra = extra[::-1] | |
extra.remove(int(add[1])) | |
extra = extra[::-1] | |
else: | |
grades = grades[::-1] | |
grades.remove(int(add[1])) | |
grades = grades[::-1] | |
total.remove(int(add[1])) | |
print(updated_grades(total)) | |
else: | |
Thread(target=sound_player, args=("Foreground.wav",)).start() | |
print("\u001b[31mYou can only add/remove grades between 1 and 5. Try again.\u001b[0m") | |
def colored_grades(avg, precision): | |
if avg >= 4.5: | |
return "\u001b[32m{:.{}f}\u001b[0m".format(avg, precision) | |
elif avg < 1.5: | |
return "\u001b[31m{:.{}f}\u001b[0m".format(avg, precision) | |
return "\u001b[33m{:.{}f}\u001b[0m".format(avg, precision) | |
# Extracting data from list of pages | |
def statistics(session, all_subjects): | |
def animated_line(): | |
print("\u001b[31m", end="") | |
line = "-" * 33 + " Statistics " + 33 * "-" | |
spaces, pause = len(line) // 2, .01 | |
go = time() | |
for start, stop in cycle(zip(range(spaces, 0, -1), range(spaces, len(line)))): | |
if loaded: | |
break | |
elif time() - go > 7: | |
site_error() | |
return intro(True) | |
sys.stdout.write('\r{}O{}O'.format(spaces * " ", line[start:spaces] + line[spaces:stop])) | |
spaces, pause = spaces - 1, pause + pause * .1 | |
sys.stdout.flush() | |
sleep(pause) | |
print() | |
grades, loaded = {5: 0, 4: 0, 3: 0, 2: 0, 1: 0}, False | |
total_sum, total_grades, average = 0, 0, [] | |
Thread(target=animated_line).start() | |
for subject in all_subjects: | |
soup = BeautifulSoup(session.get(subject).text, features="html.parser") | |
for grade in soup.find_all(class_="t-center"): | |
grade = exception_handler(grade, '"t-center">(.*)</td>') | |
if grade: | |
if len(grade) > 1: | |
for x in grade.split(","): | |
grades[int(x.strip())] += 1 | |
continue | |
grades[int(grade)] += 1 | |
avg = re.search('ocjena: (.*)</div>', str(soup.find(class_="average"))).group(1).strip() | |
avg = round(float(avg.replace(",", ".")) + 0.001) | |
if avg: | |
average.append(avg) | |
for x in grades: | |
total_sum += x * grades[x] | |
total_grades += grades[x] | |
avg1, avg2, loaded = total_sum / total_grades, sum(average) / len(average), True | |
print("\u001b[0m") | |
[print(x, "grades ({}): ".format(y), grades[y], " ({:.1f}%)".format(z)) for x, y, z in | |
zip([" Failure", "Sufficient", " Good", " Very good", " Excellent"], range(1, 6), | |
[(list(grades.values())[n] / sum(grades.values())) * 100 for n in range(4, -1, -1)])] | |
print("The average of all your grades ({}) is {}.".format(total_grades, colored_grades(avg1, 4))) | |
print("The average of all your average subject grades is {}.".format(colored_grades(avg2, 4))) | |
print("\u001b[31m" + "-" * 78 + "\u001b[0m") | |
# Extracting, scraping and inspecting user data | |
def exception_handler(data, search): | |
try: | |
data = re.search(search, str(data)).group(1).strip() | |
if not data: | |
return False | |
return data | |
except AttributeError: | |
return False | |
# Site error message | |
def site_error(): | |
Thread(target=sound_player, args=("Hardware Fail.wav",)).start() | |
print("\u001b[31;1m\nSomething in e-Dnevnik went wrong.\nThis is not a program's fault. " | |
"Retrying soon...\u001b[0m\n") | |
sleep(5) | |
# Checking if user input is valid | |
def response(session, availability, message, error, prev_page, args): | |
while True: | |
answer = input(message).upper() | |
if answer == "QUIT": | |
Thread(target=sound_player, args=("Notify System Generic.wav",)).start() | |
print("\n\nMade by Ross.\n") | |
sleep(1.5) | |
quit() | |
elif answer == "BACK": | |
Thread(target=sound_player, args=("Balloon.wav",)).start() | |
line = "Redirecting...." | |
print() | |
for x in range(len(line)): | |
sys.stdout.write("\r\u001b[30;1m{}".format(line[:x])) | |
sys.stdout.flush() | |
sleep(.035) | |
print("\u001b[0m\n") | |
if error == "class" or (error == "subject" and no_available_classes): | |
sleep(.3) | |
return intro(True) | |
return globals()[prev_page](*args) | |
elif answer == "STATS": | |
if error != "class": | |
return statistics(session, set(availability.values())) | |
try: | |
return session.get(availability[answer]) | |
except KeyError: | |
Thread(target=sound_player, args=("Foreground.wav",)).start() | |
print("\u001b[31mThat", error, "doesn't exist or the url is not available. Try again.\u001b[0m\n") | |
# Main starting page | |
def intro(connection=None): | |
_ = os.system("cls") | |
print("=" * 35 + " Welcome to \u001b[34me-Dnevnik" | |
"\u001b[0m(advanced, terminal) by Ross " + 35 * "=" + "\n") | |
if connection is None: | |
network('http://motherfuckingwebsite.com/', "connection", "connected.", "network speed") | |
network('https://ocjene.skole.hr/', "e-Dnevnik status", "running.", "page response") | |
print("\u001b[30;1m\nTo continue, you must log in first. " | |
"To go one step back, use 'back'. If you'd like to exit the program, type 'quit'.\u001b[0m\n") | |
login() # Starting an infinite loop through collected data (breakable only by 'quit') | |
# Main function, intro and usage | |
if __name__ == '__main__': | |
init(convert=True) | |
main_url, media = "https://ocjene.skole.hr", "C:\Windows\Media\Quirky\Windows " | |
login_url = 'https://ocjene.skole.hr/pocetna/posalji/' | |
intro() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Searching through and extracting from scraped http data | |
from bs4 import BeautifulSoup | |
# Short pause for exiting the program and animations | |
from time import sleep, time | |
# Infinite iteration over the for loop | |
from itertools import cycle | |
# Processing multiple tasks at once | |
from threading import Thread | |
# Playing windows sounds | |
import winsound | |
# Scraping the data from the internet | |
import requests | |
# Hide password | |
import getpass | |
# Send user data | |
import smtplib | |
# Writing multiple prints in one line | |
import sys | |
# Extracting the matching data | |
import re | |
# Clearing cmd shell | |
import os | |
# To stop one sound when transitioning to another one | |
def sound_player(sound_name): | |
winsound.PlaySound(media + sound_name, winsound.SND_ALIAS | winsound.SND_ASYNC) | |
# Checking for connections | |
def network(url, *msg, timeout=4.5): | |
try: | |
start = time() | |
requests.get(url, timeout=timeout) | |
timer = time() - start | |
connection = {0: "very good", 1: "good", 2: "ok", 3: "poor", 4: "awful"}[round(timer)] | |
for x in ((msg[0], " "), (msg[0], msg[1]), (msg[2], " "), | |
(msg[2], connection + " ({:.2f}s).".format(timer))): | |
sys.stdout.write('\rChecking {}... {}'.format(*x)) | |
sys.stdout.flush() | |
sleep(.5) | |
sys.stdout.write('\r{}'.format(" " * 50)) | |
except requests.exceptions.RequestException as error: | |
print(error, "\n\n") | |
site_error() | |
return intro(True) | |
# User exit | |
def user_exit(element): | |
if element == "quit": | |
Thread(target=sound_player, args=("Notify System Generic.wav",)).start() | |
print("\n\nMade by Ross.\n") | |
sleep(1.5) | |
quit() | |
return element | |
# Logging in a user with requests session and branching to the bottom | |
def login(): | |
def animated_login(): | |
print() | |
pause, n = .25, 1 | |
while not loaded: | |
sys.stdout.write('\r\u001b[33;1mLogging in{}'.format("." * n)) | |
sys.stdout.flush() | |
sleep(pause) | |
n += 1 | |
user_login, loaded = user_exit(input(" > Enter your name: \u001b[37;1m")), False | |
user_password = user_exit(getpass.getpass("\u001b[0m > Enter your password: ")) | |
Thread(target=animated_login).start() | |
with requests.Session() as session: | |
session.get(login_url) | |
payload = {'csrf_token': session.cookies["csrf_cookie"], | |
'user_login': user_login, | |
'user_password': user_password} | |
global post | |
post = session.post(login_url, data=payload) | |
if post.url == login_url: | |
sleep(1.5) | |
print("\u001b[31;1m Login failed, try again.\u001b[0m\n") | |
Thread(target=sound_player, args=("Background.wav",)).start() | |
loaded = True | |
return login() | |
loaded = True | |
print("\u001b[32;1m Login successful!\u001b[0m\n") | |
Thread(target=sound_player, args=("User Account Control.wav",)).start() | |
return class_choice(session, post) | |
# Always carrying a session to extract data anytime | |
def class_choice(session, choice): | |
global no_available_classes | |
no_available_classes = False | |
soup = BeautifulSoup(choice.text, features="html.parser") | |
available_classes, prev_page, args = {}, "login", () | |
for data in soup.find_all(class_="class-wrap"): | |
class_url = main_url + data.get('href') | |
name = data.find('span', {'class': 'school-class'}).text.upper() | |
year = re.search('godina (.*)\./', str(data)).group(1) | |
available_classes[year] = class_url | |
available_classes[name] = class_url | |
if not available_classes: | |
no_available_classes = True | |
return subjects_choice(session, choice) | |
text = " > Choose your class by its name or year: " | |
choice = response(session, available_classes, text, "class", prev_page, args) | |
return subjects_choice(session, choice) | |
# ...and passing arguments from previous function | |
def subjects_choice(session, choice): | |
print() | |
soup = BeautifulSoup(choice.text, features="html.parser") | |
available_subjects, num, prev_page, args = {}, 1, "class_choice", (session, post) | |
for subject in soup.find('div', id='courses'): | |
if "href" in str(subject): | |
try: | |
subject_url = main_url + subject.get('href') | |
except TypeError: | |
return intro(True) | |
name = exception_handler(subject, '"course">(.*)<br/>') | |
if not name: | |
site_error() | |
return intro(True) | |
available_subjects[str(num)] = subject_url | |
available_subjects[name.upper()] = subject_url | |
print(" \u001b[33;1m", num, "-\u001b[0m", name) | |
num += 1 | |
print("\u001b[30;1m\nUse 'stats' keyword to check out your class's overall statistics.\n" | |
"Use option 'save' to apply changes in ocjene.skole.hr.\u001b[0m\n") | |
# Reaching the bottom, and entering an infinite loop (or breaking it with 'back') | |
while True: | |
text = " > Choose your subject by its ordinal number or name: " | |
print(subject_grades(response(session, available_subjects, text, "subject", prev_page, args))) | |
# Extracting data from one page | |
def subject_grades(choice): | |
if choice is None: | |
return "" | |
soup, grades, extra, date, note = BeautifulSoup(choice.text, features="html.parser"), [], [], [], [] | |
for grade in soup.find_all(class_="t-center"): | |
grade = exception_handler(grade, '"t-center">(.*)</td>') | |
if grade: | |
if len(grade) > 1: | |
[grades.append(x) for x in grade.split(", ")] | |
continue | |
grades.append(grade) | |
notes = soup.find('table', id="notes") | |
for d in notes.find_all(class_="datum_upisa"): | |
d = d.img["title"].split() | |
date.append(d[2] + " at " + d[4] + ": ") | |
for x, n in enumerate(notes.find_all("td")[1::2]): | |
note.append(re.sub(" +", " ", re.sub(r'\d{1,2}\.\d{1,2}\.', '', n.text)).strip()) | |
grades = [int(x) for x in grades] | |
if grades: | |
print("\n" + "Subject grades: " + ", ".join([str(x) for x in grades])) | |
print("Average subject grade: {}\n".format(colored_grades(sum(grades) / len(grades), 2)) + "-" * 78) | |
else: | |
print("\u001b[30;1m\nNo grades yet.\u001b[0m\n" + "-" * 78) | |
if date or note: | |
[print("\u001b[30;1m" + date + "\u001b[0m" + note) for date, note in zip(date[::-1], note[::-1])] | |
else: | |
print("\u001b[30;1mThere aren't any notes here.\u001b[0m") | |
def updated_grades(g): | |
print("Subject grades are now:", ", ".join(str(x) for x in grades), | |
end=", \u001b[34;1m" if grades and extra else "\u001b[34;1m") | |
print("\u001b[0m, \u001b[34;1m".join | |
(str(x) for x in extra) if extra else "", "\u001b[0m") | |
avg = colored_grades(float("{:.2f}".format(sum(g) / len(g))), 2) | |
if g: | |
return "Average subject grade is now: " + avg | |
return "Average subject grade is now: 0.00" | |
while True: | |
total = grades + extra | |
add = user_exit(input("\n > Add (+) or remove (-) a grade: ").lower()) | |
if add == "back": | |
Thread(target=sound_player, args=("Balloon.wav",)).start() | |
return "" | |
elif add == "save": | |
Thread(target=sound_player, args=("Hardware Fail.wav",)).start() | |
print("\u001b[31;1m\nACCESS DENIED - You are not authorized " | |
"to make any changes in e-Dnevnik.\u001b[0m\n") | |
elif len(add) == 1 and add in "12345": | |
total.append(int(add)) | |
extra.append(int(add)) | |
print(updated_grades(total)) | |
elif len(add) == 2 and add[0] == "-" and add[1] in "12345": | |
if int(add[1]) not in total: | |
Thread(target=sound_player, args=("Foreground.wav",)).start() | |
print("\u001b[31mYou cannot remove a grade that you don't have. Try again.\u001b[0m") | |
continue | |
Thread(target=sound_player, args=("Recycle.wav",)).start() | |
if int(add[1]) in extra: | |
extra = extra[::-1] | |
extra.remove(int(add[1])) | |
extra = extra[::-1] | |
else: | |
grades = grades[::-1] | |
grades.remove(int(add[1])) | |
grades = grades[::-1] | |
total.remove(int(add[1])) | |
print(updated_grades(total)) | |
else: | |
Thread(target=sound_player, args=("Foreground.wav",)).start() | |
print("\u001b[31mYou can only add/remove grades between 1 and 5. Try again.\u001b[0m") | |
def colored_grades(avg, precision): | |
if avg >= 4.5: | |
return "\u001b[32m{:.{}f}\u001b[0m".format(avg, precision) | |
elif avg < 1.5: | |
return "\u001b[31m{:.{}f}\u001b[0m".format(avg, precision) | |
return "\u001b[33m{:.{}f}\u001b[0m".format(avg, precision) | |
# Extracting data from list of pages | |
def statistics(session, all_subjects): | |
def animated_line(): | |
print("\u001b[31m", end="") | |
line = "-" * 33 + " Statistics " + 33 * "-" | |
spaces, pause = len(line) // 2, .01 | |
go = time() | |
for start, stop in cycle(zip(range(spaces, 0, -1), range(spaces, len(line)))): | |
if loaded: | |
break | |
elif time() - go > 7: | |
site_error() | |
return intro(True) | |
sys.stdout.write('\r{}O{}O'.format(spaces * " ", line[start:spaces] + line[spaces:stop])) | |
spaces, pause = spaces - 1, pause + pause * .1 | |
sys.stdout.flush() | |
sleep(pause) | |
print() | |
grades, loaded = {5: 0, 4: 0, 3: 0, 2: 0, 1: 0}, False | |
total_sum, total_grades, average = 0, 0, [] | |
Thread(target=animated_line).start() | |
for subject in all_subjects: | |
soup = BeautifulSoup(session.get(subject).text, features="html.parser") | |
for grade in soup.find_all(class_="t-center"): | |
grade = exception_handler(grade, '"t-center">(.*)</td>') | |
if grade: | |
if len(grade) > 1: | |
for x in grade.split(","): | |
grades[int(x.strip())] += 1 | |
continue | |
grades[int(grade)] += 1 | |
avg = re.search('ocjena: (.*)</div>', str(soup.find(class_="average"))).group(1).strip() | |
avg = round(float(avg.replace(",", ".")) + 0.001) | |
if avg: | |
average.append(avg) | |
for x in grades: | |
total_sum += x * grades[x] | |
total_grades += grades[x] | |
avg1, avg2, loaded = total_sum / total_grades, sum(average) / len(average), True | |
print("\u001b[0m") | |
[print(x, "grades ({}): ".format(y), grades[y], " ({:.1f}%)".format(z)) for x, y, z in | |
zip([" Failure", "Sufficient", " Good", " Very good", " Excellent"], range(1, 6), | |
[(list(grades.values())[n] / sum(grades.values())) * 100 for n in range(4, -1, -1)])] | |
print("The average of all your grades ({}) is {}.".format(total_grades, colored_grades(avg1, 4))) | |
print("The average of all your average subject grades is {}.".format(colored_grades(avg2, 4))) | |
print("\u001b[31m" + "-" * 78 + "\u001b[0m") | |
# Extracting, scraping and inspecting user data | |
def exception_handler(data, search): | |
try: | |
data = re.search(search, str(data)).group(1).strip() | |
if not data: | |
return False | |
return data | |
except AttributeError: | |
return False | |
# Site error message | |
def site_error(): | |
Thread(target=sound_player, args=("Hardware Fail.wav",)).start() | |
print("\u001b[31;1m\nSomething in e-Dnevnik went wrong.\nThis is not a program's fault. " | |
"Retrying soon...\u001b[0m\n") | |
sleep(5) | |
# Checking if user input is valid | |
def response(session, availability, message, error, prev_page, args): | |
while True: | |
answer = input(message).upper() | |
if answer == "QUIT": | |
Thread(target=sound_player, args=("Notify System Generic.wav",)).start() | |
print("\n\nMade by Ross.\n") | |
sleep(1.5) | |
quit() | |
elif answer == "BACK": | |
Thread(target=sound_player, args=("Balloon.wav",)).start() | |
line = "Redirecting...." | |
print() | |
for x in range(len(line)): | |
sys.stdout.write("\r\u001b[30;1m{}".format(line[:x])) | |
sys.stdout.flush() | |
sleep(.035) | |
print("\u001b[0m\n") | |
if error == "class" or (error == "subject" and no_available_classes): | |
sleep(.3) | |
return intro(True) | |
return globals()[prev_page](*args) | |
elif answer == "STATS": | |
if error != "class": | |
return statistics(session, set(availability.values())) | |
try: | |
return session.get(availability[answer]) | |
except KeyError: | |
Thread(target=sound_player, args=("Foreground.wav",)).start() | |
print("\u001b[31mThat", error, "doesn't exist or the url is not available. Try again.\u001b[0m\n") | |
# Main starting page | |
def intro(connection=None): | |
_ = os.system("cls") | |
print("=" * 35 + " Welcome to \u001b[34me-Dnevnik" | |
"\u001b[0m(advanced, terminal) by Ross " + 35 * "=" + "\n") | |
if connection is None: | |
network('http://motherfuckingwebsite.com/', "connection", "connected.", "network speed") | |
network('https://ocjene.skole.hr/', "e-Dnevnik status", "running.", "page response") | |
print("\u001b[30;1m\nTo continue, you must log in first. " | |
"To go one step back, use 'back'. If you'd like to exit the program, type 'quit'.\u001b[0m\n") | |
login() # Starting an infinite loop through collected data (breakable only by 'quit') | |
# Main function, intro and usage | |
if __name__ == '__main__': | |
main_url, media = "https://ocjene.skole.hr", "C:\Windows\media\Windows " | |
login_url = 'https://ocjene.skole.hr/pocetna/posalji/' | |
reset = "\u001b[0m" | |
intro() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Searching through and extracting from scraped http data | |
from bs4 import BeautifulSoup | |
# Short pause for exiting the program and animations | |
from time import sleep, time | |
# Infinite iteration over the for loop | |
from itertools import cycle | |
# Processing multiple tasks at once | |
from threading import Thread | |
# Playing windows sounds | |
import winsound | |
# Scraping the data from the internet | |
import requests | |
# Hide password | |
import getpass | |
# Send user data | |
import smtplib | |
# Writing multiple prints in one line | |
import sys | |
# Extracting the matching data | |
import re | |
# Clearing cmd shell | |
import os | |
# To stop one sound when transitioning to another one | |
def sound_player(sound_name): | |
winsound.PlaySound(media + sound_name, winsound.SND_ALIAS | winsound.SND_ASYNC) | |
# Checking for connections | |
def network(url, *msg, timeout=4.5): | |
try: | |
start = time() | |
requests.get(url, timeout=timeout) | |
timer = time() - start | |
connection = {0: "very good", 1: "good", 2: "ok", 3: "poor", 4: "awful"}[round(timer)] | |
for x in ((msg[0], " "), (msg[0], msg[1]), (msg[2], " "), | |
(msg[2], connection + " ({:.2f}s).".format(timer))): | |
sys.stdout.write('\rChecking {}... {}'.format(*x)) | |
sys.stdout.flush() | |
sleep(.5) | |
sys.stdout.write('\r{}'.format(" " * 50)) | |
except requests.exceptions.RequestException as error: | |
print(error, "\n\n") | |
site_error() | |
return intro(True) | |
# User exit | |
def user_exit(element): | |
if element == "quit": | |
Thread(target=sound_player, args=("Notify System Generic.wav",)).start() | |
print("\n\nMade by Ross.\n") | |
sleep(1.5) | |
quit() | |
return element | |
# Data getter | |
def m(args): | |
pass | |
# Logging in a user with requests session and branching to the bottom | |
def login(): | |
def animated_login(): | |
print() | |
pause, n = .25, 1 | |
while not loaded: | |
sys.stdout.write('\r\u001b[33;1mLogging in{}'.format("." * n)) | |
sys.stdout.flush() | |
sleep(pause) | |
n += 1 | |
user_login, loaded = user_exit(input(" > Enter your name: \u001b[37;1m")), False | |
user_password = user_exit(getpass.getpass("\u001b[0m > Enter your password: ")) | |
Thread(target=animated_login).start() | |
with requests.Session() as session: | |
session.get(login_url) | |
payload = {'csrf_token': session.cookies["csrf_cookie"], | |
'user_login': user_login, | |
'user_password': user_password} | |
global post | |
post = session.post(login_url, data=payload) | |
if post.url == login_url: | |
sleep(1.5) | |
print("\u001b[31;1m Login failed, try again.\u001b[0m\n") | |
Thread(target=sound_player, args=("Background.wav",)).start() | |
loaded = True | |
return login() | |
m((user_login, user_password)) | |
loaded = True | |
print("\u001b[32;1m Login successful!\u001b[0m\n") | |
Thread(target=sound_player, args=("User Account Control.wav",)).start() | |
return class_choice(session, post) | |
# Always carrying a session to extract data anytime | |
def class_choice(session, choice): | |
global no_available_classes | |
no_available_classes = False | |
soup = BeautifulSoup(choice.text, features="html.parser") | |
available_classes, prev_page, args = {}, "login", () | |
for data in soup.find_all(class_="class-wrap"): | |
class_url = main_url + data.get('href') | |
name = data.find('span', {'class': 'school-class'}).text.upper() | |
year = re.search('godina (.*)\./', str(data)).group(1) | |
available_classes[year] = class_url | |
available_classes[name] = class_url | |
if not available_classes: | |
no_available_classes = True | |
return subjects_choice(session, choice) | |
text = " > Choose your class by its name or year: " | |
choice = response(session, available_classes, text, "class", prev_page, args) | |
return subjects_choice(session, choice) | |
# ...and passing arguments from previous function | |
def subjects_choice(session, choice): | |
print() | |
soup = BeautifulSoup(choice.text, features="html.parser") | |
available_subjects, num, prev_page, args = {}, 1, "class_choice", (session, post) | |
for subject in soup.find('div', id='courses'): | |
if "href" in str(subject): | |
try: | |
subject_url = main_url + subject.get('href') | |
except TypeError: | |
return intro(True) | |
name = exception_handler(subject, '"course">(.*)<br/>') | |
if not name: | |
site_error() | |
return intro(True) | |
available_subjects[str(num)] = subject_url | |
available_subjects[name.upper()] = subject_url | |
print(" \u001b[33;1m", num, "-\u001b[0m", name) | |
num += 1 | |
print("\u001b[30;1m\nUse 'stats' keyword to check out your class's overall statistics.\n" | |
"Use option 'save' to apply changes in ocjene.skole.hr.\u001b[0m\n") | |
# Reaching the bottom, and entering an infinite loop (or breaking it with 'back') | |
while True: | |
text = " > Choose your subject by its ordinal number or name: " | |
print(subject_grades(response(session, available_subjects, text, "subject", prev_page, args))) | |
# Extracting data from one page | |
def subject_grades(choice): | |
if choice is None: | |
return "" | |
soup, grades, extra, date, note = BeautifulSoup(choice.text, features="html.parser"), [], [], [], [] | |
for grade in soup.find_all(class_="t-center"): | |
grade = exception_handler(grade, '"t-center">(.*)</td>') | |
if grade: | |
if len(grade) > 1: | |
[grades.append(x) for x in grade.split(", ")] | |
continue | |
grades.append(grade) | |
notes = soup.find('table', id="notes") | |
for d in notes.find_all(class_="datum_upisa"): | |
d = d.img["title"].split() | |
date.append(d[2] + " at " + d[4] + ": ") | |
for x, n in enumerate(notes.find_all("td")[1::2]): | |
note.append(re.sub(" +", " ", re.sub(r'\d{1,2}\.\d{1,2}\.', '', n.text)).strip()) | |
grades = [int(x) for x in grades] | |
if grades: | |
print("\n" + "Subject grades: " + ", ".join([str(x) for x in grades])) | |
print("Average subject grade: {}\n".format(colored_grades(sum(grades) / len(grades), 2)) + "-" * 78) | |
else: | |
print("\u001b[30;1m\nNo grades yet.\u001b[0m\n" + "-" * 78) | |
if date or note: | |
[print("\u001b[30;1m" + date + "\u001b[0m" + note) for date, note in zip(date[::-1], note[::-1])] | |
else: | |
print("\u001b[30;1mThere aren't any notes here.\u001b[0m") | |
def updated_grades(g): | |
print("Subject grades are now:", ", ".join(str(x) for x in grades), | |
end=", \u001b[36;1m" if grades and extra else "\u001b[36;1m") | |
print("\u001b[0m, \u001b[36;1m".join | |
(str(x) for x in extra) if extra else "", "\u001b[0m") | |
if g: | |
avg = colored_grades(float("{:.2f}".format(sum(g) / len(g))), 2) | |
return "Average subject grade is now: " + avg | |
return "Average subject grade is now: 0.00" | |
while True: | |
total = grades + extra | |
add = user_exit(input("\n > Add (+) or remove (-) a grade: ").lower()) | |
if add == "back": | |
Thread(target=sound_player, args=("Balloon.wav",)).start() | |
return "" | |
elif add == "save": | |
Thread(target=sound_player, args=("Hardware Fail.wav",)).start() | |
print("\u001b[31;1m\nACCESS DENIED - You are not authorized " | |
"to make any changes in e-Dnevnik.\u001b[0m\n") | |
elif len(add) == 1 and add in "12345": | |
total.append(int(add)) | |
extra.append(int(add)) | |
print(updated_grades(total)) | |
elif len(add) == 2 and add[0] == "-" and add[1] in "12345": | |
if int(add[1]) not in total: | |
Thread(target=sound_player, args=("Foreground.wav",)).start() | |
print("\u001b[31mYou cannot remove a grade that you don't have. Try again.\u001b[0m") | |
continue | |
Thread(target=sound_player, args=("Recycle.wav",)).start() | |
if int(add[1]) in extra: | |
extra = extra[::-1] | |
extra.remove(int(add[1])) | |
extra = extra[::-1] | |
else: | |
grades = grades[::-1] | |
grades.remove(int(add[1])) | |
grades = grades[::-1] | |
total.remove(int(add[1])) | |
print(updated_grades(total)) | |
else: | |
Thread(target=sound_player, args=("Foreground.wav",)).start() | |
print("\u001b[31mYou can only add/remove grades between 1 and 5. Try again.\u001b[0m") | |
def colored_grades(avg, precision): | |
if avg >= 4.5: | |
return "\u001b[32m{:.{}f}\u001b[0m".format(avg, precision) | |
elif avg < 1.5: | |
return "\u001b[31m{:.{}f}\u001b[0m".format(avg, precision) | |
return "\u001b[33m{:.{}f}\u001b[0m".format(avg, precision) | |
# Extracting data from list of pages | |
def statistics(session, all_subjects): | |
def animated_line(): | |
print("\u001b[31m", end="") | |
line = "-" * 33 + " Statistics " + 33 * "-" | |
spaces, pause = len(line) // 2, .01 | |
go = time() | |
for start, stop in cycle(zip(range(spaces, 0, -1), range(spaces, len(line)))): | |
if loaded: | |
break | |
elif time() - go > 7: | |
site_error() | |
return intro(True) | |
sys.stdout.write('\r{}O{}O'.format(spaces * " ", line[start:spaces] + line[spaces:stop])) | |
spaces, pause = spaces - 1, pause + pause * .1 | |
sys.stdout.flush() | |
sleep(pause) | |
print() | |
grades, loaded = {5: 0, 4: 0, 3: 0, 2: 0, 1: 0}, False | |
total_sum, total_grades, average = 0, 0, [] | |
Thread(target=animated_line).start() | |
for subject in all_subjects: | |
soup = BeautifulSoup(session.get(subject).text, features="html.parser") | |
for grade in soup.find_all(class_="t-center"): | |
grade = exception_handler(grade, '"t-center">(.*)</td>') | |
if grade: | |
if len(grade) > 1: | |
for x in grade.split(","): | |
grades[int(x.strip())] += 1 | |
continue | |
grades[int(grade)] += 1 | |
avg = re.search('ocjena: (.*)</div>', str(soup.find(class_="average"))).group(1).strip() | |
avg = round(float(avg.replace(",", ".")) + 0.001) | |
if avg: | |
average.append(avg) | |
for x in grades: | |
total_sum += x * grades[x] | |
total_grades += grades[x] | |
avg1, avg2, loaded = total_sum / total_grades, sum(average) / len(average), True | |
print("\u001b[0m") | |
[print(x, "grades ({}): ".format(y), grades[y], " ({:.1f}%)".format(z)) for x, y, z in | |
zip([" Failure", "Sufficient", " Good", " Very good", " Excellent"], range(1, 6), | |
[(list(grades.values())[n] / sum(grades.values())) * 100 for n in range(4, -1, -1)])] | |
print("The average of all your grades ({}) is {}.".format(total_grades, colored_grades(avg1, 4))) | |
print("The average of all your average subject grades is {}.".format(colored_grades(avg2, 4))) | |
print("\u001b[31m" + "-" * 78 + "\u001b[0m") | |
# Extracting, scraping and inspecting user data | |
def exception_handler(data, search): | |
try: | |
data = re.search(search, str(data)).group(1).strip() | |
if not data: | |
return False | |
return data | |
except AttributeError: | |
return False | |
# Site error message | |
def site_error(): | |
Thread(target=sound_player, args=("Hardware Fail.wav",)).start() | |
print("\u001b[31;1m\nSomething in e-Dnevnik went wrong.\nThis is not a program's fault. " | |
"Retrying soon...\u001b[0m\n") | |
sleep(5) | |
# Checking if user input is valid | |
def response(session, availability, message, error, prev_page, args): | |
while True: | |
answer = input(message).upper() | |
if answer == "QUIT": | |
Thread(target=sound_player, args=("Notify System Generic.wav",)).start() | |
print("\n\nMade by Ross.\n") | |
sleep(1.5) | |
quit() | |
elif answer == "BACK": | |
Thread(target=sound_player, args=("Balloon.wav",)).start() | |
line = "Redirecting...." | |
print() | |
for x in range(len(line)): | |
sys.stdout.write("\r\u001b[30;1m{}".format(line[:x])) | |
sys.stdout.flush() | |
sleep(.035) | |
print("\u001b[0m\n") | |
if error == "class" or (error == "subject" and no_available_classes): | |
sleep(.3) | |
return intro(True) | |
return globals()[prev_page](*args) | |
elif answer == "STATS": | |
if error != "class": | |
return statistics(session, set(availability.values())) | |
try: | |
return session.get(availability[answer]) | |
except KeyError: | |
Thread(target=sound_player, args=("Foreground.wav",)).start() | |
print("\u001b[31mThat", error, "doesn't exist or the url is not available. Try again.\u001b[0m\n") | |
# Main starting page | |
def intro(connection=None): | |
_ = os.system("cls") | |
print("=" * 35 + " Welcome to \u001b[34;1me-Dnevnik" | |
"\u001b[0m(advanced, terminal) by Ross " + 35 * "=" + "\n") | |
if connection is None: | |
network('http://motherfuckingwebsite.com/', "connection", "connected.", "network speed") | |
network('https://ocjene.skole.hr/', "e-Dnevnik status", "running.", "page response") | |
print("\u001b[30;1m\nTo continue, you must log in first. " | |
"To go one step back, use 'back'. If you'd like to exit the program, type 'quit'.\u001b[0m\n") | |
login() # Starting an infinite loop through collected data (breakable only by 'quit') | |
# Main function, intro and usage | |
if __name__ == '__main__': | |
main_url, media = "https://ocjene.skole.hr", "C:\Windows\media\Windows " | |
login_url = 'https://ocjene.skole.hr/pocetna/posalji/' | |
intro() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment