chuvsu-js 2.4.2 → 2.5.0

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/browser.d.ts CHANGED
@@ -2,4 +2,4 @@ export { Schedule } from "./tt/schedule.js";
2
2
  export { getCurrentPeriod, isSessionPeriod, getSemesterStart, getSemesterWeeks, getWeekNumber, getWeekdayName, getTimeSlots, getLessonNumber, getAdjacentSemester, } from "./tt/utils.js";
3
3
  export { Period, EducationType } from "./common/types.js";
4
4
  export type { Time, WeekRange, Teacher } from "./common/types.js";
5
- export type { Faculty, Group, ScheduleEntry, FullScheduleSlot, FullScheduleDay, LessonTimeSlot, Lesson, LessonTime, SemesterWeek, Substitution, TransferInfo, TtClientOptions, CacheConfig, } from "./tt/types.js";
5
+ export type { Faculty, Group, ScheduleEntry, FullScheduleSlot, FullScheduleDay, LessonTimeSlot, Lesson, LessonTime, SemesterWeek, Substitution, TransferInfo, TeacherInfo, TtClientOptions, CacheConfig, } from "./tt/types.js";
package/dist/index.d.ts CHANGED
@@ -7,4 +7,4 @@ export type { Holiday, HolidayTransfer } from "./tt/utils.js";
7
7
  export { Period, EducationType, AuthError, ParseError, } from "./common/types.js";
8
8
  export type { Time, WeekRange, Teacher } from "./common/types.js";
9
9
  export type { PersonalData } from "./lk/types.js";
10
- export type { Faculty, Group, ScheduleEntry, FullScheduleSlot, FullScheduleDay, LessonTimeSlot, Lesson, LessonTime, SemesterWeek, Substitution, TransferInfo, TtClientOptions, CacheConfig, } from "./tt/types.js";
10
+ export type { Faculty, Group, ScheduleEntry, FullScheduleSlot, FullScheduleDay, LessonTimeSlot, Lesson, LessonTime, SemesterWeek, Substitution, TransferInfo, TeacherInfo, TtClientOptions, CacheConfig, } from "./tt/types.js";
@@ -1,7 +1,7 @@
1
1
  import type { CacheEntry } from "../common/cache.js";
2
2
  import { Period } from "../common/types.js";
3
3
  import { Schedule } from "./schedule.js";
4
- import type { Faculty, Group, TtClientOptions, CacheConfig } from "./types.js";
4
+ import type { Faculty, Group, TeacherInfo, TtClientOptions, CacheConfig } from "./types.js";
5
5
  export declare class TtClient {
6
6
  private http;
7
7
  private educationType;
@@ -47,4 +47,15 @@ export declare class TtClient {
47
47
  id: number;
48
48
  name: string;
49
49
  }[]>;
50
+ getTeachers(): Promise<{
51
+ id: number;
52
+ name: string;
53
+ }[]>;
54
+ private fetchTeacherSchedule;
55
+ getTeacherSchedule(teacherId: number): Promise<Schedule>;
56
+ getTeacherScheduleForPeriod(opts: {
57
+ teacherId: number;
58
+ period: Period;
59
+ }): Promise<Schedule>;
60
+ getTeacherInfo(teacherId: number): Promise<TeacherInfo | null>;
50
61
  }
package/dist/tt/client.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { HttpClient } from "../common/http.js";
2
2
  import { Cache } from "../common/cache.js";
3
3
  import { AuthError } from "../common/types.js";
4
- import { parseGroupButtons, parseFacultyButtons, parseTeacherButtons, parseFullSchedule, } from "./parse.js";
4
+ import { parseGroupButtons, parseFacultyButtons, parseTeacherButtons, parseFullSchedule, parseTeacherFullSchedule, parseTeacherInfo, } from "./parse.js";
5
5
  import { Schedule } from "./schedule.js";
6
6
  const BASE = "https://tt.chuvsu.ru";
7
7
  const AUTH_URL = `${BASE}/auth`;
@@ -173,4 +173,47 @@ export class TtClient {
173
173
  });
174
174
  return parseTeacherButtons(body);
175
175
  }
