Skip to content

Instantly share code, notes, and snippets.

@ChrisRoss5
Last active May 16, 2019 07:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ChrisRoss5/3cec41bfd7241fd6ebfb230211fd2fe3 to your computer and use it in GitHub Desktop.
Save ChrisRoss5/3cec41bfd7241fd6ebfb230211fd2fe3 to your computer and use it in GitHub Desktop.
e-Dnevnik Plus CMD
# 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()
# 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()
# 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