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
- private setSemester;
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
- setSemester() {
73
- this.semester = getSemester();
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('<th></th>');
55
- html = idx === -1 ? html : html.slice(idx + '<th></th>'.length);
56
- idx = html.indexOf('<th class="dh">');
57
- html = idx === -1 ? html : html.slice(0, idx);
58
- const regex = /<th[^>]*data-hth="([^"]+)"[^>]*>[\s\S]*?<a[^>]*data-ind="([^"]+)"[^>]*>[^<]+<\/a>[\s\S]*?<br>([^<]+)<\/th>/g;
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 = regex.exec(html)) !== null) {
62
- const [_, dataHth, index, time] = match;
63
- const parts = dataHth.split(',');
64
- const teacher = parts[0].trim();
65
- const dateType = parts[1]?.trim().split(' ') || [];
66
- const date = dateType[0] || '';
67
- const type = dateType[1] || '';
68
- result.push({ teacher, date, type, time: time.trim(), index });
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 tdMatches = [...row.matchAll(/<td[^>]*data-item="(\d+)"[^>]*>([\s\S]*?)<\/td>/g)];
170
+ const allTds = [...row.matchAll(/<td\b[^>]*>([\s\S]*?)<\/td>/g)];
82
171
  const scoresMap = new Map();
83
- for (const td of tdMatches) {
84
- const dataItem = Number(td[1]);
85
- const score = td[2].trim() || '';
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 scores = Array.from(scoresMap.values());
94
- scores.shift();
95
- const finalMatch = row.match(/<td[^>]*class="f f1"[^>]*>([\s\S]*?)<\/td>/);
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 = [...row.matchAll(/<td[^>]*class="f f2"[^>]*>([\s\S]*?)<\/td>/g)];
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: string;
101
- date: string;
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, process.env.PASSWORD);
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
- // console.log(await cb.loadData());
62
- console.log(await cb.getData());
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());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zdu-student-api",
3
- "version": "1.1.9",
3
+ "version": "1.1.10",
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",