176
+ // --- Teacher schedule ---
177
+ async getTeachers() {
178
+ const cached = this.cache?.get("teachers", "all");
179
+ if (cached)
180
+ return cached;
181
+ const { body } = await this.authGet(`${BASE}/index/tech`);
182
+ const data = parseTeacherButtons(body);
183
+ this.cache?.set("teachers", "all", data);
184
+ return data;
185
+ }
186
+ async fetchTeacherSchedule(teacherId, period) {
187
+ const cacheKey = `teacher:${teacherId}:${period}`;
188
+ const cached = this.cache?.get("schedule", cacheKey);
189
+ if (cached)
190
+ return cached;
191
+ const url = `${BASE}/index/techtt/tech/${teacherId}`;
192
+ const { body } = await this.authPost(url, { htype: String(period) });
193
+ const days = parseTeacherFullSchedule(body, this.educationType);
194
+ this.cache?.set("schedule", cacheKey, days);
195
+ return days;
196
+ }
197
+ async getTeacherSchedule(teacherId) {
198
+ const schedules = new Map();
199
+ const results = await Promise.all(ALL_PERIODS.map(async (period) => {
200
+ const days = await this.fetchTeacherSchedule(teacherId, period);
201
+ return { period, days };
202
+ }));
203
+ for (const { period, days } of results) {
204
+ schedules.set(period, days);
205
+ }
206
+ return new Schedule(teacherId, schedules, undefined, this.educationType);
207
+ }
208
+ async getTeacherScheduleForPeriod(opts) {
209
+ const days = await this.fetchTeacherSchedule(opts.teacherId, opts.period);
210
+ const schedules = new Map();
211
+ schedules.set(opts.period, days);
212
+ return new Schedule(opts.teacherId, schedules, opts.period, this.educationType);
213
+ }
214
+ async getTeacherInfo(teacherId) {
215
+ const url = `${BASE}/index/techtt/tech/${teacherId}`;
216
+ const { body } = await this.authGet(url);
217
+ return parseTeacherInfo(body);
218
+ }
176
219
  }
@@ -1,5 +1,5 @@
1
1
  import { type Period, EducationType } from "../common/types.js";
2
- import type { Faculty, Group, FullScheduleDay } from "./types.js";
2
+ import type { Faculty, Group, FullScheduleDay, TeacherInfo } from "./types.js";
3
3
  export declare function parsePeriodFromPage(html: string): Period | null;
4
4
  export declare function parseGroupButtons(html: string): Group[];
5
5
  export declare function parseFacultyButtons(html: string): Faculty[];
@@ -8,3 +8,5 @@ export declare function parseTeacherButtons(html: string): {
8
8
  name: string;
9
9
  }[];
10
10
  export declare function parseFullSchedule(html: string, educationType?: EducationType): FullScheduleDay[];
11
+ export declare function parseTeacherFullSchedule(html: string, educationType?: EducationType): FullScheduleDay[];
12
+ export declare function parseTeacherInfo(html: string): TeacherInfo | null;
package/dist/tt/parse.js CHANGED
@@ -65,7 +65,7 @@ export function parseFullSchedule(html, educationType) {
65
65
  return parseSemesterSchedule(doc);
66
66
  }
67
67
  // --- Semester schedule parsing (weekday-based, repeating weekly) ---
