zdu-student-api 1.1.9 → 1.1.10
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.
|
@@ -51,9 +51,10 @@ export declare class CabinetStudent {
|
|
|
51
51
|
*/
|
|
52
52
|
auth(login?: string, password?: string): Promise<boolean>;
|
|
53
53
|
/**
|
|
54
|
-
*
|
|
54
|
+
* Встановити семестр
|
|
55
|
+
* @param password - Семестр в форматі рядка або числа, якщо не передавати згенерується ймовірний семестр
|
|
55
56
|
*/
|
|
56
|
-
|
|
57
|
+
setSemester(semester?: 1 | 2 | '1' | '2'): void;
|
|
57
58
|
/**
|
|
58
59
|
* Отримання всіх данних
|
|
59
60
|
*/
|
|
@@ -67,10 +67,18 @@ export class CabinetStudent {
|
|
|
67
67
|
return false;
|
|
68
68
|
}
|
|
69
69
|
/**
|
|
70
|
-
*
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
* Встановити семестр
|
|
71
|
+
* @param password - Семестр в форматі рядка або числа, якщо не передавати згенерується ймовірний семестр
|
|
72
|
+
*/
|
|
73
|
+
setSemester(semester) {
|
|
74
|
+
if (!semester)
|
|
75
|
+
this.semester = getSemester();
|
|
76
|
+
else {
|
|
77
|
+
if (semester === '1' || semester === 1)
|
|
78
|
+
this.semester = 1;
|
|
79
|
+
if (semester === '2' || semester === 2)
|
|
80
|
+
this.semester = 2;
|
|
81
|
+
}
|
|
74
82
|
}
|
|
75
83
|
/**
|
|
76
84
|
* Отримання всіх данних
|
|
@@ -34,7 +34,7 @@ export async function getScores(sesId, sessGUID, prId, semester) {
|
|
|
34
34
|
if (isLoginPage(html1))
|
|
35
35
|
return result;
|
|
36
36
|
result.scheduleItem = parseSchedule(html1);
|
|
37
|
-
result.studentScores = parseScores(html1);
|
|
37
|
+
result.studentScores = parseScores(html1, result.scheduleItem.length);
|
|
38
38
|
result.studentId = result.studentScores[0]?.id;
|
|
39
39
|
if (result.studentScores.length > 1) {
|
|
40
40
|
result.studentScores.sort((a, b) => a.id.localeCompare(b.id));
|
|
@@ -51,38 +51,140 @@ export async function getScores(sesId, sessGUID, prId, semester) {
|
|
|
51
51
|
* Витягує список пар з HTML
|
|
52
52
|
*/
|
|
53
53
|
function parseSchedule(html) {
|
|
54
|
-
let idx = html.indexOf('<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
let idx = html.indexOf('<thead>');
|
|
55
|
+
if (idx === -1)
|
|
56
|
+
return [];
|
|
57
|
+
let theadEnd = html.indexOf('</thead>', idx);
|
|
58
|
+
if (theadEnd === -1)
|
|
59
|
+
return [];
|
|
60
|
+
let theadContent = html.slice(idx, theadEnd);
|
|
61
|
+
let firstTrStart = theadContent.indexOf('<tr>');
|
|
62
|
+
let firstTrEnd = theadContent.indexOf('</tr>', firstTrStart);
|
|
63
|
+
if (firstTrStart === -1 || firstTrEnd === -1)
|
|
64
|
+
return [];
|
|
65
|
+
let firstRow = theadContent.slice(firstTrStart, firstTrEnd);
|
|
66
|
+
let secondTrStart = theadContent.indexOf('<tr>', firstTrEnd);
|
|
67
|
+
let secondTrEnd = theadContent.indexOf('</tr>', secondTrStart);
|
|
68
|
+
let thirdTrStart = theadContent.indexOf('<tr>', secondTrEnd);
|
|
69
|
+
let thirdTrEnd = theadContent.indexOf('</tr>', thirdTrStart);
|
|
70
|
+
if (thirdTrStart === -1 || thirdTrEnd === -1)
|
|
71
|
+
return [];
|
|
72
|
+
let thirdRow = theadContent.slice(thirdTrStart, thirdTrEnd);
|
|
59
73
|
const result = [];
|
|
74
|
+
const thStartRegex = /<th\b/g;
|
|
75
|
+
const positions = [];
|
|
60
76
|
let match;
|
|
61
|
-
while ((match =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
77
|
+
while ((match = thStartRegex.exec(firstRow)) !== null) {
|
|
78
|
+
positions.push(match.index);
|
|
79
|
+
}
|
|
80
|
+
positions.push(firstRow.length);
|
|
81
|
+
const thirdPositions = [];
|
|
82
|
+
const thirdThStartRegex = /<th\b/g;
|
|
83
|
+
while ((match = thirdThStartRegex.exec(thirdRow)) !== null) {
|
|
84
|
+
thirdPositions.push(match.index);
|
|
85
|
+
}
|
|
86
|
+
thirdPositions.push(thirdRow.length);
|
|
87
|
+
for (let i = 0; i < positions.length - 1; i++) {
|
|
88
|
+
const start = positions[i];
|
|
89
|
+
const end = positions[i + 1];
|
|
90
|
+
const thFull = firstRow.slice(start, end);
|
|
91
|
+
const closeIdx = thFull.indexOf('</th>');
|
|
92
|
+
if (closeIdx === -1)
|
|
93
|
+
continue;
|
|
94
|
+
const thTag = thFull.slice(0, closeIdx + 5);
|
|
95
|
+
const thContent = thTag.replace(/<th[^>]*>/, '').replace(/<\/th>/, '');
|
|
96
|
+
if (!thContent.trim())
|
|
97
|
+
continue;
|
|
98
|
+
let moduleMatch = thContent.match(/^(М\d+)<br>/);
|
|
99
|
+
if (moduleMatch) {
|
|
100
|
+
const moduleNum = moduleMatch[1];
|
|
101
|
+
const timeMatch = thContent.match(/<br[^>]*>\s*(\d{2}:\d{2}-\d{2}:\d{2})/);
|
|
102
|
+
const time = timeMatch ? timeMatch[1].trim() : '';
|
|
103
|
+
result.push({
|
|
104
|
+
teacher: undefined,
|
|
105
|
+
date: undefined,
|
|
106
|
+
type: 'МД',
|
|
107
|
+
time: time,
|
|
108
|
+
index: `module_${moduleNum}`,
|
|
109
|
+
description: `Модуль ${moduleNum}`,
|
|
110
|
+
});
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
if (thContent.match(/Підсум\.|Всього|Невиправд\./)) {
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const dataHthMatch = thTag.match(/data-hth="([^"]+)"/);
|
|
117
|
+
if (!dataHthMatch)
|
|
118
|
+
continue;
|
|
119
|
+
const dataHth = dataHthMatch[1];
|
|
120
|
+
const aMatch = thContent.match(/data-ind="([^"]+)"/);
|
|
121
|
+
if (!aMatch)
|
|
122
|
+
continue;
|
|
123
|
+
const index = aMatch[1];
|
|
124
|
+
const dateMatch = thContent.match(/>(\d{2}\.\d{2}\.\d{4})</);
|
|
125
|
+
const date = dateMatch ? dateMatch[1] : '';
|
|
126
|
+
const timeMatch = thContent.match(/<br[^>]*>\s*(\d{2}:\d{2}-\d{2}:\d{2})/);
|
|
127
|
+
const time = timeMatch ? timeMatch[1].trim() : '';
|
|
128
|
+
const parts = dataHth.split(',').map((p) => p.trim());
|
|
129
|
+
const teacher = parts[0] || '';
|
|
130
|
+
let type = '';
|
|
131
|
+
if (parts[1]) {
|
|
132
|
+
const dateTypeParts = parts[1].split(/\s+/);
|
|
133
|
+
type = dateTypeParts[1] || '';
|
|
134
|
+
}
|
|
135
|
+
result.push({ teacher, date, type, time, index });
|
|
136
|
+
}
|
|
137
|
+
const descriptions = parseDescriptions(html);
|
|
138
|
+
for (const item of result) {
|
|
139
|
+
if (item.index && !item.index.startsWith('module_') && descriptions.has(item.index)) {
|
|
140
|
+
item.description = descriptions.get(item.index);
|
|
141
|
+
}
|
|
69
142
|
}
|
|
70
143
|
return result;
|
|
71
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Витягує описи занять з HTML
|
|
147
|
+
*/
|
|
148
|
+
function parseDescriptions(html) {
|
|
149
|
+
const descriptions = new Map();
|
|
150
|
+
const divRegex = /<div\s+id="r(\d+)"\s+class="hidden">\s*([\s\S]*?)\s*<br>/g;
|
|
151
|
+
let match;
|
|
152
|
+
while ((match = divRegex.exec(html)) !== null) {
|
|
153
|
+
const index = match[1];
|
|
154
|
+
const content = match[2].trim();
|
|
155
|
+
if (content && !content.startsWith('<')) {
|
|
156
|
+
descriptions.set(index, content);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return descriptions;
|
|
160
|
+
}
|
|
72
161
|
/**
|
|
73
162
|
* Витягує список оцінок з HTML
|
|
74
163
|
*/
|
|
75
|
-
function parseScores(html) {
|
|
164
|
+
function parseScores(html, expectedScoresCount) {
|
|
76
165
|
const studentRows = html.match(/<tr\s+[^>]*id="s\d+"[\s\S]*?<\/tr>/g) || [];
|
|
77
166
|
const result = [];
|
|
78
167
|
for (const row of studentRows) {
|
|
79
168
|
const idMatch = row.match(/id="(s\d+)"/);
|
|
80
169
|
const id = idMatch ? idMatch[1] : '';
|
|
81
|
-
const
|
|
170
|
+
const allTds = [...row.matchAll(/<td\b[^>]*>([\s\S]*?)<\/td>/g)];
|
|
82
171
|
const scoresMap = new Map();
|
|
83
|
-
for (const
|
|
84
|
-
const
|
|
85
|
-
const
|
|
172
|
+
for (const tdMatch of allTds) {
|
|
173
|
+
const fullTd = tdMatch[0];
|
|
174
|
+
const tdContent = tdMatch[1].trim();
|
|
175
|
+
const dataItemMatch = fullTd.match(/data-item="(\d+)"/);
|
|
176
|
+
if (!dataItemMatch)
|
|
177
|
+
continue;
|
|
178
|
+
const dataItem = Number(dataItemMatch[1]);
|
|
179
|
+
if (fullTd.match(/class="[^"]*\bf\s+f1\b[^"]*"/) ||
|
|
180
|
+
fullTd.match(/class="[^"]*\bf1\b[^"]*"/)) {
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
if (fullTd.match(/class="[^"]*\bf\s+f2\b[^"]*"/) ||
|
|
184
|
+
fullTd.match(/class="[^"]*\bf2\b[^"]*"/)) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const score = tdContent || '';
|
|
86
188
|
if (!scoresMap.has(dataItem)) {
|
|
87
189
|
scoresMap.set(dataItem, [score]);
|
|
88
190
|
}
|
|
@@ -90,11 +192,13 @@ function parseScores(html) {
|
|
|
90
192
|
scoresMap.get(dataItem).push(score);
|
|
91
193
|
}
|
|
92
194
|
}
|
|
93
|
-
const
|
|
94
|
-
scores.
|
|
95
|
-
const finalMatch = row.match(/<td[^>]*class="
|
|
195
|
+
const sortedItems = Array.from(scoresMap.entries()).sort((a, b) => a[0] - b[0]);
|
|
196
|
+
const scores = sortedItems.map(([_, values]) => values);
|
|
197
|
+
const finalMatch = row.match(/<td[^>]*class="[^"]*\bf\s+f1\b[^"]*"[^>]*>([\s\S]*?)<\/td>/);
|
|
96
198
|
const finalScore = finalMatch ? finalMatch[1].trim() : '';
|
|
97
|
-
const absenceMatches = [
|
|
199
|
+
const absenceMatches = [
|
|
200
|
+
...row.matchAll(/<td[^>]*class="[^"]*\bf\s+f2\b[^"]*"[^>]*>([\s\S]*?)<\/td>/g),
|
|
201
|
+
];
|
|
98
202
|
const absences = absenceMatches[0] ? Number(absenceMatches[0][1].trim() || 0) : 0;
|
|
99
203
|
const uabsences = absenceMatches[1] ? Number(absenceMatches[1][1].trim() || 0) : 0;
|
|
100
204
|
result.push({ id, scores, absences, uabsences, finalScore });
|
|
@@ -90,18 +90,20 @@ export interface Disciplines {
|
|
|
90
90
|
* Опис заняття у кабінеті студента
|
|
91
91
|
* @category CabinetStudent
|
|
92
92
|
* @remarks
|
|
93
|
-
* - `teacher` — Прізвище та ініціали викладача
|
|
94
|
-
* - `date` — Дата заняття у форматі "dd.mm.yyyy"
|
|
95
|
-
* - `type` — Тип заняття: "Лек", "ПрСем", "Лаб", "МК", "Екз"
|
|
93
|
+
* - `teacher` — Прізвище та ініціали викладача (undefined для модульних днів)
|
|
94
|
+
* - `date` — Дата заняття у форматі "dd.mm.yyyy" (undefined для модульних днів)
|
|
95
|
+
* - `type` — Тип заняття: "Лек", "ПрСем", "Лаб", "МК", "Екз", "МД"
|
|
96
96
|
* - `time` — Час заняття у форматі "HH:MM-HH:MM"
|
|
97
97
|
* - `index` — Значення атрибута `data-ind`, унікальний ідентифікатор
|
|
98
|
+
* - `description` — Опис заняття (може бути undefined) ("Модуль ##" для модульних днів, де ## - номер модуля, наприклад М0, М1)
|
|
98
99
|
*/
|
|
99
100
|
export interface ScheduleItem {
|
|
100
|
-
teacher
|
|
101
|
-
date
|
|
101
|
+
teacher?: string;
|
|
102
|
+
date?: string;
|
|
102
103
|
type: string;
|
|
103
104
|
time: string;
|
|
104
105
|
index: string;
|
|
106
|
+
description?: string;
|
|
105
107
|
}
|
|
106
108
|
/**
|
|
107
109
|
* Список оцінок студента.
|
package/dist/examples.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { CabinetStudent, } from './index.js';
|
|
2
1
|
import 'dotenv/config';
|
|
3
2
|
// const schedule = new Schedule();
|
|
4
3
|
// schedule.group = '23Бд-СОінф123'
|
|
@@ -53,13 +52,14 @@ import 'dotenv/config';
|
|
|
53
52
|
// const me = data2.studentScores.find((s) => s.id === data2.studentId)!;
|
|
54
53
|
// const sesID = '';
|
|
55
54
|
// const sessGUID = '';
|
|
56
|
-
const cb = new CabinetStudent(process.env.LOGIN
|
|
57
|
-
await cb.auth();
|
|
58
|
-
// // console.log(cb.sesID, cb.sessGUID);
|
|
59
|
-
// console.log(await cb.setSession(sesID, sessGUID));
|
|
60
|
-
// // console.log(cb.sesID, cb.sessGUID);
|
|
61
|
-
//
|
|
62
|
-
|
|
55
|
+
// const cb = new CabinetStudent(process.env.LOGIN!, process.env.PASSWORD!);
|
|
56
|
+
// await cb.auth();
|
|
57
|
+
// // // console.log(cb.sesID, cb.sessGUID);
|
|
58
|
+
// // console.log(await cb.setSession(sesID, sessGUID));
|
|
59
|
+
// // // console.log(cb.sesID, cb.sessGUID);
|
|
60
|
+
// await cb.loadData();
|
|
61
|
+
// const scores = cb.allScores;
|
|
62
|
+
// await writeFile('scores.json', JSON.stringify(scores, null, 2), 'utf-8');
|
|
63
63
|
// console.log(cb.data);
|
|
64
64
|
// await cb.getDisciplines();
|
|
65
65
|
// console.log(await cb.getId());
|