chuvsu-js 2.5.0 → 2.6.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/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, TeacherInfo, TtClientOptions, CacheConfig, } from "./tt/types.js";
10
+ export type { Faculty, Group, ScheduleEntry, FullScheduleSlot, FullScheduleDay, LessonTimeSlot, Lesson, LessonTime, SemesterWeek, Substitution, SubstituteForInfo, TransferInfo, TeacherInfo, TtClientOptions, CacheConfig, } from "./tt/types.js";
package/dist/tt/client.js CHANGED
@@ -203,13 +203,13 @@ export class TtClient {
203
203
  for (const { period, days } of results) {
204
204
  schedules.set(period, days);
205
205
  }
206
- return new Schedule(teacherId, schedules, undefined, this.educationType);
206
+ return new Schedule(teacherId, schedules, undefined, this.educationType, undefined, undefined, true);
207
207
  }
208
208
  async getTeacherScheduleForPeriod(opts) {
209
209
  const days = await this.fetchTeacherSchedule(opts.teacherId, opts.period);
210
210
  const schedules = new Map();
211
211
  schedules.set(opts.period, days);
212
- return new Schedule(opts.teacherId, schedules, opts.period, this.educationType);
212
+ return new Schedule(opts.teacherId, schedules, opts.period, this.educationType, undefined, undefined, true);
213
213
  }
