@lime1/esprit-ts 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +162 -42
- package/dist/index.cjs +294 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +97 -1
- package/dist/index.d.ts +97 -1
- package/dist/index.js +294 -0
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
A TypeScript client library for the ESPRIT student portal. Scrape grades, absences, credits, schedules, and student information with full type safety.
|
|
4
4
|
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🔐 **Authentication**: Secure login/logout with session management
|
|
8
|
+
- 📊 **Academic Data**:
|
|
9
|
+
- Regular grades (incremental release during semester)
|
|
10
|
+
- Principal session results (final verdict)
|
|
11
|
+
- Rattrapage grades (retake session incremental)
|
|
12
|
+
- Rattrapage session results (retake final verdict)
|
|
13
|
+
- Historical ranking across multiple years
|
|
14
|
+
- Language proficiency levels (French & English)
|
|
15
|
+
- 📅 **Attendance**: Absence tracking
|
|
16
|
+
- 🎓 **Credits**: Academic credit history
|
|
17
|
+
- 📋 **Schedules**: Class schedules with download links
|
|
18
|
+
- 👤 **Student Info**: Name and class information
|
|
19
|
+
- 🔄 **ASP.NET Support**: Full handling of complex ASP.NET postback mechanisms
|
|
20
|
+
- 📦 **Type Safety**: Complete TypeScript definitions
|
|
21
|
+
|
|
5
22
|
## Installation
|
|
6
23
|
|
|
7
24
|
```bash
|
|
@@ -14,56 +31,117 @@ yarn add @lime1/esprit-ts
|
|
|
14
31
|
|
|
15
32
|
## Usage
|
|
16
33
|
|
|
34
|
+
### Basic Example
|
|
35
|
+
|
|
17
36
|
```typescript
|
|
18
37
|
import { EspritClient } from '@lime1/esprit-ts';
|
|
19
38
|
|
|
20
|
-
|
|
21
39
|
async function main() {
|
|
22
|
-
// Create client (optionally enable debug mode)
|
|
23
40
|
const client = new EspritClient({ debug: false });
|
|
24
41
|
|
|
25
|
-
// Login
|
|
42
|
+
// Login
|
|
26
43
|
const result = await client.login('your-student-id', 'your-password');
|
|
27
|
-
|
|
28
44
|
if (!result.success) {
|
|
29
45
|
console.error('Login failed:', result.message);
|
|
30
46
|
return;
|
|
31
47
|
}
|
|
32
48
|
|
|
33
|
-
console.log('Login successful!');
|
|
34
|
-
|
|
35
49
|
// Get student info
|
|
36
50
|
const info = await client.getStudentInfo();
|
|
37
|
-
console.log(
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
51
|
+
console.log(`${info.name} - ${info.className}`);
|
|
52
|
+
|
|
53
|
+
// Get regular grades (released during semester)
|
|
54
|
+
const regularGrades = await client.getRegularGrades();
|
|
55
|
+
if (regularGrades) {
|
|
56
|
+
const [headers, ...rows] = regularGrades;
|
|
57
|
+
console.log('Headers:', headers);
|
|
58
|
+
console.log('Grades:', rows);
|
|
59
|
+
}
|
|
43
60
|
|
|
44
|
-
// Get
|
|
45
|
-
const
|
|
46
|
-
if (
|
|
47
|
-
console.log(`
|
|
61
|
+
// Get principal session result (final verdict)
|
|
62
|
+
const principalResult = await client.getPrincipalResult();
|
|
63
|
+
if (principalResult) {
|
|
64
|
+
console.log(`Average: ${principalResult.moyenneGeneral}`);
|
|
65
|
+
console.log(`Decision: ${principalResult.decision}`);
|
|
48
66
|
}
|
|
49
67
|
|
|
50
|
-
|
|
68
|
+
await client.logout();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
main().catch(console.error);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Comprehensive Example
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { EspritClient } from '@lime1/esprit-ts';
|
|
78
|
+
|
|
79
|
+
async function fetchAllData() {
|
|
80
|
+
const client = new EspritClient({ debug: true });
|
|
81
|
+
|
|
82
|
+
const login = await client.login('your-id', 'your-password');
|
|
83
|
+
if (!login.success) return;
|
|
84
|
+
|
|
85
|
+
console.log('=== Student Info ===');
|
|
86
|
+
const info = await client.getStudentInfo();
|
|
87
|
+
console.log(info);
|
|
88
|
+
|
|
89
|
+
console.log('\n=== Regular Grades ===');
|
|
90
|
+
const regularGrades = await client.getRegularGrades();
|
|
91
|
+
console.log(regularGrades);
|
|
92
|
+
|
|
93
|
+
console.log('\n=== Principal Result ===');
|
|
94
|
+
const principalResult = await client.getPrincipalResult();
|
|
95
|
+
console.log(principalResult);
|
|
96
|
+
|
|
97
|
+
console.log('\n=== Rattrapage Grades ===');
|
|
98
|
+
const rattrapageGrades = await client.getRattrapageGrades();
|
|
99
|
+
console.log(rattrapageGrades);
|
|
100
|
+
|
|
101
|
+
console.log('\n=== Rattrapage Result ===');
|
|
102
|
+
const rattrapageResult = await client.getRattrapageResult();
|
|
103
|
+
console.log(rattrapageResult);
|
|
104
|
+
|
|
105
|
+
console.log('\n=== Language Levels ===');
|
|
106
|
+
const languageLevels = await client.getLanguageLevels();
|
|
107
|
+
console.log(languageLevels);
|
|
108
|
+
|
|
109
|
+
console.log('\n=== Ranking ===');
|
|
110
|
+
const ranking = await client.getRanking();
|
|
111
|
+
console.log(ranking);
|
|
112
|
+
|
|
113
|
+
console.log('\n=== Absences ===');
|
|
51
114
|
const absences = await client.getAbsences();
|
|
52
|
-
console.log(
|
|
115
|
+
console.log(absences);
|
|
53
116
|
|
|
54
|
-
|
|
117
|
+
console.log('\n=== Credits ===');
|
|
55
118
|
const credits = await client.getCredits();
|
|
56
|
-
console.log(
|
|
119
|
+
console.log(credits);
|
|
57
120
|
|
|
58
|
-
|
|
121
|
+
console.log('\n=== Schedules ===');
|
|
59
122
|
const schedules = await client.getSchedules();
|
|
60
|
-
console.log(
|
|
123
|
+
console.log(schedules);
|
|
61
124
|
|
|
62
|
-
// Logout when done
|
|
63
125
|
await client.logout();
|
|
64
126
|
}
|
|
65
127
|
|
|
66
|
-
|
|
128
|
+
fetchAllData().catch(console.error);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Legacy Grade Format with Averages
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// For backward compatibility, the old getGrades() method still works
|
|
135
|
+
// but returns parsed Grade objects instead of raw data
|
|
136
|
+
const grades = await client.getGrades(); // deprecated
|
|
137
|
+
const summary = await client.getGradesWithAverage();
|
|
138
|
+
|
|
139
|
+
if (summary) {
|
|
140
|
+
console.log(`Total Average: ${summary.totalAverage?.toFixed(2)}`);
|
|
141
|
+
summary.grades.forEach(grade => {
|
|
142
|
+
console.log(`${grade.designation}: ${grade.moyenne?.toFixed(2)}`);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
67
145
|
```
|
|
68
146
|
|
|
69
147
|
## API Reference
|
|
@@ -76,31 +154,47 @@ main().catch(console.error);
|
|
|
76
154
|
new EspritClient(config?: EspritClientConfig)
|
|
77
155
|
```
|
|
78
156
|
|
|
79
|
-
| Option
|
|
80
|
-
|
|
81
|
-
| `debug`
|
|
82
|
-
| `userAgent` | `string`
|
|
83
|
-
| `timeout`
|
|
157
|
+
| Option | Type | Default | Description |
|
|
158
|
+
| ----------- | --------- | --------- | ------------------------ |
|
|
159
|
+
| `debug` | `boolean` | `false` | Enable debug logging |
|
|
160
|
+
| `userAgent` | `string` | Chrome UA | Custom user agent string |
|
|
161
|
+
| `timeout` | `number` | `30000` | Request timeout in ms |
|
|
84
162
|
|
|
85
163
|
#### Methods
|
|
86
164
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
|
96
|
-
|
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
165
|
+
##### Authentication
|
|
166
|
+
| Method | Returns | Description |
|
|
167
|
+
| --------------------- | ---------------------- | ---------------------- |
|
|
168
|
+
| `login(id, password)` | `Promise<LoginResult>` | Login to the portal |
|
|
169
|
+
| `logout()` | `Promise<boolean>` | Logout from the portal |
|
|
170
|
+
| `getCookies()` | `Promise<Cookie[]>` | Get session cookies |
|
|
171
|
+
|
|
172
|
+
##### Grades & Results
|
|
173
|
+
| Method | Returns | Description |
|
|
174
|
+
| ------------------------ | ----------------------------------- | -------------------------------------------------------- |
|
|
175
|
+
| `getRegularGrades()` | `Promise<RegularGrade \| null>` | Get grades released during semester (Session Principale) |
|
|
176
|
+
| `getPrincipalResult()` | `Promise<PrincipalResult \| null>` | Get final verdict of principal session |
|
|
177
|
+
| `getRattrapageGrades()` | `Promise<RattrapageGrade \| null>` | Get retake exam grades (incremental) |
|
|
178
|
+
| `getRattrapageResult()` | `Promise<RattrapageResult \| null>` | Get final verdict of rattrapage session |
|
|
179
|
+
| `getLanguageLevels()` | `Promise<LanguageLevel \| null>` | Get French and English proficiency levels |
|
|
180
|
+
| `getRanking()` | `Promise<RankingEntry[] \| null>` | Get historical academic ranking |
|
|
181
|
+
| `getGrades()` | `Promise<Grade[] \| null>` | **DEPRECATED:** Use `getRegularGrades()` instead |
|
|
182
|
+
| `getGradesWithAverage()` | `Promise<GradeSummary \| null>` | Get parsed grades with calculated averages |
|
|
183
|
+
|
|
184
|
+
##### Other Data
|
|
185
|
+
| Method | Returns | Description |
|
|
186
|
+
| ------------------- | ----------------------------- | --------------------------- |
|
|
187
|
+
| `getAbsences()` | `Promise<Absence[] \| null>` | Get student absences |
|
|
188
|
+
| `getCredits()` | `Promise<Credit[] \| null>` | Get credit history |
|
|
189
|
+
| `getSchedules()` | `Promise<Schedule[] \| null>` | Get available schedules |
|
|
190
|
+
| `getStudentName()` | `Promise<string \| null>` | Get student name |
|
|
191
|
+
| `getStudentClass()` | `Promise<string \| null>` | Get student class |
|
|
192
|
+
| `getStudentInfo()` | `Promise<StudentInfo>` | Get name and class together |
|
|
100
193
|
|
|
101
194
|
### Types
|
|
102
195
|
|
|
103
196
|
```typescript
|
|
197
|
+
// Legacy parsed grade format (used by getGrades())
|
|
104
198
|
interface Grade {
|
|
105
199
|
designation: string;
|
|
106
200
|
coefficient: number | null;
|
|
@@ -115,6 +209,32 @@ interface GradeSummary {
|
|
|
115
209
|
totalCoefficient: number;
|
|
116
210
|
}
|
|
117
211
|
|
|
212
|
+
// New grade formats (raw table data)
|
|
213
|
+
type RegularGrade = string[][]; // First row is headers
|
|
214
|
+
type RattrapageGrade = string[][]; // First row is headers
|
|
215
|
+
|
|
216
|
+
interface PrincipalResult {
|
|
217
|
+
moyenneGeneral: string | null;
|
|
218
|
+
decision: string | null;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
interface RattrapageResult {
|
|
222
|
+
moyenneGeneral: string | null;
|
|
223
|
+
decision: string | null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
interface LanguageLevel {
|
|
227
|
+
francais: string | null;
|
|
228
|
+
anglais: string | null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
interface RankingEntry {
|
|
232
|
+
anneeUniversitaire: string | null;
|
|
233
|
+
classe: string | null;
|
|
234
|
+
moyenne: string | null;
|
|
235
|
+
rang: string | null;
|
|
236
|
+
}
|
|
237
|
+
|
|
118
238
|
interface LoginResult {
|
|
119
239
|
success: boolean;
|
|
120
240
|
cookies: Cookie[];
|
package/dist/index.cjs
CHANGED
|
@@ -173,6 +173,13 @@ var EspritClient = class {
|
|
|
173
173
|
ABSENCES_URL = "https://esprit-tn.com/ESPOnline/Etudiants/absenceetud.aspx";
|
|
174
174
|
CREDITS_URL = "https://esprit-tn.com/ESPOnline/Etudiants/Historique_Cr%C3%A9dit.aspx";
|
|
175
175
|
SCHEDULES_URL = "https://esprit-tn.com/ESPOnline/Etudiants/Emplois.aspx";
|
|
176
|
+
// New grade endpoints
|
|
177
|
+
REGULAR_GRADES_URL = "https://esprit-tn.com/ESPOnline/Etudiants/Resultat2021.aspx";
|
|
178
|
+
PRINCIPAL_RESULT_URL = "https://esprit-tn.com/ESPOnline/Etudiants/ResultatPrincipale2021.aspx";
|
|
179
|
+
RATTRAPAGE_GRADES_URL = "https://esprit-tn.com/ESPOnline/Etudiants/noterat.aspx";
|
|
180
|
+
RATTRAPAGE_RESULT_URL = "https://esprit-tn.com/ESPOnline/Etudiants/ResultatRattrapage2021.aspx";
|
|
181
|
+
LANGUAGE_LEVELS_URL = "https://esprit-tn.com/ESPOnline/Etudiants/LANG2.aspx";
|
|
182
|
+
RANKING_URL = "https://esprit-tn.com/ESPOnline/Etudiants/ranking.aspx";
|
|
176
183
|
LOGOUT_URLS = [
|
|
177
184
|
"https://esprit-tn.com/esponline/Etudiants/Deconnexion.aspx",
|
|
178
185
|
"https://esprit-tn.com/esponline/online/Deconnexion.aspx"
|
|
@@ -408,6 +415,7 @@ var EspritClient = class {
|
|
|
408
415
|
/**
|
|
409
416
|
* Get student grades.
|
|
410
417
|
*
|
|
418
|
+
* @deprecated Use getRegularGrades() for more accurate naming. This method will be removed in a future version.
|
|
411
419
|
* @returns Array of Grade objects or null if page cannot be loaded
|
|
412
420
|
*/
|
|
413
421
|
async getGrades() {
|
|
@@ -685,6 +693,292 @@ var EspritClient = class {
|
|
|
685
693
|
return { name: null, className: null };
|
|
686
694
|
}
|
|
687
695
|
}
|
|
696
|
+
/**
|
|
697
|
+
* Get regular grades (grades released incrementally during the semester - Session Principale).
|
|
698
|
+
*
|
|
699
|
+
* This replaces the old getGrades() method with more accurate naming.
|
|
700
|
+
* Fetches data from Resultat2021.aspx which displays module grades as they're released.
|
|
701
|
+
*
|
|
702
|
+
* @returns Array of string arrays where first row is headers, or null if unavailable
|
|
703
|
+
*/
|
|
704
|
+
async getRegularGrades() {
|
|
705
|
+
try {
|
|
706
|
+
const response = await this.client.get(this.REGULAR_GRADES_URL, { maxRedirects: 5 });
|
|
707
|
+
if (isLoginPage(response.request?.res?.responseUrl ?? "")) {
|
|
708
|
+
this.log("Session expired - redirected to login");
|
|
709
|
+
return null;
|
|
710
|
+
}
|
|
711
|
+
if (response.data.includes("aucune note!")) {
|
|
712
|
+
this.log("No grades available yet");
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
const $ = cheerio2.load(response.data);
|
|
716
|
+
const h1Tag = $("h1").filter((_, el) => {
|
|
717
|
+
const text = $(el).text();
|
|
718
|
+
return text.includes("Notes Des Modules");
|
|
719
|
+
});
|
|
720
|
+
if (h1Tag.length === 0) {
|
|
721
|
+
this.log("Page does not contain expected content");
|
|
722
|
+
return null;
|
|
723
|
+
}
|
|
724
|
+
const table = $("#ContentPlaceHolder1_GridView1");
|
|
725
|
+
if (table.length === 0) {
|
|
726
|
+
this.log("Grades table not found");
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
const { headers, rows } = parseTable($, table);
|
|
730
|
+
return [headers, ...rows];
|
|
731
|
+
} catch (error) {
|
|
732
|
+
this.log("Error getting regular grades:", error);
|
|
733
|
+
return null;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
/**
|
|
737
|
+
* Get the final verdict/decision at the end of the principal session.
|
|
738
|
+
*
|
|
739
|
+
* Fetches data from ResultatPrincipale2021.aspx showing the overall average and pass/fail decision.
|
|
740
|
+
*
|
|
741
|
+
* @returns PrincipalResult with moyenneGeneral and decision, or null if unavailable
|
|
742
|
+
*/
|
|
743
|
+
async getPrincipalResult() {
|
|
744
|
+
try {
|
|
745
|
+
const response = await this.client.get(this.PRINCIPAL_RESULT_URL, { maxRedirects: 5 });
|
|
746
|
+
if (isLoginPage(response.request?.res?.responseUrl ?? "")) {
|
|
747
|
+
this.log("Session expired - redirected to login");
|
|
748
|
+
return null;
|
|
749
|
+
}
|
|
750
|
+
const $ = cheerio2.load(response.data);
|
|
751
|
+
const h1Tag = $("h1").filter((_, el) => {
|
|
752
|
+
const text = $(el).text();
|
|
753
|
+
return text.includes("Resultat Session Principale");
|
|
754
|
+
});
|
|
755
|
+
if (h1Tag.length === 0) {
|
|
756
|
+
this.log("Page does not contain expected content");
|
|
757
|
+
return null;
|
|
758
|
+
}
|
|
759
|
+
const table = $("#ContentPlaceHolder1_GridView3");
|
|
760
|
+
if (table.length === 0) {
|
|
761
|
+
this.log("Result table not found");
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
764
|
+
const { headers, rows } = parseTable($, table);
|
|
765
|
+
if (rows.length === 0) {
|
|
766
|
+
this.log("No result data found");
|
|
767
|
+
return null;
|
|
768
|
+
}
|
|
769
|
+
const headerLower = headers.map((h) => h.toLowerCase());
|
|
770
|
+
const moyenneIdx = headerLower.findIndex((h) => h.includes("moyenne"));
|
|
771
|
+
const decisionIdx = headerLower.findIndex((h) => h.includes("decision"));
|
|
772
|
+
return {
|
|
773
|
+
moyenneGeneral: moyenneIdx >= 0 ? rows[0][moyenneIdx] ?? null : null,
|
|
774
|
+
decision: decisionIdx >= 0 ? rows[0][decisionIdx] ?? null : null
|
|
775
|
+
};
|
|
776
|
+
} catch (error) {
|
|
777
|
+
this.log("Error getting principal result:", error);
|
|
778
|
+
return null;
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Get rattrapage (retake) grades released incrementally during the retake session.
|
|
783
|
+
*
|
|
784
|
+
* Similar to regular grades but for the retake session, fetched from noterat.aspx.
|
|
785
|
+
*
|
|
786
|
+
* @returns Array of string arrays where first row is headers, or null if unavailable
|
|
787
|
+
*/
|
|
788
|
+
async getRattrapageGrades() {
|
|
789
|
+
try {
|
|
790
|
+
const response = await this.client.get(this.RATTRAPAGE_GRADES_URL, { maxRedirects: 5 });
|
|
791
|
+
if (isLoginPage(response.request?.res?.responseUrl ?? "")) {
|
|
792
|
+
this.log("Session expired - redirected to login");
|
|
793
|
+
return null;
|
|
794
|
+
}
|
|
795
|
+
if (response.data.includes("aucune note!")) {
|
|
796
|
+
this.log("No rattrapage grades available yet");
|
|
797
|
+
return null;
|
|
798
|
+
}
|
|
799
|
+
const $ = cheerio2.load(response.data);
|
|
800
|
+
const h1Tag = $("h1").filter((_, el) => {
|
|
801
|
+
const text = $(el).text();
|
|
802
|
+
return text.includes("Notes Des Modules session Rattrapage");
|
|
803
|
+
});
|
|
804
|
+
if (h1Tag.length === 0) {
|
|
805
|
+
this.log("Page does not contain expected content");
|
|
806
|
+
return null;
|
|
807
|
+
}
|
|
808
|
+
const table = $("#ContentPlaceHolder1_GridView1");
|
|
809
|
+
if (table.length === 0) {
|
|
810
|
+
this.log("Rattrapage grades table not found");
|
|
811
|
+
return null;
|
|
812
|
+
}
|
|
813
|
+
const { headers, rows } = parseTable($, table);
|
|
814
|
+
return [headers, ...rows];
|
|
815
|
+
} catch (error) {
|
|
816
|
+
this.log("Error getting rattrapage grades:", error);
|
|
817
|
+
return null;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Get the final verdict/decision of the rattrapage (retake) session.
|
|
822
|
+
*
|
|
823
|
+
* Fetches data from ResultatRattrapage2021.aspx showing the overall average and pass/fail decision.
|
|
824
|
+
*
|
|
825
|
+
* @returns RattrapageResult with moyenneGeneral and decision, or null if unavailable
|
|
826
|
+
*/
|
|
827
|
+
async getRattrapageResult() {
|
|
828
|
+
try {
|
|
829
|
+
const response = await this.client.get(this.RATTRAPAGE_RESULT_URL, { maxRedirects: 5 });
|
|
830
|
+
if (isLoginPage(response.request?.res?.responseUrl ?? "")) {
|
|
831
|
+
this.log("Session expired - redirected to login");
|
|
832
|
+
return null;
|
|
833
|
+
}
|
|
834
|
+
const $ = cheerio2.load(response.data);
|
|
835
|
+
const h1Tag = $("h1").filter((_, el) => {
|
|
836
|
+
const text = $(el).text();
|
|
837
|
+
return text.includes("Resultat");
|
|
838
|
+
});
|
|
839
|
+
const spanTag = $("#ContentPlaceHolder1_Label18").filter((_, el) => {
|
|
840
|
+
const text = $(el).text();
|
|
841
|
+
return text.includes("Resultat");
|
|
842
|
+
});
|
|
843
|
+
if (h1Tag.length === 0 && spanTag.length === 0) {
|
|
844
|
+
this.log("Page does not contain expected content");
|
|
845
|
+
return null;
|
|
846
|
+
}
|
|
847
|
+
const table = $("#ContentPlaceHolder1_GridView3");
|
|
848
|
+
if (table.length === 0) {
|
|
849
|
+
this.log("Rattrapage result table not found");
|
|
850
|
+
return null;
|
|
851
|
+
}
|
|
852
|
+
const { headers, rows } = parseTable($, table);
|
|
853
|
+
if (rows.length === 0) {
|
|
854
|
+
this.log("No rattrapage result data found");
|
|
855
|
+
return null;
|
|
856
|
+
}
|
|
857
|
+
const headerLower = headers.map((h) => h.toLowerCase());
|
|
858
|
+
const moyenneIdx = headerLower.findIndex((h) => h.includes("moyenne"));
|
|
859
|
+
const decisionIdx = headerLower.findIndex((h) => h.includes("decision"));
|
|
860
|
+
return {
|
|
861
|
+
moyenneGeneral: moyenneIdx >= 0 ? rows[0][moyenneIdx] ?? null : null,
|
|
862
|
+
decision: decisionIdx >= 0 ? rows[0][decisionIdx] ?? null : null
|
|
863
|
+
};
|
|
864
|
+
} catch (error) {
|
|
865
|
+
this.log("Error getting rattrapage result:", error);
|
|
866
|
+
return null;
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* Get language proficiency levels (French and English).
|
|
871
|
+
*
|
|
872
|
+
* Fetches data from LANG2.aspx showing language levels like 'B1', 'B2', etc.
|
|
873
|
+
*
|
|
874
|
+
* @returns LanguageLevel with francais and anglais levels, or null if unavailable
|
|
875
|
+
*/
|
|
876
|
+
async getLanguageLevels() {
|
|
877
|
+
try {
|
|
878
|
+
const response = await this.client.get(this.LANGUAGE_LEVELS_URL, { maxRedirects: 5 });
|
|
879
|
+
if (isLoginPage(response.request?.res?.responseUrl ?? "")) {
|
|
880
|
+
this.log("Session expired - redirected to login");
|
|
881
|
+
return null;
|
|
882
|
+
}
|
|
883
|
+
const $ = cheerio2.load(response.data);
|
|
884
|
+
const h1Tag = $("h1").filter((_, el) => {
|
|
885
|
+
const text = $(el).text();
|
|
886
|
+
return text.includes("NIVEAU LANGUES");
|
|
887
|
+
});
|
|
888
|
+
if (h1Tag.length === 0) {
|
|
889
|
+
this.log("Page does not contain expected content");
|
|
890
|
+
return null;
|
|
891
|
+
}
|
|
892
|
+
const table = $("#ContentPlaceHolder1_GridView2");
|
|
893
|
+
if (table.length === 0) {
|
|
894
|
+
this.log("Language levels table not found");
|
|
895
|
+
return null;
|
|
896
|
+
}
|
|
897
|
+
const { headers, rows } = parseTable($, table);
|
|
898
|
+
if (rows.length === 0) {
|
|
899
|
+
this.log("No language level data found");
|
|
900
|
+
return null;
|
|
901
|
+
}
|
|
902
|
+
const headerLower = headers.map((h) => h.toLowerCase());
|
|
903
|
+
const francaisIdx = headerLower.findIndex((h) => h.includes("francais"));
|
|
904
|
+
const anglaisIdx = headerLower.findIndex((h) => h.includes("anglais"));
|
|
905
|
+
return {
|
|
906
|
+
francais: francaisIdx >= 0 ? rows[0][francaisIdx] ?? null : null,
|
|
907
|
+
anglais: anglaisIdx >= 0 ? rows[0][anglaisIdx] ?? null : null
|
|
908
|
+
};
|
|
909
|
+
} catch (error) {
|
|
910
|
+
this.log("Error getting language levels:", error);
|
|
911
|
+
return null;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
/**
|
|
915
|
+
* Get historical academic ranking data across multiple years.
|
|
916
|
+
*
|
|
917
|
+
* Fetches data from ranking.aspx showing the student's rank, average, and class for each academic year.
|
|
918
|
+
* The table ID can vary, so multiple IDs are tried with a fallback to structure-based detection.
|
|
919
|
+
*
|
|
920
|
+
* @returns Array of RankingEntry objects, or null if unavailable
|
|
921
|
+
*/
|
|
922
|
+
async getRanking() {
|
|
923
|
+
try {
|
|
924
|
+
const response = await this.client.get(this.RANKING_URL, { maxRedirects: 5 });
|
|
925
|
+
if (isLoginPage(response.request?.res?.responseUrl ?? "")) {
|
|
926
|
+
this.log("Session expired - redirected to login");
|
|
927
|
+
return null;
|
|
928
|
+
}
|
|
929
|
+
const $ = cheerio2.load(response.data);
|
|
930
|
+
let table = $();
|
|
931
|
+
const possibleIds = [
|
|
932
|
+
"ContentPlaceHolder1_GridView1_5",
|
|
933
|
+
"ContentPlaceHolder1_GridView1_4",
|
|
934
|
+
"ContentPlaceHolder1_GridView1_3",
|
|
935
|
+
"ContentPlaceHolder1_GridView1_2",
|
|
936
|
+
"ContentPlaceHolder1_GridView1"
|
|
937
|
+
];
|
|
938
|
+
for (const id of possibleIds) {
|
|
939
|
+
table = $(`#${id}`);
|
|
940
|
+
if (table.length > 0) {
|
|
941
|
+
this.log("Found ranking table with ID:", id);
|
|
942
|
+
break;
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
if (table.length === 0) {
|
|
946
|
+
this.log("Trying fallback table detection");
|
|
947
|
+
$("table").each((_, el) => {
|
|
948
|
+
const firstRow = $(el).find("tr").first();
|
|
949
|
+
const headers2 = firstRow.find("th").map((_2, th) => $(th).text().trim().toLowerCase()).get();
|
|
950
|
+
if (headers2.some((h) => h.includes("a.u") || h.includes("rang"))) {
|
|
951
|
+
table = $(el);
|
|
952
|
+
this.log("Found ranking table by structure");
|
|
953
|
+
return false;
|
|
954
|
+
}
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
if (table.length === 0) {
|
|
958
|
+
this.log("Ranking table not found");
|
|
959
|
+
return null;
|
|
960
|
+
}
|
|
961
|
+
const { headers, rows } = parseTable($, table);
|
|
962
|
+
if (rows.length === 0) {
|
|
963
|
+
this.log("No ranking data found");
|
|
964
|
+
return null;
|
|
965
|
+
}
|
|
966
|
+
const headerLower = headers.map((h) => h.toLowerCase());
|
|
967
|
+
const auIdx = headerLower.findIndex((h) => h.includes("a.u") || h.includes("ann\xE9e"));
|
|
968
|
+
const classeIdx = headerLower.findIndex((h) => h.includes("classe"));
|
|
969
|
+
const moyenneIdx = headerLower.findIndex((h) => h.includes("moyenne"));
|
|
970
|
+
const rangIdx = headerLower.findIndex((h) => h.includes("rang"));
|
|
971
|
+
return rows.map((row) => ({
|
|
972
|
+
anneeUniversitaire: auIdx >= 0 ? row[auIdx] ?? null : null,
|
|
973
|
+
classe: classeIdx >= 0 ? row[classeIdx] ?? null : null,
|
|
974
|
+
moyenne: moyenneIdx >= 0 ? row[moyenneIdx] ?? null : null,
|
|
975
|
+
rang: rangIdx >= 0 ? row[rangIdx] ?? null : null
|
|
976
|
+
}));
|
|
977
|
+
} catch (error) {
|
|
978
|
+
this.log("Error getting ranking:", error);
|
|
979
|
+
return null;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
688
982
|
};
|
|
689
983
|
var index_default = EspritClient;
|
|
690
984
|
// Annotate the CommonJS export names for ESM import in node:
|