zdu-student-api 1.1.9 → 1.1.11
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/dist/audience.js +1 -0
- package/dist/cabinet/data.js +1 -0
- package/dist/cabinet/session.js +1 -0
- package/dist/cabinetStudent/cabinetStudent.d.ts +3 -2
- package/dist/cabinetStudent/cabinetStudent.js +12 -4
- package/dist/cabinetStudent/disciplines.js +1 -0
- package/dist/cabinetStudent/scores.js +128 -23
- package/dist/cabinetStudent/types.d.ts +7 -5
- package/dist/cabinetTeacher/academicGroups.js +1 -0
- package/dist/examples.js +16 -10
- package/package.json +6 -6
package/dist/audience.js
CHANGED
|
@@ -74,6 +74,7 @@ export class Audience {
|
|
|
74
74
|
this.dops.forEach((dop) => {
|
|
75
75
|
dops += `&dop_${dop}=yes`;
|
|
76
76
|
});
|
|
77
|
+
// TODO FIX MIN AND MAX. IF MIN = true & !MAX, MAX=999999999
|
|
77
78
|
const response = await fetch(`https://dekanat.zu.edu.ua/cgi-bin/timetable_export.cgi?req_type=free_rooms_list&&block_name=${this.blockName !== undefined ? this.encodeCP1251(this.blockName) : ''}&size_min=${this.size_min !== undefined ? this.size_min : ''}&size_max=${this.size_max !== undefined ? this.size_max : ''}&room_type=${this.roomType !== undefined ? this.encodeCP1251(this.roomType) : ''}&begin_date=${date}&lesson=${this.lesson}${dops}&req_format=json&coding_mode=UTF8&bs=%D1%F4%EE%F0%EC%F3%E2%E0%F2%E8+%E7%E0%EF%E8%F2`);
|
|
78
79
|
if (!response.ok)
|
|
79
80
|
throw new Error(`HTTP error ${response.status}: ${response.statusText}`);
|
package/dist/cabinet/data.js
CHANGED
|
@@ -2,6 +2,7 @@ import fetch from 'cross-fetch';
|
|
|
2
2
|
import iconv from 'iconv-lite';
|
|
3
3
|
import { generateCookieString, isLoginPage } from './session.js';
|
|
4
4
|
import { parseDataPageN3, parseDataPageN31, parseTeacherData } from './parsers.js';
|
|
5
|
+
import { Buffer } from 'buffer';
|
|
5
6
|
/**
|
|
6
7
|
* Отримати анкетні дані студента
|
|
7
8
|
* @category CabinetTeacher
|
package/dist/cabinet/session.js
CHANGED
|
@@ -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
|
* Отримання всіх данних
|
|
@@ -2,6 +2,7 @@ import fetch from 'cross-fetch';
|
|
|
2
2
|
import iconv from 'iconv-lite';
|
|
3
3
|
import { parseDisciplinesPageN6, parseDisciplinesPageN7 } from '../cabinet/parsers.js';
|
|
4
4
|
import { generateCookieString, isLoginPage } from '../cabinet/session.js';
|
|
5
|
+
import { Buffer } from 'buffer';
|
|
5
6
|
/**
|
|
6
7
|
* Отримати всі дисципліни студента
|
|
7
8
|
* @category CabinetStudent
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fetch from 'cross-fetch';
|
|
2
2
|
import iconv from 'iconv-lite';
|
|
3
3
|
import { generateCookieString, isLoginPage } from '../cabinet/session.js';
|
|
4
|
+
import { Buffer } from 'buffer';
|
|
4
5
|
/**
|
|
5
6
|
* Отримати оцінки вибраного предмета студента
|
|
6
7
|
* @category CabinetStudent
|
|
@@ -34,7 +35,7 @@ export async function getScores(sesId, sessGUID, prId, semester) {
|
|
|
34
35
|
if (isLoginPage(html1))
|
|
35
36
|
return result;
|
|
36
37
|
result.scheduleItem = parseSchedule(html1);
|
|
37
|
-
result.studentScores = parseScores(html1);
|
|
38
|
+
result.studentScores = parseScores(html1, result.scheduleItem.length);
|
|
38
39
|
result.studentId = result.studentScores[0]?.id;
|
|
39
40
|
if (result.studentScores.length > 1) {
|
|
40
41
|
result.studentScores.sort((a, b) => a.id.localeCompare(b.id));
|
|
@@ -51,38 +52,140 @@ export async function getScores(sesId, sessGUID, prId, semester) {
|
|
|
51
52
|
* Витягує список пар з HTML
|
|
52
53
|
*/
|
|
53
54
|
function parseSchedule(html) {
|
|
54
|
-
let idx = html.indexOf('<
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
55
|
+
let idx = html.indexOf('<thead>');
|
|
56
|
+
if (idx === -1)
|
|
57
|
+
return [];
|
|
58
|
+
let theadEnd = html.indexOf('</thead>', idx);
|
|
59
|
+
if (theadEnd === -1)
|
|
60
|
+
return [];
|
|
61
|
+
let theadContent = html.slice(idx, theadEnd);
|
|
62
|
+
let firstTrStart = theadContent.indexOf('<tr>');
|
|
63
|
+
let firstTrEnd = theadContent.indexOf('</tr>', firstTrStart);
|
|
64
|
+
if (firstTrStart === -1 || firstTrEnd === -1)
|
|
65
|
+
return [];
|
|
66
|
+
let firstRow = theadContent.slice(firstTrStart, firstTrEnd);
|
|
67
|
+
let secondTrStart = theadContent.indexOf('<tr>', firstTrEnd);
|
|
68
|
+
let secondTrEnd = theadContent.indexOf('</tr>', secondTrStart);
|
|
69
|
+
let thirdTrStart = theadContent.indexOf('<tr>', secondTrEnd);
|
|
70
|
+
let thirdTrEnd = theadContent.indexOf('</tr>', thirdTrStart);
|
|
71
|
+
if (thirdTrStart === -1 || thirdTrEnd === -1)
|
|
72
|
+
return [];
|
|
73
|
+
let thirdRow = theadContent.slice(thirdTrStart, thirdTrEnd);
|
|
59
74
|
const result = [];
|
|
75
|
+
const thStartRegex = /<th\b/g;
|
|
76
|
+
const positions = [];
|
|
60
77
|
let match;
|
|
61
|
-
while ((match =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
78
|
+
while ((match = thStartRegex.exec(firstRow)) !== null) {
|
|
79
|
+
positions.push(match.index);
|
|
80
|
+
}
|
|
81
|
+
positions.push(firstRow.length);
|
|
82
|
+
const thirdPositions = [];
|
|
83
|
+
const thirdThStartRegex = /<th\b/g;
|
|
84
|
+
while ((match = thirdThStartRegex.exec(thirdRow)) !== null) {
|
|
85
|
+
thirdPositions.push(match.index);
|
|
86
|
+
}
|
|
87
|
+
thirdPositions.push(thirdRow.length);
|
|
88
|
+
for (let i = 0; i < positions.length - 1; i++) {
|
|
89
|
+
const start = positions[i];
|
|
90
|
+
const end = positions[i + 1];
|
|
91
|
+
const thFull = firstRow.slice(start, end);
|
|
92
|
+
const closeIdx = thFull.indexOf('</th>');
|
|
93
|
+
if (closeIdx === -1)
|
|
94
|
+
continue;
|
|
95
|
+
const thTag = thFull.slice(0, closeIdx + 5);
|
|
96
|
+
const thContent = thTag.replace(/<th[^>]*>/, '').replace(/<\/th>/, '');
|
|
97
|
+
if (!thContent.trim())
|
|
98
|
+
continue;
|
|
99
|
+
let moduleMatch = thContent.match(/^(М\d+)<br>/);
|
|
100
|
+
if (moduleMatch) {
|
|
101
|
+
const moduleNum = moduleMatch[1];
|
|
102
|
+
const timeMatch = thContent.match(/<br[^>]*>\s*(\d{2}:\d{2}-\d{2}:\d{2})/);
|
|
103
|
+
const time = timeMatch ? timeMatch[1].trim() : '';
|
|
104
|
+
result.push({
|
|
105
|
+
teacher: undefined,
|
|
106
|
+
date: undefined,
|
|
107
|
+
type: 'МД',
|
|
108
|
+
time: time,
|
|
109
|
+
index: `module_${moduleNum}`,
|
|
110
|
+
description: `Модуль ${moduleNum}`,
|
|
111
|
+
});
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
if (thContent.match(/Підсум\.|Всього|Невиправд\./)) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
const dataHthMatch = thTag.match(/data-hth="([^"]+)"/);
|
|
118
|
+
if (!dataHthMatch)
|
|
119
|
+
continue;
|
|
120
|
+
const dataHth = dataHthMatch[1];
|
|
121
|
+
const aMatch = thContent.match(/data-ind="([^"]+)"/);
|
|
122
|
+
if (!aMatch)
|
|
123
|
+
continue;
|
|
124
|
+
const index = aMatch[1];
|
|
125
|
+
const dateMatch = thContent.match(/>(\d{2}\.\d{2}\.\d{4})</);
|
|
126
|
+
const date = dateMatch ? dateMatch[1] : '';
|
|
127
|
+
const timeMatch = thContent.match(/<br[^>]*>\s*(\d{2}:\d{2}-\d{2}:\d{2})/);
|
|
128
|
+
const time = timeMatch ? timeMatch[1].trim() : '';
|
|
129
|
+
const parts = dataHth.split(',').map((p) => p.trim());
|
|
130
|
+
const teacher = parts[0] || '';
|
|
131
|
+
let type = '';
|
|
132
|
+
if (parts[1]) {
|
|
133
|
+
const dateTypeParts = parts[1].split(/\s+/);
|
|
134
|
+
type = dateTypeParts[1] || '';
|
|
135
|
+
}
|
|
136
|
+
result.push({ teacher, date, type, time, index });
|
|
137
|
+
}
|
|
138
|
+
const descriptions = parseDescriptions(html);
|
|
139
|
+
for (const item of result) {
|
|
140
|
+
if (item.index && !item.index.startsWith('module_') && descriptions.has(item.index)) {
|
|
141
|
+
item.description = descriptions.get(item.index);
|
|
142
|
+
}
|
|
69
143
|
}
|
|
70
144
|
return result;
|
|
71
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Витягує описи занять з HTML
|
|
148
|
+
*/
|
|
149
|
+
function parseDescriptions(html) {
|
|
150
|
+
const descriptions = new Map();
|
|
151
|
+
const divRegex = /<div\s+id="r(\d+)"\s+class="hidden">\s*([\s\S]*?)\s*<br>/g;
|
|
152
|
+
let match;
|
|
153
|
+
while ((match = divRegex.exec(html)) !== null) {
|
|
154
|
+
const index = match[1];
|
|
155
|
+
const content = match[2].trim();
|
|
156
|
+
if (content && !content.startsWith('<')) {
|
|
157
|
+
descriptions.set(index, content);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return descriptions;
|
|
161
|
+
}
|
|
72
162
|
/**
|
|
73
163
|
* Витягує список оцінок з HTML
|
|
74
164
|
*/
|
|
75
|
-
function parseScores(html) {
|
|
165
|
+
function parseScores(html, expectedScoresCount) {
|
|
76
166
|
const studentRows = html.match(/<tr\s+[^>]*id="s\d+"[\s\S]*?<\/tr>/g) || [];
|
|
77
167
|
const result = [];
|
|
78
168
|
for (const row of studentRows) {
|
|
79
169
|
const idMatch = row.match(/id="(s\d+)"/);
|
|
80
170
|
const id = idMatch ? idMatch[1] : '';
|
|
81
|
-
const
|
|
171
|
+
const allTds = [...row.matchAll(/<td\b[^>]*>([\s\S]*?)<\/td>/g)];
|
|
82
172
|
const scoresMap = new Map();
|
|
83
|
-
for (const
|
|
84
|
-
const
|
|
85
|
-
const
|
|
173
|
+
for (const tdMatch of allTds) {
|
|
174
|
+
const fullTd = tdMatch[0];
|
|
175
|
+
const tdContent = tdMatch[1].trim();
|
|
176
|
+
const dataItemMatch = fullTd.match(/data-item="(\d+)"/);
|
|
177
|
+
if (!dataItemMatch)
|
|
178
|
+
continue;
|
|
179
|
+
const dataItem = Number(dataItemMatch[1]);
|
|
180
|
+
if (fullTd.match(/class="[^"]*\bf\s+f1\b[^"]*"/) ||
|
|
181
|
+
fullTd.match(/class="[^"]*\bf1\b[^"]*"/)) {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (fullTd.match(/class="[^"]*\bf\s+f2\b[^"]*"/) ||
|
|
185
|
+
fullTd.match(/class="[^"]*\bf2\b[^"]*"/)) {
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const score = tdContent || '';
|
|
86
189
|
if (!scoresMap.has(dataItem)) {
|
|
87
190
|
scoresMap.set(dataItem, [score]);
|
|
88
191
|
}
|
|
@@ -90,11 +193,13 @@ function parseScores(html) {
|
|
|
90
193
|
scoresMap.get(dataItem).push(score);
|
|
91
194
|
}
|
|
92
195
|
}
|
|
93
|
-
const
|
|
94
|
-
scores.
|
|
95
|
-
const finalMatch = row.match(/<td[^>]*class="
|
|
196
|
+
const sortedItems = Array.from(scoresMap.entries()).sort((a, b) => a[0] - b[0]);
|
|
197
|
+
const scores = sortedItems.map(([_, values]) => values);
|
|
198
|
+
const finalMatch = row.match(/<td[^>]*class="[^"]*\bf\s+f1\b[^"]*"[^>]*>([\s\S]*?)<\/td>/);
|
|
96
199
|
const finalScore = finalMatch ? finalMatch[1].trim() : '';
|
|
97
|
-
const absenceMatches = [
|
|
200
|
+
const absenceMatches = [
|
|
201
|
+
...row.matchAll(/<td[^>]*class="[^"]*\bf\s+f2\b[^"]*"[^>]*>([\s\S]*?)<\/td>/g),
|
|
202
|
+
];
|
|
98
203
|
const absences = absenceMatches[0] ? Number(absenceMatches[0][1].trim() || 0) : 0;
|
|
99
204
|
const uabsences = absenceMatches[1] ? Number(absenceMatches[1][1].trim() || 0) : 0;
|
|
100
205
|
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
|
* Список оцінок студента.
|
|
@@ -2,6 +2,7 @@ import fetch from 'cross-fetch';
|
|
|
2
2
|
import iconv from 'iconv-lite';
|
|
3
3
|
import { generateCookieString, isLoginPage } from '../cabinet/session.js';
|
|
4
4
|
import { parseGroupsPage } from '../cabinet/parsers.js';
|
|
5
|
+
import { Buffer } from 'buffer';
|
|
5
6
|
/**
|
|
6
7
|
* Отримати всі академічні групи
|
|
7
8
|
* @category CabinetTeacher
|
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'
|
|
@@ -28,10 +27,16 @@ import 'dotenv/config';
|
|
|
28
27
|
// console.log(await getTeachers('Кривонос Олександр'));
|
|
29
28
|
// console.log(await getRooms('319'));
|
|
30
29
|
// const audience = new Audience();
|
|
31
|
-
// audience.blockName =
|
|
30
|
+
// // audience.blockName = 'гуртож №3';
|
|
32
31
|
// try {
|
|
32
|
+
// audience.roomType = 'Комп. клас';
|
|
33
|
+
// audience.blockName = '№1';
|
|
34
|
+
// const [day, month, year] = '09.02.2026'.split('.');
|
|
35
|
+
// audience.roomsDate = new Date(+year, +month - 1, +day + 1);
|
|
36
|
+
// console.log(audience.roomsDate);
|
|
37
|
+
// audience.lesson = 5;
|
|
33
38
|
// const audiences = await audience.getAudience();
|
|
34
|
-
// console.log(
|
|
39
|
+
// console.log('Аудиторії:', audiences);
|
|
35
40
|
// } catch (err: any) {
|
|
36
41
|
// console.error(err.message);
|
|
37
42
|
// }
|
|
@@ -53,13 +58,14 @@ import 'dotenv/config';
|
|
|
53
58
|
// const me = data2.studentScores.find((s) => s.id === data2.studentId)!;
|
|
54
59
|
// const sesID = '';
|
|
55
60
|
// 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
|
-
|
|
61
|
+
// const cb = new CabinetStudent(process.env.LOGIN!, process.env.PASSWORD!);
|
|
62
|
+
// await cb.auth();
|
|
63
|
+
// // // console.log(cb.sesID, cb.sessGUID);
|
|
64
|
+
// // console.log(await cb.setSession(sesID, sessGUID));
|
|
65
|
+
// // // console.log(cb.sesID, cb.sessGUID);
|
|
66
|
+
// await cb.loadData();
|
|
67
|
+
// const scores = cb.allScores;
|
|
68
|
+
// await writeFile('scores.json', JSON.stringify(scores, null, 2), 'utf-8');
|
|
63
69
|
// console.log(cb.data);
|
|
64
70
|
// await cb.getDisciplines();
|
|
65
71
|
// console.log(await cb.getId());
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zdu-student-api",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.11",
|
|
4
4
|
"description": "API client for ZDU student services",
|
|
5
5
|
"author": "Nicita3 <ni596157@gmail.com> (https://github.com/Nicita-3)",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -22,16 +22,16 @@
|
|
|
22
22
|
},
|
|
23
23
|
"homepage": "https://nicita-3.github.io/zdu-student-api",
|
|
24
24
|
"devDependencies": {
|
|
25
|
-
"@types/node": "^24.
|
|
26
|
-
"prettier": "^3.
|
|
25
|
+
"@types/node": "^24.12.2",
|
|
26
|
+
"prettier": "^3.8.1",
|
|
27
27
|
"ts-node": "^10.9.2",
|
|
28
|
-
"typedoc": "^0.28.
|
|
28
|
+
"typedoc": "^0.28.18",
|
|
29
29
|
"typescript": "^5.9.3"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"buffer": "^6.0.3",
|
|
33
33
|
"cross-fetch": "^4.1.0",
|
|
34
|
-
"dotenv": "^17.
|
|
35
|
-
"iconv-lite": "^0.7.
|
|
34
|
+
"dotenv": "^17.4.1",
|
|
35
|
+
"iconv-lite": "^0.7.2"
|
|
36
36
|
}
|
|
37
37
|
}
|