214
214
  async getTeacherInfo(teacherId) {
215
215
  const url = `${BASE}/index/techtt/tech/${teacherId}`;
package/dist/tt/parse.js CHANGED
@@ -134,6 +134,7 @@ function parseTransferDiv(div) {
134
134
  const parts = divHtml.split(/<br\s*\/?>/);
135
135
  const lastPart = parts[parts.length - 1]?.replace(/<[^>]*>/g, "").trim();
136
136
  const transfer = { targetDate, fromDate, fromSlot, subject };
137
+ const subgroupMatch = divText.match(/(\d+)\s*подгруппа/);
137
138
  return {
138
139
  transfer,
139
140
  entry: {
@@ -142,6 +143,7 @@ function parseTransferDiv(div) {
142
143
  type: typeMatch?.[1] ?? "",
143
144
  weeks: { from: 0, to: 0 },
144
145
  teacher: parseTeacher(lastPart ?? ""),
146
+ subgroup: subgroupMatch ? parseInt(subgroupMatch[1]) : undefined,
145
147
  transfer,
146
148
  },
147
149
  };
@@ -163,6 +165,47 @@ function parseSubstitutionDiv(div) {
163
165
  teacher = parseTeacher(teacherMatch[1].trim());
164
166
  return { date, room, teacher };
165
167
  }
168
+ function parseSubstituteForDiv(div) {
169
+ const divText = text(div);
170
+ const divHtml = div.innerHTML ?? "";
171
+ const m = divText.match(/(\d{2})\.(\d{2})\.(\d{4})\s*замена\s*вместо:/);
172
+ if (!m)
173
+ return null;
174
+ const date = parseDate(m[1], m[2], m[3]);
175
+ // Original teacher: first blue span (right after "замена вместо:")
176
+ const origTeacherMatch = divHtml.match(/замена\s*вместо:\s*<\/b><\/span>\s*<span[^>]*>([^<]+)<\/span>/);
177
+ const originalTeacher = origTeacherMatch
178
+ ? parseTeacher(origTeacherMatch[1].trim())
179
+ : { name: "" };
180
+ // Subject: second blue span
181
+ const subjectEl = div.querySelectorAll('span[style*="color: blue"]');
182
+ let subject = "";
183
+ for (const el of subjectEl) {
184
+ const t = text(el);
185
+ if (t && t !== origTeacherMatch?.[1]?.trim()) {
186
+ subject = t;
187
+ break;
188
+ }
189
+ }
190
+ if (!subject)
191
+ return null;
192
+ const roomMatch = divHtml.match(/(?:<br\s*\/?>)\s*([А-Яа-яA-Za-z]-\d+)/);
193
+ const typeMatch = divText.match(/\((лк|пр|лб|зач|экз|зчО|кр|конс)\)/);
194
+ const groupsMatch = divHtml.match(/\((?:лк|пр|лб|зач|экз|зчО|кр|конс)\)\s*(?:<br\s*\/?>)\s*([^<]+?)(?:\s*<i|$)/);
195
+ const subgroupMatch = divText.match(/(\d+)\s*подгруппа/);
196
+ return {
197
+ entry: {
198
+ room: roomMatch?.[1] ?? "",
199
+ subject,
200
+ type: typeMatch?.[1] ?? "",
201
+ weeks: { from: 0, to: 0 },
202
+ teacher: { name: "" },
203
+ groups: groupsMatch?.[1]?.trim() ?? "",
204
+ subgroup: subgroupMatch ? parseInt(subgroupMatch[1]) : undefined,
205
+ substituteFor: { date, originalTeacher },
206
+ },
207
+ };
208
+ }
166
209
  function parseSemesterEntry(el) {
167
210
  const td = el.querySelector("td") ?? el;
168
211
  const fullHtml = td.innerHTML ?? "";
@@ -319,6 +362,15 @@ function parseTeacherSemesterEntry(el) {
319
362
  return result.entry;
320
363
  }
321
364
  }
365
+ // Check for "замена вместо:" (substitute lesson for another teacher)
366
+ for (const div of redDivs) {
367
+ const result = parseSubstituteForDiv(div);
368
+ if (result) {
369
+ if (possibleChanges)
370
+ result.entry.possibleChanges = true;
371
+ return result.entry;
372
+ }
373
+ }
322
374
  const substitutions = [];
323
375
  for (const div of redDivs) {
324
376
  const sub = parseSubstitutionDiv(div);
@@ -5,12 +5,14 @@ export declare class Schedule {
5
5
  readonly groupId: number;
6
6
  readonly scheduleMap: Map<number, FullScheduleDay[]>;
7
7
  readonly educationType: EducationType;
8
+ /** Whether this is a teacher schedule (affects substitution handling). */
9
+ readonly isTeacherSchedule: boolean;
8
10
  /** List of holidays to exclude from schedule queries. Pass `[]` to disable. */
9
11
  readonly holidays: Holiday[];
10
12
  /** Government decree day-off transfers (Постановление Правительства). */
11
13
  readonly holidayTransfers: HolidayTransfer[];
12
14
  private _period?;
13
- constructor(groupId: number, scheduleMap: Map<number, FullScheduleDay[]>, period?: Period, educationType?: EducationType, holidays?: Holiday[] | null, holidayTransfers?: HolidayTransfer[]);
15
+ constructor(groupId: number, scheduleMap: Map<number, FullScheduleDay[]>, period?: Period, educationType?: EducationType, holidays?: Holiday[] | null, holidayTransfers?: HolidayTransfer[], isTeacherSchedule?: boolean);
14
16
  /** Current (or fixed) period for this schedule. */
15
17
  get period(): Period;
16
18
  /** Days for the current period. */
@@ -3,15 +3,18 @@ export class Schedule {
3
3
  groupId;
4
4
  scheduleMap;
5
5
  educationType;
6
+ /** Whether this is a teacher schedule (affects substitution handling). */
7
+ isTeacherSchedule;
6
8
  /** List of holidays to exclude from schedule queries. Pass `[]` to disable. */
7
9
  holidays;
8
10
  /** Government decree day-off transfers (Постановление Правительства). */
9
11
  holidayTransfers;
10
12
  _period;
11
- constructor(groupId, scheduleMap, period, educationType, holidays, holidayTransfers) {
13
+ constructor(groupId, scheduleMap, period, educationType, holidays, holidayTransfers, isTeacherSchedule) {
12
14
  this.groupId = groupId;
13
15
  this.scheduleMap = scheduleMap;
14
16
  this.educationType = educationType ?? 1 /* EducationType.HigherEducation */;
17
+ this.isTeacherSchedule = isTeacherSchedule ?? false;
15
18
  this._period = period;
16
19
  this.holidays = holidays ?? RUSSIAN_HOLIDAYS;
17
20
  this.holidayTransfers = holidayTransfers ?? [];
@@ -73,14 +76,14 @@ export class Schedule {
73
76
  const dayName = getWeekdayName(weekday);
74
77
  for (const d of days) {
75
78
  if (d.weekday.toLowerCase() === dayName.toLowerCase() && d.date) {
76
- lessons.push(...slotsToLessons(d.slots, d.date));
79
+ lessons.push(...slotsToLessons(d.slots, d.date, { isTeacherSchedule: this.isTeacherSchedule }));
77
80
  }
78
81
  }
79
82
  return lessons.sort(sortLessons);
80
83
  }
81
84
  const slots = this.getSlotsForWeekday(weekday, days, opts);
82
85
  const date = this.getDateForWeekday(weekday, period, opts?.week);
83
- return slotsToLessons(slots, date);
86
+ return slotsToLessons(slots, date, { isTeacherSchedule: this.isTeacherSchedule });
84
87
  }
85
88
  forDate(date, opts) {
86
89
  if (isHoliday(date, this.holidays, this.holidayTransfers))
@@ -91,7 +94,7 @@ export class Schedule {
91
94
  for (const [, d] of this.scheduleMap) {
92
95
  for (const day of d) {
93
96
  if (day.date && Schedule.isSameDay(day.date, date)) {
94
- lessons.push(...slotsToLessons(day.slots, date));
97
+ lessons.push(...slotsToLessons(day.slots, date, { isTeacherSchedule: this.isTeacherSchedule }));
95
98
  }
96
99
  }
97
100
  }
@@ -109,7 +112,7 @@ export class Schedule {
109
112
  week,
110
113
  date,
111
114
  });
112
- lessons.push(...slotsToLessons(slots, date));
115
+ lessons.push(...slotsToLessons(slots, date, { isTeacherSchedule: this.isTeacherSchedule }));
113
116
  }
114
117
  // 3. Suppress lessons that were transferred away from this date
115
118
  const transfers = collectTransfers(semesterDays);
@@ -18,6 +18,13 @@ export interface Substitution {
18
18
  /** New teacher, if changed. */
19
19
  teacher?: Teacher;
20
20
  }
21
+ /** Info about a lesson this teacher is substituting for another teacher. */
22
+ export interface SubstituteForInfo {
23
+ /** The date this substitute lesson takes place. */
24
+ date: Date;
25
+ /** The original teacher being replaced. */
26
+ originalTeacher: Teacher;
27
+ }
21
28
  /** Info about a lesson transferred from another date/slot. */
22
29
  export interface TransferInfo {
23
30
  /** Date when this lesson takes place (target). */
@@ -43,6 +50,8 @@ export interface ScheduleEntry {
43
50
  substitutions?: Substitution[];
44
51
  /** If this entry is a transferred lesson (перенос). */
45
52
  transfer?: TransferInfo;
53
+ /** If this entry is a substitute lesson (замена вместо). */
54
+ substituteFor?: SubstituteForInfo;
46
55
  /** Whether this entry is marked as potentially changing (class="want"). */
47
56
  possibleChanges?: boolean;
48
57
  }
@@ -86,6 +95,8 @@ export interface Lesson {
86
95
  originalTeacher?: Teacher;
87
96
  /** Transfer info if this lesson was moved from another date/slot. */
88
97
  transfer?: TransferInfo;
98
+ /** If this lesson is a substitute (замена вместо), the original teacher. */
99
+ substituteFor?: SubstituteForInfo;
89
100
  /** Whether this lesson is marked as potentially changing. */
90
101
  possibleChanges?: boolean;
91
102
  }
@@ -38,7 +38,9 @@ export declare function filterSlots(slots: FullScheduleSlot[], opts?: {
38
38
  week?: number;
39
39
  date?: Date;
40
40
  }): FullScheduleSlot[];
41
- export declare function slotsToLessons(slots: FullScheduleSlot[], date: Date): Lesson[];
41
+ export declare function slotsToLessons(slots: FullScheduleSlot[], date: Date, opts?: {
42
+ isTeacherSchedule?: boolean;
43
+ }): Lesson[];
42
44
  /** Collect all transfer entries from schedule days. */
43
45
  export declare function collectTransfers(days: FullScheduleDay[]): ScheduleEntry[];
44
46
  /** Remove lessons whose source date/slot match a transfer. */
package/dist/tt/utils.js CHANGED
@@ -101,6 +101,12 @@ function filterEntries(entries, opts) {
101
101
  return false;
102
102
  return isSameDay(e.transfer.targetDate, opts.date);
103
103
  }
104
+ // Substitute-for entries: only include when the query date matches
105
+ if (e.substituteFor) {
106
+ if (!opts?.date)
107
+ return false;
108
+ return isSameDay(e.substituteFor.date, opts.date);
109
+ }
104
110
  if (opts?.subgroup && e.subgroup && e.subgroup !== opts.subgroup) {
105
111
  return false;
106
112
  }
@@ -135,7 +141,7 @@ function makeLessonTime(date, time) {
135
141
  d.setHours(time.hours, time.minutes, 0, 0);
136
142
  return { date: d, hours: time.hours, minutes: time.minutes };
137
143
  }
138
- export function slotsToLessons(slots, date) {
144
+ export function slotsToLessons(slots, date, opts) {
139
145
  const lessons = [];
140
146
  for (const slot of slots) {
141
147
  for (const entry of slot.entries) {
@@ -152,6 +158,10 @@ export function slotsToLessons(slots, date) {
152
158
  room = sub.room;
153
159
  }
154
160
  if (sub.teacher) {
161
+ // On teacher schedules, a teacher substitution means another teacher
162
+ // is taking over — exclude the lesson entirely.
163
+ if (opts?.isTeacherSchedule)
164
+ continue;
155
165
  originalTeacher = teacher;
156
166
  teacher = sub.teacher;
157
167
  }
@@ -172,6 +182,7 @@ export function slotsToLessons(slots, date) {
172
182
  originalRoom,
173
183
  originalTeacher,
174
184
  transfer: entry.transfer,
185
+ substituteFor: entry.substituteFor,
175
186
  possibleChanges: entry.possibleChanges,
176
187
  });
177
188
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chuvsu-js",
3
- "version": "2.5.0",
3
+ "version": "2.6.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",