chuvsu-js 2.3.0 → 2.4.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/README.md +2 -2
- package/dist/browser.d.ts +2 -2
- package/dist/index.d.ts +4 -4
- package/dist/index.js +1 -1
- package/dist/tt/parse.js +73 -0
- package/dist/tt/schedule.js +7 -1
- package/dist/tt/types.d.ts +34 -0
- package/dist/tt/utils.d.ts +6 -1
- package/dist/tt/utils.js +58 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,8 +6,8 @@ Node.js библиотека для работы с порталами ЧувГ
|
|
|
6
6
|
- **lk.chuvsu.ru** — личный кабинет студента (персональные данные)
|
|
7
7
|
|
|
8
8
|
> [!WARNING]
|
|
9
|
-
> Пока не доработана, код
|
|
10
|
-
> Не надейтесь на правильный вывод
|
|
9
|
+
> Пока не доработана, код и архитектура говно и надо бы его 10 раз переписать.
|
|
10
|
+
> Не надейтесь на правильный вывод расписания, но впринципе я не замечал пока расхождений.
|
|
11
11
|
|
|
12
12
|
## Установка
|
|
13
13
|
|
package/dist/browser.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
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
|
-
export type { Time, WeekRange, Teacher
|
|
5
|
-
export type { Faculty, Group, ScheduleEntry, FullScheduleSlot, FullScheduleDay, LessonTimeSlot, Lesson, LessonTime, SemesterWeek, } from "./tt/types.js";
|
|
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";
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ export { Schedule } from "./tt/schedule.js";
|
|
|
4
4
|
export type { CacheEntry } from "./common/cache.js";
|
|
5
5
|
export { getCurrentPeriod, isSessionPeriod, getSemesterStart, getSemesterWeeks, getWeekNumber, getWeekdayName, getTimeSlots, getLessonNumber, getAdjacentSemester, isHoliday, RUSSIAN_HOLIDAYS, } from "./tt/utils.js";
|
|
6
6
|
export type { Holiday } from "./tt/utils.js";
|
|
7
|
-
export { Period, EducationType, AuthError, ParseError } from "./common/types.js";
|
|
8
|
-
export type { Time, WeekRange, Teacher
|
|
9
|
-
export type { PersonalData
|
|
10
|
-
export type { Faculty, Group, ScheduleEntry, FullScheduleSlot, FullScheduleDay, LessonTimeSlot, Lesson, LessonTime, SemesterWeek, TtClientOptions, CacheConfig, } from "./tt/types.js";
|
|
7
|
+
export { Period, EducationType, AuthError, ParseError, } from "./common/types.js";
|
|
8
|
+
export type { Time, WeekRange, Teacher } from "./common/types.js";
|
|
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";
|
package/dist/index.js
CHANGED
|
@@ -2,4 +2,4 @@ export { LkClient } from "./lk/client.js";
|
|
|
2
2
|
export { TtClient } from "./tt/client.js";
|
|
3
3
|
export { Schedule } from "./tt/schedule.js";
|
|
4
4
|
export { getCurrentPeriod, isSessionPeriod, getSemesterStart, getSemesterWeeks, getWeekNumber, getWeekdayName, getTimeSlots, getLessonNumber, getAdjacentSemester, isHoliday, RUSSIAN_HOLIDAYS, } from "./tt/utils.js";
|
|
5
|
-
export { AuthError, ParseError } from "./common/types.js";
|
|
5
|
+
export { AuthError, ParseError, } from "./common/types.js";
|
package/dist/tt/parse.js
CHANGED
|
@@ -109,12 +109,83 @@ function parseSemesterSchedule(doc) {
|
|
|
109
109
|
}
|
|
110
110
|
return days;
|
|
111
111
|
}
|
|
112
|
+
function parseDate(dd, mm, yyyy) {
|
|
113
|
+
return new Date(parseInt(yyyy), parseInt(mm) - 1, parseInt(dd));
|
|
114
|
+
}
|
|
115
|
+
function parseTransferDiv(div) {
|
|
116
|
+
const divText = text(div);
|
|
117
|
+
const divHtml = div.innerHTML ?? "";
|
|
118
|
+
const m = divText.match(/(\d{2})\.(\d{2})\.(\d{4})\s*перенос\s*c\s*(\d{2})\.(\d{2})\.(\d{4})\s*\((\d+)\s*пара\)/);
|
|
119
|
+
if (!m)
|
|
120
|
+
return null;
|
|
121
|
+
const targetDate = parseDate(m[1], m[2], m[3]);
|
|
122
|
+
const fromDate = parseDate(m[4], m[5], m[6]);
|
|
123
|
+
const fromSlot = parseInt(m[7]);
|
|
124
|
+
const subjectEl = div.querySelector('span[style*="color: blue"]');
|
|
125
|
+
const subject = subjectEl ? text(subjectEl) : "";
|
|
126
|
+
if (!subject)
|
|
127
|
+
return null;
|
|
128
|
+
const roomMatch = divHtml.match(/([А-Яа-яA-Za-z]-\d+)/);
|
|
129
|
+
const typeMatch = divText.match(/\((лк|пр|лб|зач|экз|зчО|кр|конс)\)/);
|
|
130
|
+
// Teacher: last text line before closing </div>
|
|
131
|
+
const parts = divHtml.split(/<br\s*\/?>/);
|
|
132
|
+
const lastPart = parts[parts.length - 1]?.replace(/<[^>]*>/g, "").trim();
|
|
133
|
+
const transfer = { targetDate, fromDate, fromSlot, subject };
|
|
134
|
+
return {
|
|
135
|
+
transfer,
|
|
136
|
+
entry: {
|
|
137
|
+
room: roomMatch?.[1] ?? "",
|
|
138
|
+
subject,
|
|
139
|
+
type: typeMatch?.[1] ?? "",
|
|
140
|
+
weeks: { from: 0, to: 0 },
|
|
141
|
+
teacher: parseTeacher(lastPart ?? ""),
|
|
142
|
+
transfer,
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
function parseSubstitutionDiv(div) {
|
|
147
|
+
const divText = text(div);
|
|
148
|
+
const divHtml = div.innerHTML ?? "";
|
|
149
|
+
const m = divText.match(/(\d{2})\.(\d{2})\.(\d{4})\s*замена\s*на:/);
|
|
150
|
+
if (!m)
|
|
151
|
+
return null;
|
|
152
|
+
const date = parseDate(m[1], m[2], m[3]);
|
|
153
|
+
let room;
|
|
154
|
+
let teacher;
|
|
155
|
+
const roomMatch = divHtml.match(/Аудитория:\s*<span[^>]*>([^<]+)<\/span>/);
|
|
156
|
+
if (roomMatch)
|
|
157
|
+
room = roomMatch[1].trim();
|
|
158
|
+
const teacherMatch = divHtml.match(/Преподаватель:\s*<span[^>]*>([^<]+)<\/span>/);
|
|
159
|
+
if (teacherMatch)
|
|
160
|
+
teacher = parseTeacher(teacherMatch[1].trim());
|
|
161
|
+
return { date, room, teacher };
|
|
162
|
+
}
|
|
112
163
|
function parseSemesterEntry(el) {
|
|
113
164
|
const td = el.querySelector("td") ?? el;
|
|
114
165
|
const fullHtml = td.innerHTML ?? "";
|
|
115
166
|
const plainText = text(td);
|
|
116
167
|
if (!plainText)
|
|
117
168
|
return null;
|
|
169
|
+
const possibleChanges = (td.getAttribute("class") ?? "").includes("want") || undefined;
|
|
170
|
+
// Detect red-bordered divs (transfers / substitutions)
|
|
171
|
+
const redDivs = td.querySelectorAll('div[style*="border: 2px solid red"]');
|
|
172
|
+
// Check for transfer (перенос) — the whole entry is the transferred lesson
|
|
173
|
+
for (const div of redDivs) {
|
|
174
|
+
const result = parseTransferDiv(div);
|
|
175
|
+
if (result) {
|
|
176
|
+
if (possibleChanges)
|
|
177
|
+
result.entry.possibleChanges = true;
|
|
178
|
+
return result.entry;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Collect substitutions (замена на)
|
|
182
|
+
const substitutions = [];
|
|
183
|
+
for (const div of redDivs) {
|
|
184
|
+
const sub = parseSubstitutionDiv(div);
|
|
185
|
+
if (sub)
|
|
186
|
+
substitutions.push(sub);
|
|
187
|
+
}
|
|
188
|
+
// Parse regular entry
|
|
118
189
|
const subjectEl = td.querySelector('span[style*="color: blue"]');
|
|
119
190
|
const subject = subjectEl ? text(subjectEl) : "";
|
|
120
191
|
if (!subject)
|
|
@@ -133,6 +204,8 @@ function parseSemesterEntry(el) {
|
|
|
133
204
|
teacher: parseTeacher(teacherMatch?.[1] ?? ""),
|
|
134
205
|
subgroup: subgroupMatch ? parseInt(subgroupMatch[1]) : undefined,
|
|
135
206
|
weekParity,
|
|
207
|
+
substitutions: substitutions.length > 0 ? substitutions : undefined,
|
|
208
|
+
possibleChanges,
|
|
136
209
|
};
|
|
137
210
|
}
|
|
138
211
|
// --- Session schedule parsing (date-based, specific dates) ---
|
package/dist/tt/schedule.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getCurrentPeriod, isSessionPeriod, getWeekdayName, getMonday, getSemesterStart, getSemesterWeeks, getWeekNumber, getAdjacentSemester, filterSlots, slotsToLessons, sortLessons, isHoliday, RUSSIAN_HOLIDAYS, } from "./utils.js";
|
|
1
|
+
import { getCurrentPeriod, isSessionPeriod, getWeekdayName, getMonday, getSemesterStart, getSemesterWeeks, getWeekNumber, getAdjacentSemester, filterSlots, slotsToLessons, sortLessons, isHoliday, collectTransfers, suppressTransferredLessons, RUSSIAN_HOLIDAYS, } from "./utils.js";
|
|
2
2
|
export class Schedule {
|
|
3
3
|
groupId;
|
|
4
4
|
scheduleMap;
|
|
@@ -104,9 +104,15 @@ export class Schedule {
|
|
|
104
104
|
const slots = this.getSlotsForWeekday(weekday, semesterDays, {
|
|
105
105
|
subgroup: opts?.subgroup,
|
|
106
106
|
week,
|
|
107
|
+
date,
|
|
107
108
|
});
|
|
108
109
|
lessons.push(...slotsToLessons(slots, date));
|
|
109
110
|
}
|
|
111
|
+
// 3. Suppress lessons that were transferred away from this date
|
|
112
|
+
const transfers = collectTransfers(semesterDays);
|
|
113
|
+
if (transfers.length > 0) {
|
|
114
|
+
return suppressTransferredLessons(lessons, transfers, date).sort(sortLessons);
|
|
115
|
+
}
|
|
110
116
|
}
|
|
111
117
|
return lessons.sort(sortLessons);
|
|
112
118
|
}
|
package/dist/tt/types.d.ts
CHANGED
|
@@ -9,6 +9,26 @@ export interface Group {
|
|
|
9
9
|
specialty?: string;
|
|
10
10
|
profile?: string;
|
|
11
11
|
}
|
|
12
|
+
/** A date-specific substitution (room and/or teacher change). */
|
|
13
|
+
export interface Substitution {
|
|
14
|
+
/** The date this substitution applies to. */
|
|
15
|
+
date: Date;
|
|
16
|
+
/** New room, if changed. */
|
|
17
|
+
room?: string;
|
|
18
|
+
/** New teacher, if changed. */
|
|
19
|
+
teacher?: Teacher;
|
|
20
|
+
}
|
|
21
|
+
/** Info about a lesson transferred from another date/slot. */
|
|
22
|
+
export interface TransferInfo {
|
|
23
|
+
/** Date when this lesson takes place (target). */
|
|
24
|
+
targetDate: Date;
|
|
25
|
+
/** Original date the lesson was moved from. */
|
|
26
|
+
fromDate: Date;
|
|
27
|
+
/** Original slot number (пара). */
|
|
28
|
+
fromSlot: number;
|
|
29
|
+
/** Subject name (used to match the source entry). */
|
|
30
|
+
subject: string;
|
|
31
|
+
}
|
|
12
32
|
export interface ScheduleEntry {
|
|
13
33
|
room: string;
|
|
14
34
|
subject: string;
|
|
@@ -17,6 +37,12 @@ export interface ScheduleEntry {
|
|
|
17
37
|
teacher: Teacher;
|
|
18
38
|
subgroup?: number;
|
|
19
39
|
weekParity?: "even" | "odd";
|
|
40
|
+
/** Date-specific substitutions (замена на). */
|
|
41
|
+
substitutions?: Substitution[];
|
|
42
|
+
/** If this entry is a transferred lesson (перенос). */
|
|
43
|
+
transfer?: TransferInfo;
|
|
44
|
+
/** Whether this entry is marked as potentially changing (class="want"). */
|
|
45
|
+
possibleChanges?: boolean;
|
|
20
46
|
}
|
|
21
47
|
export interface FullScheduleSlot {
|
|
22
48
|
number: number;
|
|
@@ -50,6 +76,14 @@ export interface Lesson {
|
|
|
50
76
|
weeks: WeekRange;
|
|
51
77
|
subgroup?: number;
|
|
52
78
|
weekParity?: "even" | "odd";
|
|
79
|
+
/** If a substitution was applied, the original room. */
|
|
80
|
+
originalRoom?: string;
|
|
81
|
+
/** If a substitution was applied, the original teacher. */
|
|
82
|
+
originalTeacher?: Teacher;
|
|
83
|
+
/** Transfer info if this lesson was moved from another date/slot. */
|
|
84
|
+
transfer?: TransferInfo;
|
|
85
|
+
/** Whether this lesson is marked as potentially changing. */
|
|
86
|
+
possibleChanges?: boolean;
|
|
53
87
|
}
|
|
54
88
|
export interface SemesterWeek {
|
|
55
89
|
week: number;
|
package/dist/tt/utils.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FullScheduleSlot, SemesterWeek, Lesson, LessonTimeSlot } from "./types.js";
|
|
1
|
+
import type { FullScheduleDay, FullScheduleSlot, ScheduleEntry, SemesterWeek, Lesson, LessonTimeSlot } from "./types.js";
|
|
2
2
|
import { Period, EducationType } from "../common/types.js";
|
|
3
3
|
import type { Time } from "../common/types.js";
|
|
4
4
|
export declare function getCurrentPeriod(opts?: {
|
|
@@ -36,8 +36,13 @@ export declare function getWeekNumber(opts: {
|
|
|
36
36
|
export declare function filterSlots(slots: FullScheduleSlot[], opts?: {
|
|
37
37
|
subgroup?: number;
|
|
38
38
|
week?: number;
|
|
39
|
+
date?: Date;
|
|
39
40
|
}): FullScheduleSlot[];
|
|
40
41
|
export declare function slotsToLessons(slots: FullScheduleSlot[], date: Date): Lesson[];
|
|
42
|
+
/** Collect all transfer entries from schedule days. */
|
|
43
|
+
export declare function collectTransfers(days: FullScheduleDay[]): ScheduleEntry[];
|
|
44
|
+
/** Remove lessons whose source date/slot match a transfer. */
|
|
45
|
+
export declare function suppressTransferredLessons(lessons: Lesson[], transfers: ScheduleEntry[], date: Date): Lesson[];
|
|
41
46
|
export declare function getTimeSlots(educationType: EducationType): LessonTimeSlot[];
|
|
42
47
|
export declare function getLessonNumber(time: Time, educationType: EducationType): number;
|
|
43
48
|
export declare function getAdjacentSemester(session: Period): Period;
|
package/dist/tt/utils.js
CHANGED
|
@@ -88,8 +88,19 @@ export function getWeekNumber(opts) {
|
|
|
88
88
|
const diff = targetMonday.getTime() - startMonday.getTime();
|
|
89
89
|
return Math.floor(diff / (7 * 24 * 60 * 60 * 1000));
|
|
90
90
|
}
|
|
91
|
+
function isSameDay(a, b) {
|
|
92
|
+
return (a.getFullYear() === b.getFullYear() &&
|
|
93
|
+
a.getMonth() === b.getMonth() &&
|
|
94
|
+
a.getDate() === b.getDate());
|
|
95
|
+
}
|
|
91
96
|
function filterEntries(entries, opts) {
|
|
92
97
|
return entries.filter((e) => {
|
|
98
|
+
// Transfer entries: only include when the query date matches the target date
|
|
99
|
+
if (e.transfer) {
|
|
100
|
+
if (!opts?.date)
|
|
101
|
+
return false;
|
|
102
|
+
return isSameDay(e.transfer.targetDate, opts.date);
|
|
103
|
+
}
|
|
93
104
|
if (opts?.subgroup && e.subgroup && e.subgroup !== opts.subgroup) {
|
|
94
105
|
return false;
|
|
95
106
|
}
|
|
@@ -110,7 +121,7 @@ function filterEntries(entries, opts) {
|
|
|
110
121
|
});
|
|
111
122
|
}
|
|
112
123
|
export function filterSlots(slots, opts) {
|
|
113
|
-
if (opts?.subgroup == null && opts?.week == null)
|
|
124
|
+
if (opts?.subgroup == null && opts?.week == null && opts?.date == null)
|
|
114
125
|
return slots;
|
|
115
126
|
return slots
|
|
116
127
|
.map((slot) => ({
|
|
@@ -128,22 +139,66 @@ export function slotsToLessons(slots, date) {
|
|
|
128
139
|
const lessons = [];
|
|
129
140
|
for (const slot of slots) {
|
|
130
141
|
for (const entry of slot.entries) {
|
|
142
|
+
let room = entry.room;
|
|
143
|
+
let teacher = entry.teacher;
|
|
144
|
+
let originalRoom;
|
|
145
|
+
let originalTeacher;
|
|
146
|
+
// Apply date-specific substitutions
|
|
147
|
+
if (entry.substitutions) {
|
|
148
|
+
const sub = entry.substitutions.find((s) => isSameDay(s.date, date));
|
|
149
|
+
if (sub) {
|
|
150
|
+
if (sub.room) {
|
|
151
|
+
originalRoom = room;
|
|
152
|
+
room = sub.room;
|
|
153
|
+
}
|
|
154
|
+
if (sub.teacher) {
|
|
155
|
+
originalTeacher = teacher;
|
|
156
|
+
teacher = sub.teacher;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
131
160
|
lessons.push({
|
|
132
161
|
number: slot.number,
|
|
133
162
|
start: makeLessonTime(date, slot.timeStart),
|
|
134
163
|
end: makeLessonTime(date, slot.timeEnd),
|
|
135
164
|
subject: entry.subject,
|
|
136
165
|
type: entry.type,
|
|
137
|
-
room
|
|
138
|
-
teacher
|
|
166
|
+
room,
|
|
167
|
+
teacher,
|
|
139
168
|
weeks: entry.weeks,
|
|
140
169
|
subgroup: entry.subgroup,
|
|
141
170
|
weekParity: entry.weekParity,
|
|
171
|
+
originalRoom,
|
|
172
|
+
originalTeacher,
|
|
173
|
+
transfer: entry.transfer,
|
|
174
|
+
possibleChanges: entry.possibleChanges,
|
|
142
175
|
});
|
|
143
176
|
}
|
|
144
177
|
}
|
|
145
178
|
return lessons;
|
|
146
179
|
}
|
|
180
|
+
/** Collect all transfer entries from schedule days. */
|
|
181
|
+
export function collectTransfers(days) {
|
|
182
|
+
const transfers = [];
|
|
183
|
+
for (const day of days) {
|
|
184
|
+
for (const slot of day.slots) {
|
|
185
|
+
for (const entry of slot.entries) {
|
|
186
|
+
if (entry.transfer)
|
|
187
|
+
transfers.push(entry);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return transfers;
|
|
192
|
+
}
|
|
193
|
+
/** Remove lessons whose source date/slot match a transfer. */
|
|
194
|
+
export function suppressTransferredLessons(lessons, transfers, date) {
|
|
195
|
+
return lessons.filter((lesson) => {
|
|
196
|
+
return !transfers.some((t) => t.transfer &&
|
|
197
|
+
isSameDay(t.transfer.fromDate, date) &&
|
|
198
|
+
t.transfer.fromSlot === lesson.number &&
|
|
199
|
+
t.transfer.subject === lesson.subject);
|
|
200
|
+
});
|
|
201
|
+
}
|
|
147
202
|
// --- Lesson time slots ---
|
|
148
203
|
const VO_TIME_SLOTS = [
|
|
149
204
|
{
|