const { DateTime } = require("luxon"); const fetch = require("node-fetch"); async function request(endpoint, body) { const res = await fetch(endpoint.startsWith("https://") ? endpoint : "https://www.ssps.cz/" + endpoint, { method: body ? "POST" : "GET", body, headers: { accept: "application/json", "user-agent": "Discord Bot (https://danbulant.eu Daniel Bulant bulant.da.2021@ssps.cz)", "x-notes": "Zobrazeni informaci o skole v ramci Discordu" } }); return await res.json(); } /** * @typedef AbsentClass * @property {ClassEntity} Entity * @property {(null | "ODP")[]} Reasons */ /** * @typedef ClassEntity * @property {string} Id * @property {string} Abbrev */ /** * @typedef ChangedClass * @property {ClassEntity} Class * @property {ChangedLesson[]} ChangedLessons * @property {ChangedLesson[]} CancelledLessons * @property {string[]} ChangedGroups */ /** * @typedef ChangedLesson * @property {"supluje" | "přesun >>" | "přesun <<" | "spojí"} ChgType1 * @property {string} ChgType2 * @property {string} Hour Číslo ale ve stringu * @property {string} Subject 3 letter code in uppercase * @property {string} Group Empty if all * @property {string} [Room] Room number * @property {string} [Teacher] Teacher code (Abbrev) */ class Supplementations { constructor(data) { /** @type {{ AbsentClasses: AbsentClass[], ChangesForClasses: ChangedClass[]}} */ this.data = data; } getByClassName(name) { const absent = this.data.AbsentClasses.filter(t => t.Entity.Abbrev === name); const changed = this.data.ChangesForClasses.filter(t => t.Class.Abbrev === name); return { absent, changed }; } getByTeacher(abbrev) { return this.data.ChangesForTeachers.filter(t => t.Teacher.Abbrev === abbrev || t.Teacher.Name === abbrev); } } /** * @typedef Atom * @property {string} Id * @property {string} Abbrev * @property {string} Name */ /** * @typedef CellAtom * @property {Atom} Class * @property {Atom} Group * @property {Atom} Subject * @property {Atom} Teacher * @property {Atom} Room * @property {Atom[]} Cycles * @property {Atom[]} Stamps */ class Schedule { /** @type {(CellAtom | CellAtom[])[][]} */ schedule = []; constructor(data) { this.data = data; for(var cell of data.Cells) { if(!this.schedule[cell.DayIndex]) this.schedule[cell.DayIndex] = []; this.schedule[cell.DayIndex][cell.HourIndex] = cell.Atoms; } } get type() { return this.data.Type; } get id() { return this.data.TargetId; } } /** * @typedef WPPost * @property {number} id * @property {string} date ISO date * @property {string} date_gmt * @property {{ rendered: string }} guild * @property {string} modified ISO date * @property {string} modified_gmt * @property {string} slug Used in pretty URLs * @property {string} status Always publish in public API * @property {"post"} type * @property {string} link * @property {{ rendered: string }} title * @property {{ rendered: string, protected: boolean }} content * @property {{ rendered: string, protected: boolean }} excerpt * @property {number} author * @property {number} featured_media * @property {"closed" | "open"} comment_status Seems to be always closed * @property {"closed" | "open"} ping_status Seems to be always on * @property {boolean} sticky * @property {string} template * @property {string} format commonly 'standard' * @property {[]} meta ? * @property {number[]} categories * @property {WPPostLinks} _links */ /** * @typedef WPPostLinks * @property {{ href: string }[]} self * @property {{ href: string }[]} collection * @property {{ href: string }[]} about * @property {{ href: string, embeddable: boolean }[]} author * @property {{ href: string, embeddable: boolean }[]} replies * @property {{ href: string, count: number }[]} version-history */ /** * @typedef ProfesniUzivatel * @property {string|null} about * @property {string|null} birthday * @property {boolean} blocked * @property {string|null} class * @property {number} documentId * @property {string} email * @property {string} firstName * @property {string} lastName * @property {number} id * @property {string} imagePath * @property {number} similarity */ class ProfesniSitAPI { async *getUsers() { var errors = 0; var id = 1; while(errors < 15) { /** @type {ProfesniUzivatel|null} */ const res = await request(`https://profesnisit.ssps.cz/user/get-user?id=${id}`); id++; console.log("Trying", id); if(!res || Object.values(res).every(t => !t)) { errors++; console.log("Errored", errors, "times"); continue; } errors = 0; yield res; } } } const letterMap = { "ě": "e", "š": "s", "č": "c", "ř": "r", "ž": "z", "ý": "y", "á": "a", "í": "i", "é": "e", "ó": "o", "ú": "u", "ů": "u" }; class API { request = request; profesniSit = new ProfesniSitAPI; async getSupplementations(date = new DateTime) { const res = await request(`wp-content/themes/ssps-wordpress-theme/supplementation.php/?date=${date.toFormat("yyyyMMdd")}`); return new Supplementations(res); } /** * @returns {Promise} */ async getSchedule(className) { const res = await request(`wp-content/themes/ssps-wordpress-theme/schedule.php/?class=${className}`); return new Schedule(res); } async getNews() { /** @type {WPPost[]} */ const res = await request(`wp-json/wp/v2/posts`); return res; } formatRoom(room) { if(!room) return ""; return room.toString().padStart(3, "0"); } isTeacherMail(mail) { return /[a-z.]+@ssps\.cz/.test(mail); } isStudentMail(mail) { return /[a-z]+\.[a-z]{2}\.20[12][0-9]@ssps\.cz/.test(mail); } buildTeacherMail(name) { if(!Array.isArray(name)) name = name.split(" "); return `${this.removeCestina(name[1] + " " + name[0]).replace(/ /g, ".")}@ssps.cz`.toLowerCase(); } buildStudentMail(name, year) { if(!Array.isArray(name)) name = name.split(" "); return `${this.removeCestina(name[1])}.${this.removeCestina(name[0]).substr(0,2)}.${year}@ssps.cz`.toLowerCase(); } removeCestina(str) { return str.split("").map(t => letterMap[t] || t).join(""); } groups = [ ["celá třída"], ["Skupina 1", "Skupina 2"], ["Cizí jazyk 1", "Cizí jazyk 2"] ] map = { "1A": "1U", "1B": "1V", "1C": "1W", "1G": "1X", "1K": "1Y", "2A": "1P", "2B": "1Q", "2C": "1R", "2L": "1S", "2K": "1T", "3A": "1K", "3B": "1L", "3C": "1M", "3L": "1N", "3K": "1O", "4A": "1E", "4B": "1F", "4C": "1G", "4L": "1I", "4K": "1J" } demap = { "1U": "1A", "1V": "1B", "1W": "1C", "1X": "1G", "1Y": "1K", "1P": "2A", "1Q": "2B", "1R": "2C", "1S": "2L", "1T": "2K", "1K": "3A", "1L": "3B", "1M": "3C", "1N": "3L", "1O": "3K", "1E": "4A", "1F": "4B", "1G": "4C", "1I": "4L", "1J": "4K", } } module.exports = new API;