From f61b5cc1dff33b5631b6416685c3fb4521582585 Mon Sep 17 00:00:00 2001 From: Gabriel29306 Date: Fri, 17 Jan 2025 20:53:19 +0100 Subject: [PATCH 1/6] feat(studentGradebook): add model Signed-off-by: Gabriel29306 --- src/models/gradebook.ts | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/models/gradebook.ts diff --git a/src/models/gradebook.ts b/src/models/gradebook.ts new file mode 100644 index 0000000..bb36e13 --- /dev/null +++ b/src/models/gradebook.ts @@ -0,0 +1,41 @@ +import { Subject } from "./subject"; + +export type GradeBook = + | GradeBookNot + | GradeBookOk; + +type GradeBookNot = Readonly<{ + available: false; + message?: string; +}>; + +type GradeBookOk = Readonly<{ + available: true; + /** + * The differents assessments like the mentions or the overall rating + */ + overallAssessments: { + name: string; + value: string; + }[]; + /** + * graph image as base64 png (with white backgound) + */ + graph?: string; + subjects: GradeBookSubject[]; + url: string; +}>; + +export type GradeBookSubject = { + subject: Subject; + subjectGroup: string; + coef: number; + averages: { + student: number; + classOverall: number; + max: number; + min: number; + }; + assessments: string[]; + teachers: string[]; +}; From e41bfb48e689475d10f5ed803dd1e045ec4063cc Mon Sep 17 00:00:00 2001 From: Gabriel29306 Date: Fri, 17 Jan 2025 21:02:58 +0100 Subject: [PATCH 2/6] feat(studentGradebook): add decoder Signed-off-by: Gabriel29306 --- src/decoders/gradebook.ts | 48 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/decoders/gradebook.ts diff --git a/src/decoders/gradebook.ts b/src/decoders/gradebook.ts new file mode 100644 index 0000000..50052a2 --- /dev/null +++ b/src/decoders/gradebook.ts @@ -0,0 +1,48 @@ +import { GradeBook, GradeBookSubject } from "~/models/gradebook"; +import { decodeSubject } from "./subject"; +import { gradebookPDF } from "~/api"; +import { Period, SessionHandle } from "~/models"; + +/** + * @param gradeBookData response from PageBulletins + */ +export const decodeGradeBook = async (session: SessionHandle, period: Period, gradeBookData: any): Promise => { + // When bad period is used, the return is `{ data: {}, nom: 'PageBulletins' }` but the session don't expire. + if (Object.keys(gradeBookData).length == 0 || gradeBookData.message) + return { available: false, message: gradeBookData.message ?? undefined }; + + let overallAssessments: { name: string; value: string; }[] = gradeBookData.ObjetListeAppreciations.V.ListeAppreciations.V.map( + (assessment: any) => { + return { name: assessment.Intitule, value: assessment.L }; + } + ); + const subjects = (gradeBookData.ListeServices.V as Array).map( + (subjectData) => { + return { + subject: decodeSubject(subjectData.Matiere?.V), + subjectGroup: subjectData.SurMatiere.V, + coef: parseInt(subjectData.Coefficient.V), + + averages: { + student: parseFloat(subjectData.MoyenneEleve.V.replace(",", ".")), + classOverall: parseFloat(subjectData.MoyenneClasse.V.replace(",", ".")), + max: parseFloat(subjectData.MoyenneSup.V.replace(",", ".")), + min: parseFloat(subjectData.MoyenneInf.V.replace(",", ".")) + }, + + assessments: (subjectData.ListeAppreciations.V as Array).map((a) => a.L), + teachers: subjectData.ListeElements?.V.map((elem: any) => elem.ListeProfesseurs?.V.map((v: any) => v.L)) + ?? subjectData.ListeProfesseurs?.V.map((a: any) => a.L) + ?? [] + } as GradeBookSubject; + } + ); + + return { + available: true, + overallAssessments, + graph: gradeBookData?.graphe?.replace("\\n", ""), + subjects, + url: await gradebookPDF(session, period) + }; +}; From 2e8caf8c38abd65f2805bcf625caea3cdbcc98b4 Mon Sep 17 00:00:00 2001 From: Gabriel29306 Date: Fri, 17 Jan 2025 21:03:22 +0100 Subject: [PATCH 3/6] feat(studentGradebook): add API Signed-off-by: Gabriel29306 --- src/api/gradebook.ts | 26 ++++++++++++++++++++++++++ src/api/index.ts | 1 + 2 files changed, 27 insertions(+) create mode 100644 src/api/gradebook.ts diff --git a/src/api/gradebook.ts b/src/api/gradebook.ts new file mode 100644 index 0000000..f618d35 --- /dev/null +++ b/src/api/gradebook.ts @@ -0,0 +1,26 @@ +import { RequestFN } from "~/core/request-function"; +import { encodePeriod } from "~/encoders/period"; +import { type Period, type SessionHandle, TabLocation } from "~/models"; +import { apiProperties } from "./private/api-properties"; + +import { decodeGradeBook } from "~/decoders/gradebook"; +import { GradeBook } from "~/models/gradebook"; + +export const gradebook = async (session: SessionHandle, period: Period): Promise => { + const properties = apiProperties(session); + + const periodModified = JSON.parse(JSON.stringify(period)); + periodModified.kind = 2; // Why ? Idk, but needed + + const request = new RequestFN(session, "PageBulletins", { + [properties.data]: { + classe: {}, + eleve: {}, + periode: encodePeriod(periodModified) + }, + [properties.signature]: { onglet: TabLocation.Gradebook } + }); + + const response = await request.send(); + return await decodeGradeBook(session, period, response.data[properties.data]); +}; diff --git a/src/api/index.ts b/src/api/index.ts index 703651a..2542e13 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -29,6 +29,7 @@ export * from "./discussions"; export * from "./evaluations"; export * from "./geolocation"; export * from "./gradebook-pdf"; +export * from "./gradebook"; export * from "./grades-overview"; export * from "./homepage"; export * from "./instance"; From 30fb7579fccd2cb4b19571e4aa770ff5811ace43 Mon Sep 17 00:00:00 2001 From: Gabriel29306 Date: Fri, 17 Jan 2025 21:03:56 +0100 Subject: [PATCH 4/6] feat(studentGradebook): add example Signed-off-by: Gabriel29306 --- examples/gradebook.ts | 50 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 examples/gradebook.ts diff --git a/examples/gradebook.ts b/examples/gradebook.ts new file mode 100644 index 0000000..527060f --- /dev/null +++ b/examples/gradebook.ts @@ -0,0 +1,50 @@ +import * as pronote from "../src"; +import { credentials } from "./_credentials"; + +void async function main() { + const session = pronote.createSessionHandle(); + await pronote.loginCredentials(session, { + url: credentials.pronoteURL, + kind: pronote.AccountKind.STUDENT, + username: credentials.username, + password: credentials.password, + deviceUUID: credentials.deviceUUID + }); + const tab = session.userResource.tabs.get(pronote.TabLocation.Gradebook); + if (!tab) throw new Error("Cannot retrieve periods for the grades tab, you maybe don't have access to it."); + const selectedPeriod = tab.defaultPeriod!; + + console.log("Available periods for this tab ->", tab.periods.map((period) => period.name).join(", ")); + console.log("We selected the default period,", selectedPeriod.name, "!\n"); + + const gradebook = await pronote.gradebook(session, selectedPeriod); + + if (!gradebook.available) throw new Error(`The gradebook for "${selectedPeriod.name}" is not available` + (gradebook.message ? ` : ${gradebook.message}`:"")); + + console.group("--- Subjects ---"); + + gradebook.subjects.forEach( + (subject) => { + console.group(subject.subject.name, ":", subject.teachers.join(", ")); + console.log("Coef:", subject.coef); + console.group("Averages:"); + console.log("Student:", subject.averages.student); + console.log("Class overall:", subject.averages.classOverall); + console.log("Max:", subject.averages.max); + console.log("Min:", subject.averages.min); + console.groupEnd(); // Averages + console.group("Assessment" + (subject.assessments.length > 1 ? "s: " : ": ")); + subject.assessments.forEach((a) => console.log(a)); + console.groupEnd(); // Assessments + console.groupEnd(); // Subject + } + ); + console.groupEnd(); // Subjects + console.log(); // Make space between categories + + console.group("--- Overall ---"); + console.group("Assessment" + (gradebook.overallAssessments.length > 1 ? "s :" : " :")); + gradebook.overallAssessments.forEach((a) => console.log(a.name, ":", a.value)); + console.groupEnd(); // Assessments + console.groupEnd(); // Overall +}(); From 24d2f8bea341a1159bced2b4c13d01cdc2089323 Mon Sep 17 00:00:00 2001 From: Gabriel29306 Date: Fri, 17 Jan 2025 21:43:52 +0100 Subject: [PATCH 5/6] fix(studentGradebook): use Error instead of a `available` property Signed-off-by: Gabriel29306 --- examples/gradebook.ts | 12 +++++++++--- src/decoders/gradebook.ts | 5 ++--- src/models/gradebook.ts | 11 +---------- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/examples/gradebook.ts b/examples/gradebook.ts index 527060f..ff593d5 100644 --- a/examples/gradebook.ts +++ b/examples/gradebook.ts @@ -1,5 +1,6 @@ import * as pronote from "../src"; import { credentials } from "./_credentials"; +import { GradeBook } from "~/models/gradebook"; void async function main() { const session = pronote.createSessionHandle(); @@ -17,9 +18,14 @@ void async function main() { console.log("Available periods for this tab ->", tab.periods.map((period) => period.name).join(", ")); console.log("We selected the default period,", selectedPeriod.name, "!\n"); - const gradebook = await pronote.gradebook(session, selectedPeriod); - - if (!gradebook.available) throw new Error(`The gradebook for "${selectedPeriod.name}" is not available` + (gradebook.message ? ` : ${gradebook.message}`:"")); + let gradebook: GradeBook; + try { + gradebook = await pronote.gradebook(session, selectedPeriod); + } + catch (error) { + console.error("The period is not accesibles"); + throw error; + } console.group("--- Subjects ---"); diff --git a/src/decoders/gradebook.ts b/src/decoders/gradebook.ts index 50052a2..452ec8c 100644 --- a/src/decoders/gradebook.ts +++ b/src/decoders/gradebook.ts @@ -1,7 +1,7 @@ import { GradeBook, GradeBookSubject } from "~/models/gradebook"; import { decodeSubject } from "./subject"; import { gradebookPDF } from "~/api"; -import { Period, SessionHandle } from "~/models"; +import { PageUnavailableError, Period, SessionHandle } from "~/models"; /** * @param gradeBookData response from PageBulletins @@ -9,7 +9,7 @@ import { Period, SessionHandle } from "~/models"; export const decodeGradeBook = async (session: SessionHandle, period: Period, gradeBookData: any): Promise => { // When bad period is used, the return is `{ data: {}, nom: 'PageBulletins' }` but the session don't expire. if (Object.keys(gradeBookData).length == 0 || gradeBookData.message) - return { available: false, message: gradeBookData.message ?? undefined }; + throw new PageUnavailableError(); let overallAssessments: { name: string; value: string; }[] = gradeBookData.ObjetListeAppreciations.V.ListeAppreciations.V.map( (assessment: any) => { @@ -39,7 +39,6 @@ export const decodeGradeBook = async (session: SessionHandle, period: Period, gr ); return { - available: true, overallAssessments, graph: gradeBookData?.graphe?.replace("\\n", ""), subjects, diff --git a/src/models/gradebook.ts b/src/models/gradebook.ts index bb36e13..9a51bdb 100644 --- a/src/models/gradebook.ts +++ b/src/models/gradebook.ts @@ -1,16 +1,7 @@ import { Subject } from "./subject"; -export type GradeBook = - | GradeBookNot - | GradeBookOk; - -type GradeBookNot = Readonly<{ - available: false; +export type GradeBook = Readonly<{ message?: string; -}>; - -type GradeBookOk = Readonly<{ - available: true; /** * The differents assessments like the mentions or the overall rating */ From 9f57f2ba7dfa4e74d06bf1fdfc7ea35d16c66537 Mon Sep 17 00:00:00 2001 From: Gabriel29306 Date: Fri, 17 Jan 2025 21:48:04 +0100 Subject: [PATCH 6/6] fix(studentGradebook): simplify period kind change Signed-off-by: Gabriel29306 --- src/api/gradebook.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/api/gradebook.ts b/src/api/gradebook.ts index f618d35..ada5168 100644 --- a/src/api/gradebook.ts +++ b/src/api/gradebook.ts @@ -9,14 +9,13 @@ import { GradeBook } from "~/models/gradebook"; export const gradebook = async (session: SessionHandle, period: Period): Promise => { const properties = apiProperties(session); - const periodModified = JSON.parse(JSON.stringify(period)); - periodModified.kind = 2; // Why ? Idk, but needed + period = {...period, kind: 2}; const request = new RequestFN(session, "PageBulletins", { [properties.data]: { classe: {}, eleve: {}, - periode: encodePeriod(periodModified) + periode: encodePeriod(period) }, [properties.signature]: { onglet: TabLocation.Gradebook } });