Skip to content

Commit 8436a99

Browse files
committed
feat: add signatures parsing
1 parent 344d329 commit 8436a99

File tree

11 files changed

+1541
-5
lines changed

11 files changed

+1541
-5
lines changed

src/biome/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { type CAS, OAuth2 } from "~cas";
66
export * from "./models";
77

88
export class Biome {
9-
public static readonly oauth2: OAuth2 = new OAuth2("biome-prod", "https://biome.unilim.fr/authentication/callback", ["openid", "profile", "email"]);
9+
public static readonly oauth2 = new OAuth2("biome-prod", "https://biome.unilim.fr/authentication/callback", ["openid", "profile", "email"]);
1010
private static readonly HOST = "https://apis.unilim.fr";
1111

1212
public get roles(): Array<string> {

src/iut/signatures/index.ts

Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1+
import type { Cheerio } from "cheerio";
2+
import type { AnyNode } from "domhandler";
13
import type { HttpResponse } from "schwi";
4+
import type { Module, ReportCard, Semester, TeachingUnit } from "./models";
5+
import { load as cheerio } from "cheerio";
26
import { HttpRequest, HttpRequestRedirection, send } from "schwi";
37
import { CAS, OAuth2 } from "~cas";
48

9+
export * from "./models";
10+
511
export class Signatures {
612
public static readonly COOKIE = "session";
713

814
public static readonly HOST = "https://signatures.unilim.fr";
9-
public static readonly oauth2: OAuth2 = new OAuth2("signatures", "https://signatures.unilim.fr/callback", ["openid", "profile"]);
15+
public static readonly oauth2 = new OAuth2("signatures", "https://signatures.unilim.fr/callback", ["openid", "profile"]);
1016

1117
public constructor(
1218
public readonly session: string
@@ -59,4 +65,90 @@ export class Signatures {
5965

6066
return new Signatures(session);
6167
}
68+
69+
public static parse(html: string): ReportCard {
70+
const text = <T extends AnyNode>(node: Cheerio<T>): string =>
71+
node.text().trim();
72+
73+
const document = cheerio(html);
74+
const semesters: Array<Semester> = [];
75+
76+
for (const button of document("button[data-bs-target*=semestre-]")) {
77+
const table = document(`.tab-pane[aria-labelledby="${button.attribs.id}"] tbody`);
78+
79+
const units: Array<TeachingUnit> = [];
80+
let unit: TeachingUnit | undefined;
81+
let module: Module | undefined;
82+
83+
for (const tr of table.find("tr")) {
84+
const level = parseInt(tr.attribs.class.replace("level-", ""));
85+
86+
const col1 = document(tr).find("td").first();
87+
const col2 = col1.next();
88+
const col3 = col2.next();
89+
const col4 = col3.next();
90+
const col5 = col4.next();
91+
const col6 = col5.next();
92+
93+
const name = text(col2);
94+
const coefficient = Number(text(col4));
95+
const average = text(col5) ? Number(text(col5)) : null;
96+
97+
switch (level) {
98+
case 1: {
99+
unit = {
100+
average,
101+
code: text(col1),
102+
coefficient,
103+
modules: [],
104+
name
105+
};
106+
107+
units.push(unit);
108+
break;
109+
}
110+
111+
case 2: {
112+
if (!unit) break;
113+
114+
module = {
115+
average,
116+
code: text(col1),
117+
coefficient,
118+
exams: [],
119+
name
120+
};
121+
122+
unit.modules.push(module);
123+
break;
124+
}
125+
126+
case 3: {
127+
if (!module) break;
128+
129+
const globalAverage = text(col6).replace(/[()]/g, "");
130+
const date = text(col1); // DD/MM/YYYY, empty string if none.
131+
132+
module.exams.push({
133+
average,
134+
coefficient,
135+
date: date ? new Date(date.split("/").reverse().join("-")) : null,
136+
globalAverage: globalAverage ? Number(globalAverage) : null,
137+
name
138+
});
139+
}
140+
}
141+
}
142+
}
143+
144+
const [fullName, promotion] = document(".list-group-item").map((_, node) => {
145+
return text(document(node.lastChild!));
146+
}).toArray();
147+
148+
return {
149+
fullName,
150+
promotion,
151+
semesters
152+
};
153+
}
62154
}

src/iut/signatures/models/Exam.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export interface Exam {
2+
average: null | number;
3+
coefficient: number;
4+
date: Date | null;
5+
globalAverage: null | number;
6+
name: string;
7+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { Exam } from "./Exam";
2+
3+
export interface Module {
4+
average: null | number;
5+
code: string;
6+
coefficient: number;
7+
exams: Array<Exam>;
8+
name: string;
9+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import type { Semester } from "./Semester";
2+
3+
export interface ReportCard {
4+
fullName: string;
5+
promotion: string;
6+
semesters: Array<Semester>;
7+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import type { TeachingUnit } from "./TeachingUnit";
2+
3+
export interface Semester {
4+
name: string;
5+
units: Array<TeachingUnit>;
6+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { Module } from "./Module";
2+
3+
export interface TeachingUnit {
4+
average: null | number;
5+
code: string;
6+
coefficient: number;
7+
modules: Array<Module>;
8+
name: string;
9+
}

src/iut/signatures/models/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export type { Exam } from "./Exam";
2+
export type { Module } from "./Module";
3+
export type { ReportCard } from "./ReportCard";
4+
export type { Semester } from "./Semester";
5+
export type { TeachingUnit } from "./TeachingUnit";

tests/signatures/empty.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ describe("iut/signatures", () => {
1313
expect(Signatures.oauth2.scopes).toEqual(["openid", "profile"]);
1414
});
1515

16-
// it("should parse correctly the report card", () => {
17-
// const report = Signatures.parse(html as unknown as string);
18-
// });
16+
it("should parse correctly the report card", () => {
17+
const report = Signatures.parse(html as unknown as string);
18+
});
1919
});

0 commit comments

Comments
 (0)