def find_keyword(exam, phrase): data = get_exam(exam=exam) exam_json = json.dumps(data) for email, _ in get_roster(exam=exam): scrambled = scramble(email, json.loads(exam_json)) if phrase in json.dumps(scrambled): print(email)
def identify_watermark(exam, image): """ Identify the student from a screenshot containing a watermark. """ img = cv2.imread(image) img = cv2.copyMakeBorder(img, 100, 100, 100, 100, cv2.BORDER_CONSTANT) corners = [] bits = [] def handle_click(event, x, y, flags, params): if event == cv2.EVENT_LBUTTONDOWN: bits.append(Point(x, y)) cv2.circle(img, (x, y), 5, (255, 0, 0), -1) if event == cv2.EVENT_RBUTTONDOWN: corners.append(Point(x, y)) cv2.circle(img, (x, y), 5, (0, 255, 0), -1) cv2.namedWindow("image") cv2.setMouseCallback("image", handle_click) while True: cv2.imshow("image", img) if cv2.waitKey(20) & 0xFF == 13: break print( decode_watermark(get_exam(exam=exam), get_roster(exam=exam), corners, bits))
def compile_all(exam, subtitle, out, do_twice, email, exam_type, semester, deadline): """ Compile individualized PDFs for the specified exam. Exam must have been deployed first. """ if not out: out = "out/latex/" + exam pathlib.Path(out).mkdir(parents=True, exist_ok=True) exam_data = get_exam(exam=exam) password = exam_data.pop("secret")[:-1] print(password) exam_str = json.dumps(exam_data) roster = get_roster(exam=exam) if email: roster = [line_info for line_info in roster if line_info[0] == email] if len(roster) == 0: if deadline: roster = [(email, deadline)] else: raise ValueError("Email does not exist in the roster!") for email, deadline in roster: if not deadline: continue exam_data = json.loads(exam_str) scramble(email, exam_data) deadline_utc = datetime.utcfromtimestamp(int(deadline)) deadline_pst = pytz.utc.localize(deadline_utc).astimezone( pytz.timezone("America/Los_Angeles")) deadline_string = deadline_pst.strftime("%I:%M%p") with render_latex( exam_data, { "emailaddress": sanitize_email(email), "deadline": deadline_string, "coursecode": prettify(exam.split("-")[0]), "description": subtitle, "examtype": exam_type, "semester": semester, }, do_twice=do_twice, ) as pdf: pdf = Pdf.open(BytesIO(pdf)) pdf.save( os.path.join( out, "exam_" + email.replace("@", "_").replace(".", "_") + ".pdf"), encryption=Encryption(owner=password, user=password), ) pdf.close()
def cheaters(exam, target): """ Identify potential instances of cheating. """ if not target: target = "out/logs/" + exam logs = [] for email, deadline in get_roster(exam=exam): with open(os.path.join(target, email)) as f: logs.append([email, json.load(f)]) find_unexpected_words(exam, logs)
def save_logs(exam, out): """ Save the full submission log for later analysis. Note that this command is slow. To view a single log entry, run `examtool log`. """ out = out or "out/logs/" + exam pathlib.Path(out).mkdir(parents=True, exist_ok=True) roster = get_roster(exam=exam) for i, (email, deadline) in enumerate(roster): print(email) logs = get_logs(exam=exam, email=email) with open(os.path.join(out, email), "w") as f: json.dump(logs, f)
def download(exam, emails_to_download: [str] = None, debug: bool = False): exam_json = get_exam(exam=exam) exam_json.pop("secret") exam_json = json.dumps(exam_json) template_questions = list(extract_questions(json.loads(exam_json))) total = [["Email"] + [ question["text"] for question in extract_questions(json.loads(exam_json)) ]] email_to_data_map = {} if emails_to_download is None: roster = get_roster(exam=exam) emails_to_download = [email for email, _ in roster] i = 1 for email, response in tqdm(get_submissions(exam=exam), dynamic_ncols=True, desc="Downloading", unit="Exam"): i += 1 if emails_to_download is not None and email not in emails_to_download: continue if debug and 1 < len(response) < 10: tqdm.write(email, response) total.append([email]) for question in template_questions: total[-1].append(response.get(question["id"], "")) student_questions = list( extract_questions( scramble(email, json.loads(exam_json), keep_data=True))) email_to_data_map[email] = { "student_questions": student_questions, "responses": response, } return json.loads(exam_json), template_questions, email_to_data_map, total
def cheaters(exam, target, out): """ Identify potential instances of cheating. """ if not target: target = "out/logs/" + exam logs = defaultdict(list) for email, deadline in get_roster(exam=exam): short_target = os.path.join(target, email) full_target = os.path.join(target, "full", email) if os.path.exists(short_target): with open(short_target) as f: logs[email].extend(json.load(f)) if os.path.exists(full_target): with open(full_target) as f: data = json.load(f) for record in data: if "snapshot" not in record: print(email, record) else: logs[email].append({ **record["snapshot"], "timestamp": record["timestamp"] }) logs[email].append({ **record["history"], "timestamp": record["timestamp"] }) logs = list(logs.items()) suspects = find_unexpected_words(exam, logs) if out: if suspects: writer = csv.writer(out) keys = asdict(suspects[0]) writer.writerow(list(keys)) for suspect in suspects: writer.writerow(asdict(suspect).values()) suspect.explain() else: for suspect in suspects: suspect.explain()
def save_logs(exam, out, full, fetch_all): """ Save the full submission log for later analysis. Note that this command is slow. To view a single log entry, run `examtool log`. """ out = out or "out/logs/" + exam full_out = os.path.join(out, "full") pathlib.Path(out).mkdir(parents=True, exist_ok=True) pathlib.Path(full_out).mkdir(parents=True, exist_ok=True) roster = get_roster(exam=exam) for i, (email, deadline) in enumerate(roster): print(email) try: target = os.path.join(out, email) if os.path.exists(target) and not fetch_all: print("Skipping", email) else: print("Fetching short logs for", email) logs = get_logs(exam=exam, email=email) with open(target, "w") as f: json.dump(logs, f) if full: target = os.path.join(full_out, email) if os.path.exists(target) and not fetch_all: print("Skipping", email, "for full logs") else: print("Fetching full logs for", email) logs = get_full_logs(exam=exam, email=email) with open(os.path.join(full_out, email), "w") as f: json.dump(logs, f) except KeyboardInterrupt: raise except: print("Failure for email", email, "continuing...")
def send(exam, target, email, subject, filename): """ Email an encrypted PDF to all students taking an exam. Specify `email` to email only a particular student. """ if not target: target = "out/latex/" + exam course = prettify(exam.split("-")[0]) filename = filename.format(course=course) subject = subject.format(course=course) body = ( "Hello!\n\n" "You have an upcoming exam taking place on exam.cs61a.org. " "You should complete your exam on that website.\n\n" "Course: {course}\n" "Exam: {exam}\n\n" "However, if you encounter technical difficulties and are unable to do so, " "we have attached an encrypted PDF containing the same exam. " "You can then email your exam solutions to course staff before the deadline " "rather than submitting using exam.cs61a.org. " "To unlock the PDF, its password will be revealed on Piazza when the exam starts.\n\n" "Good luck, and remember to have fun!").format(course=course, exam=exam) roster = [] if email: roster = [email] else: for email, deadline in get_roster(exam=exam): if deadline: roster.append(email) key = get_api_key(exam=exam) print(("Subject: {subject}\n" "PDF filename: {filename}\n" "Body: {body}\n\n").format(body=body, filename=filename, subject=subject)) if (input("Sending email to {} people - confirm? (y/N) ".format( len(roster))).lower() != "y"): exit(1) for email in roster: with open( os.path.join( target, "exam_" + email.replace("@", "_").replace(".", "_") + ".pdf"), "rb", ) as f: pdf = base64.b64encode(f.read()).decode("ascii") data = { "from": { "email": "*****@*****.**", "name": "CS 61A Exam Platform" }, "personalizations": [{ "to": [{ "email": email }], "substitutions": {} }], "subject": subject, "content": [{ "type": "text/plain", "value": body }], "attachments": [{ "content": pdf, "type": "application/pdf", "filename": filename, "disposition": "attachment", }], } send_email_local(key, data)
from examtool.api.database import get_exam, get_roster from examtool.api.extract_questions import extract_questions from examtool.api.scramble import scramble from google.cloud import firestore import warnings warnings.filterwarnings( "ignore", "Your application has authenticated using end user credentials") db = firestore.Client() exams = [x.id for x in db.collection("exams").stream()] for exam in exams: print("checking", exam) exam_json = json.dumps(get_exam(exam=exam)) roster = get_roster(exam=exam) flagged = set() for email, _ in roster: template_questions = extract_questions(json.loads(exam_json)) student_questions = list( extract_questions( scramble(email, json.loads(exam_json), keep_data=True))) student_question_lookup = {q['id']: q for q in student_questions} for question in template_questions: if question["id"] not in student_question_lookup: continue if question["type"] not in ["multiple_choice", "select_all"]: continue if question["id"] in flagged: