zdu-student-api 1.1.5 → 1.1.6
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 +23 -12
- 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 +37 -0
- package/dist/cabinet/data.js +69 -0
- package/dist/cabinet/disciplines.d.ts +19 -0
- package/dist/cabinet/disciplines.js +87 -0
- package/dist/cabinet/index.d.ts +6 -2
- package/dist/cabinet/index.js +6 -2
- package/dist/cabinet/parsers.d.ts +18 -0
- package/dist/cabinet/parsers.js +166 -0
- package/dist/cabinet/scores.d.ts +12 -0
- package/dist/cabinet/scores.js +101 -0
- package/dist/cabinet/sesId.d.ts +5 -4
- package/dist/cabinet/sesId.js +17 -16
- package/dist/cabinet/types.d.ts +139 -2
- package/dist/cabinet/utils.d.ts +8 -0
- package/dist/cabinet/utils.js +22 -0
- package/dist/cabinet/validSession.d.ts +8 -0
- package/dist/cabinet/validSession.js +29 -0
- package/dist/constants.d.ts +49 -49
- package/dist/constants.js +55 -52
- package/dist/examples.js +55 -12
- package/dist/index.d.ts +10 -10
- package/dist/index.js +10 -10
- package/dist/schedule.d.ts +62 -62
- package/dist/schedule.js +74 -68
- package/dist/types.d.ts +13 -13
- package/dist/types.js +0 -1
- package/package.json +2 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import fetch from 'cross-fetch';
|
|
2
|
+
import iconv from 'iconv-lite';
|
|
3
|
+
import { parseDisciplinesPageN6, parseDisciplinesPageN7 } from './parsers.js';
|
|
4
|
+
import { generateCookieString, isLoginPage } from './utils.js';
|
|
5
|
+
/**
|
|
6
|
+
* Отримати всі дисципліни студента
|
|
7
|
+
* @category Cabinet
|
|
8
|
+
* @param sesId - ID сесії користувача
|
|
9
|
+
* @param sessGUID - GUID сесії з cookie
|
|
10
|
+
* @throws {Error} Якщо виникають проблеми з запитом або дані некоректні.
|
|
11
|
+
* @returns Масив дисциплін {@link Disciplines}
|
|
12
|
+
*/
|
|
13
|
+
export async function getDisciplines(sesId, sessGUID) {
|
|
14
|
+
try {
|
|
15
|
+
const result = { ok: false, disciplines: [] };
|
|
16
|
+
const cookieString = generateCookieString(sessGUID);
|
|
17
|
+
const response1 = await fetch(`https://dekanat.zu.edu.ua/cgi-bin/classman.cgi?n=3&sesID=${sesId}`, { headers: { Cookie: cookieString } });
|
|
18
|
+
const buffer1 = await response1.arrayBuffer();
|
|
19
|
+
const html1 = iconv.decode(Buffer.from(buffer1), 'windows-1251');
|
|
20
|
+
if (isLoginPage(html1))
|
|
21
|
+
return result;
|
|
22
|
+
let data = extractStrongValue(html1, 'Семестрові бали');
|
|
23
|
+
if (data === undefined)
|
|
24
|
+
return result;
|
|
25
|
+
const response2 = await fetch(`https://dekanat.zu.edu.ua/cgi-bin/${data.slice(2)}`, {
|
|
26
|
+
headers: { Cookie: cookieString },
|
|
27
|
+
});
|
|
28
|
+
const buffer2 = await response2.arrayBuffer();
|
|
29
|
+
const html2 = iconv.decode(Buffer.from(buffer2), 'windows-1251');
|
|
30
|
+
if (isLoginPage(html2))
|
|
31
|
+
return result;
|
|
32
|
+
result.disciplines = parseDisciplinesPageN6(html2);
|
|
33
|
+
result.ok = true;
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
console.error('Error in getDisciplines:', e);
|
|
38
|
+
throw e;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Витягує значення з HTML після label до кінця li тегу
|
|
43
|
+
*/
|
|
44
|
+
function extractStrongValue(html, label) {
|
|
45
|
+
const textIdx = html.indexOf(label);
|
|
46
|
+
if (textIdx === -1)
|
|
47
|
+
return undefined;
|
|
48
|
+
const liStart = html.lastIndexOf('<li', textIdx);
|
|
49
|
+
if (liStart === -1)
|
|
50
|
+
return undefined;
|
|
51
|
+
const liEnd = html.indexOf('</li>', textIdx);
|
|
52
|
+
if (liEnd === -1)
|
|
53
|
+
return undefined;
|
|
54
|
+
const liContent = html.substring(liStart, liEnd);
|
|
55
|
+
const hrefMatch = liContent.match(/href\s*=\s*["']([^"']+)["']/i);
|
|
56
|
+
if (!hrefMatch)
|
|
57
|
+
return undefined;
|
|
58
|
+
const href = hrefMatch[1].trim();
|
|
59
|
+
return href || undefined;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Отримати поточні дисципліни студента
|
|
63
|
+
* @category Cabinet
|
|
64
|
+
* @param sesId - ID сесії користувача
|
|
65
|
+
* @param sessGUID - GUID сесії з cookie
|
|
66
|
+
* @throws {Error} Якщо виникають проблеми з запитом або дані некоректні.
|
|
67
|
+
* @returns Масив дисциплін {@link Disciplines}
|
|
68
|
+
*/
|
|
69
|
+
export async function getСurrentDisciplines(sesId, sessGUID) {
|
|
70
|
+
try {
|
|
71
|
+
const result = { ok: false, disciplines: [] };
|
|
72
|
+
const cookieString = generateCookieString(sessGUID);
|
|
73
|
+
const response1 = await fetch(`https://dekanat.zu.edu.ua/cgi-bin/classman.cgi?n=7&sesID=${sesId}`, { headers: { Cookie: cookieString } });
|
|
74
|
+
const buffer1 = await response1.arrayBuffer();
|
|
75
|
+
const html1 = iconv.decode(Buffer.from(buffer1), 'windows-1251');
|
|
76
|
+
if (isLoginPage(html1))
|
|
77
|
+
return result;
|
|
78
|
+
result.disciplines = parseDisciplinesPageN7(html1);
|
|
79
|
+
//console.log(html1);
|
|
80
|
+
result.ok = true;
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
console.error('Error in getСurrentDisciplines:', e);
|
|
85
|
+
throw e;
|
|
86
|
+
}
|
|
87
|
+
}
|
package/dist/cabinet/index.d.ts
CHANGED
package/dist/cabinet/index.js
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Discipline, Data } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Парсить дані з сторінки n=31 (анкетні дані)
|
|
4
|
+
*/
|
|
5
|
+
export declare function parseDataPageN31(html: string): Partial<Data>;
|
|
6
|
+
/**
|
|
7
|
+
* Парсить дані з сторінки n=3 (загальна інформація)
|
|
8
|
+
*/
|
|
9
|
+
export declare function parseDataPageN3(html: string): Partial<Data>;
|
|
10
|
+
/**
|
|
11
|
+
* Парсить сторінку "Семестрові бали" (n=6) і повертає список дисциплін
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseDisciplinesPageN6(html: string): Discipline[];
|
|
14
|
+
/**
|
|
15
|
+
* Парсить HTML і повертає масив дисциплін (n=7)
|
|
16
|
+
* @param html - HTML сторінки з select[id="prt"]
|
|
17
|
+
*/
|
|
18
|
+
export declare function parseDisciplinesPageN7(html: string): Discipline[];
|
|
@@ -0,0 +1,166 @@
|
|
|
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
|
+
}
|
|
@@ -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
|
|
1
|
+
import { SessionData } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Отримати sesID та sessGUID користувача
|
|
4
4
|
* @category Cabinet
|
|
5
|
-
* @param
|
|
6
|
-
* @param password -
|
|
5
|
+
* @param login - Прізвище користувача
|
|
6
|
+
* @param password - Пароль
|
|
7
|
+
* @throws {Error} Якщо виникають проблеми з запитом або дані некоректні.
|
|
7
8
|
* @returns Об'єкт { sesID, sessGUID }
|
|
8
9
|
*/
|
|
9
|
-
export declare function getSesId(
|
|
10
|
+
export declare function getSesId(login: string, password: string): Promise<SessionData>;
|
package/dist/cabinet/sesId.js
CHANGED
|
@@ -1,39 +1,40 @@
|
|
|
1
1
|
import fetch from 'cross-fetch';
|
|
2
|
-
import iconv from
|
|
2
|
+
import iconv from 'iconv-lite';
|
|
3
3
|
/**
|
|
4
4
|
* Отримати sesID та sessGUID користувача
|
|
5
5
|
* @category Cabinet
|
|
6
|
-
* @param
|
|
7
|
-
* @param password -
|
|
6
|
+
* @param login - Прізвище користувача
|
|
7
|
+
* @param password - Пароль
|
|
8
|
+
* @throws {Error} Якщо виникають проблеми з запитом або дані некоректні.
|
|
8
9
|
* @returns Об'єкт { sesID, sessGUID }
|
|
9
10
|
*/
|
|
10
|
-
export async function getSesId(
|
|
11
|
+
export async function getSesId(login, password) {
|
|
11
12
|
try {
|
|
12
|
-
const formData = `user_name=${
|
|
13
|
-
const encodedFormData = iconv.encode(formData,
|
|
14
|
-
const response = await fetch(
|
|
15
|
-
method:
|
|
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',
|
|
16
17
|
body: new Uint8Array(encodedFormData),
|
|
17
|
-
redirect:
|
|
18
|
+
redirect: 'manual',
|
|
18
19
|
});
|
|
19
20
|
if (response.status === 302) {
|
|
20
21
|
const buffer = await response.arrayBuffer();
|
|
21
|
-
const responseText = iconv.decode(Buffer.from(buffer),
|
|
22
|
-
const cookies = response.headers.get(
|
|
23
|
-
let sessGUID =
|
|
22
|
+
const responseText = iconv.decode(Buffer.from(buffer), 'utf-8');
|
|
23
|
+
const cookies = response.headers.get('set-cookie');
|
|
24
|
+
let sessGUID = '';
|
|
24
25
|
if (cookies) {
|
|
25
|
-
const sessGUIDStart = cookies.indexOf(
|
|
26
|
-
const sessGUIDEnd = cookies.indexOf(
|
|
26
|
+
const sessGUIDStart = cookies.indexOf('SessGUID=') + 'SessGUID='.length;
|
|
27
|
+
const sessGUIDEnd = cookies.indexOf(';', sessGUIDStart);
|
|
27
28
|
sessGUID = cookies.substring(sessGUIDStart, sessGUIDEnd);
|
|
28
29
|
}
|
|
29
|
-
const sesIDIndex = responseText.indexOf(
|
|
30
|
+
const sesIDIndex = responseText.indexOf('sesID=') + 6;
|
|
30
31
|
const sesID = responseText.substring(sesIDIndex, sesIDIndex + 36);
|
|
31
32
|
return { ok: true, sesID, sessGUID };
|
|
32
33
|
}
|
|
33
34
|
return { ok: false, sesID: '', sessGUID: '' };
|
|
34
35
|
}
|
|
35
36
|
catch (e) {
|
|
36
|
-
console.error(
|
|
37
|
+
console.error('Error in getSesID:', e);
|
|
37
38
|
throw e;
|
|
38
39
|
}
|
|
39
40
|
}
|
package/dist/cabinet/types.d.ts
CHANGED
|
@@ -3,11 +3,148 @@
|
|
|
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;
|
|
11
11
|
sesID: string;
|
|
12
12
|
sessGUID: string;
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Анкетні дані студента
|
|
16
|
+
* @category Cabinet
|
|
17
|
+
* @remarks
|
|
18
|
+
* **Статус запиту:**
|
|
19
|
+
* - `ok` — Успіх отримання даних (true/false)
|
|
20
|
+
*
|
|
21
|
+
* **ПІБ студента:**
|
|
22
|
+
* - `fullName` — Повне ім'я студента (Прізвище Ім'я По батькові)
|
|
23
|
+
* - `lastName` — Прізвище
|
|
24
|
+
* - `firstName` — Ім'я
|
|
25
|
+
* - `middleName` — По батькові
|
|
26
|
+
*
|
|
27
|
+
* **Загальні дані:**
|
|
28
|
+
* - `birthDate` — Дата народження (формат: DD.MM.YYYY)
|
|
29
|
+
* - `gender` — Стать (Чол/Жін)
|
|
30
|
+
* - `previousFullName` — Попереднє ПІБ студента (якщо змінювалось)
|
|
31
|
+
* - `country` — Країна, з якої навчається студент
|
|
32
|
+
* - `lastNameEng` — Прізвище англійською
|
|
33
|
+
* - `firstNameEng` — Ім'я англійською
|
|
34
|
+
* - `middleNameEng` — По батькові англійською (зазвичай немає)
|
|
35
|
+
*
|
|
36
|
+
* **Контактні дані:**
|
|
37
|
+
* - `email` — Електронна пошта студента
|
|
38
|
+
* - `phones` — Масив телефонних номерів студента
|
|
39
|
+
*
|
|
40
|
+
* **Дані про навчання:**
|
|
41
|
+
* - `faculty` — Факультет
|
|
42
|
+
* - `specialty` — Спеціальність (назва освітньої програми)
|
|
43
|
+
* - `degree` — Ступінь / Освітньо-професійний ступінь (бакалавр, магістр тощо)
|
|
44
|
+
* - `group` — Академічна група
|
|
45
|
+
* - `studyForm` — Форма навчання (Денна, Заочна, Вечірня)
|
|
46
|
+
* - `paymentForm` — Форма оплати навчання (Держ.замовлення, Контракт)
|
|
47
|
+
* - `enrollmentOrder` — Номер наказу на зарахування
|
|
48
|
+
* - `enrollmentDate` — Дата наказу на зарахування (формат: DD.MM.YYYY)
|
|
49
|
+
* - `studyDuration` — Термін навчання (наприклад: "4 роки")
|
|
50
|
+
* - `graduationDate` — Дата закінчення навчання (формат: DD.MM.YYYY)
|
|
51
|
+
*/
|
|
52
|
+
export interface Data {
|
|
53
|
+
ok: boolean;
|
|
54
|
+
fullName?: string;
|
|
55
|
+
lastName?: string;
|
|
56
|
+
firstName?: string;
|
|
57
|
+
middleName?: string;
|
|
58
|
+
birthDate?: string;
|
|
59
|
+
gender?: string;
|
|
60
|
+
previousFullName?: string;
|
|
61
|
+
country?: string;
|
|
62
|
+
lastNameEng?: string;
|
|
63
|
+
firstNameEng?: string;
|
|
64
|
+
middleNameEng?: string;
|
|
65
|
+
email?: string;
|
|
66
|
+
phones?: string[];
|
|
67
|
+
faculty?: string;
|
|
68
|
+
specialty?: string;
|
|
69
|
+
degree?: string;
|
|
70
|
+
group?: string;
|
|
71
|
+
studyForm?: string;
|
|
72
|
+
paymentForm?: string;
|
|
73
|
+
enrollmentOrder?: string;
|
|
74
|
+
enrollmentDate?: string;
|
|
75
|
+
studyDuration?: string;
|
|
76
|
+
graduationDate?: string;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* name та prId дисципліни
|
|
80
|
+
* @category Cabinet
|
|
81
|
+
* @remarks
|
|
82
|
+
* - `name` — Повна назва дисципліни
|
|
83
|
+
* - `prId` — Айді дисципліни
|
|
84
|
+
*/
|
|
85
|
+
export interface Discipline {
|
|
86
|
+
name: string;
|
|
87
|
+
prId: string;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* name та prId дисципліни
|
|
91
|
+
* @category Cabinet
|
|
92
|
+
* @remarks
|
|
93
|
+
* - `ok` — Успіх отримання токену
|
|
94
|
+
* - `disciplines` — Масив дисциплін {@link Discipline}
|
|
95
|
+
*/
|
|
96
|
+
export interface Disciplines {
|
|
97
|
+
ok: boolean;
|
|
98
|
+
disciplines: Discipline[];
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Опис заняття у кабінеті студента
|
|
102
|
+
* @category Cabinet
|
|
103
|
+
* @remarks
|
|
104
|
+
* - `teacher` — Прізвище та ініціали викладача
|
|
105
|
+
* - `date` — Дата заняття у форматі "dd.mm.yyyy"
|
|
106
|
+
* - `type` — Тип заняття: "Лек", "ПрСем", "Лаб", "МК", "Екз"
|
|
107
|
+
* - `time` — Час заняття у форматі "HH:MM-HH:MM"
|
|
108
|
+
* - `index` — Значення атрибута `data-ind`, унікальний ідентифікатор
|
|
109
|
+
*/
|
|
110
|
+
export interface ScheduleItem {
|
|
111
|
+
teacher: string;
|
|
112
|
+
date: string;
|
|
113
|
+
type: string;
|
|
114
|
+
time: string;
|
|
115
|
+
index: string;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Список оцінок студента.
|
|
119
|
+
* @category Cabinet
|
|
120
|
+
* @remarks
|
|
121
|
+
* - `id` — Унікальний ідентифікатор студента
|
|
122
|
+
* - `scores` — Оцінки по днях, масив масивів рядків (перший елемент - основна оцінка, другий елемент - оцінка перездачі)
|
|
123
|
+
* - `absences` — Кількість пропусків
|
|
124
|
+
* - `uabsences` — Кількість невиправлених пропусків
|
|
125
|
+
* - `finalScore` — Фінальна оцінка
|
|
126
|
+
*/
|
|
127
|
+
export interface StudentScores {
|
|
128
|
+
id: string;
|
|
129
|
+
scores: string[][];
|
|
130
|
+
absences: number;
|
|
131
|
+
uabsences: number;
|
|
132
|
+
finalScore: string;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Об'єкт оцінок з предмету.
|
|
136
|
+
* @category Cabinet
|
|
137
|
+
* @remarks
|
|
138
|
+
* - `ok` — Успіх отримання токену
|
|
139
|
+
* - `prId` — Айді дисципліни
|
|
140
|
+
* - `studentId` — Унікальний айді студента для предметів
|
|
141
|
+
* - `scheduleItem` — Опис заняття у кабінеті студента {@link ScheduleItem}
|
|
142
|
+
* - `studentScores` — Список оцінок студента {@link StudentScores}
|
|
143
|
+
*/
|
|
144
|
+
export interface Scores {
|
|
145
|
+
ok: boolean;
|
|
146
|
+
prId: string;
|
|
147
|
+
studentId: string;
|
|
148
|
+
scheduleItem: ScheduleItem[];
|
|
149
|
+
studentScores: StudentScores[];
|
|
150
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Перевіряє чи є це сторінка авторизації
|
|
3
|
+
*/
|
|
4
|
+
export function isLoginPage(html) {
|
|
5
|
+
return (html.includes('Авторизація користувача') ||
|
|
6
|
+
html.includes('user_name') ||
|
|
7
|
+
html.includes('Недійсний ідентифікатор сесії користувача'));
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Генерує cookie строку з DateTime та SessGUID
|
|
11
|
+
*/
|
|
12
|
+
export function generateCookieString(sessGUID) {
|
|
13
|
+
const now = new Date();
|
|
14
|
+
const day = String(now.getDate()).padStart(2, '0');
|
|
15
|
+
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
16
|
+
const year = now.getFullYear();
|
|
17
|
+
const hours = String(now.getHours()).padStart(2, '0');
|
|
18
|
+
const minutes = String(now.getMinutes()).padStart(2, '0');
|
|
19
|
+
const seconds = String(now.getSeconds()).padStart(2, '0');
|
|
20
|
+
const dateTime = `${day}.${month}.${year}+${hours}%3A${minutes}%3A${seconds}`;
|
|
21
|
+
return `DateTime=${dateTime}; SessGUID=${sessGUID}`;
|
|
22
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Перевірка на валідність сесії
|
|
3
|
+
* @category Cabinet
|
|
4
|
+
* @param sesId - ID сесії користувача
|
|
5
|
+
* @param sessGUID - GUID сесії з cookie
|
|
6
|
+
* @returns boolean значення.
|
|
7
|
+
*/
|
|
8
|
+
export declare function isValidSession(sesId: string, sessGUID: string): Promise<boolean>;
|