|
| 1 | +import type { Cheerio } from "cheerio"; |
| 2 | +import type { AnyNode } from "domhandler"; |
1 | 3 | import type { HttpResponse } from "schwi"; |
| 4 | +import type { Module, ReportCard, Semester, TeachingUnit } from "./models"; |
| 5 | +import { load as cheerio } from "cheerio"; |
2 | 6 | import { HttpRequest, HttpRequestRedirection, send } from "schwi"; |
3 | 7 | import { CAS, OAuth2 } from "~cas"; |
4 | 8 |
|
| 9 | +export * from "./models"; |
| 10 | + |
5 | 11 | export class Signatures { |
6 | 12 | public static readonly COOKIE = "session"; |
7 | 13 |
|
8 | 14 | 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"]); |
10 | 16 |
|
11 | 17 | public constructor( |
12 | 18 | public readonly session: string |
@@ -59,4 +65,90 @@ export class Signatures { |
59 | 65 |
|
60 | 66 | return new Signatures(session); |
61 | 67 | } |
| 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 | + } |
62 | 154 | } |
0 commit comments