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))
Beispiel #3
0
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()
Beispiel #4
0
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)
Beispiel #5
0
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)
Beispiel #6
0
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
Beispiel #7
0
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()
Beispiel #8
0
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...")
Beispiel #9
0
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)
Beispiel #10
0
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: