İTÜ Dersçi is a Flask application that stores course schedules of ITU undergraduate programs, notifies you when a quota is available and helps you with your course registrations
from flask import Flask, render_template, jsonify, send_file, request
import os
import json
from update import update
import threading
import time
import notify

app = Flask(__name__)

with open("course_abbreviations.json", "r") as abbreviations_file:
    course_abbreviations = json.load(abbreviations_file)


def run_update_task(delay_seconds):
    while True:
        time.sleep(delay_seconds)
        update()
        text = "Course data was updated at " + time.strftime("%H:%M:%S %d.%m.%Y") + "."
        subject = "ITU Dersçi Course Data was Updated"
        notify.by_email_update(subject, text)


delay_seconds = 15 * 60

update_thread = threading.Thread(target=run_update_task, args=(delay_seconds,))
update_thread.daemon = True
update_thread.start()


@app.route("/")
def index():
    return render_template("index.html", course_abbreviations=course_abbreviations)


@app.route("/static/<path:filename>")
def serve_static(filename):
    return send_file(os.path.join("static", filename))


@app.route("/get_course_data/<course_abbreviation>")
def get_course_data(course_abbreviation):
    json_file_path = os.path.join(
        "course_data", f"course_data_{course_abbreviation}.json"
    )
    try:
        return send_file(json_file_path, mimetype="application/json")
    except FileNotFoundError:
        return jsonify({"error": "File not found"}), 404


if __name__ == "__main__":
    app.run(debug=True)

src/app.py
gbudanur/itu_dersci
import requests
from dotenv import load_dotenv
import os

load_dotenv()

API_KEY = os.getenv("API_KEY").strip("'")

URL = os.getenv("URL").strip("'")


def by_email_update(subject, text):
    try:
        response = requests.post(
            f"https://api.mailgun.net/v3/{URL}/messages",
            auth=("api", API_KEY),
            data={
                "from": f"ITU Dersçi Admin <itudersciadmin@{URL}>",
                "to": ["[ADMIN_EMAIL]", "Admin"],
                "subject": subject,
                "text": text,
            },
        )

        response.raise_for_status()

        if response.status_code == 200:
            print("Email sent successfully!")
        else:
            print(f"Email sending failed with status code {response.status_code}")

    except requests.exceptions.RequestException as e:
        print(f"An error occurred while sending the email: {e}")


if __name__ == "__main__":
    subject = "Test"
    text = "Test"
    by_email_update(subject, text)

src/notify.py
gbudanur/itu_dersci
import requests
from lxml import html
import json
import time
import os


def scraper(course_abbreviation):
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36"
    }

    url = f"https://www.sis.itu.edu.tr/TR/ogrenci/ders-programi/ders-programi.php?seviye=LS&derskodu={course_abbreviation}"

    response = requests.get(url)

    if response.status_code == 200:
        page_content = html.fromstring(response.content.decode("windows-1254"))

        course_table = page_content.xpath("/html/body/div[1]/div[2]/div/div[3]/table")[
            0
        ]

        course_data = []

        for row in course_table.xpath(".//tr")[1:]:
            columns = row.xpath(".//td")

            course_code_element = columns[1].find(".//a")
            course_code = (
                course_code_element.text_content().strip()
                if course_code_element is not None
                else ""
            )

            capacity = columns[9].text_content().strip()
            enrolled = columns[10].text_content().strip()

            no_capacity = "Yes" if capacity == enrolled else "No"

            course_info = {
                "CRN": columns[0].text_content().strip(),
                "Course Code": course_code,
                "Course Title": columns[2].text_content().strip(),
                "Teaching Method": columns[3].text_content().strip(),
                "Instructor": columns[4].text_content().strip(),
                "Building": columns[5].text_content().strip(),
                "Day": columns[6].text_content().strip(),
                "Time": columns[7].text_content().strip(),
                "Room": columns[8].text_content().strip(),
                "Capacity": capacity,
                "Enrolled": enrolled,
                "Reservation": columns[11].text_content().strip(),
                "Major Restriction": columns[12].text_content().strip(),
                "Prerequisites": columns[13].text_content().strip(),
                "Class Restrictions": columns[14].text_content().strip(),
                "No Capacity": no_capacity,
            }

            course_data.append(course_info)

        if course_data and course_data[0]["CRN"] == "CRN":
            course_data.pop(0)

        last_update = time.strftime("%H:%M:%S %d.%m.%Y")
        course_data.append({"Last Update": last_update})

        folder_path = "course_data"

        os.makedirs(folder_path, exist_ok=True)

        filename = os.path.join(folder_path, f"course_data_{course_abbreviation}.json")

        with open(filename, "w", encoding="utf-8") as jsonfile:
            json.dump(course_data, jsonfile, indent=4, ensure_ascii=False)

        print(f"Course data saved to {filename}")

    else:
        print(
            f"Failed to retrieve data for course abbreviation {course_abbreviation}. Status code: {response.status_code}"
        )


if __name__ == "__main__":
    course_abbreviation = input("Enter course abbreviation: ")
    scraper(course_abbreviation)

src/scraper.py
gbudanur/itu_dersci
import json
import os


def search(abbreviation, search_type, keyword, check_no_capacity=False):
    matches = []

    folder_path = "course_data"

    os.makedirs(folder_path, exist_ok=True)

    file_path = os.path.join(folder_path, f"course_data_{abbreviation}.json")

    with open(file_path, "r", encoding="utf-8") as jsonfile:
        course_data = json.load(jsonfile)

    for course in course_data:
        if search_type == "CRN" and course.get("CRN") == keyword:
            if not check_no_capacity or (
                check_no_capacity and course.get("No Capacity") == "No"
            ):
                matches.append(course)
        elif search_type == "Course Code" and course.get("Course Code") == keyword:
            if not check_no_capacity or (
                check_no_capacity and course.get("No Capacity") == "No"
            ):
                matches.append(course)

    return matches


# How to use the search function
# abbreviation = "AKM"
#search_type = "Course Code"
#keyword = "AKM 204"

#results = search(abbreviation, search_type, keyword)

#if results:
#    for course in results:
#       print(f"CRN: {course['CRN']}")
#       print(f"Course Code: {course['Course Code']}")
#       print(f"Course Title: {course['Course Title']}")
#       print(f"No Capacity: {course['No Capacity']}")


#else:
#   print("No matching courses found.")

src/search.py
gbudanur/itu_dersci
from scraper import scraper
import json
from time import sleep
import random as rand


def update():
    with open("course_abbreviations.json", "r") as jsonfile:
        course_abbreviations = json.load(jsonfile)

    for course_abbreviation in course_abbreviations:
        delay = rand.randint(1, 3) / 10
        sleep(delay)
        scraper(course_abbreviation)


if __name__ == "__main__":
    update()

src/update.py
gbudanur/itu_dersci