Kepler Script

Kepler için notlar

  1. // ==UserScript==
  2. // @name Kepler Script
  3. // @author KazuroAkashi
  4. // @match https://obs.itu.edu.tr/ogrenci/
  5. // @license MIT
  6. // @version 0.0.1.20250201182321
  7. // @namespace https://greasyfork.org/users/1419483
  8. // @description Kepler için notlar
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. async function getJWT() {
  15. return new Promise((resolve, reject) => {
  16. const xhr = new XMLHttpRequest();
  17. xhr.open("GET", "https://obs.itu.edu.tr/ogrenci/auth/jwt");
  18.  
  19. xhr.onload = () => {
  20. if (xhr.readyState == 4 && xhr.status == 200) {
  21. resolve(xhr.responseText);
  22. } else {
  23. reject(xhr.status);
  24. }
  25. };
  26. xhr.send();
  27. })
  28. }
  29.  
  30. async function getDonemListesi(jwt) {
  31. return new Promise((resolve, reject) => {
  32. const xhr = new XMLHttpRequest();
  33. xhr.open("GET", "https://obs.itu.edu.tr/api/ogrenci/DonemListesi");
  34. xhr.setRequestHeader("Authorization", "Bearer " + jwt);
  35.  
  36. xhr.onload = () => {
  37. if (xhr.readyState == 4 && xhr.status == 200) {
  38. resolve(JSON.parse(xhr.response));
  39. } else {
  40. reject(xhr.status);
  41. }
  42. };
  43. xhr.send();
  44. })
  45. }
  46.  
  47. async function getSinifListesi(jwt, donemId) {
  48. return new Promise((resolve, reject) => {
  49. const xhr = new XMLHttpRequest();
  50. xhr.open("GET", "https://obs.itu.edu.tr/api/ogrenci/sinif/KayitliSinifListesi/" + donemId);
  51. xhr.setRequestHeader("Authorization", "Bearer " + jwt);
  52.  
  53. xhr.onload = () => {
  54. if (xhr.readyState == 4 && xhr.status == 200) {
  55. resolve(JSON.parse(xhr.response));
  56. } else {
  57. reject(xhr.status);
  58. }
  59. };
  60. xhr.send();
  61. })
  62. }
  63.  
  64. async function getHarfNotuListesi(jwt, donemId) {
  65. return new Promise((resolve, reject) => {
  66. const xhr = new XMLHttpRequest();
  67. xhr.open("GET", "https://obs.itu.edu.tr/api/ogrenci/Sinif/SinifHarfNotuListesi/" + donemId);
  68. xhr.setRequestHeader("Authorization", "Bearer " + jwt);
  69.  
  70. xhr.onload = () => {
  71. if (xhr.readyState == 4 && xhr.status == 200) {
  72. const arr = JSON.parse(xhr.response).sinifHarfNotuResultList;
  73. const obj = {};
  74.  
  75. for (const not of arr) {
  76. obj[not.crn] = not.harfNotu;
  77. }
  78.  
  79. resolve(obj);
  80. } else {
  81. reject(xhr.status);
  82. }
  83. };
  84. xhr.send();
  85. })
  86. }
  87.  
  88. async function getNotListesi(jwt, sinifId) {
  89. return new Promise((resolve, reject) => {
  90. const xhr = new XMLHttpRequest();
  91. xhr.open("GET", "https://obs.itu.edu.tr/api/ogrenci/Sinif/SinifDonemIciNotListesi/" + sinifId);
  92. xhr.setRequestHeader("Authorization", "Bearer " + jwt);
  93.  
  94. xhr.onload = () => {
  95. if (xhr.readyState == 4 && xhr.status == 200) {
  96. resolve(JSON.parse(xhr.response));
  97. } else {
  98. reject(xhr.status);
  99. }
  100. };
  101. xhr.send();
  102. })
  103. }
  104.  
  105. String.prototype.formatStr = String.prototype.formatStr ||
  106. function () {
  107. "use strict";
  108. var str = this.toString();
  109. if (arguments.length) {
  110. var t = typeof arguments[0];
  111. var key;
  112. var args = ("string" === t || "number" === t) ?
  113. Array.prototype.slice.call(arguments)
  114. : arguments[0];
  115.  
  116. for (key in args) {
  117. str = str.replace(new RegExp("\\{" + key + "\\}", "gi"), args[key]);
  118. }
  119. }
  120.  
  121. return str;
  122. };
  123.  
  124. const htmlParser = new DOMParser();
  125. function createHTMLElement(str) {
  126. const doc = htmlParser.parseFromString(str, "text/html");
  127. return doc.body.firstChild;
  128. }
  129.  
  130. function insertBeforeHTMLElement(str, el) {
  131. const insert = createHTMLElement(str);
  132. el.parentElement.insertBefore(insert, el);
  133. }
  134.  
  135. const newOptionTemplate = `
  136. <option value="{id}">{name}</option>
  137. `;
  138.  
  139. const newCardTemplate = `
  140. <div class="row">
  141. <div class="col-md-12 mb-5">
  142. <div class="card info-graphic info-graphic--service">
  143. <div class="card-body">
  144. <h2>Notlar</h2>
  145. <h4 style="font-weight: 600; margin-left: 10px;">Yeni girilen: <span style="color: {3}">{2}</span></h4>
  146. <button id="notscript-notifperm">Bildirimleri Aç</button>
  147. <select id="notscript-donemlist" class="form-control" style="margin: 10px">
  148. {1}
  149. </select>
  150. <div class="row" style="justify-content: center; align-items: center;">
  151. <input id="notscript-hidetrivial" type="checkbox">
  152. <label for="notscript-hidetrivial" style="padding-left: 10px; margin: 0; user-select: none;">Hiç not girilmemiş dersleri gizle</label>
  153. </div>
  154. {0}
  155. </div>
  156. </div>
  157. </div>
  158. </div>
  159. `;
  160.  
  161. const newDonemTemplate = `
  162. <div style="display: none" class="notscript-donem" data-id={id}>{classes}</div>
  163. `;
  164.  
  165. const newClassTemplateTable = `
  166. <div class="col-lg-12 mb-3 notscript-class" data-crn="{crn}">
  167. <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>
  168. <div class="table-vertical table-vertical--unheight">
  169. <table class="table table-striped table-bordered" style="table-layout: fixed">
  170. <tbody>
  171. {notes}
  172. <tr>
  173. <th class="title" style="width: 40%">Ağırlıklı Ortalama</th>
  174. <td>{average}</td>
  175. </tr>
  176. <tr style="display: {harfnotudisp}">
  177. <th class="title" style="width: 40%">Harf Notu</th>
  178. <td style="text-shadow: 0 0 4px rgb(from {harfnotucolor} r g b / .4); font-weight: 600; font-size: 2rem; color: {harfnotucolor}">{harfnotu}</td>
  179. </tr>
  180. </tbody>
  181. </table>
  182. </div>
  183. </div>
  184. `;
  185.  
  186. const newNoteTemplateTable = `
  187. <tr>
  188. <th class="title" style="width: 40%">{name} - %{perc} - Sıra: {pos}/{enrolled} - Ort. {avg} - S.Sap {devi}</th>
  189. <td>{note}</td>
  190. </tr>
  191. `;
  192.  
  193. const classLineTemplate = `
  194. <div id="notscript-line-{crn}" style="width: 100%; height: 1px; background: #358aed; margin-bottom: 15px; margin-top: 13px;"></div>
  195. `;
  196.  
  197. const donems = {};
  198.  
  199. const changes = [];
  200. const changescrn = [];
  201.  
  202. async function generateDonemElement(donemId, hidecrns, jwt) {
  203. const sinifListesi = (await getSinifListesi(jwt, donemId)).kayitSinifResultList;
  204. const harfNotuListesi = (await getHarfNotuListesi(jwt, donemId));
  205.  
  206. let classesEl = "";
  207. for (const sinif of sinifListesi) {
  208. if (classesEl !== "") classesEl += classLineTemplate.formatStr({ crn: sinif.crn });
  209.  
  210. const sinifNameEn = sinif.bransKodu + sinif.dersKodu + " - " + sinif.dersAdiEN + " (CRN: " + sinif.crn + ")";
  211. const sinifNameTr = sinif.bransKodu + sinif.dersKodu + " - " + sinif.dersAdiTR + " (CRN: " + sinif.crn + ")";
  212. const sinifId = sinif.sinifId;
  213.  
  214. const notListesiObj = (await getNotListesi(jwt, sinifId));
  215. const notListesi = notListesiObj.sinifDonemIciNotListesi;
  216. const ortalama = notListesiObj.ortalama;
  217.  
  218. const savedOrtalama = window.localStorage.getItem("crn" + sinif.crn + ".ortalama");
  219. const savedHarfNotu = window.localStorage.getItem("crn" + sinif.crn + ".harfnotu");
  220.  
  221. if (savedOrtalama !== ortalama || (harfNotuListesi[sinif.crn] && savedHarfNotu !== harfNotuListesi[sinif.crn])) {
  222. changes.push(sinif.dersAdiTR);
  223. changescrn.push(sinif.crn);
  224. }
  225.  
  226. window.localStorage.setItem("crn" + sinif.crn + ".ortalama", ortalama);
  227. window.localStorage.setItem("crn" + sinif.crn + ".harfnotu", harfNotuListesi[sinif.crn]);
  228.  
  229. window.localStorage.setItem("crn" + sinif.crn + ".notif_ortalama", ortalama);
  230. window.localStorage.setItem("crn" + sinif.crn + ".notif_harfnotu", harfNotuListesi[sinif.crn]);
  231.  
  232. let notesEl = "";
  233. for (const not of notListesi) {
  234. const name = not.degerlendirmeOlcutuAdi;
  235. const perc = not.degerlendirmeKatkisi;
  236. const note = not.not;
  237. const pos = not.sinifSirasi;
  238. const enrolled = not.ogrenciSayisi;
  239. const avg = not.ortalama;
  240. const devi = not.standartSapma;
  241.  
  242. notesEl += newNoteTemplateTable.formatStr({ name, perc, note, pos, enrolled, avg, devi });
  243. }
  244.  
  245. if (notListesi.length === 0 && !harfNotuListesi[sinif.crn]) {
  246. hidecrns.push(sinif.crn);
  247. }
  248.  
  249. let harfnotudisp = "none";
  250. let harfnotu = "";
  251. let harfnotucolor = "red"
  252. if (harfNotuListesi[sinif.crn]) {
  253. harfnotudisp = "";
  254. harfnotu = harfNotuListesi[sinif.crn];
  255.  
  256. if (harfnotu === "AA" || harfnotu === "BL") {
  257. harfnotucolor = "#22bb22";
  258. } else if (harfnotu === "BA+" || harfnotu === "BA" || harfnotu === "BB+" || harfnotu === "BB") {
  259. harfnotucolor = "#22dd22"
  260. } else if (harfnotu === "CB+" || harfnotu === "CB" || harfnotu === "CC+" || harfnotu === "CC") {
  261. harfnotucolor = "#aadd22"
  262. } else if (harfnotu === "DC+" || harfnotu === "DC" || harfnotu === "DD+" || harfnotu === "DD") {
  263. harfnotucolor = "#dddd22"
  264. } else if (harfnotu === "VF" || harfnotu === "FF" || harfnotu === "BZ") {
  265. harfnotucolor = "#dd2222"
  266. }
  267. }
  268.  
  269. classesEl += newClassTemplateTable.formatStr({ crn: sinif.crn, name: sinifNameTr, notes: notesEl, average: ortalama, harfnotudisp, harfnotu, harfnotucolor });
  270. }
  271.  
  272. donems[donemId] = newDonemTemplate.formatStr({ id: donemId, classes: classesEl });
  273. }
  274.  
  275. async function printNotlar() {
  276. const jwt = await getJWT();
  277. const donemListesi = (await getDonemListesi(jwt)).ogrenciDonemListesi;
  278. const sonDonem = donemListesi[donemListesi.length - 1];
  279. const sonDonemId = sonDonem.akademikDonemId;
  280.  
  281. const addBefore = document.querySelectorAll(".obs > .container-fluid > div > .row")[1];
  282.  
  283. const hidecrns = [];
  284. let donemOptList = "";
  285.  
  286. for (let i = donemListesi.length - 1; i >= 0; i--) {
  287. const donem = donemListesi[i];
  288. donemOptList += newOptionTemplate.formatStr({ id: donem.akademikDonemId, name: donem.akademikDonemAdi });
  289. }
  290.  
  291. await generateDonemElement(sonDonemId, hidecrns, jwt);
  292.  
  293. const cardEl = newCardTemplate.formatStr(donems[sonDonemId], donemOptList, changes.length === 0 ? "Yok" : changes.join(", "), changes.length === 0 ? "red" : "green");
  294. const cardEll = createHTMLElement(cardEl);
  295. addBefore.parentElement.insertBefore(cardEll, addBefore);
  296.  
  297. const sonDonemEl = cardEll.querySelector("div[data-id=\"" + sonDonemId + "\"]");
  298.  
  299. const donemlistEl = cardEll.querySelector("#notscript-donemlist");
  300.  
  301. donemlistEl.onchange = async (e) => {
  302. const donemElList = cardEll.querySelectorAll(".notscript-donem");
  303.  
  304. for (const donem of donemElList) {
  305. if (donem.dataset.id === donemlistEl.value) donem.style.display = "";
  306. else donem.style.display = "none";
  307. }
  308.  
  309. if (!donems[donemlistEl.value]) {
  310. await generateDonemElement(donemlistEl.value, hidecrns, jwt);
  311.  
  312. const donemEl = createHTMLElement(donems[donemlistEl.value]);
  313. sonDonemEl.parentElement.insertBefore(donemEl, sonDonemEl);
  314.  
  315. donemEl.style.display = "";
  316. }
  317. }
  318. donemlistEl.onchange();
  319.  
  320. for (const changecrn of changescrn) {
  321. sonDonemEl.querySelector("#notscript-updatedot-"+changecrn).style.display = "";
  322. }
  323.  
  324. const notifpermBtn = cardEll.querySelector("#notscript-notifperm");
  325. if (Notification.permission === "granted") {
  326. notifpermBtn.style.display = "none";
  327. }
  328. notifpermBtn.onclick = () => {
  329. Notification.requestPermission().then((perm) => {
  330. if (perm === "granted") {
  331. notifpermBtn.style.display = "none";
  332. setInterval(async () => {
  333. console.log("Güncelleme kontrol ediliyor...");
  334. const sinifListesi = (await getSinifListesi(jwt, sonDonemId)).kayitSinifResultList;
  335. const harfNotuListesi = (await getHarfNotuListesi(jwt, sonDonemId));
  336.  
  337. for (const sinif of sinifListesi) {
  338. const sinifNameEn = sinif.bransKodu + sinif.dersKodu + " - " + sinif.dersAdiEN + " (CRN: " + sinif.crn + ")";
  339. const sinifNameTr = sinif.bransKodu + sinif.dersKodu + " - " + sinif.dersAdiTR + " (CRN: " + sinif.crn + ")";
  340. const sinifId = sinif.sinifId;
  341.  
  342. const notListesiObj = (await getNotListesi(jwt, sinifId));
  343. const notListesi = notListesiObj.sinifDonemIciNotListesi;
  344. const ortalama = notListesiObj.ortalama;
  345.  
  346. const savedOrtalama = window.localStorage.getItem("crn" + sinif.crn + ".notif_ortalama");
  347. const savedHarfNotu = window.localStorage.getItem("crn" + sinif.crn + ".notif_harfnotu");
  348.  
  349. if (savedOrtalama !== ortalama || (harfNotuListesi[sinif.crn] && savedHarfNotu !== harfNotuListesi[sinif.crn])) {
  350. new Notification(sinif.dersAdiTR + " notunda güncelleme var!");
  351. }
  352.  
  353. window.localStorage.setItem("crn" + sinif.crn + ".notif_ortalama", ortalama);
  354. window.localStorage.setItem("crn" + sinif.crn + ".notif_harfnotu", harfNotuListesi[sinif.crn]);
  355. }
  356. }, 30000);
  357. }
  358. });
  359. }
  360.  
  361. const hidetrivialEl = cardEll.querySelector("#notscript-hidetrivial");
  362. hidetrivialEl.checked = window.localStorage.getItem("hide_trivial_classes");
  363.  
  364. const classes = cardEll.querySelectorAll(".notscript-class");
  365.  
  366. hidetrivialEl.onchange = (e) => {
  367. const trivialClasses = classes.values().filter(cl => hidecrns.includes(cl.dataset.crn)).toArray();
  368. const disp = hidetrivialEl.checked ? "none" : "";
  369. window.localStorage.setItem("hide_trivial_classes", hidetrivialEl.checked);
  370. for (const cl of trivialClasses) {
  371. cl.style.display = disp;
  372.  
  373. const line = cardEll.querySelector("#notscript-line-" + cl.dataset.crn);
  374. line.style.display = disp;
  375. }
  376. }
  377.  
  378. hidetrivialEl.onchange();
  379.  
  380. notifpermBtn.onclick();
  381. }
  382.  
  383. printNotlar();
  384.  
  385.  
  386. })();