diff --git a/src/api/NotesApi.ts b/src/api/NotesApi.ts index 65db938..b0b2103 100644 --- a/src/api/NotesApi.ts +++ b/src/api/NotesApi.ts @@ -1,8 +1,5 @@ -import { - getJSFFormParams, - getViewState, - scheduleResponseToEvents, -} from "../utils/AurionUtils"; +import { getViewState } from "../utils/AurionUtils"; +import { getNotesFromResponse } from "../utils/NotesUtils"; import Session from "./Session"; class NotesApi { @@ -12,11 +9,12 @@ class NotesApi { } // Récupération des notes - public fetchNotes(): Promise { - return new Promise(async (resolve, reject) => { + public fetchNotes(): Promise { + return new Promise(async (resolve, reject) => { try { + // On part depuis la page planning pour pouvoir finalement accéder à la page des notes const schedulePage = await this.session.sendGET( - "/faces/LearnerNotationListPage.xhtml", + "/faces/Planning.xhtml", ); let viewState = getViewState(schedulePage); if (viewState) { @@ -33,8 +31,7 @@ class NotesApi { const response = await this.session.sendGET( "faces/LearnerNotationListPage.xhtml", ); - console.log(response); - resolve(); + resolve(getNotesFromResponse(response)); } else { reject(new Error("Viewstate not found")); } diff --git a/src/api/ScheduleApi.ts b/src/api/PlanningApi.ts similarity index 63% rename from src/api/ScheduleApi.ts rename to src/api/PlanningApi.ts index f8b7977..3423226 100644 --- a/src/api/ScheduleApi.ts +++ b/src/api/PlanningApi.ts @@ -1,19 +1,16 @@ -import { - getJSFFormParams, - getViewState, - scheduleResponseToEvents, -} from "../utils/AurionUtils"; +import { getJSFFormParams, getViewState } from "../utils/AurionUtils"; +import { planningResponseToEvents } from "../utils/PlanningUtils"; import Session from "./Session"; -class ScheduleApi { +class PlanningApi { private session: Session; constructor(session: Session) { this.session = session; } // 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): Promise { - return new Promise(async (resolve, reject) => { + public fetchPlanning(startDate?: string): Promise { + return new Promise(async (resolve, reject) => { try { const schedulePage = await this.session.sendGET( "/faces/Planning.xhtml", @@ -43,19 +40,22 @@ class ScheduleApi { const endDate = startDate + 6 * 24 * 60 * 60 * 1000; 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(); - // On fixe la date de fin à une semaine après - const endTimestamp = - startTimestamp + 6 * 24 * 60 * 60 * 1000; + const now = new Date(); + // Obtenir le jour actuel (0 = dimanche, 1 = lundi, ..., 6 = samedi) + let day = now.getDay(); + // Calculer la différence pour atteindre lundi (0 = dimanche => 1 jour pour atteindre lundi) + const daysToMonday = day === 0 ? 1 : 1 - day; + // Créer la date de début (lundi 6h00) + const startDate = new Date(now); + startDate.setDate(now.getDate() + daysToMonday); // Passer au lundi + startDate.setHours(6, 0, 0, 0); // Fixer à 6h00 + // Date de fin (6 jours après la date de début) + const endDate = new Date(startDate); + endDate.setDate(startDate.getDate() + 6); // Ajouter 6 jours + // Convertir les dates en timestamp + const startTimestamp = startDate.getTime(); + const endTimestamp = endDate.getTime(); + params.append( "form:j_idt118_start", startTimestamp.toString(), @@ -70,7 +70,7 @@ class ScheduleApi { "faces/Planning.xhtml", params, ); - resolve(scheduleResponseToEvents(response)); + resolve(planningResponseToEvents(response)); } else { reject(new Error("Viewstate not found")); } @@ -81,4 +81,4 @@ class ScheduleApi { } } -export default ScheduleApi; +export default PlanningApi; diff --git a/src/api/Session.ts b/src/api/Session.ts index 98da860..c022ac6 100644 --- a/src/api/Session.ts +++ b/src/api/Session.ts @@ -1,5 +1,5 @@ import axios, { AxiosInstance } from "axios"; -import ScheduleApi from "./ScheduleApi"; +import PlanningApi from "./PlanningApi"; import { getJSFFormParams, getViewState } from "../utils/AurionUtils"; import NotesApi from "./NotesApi"; @@ -17,8 +17,8 @@ class Session { } // API pour le calendrier - public getScheduleApi(): ScheduleApi { - return new ScheduleApi(this); + public getPlanningApi(): PlanningApi { + return new PlanningApi(this); } // API pour les notes public getNotesApi(): NotesApi { diff --git a/src/utils/AurionUtils.ts b/src/utils/AurionUtils.ts index 91ba3a4..496e2fa 100644 --- a/src/utils/AurionUtils.ts +++ b/src/utils/AurionUtils.ts @@ -14,52 +14,6 @@ export function getViewState(html: string): string | undefined { return undefined; } -// Conversion du calendrier au format JSON -export function getJSONSchedule(xml: string): object { - const parser = load(xml, { - xmlMode: true, - }); - const json = parser('update[id="form:j_idt118"]').text(); - return JSON.parse(json)["events"]; -} - -// On convertit la réponse du serveur XML en cours du planning -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 par défaut... - let subject = ""; - let title = ""; - if (eventInfo.length >= 9) { - subject = eventInfo[eventInfo.length - 6].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, - 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) diff --git a/src/utils/NotesUtils.ts b/src/utils/NotesUtils.ts new file mode 100644 index 0000000..a560a1c --- /dev/null +++ b/src/utils/NotesUtils.ts @@ -0,0 +1,70 @@ +import { load } from "cheerio"; + +export function getNotesFromResponse(htmlReponse: string): NotesList[] { + // On parcourt le tableau des notes + const parser = load(htmlReponse); + const table = parser("table tbody"); + const notes: Note[] = []; + //On récupère chaque notes avec les informations associées + table.find("tr").each((index, element) => { + const note: Note = { + date: "", + code: "", + subject: "", + note: "", + absence: "", + description: "", + instructor: "", + }; + const fields = [ + "date", + "code", + "subject", + "note", + "absence", + "description", + "instructor", + ]; + // On construit l'objet note avec les informations de la ligne + load(element)("td").each((index, element) => { + let value = load(element).text().trim(); + // S'il s'agit du code de la note, on le formate + if (index === 1) { + // On supprime le dernier _DS+Numéro afin de créer un code commun avec les notes d'une même matière + value = value.replace(/_DS\d$/, ""); + } + note[fields[index]] = value; + }); + notes.push(note); + }); + // On regroupe les notes par matière + const notesByCode: NotesList[] = []; + notes.forEach((note) => { + // On regroupe par code de matière + const code = note.code; + const existingNote = notesByCode.find((n) => n.code === code); + if (existingNote) { + existingNote.notes.push(note); + } else { + notesByCode.push({ + code, + notes: [note], + }); + } + }); + + return notesByCode; +} + +// On calcule la moyenne d'une liste de notes +export function noteAverage(note: Note[]): number { + let sum = 0; + let count = 0; + note.forEach((n) => { + if (n.note !== "") { + sum += parseFloat(n.note); + count++; + } + }); + return sum / count; +} diff --git a/src/utils/PlanningUtils.ts b/src/utils/PlanningUtils.ts new file mode 100644 index 0000000..7ee1efe --- /dev/null +++ b/src/utils/PlanningUtils.ts @@ -0,0 +1,47 @@ +import { load } from "cheerio"; + +// Conversion du calendrier au format JSON +export function getJSONSchedule(xml: string): object { + const parser = load(xml, { + xmlMode: true, + }); + const json = parser('update[id="form:j_idt118"]').text(); + return JSON.parse(json)["events"]; +} + +// On convertit la réponse du serveur XML en cours du planning +export function planningResponseToEvents(response: string): PlanningEvent[] { + 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 par défaut... + let subject = ""; + let title = ""; + if (eventInfo.length >= 9) { + subject = eventInfo[eventInfo.length - 6].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, + className: event.className, + }; + }); +} diff --git a/src/utils/types.ts b/src/utils/types.ts index a6b8aa7..1b96cf0 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,4 +1,4 @@ -type ScheduleEvent = { +type PlanningEvent = { id: string; title: string; subject: string; @@ -9,3 +9,17 @@ type ScheduleEvent = { end: string; className: string; }; +type Note = { + date: string; + code: string; + subject: string; + note: string; + absence: string; + description: string; + instructor: string; + [key: string]: string; +}; +type NotesList = { + code: string; + notes: Note[]; +}; diff --git a/tests/NotesApi.test.ts b/tests/NotesApi.test.ts index 4fb8a29..6824a05 100644 --- a/tests/NotesApi.test.ts +++ b/tests/NotesApi.test.ts @@ -1,4 +1,5 @@ import { login } from "../src/api/Session"; +import { noteAverage } from "../src/utils/NotesUtils"; describe("NotesApi", () => { it("should receive notes", async () => { const username = process.env.TEST_USERNAME; @@ -10,7 +11,12 @@ describe("NotesApi", () => { } const session = await login(username, password); - const schedule = await session.getNotesApi().fetchNotes(); - expect(schedule).not.toBeDefined(); + const notes = await session.getNotesApi().fetchNotes(); + console.log(JSON.stringify(notes, null, 2)); + console.log("Les moyennes: "); + notes.forEach((note) => { + console.log(note.code + ": " + noteAverage(note.notes)); + }); + expect(notes).toBeInstanceOf(Array); }); }); diff --git a/tests/ScheduleApi.test.ts b/tests/PlanningApi.test.ts similarity index 62% rename from tests/ScheduleApi.test.ts rename to tests/PlanningApi.test.ts index f7b12a0..20d83fe 100644 --- a/tests/ScheduleApi.test.ts +++ b/tests/PlanningApi.test.ts @@ -1,6 +1,6 @@ import { login } from "../src/api/Session"; -describe("ScheduleApi", () => { - it("should receive a schedule", async () => { +describe("PlanningApi", () => { + it("should receive a planning", async () => { const username = process.env.TEST_USERNAME; const password = process.env.TEST_PASSWORD; if (!username || !password) { @@ -10,7 +10,8 @@ describe("ScheduleApi", () => { } const session = await login(username, password); - const schedule = await session.getScheduleApi().fetchSchedule(); - expect(schedule).toBeInstanceOf(Array); + const planning = await session.getPlanningApi().fetchPlanning(); + console.log(planning); + expect(planning).toBeInstanceOf(Array); }); });