const axios = require("axios"); const EncryptionHelper = require("./EncryptionHelper"); const { Head, Functions, Message, Homework, SchoolYear, TimetableDay, AbsentSummary } = require("./parsers"); class VirtuellerStundenplan { baseUrl = "https://virtueller-stundenplan.de/Reservierung/"; #password; constructor(config) { this.username = config.username; this.#password = config.password; if(config.key) this.key = config.key; this.holidays = config.holidays; } async fetchKey(returnKey=false) { const url = new URL("RestGetKeyphrase.php", this.baseUrl); const req = await axios(url.href, { headers: { Authorization: "Basic " + Buffer.from(this.username + ":" + this.#password).toString('base64'), }, }); this.key = req.data.KeyPhrase; return returnKey ? this.key : true; } /** * Returns all enabled and disabled functions. * * @returns {Functions} */ async getFunctions() { return new Functions(await this.request("RESTEnabledFunctions.php")); } /** * Returns something with Courses. * This Response is currently unparsed. * * @returns {Any[]} */ async getCourseList() { const data = await this.request("RESTKursliste.php"); return { head: new Head(data.Msg), content: data.Kurse, }; } /** * Returns all classes of the school. * * @returns {String[]} */ async getClassList() { const data = await this.request("RESTKlassenliste.php"); return { head: new Head(data.Msg), content: data.Klassen, }; } /** * Returns all Messages. * * @returns {Message[]} */ async getMessages() { const data = await this.request("RESTMessages.php"); let res = []; data.Messages.forEach(message => { res.push(new Message(message)); }); return data; } /** * getHomework response * * @typedef {Object} getHomeworkResponse * @property {Head} head - Info to the response * @property {Homework[]} content */ /** * Returns all Homework (Classbook) entries. * Date arguments are inclusive. * * @param {[String|Date|number]} [from=Date.now()] - First Day * @param {[String|Date|number]} [to=Date.now()] - Last Day * @returns {getHomeworkResponse} getHomework response */ async getHomework(from=new Date(), to=new Date()) { const query = new URLSearchParams(); query.set("KlaBuDateVon", this.getDateString(from)); query.set("KlaBuDateBis", this.getDateString(to)); const data = await this.request("RESTHausaufgaben.php", query); let res = { head: new Head(data.Msg), content: [], }; data.Inhalte.forEach(homework => { res.content.push(new Homework(homework)); }); return res; } /** * getSchoolYears response * * @typedef {Object} getSchoolYearsResponse * @property {Head} head - Info to the response * @property {SchoolYear[]} content */ /** * Returns all school years. * * @returns {getSchoolYearsResponse} getSchoolYears response */ async getSchoolYears() { const data = await this.request("RESTSchuljahre.php"); let res = { head: new Head(data.Msg), content: [], }; data.Schuljahre.forEach(year => { res.content.push(new SchoolYear(year)); }); return res; } /** * Returns own grades (performances). * The response is currently mostly unparsed * * @param {string|number} schoolYear ID of the school year. * @returns {Object} response * @returns {Head} response.head - Info to the response * @returns {Object} response.content */ async getMyGrades(schoolYear) { const query = new URLSearchParams(); query.set("SJ", schoolYear); const data = await this.request("RESTMeineNoten.php", query); return { head: new Head(data.Msg), content: { performances: data.Leistungen, totalPerformances: data.LeistungenGesamt, comment: data.Kommentar, }, }; } /** * getTimetable response * * @typedef {Object} getTimetableResponse * @property {Head} head - Info to the response * @property {TimetableDay[]} content */ /** * Returns own timetable. * * Small note: If you are in only one class, and want to * fetch it with the class name, the response head will * be slightly different -> has a bit less metadata * * @param {[String|Date|number]} [from=Date.now()] - Date or timestamp * @param {[String|Date|number]} [Class] - The class of which timetable should be fetched. unset/null to own timetable. * @returns {getTimetableResponse} getTimetable response */ async getTimetable(from=new Date(), Class=null) { const query = new URLSearchParams(); query.set("KlaBuDateVon", this.getDateString(from)); if(Class) query.set("KLASSE", Class); const data = await this.request(Class ? "RESTKlassenStundenplan.php" : "RESTMeinStundenplan.php", query); let res = { head: new Head(data.Msg), content: [], } for (let i = 0; i < data.Stunden.length; i++) { const day = [ data.Stunden[i], data.StundenF[i], data.StundenR[i], data.StundenK[i], data.CellStyles[i], ]; const hasContent = /[\da-zA-Z]/.test(JSON.stringify(day)); res.content[i] = hasContent ? new TimetableDay(day, this.holidays) : null; } return res; } /** * Returns own timetable. * This function is a alias of getTimetable(), but without the Class option * * @returns {getTimetableResponse} getTimetable response */ async getMyTimetable(from=new Date()) { return await this.getTimetable(from); } /** * Returns own Infos. * This Response is currently unparsed. * * @returns {Object} */ async getMyInfos() { return await this.request("RESTSuSInfo.php"); } /** * Returns own profile picture. * The response is currently mostly unparsed * * @returns {Object} response * @returns {Head} response.head - Info to the response * @returns {Object} response.content */ async getMyPicture() { const data = await this.request("RESTSuSMeinBild.php"); return { head: new Head(data.Msg), content: { schoolName: data.Schulname, picture: data.Bild, }, }; } /** * Returns own BBS ID QR Code. * This Response is currently unparsed. * * @returns {Object} */ async getMyBBSIDQR() { return await this.request("RESTBBSIDQR.php"); } /** * Returns own BBS GUID QR Code. * This Response is currently unparsed. * * @returns {Object} */ async getMyBBSGUID() { return await this.request("RESTBBSIDGUID.php"); } /** * Reports self as sick. * * This Request is untested. * * This Response is currently unparsed. * * @returns {Object} */ async reportAsSick(from=new Date(), to=new Date()) { const query = new URLSearchParams(); query.set("KlaBuDateVon", this.getDateString(from)); query.set("KlaBuDateBis", this.getDateString(to)); return await this.request("RESTSuSKrankmelden.php", query); } /** * getAbsentDays response * * @typedef {Object} getAbsentDaysResponse * @property {Head} head - Info to the response * @property {AbsentSummary[]} content */ /** * Returns absent days. * * @param {[String|Date|number]} [from=Date.now()] - Date or timestamp * @param {[String|Date|number]} [to=Date.now()] - Date or timestamp * @returns {getAbsentDaysResponse} getAbsentDays response */ async getAbsentDays(from=new Date(), to=new Date()) { const query = new URLSearchParams(); query.set("KlaBuDateVon", this.getDateString(from)); query.set("KlaBuDateBis", this.getDateString(to)); const data = await this.request("RESTFehltageliste.php", query); return { head: new Head(data.Msg), content: new AbsentSummary(data), }; } /** * Returns list of exams. * The response is currently mostly unparsed * * @returns {Object} response * @returns {Head} response.head - Info to the response * @returns {Object[]} response.content */ async getExamList(from=new Date(), to=new Date()) { const query = new URLSearchParams(); query.set("KlaBuDateVon", this.getDateString(from)); query.set("KlaBuDateBis", this.getDateString(to)); const data = await this.request("RESTKlausurliste.php", query); return { head: new Head(data.Msg), content: data.Klausuren, }; } /** * Subscribes to notifications. * This Response is currently unparsed. * * @returns {Object} */ async subscribeToNotifications(Token, Register, IOS) { const query = new URLSearchParams(); query.set("Token", Token); query.set("Register", Register); query.set("IOS", IOS); return await this.request("RESTDeviceToken.php", query); } getDateString(date) { if(typeof date === "string" && /^\d{4}\-\d{2}\-\d{2}$/.test(date)) { return date; } if(date instanceof Date) { return date.toISOString().slice(0, 10); } if(typeof date === "number") { return this.getDateString(new Date(date)); } throw new Error(`"${date}" is not a valid type for date.`); } /** * * @param {string} url * @param {URLSearchParams} query Query parameters * @returns {Any} Response from Server. Error when an error occoured. */ async request(url, query) { if(!this.key && await this.fetchKey() === false) return false; url = new URL(url, this.baseUrl); if(query) url.search = query.toString(); url.searchParams.set("key", EncryptionHelper.makeEncryptedParameter(this.username, this.key)); const req = await axios(url.href, { headers: { Authorization: "Basic " + Buffer.from(this.username + ":" + this.#password).toString('base64'), }, }); return req.data; } } module.exports = VirtuellerStundenplan