diff --git a/src/api/ScheduleApi.ts b/src/api/ScheduleApi.ts index 2fc0c50..a6eda2d 100644 --- a/src/api/ScheduleApi.ts +++ b/src/api/ScheduleApi.ts @@ -1,4 +1,8 @@ -import { getJSONSchedule, getViewState } from "../utils/AurionUtils"; +import { + getJSFFormParams, + getViewState, + scheduleResponseToEvents, +} from "../utils/AurionUtils"; import Session from "./Session"; class ScheduleApi { @@ -7,75 +11,66 @@ class ScheduleApi { this.session = session; } - public fetchSchedule(): Promise { - return new Promise(async (resolve, reject) => { + // Récupération de l'emploi du temps en fonction de la date de début et de fin (timestamps en millisecondes) + public fetchSchedule( + startDate?: string, + endDate?: string, + ): Promise { + return new Promise(async (resolve, reject) => { try { const schedulePage = await this.session.sendGET( "/faces/Planning.xhtml", ); let viewState = getViewState(schedulePage); if (viewState) { - const params = new URLSearchParams(); - // On ajoute les paramètres nécessaires pour effectuer une requête POST - params.append("javax.faces.partial.ajax", "true"); - params.append("javax.faces.source", "form:j_idt46"); - params.append( - "javax.faces.partial.execute", - "form:j_idt46", + // Ici 291906 correspond au menu 'Scolarité' dans la sidebar + // Requête utile pour intialiser le ViewState (obligatoire pour effectuer une requête) + await this.session.sendSidebarRequest("291906", viewState); + + // Ici 1_4 correspond au sous-menu 'Emploi du temps' dans la sidebar + // On récupère le ViewState pour effectuer la prochaine requête + viewState = await this.session.sendSidebarSubmenuRequest( + "1_4", + viewState, ); - params.append("javax.faces.partial.render", "form:sidebar"); - params.append("form:j_idt46", "form:j_idt46"); - params.append( - "webscolaapp.Sidebar.ID_SUBMENU", - "submenu_291906", + + // On envoie enfin la requête pour obtenir l'emploi du temps + const params = getJSFFormParams( + "j_idt118", + "j_idt118", + viewState, ); - params.append("form", "form"); - params.append("javax.faces.ViewState", viewState); + if (startDate && endDate) { + params.append("form:j_idt118_start", startDate); + params.append("form:j_idt118_end", endDate); + } else { + // On récupère le timestamp du lundi de la semaine en cours + const currentDate = new Date(); + const currentDay = currentDate.getDay(); + const daysUntilMonday = + (currentDay === 0 ? 1 : 8) - currentDay; + currentDate.setDate( + currentDate.getDate() + daysUntilMonday, + ); + currentDate.setHours(0, 0, 0, 0); + const startTimestamp = currentDate.getTime(); + const endTimestamp = + startTimestamp + 6 * 24 * 60 * 60 * 1000; + params.append( + "form:j_idt118_start", + startTimestamp.toString(), + ); + params.append( + "form:j_idt118_end", + endTimestamp.toString(), + ); + } + const response = await this.session.sendPOST( "faces/Planning.xhtml", params, ); - - const params2 = new URLSearchParams(); - // On ajoute les paramètres nécessaires pour effectuer une requête POST - params2.append("form", "form"); - params2.append("javax.faces.ViewState", viewState); - params2.append("form:sidebar", "form:sidebar"); - params2.append("form:sidebar_menuid", "1_4"); - const response2 = await this.session.sendPOST( - "faces/Planning.xhtml", - params2, - ); - viewState = getViewState(response2); - if (!viewState) { - return reject(new Error("Viewstate not found")); - } - const params3 = new URLSearchParams(); - params3.append("javax.faces.partial.ajax", "true"); - params3.append("javax.faces.source", "form:j_idt118"); - params3.append( - "javax.faces.partial.execute", - "form:j_idt118", - ); - params3.append( - "javax.faces.partial.render", - "form:j_idt118", - ); - params3.append("form:j_idt118", "form:j_idt118"); - params3.append("form:j_idt118_start", "1731279600000"); - params3.append("form:j_idt118_end", "1731798000000"); - params3.append("form", "form"); - params3.append( - "form:idInit", - "webscolaapp.Planning_-6802915683822110557", - ); - params3.append("javax.faces.ViewState", viewState); - const response3 = await this.session.sendPOST( - "faces/Planning.xhtml", - params3, - ); - console.log(getJSONSchedule(response3)); - resolve(response2); + resolve(scheduleResponseToEvents(response)); } else { reject(new Error("Viewstate not found")); } diff --git a/src/api/Session.ts b/src/api/Session.ts index 2926211..ddd577d 100644 --- a/src/api/Session.ts +++ b/src/api/Session.ts @@ -1,5 +1,6 @@ import axios, { AxiosInstance } from "axios"; import ScheduleApi from "./ScheduleApi"; +import { getJSFFormParams, getViewState } from "../utils/AurionUtils"; class Session { private client: AxiosInstance; @@ -19,6 +20,66 @@ class Session { return new ScheduleApi(this); } + // (1ère phase) Besoin de simuler le clic sur la sidebar pour obtenir le ViewState nécessaire aux fonctionnements des reqûetes + public sendSidebarRequest( + subMenuId: string, + viewState: string, + ): Promise { + return new Promise(async (resolve, reject) => { + try { + // 1 ère sidebar: formId = j_idt46, renderId = sidebar + const params = getJSFFormParams( + "j_idt46", + "sidebar", + viewState, + ); + // On ajoute l'ID du sous-menu qui correspond à la rubrique chosie (Scolarité, mon compte, divers, ...) + params.append( + "webscolaapp.Sidebar.ID_SUBMENU", + `submenu_${subMenuId}`, + ); + // On envoie la requête POST + const response = await this.sendPOST( + "faces/Planning.xhtml", + params, + ); + resolve(response); + } catch (err) { + reject(err); + } + }); + } + + // (2ème phase) Simulation du sous menu de la side bar pour obtenir le ViewState nécessaire aux fonctionnements des requêtes + // Cette fonction retourne un second ViewState qui sera utilisé pour effectuer les prochaines requêtes POST + public sendSidebarSubmenuRequest( + subMenuId: string, + viewState: string, + ): Promise { + return new Promise(async (resolve, reject) => { + try { + const params = new URLSearchParams(); + // On ajoute les paramètres nécessaires pour effectuer une requête POST + params.append("form", "form"); + params.append("javax.faces.ViewState", viewState); + params.append("form:sidebar", "form:sidebar"); + params.append("form:sidebar_menuid", subMenuId); + const response = await this.sendPOST( + "faces/Planning.xhtml", + params, + ); + const secondViewState = getViewState(response); + if (secondViewState) { + resolve(secondViewState); + } else { + reject(new Error("Viewstate not found")); + } + } catch (err) { + reject(err); + } + }); + } + public sendGET(url: string): Promise { return this.client.get(url).then((response) => response.data); } diff --git a/src/utils/AurionUtils.ts b/src/utils/AurionUtils.ts index 91db885..3f3a9a0 100644 --- a/src/utils/AurionUtils.ts +++ b/src/utils/AurionUtils.ts @@ -22,3 +22,60 @@ export function getJSONSchedule(xml: string): object { const json = parser('update[id="form:j_idt118"]').text(); return JSON.parse(json)["events"]; } + +export function scheduleResponseToEvents(response: string): ScheduleEvent[] { + const json: any = getJSONSchedule(response); + + return json.map((event: any) => { + // On récupère les informations des cours + const eventInfo = event.title.split(" - "); + + let room = eventInfo[1].trim(); + // Pour les matières qui ne sont pas bien formatées... + let subject = ""; + let title = ""; + if (eventInfo.length >= 9) { + subject = eventInfo[eventInfo.length - 5].trim(); + title = eventInfo[eventInfo.length - 4].trim(); + } else { + subject = eventInfo[eventInfo.length - 4].trim(); + title = eventInfo[eventInfo.length - 3].trim(); + } + + let instructors = eventInfo[eventInfo.length - 2].trim(); + let learners = eventInfo[eventInfo.length - 1].trim(); + + return { + id: event.id, + title, + subject, + room, + instructors, + learners, + start: event.start, + end: event.end, + allDay: event.allDay, + editable: event.editable, + className: event.className, + }; + }); +} + +// Paramètres nécessaires pour effectuer une requête avec le backend Java Server Faces (JSF) +// Form ID : ID du formulaire (Récupérable avec BurpSuite / Inspecteur de requêtes) +// Render ID : ID de l'élément à mettre à jour (Récupérable avec BurpSuite / Inspecteur de requêtes) +export function getJSFFormParams( + formId: string, + renderId: string, + viewState: string, +): URLSearchParams { + const params = new URLSearchParams(); + params.append("javax.faces.partial.ajax", "true"); + params.append("javax.faces.source", `form:${formId}`); + params.append("javax.faces.partial.execute", `form:${formId}`); + params.append("javax.faces.partial.render", `form:${renderId}`); + params.append(`form:${formId}`, `form:${formId}`); + params.append("form", "form"); + params.append("javax.faces.ViewState", viewState); + return params; +} diff --git a/src/utils/types.ts b/src/utils/types.ts index e69de29..8fda03b 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -0,0 +1,13 @@ +type ScheduleEvent = { + id: string; + title: string; + subject: string; + room: string; + instructors: string; + learners: string; + start: string; + end: string; + allDay: boolean; + editable: boolean; + className: string; +}; diff --git a/tests/ScheduleApi.test.ts b/tests/ScheduleApi.test.ts index 8ae567d..b57126e 100644 --- a/tests/ScheduleApi.test.ts +++ b/tests/ScheduleApi.test.ts @@ -10,6 +10,8 @@ describe("ScheduleApi", () => { } const session = await login(username, password); - await session.getScheduleApi().fetchSchedule(); + const schedule = await session.getScheduleApi().fetchSchedule(); + console.log(schedule); + expect(schedule).not.toBeNull(); }); });