import sys from urllib.parse import urlparse import requests from requests_oauthlib import OAuth2Session from selenium import webdriver import os import os.path import time import http.server import shutil import socketserver from threading import Thread from werkzeug.urls import url_decode import pprint from PyInquirer import prompt, print_json import json import datetime import os import argparse import webbrowser from bgservice import bgservice as bg import atexit client_id = r'QeZPBSKqdvWFfBv1VYTSv9iFGz5T9pVJtNUjbEr6' client_secret = r'0Wl3hAIGY9SvYOqTOLUiLNYa4OlCgZYdno9ZbcgCT7RGQ8x2f1l2HzZHsQ7ijC74A0mrOhhCVeZugqAmOADHIv5fHxaa7GqFNtQr11HX9ySTw3DscKsphCVi5P71mlGY' redirect_uri = 'http://localhost:8000/callback/' token_url = 'https://ion.tjhsst.edu/oauth/token/' scope = ["read"] USER = "" PWD = "" def main(): """ The Command Line Interface (CLI) for SkoolOS Serves to allow both teachers and students to access the majority of the features of SkoolOS """ print("") print("░██████╗██╗░░██╗░█████╗░░█████╗░██╗░░░░░  ░█████╗░░██████╗") print("██╔════╝██║░██╔╝██╔══██╗██╔══██╗██║░░░░░  ██╔══██╗██╔════╝") print("╚█████╗░█████═╝░██║░░██║██║░░██║██║░░░░░  ██║░░██║╚█████╗░") print("░╚═══██╗██╔═██╗░██║░░██║██║░░██║██║░░░░░  ██║░░██║░╚═══██╗") print("██████╔╝██║░╚██╗╚█████╔╝╚█████╔╝███████╗  ╚█████╔╝██████╔╝") print("╚═════╝░╚═╝░░╚═╝░╚════╝░░╚════╝░╚══════╝  ░╚════╝░╚═════╝░") print("") profiles = os.listdir() if not ("profile" in str(profiles)): try: URL = "http://127.0.0.1:8000/api/" r = requests.get(url=URL) except: print("Run Django server on http://127.0.0.1:8000/ before continuing") sys.exit(0) input("Welcome to SkoolOS. Press any key to create an account") # webbrowser.open("http://127.0.0.1:8000/login", new=2) authenticate() profiles = os.listdir() users = [] info = [] count = 1 for i in range(len(profiles)): p = profiles[i] if 'profile' in p: f = open(p, 'r') d = json.loads(f.read()) f.close() info.append(d) users.append(str(count) + ") " + d['username']) count = count + 1 users.append(str(count) + ") Make new user") user = [ { 'type': 'list', 'name': 'user', 'choices': users, 'message': 'Select User: ', }, ] u = int(prompt(user)['user'].split(")")[0]) - 1 if u + 1 == count: authenticate() return data = info[u] PWD = data['password'] USER = data['username'] print(data['username']) if data['is_student']: # bg.watch_dir() # atexit.register(stop_bg_service) studentCLI(USER, PWD) else: teacherCLI(USER, PWD) #################################################################################################### STUDENT METHODS def stop_bg_service(): print("good") bg.stop_watching() print('also') cur_path = os.path.dirname('') # Change to definite SkoolOS dir print('yes') newpath = os.path.relpath('bgservice/SkoolOS/logs/') logText = "" with open(newpath + '/skooloslog', 'r') as logfile: logfile.read() print (logText) def studentCLI(user, password): """ The CLI for students to access :param user: student username :param password: student password """ from CLI import student data = getUser(user, password, 'student') student = student.Student(data, password) student.update() EXIT = False while not EXIT: course = chooseClassStudent(student) if course == "Exit SkoolOS": return EXIT = classOptionsStudent(student, course) # return class def chooseClassStudent(student): """ Chooses a class for a student to view and work on :param student: a student :return: a course prompt """ carray = student.sclass.split(",") if len(carray) == 1 and carray[0] == "": carray.remove("") print("No classes") carray.append("Exit SkoolOS") courses = [ { 'type': 'list', 'name': 'course', 'choices': carray, 'message': 'Select class: ', }, ] course = prompt(courses)['course'] print(course) return course def classOptionsStudent(student, course): """ Allows students to choose what they want to do related to a class The student can save, exit, or go back :param student: a student :param course: a course :return: True if exiting, False if going back """ student.viewClass(course) student.getAssignments(course, 100) choices = ["Save","Submit assignment","Back","Exit SkoolOS"] options = [ { 'type': 'list', 'name': 'option', 'choices': choices, 'message': 'Select: ', }, ] option = prompt(options)['option'] if option == "Save": student.update() print("Saved!") classOptionsStudent(student, course) if option == "Back": student.exitCLI() # dont exit cli return False if option == "Exit SkoolOS": student.exitCLI() # exit cli return True if(option == "Submit assignment"): assignments = os.listdir(student.username) tlist = [] b = True for a in assignments: oname = a + "_" + course a = student.username + "/" + a if(os.path.isdir(a) and not "." in a and not oname in student.completed): tlist.append(a) assignments = tlist assignments.append("Back") print(assignments) options = [ { 'type': 'list', 'name': 'submit', 'choices':assignments, 'message': 'Select: ', }, ] ass = prompt(options)['submit'] if(ass == "Back"): return False else: student.submit(course, ass) return False #################################################################################################### TEACHER METHODS def teacherCLI(user, password): """ The CLI for teachers to access :param user: teachers username :param password: teachers password """ from CLI import teacher data = getUser(user, password, 'teacher') print(data) teacher = teacher.Teacher(data, password) EXIT = False # 1. make a class # 2. add studeents to an existing class # 3. Get progress logs on a student # 2. make an assignment for a class # 3. view student submissions for an assignment while not EXIT: # Options: '1) Request Student', "2) Add assignment", "3) View student information", "4) Exit" course = chooseGeneralTeacher(teacher) if course == "Exit SkoolOS": EXIT = True elif course == "Make New Class": EXIT = makeClassTeacher(teacher) # selected a class else: #Pull confirmed students directory #teacher.getStudents(course) option = classOptionsTeacher(teacher, course) if option == '1': EXIT = addStudentsTeacher(teacher, course) elif option == '2': EXIT = addAssignmentTeacher(teacher, course) elif option == '3': EXIT = viewStudentsTeacher(teacher, course) else: EXIT = True def chooseGeneralTeacher(teacher): carray = [] for c in teacher.classes: carray.append(c) carray.append("Make New Class") carray.append("Exit SkoolOS") courses = [ { 'type': 'list', 'name': 'course', 'choices': carray, 'message': 'Select class: ', }, ] course = prompt(courses)['course'] return course def makeClassTeacher(teacher): questions = [ { 'type': 'input', 'name': 'cname', 'message': 'Class Name (Must be: _): ', }, ] cname = prompt(questions)['cname'] print(cname) while not ("_" + teacher.username) in cname: print("Incorrect naming format") questions = [ { 'type': 'input', 'name': 'cname', 'message': 'Class Name (Must be: _): ', }, ] cname = prompt(questions)['cname'] teacher.makeClass(cname) soption = ["1) Add individual student", "2) Add list of students through path", "3) Exit"] questions = [ { 'type': 'list', 'choices': soption, 'name': 'students', 'message': 'Add Students): ', }, ] choice = prompt(questions)['students'].split(")")[0] if "1" == choice: s = input("Student name: ") teacher.addStudent(s, cname) if "2" == choice: print("File must be .txt and have 1 student username per line") path = input("Relative Path: ") while not os.path.exists(path): if path == 'N': return True print(path + " is not a valid path") path = input("Enter file path ('N' to exit): ") f = open(path, 'r') students = f.read().splitlines() teacher.reqAddStudentList(students, cname) return False def classOptionsTeacher(teacher, course): print("Class: " + course) unconf = getDB(teacher.username, teacher.password, "http://localhost:8000/api/classes/" + course)['unconfirmed'] for s in unconf: teacher.addStudent(s, course) options = ['1) Request Student', "2) Add assignment", "3) View student information", "4) Exit"] questions = [ { 'type': 'list', 'name': 'course', 'choices': options, 'message': 'Select option: ', }, ] option = prompt(questions)['course'].split(")")[0] return option def addStudentsTeacher(teacher, course): soption = ["1) Add individual student", "2) Add list of students through path", "3) Exit"] questions = [ { 'type': 'list', 'choices': soption, 'name': 'students', 'message': 'Add list of students (input path): ', }, ] schoice = prompt(questions)['students'].split(")")[0] if schoice == '1': questions = [ { 'type': 'input', 'name': 'student', 'message': 'Student Name: ', }, ] s = prompt(questions)['student'] teacher.reqStudent(s, course) return False if schoice == '2': questions = [ { 'type': 'input', 'name': 'path', 'message': 'Path: ', }, ] path = prompt(questions)['path'] while not os.path.exists(path): if path == 'N': sys.exit(0) print(path + " is not a valid path") path = input("Enter file path ('N' to exit): ") f = open(path, 'r') students = f.read().splitlines() teacher.reqAddStudentList(students, course) return False else: return True def addAssignmentTeacher(teacher, course): nlist = os.listdir(teacher.username + "/" + course) alist = getDB(teacher.username, teacher.password, "http://localhost:8000/api/classes/" + course)['assignments'] print(nlist) tlist = [] b = True for n in nlist: b = True print(teacher.username + "/" + course + "/" + n) for a in alist: if n in a or n == a: # print("Assignments: " + n) b = False if not os.path.isdir(teacher.username + "/" + course + "/" + n): b = False if b: tlist.append(n) nlist = tlist if len(nlist) == 0: print("No new assignments found") print( "To make an assignment: make a subdirectory in the " + course + " folder. Add a file within the new folder") return False questions = [ { 'type': 'list', 'choices': nlist, 'name': 'assignment', 'message': 'Select new assignment: ', }, ] ass = prompt(questions)['assignment'] apath = teacher.username + "/" + course + "/" + ass due = input("Enter due date (Example: 2020-08-11 16:58): ") due = due + ":33.383124" due = due.strip() f = False while not f: try: datetime.datetime.strptime(due, '%Y-%m-%d %H:%M:%S.%f') f = True except: print("Due-date format is incorrect.") print(due) due = input("Enter due date (Example: 2020-08-11 16:58): ") due = due + ":33.383124" teacher.addAssignment(apath, course, due) return False def viewStudentsTeacher(teacher, course): data = getDB(teacher.username, teacher.password, "http://127.0.0.1:8000/api/classes/" + course) students = data["confirmed"] unconf = data['unconfirmed'] print("Students in class: ") for s in students: print(s) print("Requsted Students: ") for s in unconf: print(s) student = input("View student (Enter student's ion username): ") while((not student in str(data['confirmed'])) and (not student in str(data['unconfirmed']))): print("Student not affiliated with class") student = input("View student ('N' to exit): ") if student == 'N': return False sinfo = getDB(teacher.username, teacher.password, "http://127.0.0.1:8000/api/students/" + student + "/") pprint.pprint(sinfo) print("Confirmed: " + str(student in str(data['confirmed']))) if(student in str(data['confirmed'])): path = teacher.username + "/Students/" + course + "/" + student print(student + "'s work: " + path) fin = sinfo['completed'].split(",") alist = [] for f in fin: if(course in f): late = teacher.afterSubmit(course, f, student) if(late): s = f.split("_")[0] + " (LATE)" else: s = f.split("_")[0] alist.append(s) print("Has submitted: " + str(alist)) answer = None while answer not in ("yes", "no"): answer = input("Would you like to view the student's logs?: ") if answer == "yes" or answer=="y": print (sinfo['log']) elif answer == "no" or answer=="n": print("OK!") else: print("Please enter yes or no.") ############################################################################################################################################ def getUser(ion_user, password, utype): """ Returns user information :param ion_user: user :param password: user's password :param utype: type of user (student or teacher :return: api user information """ if 'student' in utype: URL = "http://127.0.0.1:8000/api/students/" + ion_user + "/" else: URL = "http://127.0.0.1:8000/api/teachers/" + ion_user + "/" print(URL) r = requests.get(url=URL, auth=(ion_user, password)) print(r.json()) if r.status_code == 200: data = r.json() print(200) return data elif r.status_code == 404: print("Make new account!") return None elif r.status_code == 403: print("Invalid username/password") return None else: print(r.status_code) return None def patchDB(USER, PWD, url, data): """ Sends a PATCH request to url :param USER: username :param PWD: password :param url: URL for request :param data: data to request :return: json request response """ r = requests.patch(url=url, data=data, auth=(USER, PWD)) print("PATH:" + str(r.status_code)) return r.json() def getDB(USER, PWD, url): """ Sends a GET request to url :param USER: username :param PWD: password :param url: URL for request :return: json request response """ r = requests.get(url=url, auth=(USER, PWD)) print("GET:" + str(r.status_code)) return r.json() def postDB(USER, PWD, url, data): """ Sends a POST request to url :param USER: username :param PWD: password :param url: URL for request :param data: data to request :return: json request response """ r = requests.post(url=url, data=data, auth=(USER, PWD)) print("POST:" + str(r.status_code)) return r.json() def putDB(USER, PWD, url, data): """ Sends a PUT request to url :param USER: username :param PWD: password :param url: URL for request :param data: data to request :return: json request response """ r = requests.put(url=url, data=data, auth=(USER, PWD)) print("PUT:" + str(r.status_code)) return r.json() def delDB(USER, PWD, url): """ Sends a DELETE request to url :param USER: username :param PWD: password :param url: URL for request :return: json request response """ r = requests.delete(url=url, auth=(USER, PWD)) print("DELETE:" + str(r.status_code)) return None def makePass(): """ Prompts the user to create a password :return: the password """ questions = [ { 'type': 'password', 'name': 'pwd', 'message': 'Enter SkoolOS Password (NOT ION PASSWORD): ', }, ] pwd = prompt(questions)['pwd'] while len(pwd) < 7: print("Password too short (Must be over 6 characters)") pwd = prompt(questions)['pwd'] conf = [ { 'type': 'password', 'name': 'pwd', 'message': 'Re-enter password: ', }, ] pwd2 = prompt(conf)['pwd'] while not pwd == pwd2: print("Passwords do not match.") pwd2 = prompt(conf)['pwd'] else: print("PASSWORD SAVED") return pwd def authenticate(): """ Authenticates the user via Ion OAuth """ oauth = OAuth2Session(client_id=client_id, redirect_uri=redirect_uri, scope=scope) authorization_url, state = oauth.authorization_url("https://ion.tjhsst.edu/oauth/authorize/") import os cdir = os.getcwd() # Linux: chromdriver-linux # Macos: chromdriver-mac # Windows: chromdriver.exe print("OS: ") print("MacOS") print("Linux") system = input("Enter OS: ") while(system.lower() != "linux" and system.lower() != "macos"): print("Not valid OS") system = input("Enter OS: ") if(system.lower() == 'macos'): path = os.path.join(os.getcwd(), 'chromedriver', 'chromedriver-mac') if(system.lower() == 'linux'): path = os.path.join(os.getcwd(), 'chromedriver', 'chromedriver-linux') browser = webdriver.Chrome(path) browser.get("localhost:8000/login") # while "http://localhost:8000/callback/?code" not in browser.current_url: # time.sleep(0.25) url = browser.current_url gets = url_decode(url.replace("http://localhost:8000/login/?", "")) while "http://localhost:8000/login/?username=" not in browser.current_url and ( not browser.current_url == "http://localhost:8000/"): # http://localhost:8000/ time.sleep(0.25) url = browser.current_url gets = url_decode(url.replace("http://localhost:8000/login/?username=", "")) # code = gets.get("code") # if state == gets.get("state"): # state = gets.get("state") # print("states good") browser.quit() questions = [ { 'type': 'input', 'name': 'username', 'message': 'Enter SkoolOS Username (Same as ION Username): ', }, { 'type': 'password', 'name': 'pwd', 'message': 'Enter SkoolOS Password (NOT ION PASSWORD): ', }, ] data = prompt(questions) pwd = data['pwd'] user = data['username'] r = requests.get(url="http://localhost:8000/api/", auth=(user, pwd)) while r.status_code != 200: print("INCORRECT LOGIN CREDENTIALS") r = requests.get(url="http://localhost:8000/api/", auth=(user, pwd)) data = prompt(questions) pwd = data['pwd'] user = data['username'] print(r.status_code) r = requests.get(url="http://localhost:8000/api/students/" + user + "/", auth=(user, pwd)) is_student = False if r.status_code == 200: is_student = True print("Welcome, student " + user) r = requests.get(url="http://localhost:8000/api/students/" + user + "/", auth=(user, pwd)) profile = r.json() username = profile['ion_user'] grade = profile['grade'] profile = { 'username': username, 'grade': grade, 'is_student': is_student, 'password': pwd, } fname = "." + username + "profile" profileFile = open(fname, "w") profileFile.write(json.dumps(profile)) profileFile.close() else: print("Welcome, teacher " + user) r = requests.get(url="http://localhost:8000/api/teachers/" + user + "/", auth=(user, pwd)) profile = r.json() username = profile['ion_user'] profile = { 'username': username, 'is_student': is_student, 'password': pwd, } fname = "." + username + "profile" profileFile = open(fname, "w") profileFile.write(json.dumps(profile)) profileFile.close() sys.exit(0) def create_server(): """ Creates a simple HTTP server for creating api requests from the CLI """ port = 8000 handler = http.server.SimpleHTTPRequestHandler httpd = socketserver.TCPServer(("", port), handler) print("serving at port:" + str(port)) httpd.serve_forever() if __name__ == "__main__": main()