zdu-student-api 1.1.5 → 1.1.7
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/LICENSE +21 -0
- package/README.md +242 -18
- package/dist/audience.d.ts +44 -44
- package/dist/audience.js +50 -48
- package/dist/cabinet/cabinet.d.ts +79 -0
- package/dist/cabinet/cabinet.js +162 -0
- package/dist/cabinet/data.d.ts +66 -0
- package/dist/cabinet/data.js +115 -0
- package/dist/cabinet/disciplines.d.ts +19 -0
- package/dist/cabinet/disciplines.js +87 -0
- package/dist/cabinet/index.d.ts +3 -2
- package/dist/cabinet/index.js +3 -2
- package/dist/cabinet/parsers.d.ts +37 -0
- package/dist/cabinet/parsers.js +284 -0
- package/dist/cabinet/scores.d.ts +12 -0
- package/dist/cabinet/scores.js +101 -0
- package/dist/cabinet/sesId.d.ts +10 -9
- package/dist/cabinet/sesId.js +40 -39
- package/dist/cabinet/session.d.ts +33 -0
- package/dist/cabinet/session.js +99 -0
- package/dist/cabinet/types.d.ts +2 -2
- package/dist/cabinet/utils.d.ts +8 -0
- package/dist/cabinet/utils.js +19 -0
- package/dist/cabinet/validSession.d.ts +8 -0
- package/dist/cabinet/validSession.js +29 -0
- package/dist/cabinetStudent/cabinetStudent.d.ts +87 -0
- package/dist/cabinetStudent/cabinetStudent.js +181 -0
- package/dist/cabinetStudent/disciplines.d.ts +19 -0
- package/dist/cabinetStudent/disciplines.js +86 -0
- package/dist/cabinetStudent/index.d.ts +4 -0
- package/dist/cabinetStudent/index.js +4 -0
- package/dist/cabinetStudent/scores.d.ts +12 -0
- package/dist/cabinetStudent/scores.js +101 -0
- package/dist/cabinetStudent/types.d.ts +137 -0
- package/dist/cabinetStudent/types.js +1 -0
- package/dist/cabinetTeacher/academicGroups.d.ts +10 -0
- package/dist/cabinetTeacher/academicGroups.js +36 -0
- package/dist/cabinetTeacher/cabinetTeacher.d.ts +71 -0
- package/dist/cabinetTeacher/cabinetTeacher.js +132 -0
- package/dist/cabinetTeacher/index.d.ts +3 -0
- package/dist/cabinetTeacher/index.js +3 -0
- package/dist/cabinetTeacher/types.d.ts +68 -0
- package/dist/cabinetTeacher/types.js +1 -0
- package/dist/constants.d.ts +49 -49
- package/dist/constants.js +55 -52
- package/dist/examples.d.ts +1 -1
- package/dist/examples.js +70 -15
- package/dist/index.d.ts +12 -10
- package/dist/index.js +12 -10
- package/dist/schedule.d.ts +62 -62
- package/dist/schedule.js +74 -68
- package/dist/types.d.ts +17 -16
- package/dist/types.js +0 -1
- package/package.json +35 -33
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Парсить дані з сторінки n=31 (анкетні дані)
|
|
3
|
+
*/
|
|
4
|
+
export function parseDataPageN31(html) {
|
|
5
|
+
const data = {};
|
|
6
|
+
const fullName = extractFullName(html);
|
|
7
|
+
if (fullName) {
|
|
8
|
+
data.fullName = fullName;
|
|
9
|
+
const nameParts = parseFullName(fullName);
|
|
10
|
+
Object.assign(data, nameParts);
|
|
11
|
+
}
|
|
12
|
+
data.birthDate = extractStrongValue(html, 'Дата народження');
|
|
13
|
+
data.gender = extractStrongValue(html, 'Стать');
|
|
14
|
+
data.previousFullName = extractStrongValue(html, 'Попереднє ПІБ студента');
|
|
15
|
+
data.country = extractStrongValue(html, 'Країна, з якої навчається студент');
|
|
16
|
+
data.lastNameEng = extractStrongValue(html, 'Прізвище англ.');
|
|
17
|
+
data.firstNameEng = extractStrongValue(html, 'Ім`я англ.');
|
|
18
|
+
data.middleNameEng = extractStrongValue(html, 'По батькові англ.');
|
|
19
|
+
data.email = extractStrongValue(html, 'E-mail:');
|
|
20
|
+
const phonesStr = extractStrongValue(html, 'Телефон(и)');
|
|
21
|
+
if (phonesStr) {
|
|
22
|
+
data.phones = phonesStr
|
|
23
|
+
.split(';')
|
|
24
|
+
.map((p) => p.trim())
|
|
25
|
+
.filter((p) => p.length > 0);
|
|
26
|
+
}
|
|
27
|
+
return data;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Парсить дані з сторінки n=3 (загальна інформація)
|
|
31
|
+
*/
|
|
32
|
+
export function parseDataPageN3(html) {
|
|
33
|
+
const data = {};
|
|
34
|
+
const extractFromParagraph = (label) => {
|
|
35
|
+
const labelIdx = html.indexOf(label);
|
|
36
|
+
if (labelIdx === -1)
|
|
37
|
+
return undefined;
|
|
38
|
+
const pStart = html.lastIndexOf('<p>', labelIdx);
|
|
39
|
+
if (pStart === -1)
|
|
40
|
+
return undefined;
|
|
41
|
+
const pEnd = html.indexOf('</p>', labelIdx);
|
|
42
|
+
if (pEnd === -1)
|
|
43
|
+
return undefined;
|
|
44
|
+
const pContent = html.substring(pStart, pEnd);
|
|
45
|
+
const strongMatch = pContent.match(/<strong>([^<]+)<\/strong>/);
|
|
46
|
+
if (!strongMatch)
|
|
47
|
+
return undefined;
|
|
48
|
+
const value = strongMatch[1].trim();
|
|
49
|
+
return value.length > 0 ? value : undefined;
|
|
50
|
+
};
|
|
51
|
+
data.faculty = extractFromParagraph('Факультет');
|
|
52
|
+
const specialtyPIdx = html.indexOf('Спеціальність');
|
|
53
|
+
if (specialtyPIdx !== -1) {
|
|
54
|
+
const pStart = html.lastIndexOf('<p>', specialtyPIdx);
|
|
55
|
+
const pEnd = html.indexOf('</p>', specialtyPIdx);
|
|
56
|
+
if (pStart !== -1 && pEnd !== -1) {
|
|
57
|
+
const pContent = html.substring(pStart, pEnd);
|
|
58
|
+
const specialtyMatch = pContent.match(/<strong>"([^"]+)"<\/strong>/) ||
|
|
59
|
+
pContent.match(/<strong>([^<]+)<\/strong>/);
|
|
60
|
+
if (specialtyMatch)
|
|
61
|
+
data.specialty = specialtyMatch[1].trim();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
data.degree = extractFromParagraph('Ступінь / Освітньо-професійний ступінь');
|
|
65
|
+
data.group = extractFromParagraph('Группа');
|
|
66
|
+
data.studyForm = extractFromParagraph('Форма навчання');
|
|
67
|
+
data.paymentForm = extractFromParagraph('Форма оплати навчання');
|
|
68
|
+
data.studyDuration = extractFromParagraph('Термін навчання');
|
|
69
|
+
data.graduationDate = extractFromParagraph('Дата закінчення навчання');
|
|
70
|
+
const orderIdx = html.indexOf('Наказ на зарахування');
|
|
71
|
+
if (orderIdx !== -1) {
|
|
72
|
+
const pStart = html.lastIndexOf('<p>', orderIdx);
|
|
73
|
+
const pEnd = html.indexOf('</p>', orderIdx);
|
|
74
|
+
if (pStart !== -1 && pEnd !== -1) {
|
|
75
|
+
const pContent = html.substring(pStart, pEnd);
|
|
76
|
+
const orderMatch = pContent.match(/Наказ на зарахування\s+<strong>([^<]+)<\/strong>\s+від\s+<strong>([^<]+)<\/strong>/);
|
|
77
|
+
if (orderMatch) {
|
|
78
|
+
data.enrollmentOrder = orderMatch[1].trim();
|
|
79
|
+
data.enrollmentDate = orderMatch[2].trim();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return data;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Витягує значення з HTML після label до кінця li тегу
|
|
87
|
+
*/
|
|
88
|
+
function extractStrongValue(html, label) {
|
|
89
|
+
const labelIdx = html.indexOf(label);
|
|
90
|
+
if (labelIdx === -1)
|
|
91
|
+
return undefined;
|
|
92
|
+
const liStart = html.lastIndexOf('<li>', labelIdx);
|
|
93
|
+
if (liStart === -1)
|
|
94
|
+
return undefined;
|
|
95
|
+
const liEnd = html.indexOf('</li>', labelIdx);
|
|
96
|
+
if (liEnd === -1)
|
|
97
|
+
return undefined;
|
|
98
|
+
const liContent = html.substring(liStart, liEnd);
|
|
99
|
+
const strongMatch = liContent.match(/<strong>(.*?)<\/strong>/);
|
|
100
|
+
if (!strongMatch)
|
|
101
|
+
return undefined;
|
|
102
|
+
const value = strongMatch[1].trim();
|
|
103
|
+
return value.length > 0 ? value : undefined;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Витягує повне ім'я з h3 тегу
|
|
107
|
+
*/
|
|
108
|
+
function extractFullName(html) {
|
|
109
|
+
const h2Idx = html.indexOf('<h2>Анкетні дані студента</h2>');
|
|
110
|
+
if (h2Idx === -1)
|
|
111
|
+
return undefined;
|
|
112
|
+
const h3Match = html.substring(h2Idx).match(/<h3>([^<]+)<\/h3>/);
|
|
113
|
+
if (!h3Match)
|
|
114
|
+
return undefined;
|
|
115
|
+
return h3Match[1].trim();
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Парсить повне ім'я на прізвище, ім'я та по батькові
|
|
119
|
+
*/
|
|
120
|
+
function parseFullName(fullName) {
|
|
121
|
+
const parts = fullName.trim().split(/\s+/);
|
|
122
|
+
if (parts.length === 0)
|
|
123
|
+
return {};
|
|
124
|
+
if (parts.length === 1)
|
|
125
|
+
return { lastName: parts[0] };
|
|
126
|
+
if (parts.length === 2)
|
|
127
|
+
return { lastName: parts[0], firstName: parts[1] };
|
|
128
|
+
return {
|
|
129
|
+
lastName: parts[0],
|
|
130
|
+
firstName: parts[1],
|
|
131
|
+
middleName: parts.slice(2).join(' '),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Парсить сторінку "Семестрові бали" (n=6) і повертає список дисциплін
|
|
136
|
+
*/
|
|
137
|
+
export function parseDisciplinesPageN6(html) {
|
|
138
|
+
const result = [];
|
|
139
|
+
const rowRegex = /<tr>\s*<td>([^<]+)<\/td>\s*<td>\d+<\/td>\s*<td>[\s\S]*?<a[^>]+href="[^"]*prID=(\d+)[^"]*"[\s\S]*?<\/a>[\s\S]*?<\/td>\s*<\/tr>/gi;
|
|
140
|
+
let match;
|
|
141
|
+
while ((match = rowRegex.exec(html)) !== null) {
|
|
142
|
+
const name = match[1].trim();
|
|
143
|
+
const prId = match[2].trim();
|
|
144
|
+
if (name && prId) {
|
|
145
|
+
result.push({ name, prId });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Парсить HTML і повертає масив дисциплін (n=7)
|
|
152
|
+
* @param html - HTML сторінки з select[id="prt"]
|
|
153
|
+
*/
|
|
154
|
+
export function parseDisciplinesPageN7(html) {
|
|
155
|
+
const disciplines = [];
|
|
156
|
+
const optionRegex = /<option\s+value="(\d+)">([\s\S]*?)<\/option>/g;
|
|
157
|
+
let match;
|
|
158
|
+
while ((match = optionRegex.exec(html)) !== null) {
|
|
159
|
+
const value = match[1];
|
|
160
|
+
const name = match[2].trim();
|
|
161
|
+
if (value !== '-1') {
|
|
162
|
+
disciplines.push({ prId: value, name });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return disciplines;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Парсить головну сторінку кабінету викладача
|
|
169
|
+
* @param html - HTML сторінки кабінету викладача
|
|
170
|
+
* @returns Дані викладача
|
|
171
|
+
*/
|
|
172
|
+
export function parseTeacherData(html) {
|
|
173
|
+
const data = { ok: false };
|
|
174
|
+
try {
|
|
175
|
+
const fullName = extractTeacherFullName(html);
|
|
176
|
+
if (fullName) {
|
|
177
|
+
data.fullName = fullName;
|
|
178
|
+
const nameParts = parseFullName(fullName);
|
|
179
|
+
Object.assign(data, nameParts);
|
|
180
|
+
}
|
|
181
|
+
data.department = extractDepartment(html);
|
|
182
|
+
data.partTimeHours = extractHoursValue(html, /за сумісництвом\s*:\s*<strong>[^0-9]*\((\d+)[^)]*\)<\/strong>/);
|
|
183
|
+
data.workDurationMonths = extractNumericValue(html, /Тривалість роботи в навч\. році \(місяців\):\s*<strong>(\d+)<\/strong>/);
|
|
184
|
+
data.totalPositionHours = extractHoursValue(html, /загалом\s*<strong>(\d+)\s*год\.<\/strong>\s*за ставками/);
|
|
185
|
+
data.workloadByStaff = extractHoursValue(html, /за штатом\s*-\s*<strong>(\d+)\s*год\.<\/strong>/);
|
|
186
|
+
const workloadSectionIndex = html.indexOf('Розподілене навчальне навантаження:');
|
|
187
|
+
if (workloadSectionIndex !== -1) {
|
|
188
|
+
const workloadSection = html.substring(workloadSectionIndex);
|
|
189
|
+
data.totalWorkload = extractHoursValue(workloadSection, /загалом\s*<strong>(\d+)\s*год\.<\/strong>/);
|
|
190
|
+
}
|
|
191
|
+
data.ok = true;
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
data.ok = false;
|
|
195
|
+
}
|
|
196
|
+
return data;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Витягує повне ім'я викладача з h2 тегу
|
|
200
|
+
*/
|
|
201
|
+
function extractTeacherFullName(html) {
|
|
202
|
+
const h2Match = html.match(/<h2>Викладач:\s*([^<]+)<\/h2>/);
|
|
203
|
+
if (!h2Match)
|
|
204
|
+
return undefined;
|
|
205
|
+
return h2Match[1].trim();
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Витягує назву кафедри з h3 тегу
|
|
209
|
+
*/
|
|
210
|
+
function extractDepartment(html) {
|
|
211
|
+
const h3Match = html.match(/<h3>Кафедра:\s*([^<]+)<\/h3>/);
|
|
212
|
+
if (!h3Match)
|
|
213
|
+
return undefined;
|
|
214
|
+
return h3Match[1].trim();
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Витягує числове значення годин за регулярним виразом
|
|
218
|
+
*/
|
|
219
|
+
function extractHoursValue(html, regex) {
|
|
220
|
+
const match = html.match(regex);
|
|
221
|
+
if (!match)
|
|
222
|
+
return undefined;
|
|
223
|
+
const value = parseInt(match[1], 10);
|
|
224
|
+
return isNaN(value) ? undefined : value;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Витягує числове значення за регулярним виразом
|
|
228
|
+
*/
|
|
229
|
+
function extractNumericValue(html, regex) {
|
|
230
|
+
const match = html.match(regex);
|
|
231
|
+
if (!match)
|
|
232
|
+
return undefined;
|
|
233
|
+
const value = parseInt(match[1], 10);
|
|
234
|
+
return isNaN(value) ? undefined : value;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Парсить сторінку зі списком академічних груп викладача
|
|
238
|
+
* @param html - HTML сторінки "Академічні групи"
|
|
239
|
+
* @returns Список груп викладача
|
|
240
|
+
*/
|
|
241
|
+
export function parseGroupsPage(html, semester) {
|
|
242
|
+
try {
|
|
243
|
+
const groups = [];
|
|
244
|
+
const rowRegex = /<tr><td><a[^>]+href="\.\/teachers\.cgi\?sesID=[^"]+&n=1&grp=([^"&]+)&teacher=(\d+)"[^>]*>([^<]+)<\/a><\/td><td[^>]*>(\d+)<\/td><td>([^<]+?)<br\s*\/>\s*<em>([^<]+)<\/em><\/td><\/tr>/gi;
|
|
245
|
+
let match;
|
|
246
|
+
while ((match = rowRegex.exec(html)) !== null) {
|
|
247
|
+
const encodedName = match[1].trim();
|
|
248
|
+
const teacherId = match[2].trim();
|
|
249
|
+
const name = match[3].trim();
|
|
250
|
+
const course = parseInt(match[4].trim(), 10);
|
|
251
|
+
const specialty = match[5].trim();
|
|
252
|
+
const faculty = match[6].trim();
|
|
253
|
+
// Формуємо повний URL журналу
|
|
254
|
+
const journalUrl = `./teachers.cgi?sesID={sesID}&n=1&grp=${encodedName}&teacher=${teacherId}`;
|
|
255
|
+
groups.push({
|
|
256
|
+
name,
|
|
257
|
+
semester,
|
|
258
|
+
encodedName,
|
|
259
|
+
course,
|
|
260
|
+
specialty,
|
|
261
|
+
faculty,
|
|
262
|
+
teacherId,
|
|
263
|
+
journalUrl,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return groups;
|
|
267
|
+
}
|
|
268
|
+
catch {
|
|
269
|
+
return [];
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* Декодує назву групи з URL-encoded формату
|
|
274
|
+
* @param encodedName - URL-encoded назва групи
|
|
275
|
+
* @returns Декодована назва групи
|
|
276
|
+
*/
|
|
277
|
+
export function decodeGroupName(encodedName) {
|
|
278
|
+
try {
|
|
279
|
+
return decodeURIComponent(encodedName);
|
|
280
|
+
}
|
|
281
|
+
catch (error) {
|
|
282
|
+
return encodedName;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Scores } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Отримати оцінки пвибраного предмета студента
|
|
4
|
+
* @category Cabinet
|
|
5
|
+
* @param sesId - ID сесії користувача
|
|
6
|
+
* @param sessGUID - GUID сесії з cookie
|
|
7
|
+
* @param prId - ID дисципліни
|
|
8
|
+
* @param semester - Семестр
|
|
9
|
+
* @throws {Error} Якщо виникають проблеми з запитом або дані некоректні.
|
|
10
|
+
* @returns Масив дисциплін {@link Disciplines}
|
|
11
|
+
*/
|
|
12
|
+
export declare function getScores(sesId: string, sessGUID: string, prId: string, semester: 0 | 1): Promise<Scores>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import fetch from 'cross-fetch';
|
|
2
|
+
import iconv from 'iconv-lite';
|
|
3
|
+
import { generateCookieString, isLoginPage } from './utils.js';
|
|
4
|
+
/**
|
|
5
|
+
* Отримати оцінки пвибраного предмета студента
|
|
6
|
+
* @category Cabinet
|
|
7
|
+
* @param sesId - ID сесії користувача
|
|
8
|
+
* @param sessGUID - GUID сесії з cookie
|
|
9
|
+
* @param prId - ID дисципліни
|
|
10
|
+
* @param semester - Семестр
|
|
11
|
+
* @throws {Error} Якщо виникають проблеми з запитом або дані некоректні.
|
|
12
|
+
* @returns Масив дисциплін {@link Disciplines}
|
|
13
|
+
*/
|
|
14
|
+
export async function getScores(sesId, sessGUID, prId, semester) {
|
|
15
|
+
try {
|
|
16
|
+
const result = {
|
|
17
|
+
ok: false,
|
|
18
|
+
prId: prId,
|
|
19
|
+
studentId: '',
|
|
20
|
+
scheduleItem: [],
|
|
21
|
+
studentScores: [],
|
|
22
|
+
};
|
|
23
|
+
const cookieString = generateCookieString(sessGUID);
|
|
24
|
+
const formData = `n=7&sesID=${sesId}&teacher=0&irc=0&tid=0&CYKLE=-1&prt=${prId}&hlf=${semester}&grade=0&m=-1`;
|
|
25
|
+
const encodedFormData = iconv.encode(formData, 'windows-1251');
|
|
26
|
+
const response1 = await fetch(`https://dekanat.zu.edu.ua/cgi-bin/classman.cgi?n=7&sesID=${sesId}`, {
|
|
27
|
+
method: 'POST',
|
|
28
|
+
body: new Uint8Array(encodedFormData),
|
|
29
|
+
redirect: 'manual',
|
|
30
|
+
headers: { Cookie: cookieString },
|
|
31
|
+
});
|
|
32
|
+
const buffer1 = await response1.arrayBuffer();
|
|
33
|
+
let html1 = iconv.decode(Buffer.from(buffer1), 'windows-1251');
|
|
34
|
+
if (isLoginPage(html1))
|
|
35
|
+
return result;
|
|
36
|
+
result.scheduleItem = parseSchedule(html1);
|
|
37
|
+
result.studentScores = parseScores(html1);
|
|
38
|
+
result.studentId = result.studentScores[0].id;
|
|
39
|
+
result.studentScores.sort((a, b) => a.id.localeCompare(b.id));
|
|
40
|
+
result.ok = true;
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
console.error('Error in getQuestionnaireData:', e);
|
|
45
|
+
throw e;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Витягує список пар з HTML
|
|
50
|
+
*/
|
|
51
|
+
function parseSchedule(html) {
|
|
52
|
+
let idx = html.indexOf('<th></th>');
|
|
53
|
+
html = idx === -1 ? html : html.slice(idx + '<th></th>'.length);
|
|
54
|
+
idx = html.indexOf('<th class="dh">');
|
|
55
|
+
html = idx === -1 ? html : html.slice(0, idx);
|
|
56
|
+
const regex = /<th[^>]*data-hth="([^"]+)"[^>]*>[\s\S]*?<a[^>]*data-ind="([^"]+)"[^>]*>[^<]+<\/a>[\s\S]*?<br>([^<]+)<\/th>/g;
|
|
57
|
+
const result = [];
|
|
58
|
+
let match;
|
|
59
|
+
while ((match = regex.exec(html)) !== null) {
|
|
60
|
+
const [_, dataHth, index, time] = match;
|
|
61
|
+
const parts = dataHth.split(',');
|
|
62
|
+
const teacher = parts[0].trim();
|
|
63
|
+
const dateType = parts[1]?.trim().split(' ') || [];
|
|
64
|
+
const date = dateType[0] || '';
|
|
65
|
+
const type = dateType[1] || '';
|
|
66
|
+
result.push({ teacher, date, type, time: time.trim(), index });
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Витягує список оцінок з HTML
|
|
72
|
+
*/
|
|
73
|
+
function parseScores(html) {
|
|
74
|
+
const studentRows = html.match(/<tr\s+[^>]*id="s\d+"[\s\S]*?<\/tr>/g) || [];
|
|
75
|
+
const result = [];
|
|
76
|
+
for (const row of studentRows) {
|
|
77
|
+
const idMatch = row.match(/id="(s\d+)"/);
|
|
78
|
+
const id = idMatch ? idMatch[1] : '';
|
|
79
|
+
const tdMatches = [...row.matchAll(/<td[^>]*data-item="(\d+)"[^>]*>([\s\S]*?)<\/td>/g)];
|
|
80
|
+
const scoresMap = new Map();
|
|
81
|
+
for (const td of tdMatches) {
|
|
82
|
+
const dataItem = Number(td[1]);
|
|
83
|
+
const score = td[2].trim() || '';
|
|
84
|
+
if (!scoresMap.has(dataItem)) {
|
|
85
|
+
scoresMap.set(dataItem, [score]);
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
scoresMap.get(dataItem).push(score);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const scores = Array.from(scoresMap.values());
|
|
92
|
+
scores.shift();
|
|
93
|
+
const finalMatch = row.match(/<td[^>]*class="f f1"[^>]*>([\s\S]*?)<\/td>/);
|
|
94
|
+
const finalScore = finalMatch ? finalMatch[1].trim() : '';
|
|
95
|
+
const absenceMatches = [...row.matchAll(/<td[^>]*class="f f2"[^>]*>([\s\S]*?)<\/td>/g)];
|
|
96
|
+
const absences = absenceMatches[0] ? Number(absenceMatches[0][1].trim() || 0) : 0;
|
|
97
|
+
const uabsences = absenceMatches[1] ? Number(absenceMatches[1][1].trim() || 0) : 0;
|
|
98
|
+
result.push({ id, scores, absences, uabsences, finalScore });
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
package/dist/cabinet/sesId.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { SessionData } from
|
|
2
|
-
/**
|
|
3
|
-
* Отримати sesID та sessGUID користувача
|
|
4
|
-
* @category Cabinet
|
|
5
|
-
* @param
|
|
6
|
-
* @param password -
|
|
7
|
-
* @
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import { SessionData } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Отримати sesID та sessGUID користувача
|
|
4
|
+
* @category Cabinet
|
|
5
|
+
* @param login - Прізвище користувача
|
|
6
|
+
* @param password - Пароль
|
|
7
|
+
* @throws {Error} Якщо виникають проблеми з запитом або дані некоректні.
|
|
8
|
+
* @returns Об'єкт { sesID, sessGUID }
|
|
9
|
+
*/
|
|
10
|
+
export declare function getSesId(login: string, password: string): Promise<SessionData>;
|
package/dist/cabinet/sesId.js
CHANGED
|
@@ -1,39 +1,40 @@
|
|
|
1
|
-
import fetch from 'cross-fetch';
|
|
2
|
-
import iconv from
|
|
3
|
-
/**
|
|
4
|
-
* Отримати sesID та sessGUID користувача
|
|
5
|
-
* @category Cabinet
|
|
6
|
-
* @param
|
|
7
|
-
* @param password -
|
|
8
|
-
* @
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
1
|
+
import fetch from 'cross-fetch';
|
|
2
|
+
import iconv from 'iconv-lite';
|
|
3
|
+
/**
|
|
4
|
+
* Отримати sesID та sessGUID користувача
|
|
5
|
+
* @category Cabinet
|
|
6
|
+
* @param login - Прізвище користувача
|
|
7
|
+
* @param password - Пароль
|
|
8
|
+
* @throws {Error} Якщо виникають проблеми з запитом або дані некоректні.
|
|
9
|
+
* @returns Об'єкт { sesID, sessGUID }
|
|
10
|
+
*/
|
|
11
|
+
export async function getSesId(login, password) {
|
|
12
|
+
try {
|
|
13
|
+
const formData = `user_name=${login}&user_pwd=${password}&n=1&rout=&t=16161`;
|
|
14
|
+
const encodedFormData = iconv.encode(formData, 'windows-1251');
|
|
15
|
+
const response = await fetch('https://dekanat.zu.edu.ua/cgi-bin/classman.cgi?n=1&ts=16161', {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
body: new Uint8Array(encodedFormData),
|
|
18
|
+
redirect: 'manual',
|
|
19
|
+
});
|
|
20
|
+
if (response.status === 302) {
|
|
21
|
+
const buffer = await response.arrayBuffer();
|
|
22
|
+
const responseText = iconv.decode(Buffer.from(buffer), 'utf-8');
|
|
23
|
+
const cookies = response.headers.get('set-cookie');
|
|
24
|
+
let sessGUID = '';
|
|
25
|
+
if (cookies) {
|
|
26
|
+
const sessGUIDStart = cookies.indexOf('SessGUID=') + 'SessGUID='.length;
|
|
27
|
+
const sessGUIDEnd = cookies.indexOf(';', sessGUIDStart);
|
|
28
|
+
sessGUID = cookies.substring(sessGUIDStart, sessGUIDEnd);
|
|
29
|
+
}
|
|
30
|
+
const sesIDIndex = responseText.indexOf('sesID=') + 6;
|
|
31
|
+
const sesID = responseText.substring(sesIDIndex, sesIDIndex + 36);
|
|
32
|
+
return { ok: true, sesID, sessGUID };
|
|
33
|
+
}
|
|
34
|
+
return { ok: false, sesID: '', sessGUID: '' };
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
console.error('Error in getSesID:', e);
|
|
38
|
+
throw e;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { SessionData } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Отримати sesID та sessGUID користувача
|
|
4
|
+
* @category Cabinet
|
|
5
|
+
* @param login - Прізвище користувача
|
|
6
|
+
* @param password - Пароль
|
|
7
|
+
* @param type - тип кабінету 'student' | 'teacher'
|
|
8
|
+
* @throws {Error} Якщо виникають проблеми з запитом або дані некоректні.
|
|
9
|
+
* @returns Об'єкт { sesID, sessGUID }
|
|
10
|
+
*/
|
|
11
|
+
export declare function getSesId(login: string, password: string, type: 'student' | 'teacher'): Promise<SessionData>;
|
|
12
|
+
/**
|
|
13
|
+
* Перевірка на валідність сесії
|
|
14
|
+
* @category Cabinet
|
|
15
|
+
* @param sesId - ID сесії користувача
|
|
16
|
+
* @param sessGUID - GUID сесії з cookie
|
|
17
|
+
* @param type - тип кабінету 'student' | 'teacher'
|
|
18
|
+
* @returns boolean значення.
|
|
19
|
+
*/
|
|
20
|
+
export declare function isValidSession(sesId: string, sessGUID: string, type: 'student' | 'teacher'): Promise<boolean>;
|
|
21
|
+
/**
|
|
22
|
+
* Генерує cookie строку з DateTime та SessGUID
|
|
23
|
+
* @category Cabinet
|
|
24
|
+
* @param sessGUID - GUID сесії з cookie
|
|
25
|
+
* @returns cookie рядок
|
|
26
|
+
*/
|
|
27
|
+
export declare function generateCookieString(sessGUID: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Перевірка чи є отримана сторінка сторінкою авторизації
|
|
30
|
+
* @category Cabinet
|
|
31
|
+
* @returns true | false
|
|
32
|
+
*/
|
|
33
|
+
export declare function isLoginPage(html: string): boolean;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import fetch from 'cross-fetch';
|
|
2
|
+
import iconv from 'iconv-lite';
|
|
3
|
+
/**
|
|
4
|
+
* Отримати sesID та sessGUID користувача
|
|
5
|
+
* @category Cabinet
|
|
6
|
+
* @param login - Прізвище користувача
|
|
7
|
+
* @param password - Пароль
|
|
8
|
+
* @param type - тип кабінету 'student' | 'teacher'
|
|
9
|
+
* @throws {Error} Якщо виникають проблеми з запитом або дані некоректні.
|
|
10
|
+
* @returns Об'єкт { sesID, sessGUID }
|
|
11
|
+
*/
|
|
12
|
+
export async function getSesId(login, password, type) {
|
|
13
|
+
try {
|
|
14
|
+
const urlStudent = 'https://dekanat.zu.edu.ua/cgi-bin/classman.cgi?n=1&ts=16161';
|
|
15
|
+
const urlTeacher = 'https://dekanat.zu.edu.ua/cgi-bin/kaf.cgi?n=1&ts=28888';
|
|
16
|
+
const formData = `user_name=${login}&user_pwd=${password}${type == 'student' ? '&n=1&rout=&t=16161' : '&n=1&rout=&t=1'}`;
|
|
17
|
+
const encodedFormData = iconv.encode(formData, 'windows-1251');
|
|
18
|
+
const response = await fetch(type == 'student' ? urlStudent : urlTeacher, {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
body: new Uint8Array(encodedFormData),
|
|
21
|
+
redirect: 'manual',
|
|
22
|
+
});
|
|
23
|
+
if (response.status === 302) {
|
|
24
|
+
const buffer = await response.arrayBuffer();
|
|
25
|
+
const responseText = iconv.decode(Buffer.from(buffer), 'utf-8');
|
|
26
|
+
const cookies = response.headers.get('set-cookie');
|
|
27
|
+
let sessGUID = '';
|
|
28
|
+
if (cookies) {
|
|
29
|
+
const sessGUIDStart = cookies.indexOf('SessGUID=') + 'SessGUID='.length;
|
|
30
|
+
const sessGUIDEnd = cookies.indexOf(';', sessGUIDStart);
|
|
31
|
+
sessGUID = cookies.substring(sessGUIDStart, sessGUIDEnd);
|
|
32
|
+
}
|
|
33
|
+
const sesIDIndex = responseText.indexOf('sesID=') + 6;
|
|
34
|
+
const sesID = responseText.substring(sesIDIndex, sesIDIndex + 36);
|
|
35
|
+
return { ok: true, sesID, sessGUID };
|
|
36
|
+
}
|
|
37
|
+
return { ok: false, sesID: '', sessGUID: '' };
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
console.error('Error in getSesID:', e);
|
|
41
|
+
throw e;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Перевірка на валідність сесії
|
|
46
|
+
* @category Cabinet
|
|
47
|
+
* @param sesId - ID сесії користувача
|
|
48
|
+
* @param sessGUID - GUID сесії з cookie
|
|
49
|
+
* @param type - тип кабінету 'student' | 'teacher'
|
|
50
|
+
* @returns boolean значення.
|
|
51
|
+
*/
|
|
52
|
+
export async function isValidSession(sesId, sessGUID, type) {
|
|
53
|
+
const urlStudent = 'https://dekanat.zu.edu.ua/cgi-bin/classman.cgi?n=3&sesID=';
|
|
54
|
+
const urlTeacher = 'https://dekanat.zu.edu.ua/cgi-bin/kaf.cgi?n=2&sesID=';
|
|
55
|
+
try {
|
|
56
|
+
const cookieString = generateCookieString(sessGUID);
|
|
57
|
+
const response = await fetch(`${type == 'student' ? urlStudent : urlTeacher}${sesId}`, {
|
|
58
|
+
headers: {
|
|
59
|
+
Cookie: cookieString,
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
const buffer = await response.arrayBuffer();
|
|
63
|
+
const html = iconv.decode(Buffer.from(buffer), 'windows-1251');
|
|
64
|
+
if (!isLoginPage(html)) {
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Генерує cookie строку з DateTime та SessGUID
|
|
75
|
+
* @category Cabinet
|
|
76
|
+
* @param sessGUID - GUID сесії з cookie
|
|
77
|
+
* @returns cookie рядок
|
|
78
|
+
*/
|
|
79
|
+
export function generateCookieString(sessGUID) {
|
|
80
|
+
const now = new Date();
|
|
81
|
+
const day = String(now.getDate()).padStart(2, '0');
|
|
82
|
+
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
83
|
+
const year = now.getFullYear();
|
|
84
|
+
const hours = String(now.getHours()).padStart(2, '0');
|
|
85
|
+
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
86
|
+
const seconds = String(now.getSeconds()).padStart(2, '0');
|
|
87
|
+
const dateTime = `${day}.${month}.${year}+${hours}%3A${minutes}%3A${seconds}`;
|
|
88
|
+
return `DateTime=${dateTime}; SessGUID=${sessGUID}`;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Перевірка чи є отримана сторінка сторінкою авторизації
|
|
92
|
+
* @category Cabinet
|
|
93
|
+
* @returns true | false
|
|
94
|
+
*/
|
|
95
|
+
export function isLoginPage(html) {
|
|
96
|
+
return (html.includes('Авторизація користувача') ||
|
|
97
|
+
html.includes('user_name') ||
|
|
98
|
+
html.includes('Недійсний ідентифікатор сесії користувача'));
|
|
99
|
+
}
|
package/dist/cabinet/types.d.ts
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* @category Cabinet
|
|
4
4
|
* @remarks
|
|
5
5
|
* - `ok` — Успіх отримання токену
|
|
6
|
-
* - `sesID` —
|
|
7
|
-
* - `sessGUID` —
|
|
6
|
+
* - `sesID` — ID сесії користувача
|
|
7
|
+
* - `sessGUID` — GUID сесії з cookie
|
|
8
8
|
*/
|
|
9
9
|
export interface SessionData {
|
|
10
10
|
ok: boolean;
|