Gestion de la session + En cours: Emploi du temps

This commit is contained in:
dd060606
2024-11-16 19:17:36 +01:00
parent d807c0d514
commit b0b91d7949
13 changed files with 4369 additions and 0 deletions

2
.gitignore vendored
View File

@@ -128,3 +128,5 @@ dist
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
.env

5
.prettierrc Normal file
View File

@@ -0,0 +1,5 @@
{
"semi": true,
"singleQuote": false,
"tabWidth": 4
}

12
jest.config.ts Normal file
View File

@@ -0,0 +1,12 @@
import type { Config } from "jest";
import dotenv from "dotenv";
import path from "path";
dotenv.config({ path: path.resolve(__dirname, "./.env") });
const config: Config = {
preset: "ts-jest",
testEnvironment: "node",
testMatch: ["**/tests/**/*.test.ts"],
};
export default config;

4088
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

27
package.json Normal file
View File

@@ -0,0 +1,27 @@
{
"name": "webaurion-api",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"build": "tsc",
"test": "jest",
"lint": "eslint src --ext .ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"axios": "^1.7.7",
"cheerio": "^1.0.0",
"dotenv": "^16.4.5"
},
"devDependencies": {
"@types/jest": "^29.5.14",
"jest": "^29.7.0",
"prettier": "^3.3.3",
"ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"typescript": "^5.6.3"
}
}

89
src/api/ScheduleApi.ts Normal file
View File

@@ -0,0 +1,89 @@
import { getJSONSchedule, getViewState } from "../utils/AurionUtils";
import Session from "./Session";
class ScheduleApi {
private session: Session;
constructor(session: Session) {
this.session = session;
}
public fetchSchedule(): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
try {
const schedulePage = await this.session.sendGET<string>(
"/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",
);
params.append("javax.faces.partial.render", "form:sidebar");
params.append("form:j_idt46", "form:j_idt46");
params.append(
"webscolaapp.Sidebar.ID_SUBMENU",
"submenu_291906",
);
params.append("form", "form");
params.append("javax.faces.ViewState", viewState);
const response = await this.session.sendPOST<string>(
"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<string>(
"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<string>(
"faces/Planning.xhtml",
params3,
);
console.log(getJSONSchedule(response3));
resolve(response2);
} else {
reject(new Error("Viewstate not found"));
}
} catch (error) {
reject(error);
}
});
}
}
export default ScheduleApi;

77
src/api/Session.ts Normal file
View File

@@ -0,0 +1,77 @@
import axios, { AxiosInstance } from "axios";
import ScheduleApi from "./ScheduleApi";
class Session {
private client: AxiosInstance;
constructor(baseURL: string, token: string) {
this.client = axios.create({
baseURL,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Cookie: `JSESSIONID=${token}`,
},
});
}
// API pour le calendrier
public getScheduleApi(): ScheduleApi {
return new ScheduleApi(this);
}
public sendGET<T>(url: string): Promise<T> {
return this.client.get<T>(url).then((response) => response.data);
}
public sendPOST<T>(url: string, data: unknown): Promise<T> {
return this.client.post<T>(url, data).then((response) => response.data);
}
}
/**
* Authentifie un utilisateur avec un nom d'utilisateur et un mot de passe.
*
* @param {string} username - Le nom d'utilisateur de l'utilisateur.
* @param {string} password - Le mot de passe de l'utilisateur.
* @returns {Promise<Session>} Une promesse qui se résout avec une instance de Session si l'authentification réussit.
* @throws {Error} Si l'authentification échoue.
*/
export function login(username: string, password: string): Promise<Session> {
const baseURL = "https://web.isen-ouest.fr/webAurion";
return new Promise<Session>((resolve, reject) => {
const params = new URLSearchParams();
params.append("username", username);
params.append("password", password);
axios
.post(`${baseURL}/login`, params, {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
maxRedirects: 0,
})
.then((response) => {
//Si la réponse est 302 et que le cookie est défini alors on retourne une nouvelle session
if (response.status === 302 && response.headers["set-cookie"]) {
const token = response.headers["set-cookie"][0]
.split(";")[0]
.split("=")[1];
resolve(new Session(baseURL, token));
} else {
reject(new Error("Login failed."));
}
})
.catch((err) => {
//Si la réponse est 302 et que le cookie est défini alors on retourne une nouvelle session
if (err.response && err.response.status === 302) {
const token = err.response.headers["set-cookie"][0]
.split(";")[0]
.split("=")[1];
resolve(new Session(baseURL, token));
} else {
reject(new Error("Login failed."));
}
});
});
}
export default Session;

1
src/index.ts Normal file
View File

@@ -0,0 +1 @@
export { default as Session } from "./api/Session";

24
src/utils/AurionUtils.ts Normal file
View File

@@ -0,0 +1,24 @@
import { load } from "cheerio";
// Extraction du ViewState de la page HTML (obligatoire pour effectuer une requête)
export function getViewState(html: string): string | undefined {
const parser = load(html);
//On recherche l'élément input avec l'attribut name="javax.faces.ViewState"
const inputElement = parser('input[name="javax.faces.ViewState"]');
if (inputElement.length > 0) {
//On récupère la valeur de l'attribut value
const viewState = inputElement.attr("value");
return viewState;
}
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"];
}

0
src/utils/types.ts Normal file
View File

15
tests/ScheduleApi.test.ts Normal file
View File

@@ -0,0 +1,15 @@
import { login } from "../src/api/Session";
describe("ScheduleApi", () => {
it("should receive a schedule", async () => {
const username = process.env.TEST_USERNAME;
const password = process.env.TEST_PASSWORD;
if (!username || !password) {
throw new Error(
"TEST_USERNAME or TEST_PASSWORD is not set in environment variables.",
);
}
const session = await login(username, password);
await session.getScheduleApi().fetchSchedule();
});
});

15
tests/Session.test.ts Normal file
View File

@@ -0,0 +1,15 @@
import { login } from "../src/api/Session";
describe("AuthApi", () => {
it("should log in a user and receive a session", async () => {
const username = process.env.TEST_USERNAME;
const password = process.env.TEST_PASSWORD;
if (!username || !password) {
throw new Error(
"TEST_USERNAME or TEST_PASSWORD is not set in environment variables.",
);
}
const result = await login(username, password);
expect(result).toBeDefined();
});
});

14
tsconfig.json Normal file
View File

@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES6",
"module": "CommonJS",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"moduleResolution": "node",
"declaration": true,
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "tests"]
}