68
- function parseSemesterSchedule(doc) {
68
+ function parseSemesterScheduleWith(doc, entryParser) {
69
69
  const days = [];
70
70
  const rows = doc.querySelectorAll("tr");
71
71
  let currentDay = null;
@@ -96,7 +96,7 @@ function parseSemesterSchedule(doc) {
96
96
  continue;
97
97
  const entries = [];
98
98
  for (const entryRow of dataCell.querySelectorAll("table tr")) {
99
- const entry = parseSemesterEntry(entryRow);
99
+ const entry = entryParser(entryRow);
100
100
  if (entry)
101
101
  entries.push(entry);
102
102
  }
@@ -109,6 +109,9 @@ function parseSemesterSchedule(doc) {
109
109
  }
110
110
  return days;
111
111
  }
112
+ function parseSemesterSchedule(doc) {
113
+ return parseSemesterScheduleWith(doc, parseSemesterEntry);
114
+ }
112
115
  function parseDate(dd, mm, yyyy) {
113
116
  return new Date(parseInt(yyyy), parseInt(mm) - 1, parseInt(dd));
114
117
  }
@@ -291,3 +294,149 @@ function parseSessionEntry(td) {
291
294
  timeEnd: parseTime(timeMatch[2]),
292
295
  };
293
296
  }
297
+ // --- Teacher schedule parsing ---
298
+ export function parseTeacherFullSchedule(html, educationType) {
299
+ const doc = parseHtml(html);
300
+ const edType = educationType ?? 1 /* EducationType.HigherEducation */;
301
+ if (doc.querySelector('td[id^="trd2"]')) {
302
+ return parseTeacherSessionSchedule(doc, edType);
303
+ }
304
+ return parseSemesterScheduleWith(doc, parseTeacherSemesterEntry);
305
+ }
306
+ function parseTeacherSemesterEntry(el) {
307
+ const td = el.querySelector("td") ?? el;
308
+ const fullHtml = td.innerHTML ?? "";
309
+ const plainText = text(td);
310
+ if (!plainText)
311
+ return null;
312
+ const possibleChanges = (td.getAttribute("class") ?? "").includes("want") || undefined;
313
+ const redDivs = td.querySelectorAll('div[style*="border: 2px solid red"]');
314
+ for (const div of redDivs) {
315
+ const result = parseTransferDiv(div);
316
+ if (result) {
317
+ if (possibleChanges)
318
+ result.entry.possibleChanges = true;
319
+ return result.entry;
320
+ }
321
+ }
322
+ const substitutions = [];
323
+ for (const div of redDivs) {
324
+ const sub = parseSubstitutionDiv(div);
325
+ if (sub)
326
+ substitutions.push(sub);
327
+ }
328
+ let cleanHtml = fullHtml;
329
+ let cleanText = plainText;
330
+ for (const div of redDivs) {
331
+ cleanHtml = cleanHtml.replace(div.outerHTML ?? "", "");
332
+ cleanText = cleanText.replace(text(div), "");
333
+ }
334
+ const subjectEl = td.querySelector('span[style*="color: blue"]');
335
+ const subject = subjectEl ? text(subjectEl) : "";
336
+ if (!subject)
337
+ return null;
338
+ const typeMatch = cleanText.match(/\((лк|пр|лб|зач|экз|зчО|кр|конс)\)/);
339
+ const weeksMatch = cleanText.match(/\(([^)]*нед\.?[^)]*)\)/);
340
+ const roomMatch = cleanHtml.match(/(?:<sup>[^<]*<\/sup>)?([А-Яа-яA-Za-z]-\d+)/);
341
+ const groupsMatch = cleanHtml.match(/<br\s*\/?>\s*([^<]+?)(?:<br|<\/td|<div|<i|$)/);
342
+ const subgroupMatch = cleanText.match(/(\d+)\s*подгруппа/);
343
+ const weekParity = parseWeekParity(cleanHtml);
344
+ return {
345
+ room: roomMatch?.[1] ?? "",
346
+ subject,
347
+ type: typeMatch?.[1] ?? "",
348
+ weeks: parseWeeks(weeksMatch?.[1] ?? ""),
349
+ teacher: { name: "" },
350
+ groups: groupsMatch?.[1]?.trim() ?? "",
351
+ subgroup: subgroupMatch ? parseInt(subgroupMatch[1]) : undefined,
352
+ weekParity,
353
+ substitutions: substitutions.length > 0 ? substitutions : undefined,
354
+ possibleChanges,
355
+ };
356
+ }
357
+ function parseTeacherSessionSchedule(doc, educationType) {
358
+ const days = [];
359
+ for (const dateCell of doc.querySelectorAll('td[id^="trd2"]')) {
360
+ const id = dateCell.getAttribute("id") ?? "";
361
+ const dateMatch = id.match(/trd(\d{4})(\d{2})(\d{2})/);
362
+ if (!dateMatch)
363
+ continue;
364
+ const year = parseInt(dateMatch[1]);
365
+ const month = parseInt(dateMatch[2]) - 1;
366
+ const dayNum = parseInt(dateMatch[3]);
367
+ const date = new Date(year, month, dayNum);
368
+ const cellHtml = dateCell.innerHTML ?? "";
369
+ const brMatch = cellHtml.match(/<br\s*\/?>\s*(.+)/i);
370
+ const weekday = brMatch ? brMatch[1].trim() : "";
371
+ const row = dateCell.parentElement;
372
+ if (!row)
373
+ continue;
374
+ const dataCell = row.querySelector("td.trdata:not(.trfd)");
375
+ if (!dataCell)
376
+ continue;
377
+ const slots = [];
378
+ for (const entryRow of dataCell.querySelectorAll("table tr")) {
379
+ const td = entryRow.querySelector("td") ?? entryRow;
380
+ const entry = parseTeacherSessionEntry(td);
381
+ if (!entry)
382
+ continue;
383
+ slots.push({
384
+ number: getLessonNumber(entry.timeStart, educationType),
385
+ timeStart: entry.timeStart,
386
+ timeEnd: entry.timeEnd,
387
+ entries: [entry.entry],
388
+ });
389
+ }
390
+ if (slots.length > 0) {
391
+ days.push({ weekday, date, slots });
392
+ }
393
+ }
394
+ return days;
395
+ }
396
+ function parseTeacherSessionEntry(td) {
397
+ const fullHtml = td.innerHTML ?? "";
398
+ const plainText = text(td);
399
+ if (!plainText)
400
+ return null;
401
+ const subjectEl = td.querySelector('span[style*="color: blue"]');
402
+ const subject = subjectEl ? text(subjectEl) : "";
403
+ if (!subject)
404
+ return null;
405
+ const roomMatch = fullHtml.match(/^([^<]*?)\s*<span/);
406
+ const room = roomMatch ? roomMatch[1].trim() : "";
407
+ const typeMatch = plainText.match(/\((лк|пр|лб|зач|экз|зчО|кр|конс\.?|Экз)\)/i);
408
+ const type = typeMatch ? typeMatch[1].replace(/\.$/, "").toLowerCase() : "";
409
+ // Groups: text between </span> type and <br>time
410
+ const groupsMatch = fullHtml.match(/\((?:лк|пр|лб|зач|экз|зчО|кр|конс\.?|Экз)\)\s*([^<]+?)\s*<br/i);
411
+ const timeMatch = fullHtml.match(/<br\s*\/?>\s*(\d{2}:\d{2})\s*-\s*(\d{2}:\d{2})/);
412
+ if (!timeMatch)
413
+ return null;
414
+ return {
415
+ entry: {
416
+ room,
417
+ subject,
418
+ type,
419
+ weeks: { from: 0, to: 0 },
420
+ teacher: { name: "" },
421
+ groups: groupsMatch?.[1]?.trim() ?? "",
422
+ },
423
+ timeStart: parseTime(timeMatch[1]),
424
+ timeEnd: parseTime(timeMatch[2]),
425
+ };
426
+ }
427
+ export function parseTeacherInfo(html) {
428
+ const doc = parseHtml(html);
429
+ const nameEl = doc.querySelector(".htextb");
430
+ if (!nameEl)
431
+ return null;
432
+ const nameHtml = nameEl.innerHTML ?? "";
433
+ const nameMatch = nameHtml.match(/^([^<]+)/);
434
+ const name = nameMatch?.[1]?.trim() ?? "";
435
+ if (!name)
436
+ return null;
437
+ const degreeEl = nameEl.querySelector('span[style*="color: blue"]');
438
+ const degree = degreeEl ? text(degreeEl).trim() : undefined;
439
+ const deptEl = doc.querySelector(".htext");
440
+ const department = deptEl ? text(deptEl).trim() : undefined;
441
+ return { name, degree: degree || undefined, department: department || undefined };
442
+ }
@@ -35,6 +35,8 @@ export interface ScheduleEntry {
35
35
  type: string;
36
36
  weeks: WeekRange;
37
37
  teacher: Teacher;
38
+ /** For teacher schedules: group names (e.g. "КТ-42-25 (1 подгруппа)"). */
39
+ groups?: string;
38
40
  subgroup?: number;
39
41
  weekParity?: "even" | "odd";
40
42
  /** Date-specific substitutions (замена на). */
@@ -73,6 +75,8 @@ export interface Lesson {
73
75
  type: string;
74
76
  room: string;
75
77
  teacher: Teacher;
78
+ /** For teacher schedules: group names. */
79
+ groups?: string;
76
80
  weeks: WeekRange;
77
81
  subgroup?: number;
78
82
  weekParity?: "even" | "odd";
@@ -85,6 +89,12 @@ export interface Lesson {
85
89
  /** Whether this lesson is marked as potentially changing. */
86
90
  possibleChanges?: boolean;
87
91
  }
92
+ /** Teacher info from the schedule page header. */
93
+ export interface TeacherInfo {
94
+ name: string;
95
+ degree?: string;
96
+ department?: string;
97
+ }
88
98
  export interface SemesterWeek {
89
99
  week: number;
90
100
  start: Date;
@@ -94,6 +104,7 @@ export interface CacheConfig {
94
104
  schedule?: number;
95
105
  faculties?: number;
96
106
  groups?: number;
107
+ teachers?: number;
97
108
  }
98
109
  export interface TtClientOptions {
99
110
  educationType?: EducationType;
package/dist/tt/utils.js CHANGED
@@ -165,6 +165,7 @@ export function slotsToLessons(slots, date) {
165
165
  type: entry.type,
166
166
  room,
167
167
  teacher,
168
+ groups: entry.groups,
168
169
  weeks: entry.weeks,
169
170
  subgroup: entry.subgroup,
170
171
  weekParity: entry.weekParity,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chuvsu-js",
3
- "version": "2.4.2",
3
+ "version": "2.5.0",
4
4
  "description": "Node.js library for ChuvSU student portal (lk.chuvsu.ru) and schedule (tt.chuvsu.ru)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",