AUA Courses Plugin

AUA Courses Plugin helps you find and filter courses more quickly and with greater flexibility.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         AUA Courses Plugin 
// @namespace    https://github.com/rezocrypt/aua-utils/
// @version      1.0.0
// @description  AUA Courses Plugin helps you find and filter courses more quickly and with greater flexibility.
// @match        https://auasonis.jenzabarcloud.com/GENSRsC.cfm*
// @run-at       document-end
// @grant        none
// @icon         https://aua.am/favicon.ico
// @author       rezocrypt 
// @license      GPL-3.0
// @supportURL   https://t.me/rezocrypt
// ==/UserScript==

(function () {
    "use strict";

    // ----- Global Variables And Constants -----

    let courses = [];
    let matchingCourses = [];
    let showOnly = false;
    let checkedDays = [];

    const dayCheckboxes = {};
    const availableTimes = [];
    const timeCheckboxes = {};

    const AUA_BLUE = "#002147";

    // This function generates a params object for fetching courses. This is complex because Jenzabar
    // doesn't provide automaticaly correct semester and year parameters so we need to find it by
    // ourselves.
    function getRequestParams(year = null, semester = null) {
        // Defining semesters information
        const semesters = [
            {
                id: "spring",
                index: 3,
                coursesBegin: "January 19",
                coursesEnd: "May 14",
            },
            {
                id: "summer",
                index: 4,
                coursesBegin: "June 15",
                coursesEnd: "August 7",
            },
            {
                id: "fall",
                index: 1,
                coursesBegin: "August 20",
                coursesEnd: "December 9",
            },
        ];

        // Here will be final result stored
        let matchedSemester = null;

        // Finding out for which semester we must show courses now (which idk why jenzabar DIDN'T)
        semesters.forEach((semester, index) => {
            // Parsing dates
            const semesterEndDate = semester.coursesEnd;
            const nextSemesterEndDate =
                semesters[index + 1]?.coursesEnd || semesters[0].coursesEnd;

            // Getting date objects
            const startDate = new Date(`${semesterEndDate}, 1900`);
            const endDate = new Date(
                `${nextSemesterEndDate}, ${1900 + (semesters[index + 1] ? 0 : 1)}`,
            );

            // Defining current date to compare with something but with year 1900
            const currentDate = new Date();
            const currentYear = currentDate.getFullYear();
            let formattedYear = null;
            const compareDate = new Date(`${semesters[0].coursesEnd}, 1900`);

            // Setting full year for comparing
            currentDate.setFullYear(1900);

            // Comparing to get the full year for request which needs to be something like 202526
            if (currentDate <= compareDate) {
                formattedYear = `${currentYear - 1}${currentYear % 100}`;
            } else {
                formattedYear = `${currentYear}${(currentYear % 100) + 1}`;
            }

            // Then setting to one more year if it is actually in spring semester (its too complex here)
            currentDate.setFullYear(1900 + (currentDate <= compareDate ? 1 : 0));

            // Finally checking if we are between the ends of our semester and the following semester
            if (startDate <= currentDate && currentDate <= endDate) {
                matchedSemester = semesters[semesters[index + 1] ? index + 1 : 0];
                matchedSemester["year"] = formattedYear;
            }
        });

        /* EXAMPLE const params = {chkschyear: "202526", chkschsem: "3", search: "" }; */

        // Defining finally the parameters
        const params = {
            chkschyear: year || matchedSemester.year,
            chkschsem: semester || matchedSemester.index,
            search: "",
        };

        // Returning
        return params;
    }

    // Defining initial params for first load of the plugin
    const params = getRequestParams();


    // ========== HTML Generation and Manipulation Logic ==========

    // Here I decided to use one function to create the entire window to keep things organized
    // which is not done in gened-plugin.js.
    function createWindow() {
        // ----- Main Window Section -----

        // Creating main window element inside which everything will be placed
        const win = document.createElement("div");
        win.id = "aua-courses-plugin-window";
        win.style.cssText = `
            position: fixed;
            top: 100px;
            right: 100px;
            height: auto;
            max-height: 80vh;
            width: 300px;
            background: white;
            color: #fff;
            border: 1px solid #333;
            border-radius: 6px;
            font-size: 13px;
            overflow: auto;
            z-index: 999999;
            resize: both;     /* bottom-right corner resize */
        `;



        // ----- Header Section -----

        // Creating header
        const header = document.createElement("div");
        header.style.cssText = `
            background: ${AUA_BLUE};
            color: #ffffff;
            padding: 10px 8px 8px 8px;
            cursor: move;
            border-top-left-radius: 6px;
            border-top-right-radius: 6px;
            position: sticky;
            top: 0;
            z-index: 10;
        `;

        // Creating image container for AUA log
        const imageContainer = document.createElement("div");
        imageContainer.style.cssText = `
            display: flex;
            justify-content: center;
            margin-bottom: 5px;
        `;

        // Creating the actual image using aua.am/favicon.ico in case of change
        const image = document.createElement("img");
        image.src = "https://aua.am/favicon.ico";
        image.style.cssText = `
            width: 32px;
            height: 32px;
        `;
        imageContainer.appendChild(image);

        // Appending image container to header
        header.appendChild(imageContainer);

        // Creating header text for plugin
        const headerText = document.createElement("div");
        headerText.textContent = "AUA Courses Plugin";
        headerText.style.cssText = `
            font-size: 16px;
            font-weight: bold;
            margin-top: 4px;
            text-align: center;
        `;

        // Appending header text to header
        header.appendChild(headerText);

        // Movable behavior which lets to move window by dragging the header
        let dx, dy;
        header.onpointerdown = (e) => {
            dx = e.clientX - win.offsetLeft;
            dy = e.clientY - win.offsetTop;
            document.onpointermove = (e) => {
                win.style.left = e.clientX - dx + "px";
                win.style.top = e.clientY - dy + "px";
            };
            document.onpointerup = () => {
                document.onpointermove = null;
                document.onpointerup = null;
            };
        };

        // Close button of header 
        const closeButton = document.createElement("button");
        closeButton.textContent = "×";
        closeButton.title = "Close Window";
        closeButton.style.cssText = `
            position: absolute;
            top: 8px;
            right: 8px;
            width: 24px;
            height: 24px;
            border: none;
            background: #d32f2f;
            color: #fff;
            font-weight: bold;
            font-size: 16px;
            border-radius: 4px;
            cursor: pointer;
            line-height: 20px;
            text-align: center;
            padding: 0;
            transition: 0.2s;
        `;

        // Adding event listeners to close button for hover effect and click action
        closeButton.addEventListener("mouseenter", () => {
            closeButton.style.opacity = "0.8";
        });
        closeButton.addEventListener("mouseleave", () => {
            closeButton.style.opacity = "1";
        });
        closeButton.addEventListener("click", () => closeWindow());

        // Appending close button to header
        header.appendChild(closeButton);



        // ----- Content Section -----

        // Creating content container inside which will other interactive elements be placed
        const content = document.createElement("div");
        content.style.cssText = `
            padding: 10px;
            text-align: left;
            background: white;
            overflow: auto;
        `;



        // ----- Semester Section -----

        // Defining wrapper inside which will be semester title and select
        const semesterWrapper = document.createElement("div");
        semesterWrapper.style.cssText = `
            display: flex;
            align-items: center;
            margin-top: 10px;
            margin-bottom: 6px;
        `;

        // Defining semester title
        const semesterTitle = document.createElement("div");
        semesterTitle.textContent = "Semester";
        semesterTitle.style.cssText = `
            color: ${AUA_BLUE};
            font-weight: bold;
            font-size: 16px;
        `;

        // Appending semester title to wrapper
        semesterWrapper.appendChild(semesterTitle);

        // Defining semester select where will be semesters as options
        const selectSemester = document.createElement("select");
        selectSemester.id = "semesterSelect";
        selectSemester.style.cssText = `
            margin-left: auto;
            width: 120px;  // adjust width for semester
            border: 1px solid ${AUA_BLUE};
            border-radius: 4px;
            padding: 4px 6px;
            background: #ffffff;
            color: ${AUA_BLUE};
            font-weight: normal;
            font-size: 13px;
            cursor: pointer;
            transition: all 0.2s ease;
        `;

        // Defining semester options (for some reason semester values are 3,4,1 for spring, summer, fall)
        // ask to jenzabar why
        const semesters = [
            { value: "3", text: "Spring" },
            { value: "4", text: "Summer" },
            { value: "1", text: "Fall" },
        ];

        // Appending semesters as options to select
        semesters.forEach((sem) => {
            const opt = document.createElement("option");
            opt.value = sem.value;
            opt.textContent = sem.text;
            selectSemester.appendChild(opt);
        });


        // Append after 100ms to avoid JCF interference (otherwise JCF overrides it)
        setTimeout(() => {
            semesterWrapper.appendChild(selectSemester);
        }, 100);


        // Appending semesterWrapper to content
        content.appendChild(semesterWrapper);



        // ----- Year Section -----

        // Defining wrapper for year title and input
        const yearWrapper = document.createElement("div");
        yearWrapper.style.cssText = `
            display: flex;
            align-items: center;
            margin-top: 10px;
            margin-bottom: 6px;
        `;

        // Defining year title element
        const yearTitle = document.createElement("div");
        yearTitle.textContent = "Year";
        yearTitle.style.cssText = `
            color: ${AUA_BLUE};
            font-weight: bold;
            font-size: 16px;
        `;

        // Appending year title to wrapper
        yearWrapper.appendChild(yearTitle);

        // Defining year input element (first I was going to use select but 
        // someone may want to enter custom year like 202021 and that would be impossible.
        // So input is better here (at least I think so).    
        const inputYear = document.createElement("input");
        inputYear.id = "yearInput";
        inputYear.type = "text";
        inputYear.value = params.chkschyear;
        inputYear.style.cssText = `
            margin-left: auto;
            width: 120px;  
            border: 1px solid ${AUA_BLUE};
            border-radius: 4px;
            padding: 4px 6px;
            background: #ffffff;
            color: ${AUA_BLUE};
            font-weight: normal;
            font-size: 13px;
            box-sizing: border-box;
            cursor: text;
            transition: all 0.2s ease;
        `;

        // Appending year input to wrapper
        yearWrapper.appendChild(inputYear);

        // Append after 100ms to avoid JCF interference
        yearTitle.insertAdjacentElement("afterend", inputYear);

        // Appending yearWrapper to content
        content.appendChild(yearWrapper);


        // ----- Reload Button Section -----

        // This butto sends data to fetchCourses function to fetch courses with current semester and year
        // that user entered in inputs selectSemester and inputYear.
        const reloadButton = document.createElement("button");
        reloadButton.textContent = "Reload";
        reloadButton.style.cssText = `
            width: 100%;
            margin-top: 10px;
            margin-bottom: 5px;
            padding: 8px;
            border: 1px solid ${AUA_BLUE};
            background: #fff;
            cursor: pointer;
            color: ${AUA_BLUE};
            font-weight: bold;
            transition: all 0.2s ease;
        `;

        // Appending relad button to content
        content.appendChild(reloadButton);

        // Hover effect
        reloadButton.addEventListener("mouseenter", () => {
            reloadButton.style.background = AUA_BLUE;
            reloadButton.style.color = "#fff";
        });
        reloadButton.addEventListener("mouseleave", () => {
            reloadButton.style.background = "#fff";
            reloadButton.style.color = AUA_BLUE;
        });

        // Click effect
        reloadButton.addEventListener("mousedown", () => {
            reloadButton.style.transform = "scale(0.97)";
        });
        reloadButton.addEventListener("mouseup", () => {
            reloadButton.style.transform = "scale(1)";
        });

        // The event that calls fetchCourses function
        reloadButton.addEventListener("click", () => fetchCourses());



        // ----- Show Only Matching Button Section -----

        // This button shows only matching courses when clicked and hides non-matching ones.

        // Creates container for show only matching button
        const bottomContainer = document.createElement("div");
        bottomContainer.style.cssText = `
            margin-top: 5px;
            padding-top: 6px;
            text-align: center;
        `;

        // Creating the actual button
        const showOnlyButton = document.createElement("button");
        showOnlyButton.innerHTML = `
            Show only matching ( <span style="font-size: 12px;" id="matchedCount">0</span> )
        `;
        showOnlyButton.id = "showOnlyButton";
        showOnlyButton.style.cssText = `
            width: 100%;
            padding: 6px;
            border: 1px solid ${AUA_BLUE};
            border-radius: 4px;
            background: #ffffff;
            color: ${AUA_BLUE} !important;
            font-weight: bold;
            cursor: pointer;
            transition: 0.2s;
            margin-bottom: 15px;
        `;

        // Hover effect
        showOnlyButton.addEventListener("mouseenter", () => {
            showOnlyButton.style.background = AUA_BLUE;
            showOnlyButton.style.color = "#fff";
        });
        showOnlyButton.addEventListener("mouseleave", () => {
            if (!showOnly) {
                showOnlyButton.style.background = "#fff";
                showOnlyButton.style.color = AUA_BLUE;
            }
        });

        // Click effect
        showOnlyButton.addEventListener("mousedown", () => {
            showOnlyButton.style.transform = "scale(0.97)";
        });
        showOnlyButton.addEventListener("mouseup", () => {
            showOnlyButton.style.transform = "scale(1)";
        });

        // Toggle functionality
        showOnlyButton.addEventListener("click", () => {
            // Toggling showOnly variable
            showOnly = !showOnly;

            // Updating button styles based on state
            if (showOnly) {
                showOnlyButton.style.setProperty(
                    "background",
                    `${AUA_BLUE}`,
                    "important",
                );
                showOnlyButton.style.setProperty("color", "#ffffff", "important");
            } else {
                showOnlyButton.style.setProperty("background", "#ffffff", "important");
                showOnlyButton.style.setProperty("color", `${AUA_BLUE}`, "important");
            }

            // Finally calling highlightRows to apply the changes
            highlightRows();
        });

        // Appending show only button to bottom container and then to content
        bottomContainer.appendChild(showOnlyButton);
        content.appendChild(bottomContainer);



        // ----- Search Input Section -----

        // Creating search title
        const searchTitle = document.createElement("div");
        searchTitle.textContent = "Search";
        searchTitle.style.cssText = `
            font-size: 15px;
            font-weight: bold;
            color: #002147;
            margin-bottom: 4px;
        `;
        content.appendChild(searchTitle);

        // Creating search input
        const searchInput = document.createElement("input");
        searchInput.autofocus = true;
        searchInput.type = "text";
        searchInput.id = "searchInput";
        searchInput.placeholder = "Search keywords";
        searchInput.style.cssText = `
            width: 100%;
            box-sizing: border-box;
        `;

        // Using debounce to avoid too many calls while user is typing
        // so if it is typing like f r e s h m a n it won't call highlightRows
        // for each letter but only after user stops typing for 500ms
        let debounceTimer;
        searchInput.addEventListener("input", () => {
            clearTimeout(debounceTimer);
            debounceTimer = setTimeout(() => {
                highlightRows();
            }, 500); // 500ms delay after user stops typing
        });

        // Appending search input to content
        content.appendChild(searchInput);




        // ----- Clear Filters Button Section -----

        // This button clears all filters (search input, day checkboxes, time checkboxes)

        // Creating the actual button
        const clearFiltersButton = document.createElement("button");
        clearFiltersButton.textContent = "Clear Filters";
        clearFiltersButton.style.cssText = `
            width: 100%;
            margin-top: 15px;
            margin-bottom: 5px;
            padding: 8px;
            border: 1px solid ${AUA_BLUE};
            background: #fff;
            cursor: pointer;
            color: ${AUA_BLUE};
            font-weight: bold;
            transition: all 0.2s ease;
        `;

        // Appending clear filters button to content
        content.appendChild(clearFiltersButton);

        // Hover effect
        clearFiltersButton.addEventListener("mouseenter", () => {
            clearFiltersButton.style.background = AUA_BLUE;
            clearFiltersButton.style.color = "#fff";
        });
        clearFiltersButton.addEventListener("mouseleave", () => {
            clearFiltersButton.style.background = "#fff";
            clearFiltersButton.style.color = AUA_BLUE;
        });

        // Click effect
        clearFiltersButton.addEventListener("mousedown", () => {
            clearFiltersButton.style.transform = "scale(0.97)";
        });
        clearFiltersButton.addEventListener("mouseup", () => {
            clearFiltersButton.style.transform = "scale(1)";
        });

        // Call your function
        clearFiltersButton.addEventListener("click", () => clearFilters());




        // ----- Weekdays Filter (TOGGLE) Section -----

        // Weekdays title (clickable for toggle)
        const weekdaysTitle = document.createElement("div");
        weekdaysTitle.style.cssText = `
            color: ${AUA_BLUE};
            font-weight: bold;
            margin-top: 20px;
            margin-bottom: 6px;
            font-size: 16px;
            cursor: pointer;
            user-select: none;
            display: flex;
            align-items: center;
            gap: 6px;
        `;

        // Arrow for toggling the weekdays container as dropdown
        const arrow = document.createElement("span");
        arrow.textContent = "▾"; // open
        arrow.style.fontSize = "12px";

        // Title text (I played lot with Chatgpt that's why it is created separately two titles)
        const titleText = document.createElement("span");
        titleText.textContent = "Weekdays";

        // Appending arrow and title to weekdays title
        weekdaysTitle.appendChild(arrow);
        weekdaysTitle.appendChild(titleText);

        // Appending weekdays title to content
        content.appendChild(weekdaysTitle);

        // Creating container for weekdays' checkboxes
        const weekdaysContainer = document.createElement("div");
        weekdaysContainer.style.cssText = `
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 6px 12px;
            margin-bottom: 10px;
        `;

        // Appending weekdays container to content
        content.appendChild(weekdaysContainer);

        // Logic for toggling weekdays container when clicking the title
        let weekdaysVisible = true;
        weekdaysTitle.addEventListener("click", () => {
            weekdaysVisible = !weekdaysVisible;
            weekdaysContainer.style.display = weekdaysVisible ? "grid" : "none";
            arrow.textContent = weekdaysVisible ? "▾" : "▸";
        });

        // Weekday options (I used these codes because they are present in course times but
        // for names I think it is better to have full names. I haven't use to be determined
        // because it is too long and I do not know CSS good enough).
        const days = [
            { code: "MON", name: "Monday" },
            { code: "TUE", name: "Tuesday" },
            { code: "WED", name: "Wednesday" },
            { code: "THU", name: "Thursday" },
            { code: "FRI", name: "Friday" },
            { code: "SAT", name: "Saturday" },
            { code: "TBD", name: "Unknown" },
        ];

        // Creating checkboxes for each weekday using the days array and foreach
        days.forEach((day) => {
            // Creating label for checkbox and text
            const label = document.createElement("label");
            label.style.cssText = `
                display: flex;
                align-items: center;
                font-size: 13px;
                cursor: pointer;
                color: ${AUA_BLUE};
            `;

            // Creating the actual checkbox element
            const checkbox = document.createElement("input");
            checkbox.type = "checkbox";
            checkbox.value = day.code;
            checkbox.style.marginRight = "6px";

            // Adding event listener to checkbox to update checkedDays and highlight rows when changed
            checkbox.addEventListener("change", () => highlightRows());

            // Appending checkbox and text to label
            label.appendChild(checkbox);
            label.appendChild(document.createTextNode(day.name));

            // Appending label to weekdays container
            weekdaysContainer.appendChild(label);

            // Storing checkbox in dayCheckboxes object for later use and reference
            dayCheckboxes[day.code] = checkbox;
        });





        // ----- Times Filter (TOGGLE) Section -----

        // This is actually complex thing. Depending on available times in courses matching
        // the search and weekdays filters, the available times checkboxes are generated 

        // Times title (clickable for toggle)
        const timesTitle = document.createElement("div");
        timesTitle.style.cssText = `
            color: ${AUA_BLUE};
            font-weight: bold;
            margin-top: 20px;
            margin-bottom: 6px;
            font-size: 16px;
            cursor: pointer;
            user-select: none;
            display: flex;
            align-items: center;
            gap: 6px;
`;

        // Arrow for toggling times container
        const timesArrow = document.createElement("span");
        timesArrow.textContent = "▸"; // collapsed initially
        timesArrow.style.fontSize = "12px";

        // Title text
        const timesText = document.createElement("span");
        timesText.textContent = "Times";

        // Appending arrow and title to times title
        timesTitle.appendChild(timesArrow);
        timesTitle.appendChild(timesText);

        // Appending times title to content
        content.appendChild(timesTitle);

        // Container for actual time options (checkboxes)
        const timesContainer = document.createElement("div");
        timesContainer.id = "timesContainer";
        timesContainer.style.cssText = `
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
            gap: 6px;
            margin-bottom: 10px;
        `;

        // Appending times container to content
        content.appendChild(timesContainer);

        // Logic for toggling times container when clicking the title
        let timesVisible = false; // default not visible
        timesContainer.style.display = "none"; // hide initially
        timesTitle.addEventListener("click", () => {
            timesVisible = !timesVisible;
            timesContainer.style.display = timesVisible ? "grid" : "none";
            timesArrow.textContent = timesVisible ? "▾" : "▸";
        });







        // ----- Footer Section -----

        // This is footer part of the window which contains social icons (yet)
        const footer = document.createElement("div");
        footer.style.cssText = `
            width: 100%;
            background: #002147;
            display: flex;
            justify-content: center;
            align-items: center;
            gap: 14px;
            padding: 10px 0;
            border-bottom-left-radius: 6px;
            border-bottom-right-radius: 6px;
            box-sizing: border-box;
            flex-shrink: 0; /* keep footer height fixed */
        `;

        // Function to create clickable social icons
        function createIcon(src, title, url) {
            // Creating image element for icon
            const icon = document.createElement("img");
            icon.src = src;
            icon.style.cssText = `
                        width: 22px;
                        height: 22px;
                        filter: invert(1);
                        cursor: pointer;
                `;
            icon.title = title;

            // Adding click event to open the url in new tab
            icon.addEventListener("click", () => {
                window.open(url, "_blank");
            });

            // Returning the created icon element
            return icon;
        }

        // Creating GitHub icon using createIcon function
        const githubIcon = createIcon(
            "https://cdn.jsdelivr.net/gh/simple-icons/simple-icons/icons/github.svg",
            "GitHub",
            "https://github.com/rezocrypt/aua-utils",
        );

        // Creating Telegram icon using createIcon function
        const telegramIcon = createIcon(
            "https://cdn.jsdelivr.net/gh/simple-icons/simple-icons/icons/telegram.svg",
            "Telegram",
            "https://t.me/rezocrypt",
        );

        // Adding created social icons to footer
        footer.appendChild(githubIcon);
        footer.appendChild(telegramIcon);



        // ----- Window Assembly Section -----

        // Appending created sections to main window (only big sections because
        // smaller ones are already inside bigger ones)
        win.appendChild(header);
        win.appendChild(content);
        win.appendChild(footer);

        // Finally appending the window to body
        document.body.appendChild(win);
    }


    // ========== HTML Generation and Manipulation Logic ==========




    // ========== Logic Functionality ==========

    // This function is for clearing unnecessary things from the page before loading
    function clearFromUnnecessaryElements() {
        // This is main table where the rows will be REPLACED with new fetched ones (not clearing yet)
        const coursesDiv = document.querySelector(".table-responsive");

        // This form for search is not necessary and must be removed
        document.querySelector(".update-form")?.remove();

        // This heading text Courses By Semester is not necessary and must be removed
        document.querySelector(".heading-wrap")?.remove();
    }

    // Main function that is called when we got fetched
    function afterFetch(html) {
        // Preventing multiple fetches at the same time
        window.nowIsFetching = true;

        // Creating parser that parses the html content to doc variable
        const parser = new DOMParser();
        const doc = parser.parseFromString(html, "text/html");

        // Defining table that we got from fetch and table which we need to replace.
        const coursesTableParsed = doc.getElementById("crsbysemester");
        const coursesDiv = document.querySelector(".table-responsive");

        //Clearing original table content
        if (coursesDiv) {
            coursesDiv.innerHTML = "";
        }

        // Checking if both exist and replacing the original table with fetched one.
        if (coursesTableParsed && coursesDiv) {

            // Appending fetched table to the page (replacing with fetched courses)
            coursesDiv.appendChild(coursesTableParsed);

            // Updating courses array
            updateCoursesArray();
        } else {
            // In case of something went wrong but idk why code enters here because it calls afterFetch few times
            // Maybe because of fetchCourses being called multiple times.
            console.log("Either crsbysemester or table-responsive not found.");

            // Stopping loading animation
            stopLoadingAnimation();

            // Allowing new fetches
            window.nowIsFetching = false;
        }



        // ----- Setting Inputs to Current Semester and Year -----

        // Setting semester and year inputs to current values
        const selectSemester = document.querySelector("#semesterSelect"); // or your reference
        const inputYear = document.querySelector("#yearInput");

        // Change select to current semester and year (if by any reason they are empty)
        if (selectSemester.value === "") {
            selectSemester.value = params.chkschsem;
        }
        if (inputYear.value === "") {
            inputYear.value = params.chkschyear;
        }

        // Clearing elements that are not needed
        clearFromUnnecessaryElements();

        // Generating available times for courses
        generateAvailableTimesOfAllCourses();

        // Parsing available times to checkboxes
        parseAvailableTimesToCheckboxes(availableTimes);

        // Clicking show only matches button for by default showing only matching courses
        document.getElementById("showOnlyButton")?.click();

        // Stopping loading animation
        stopLoadingAnimation();

        // Allowing new fetches
        window.nowIsFetching = true;
    }

    // Fetching to same origin to request courses.
    // By default we get all courses because in params "search=" which means
    // send me all courses. Believe me, now browsers are fast enough in terms of 
    // rendering and performing network requests for each filter it will take 
    // decades. So fetching all courses once and then filtering them on client side is
    // the best approach here (at least I think so).
    async function fetchCourses() {
        // Starting loading animation
        startLoadingAnimation();

        // Getting current values from inputs
        const selectSemester = document.querySelector("#semesterSelect");
        const inputYear = document.querySelector("#yearInput");

        // Getting params from inputs
        const currentParams = getRequestParams(
            inputYear?.value || null,
            selectSemester?.value || null,
        );

        // Fetching with new params FETCH WORKS BECAUSE IT IS NOT CROSS-ORIGIN and that's
        // the reason why I chose to make plugin and not static website.
        fetch("https://auasonis.jenzabarcloud.com/GENSRsC.cfm", {
            method: "POST",
            headers: { "Content-Type": "application/x-www-form-urlencoded" },
            body: new URLSearchParams(currentParams),
        })
            .then((res) => res.text())
            .then(afterFetch)
            .catch((err) => console.log("Error fetching courses:", err));
    }

    // Update courses array from the table
    function updateCoursesArray() {

        // Getting all matching rows
        const rows = getRows();

        // Clearing previous courses
        courses = [];

        // Parsing each row to get course details
        rows.forEach((row) => {

            // Getting all cells in the row
            const cells = row.querySelectorAll("td");

            // Getting title and section spans
            const titleSpan = cells[0].querySelector(".title");
            const sectionSpan = cells[0].querySelector(".crsbysemester_course_id");

            // Defining the course data object where all parsed data will be stored
            const courseObj = {
                name: titleSpan?.textContent.trim() || "",
                code: sectionSpan?.textContent.replace(/[()]/g, "").trim() || "",
                section: cells[1]?.textContent.trim() || "",
                session: cells[2]?.textContent.trim() || "",
                credits: cells[3]?.textContent.trim() || "",
                campus: cells[4]?.textContent.trim() || "",
                instructor: cells[5]?.textContent.trim() || "",
                times: cells[6]?.textContent.trim() || "",
                takenSeats: cells[7]?.textContent.trim() || "",
                deliveryMethod: cells[10]?.textContent.trim() || "",
                location: cells[11]?.textContent.trim() || "",
            };

            // Pushing course object to courses array
            courses.push(courseObj);
        });
    }

    // Function for updating checked weekdays
    function updateCheckedWeekdays() {
        const checkboxKeywords = [];

        // Looping through dayCheckboxes to see which are checked
        // and marking each as keyword (because the codes that i used
        // are present in course times string)
        for (const code in dayCheckboxes) {
            // If checked, add to keywords
            if (dayCheckboxes[code].checked) {
                checkboxKeywords.push(code);
            }
        }

        // Finally updating checkedDays array with new keywords
        checkedDays = checkboxKeywords;
    }

    // Scrolling to nearest matching row
    function scrollToNearestMatching() {

        // Getting all matching rows
        const rows = getMatchingRows();

        // Scrolling to the first visible matching row
        for (const row of rows) {
            if (row.offsetParent !== null) {
                row.scrollIntoView({ behavior: "smooth", block: "center" });
                break;
            }
        }
    }

    // Variable to keep track of last scrolled index for Enter key navigation
    let lastScrolledIndex = -1;

    // Setting up Enter key navigation in search input
    function setupSearchInputEnterNavigation() {
        const searchInputElement = document.getElementById("searchInput");

        searchInputElement.addEventListener("keydown", (e) => {
            if (e.key !== "Enter") return;

            const rows = Array.from(getMatchingRows() || []).filter(row => row.offsetParent !== null);
            if (!rows.length) return;

            // Clear old temporary highlights
            rows.forEach(r => {
                if (r.dataset.tempHighlight === "true") {
                    // Restore the previous color stored in data attribute
                    r.style.backgroundColor = r.dataset.originalBg || "";
                    delete r.dataset.tempHighlight;
                    delete r.dataset.originalBg;
                }
            });

            // Update index
            if (e.shiftKey) {
                lastScrolledIndex = (lastScrolledIndex - 1 + rows.length) % rows.length;
            } else {
                lastScrolledIndex = (lastScrolledIndex + 1) % rows.length;
            }

            const row = rows[lastScrolledIndex];

            // Scroll to row
            row.scrollIntoView({ behavior: "smooth", block: "center" });

            // Temporary highlight
            row.dataset.tempHighlight = "true";
            // Store current color before temporary highlight
            row.dataset.originalBg = getComputedStyle(row).backgroundColor;
            row.style.backgroundColor = "#ffcc33";

            setTimeout(() => {
                row.style.backgroundColor = row.dataset.originalBg;
                delete row.dataset.tempHighlight;
                delete row.dataset.originalBg;
            }, 500);
        });
    }

    // Function for extracting the rows from courses table
    function getRows() {
        // Getting the table
        const table = document.getElementById("crsbysemester");

        // If table not found, return but for sure this will cause lot of errors
        if (!table) return;

        // Getting all rows with matching class
        const rows = table.querySelectorAll("tbody tr");

        // Returning the rows
        return rows;
    }

    // Function for extracting matching rows from courses table
    function getMatchingRows() {
        // Getting the table
        const table = document.getElementById("crsbysemester");

        // If table not found, return but for sure this will cause lot of errors
        if (!table) return;

        // Getting all rows with matching class
        const rows = table.querySelectorAll("tbody tr.matching");

        // Returning the rows
        return rows;
    }

    // Generating available times for courses. It goes through courses
    // and extracts times using regex and adds to availableTimes array
    // if they match the regex and are not already present.
    function generateAvailableTimesOfAllCourses() {
        courses.forEach((course) => {
            // Selecting the matching times using regex. So we will get extracted array 
            // of times like ["9:00am-10:15am", "11:00am-12:15pm"]
            const times = course.times.match(
                /\b\d{1,2}:\d{2}(?:am|pm)-\d{1,2}:\d{2}(?:am|pm)\b/gi,
            );

            // If no times found, skip
            if (times === null) return;

            // Adding each time to availableTimes if not already present
            times.forEach((time) => {
                if (!availableTimes.includes(time)) {
                    availableTimes.push(time);
                }
            });
        });

        // Sorting available times to be from earliest to latest but I forgot
        // to implement proper time sorting so it is just alphabetical for now
        // but I think it is not too complex to fix.
        availableTimes.sort();
    }

    // Parse avialbale times to checkboxes
    function parseAvailableTimesToCheckboxes(availableTimes) {

        // Getting times container where we will add/remove checkboxes
        const timesContainer = document.getElementById("timesContainer");

        // Remove unchecked checkboxes (like this is helpful when from old matching some
        // times are checked by user and its not good to remove them automatically)
        Object.keys(timeCheckboxes).forEach((time) => {
            const checkbox = timeCheckboxes[time];
            if (!checkbox.checked) {
                checkbox.parentElement.remove(); // remove label from DOM
                delete timeCheckboxes[time];
            }
        });

        // Add new checkboxes
        availableTimes.forEach((time) => {

            // Skip if already exists (or checked)
            if (timeCheckboxes[time]) {
                return;
            }

            // Creating label for checkbox and text
            const label = document.createElement("label");
            label.style.cssText = `
                display: flex;
                align-items: center;
                font-size: 13px;
                cursor: pointer;
                color: ${AUA_BLUE};
            `;

            // Creating the actual checkbox element
            const checkbox = document.createElement("input");
            checkbox.type = "checkbox";
            checkbox.value = time;
            checkbox.style.marginRight = "6px";

            // Adding event listener to checkbox to update checkedDays and highlight rows when changed
            checkbox.addEventListener("change", () => highlightRows("time-filter"));

            // Appending checkbox and text to label
            label.appendChild(checkbox);
            label.appendChild(document.createTextNode(time));

            // Appending label to times container
            timesContainer.appendChild(label);

            // Storing checkbox in timeCheckboxes object for later use and reference
            timeCheckboxes[time] = checkbox;
        });

    }


    // Function for updating matched count
    function updateMatchedCount() {
        const matchedCountSpan = document.getElementById("matchedCount");
        if (matchedCountSpan) {
            matchedCountSpan.textContent = matchingCourses.length;
        }
    }

    // Function for closing the plugin window
    function closeWindow() {
        // Getting the window element
        const win = document.getElementById("aua-courses-plugin-window");
        if (win) win.remove();

        // Creating notification element to notify that this is one time action and after
        // refreshing it will still be enabled.
        const notif = document.createElement("div");
        notif.innerHTML = "⚠ Plugin not disabled! Disable it manually from the <b>Extension</b> or from <b>Tampermonkey</b>.";
        notif.style.cssText = `
            position: fixed;
            top: 20px;
            left: 50%;
            transform: translateX(-50%) translateY(-20px);
            background: #fff3cd;
            color: #856404;
            padding: 10px 20px;
            border-left: 5px solid #ffc107;
            border-radius: 5px;
            font-size: 14px;
            box-shadow: 0 2px 6px rgba(0,0,0,0.15);
            opacity: 0;
            transition: opacity 0.4s ease, transform 0.4s ease;
            z-index: 9999;
            max-width: 90%;
            text-align: center;
            font-family: sans-serif;
        `;

        // Appending notification to body
        document.body.appendChild(notif);

        // Triggering animation
        requestAnimationFrame(() => {
            notif.style.opacity = "1";
            notif.style.transform = "translateX(-50%) translateY(0)";
        });

        // Removing notification after 4 seconds
        setTimeout(() => {
            notif.style.opacity = "0";
            notif.style.transform = "translateX(-50%) translateY(-20px)";
            notif.addEventListener("transitionend", () => notif.remove());
        }, 4000);
    }


    // Function for clearing all filters
    function clearFilters() {
        // Clear search input
        const searchInput = document.getElementById("searchInput");
        if (searchInput) {
            searchInput.value = "";
        }

        // Clear day checkboxes
        for (const code in dayCheckboxes) {
            dayCheckboxes[code].checked = false;
        }

        // Clear time checkboxes
        for (const time in timeCheckboxes) {
            timeCheckboxes[time].checked = false;
        }

        // Update checked days
        updateCheckedWeekdays();

        // Highlight rows again
        highlightRows();
    }


    // Start loading animation (which is AUA logo just rotating in the middle with blue border)
    function startLoadingAnimation() {

        // If it exists already, do not create another
        if (document.getElementById("centerLoader")) return;

        // Creating the main loader element
        const loader = document.createElement("div");
        loader.id = "centerLoader";
        loader.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            width: 50px;
            height: 50px;
            border: 5px solid #e0e0e0;
            border-top: 5px solid #002147;
            border-radius: 50%;
            animation: spinLoader 1s linear infinite;
            display: flex;
            justify-content: center;
            align-items: center;
            z-index: 9999;
        `;

        // Creating the image element inside loader
        const img = document.createElement("img");
        img.src = "https://play-lh.googleusercontent.com/lPU7TDhLFw2IfDxV_lEFw-YGSXnN7SgR-ybL4hQsNneceUR3mNxcwKV4NdZuKBf1daU=w240-h480-rw";
        img.style.cssText = `
            width: 80%;
            height: 80%;
            border-radius: 50%;
        `;

        // Appending image to loader
        loader.appendChild(img);

        // Appending loader to body
        document.body.appendChild(loader);

        // Adding keyframes for spin animation if not already present
        if (!document.getElementById("centerLoaderKeyframes")) {
            const style = document.createElement("style");
            style.id = "centerLoaderKeyframes";
            style.textContent = `
                @keyframes spinLoader {
                    0% { transform: translate(-50%, -50%) rotate(0deg); }
                    100% { transform: translate(-50%, -50%) rotate(360deg); }
                }
            `;

            // Appending style to head
            document.head.appendChild(style);
        }
    }

    // Function that stops loading animation (by just removing the loader element)
    function stopLoadingAnimation() {
        // Defining loader element
        const loader = document.getElementById("centerLoader");

        // Removing loader if exists
        if (loader) loader.remove();
    }

    // Function for highlighting rows based on search and filters
    function highlightRows(calledBy = null) {
        console.log("I at least entered highlightRows");
        // Defining array for available times
        let availableTimes = [];

        // Clearing previous matching courses
        matchingCourses = [];



        // ----- Search Input -----

        // Getting as text
        const searchInputText = document
            .getElementById("searchInput")
            .value.toLowerCase();

        // Extracting keywords from search input
        const searchInputKeywords = searchInputText
            .split(" ")
            .filter((kw) => kw.trim() !== "");



        // Updating checked weekdays
        updateCheckedWeekdays();

        // Getting matching rows
        const rows = getRows();

        // Iterating through rows and courses
        rows.forEach((row, index) => {
            const course = courses[index];
            let searchMatches = true;

            //------Search matching------

            // Checking each keyword from search input to see if it is present
            for (const keyword of searchInputKeywords) {
                const classKeywords = [];

                // Collect all fields to search in
                for (const key in course) {
                    if (course.hasOwnProperty(key)) {
                        classKeywords.push(course[key].toLowerCase());
                    }
                }

                // Check if keyword is found in any field
                let foundInAnyField = classKeywords.some((field) =>
                    field.includes(keyword),
                );

                // If keyword not found in any field, mark searchMatches as false
                if (!foundInAnyField && searchInputKeywords.length > 0) {
                    searchMatches = false;
                }
            }



            //------Weekdays matching------

            // The variable that will decide weather daysMatch has happend
            let daysMatch = false;

            // If no weekdays are checked, consider it a match (because maybe user just ignored that filter)
            if (checkedDays.length === 0) {
                daysMatch = true; // If no days are checked, consider it a match
            } else {
                for (const day of checkedDays) {
                    // If includes at least one checked day, it's a match
                    if (course.times.includes(day)) {
                        daysMatch = true;
                        break;
                    }
                }
            }



            //------Times matching------

            // The variable that will decide weather timesMatch has happend
            let timesMatch = false;

            // Getting checked times as array from timeCheckboxes where are only checked ones
            const checkedTimes = Object.keys(timeCheckboxes).filter(
                (time) => timeCheckboxes[time].checked,
            );

            // If no times are checked, consider it a match (because maybe user just ignored that filter)
            if (checkedTimes.length === 0) {
                timesMatch = true; // If no times are checked, consider it a match
            } else {
                for (const time of checkedTimes) {
                    if (course.times.includes(time)) {
                        timesMatch = true;
                        break;
                    }
                }
            }



            // ----- Final Decision -----

            // I think its best to check if all three matches are true. Also if you think it would 
            // be useful you can add option to choose AND/OR logic between these three filters but idk,
            // it will make everything more complicated.
            let finalMatch = searchMatches && (daysMatch && timesMatch);

            // The actual logic if finalMatch has happened
            if (finalMatch) {

                // Highlighting row if any filter is active (because if no filter is active, all rows are matching)
                if (!(searchInputKeywords.length === 0 && checkedDays.length === 0 && checkedTimes.length === 0)) {
                    row.style.setProperty("background-color", "#ffff99", "important"); // Highlight color
                }
                else {
                    row.style.setProperty("background-color", "", "important"); // Reset color
                }

                // Showing row
                row.style.display = "";

                // Adding matching class and removing not-matching class
                row.classList.add("matching");
                row.classList.remove("not-matching");

                // Adding course to matchingCourses array
                matchingCourses.push(course);

                // Extracting available times from matching courses only if not called by time-filter
                // because if one time checkbox is clicked it will remove all others from available times
                if (calledBy !== "time-filter") {
                    const times = course.times.match(
                        /\b\d{1,2}:\d{2}(?:am|pm)-\d{1,2}:\d{2}(?:am|pm)\b/gi,
                    );
                    if (times !== null) {
                        times.forEach((time) => {
                            if (!availableTimes.includes(time)) {
                                availableTimes.push(time);
                            }
                        });
                    }
                }
            } else {
                // Resetting any highlight
                row.style.setProperty("background-color", "", "important"); // Reset color
                row.style.display = showOnly ? "none" : ""; // Hide if show only matching is active

                // Adding not-matching class and removing matching class
                row.classList.add("not-matching");
                row.classList.remove("matching");
            }
        });


        // Scrolling to nearest matching row only if called not by time-filter
        scrollToNearestMatching();

        // After performing search
        availableTimes.sort();

        // Parsing available times to checkboxes only if not called by time-filter 
        // because it will remove all unchecked ones
        if (calledBy !== "time-filter") {
            parseAvailableTimesToCheckboxes(availableTimes);
        }

        // Updating matched count
        updateMatchedCount();
    }

    // Initializing the plugin
    updateCheckedWeekdays();
    fetchCourses();
    createWindow();
    setupSearchInputEnterNavigation();

})();