Kepler Script

Kepler için notlar

// ==UserScript==
// @name         Kepler Script
// @author       KazuroAkashi
// @match        https://obs.itu.edu.tr/ogrenci/
// @license MIT
// @version 0.0.1.20250201182321
// @namespace https://greasyfork.org/users/1419483
// @description Kepler için notlar
// ==/UserScript==

(function() {
    'use strict';

    async function getJWT() {
        return new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest();
            xhr.open("GET", "https://obs.itu.edu.tr/ogrenci/auth/jwt");

            xhr.onload = () => {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    resolve(xhr.responseText);
                } else {
                    reject(xhr.status);
                }
            };
            xhr.send();
        })
    }

    async function getDonemListesi(jwt) {
        return new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest();
            xhr.open("GET", "https://obs.itu.edu.tr/api/ogrenci/DonemListesi");
            xhr.setRequestHeader("Authorization", "Bearer " + jwt);

            xhr.onload = () => {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    resolve(JSON.parse(xhr.response));
                } else {
                    reject(xhr.status);
                }
            };
            xhr.send();
        })
    }

    async function getSinifListesi(jwt, donemId) {
        return new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest();
            xhr.open("GET", "https://obs.itu.edu.tr/api/ogrenci/sinif/KayitliSinifListesi/" + donemId);
            xhr.setRequestHeader("Authorization", "Bearer " + jwt);

            xhr.onload = () => {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    resolve(JSON.parse(xhr.response));
                } else {
                    reject(xhr.status);
                }
            };
            xhr.send();
        })
    }

    async function getHarfNotuListesi(jwt, donemId) {
        return new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest();
            xhr.open("GET", "https://obs.itu.edu.tr/api/ogrenci/Sinif/SinifHarfNotuListesi/" + donemId);
            xhr.setRequestHeader("Authorization", "Bearer " + jwt);

            xhr.onload = () => {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    const arr = JSON.parse(xhr.response).sinifHarfNotuResultList;
                    const obj = {};

                    for (const not of arr) {
                        obj[not.crn] = not.harfNotu;
                    }

                    resolve(obj);
                } else {
                    reject(xhr.status);
                }
            };
            xhr.send();
        })
    }

    async function getNotListesi(jwt, sinifId) {
        return new Promise((resolve, reject) => {
            const xhr = new XMLHttpRequest();
            xhr.open("GET", "https://obs.itu.edu.tr/api/ogrenci/Sinif/SinifDonemIciNotListesi/" + sinifId);
            xhr.setRequestHeader("Authorization", "Bearer " + jwt);

            xhr.onload = () => {
                if (xhr.readyState == 4 && xhr.status == 200) {
                    resolve(JSON.parse(xhr.response));
                } else {
                    reject(xhr.status);
                }
            };
            xhr.send();
        })
    }

    String.prototype.formatStr = String.prototype.formatStr ||
        function () {
        "use strict";
        var str = this.toString();
        if (arguments.length) {
            var t = typeof arguments[0];
            var key;
            var args = ("string" === t || "number" === t) ?
                Array.prototype.slice.call(arguments)
            : arguments[0];

            for (key in args) {
                str = str.replace(new RegExp("\\{" + key + "\\}", "gi"), args[key]);
            }
        }

        return str;
    };

    const htmlParser = new DOMParser();
    function createHTMLElement(str) {
        const doc = htmlParser.parseFromString(str, "text/html");
        return doc.body.firstChild;
    }

    function insertBeforeHTMLElement(str, el) {
        const insert = createHTMLElement(str);
        el.parentElement.insertBefore(insert, el);
    }

    const newOptionTemplate = `
<option value="{id}">{name}</option>
`;

    const newCardTemplate = `
<div class="row">
  <div class="col-md-12 mb-5">
    <div class="card info-graphic info-graphic--service">
      <div class="card-body">
        <h2>Notlar</h2>
        <h4 style="font-weight: 600; margin-left: 10px;">Yeni girilen: <span style="color: {3}">{2}</span></h4>
        <button id="notscript-notifperm">Bildirimleri Aç</button>
        <select id="notscript-donemlist" class="form-control" style="margin: 10px">
          {1}
        </select>
        <div class="row" style="justify-content: center; align-items: center;">
          <input id="notscript-hidetrivial" type="checkbox">
          <label for="notscript-hidetrivial" style="padding-left: 10px; margin: 0; user-select: none;">Hiç not girilmemiş dersleri gizle</label>
        </div>
        {0}
      </div>
    </div>
  </div>
</div>
`;

    const newDonemTemplate = `
<div style="display: none" class="notscript-donem" data-id={id}>{classes}</div>
`;

    const newClassTemplateTable = `
<div class="col-lg-12 mb-3 notscript-class" data-crn="{crn}">
  <h4 style="font-weight: 600; position: relative;"><span id="notscript-updatedot-{crn}" style="display: none; position: absolute; left: -15px; width: 6px; height: 6px; background: green; border-radius: 50%; top: 7px; box-shadow: 0 0 2px 2px rgb(from green r g b / 0.5);"></span>{name}</h4>
  <div class="table-vertical table-vertical--unheight">
    <table class="table table-striped table-bordered" style="table-layout: fixed">
      <tbody>
        {notes}
        <tr>
          <th class="title" style="width: 40%">Ağırlıklı Ortalama</th>
          <td>{average}</td>
        </tr>
        <tr style="display: {harfnotudisp}">
          <th class="title" style="width: 40%">Harf Notu</th>
          <td style="text-shadow: 0 0 4px rgb(from {harfnotucolor} r g b / .4); font-weight: 600; font-size: 2rem; color: {harfnotucolor}">{harfnotu}</td>
        </tr>
      </tbody>
    </table>
  </div>
</div>
`;

    const newNoteTemplateTable = `
<tr>
  <th class="title" style="width: 40%">{name} - %{perc} - Sıra: {pos}/{enrolled} - Ort. {avg} - S.Sap {devi}</th>
  <td>{note}</td>
</tr>
`;

    const classLineTemplate = `
<div id="notscript-line-{crn}" style="width: 100%; height: 1px; background: #358aed; margin-bottom: 15px; margin-top: 13px;"></div>
`;

    const donems = {};

    const changes = [];
    const changescrn = [];

    async function generateDonemElement(donemId, hidecrns, jwt) {
        const sinifListesi = (await getSinifListesi(jwt, donemId)).kayitSinifResultList;
        const harfNotuListesi = (await getHarfNotuListesi(jwt, donemId));

        let classesEl = "";
        for (const sinif of sinifListesi) {
            if (classesEl !== "") classesEl += classLineTemplate.formatStr({ crn: sinif.crn });

            const sinifNameEn = sinif.bransKodu + sinif.dersKodu + " - " + sinif.dersAdiEN + " (CRN: " + sinif.crn + ")";
            const sinifNameTr = sinif.bransKodu + sinif.dersKodu + " - " + sinif.dersAdiTR + " (CRN: " + sinif.crn + ")";
            const sinifId = sinif.sinifId;

            const notListesiObj = (await getNotListesi(jwt, sinifId));
            const notListesi = notListesiObj.sinifDonemIciNotListesi;
            const ortalama = notListesiObj.ortalama;

            const savedOrtalama = window.localStorage.getItem("crn" + sinif.crn + ".ortalama");
            const savedHarfNotu = window.localStorage.getItem("crn" + sinif.crn + ".harfnotu");

            if (savedOrtalama !== ortalama || (harfNotuListesi[sinif.crn] && savedHarfNotu !== harfNotuListesi[sinif.crn])) {
                changes.push(sinif.dersAdiTR);
                changescrn.push(sinif.crn);
            }

            window.localStorage.setItem("crn" + sinif.crn + ".ortalama", ortalama);
            window.localStorage.setItem("crn" + sinif.crn + ".harfnotu", harfNotuListesi[sinif.crn]);

            window.localStorage.setItem("crn" + sinif.crn + ".notif_ortalama", ortalama);
            window.localStorage.setItem("crn" + sinif.crn + ".notif_harfnotu", harfNotuListesi[sinif.crn]);

            let notesEl = "";
            for (const not of notListesi) {
                const name = not.degerlendirmeOlcutuAdi;
                const perc = not.degerlendirmeKatkisi;
                const note = not.not;
                const pos = not.sinifSirasi;
                const enrolled = not.ogrenciSayisi;
                const avg = not.ortalama;
                const devi = not.standartSapma;

                notesEl += newNoteTemplateTable.formatStr({ name, perc, note, pos, enrolled, avg, devi });
            }

            if (notListesi.length === 0 && !harfNotuListesi[sinif.crn]) {
                hidecrns.push(sinif.crn);
            }

            let harfnotudisp = "none";
            let harfnotu = "";
            let harfnotucolor = "red"
            if (harfNotuListesi[sinif.crn]) {
                harfnotudisp = "";
                harfnotu = harfNotuListesi[sinif.crn];

                if (harfnotu === "AA" || harfnotu === "BL") {
                    harfnotucolor = "#22bb22";
                } else if (harfnotu === "BA+" || harfnotu === "BA" || harfnotu === "BB+" || harfnotu === "BB") {
                    harfnotucolor = "#22dd22"
                } else if (harfnotu === "CB+" || harfnotu === "CB" || harfnotu === "CC+" || harfnotu === "CC") {
                    harfnotucolor = "#aadd22"
                } else if (harfnotu === "DC+" || harfnotu === "DC" || harfnotu === "DD+" || harfnotu === "DD") {
                    harfnotucolor = "#dddd22"
                } else if (harfnotu === "VF" || harfnotu === "FF" || harfnotu === "BZ") {
                    harfnotucolor = "#dd2222"
                }
            }

            classesEl += newClassTemplateTable.formatStr({ crn: sinif.crn, name: sinifNameTr, notes: notesEl, average: ortalama, harfnotudisp, harfnotu, harfnotucolor });
        }

        donems[donemId] = newDonemTemplate.formatStr({ id: donemId, classes: classesEl });
    }

    async function printNotlar() {
        const jwt = await getJWT();
        const donemListesi = (await getDonemListesi(jwt)).ogrenciDonemListesi;
        const sonDonem = donemListesi[donemListesi.length - 1];
        const sonDonemId = sonDonem.akademikDonemId;

        const addBefore = document.querySelectorAll(".obs > .container-fluid > div > .row")[1];

        const hidecrns = [];
        let donemOptList = "";

        for (let i = donemListesi.length - 1; i >= 0; i--) {
            const donem = donemListesi[i];
            donemOptList += newOptionTemplate.formatStr({ id: donem.akademikDonemId, name: donem.akademikDonemAdi });
        }

        await generateDonemElement(sonDonemId, hidecrns, jwt);

        const cardEl = newCardTemplate.formatStr(donems[sonDonemId], donemOptList, changes.length === 0 ? "Yok" : changes.join(", "), changes.length === 0 ? "red" : "green");
        const cardEll = createHTMLElement(cardEl);
        addBefore.parentElement.insertBefore(cardEll, addBefore);

        const sonDonemEl = cardEll.querySelector("div[data-id=\"" + sonDonemId + "\"]");

        const donemlistEl = cardEll.querySelector("#notscript-donemlist");

        donemlistEl.onchange = async (e) => {
            const donemElList = cardEll.querySelectorAll(".notscript-donem");

            for (const donem of donemElList) {
                if (donem.dataset.id === donemlistEl.value) donem.style.display = "";
                else donem.style.display = "none";
            }

            if (!donems[donemlistEl.value]) {
                await generateDonemElement(donemlistEl.value, hidecrns, jwt);

                const donemEl = createHTMLElement(donems[donemlistEl.value]);
                sonDonemEl.parentElement.insertBefore(donemEl, sonDonemEl);

                donemEl.style.display = "";
            }
        }
        donemlistEl.onchange();

        for (const changecrn of changescrn) {
            sonDonemEl.querySelector("#notscript-updatedot-"+changecrn).style.display = "";
        }

        const notifpermBtn = cardEll.querySelector("#notscript-notifperm");
        if (Notification.permission === "granted") {
            notifpermBtn.style.display = "none";
        }
        notifpermBtn.onclick = () => {
            Notification.requestPermission().then((perm) => {
                if (perm === "granted") {
                    notifpermBtn.style.display = "none";
                    setInterval(async () => {
                        console.log("Güncelleme kontrol ediliyor...");
                        const sinifListesi = (await getSinifListesi(jwt, sonDonemId)).kayitSinifResultList;
                        const harfNotuListesi = (await getHarfNotuListesi(jwt, sonDonemId));

                        for (const sinif of sinifListesi) {
                            const sinifNameEn = sinif.bransKodu + sinif.dersKodu + " - " + sinif.dersAdiEN + " (CRN: " + sinif.crn + ")";
                            const sinifNameTr = sinif.bransKodu + sinif.dersKodu + " - " + sinif.dersAdiTR + " (CRN: " + sinif.crn + ")";
                            const sinifId = sinif.sinifId;

                            const notListesiObj = (await getNotListesi(jwt, sinifId));
                            const notListesi = notListesiObj.sinifDonemIciNotListesi;
                            const ortalama = notListesiObj.ortalama;

                            const savedOrtalama = window.localStorage.getItem("crn" + sinif.crn + ".notif_ortalama");
                            const savedHarfNotu = window.localStorage.getItem("crn" + sinif.crn + ".notif_harfnotu");

                            if (savedOrtalama !== ortalama || (harfNotuListesi[sinif.crn] && savedHarfNotu !== harfNotuListesi[sinif.crn])) {
                                new Notification(sinif.dersAdiTR + " notunda güncelleme var!");
                            }

                            window.localStorage.setItem("crn" + sinif.crn + ".notif_ortalama", ortalama);
                            window.localStorage.setItem("crn" + sinif.crn + ".notif_harfnotu", harfNotuListesi[sinif.crn]);
                        }
                    }, 30000);
                }
            });
        }

        const hidetrivialEl = cardEll.querySelector("#notscript-hidetrivial");
        hidetrivialEl.checked = window.localStorage.getItem("hide_trivial_classes");

        const classes = cardEll.querySelectorAll(".notscript-class");

        hidetrivialEl.onchange = (e) => {
            const trivialClasses = classes.values().filter(cl => hidecrns.includes(cl.dataset.crn)).toArray();
            const disp = hidetrivialEl.checked ? "none" : "";
            window.localStorage.setItem("hide_trivial_classes", hidetrivialEl.checked);
            for (const cl of trivialClasses) {
                cl.style.display = disp;

                const line = cardEll.querySelector("#notscript-line-" + cl.dataset.crn);
                line.style.display = disp;
            }
        }

        hidetrivialEl.onchange();

        notifpermBtn.onclick();
    }

    printNotlar();


})();