diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e2742a..6ed3459 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ - **0.2.0**: Réécriture en **typescript** - **1.0.0**: Quand le module sera stable +## 0.2.8 +- Prise en charge de la **double authentification** + - Avec ajout des types +- Réécriture de `Request.ts` pour une meilleure gestion et flexibilité des requêtes vers Ecoledirecte +- Amélioration de l'exemple `login.ts`: 2FA et interface agréable + - Ajout de `ora` et `enquirer` aux dépendances de développement +- Ajout de `getProfilePictureBase64` dans `getDownloads.ts` pour télécharger la photo de profil en base64 +- Mise à jour de la documentation en conséquences + ## 0.2.7 - Conversion des dates de la vie-scolaire renvoyé par ED (Merci Rémy) - Fix de la réponse de l'EDT diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 70a3220..1d9b3a8 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -11,34 +11,36 @@ - [Scripts](#scripts) -## Guide de l'utilisateur +# Guide de l'utilisateur Ce guide vous permet de comprendre comment fonctionne ce module et comment vous pouvez l'utiliser. -Ce projet est développé en _**Typescript**_, il est compatible avec toutes les technologies **Javascript**. +Ce projet est développé en _**Typescript**_, il est compatible avec toutes les technologies **Javascript**. Fonctionne avec **npm 18** ! -### Installation +## Installation ```sh npm i @papillonapp/ed-core ``` -### Utilisation +## Utilisation _Ce module utilise des fonctions asynchrones pour fonctionner._ **1. Importer le module** ```typescript -import {EDCore} from "@papillonapp/ed-core"; +import { EDCore } from "@papillonapp/ed-core"; ``` + **2. Initialiser le module** ```typescript const ED = new EDCore() ``` -**3. S'authentifier, par token ou identifiants** +**3. S'authentifier** + +Merci de vous référer à la section [authentification](#authentification). -_Avec ses identifiants_ ```typescript await ED.auth.login("username", "password", "uuidv4") ``` @@ -51,28 +53,91 @@ await ED.auth.login("username", "password", "uuidv4") > await ED.auth.login("username", "password", uuid) > ``` -_Avec un token_ -> [!WARNING] -> S'authentifier par token empêchera `papillon-ed-core` de se reconnecter à votre compte, ce qui entrainera une erreur après expiration du token. +**4. Visitez la documentation** + +Désormais connectés, il vous faudra lire la [documentation des références](#références) pour comprendre et utiliser chaque fonctionnalité. + +Des exemples sont aussi écrits dans `examples/`. Ajoutez vos identifiants dans `examples/login.ts` et tester les fonctionnalités avec `ts-node examples/.ts` + +Il existe aussi des documentations plus précises sur certaines fonctionnalités : +- [Commandes](#commandes) +- [Téléchargements](#téléchargements) + +## Authentification + +Cette section est dédiée à l'authentification, et vous permettra de comprendre plus précisément les différents modes d'authentification et leurs différences... + +L'authentification est accessible ainsi : +```typescript +ED.auth +``` + +Vous pouvez vous authentifier de plusieurs manières : +- ~~[Classique](); équivalent à une connexion depuis un navigateur.~~ (abandonné) +- [Mobile ou permanante](#permanante); équivalent à une authentification depuis l'application mobile Ecoledirecte. +- [Token](#token); déconseillée, vous vous connectez avec un token Ecoledirecte, vous devez donc assurer le renouvellement de celui-ci vous-même. + +> [!CAUTION] +> Authentification à facteurs ! Depuis Avril 2024, Ecoledirecte a mis en place une authentification à facteurs, avec un QCM pour sécuriser les connexions... Veuillez lire [À double facteurs](#à-double-facteurs) + +### Permanante + +Depuis la version `0.2.7`, la connexion **permanante**, permettant de renouveler le token est utilisée par défaut par `ed-core`. + +Elle correspond à une authentification depuis l'application Ecoledirecte mobile. Vous aurez besoin de générer un **UUIDv4**, qui ser l'identifiant unique de votre "session" et permettra le renouvellement du token. + +```typescript +import { v4 as uuidv4 } from 'uuid'; +import { EDCore } from "@papillonapp/ed-core"; + +const uuid = uuidv4() +await ED.auth.login("username", "password", uuid) +``` + +Quand une erreur de code `12` apparait, c'est que la [double authentification](#à-double-facteurs) est nécessaire. + +### Token + +L'authentification par token permet de se connecter à Ecoledirecte avec un token **généré par vos soins**. + ```typescript const userId = 0000 ED.setToken('token', userId) ``` -**4. Visitez la documentation** +L'identifiant de l'utilisateur est nécessaire. -Désormais connectés, il vous faudra lire la [documentation des références](#références) pour comprendre et utiliser chaque fonctionnalités. +### À double facteurs -Il existe aussi des documentation plus précises sur certaines fonctionnalités: -- [Commandes](#commandes) -- [Téléchargements](#téléchargements) +Quand une erreur de code `12` est renvoyée, vous devez répondre à des questions pour vous authentifier... + +_Il est conseillé de lire [`examples/login.ts`](examples/login.ts) pour voir une implémentation de cette double authentification_ + +1. Récupérer le jeton de la double authentification +```typescript +const token = await ED.auth.get2FAToken("username", "password") +``` +2. Récupérer le questionnaire +```typescript +const QCM = await ED.auth.get2FA(token) +QCM.question // La question +QCM.propositions // Les réponses possibles +``` +3. Envoyer la réponse +```typescript +const authFactors = await ED.auth.resolve2FA("La réponse") // Renvoie un objet utilisé pour s'authentifier +``` +4. S'authentifier avec les facteurs +```typescript +await ED.auth.login("username", "password", "uuid", authFactors) +``` -### Commandes +## Commandes > [!WARNING] > Le module de commande est encore instable ! -Ce module est accessible ainsi: +Ce module est accessible ainsi : ```typescript ED.orders ``` @@ -121,7 +186,7 @@ ED.orders.deleteOrder(100) _Renvoie une réponse vide_ -### Téléchargements +## Téléchargements À plusieurs endroits vous pourrez être amenés à devoir télécharger des documents (exemple: documents administratifs renvoyés par `ED.documents`). @@ -155,7 +220,7 @@ type fileType = "CLOUD" | "FICHIER_CDT" | "PIECE_JOINTE" | "FICHIER_MENU_RESTAUR - "FICHIER_MENU_RESTAURATION"; télécharger un menu (voir **`Menu`** dans [`getCantine.ts`](#getcantine)). - "ADMINISTRATIF"; télécharger un fichier administratif (si le document provient d'une année antérieur, l'argument `year` devra contenir l'année). -### Références +## Références > [!NOTE] > Des exemples sont disponibles dans `exemples/` @@ -172,7 +237,7 @@ Les références sont données ainsi: - Si la propriété est une fonction, le type est `(arg: type) => type`. - Si la fonction est `async`, elle renvoie une `Promise` -#### EDCore +### EDCore La classe principale du module. @@ -207,7 +272,7 @@ La classe principale du module. _Ouvrir [`src/session.ts`](src/session.ts)_ -#### GetHomeworks +### GetHomeworks La classe de gestion des devoirs. @@ -218,7 +283,7 @@ La classe de gestion des devoirs. _Ouvrir [`src/fetch/getHomeworks.ts`](src/fetch/getHomeworks.ts)_ -#### GetGrades +### GetGrades La classe de gestion des notes. @@ -229,7 +294,7 @@ La classe de gestion des notes. _Ouvrir [`src/fetch/getGrades.ts`](src/fetch/getGrades.ts)_ -#### GetTimetable +### GetTimetable La classe de gestion de l'EDT. Les jours sont formatés `YYYY-MM-DD`. @@ -241,7 +306,7 @@ La classe de gestion de l'EDT. Les jours sont formatés `YYYY-MM-DD`. _Ouvrir [`src/fetch/getTimetable.ts`](src/fetch/getTimetable.ts)_ -#### GetSchoolLife +### GetSchoolLife La classe de gestion de la vie scolaire @@ -252,7 +317,7 @@ La classe de gestion de la vie scolaire _Ouvrir [`src/fetch/getSchoolLife.ts`](src/fetch/getSchoolLife.ts)_ -#### GetCantine +### GetCantine La classe de gestion des modules de cantine. @@ -265,7 +330,7 @@ La classe de gestion des modules de cantine. _Ouvrir [`src/fetch/getCantine.ts`](src/fetch/getCantine.ts)_ -#### GetDigitalManuals +### GetDigitalManuals La classe de gestion des manuels scolaires. @@ -276,7 +341,7 @@ La classe de gestion des manuels scolaires. _Ouvrir [`src/fetch/getDigitalManuals.ts`](src/fetch/getDigitalManuals.ts)_ -#### GetMessaging +### GetMessaging La classe de gestion de la messagerie. @@ -288,7 +353,7 @@ La classe de gestion de la messagerie. _Ouvrir [`src/fetch/getMessaging.ts`](src/fetch/getMessaging.ts)_ -#### GetTimeline +### GetTimeline La classe de gestion des timeline. @@ -300,7 +365,7 @@ La classe de gestion des timeline. _Ouvrir [`src/fetch/getTimeline.ts`](src/fetch/getTimeline.ts)_ -#### GetDocuments +### GetDocuments La classe de gestion des documents administratifs. > [!CAUTION] @@ -313,7 +378,7 @@ La classe de gestion des documents administratifs. _Ouvrir [`src/fetch/getDocuments.ts`](src/fetch/getDocuments.ts)_ -#### GetForms +### GetForms La classe de gestion des formulaires. > [!CAUTION] @@ -326,7 +391,7 @@ La classe de gestion des formulaires. _Ouvrir [`src/fetch/getForms.ts`](src/fetch/getForms.ts)_ -#### GetWorkspaces +### GetWorkspaces La classe de gestion des espaces de travail. > [!CAUTION] @@ -345,7 +410,7 @@ La classe de gestion des espaces de travail. _Ouvrir [`src/fetch/getWorkspaces.ts`](src/fetch/getWorkspaces.ts)_ -#### GetCommunicationBook +### GetCommunicationBook La classe de gestion du carnet de liaison. > [!CAUTION] @@ -358,7 +423,7 @@ La classe de gestion du carnet de liaison. _Ouvrir [`src/fetch/getCommunicationBook.ts`](src/fetch/getCommunicationBook.ts)_ -#### GetCloud +### GetCloud La classe de gestion du cloud. > [!CAUTION] @@ -371,7 +436,7 @@ La classe de gestion du cloud. _Ouvrir [`src/fetch/getCloud.ts`](src/fetch/getCloud.ts)_ -#### GetOrders +### GetOrders La classe de gestion des commandes. > [!WARNING] @@ -389,7 +454,7 @@ La classe de gestion des commandes. _Ouvrir [`src/fetch/getOrders.ts`](src/fetch/getOrders.ts)_ -#### GetEsidoc +### GetEsidoc La classe de gestion du module Esidoc. @@ -400,11 +465,11 @@ La classe de gestion du module Esidoc. _Ouvrir [`src/fetch/getEsidoc.ts`](src/fetch/getEsidoc.ts)_ -#### GetDownloads +### GetDownloads La classe de gestion des téléchargements. -##### fileType +#### fileType ```typescript type fileType = "CLOUD" | "FICHIER_CDT" | "PIECE_JOINTE" | "FICHIER_MENU_RESTAURATION" | "ADMINISTRATIF"; ``` @@ -417,40 +482,48 @@ type fileType = "CLOUD" | "FICHIER_CDT" | "PIECE_JOINTE" | "FICHIER_MENU_RESTAUR _Ouvrir [`src/fetch/getDownloads.ts`](src/fetch/getDownloads.ts)_ -#### Auth +### Auth La classe de gestion de l'authentification et de l'utilisateur. -_Voir [s'authentifier avec ed-core](#utilisation)_ +_Voir [s'authentifier avec ed-core](#authentification)_ -| Propriété | Type | Commentaire | -|--------------|-----------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| login() | `async (username: string, password: string, uuid: string) => void` | Se connecte à Ecoledirecte avec des identifiants. `uuid` est un UUIDv4 utilisé pour reconnaitre l'appareil et regénérger un token. Lisez le guide pour savoir comment le générer. | -| renewToken() | `async (username: string, uuid: string, accessToken: string) => void` | Régénère un token à l'aide du nom d'utilisateur, de l'`uuid` et de l'`accessToken` et remplace automatiquement le token actuel. | -| setToken() | `(token: string, id: number) => boolean` | Se connecte à Ecoledirecte avec un token | +| Propriété | Type | Commentaire | +|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| login() | `async (username: string, password: string, uuid: string, fa?: object) => void` | Se connecte à Ecoledirecte avec des identifiants. `uuid` est un UUIDv4 utilisé pour reconnaitre l'appareil et regénérger un token. Lisez le guide pour savoir comment le générer. `fa` est l'objet contenant les facteurs d'authentification de `resolve2FA()` | +| renewToken() | `async (username: string, uuid: string, accessToken: string) => void` | Régénère un token à l'aide du nom d'utilisateur, de l'`uuid` et de l'`accessToken` et remplace automatiquement le token actuel. | +| setToken() | `(token: string, id: number) => boolean` | Se connecte à Ecoledirecte avec un token. | +| get2FAToken() | `async (username: string, password: string) => string` | Renvoie le jeton nécessaire à la récupération du QCM 2FA. | +| get2FA() | `async (token: string) => `[`doubleauthResData`](https://github.com/camarm-dev/ecoledirecte-api-types/blob/main/v3/responses/login/doubleauth.ts#L12) | Récupère le questionnaire 2FA avec le token de `get2FAToken()`. | +| resolve2FA() | `async (answer: string) => `[`doubleauthValidationResData`](https://github.com/camarm-dev/ecoledirecte-api-types/blob/main/v3/responses/login/doubleauth.ts#L19) | Renvoie les facteurs permettant l'uthentification. | _Ouvrir [`src/auth.ts`](src/auth.ts)_ -#### Request +### Request La classe de gestion des requêtes. -| Propriété | Type | Commentaire | -|------------------|---------------------------------------------|--------------------------------------------------------------------| -| post() | `async (url: string, body: string) => void` | Exécute la requête à l'API ecoledirect (path: `url`, body: `body`) | +| Propriété | Type | Commentaire | +|-----------|-------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| post() | `async (url: string, body: string, params?: string, ignoreErrors: boolean = false) => object` | Exécute la requête à l'API ecoledirect (path: `url`, body: `body`, les données utiles trasnférées à Ecoledirecte , paramètres d'url `params` et pour obtenir tout les réponses, même les erreurs Ecoledirecte, passer `ignoreErrors` à `true`) avec comme paramètre **verbe=get**. Renvoie la réponse sous forme d'un object **JSON** | +| get() | `async (url: string, body: string, params?: string, ignoreErrors: boolean = false) => object` | Exécute la requête à l'API ecoledirect (path: `url`, body: `body`, les données utiles trasnférées à Ecoledirecte , paramètres d'url `params` et pour obtenir tout les réponses, même les erreurs Ecoledirecte, passer `ignoreErrors` à `true`) avec comme paramètre **verbe=post**. Renvoie la réponse sous forme d'un object **JSON** | +| delete() | `async (url: string, body: string, params?: string, ignoreErrors: boolean = false) => object` | Exécute la requête à l'API ecoledirect (path: `url`, body: `body`, les données utiles trasnférées à Ecoledirecte , paramètres d'url `params` et pour obtenir tout les réponses, même les erreurs Ecoledirecte, passer `ignoreErrors` à `true`) avec comme paramètre **verbe=delete**. Renvoie la réponse sous forme d'un object **JSON** | +| put() | `async (url: string, body: string, params?: string, ignoreErrors: boolean = false) => object` | Exécute la requête à l'API ecoledirect (path: `url`, body: `body`, les données utiles trasnférées à Ecoledirecte , paramètres d'url `params` et pour obtenir tout les réponses, même les erreurs Ecoledirecte, passer `ignoreErrors` à `true`) avec comme paramètre **verbe=put**. Renvoie la réponse sous forme d'un object **JSON** | +| request() | `async (url: string, body: string, ignoreErrors: boolean = false) => object` | Exécute une requête (url: `url`, body: `body` et pour obtenir tout les réponses, même les erreurs Ecoledirecte, passer `ignoreErrors` à `true`). Renvoie la réponse sous forme d'un object **JSON** | +| blob() | `async (url: string, body: string, completeUrl: boolean = false, method: "GET" \| "POST" = "GET") => Blob` | Exécute la requête et renvoie un objet Blob. `completeUrl` sur `true` indiquera que `url` est la cible complète, pas un chemin de `https://api.ecoledirecte.com` | _Ouvrir [`src/Request.ts`](src/Request.ts)_ --- -## Guide du développeur +# Guide du développeur Ce guide vous permet de comprendre comment ce module est développé et donc d'y contribuer ! Ce projet est développé en _**Typescript**_. Vous devez maitriser ce languages ainsi que le développement de modules _NodeJs_ pour commencer. -### Installation +## Installation > [!NOTE] > Si vous souhaitez contribuer ou modifier le code, veuillez _fork_ le dépot et cloner votre copie de celui-ci. @@ -478,17 +551,17 @@ git submodule update --init --recursive Et voilà, vous êtes prêts ! -### Structure +## Structure Le module est structuré de la manière suivante : - `src/fetch` : Contient les fonctions de récupération des données de l'API d'EcoleDirecte - `src/session.ts` : Contient les fonctions de gestion de la session - `src/auth.ts` : Contient les fonctions d'authentification - `src/errors.ts` : Contient les erreurs pouvant être retournées par le module. *Les erreurs doivent suivre la même structure pour chaque module.* -- `src/types`: Submodule git des types des réponses ED. (réadaptation de [Armand CAMPONOVO](https:/github.com/camarm-dev), travail original [ab2r](https://github.com/ab2r)) +- `src/types`: Submodule git des types des réponses ED. (réadaptation de [Armand CAMPONOVO](https://github.com/camarm-dev), travail original [ab2r](https://github.com/ab2r)) - `src/utils/types`: Contient les types des `data` requêtes, et des types utiles à ce module. -### Scripts +## Scripts **Linter: eslint** ```shell diff --git a/examples/homeworks.ts b/examples/homeworks.ts index 441e113..98a283a 100644 --- a/examples/homeworks.ts +++ b/examples/homeworks.ts @@ -4,12 +4,11 @@ import { login, ED } from "./login"; login().then(() => { ED.homeworks.fetch().then(homeworks => { - console.log("Devoirs:"); Object.keys(homeworks).forEach(key => { - console.log(`\tPour le ${key}:`); + console.log(`[${key}]`); const work = homeworks[key]; work.forEach(subject => { - console.log(`\t\tDevoirs en ${subject.matiere} (${subject.codeMatiere}), donné le ${subject.donneLe}. ${subject.effectue ? "Effectué": "Non effectué"}, ${subject.interrogation ? "interrogation prévue": "pas d'interrogation"} et ${subject.rendreEnLigne ? "documents à rendre en ligne": "rien à rendre en ligne"}.`); + console.log(`\tDevoirs en ${subject.matiere} (${subject.codeMatiere}), donné le ${subject.donneLe}. ${subject.effectue ? "Effectué": "Non effectué"}, ${subject.interrogation ? "interrogation prévue": "pas d'interrogation"} et ${subject.rendreEnLigne ? "documents à rendre en ligne": "rien à rendre en ligne"}.`); }); }); }); diff --git a/examples/login.ts b/examples/login.ts index 54be401..3cf7987 100644 --- a/examples/login.ts +++ b/examples/login.ts @@ -1,15 +1,49 @@ import {EDCore} from "../index"; -import {studentAccount} from "~/types"; import { v4 as uuidv4 } from "uuid"; +// @ts-ignore +import { Select } from "enquirer"; +import ora, {Ora} from "ora"; +import {AccountInfo} from "../src/utils/types/accounts"; export const ED = new EDCore(); +const username = ""; +const password = ""; +const uid = uuidv4(); + +async function handle2FA() { + const token = await ED.auth.get2FAToken(username, password); + const QCM = await ED.auth.get2FA(token); + const chooseAnswer = new Select({ + name: 'answer', + message: QCM.question, + choices: QCM.propositions + }); + const answer = await chooseAnswer.run() + const loader = ora('Envoie de la réponse...').start(); + const authFactors = await ED.auth.resolve2FA(answer); + loader.succeed('Envoie de la réponse') + loader.start('Connexion...') + await ED.auth.login(username, password, uid, authFactors) + loggedInHook(loader) +} + +function loggedInHook(loader: Ora) { + const account = ED.student as AccountInfo; + loader.succeed(`Connecté en tant que ${account.prenom} ${account.nom}`); +} + export async function login() { - await ED.auth.login("jean", "jean%", uuidv4()).then(() => { - const account = ED.student as studentAccount; - console.log(`Logged in as ${account.particule} ${account.prenom} ${account.nom}`); - }).catch(err => { - console.error(`Failed to login: Error ${err.code}: ${err.message}`); + const loader = ora('Authentification...').start(); + await ED.auth.login(username, password, uid).then(() => { + loggedInHook(loader); + }).catch(async err => { + if (err.code == 12) { + loader.fail('La double authentification est activée, répondez à la question pour vous connecter'); + await handle2FA(); + return; + } + loader.fail(`Failed to login: Error ${err.code}: ${err.message}`); process.exit(); }); } diff --git a/examples/timeline.ts b/examples/timeline.ts index 8e74174..51e285c 100644 --- a/examples/timeline.ts +++ b/examples/timeline.ts @@ -4,13 +4,13 @@ import { login, ED } from "./login"; login().then(() => { ED.timeline.fetch().then(timeline => { - console.log("Timeline personnelle:"); + console.log("[Timeline personnelle]"); timeline.forEach(event => { console.log(`\t${event.titre}, ${event.date} (${event.soustitre}, ${event.contenu}).`); }); }); ED.timeline.fetchCommonTimeline().then(data => { - console.log("Timeline commune:"); + console.log("[Timeline commune]"); data.postits.forEach(postit => { console.log(`\t[POSTIT] ${postit.contenu} par ${postit.auteur.particule} ${postit.auteur.nom}`); }); diff --git a/examples/timetable.ts b/examples/timetable.ts index da29617..0704147 100644 --- a/examples/timetable.ts +++ b/examples/timetable.ts @@ -5,11 +5,11 @@ import { login, ED } from "./login"; login().then(() => { // La date doit être donnée en YYYY-MM-DD const today = new Date(); - const todayDate = `${today.getFullYear()}-${today.getMonth() + 1}-${today.getDate()}`; + const month = today.getMonth() + 1 >= 10 ? today.getMonth(): `0${today.getMonth() + 2}` + const todayDate = `${today.getFullYear()}-${month}-${today.getDate()}`; ED.timetable.fetchByDay(todayDate).then(timetable => { - console.log("Emploi du temps d'aujourd'hui:"); - Object.keys(timetable).forEach(key => { - const matiere = timetable[key]; + console.log("\nEmploi du temps d'aujourd'hui:"); + timetable.forEach(matiere => { console.log(`\t${matiere.text ? matiere.text: matiere.matiere} (${matiere.codeMatiere}) avec ${matiere.prof ? matiere.prof: "pas de prof"}, de ${matiere.start_date} à ${matiere.end_date} en salle ${matiere.salle ? matiere.salle: "pas de salle"}.`); }); }); diff --git a/package.json b/package.json index 8c11fc2..5dfec56 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@papillonapp/ed-core", - "version": "0.2.7", + "version": "0.2.8", "description": "API EcoleDirecte pour PapillonApp (c)", "main": "./dist/index.js", "types": "./dist/index.d.ts", @@ -37,6 +37,7 @@ "@stylistic/eslint-plugin": "^1.5.3", "@types/jest": "^29.5.12", "@typescript-eslint/parser": "^6.18.0", + "enquirer": "^2.4.1", "eslint": "^8.56.0", "eslint-config-standard-with-typescript": "^43.0.1", "eslint-plugin-import": "^2.29.1", @@ -44,6 +45,7 @@ "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^6.1.1", "jest": "^29.7.0", + "ora": "^5.0.0", "remove": "^0.1.5", "ts-jest": "^29.1.2", "ts-node": "^10.9.2", diff --git a/src/Request.ts b/src/Request.ts index aa0f40a..cbcec31 100644 --- a/src/Request.ts +++ b/src/Request.ts @@ -1,14 +1,15 @@ -import { API } from "./constants"; +import {API, VERSION} from "./constants"; import {Session} from "./session"; import { + A2F_ERROR, + INVALID_API_URL, + INVALID_BODY, + INVALID_VERSION, + OBJECT_NOT_FOUND, SESSION_EXPIRED, TOKEN_INVALID, UNAUTHORIZED, - WRONG_CREDENTIALS, - INVALID_API_URL, - OBJECT_NOT_FOUND, - INVALID_BODY, - A2F_ERROR + WRONG_CREDENTIALS } from "~/errors"; import {RequestOptions} from "~/utils/types/requests"; import {response} from "~/types/v3/responses/default/responses"; @@ -29,28 +30,93 @@ class Request { }; } - async blob(url: string, body: string) { + /** + * + * @param url Path to fetch or Url to fetch + * @param body request payload + * @param completeUrl set to true, `url` will be used as a full url, not a route of "api.ecoledirecte.com" + * @param method GET request or POST request + */ + async blob(url: string, body: string, completeUrl: boolean = false, method: "POST" | "GET" = "POST") { if(this.session.isLoggedIn) this.requestOptions.headers["X-token"] = this.session._token; - const finalUrl = API + url; + const finalUrl = completeUrl ? url: API + url; + if (method == "GET") { + return await fetch(finalUrl, { + method: method, + headers: this.requestOptions.headers + }).then(response => response.blob()); + } return await fetch(finalUrl, { - method: "POST", + method: method, headers: this.requestOptions.headers, body: body }).then(response => response.blob()); } - async post(url: string, body: string) { - if(this.session.isLoggedIn) this.requestOptions.headers["X-token"] = this.session._token; - const finalUrl = API + url; - return await fetch(finalUrl, { + /** + * + * @param url The path to fetch + * @param body The string formatted body data + * @param params A string containing extra parameters (e.g "foo=bar&mode=auto") + * @param ignoreErrors Disable error handling, will return a response, even if it's an error response + */ + async post(url: string, body: string, params?: string, ignoreErrors: boolean = false) { + const paramsString = params ? "&" + params: ""; + const finalUrl = `${API}${url}${url.includes("?") ? `&verbe=post&v=${VERSION}${paramsString}` : `?verbe=post&v=${VERSION}${paramsString}`}`; + return await this.request(finalUrl, body, ignoreErrors); + } + + /** + * + * @param url The path to fetch + * @param body The string formatted body data + * @param params A string containing extra parameters (e.g "foo=bar&mode=auto") + * @param ignoreErrors Disable error handling, will return a response, even if it's an error response + */ + async get(url: string, body: string, params?: string, ignoreErrors: boolean = false) { + const paramsString = params ? "&" + params: ""; + const finalUrl = `${API}${url}${url.includes("?") ? `&verbe=get&v=${VERSION}${paramsString}` : `?verbe=get&v=${VERSION}${paramsString}`}`; + return await this.request(finalUrl, body, ignoreErrors); + } + + /** + * + * @param url The path to fetch + * @param body The string formatted body data + * @param params A string containing extra parameters (e.g "foo=bar&mode=auto") + * @param ignoreErrors Disable error handling, will return a response, even if it's an error response + */ + async delete(url: string, body: string, params?: string, ignoreErrors: boolean = false) { + const paramsString = params ? "&" + params: ""; + const finalUrl = `${API}${url}${url.includes("?") ? `&verbe=delete&v=${VERSION}${paramsString}` : `?verbe=delete&v=${VERSION}${paramsString}`}`; + return await this.request(finalUrl, body, ignoreErrors); + } + + /** + * + * @param url The path to fetch + * @param body The string formatted body data + * @param params A string containing extra parameters (e.g "foo=bar&mode=auto") + * @param ignoreErrors Disable error handling, will return a response, even if it's an error response + */ + async put(url: string, body: string, params?: string, ignoreErrors: boolean = false) { + const paramsString = params ? "&" + params: ""; + const finalUrl = `${API}${url}${url.includes("?") ? `&verbe=put&v=${VERSION}${paramsString}` : `?verbe=put&v=${VERSION}${paramsString}`}`; + return await this.request(finalUrl, body, ignoreErrors); + } + + async request(url: string, body: string, ignoreErrors: boolean = false) { + if(this.session._token) this.requestOptions.headers["X-token"] = this.session._token; + return await fetch(url, { method: "POST", headers: this.requestOptions.headers, body: body }) .then(res => res.text()) .then(res => { - const response = res.startsWith("{") ? JSON.parse(res) : res; - if(typeof response != "object" && response.includes("Loading...")) throw INVALID_API_URL.drop(); + const response = res.startsWith("{") ? JSON.parse(res): res; + if (ignoreErrors) return response; + if (typeof response != "object" && response.includes("Loading...")) throw INVALID_API_URL.drop(); if (response.code == 525) { throw SESSION_EXPIRED.drop(); } @@ -75,6 +141,9 @@ class Request { if(response.code == 250) { throw A2F_ERROR.drop(); } + if(response.code == 517) { + throw INVALID_VERSION.drop(); + } return response; }) as Promise; } diff --git a/src/auth.ts b/src/auth.ts index 51b83ac..18e7c3a 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -1,9 +1,19 @@ -import {account, loginRes, loginResData} from "~/types/v3"; +import { + account, + doubleauthResData, + doubleauthResSuccess, + doubleauthValidationResData, + doubleauthValidationResSuccess, + loginRes, + loginResData +} from "~/types/v3"; import bodyToString from "./utils/body"; import {Session} from "./session"; import {EstablishmentInfo} from "~/utils/types/establishments"; import {AccountInfo, Profile} from "~/utils/types/accounts"; -import {authRequestData} from "~/types/v3/requests/student"; +import {authRequestData, loginQCMValidationRequestData} from "~/types/v3/requests/student"; +import { body } from "~/types/v3/requests/default/body"; +import {decodeString, encodeString} from "~/utils/base64"; class Auth { @@ -39,7 +49,7 @@ class Auth { sexe: profile.sexe ?? "", classe: profile.classe, photo: profile.photo ?? "" - }; + } as AccountInfo; } #parseLoginResponse(response: loginRes) { @@ -59,7 +69,7 @@ class Auth { } } - async login(username: string, password: string, uuid: string) { + async login(username: string, password: string, uuid: string, fa?: { cv: string, cn: string }) { const url = "/login.awp"; const body = { identifiant: username, @@ -68,11 +78,52 @@ class Auth { sesouvenirdemoi: true, uuid: uuid } as authRequestData; + if (fa?.cv && fa?.cn) { + body.fa = [{ cv: fa.cv, cn: fa.cn }]; + } return await this.session.request.post(url, bodyToString(body)).then((response: loginRes) => { this.#parseLoginResponse(response); }); } + async get2FAToken(username: string, password: string): Promise { + const url = "/login.awp"; + const body = { + identifiant: username, + motdepasse: encodeURIComponent(password) + } as authRequestData; + return await this.session.request.post(url, bodyToString(body), "", true).then((response: loginRes) => response.token); + } + + async get2FA(token: string): Promise { + const url = "/connexion/doubleauth.awp"; + const body = {} as body; + + this.session._token = token; + return await this.session.request.get(url, bodyToString(body)).then((response: doubleauthResSuccess) => { + const parsedData = response.data; + const choices = []; + + parsedData.question = decodeString(parsedData.question, false); + for (const choice of parsedData.propositions) { + choices.push(decodeString(choice, false)); + } + parsedData.propositions = choices; + + return parsedData; + }); + } + + async resolve2FA(anwser: string): Promise { + const url = "/connexion/doubleauth.awp"; + const body = { + choix: encodeString(anwser) + } as loginQCMValidationRequestData; + return await this.session.request.post(url, bodyToString(body)).then((response: doubleauthValidationResSuccess) => { + return response.data; + }); + } + async renewToken(username: string, uuid: string, accessToken: string) { const url = "/login.awp"; const body = { diff --git a/src/constants.ts b/src/constants.ts index e333ea0..0c959ea 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -1,5 +1,7 @@ export const API = "https://api.ecoledirecte.com/v3"; +export const VERSION = "6.15.1"; export default { - API + API, + VERSION }; diff --git a/src/errors.ts b/src/errors.ts index 2d11d5a..298075c 100644 --- a/src/errors.ts +++ b/src/errors.ts @@ -14,6 +14,7 @@ const OBJECT_NOT_FOUND = error(10, "The object you were trying to retrieve was n const INVALID_BODY = error(11, "Values provided in body are wrong and the request errored with code 512."); const A2F_ERROR = error(12, "Dual authentication required"); const ACCOUNT_DISABLED = error(13, "Disabled Account"); +const INVALID_VERSION = error(14, "Please update the application to the latest version"); function error(code: number, message: ErrorMessage){ return { @@ -40,4 +41,5 @@ export { INVALID_BODY, A2F_ERROR, ACCOUNT_DISABLED, + INVALID_VERSION }; diff --git a/src/fetch/getCantine.ts b/src/fetch/getCantine.ts index e40d4c2..c984577 100644 --- a/src/fetch/getCantine.ts +++ b/src/fetch/getCantine.ts @@ -39,8 +39,8 @@ class GetCantine { async fetchSchoolMenu(): Promise> { const data = {} as body; - const url = "/menusRestaurationScolaire.awp?verbe=get"; - return await this.session.request.post(url, bodyToString(data)).then((response: schoolMenuResSuccess) => { + const url = "/menusRestaurationScolaire.awp"; + return await this.session.request.get(url, bodyToString(data)).then((response: schoolMenuResSuccess) => { const menuList: Array = []; for (const menu of response.data) { menuList.push(new Menu(menu, this.session)); diff --git a/src/fetch/getCloud.ts b/src/fetch/getCloud.ts index 4681092..93bd954 100644 --- a/src/fetch/getCloud.ts +++ b/src/fetch/getCloud.ts @@ -12,9 +12,9 @@ class GetCloud { } async fetch(): Promise> { - const url = `/cloud/E/${this.session.student.id}.awp?verbe=get`; + const url = `/cloud/E/${this.session.student.id}.awp`; const body = {} as body; - return await this.session.request.post(url, bodyToString(body)).then((response: cloudRes) => { + return await this.session.request.get(url, bodyToString(body)).then((response: cloudRes) => { return response.data; }) as Promise>; } diff --git a/src/fetch/getCommunicationBook.ts b/src/fetch/getCommunicationBook.ts index a034122..a57cc97 100644 --- a/src/fetch/getCommunicationBook.ts +++ b/src/fetch/getCommunicationBook.ts @@ -15,9 +15,9 @@ class GetCommunicationBook { } async fetch(): Promise { - const url = `/E/${this.session.student.id}/eleveCarnetCorrespondance.awp?verbe=get`; + const url = `/E/${this.session.student.id}/eleveCarnetCorrespondance.awp`; const data = {} as communicationBookRequestData; - return await this.session.request.post(url, bodyToString(data)).then((response: communicationBookRes) => response.data as communicationBookResData); + return await this.session.request.get(url, bodyToString(data)).then((response: communicationBookRes) => response.data as communicationBookResData); } } diff --git a/src/fetch/getDigitalManuals.ts b/src/fetch/getDigitalManuals.ts index fded0da..0f9c5df 100644 --- a/src/fetch/getDigitalManuals.ts +++ b/src/fetch/getDigitalManuals.ts @@ -15,9 +15,9 @@ class GetDigitalManuals { async fetch(): Promise { if(!this.session.findModule("MANUELS_SCOLAIRES").enable) throw MODULE_DISABLE.drop("MANUELS_SCOLAIRES"); - const url = `/Eleves/${this.session.student.id}/manuelsNumeriques.awp?verbe=get`; + const url = `/Eleves/${this.session.student.id}/manuelsNumeriques.awp`; const data = {} as manualsRequestData; - return await this.session.request.post(url, bodyToString(data)).then(response => response as manualsRes); + return await this.session.request.get(url, bodyToString(data)).then(response => response as manualsRes); } } diff --git a/src/fetch/getDocuments.ts b/src/fetch/getDocuments.ts index 509c931..2bbe670 100644 --- a/src/fetch/getDocuments.ts +++ b/src/fetch/getDocuments.ts @@ -13,9 +13,10 @@ class GetDocuments { /** * @param {string} année des documents Rien pour l'année actuelle, YYYY-YYYY pour l'année scolaire YYYY-YYYY. */ async fetch(archive: string = ""): Promise { - const url = `/elevesDocuments.awp?archive=${archive}&verbe=get`; + const url = "/elevesDocuments.awp"; + const parameters = `archive=${archive}`; const data = {} as documentsRequestData; - return await this.session.request.post(url, bodyToString(data)).then((response: studentDocsRes) => response.data as studentDocsResData); + return await this.session.request.get(url, bodyToString(data), parameters).then((response: studentDocsRes) => response.data as studentDocsResData); } } diff --git a/src/fetch/getDownloads.ts b/src/fetch/getDownloads.ts index 2f3e96f..54180bb 100644 --- a/src/fetch/getDownloads.ts +++ b/src/fetch/getDownloads.ts @@ -1,6 +1,8 @@ import { Session } from "~/session"; import bodyToString from "~/utils/body"; import {body} from "~/types/v3/requests/default/body"; +import {encodeString} from "~/utils/base64"; +import {AccountInfo} from "~/utils/types/accounts"; /** * ADMINISTRATIF is not supported by Ecoledirecte; it is used to programmatically add argument to request so the administrative document can be downloaded. @@ -21,7 +23,7 @@ class GetDownloads { * @param fileType * @param year two possibilities; empty if download current year message attachment / administrative document or YYYY-YYYY year range if downloading "archive" administrative document or old messages attachment */ - async getFileBlob(fileId: number | string, fileType: fileType, year: string = "") { + async getFileBlob(fileId: number | string, fileType: fileType, year: string = ""): Promise { const endpoint = `/telechargement.awp?verbe=get&fichierId=${fileId}`; let url; switch (fileType) { @@ -46,10 +48,23 @@ class GetDownloads { * @param fileType * @param year two possibilities; empty if download current year message attachment / administrative document or YYYY-YYYY year range if downloading "archive" administrative document or old messages attachment */ - async getFileBase64(fileId: number | string, fileType: fileType, year: string = "") { + async getFileBase64(fileId: number | string, fileType: fileType, year: string = ""): Promise { const blob = await this.getFileBlob(fileId, fileType, year); const binaryString = String.fromCharCode(...new Uint8Array(await blob.arrayBuffer())); - return `data:${blob.type};base64,${btoa(binaryString)}`; + return `data:${blob.type};base64,${encodeString(binaryString, "binary")}`; + } + + /** + * Renvoie la photo de profil en format base64 + */ + async getProfilePictureBase64(): Promise { + const student = this.session.student as AccountInfo; + const url = student.photo; + this.session.request.requestOptions.headers["Referer"] = "https://www.ecoledirecte.com/"; + const blob = await this.session.request.blob(url, bodyToString({}), true, "GET"); + delete this.session.request.requestOptions.headers["Referer"]; + const binaryString = String.fromCharCode(...new Uint8Array(await blob.arrayBuffer())); + return `data:${blob.type};base64,${encodeString(binaryString, "binary")}`; } } diff --git a/src/fetch/getForms.ts b/src/fetch/getForms.ts index f0c1632..6d574a8 100644 --- a/src/fetch/getForms.ts +++ b/src/fetch/getForms.ts @@ -12,13 +12,13 @@ class GetForms { } async fetch(annee: string = "2023-2024"): Promise> { - const url = "/edforms.awp?verbe=get"; + const url = "/edforms.awp"; const data = { anneeForms: annee, typeEntity: "E", idEntity: this.session.student.id } as formsRequestData; - return await this.session.request.post(url, bodyToString(data)).then((response: formsRes) => response.data as Array
); + return await this.session.request.get(url, bodyToString(data)).then((response: formsRes) => response.data as Array); } } diff --git a/src/fetch/getGrades.ts b/src/fetch/getGrades.ts index 6555328..8cc808c 100644 --- a/src/fetch/getGrades.ts +++ b/src/fetch/getGrades.ts @@ -13,11 +13,11 @@ class GetGrades { } async fetch(): Promise { - const url = `/eleves/${this.session.student.id}/notes.awp?verbe=get`; + const url = `/eleves/${this.session.student.id}/notes.awp`; const body = { anneeScolaire: "" } as gradesRequestData; - return await this.session.request.post(url, bodyToString(body)).then((response: gradesRes) => { + return await this.session.request.get(url, bodyToString(body)).then((response: gradesRes) => { const data = response.data as gradesResData; data.parametrage.libelleEval1 = decodeString(data.parametrage.libelleEval1); data.parametrage.libelleEval2 = decodeString(data.parametrage.libelleEval2); diff --git a/src/fetch/getHomeworks.ts b/src/fetch/getHomeworks.ts index 698f35f..b6eb86e 100644 --- a/src/fetch/getHomeworks.ts +++ b/src/fetch/getHomeworks.ts @@ -39,15 +39,15 @@ class GetHomeworks { } async fetch(): Promise { - const url = `/Eleves/${this.session.student.id}/cahierdetexte.awp?verbe=get`; + const url = `/Eleves/${this.session.student.id}/cahierdetexte.awp`; const data = {} as textbookRequestData; - return await this.session.request.post(url, bodyToString(data)).then((response: textbookResSuccess) => response.data); + return await this.session.request.get(url, bodyToString(data)).then((response: textbookResSuccess) => response.data); } async getByDay(day: string, removeHTMLTags: boolean = true): Promise { - const url = `/Eleves/${this.session.student.id}/cahierdetexte/${day}.awp?verbe=get`; + const url = `/Eleves/${this.session.student.id}/cahierdetexte/${day}.awp`; const data = {} as textbookRequestData; - return await this.session.request.post(url, bodyToString(data)).then((response: textbookDateRes) => { + return await this.session.request.get(url, bodyToString(data)).then((response: textbookDateRes) => { if (response.code == 200) { const data = response.data as textbookDateResData; const homeworks = data.matieres; @@ -62,12 +62,12 @@ class GetHomeworks { } async setStatus(homeworkID: number, isDone: boolean): Promise { - const url = `/E/${this.session.student.id}/cahierdetexte.awp?verbe=put`; + const url = `/E/${this.session.student.id}/cahierdetexte.awp`; const data = { idDevoirsEffectues: [isDone ? homeworkID : null], idDevoirsNonEffectues: [isDone ? null : homeworkID] } as textbookSetDoneStatusRequestData; - return await this.session.request.post(url, bodyToString(data)); + return await this.session.request.put(url, bodyToString(data)); } } diff --git a/src/fetch/getMessaging.ts b/src/fetch/getMessaging.ts index 986f3b1..f67b5b0 100644 --- a/src/fetch/getMessaging.ts +++ b/src/fetch/getMessaging.ts @@ -13,41 +13,45 @@ class GetMessaging { } async fetchReceivedMessages(anneeMessages: string = "2023-2024"): Promise { - const url = `/eleves/${this.session.student.id}/messages.awp?force=false&typeRecuperation=received&idClasseur=0&orderBy=date&order=desc&query=&onlyRead=&page=0&itemsPerPage=100&getAll=0&verbe=get`; + const url = `/eleves/${this.session.student.id}/messages.awp`; + const parameters = "force=false&typeRecuperation=received&idClasseur=0&orderBy=date&order=desc&query=&onlyRead=&page=0&itemsPerPage=100&getAll=0"; const data = { "anneeMessages": anneeMessages } as mailboxRequestData; - return await this.session.request.post(url, bodyToString(data)).then((response: mailboxRes) => { + return await this.session.request.get(url, bodyToString(data), parameters).then((response: mailboxRes) => { return response.data; }) as Promise; } async fetchSentMessages(anneeMessages: string = "2023-2024"): Promise { - const url = `/eleves/${this.session.student.id}/messages.awp?force=false&typeRecuperation=sent&idClasseur=0&orderBy=date&order=desc&query=&onlyRead=&page=0&itemsPerPage=100&getAll=0&verbe=get`; + const url = `/eleves/${this.session.student.id}/messages.awp`; + const parameters = "force=false&typeRecuperation=sent&idClasseur=0&orderBy=date&order=desc&query=&onlyRead=&page=0&itemsPerPage=100&getAll=0"; const data = { "anneeMessages": anneeMessages } as mailboxRequestData; - return await this.session.request.post(url, bodyToString(data)).then((response: mailboxRes) => { + return await this.session.request.get(url, bodyToString(data), parameters).then((response: mailboxRes) => { return response.data; }) as Promise; } async fetchMessageContentReceived(messageId: number, anneeMessages = "2023-2024"): Promise { - const url = `/eleves/${this.session.student.id}/messages/${messageId}.awp?verbe=get&mode=destinataire`; + const url = `/eleves/${this.session.student.id}/messages/${messageId}.awp`; + const parameters = "mode=destinataire"; const data = { "anneeMessages": anneeMessages } as mailboxRequestData; - return await this.session.request.post(url, bodyToString(data)).then((response: mailboxRes) => { + return await this.session.request.get(url, bodyToString(data), parameters).then((response: mailboxRes) => { return response.data; }) as Promise; } async fetchMessageContentSent(messageId: number, anneeMessages = "2023-2024"): Promise { - const url = `/eleves/${this.session.student.id}/messages/${messageId}.awp?verbe=get&mode=expediteur`; + const url = `/eleves/${this.session.student.id}/messages/${messageId}.awp`; + const parameters = "mode=expediteur"; const data = { "anneeMessages": anneeMessages } as mailboxRequestData; - return await this.session.request.post(url, bodyToString(data)).then((response: mailboxRes) => { + return await this.session.request.get(url, bodyToString(data), parameters).then((response: mailboxRes) => { return response.data; }) as Promise; } diff --git a/src/fetch/getOrders.ts b/src/fetch/getOrders.ts index c5f5efa..86d67f1 100644 --- a/src/fetch/getOrders.ts +++ b/src/fetch/getOrders.ts @@ -29,8 +29,8 @@ class GetOrders { async fetchOrders(): Promise { if(!this.isEnabled()) return undefined; const data = {} as body; - const url = `/E/${this.session.student.id}/commandesPassage.awp?verbe=get`; - return await this.session.request.post(url, bodyToString(data)).then((response: ordersResSuccess) => response.data); + const url = `/E/${this.session.student.id}/commandesPassage.awp`; + return await this.session.request.get(url, bodyToString(data)).then((response: ordersResSuccess) => response.data); } /** @@ -42,8 +42,8 @@ class GetOrders { const splitDate = date.split("-"); const formattedDate = `${splitDate[0]}${splitDate[1]}${splitDate[2]}`; const data = {} as body; - const url = `/E/${this.session.student.id}/commandesPassage/pointsDePassage/${placeId}/${formattedDate}.awp?verbe=get`; - return await this.session.request.post(url, bodyToString(data)).then((response: startOrderResSuccess) => response.data); + const url = `/E/${this.session.student.id}/commandesPassage/pointsDePassage/${placeId}/${formattedDate}.awp`; + return await this.session.request.get(url, bodyToString(data)).then((response: startOrderResSuccess) => response.data); } /** @@ -54,7 +54,7 @@ class GetOrders { * @param placeId - the id of "point de passage" (probably means place) */ async order(articles: Array, hour: string, date: string, placeId: number): Promise { - const url = `/E/${this.session.student.id}/commandesPassage.awp?verbe=post`; + const url = `/E/${this.session.student.id}/commandesPassage.awp`; const data = { articles: articles, creneau: hour, @@ -65,9 +65,9 @@ class GetOrders { } async deleteOrder(orderId: number): Promise { - const url = `/E/${this.session.student.id}/commandesPassage.awp/${orderId}.awp?verbe=delete`; + const url = `/E/${this.session.student.id}/commandesPassage.awp/${orderId}.awp`; const data = {} as body; - return await this.session.request.post(url, bodyToString(data)).then((response: emptyRes) => response); + return await this.session.request.delete(url, bodyToString(data)).then((response: emptyRes) => response); } } diff --git a/src/fetch/getSchoolLife.ts b/src/fetch/getSchoolLife.ts index 88396bb..43e07e4 100644 --- a/src/fetch/getSchoolLife.ts +++ b/src/fetch/getSchoolLife.ts @@ -14,9 +14,9 @@ class GetSchoolLife { } async fetch(): Promise { - const url = `/eleves/${this.session.student.id}/viescolaire.awp?verbe=get`; + const url = `/eleves/${this.session.student.id}/viescolaire.awp`; const data = {} as schoolLifeRequestData; - return await this.session.request.post(url, bodyToString(data)).then((response: schoolLifeRes) => { + return await this.session.request.get(url, bodyToString(data)).then((response: schoolLifeRes) => { const res = response.data as EDCoreSchoolLifeResData; for (let i = 0; i < res.absencesRetards.length; i++) { res.absencesRetards[i].interval = dateStringAsTimeInterval(res.absencesRetards[i].displayDate); diff --git a/src/fetch/getTimeline.ts b/src/fetch/getTimeline.ts index c599300..5cf98de 100644 --- a/src/fetch/getTimeline.ts +++ b/src/fetch/getTimeline.ts @@ -13,15 +13,15 @@ class GetTimeline { } async fetch(): Promise> { - const url = `/eleves/${this.session.student.id}/timeline.awp?verbe=get`; + const url = `/eleves/${this.session.student.id}/timeline.awp`; const data = {} as timelineRequestData; - return await this.session.request.post(url, bodyToString(data)).then((response: studTlRes) => response.data as Array); + return await this.session.request.get(url, bodyToString(data)).then((response: studTlRes) => response.data as Array); } async fetchCommonTimeline(): Promise { - const url = `/E/${this.session.student.id}/timelineAccueilCommun.awp?verbe=get`; + const url = `/E/${this.session.student.id}/timelineAccueilCommun.awp`; const data = {} as timelineRequestData; - return await this.session.request.post(url, bodyToString(data)).then((response: studCommonTlRes) => { + return await this.session.request.get(url, bodyToString(data)).then((response: studCommonTlRes) => { if (response.code == 200) { const data = response.data as studCommonTlResData; data.evenements.forEach(event => { diff --git a/src/fetch/getTimetable.ts b/src/fetch/getTimetable.ts index 7ab70bc..ca3eeed 100644 --- a/src/fetch/getTimetable.ts +++ b/src/fetch/getTimetable.ts @@ -13,25 +13,25 @@ class GetTimetable { } async fetchByDay(date: string): Promise { - const url = `/E/${this.session.student.id}/emploidutemps.awp?verbe=get`; + const url = `/E/${this.session.student.id}/emploidutemps.awp`; const data = { dateDebut: date, dateFin: date, avecTrous: false } as timetableRequestData; - return await this.session.request.post(url, bodyToString(data)).then((response: timetableRes) => { + return await this.session.request.get(url, bodyToString(data)).then((response: timetableRes) => { return response.data; }) as Promise; } async fetchByDate(startDate: string, endDate: string): Promise { - const url = `/E/${this.session.student.id}/emploidutemps.awp?verbe=get`; + const url = `/E/${this.session.student.id}/emploidutemps.awp`; const data = { dateDebut: startDate, dateFin: endDate, avecTrous: false } as timetableRequestData; - return await this.session.request.post(url, bodyToString(data)).then((response: timetableRes) => { + return await this.session.request.get(url, bodyToString(data)).then((response: timetableRes) => { return response.data; }) as Promise; } diff --git a/src/fetch/getWorkspaces.ts b/src/fetch/getWorkspaces.ts index 634d865..6b419bb 100644 --- a/src/fetch/getWorkspaces.ts +++ b/src/fetch/getWorkspaces.ts @@ -23,50 +23,50 @@ class GetWorkspaces { } async fetch(): Promise> { - const url = `/E/${this.session.student.id}/espacestravail.awp?verbe=get`; + const url = `/E/${this.session.student.id}/espacestravail.awp`; const data = {} as workspaceRequestData; - return await this.session.request.post(url, bodyToString(data)).then((response: workspacesRes) => response.data as Array); + return await this.session.request.get(url, bodyToString(data)).then((response: workspacesRes) => response.data as Array); } async get(id: string): Promise { - const url = `/E/${this.session.student.id}/espacestravail/${id}.awp?verbe=get`; + const url = `/E/${this.session.student.id}/espacestravail/${id}.awp`; const data = {} as workspaceRequestData; - return await this.session.request.post(url, bodyToString(data)).then((response: workspaceRes) => response.data as workspace); + return await this.session.request.get(url, bodyToString(data)).then((response: workspaceRes) => response.data as workspace); } async getDiary(espaceId: number): Promise { - const url = `/W/${espaceId}/agendaEvenements.awp?verbe=get`; + const url = `/W/${espaceId}/agendaEvenements.awp`; const data = { nbProchainsEvents: 0 } as workspaceDiaryRequestData; - return await this.session.request.post(url, bodyToString(data)).then((response: workspaceDiaryRes) => response.data as diaryResData); + return await this.session.request.get(url, bodyToString(data)).then((response: workspaceDiaryRes) => response.data as diaryResData); } async getTopics(espaceId: number): Promise { - const url = `/E/${this.session.student.id}/espacestravail/${espaceId}/topics.awp?verbe=get`; + const url = `/E/${this.session.student.id}/espacestravail/${espaceId}/topics.awp`; const data = {}; - return await this.session.request.post(url, bodyToString(data)).then((response: workspaceTopicsRes) => response.data as topicsResData); + return await this.session.request.get(url, bodyToString(data)).then((response: workspaceTopicsRes) => response.data as topicsResData); } async getMembers(espaceId: number): Promise { - const url = `/E/${this.session.student.id}/espacestravail/${espaceId}/membres.awp?verbe=get`; + const url = `/E/${this.session.student.id}/espacestravail/${espaceId}/membres.awp`; const data = {}; - return await this.session.request.post(url, bodyToString(data)).then((response: workspaceMembersRes) => response.data as membersResData); + return await this.session.request.get(url, bodyToString(data)).then((response: workspaceMembersRes) => response.data as membersResData); } async join(espace: workspace): Promise { - const url = `/E/${this.session.student.id}/espacestravail/${espace.id}/acces.awp?verbe=post`; + const url = `/E/${this.session.student.id}/espacestravail/${espace.id}/acces.awp`; const data = {} as joinWorkspaceRequestData; return await this.session.request.post(url, bodyToString(data)).then((response: emptyRes) => response); } async leave(espace: number): Promise { - const url = `/E/${this.session.student.id}/espacestravail/${espace}/acces.awp?verbe=delete`; + const url = `/E/${this.session.student.id}/espacestravail/${espace}/acces.awp`; const data = {} as workspaceRequestData; - return await this.session.request.post(url, bodyToString(data)).then((response: emptyRes) => response); + return await this.session.request.delete(url, bodyToString(data)).then((response: emptyRes) => response); } } diff --git a/src/session.ts b/src/session.ts index b0daf44..7a32e61 100644 --- a/src/session.ts +++ b/src/session.ts @@ -8,8 +8,8 @@ import {GetMessaging} from "~/fetch/getMessaging"; import {Request} from "./Request"; import {Auth} from "./auth"; -import {account, accountModule} from "~/types/v3"; -import {BlankAccount, accountParameters} from "~/utils/types/accounts"; +import {accountModule} from "~/types/v3"; +import {BlankAccount, accountParameters, AccountInfo} from "~/utils/types/accounts"; import {EmptyModule} from "~/utils/types/modules"; import {EstablishmentInfo} from "~/utils/types/establishments"; import {GetTimeline} from "~/fetch/getTimeline"; @@ -29,7 +29,7 @@ class Session { _accessToken: undefined | string; isLoggedIn: boolean; settings?: accountParameters; - student: account | BlankAccount; + student: AccountInfo | BlankAccount; school?: EstablishmentInfo; modules?: Array; diff --git a/src/types b/src/types index efde213..2728e02 160000 --- a/src/types +++ b/src/types @@ -1 +1 @@ -Subproject commit efde213f785d34187fe055fd653078a0dceb42d1 +Subproject commit 2728e022daf63f51f3be6bda88c940f7fdfd3dfb diff --git a/src/utils/base64.ts b/src/utils/base64.ts index ccf89bc..33c3bc4 100644 --- a/src/utils/base64.ts +++ b/src/utils/base64.ts @@ -3,9 +3,17 @@ * * Escape function allow to transform encoded chars into utf-8 ones. * */ -export function decodeString(value: string) { - if (escape) { - return decodeURIComponent(escape(atob(value))); +export function decodeString(value: string, escapeString: boolean = true): string { + const decoded = Buffer.from(value, "base64").toString(); + if (escape && escapeString) { + return decodeURIComponent(escape(decoded)); } - return atob(value); + return decoded; +} + +/** + * Encode string to b64 + */ +export function encodeString(value: string, encoding: BufferEncoding = "utf8"): string { + return Buffer.from(value, encoding).toString("base64"); } diff --git a/src/utils/types/requests.ts b/src/utils/types/requests.ts index 0ddfa5f..ed73ed1 100644 --- a/src/utils/types/requests.ts +++ b/src/utils/types/requests.ts @@ -4,6 +4,7 @@ interface RequestOptions { interface EDHeaders { "X-token"?: string + "Referer"?: string } export type { diff --git a/src/utils/types/timetable.ts b/src/utils/types/timetable.ts index ac969aa..34822ce 100644 --- a/src/utils/types/timetable.ts +++ b/src/utils/types/timetable.ts @@ -1,8 +1,6 @@ import {course} from "~/types"; -interface timetableCourseList { - [key: string]: course -} +type timetableCourseList = course[]; export type { timetableCourseList