chuvsu-js 0.1.0 → 0.3.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/lk/client.d.ts +3 -0
- package/dist/lk/client.js +15 -2
- package/dist/tt/client.d.ts +11 -6
- package/dist/tt/client.js +64 -35
- package/dist/tt/schedule.d.ts +0 -1
- package/dist/tt/schedule.js +2 -2
- package/dist/tt/types.d.ts +0 -1
- package/package.json +1 -1
package/dist/lk/client.d.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import type { PersonalData } from "./types.js";
|
|
2
2
|
export declare class LkClient {
|
|
3
3
|
private http;
|
|
4
|
+
private credentials;
|
|
4
5
|
login(opts: {
|
|
5
6
|
email: string;
|
|
6
7
|
password: string;
|
|
7
8
|
}): Promise<void>;
|
|
9
|
+
private isSessionExpired;
|
|
10
|
+
private authGet;
|
|
8
11
|
getPersonalData(): Promise<PersonalData>;
|
|
9
12
|
getGroupId(): Promise<number | null>;
|
|
10
13
|
}
|
package/dist/lk/client.js
CHANGED
|
@@ -6,14 +6,27 @@ const LOGIN_URL = `${BASE}/info/login.php`;
|
|
|
6
6
|
const STUDENT_BASE = `${BASE}/student`;
|
|
7
7
|
export class LkClient {
|
|
8
8
|
http = new HttpClient();
|
|
9
|
+
credentials = null;
|
|
9
10
|
async login(opts) {
|
|
10
11
|
const res = await this.http.post(LOGIN_URL, { email: opts.email, password: opts.password, role: "1", enter: "" }, false);
|
|
11
12
|
if (!(res.status === 302 && res.location?.includes("student"))) {
|
|
12
13
|
throw new AuthError("LK login failed");
|
|
13
14
|
}
|
|
15
|
+
this.credentials = opts;
|
|
16
|
+
}
|
|
17
|
+
isSessionExpired(body) {
|
|
18
|
+
return body.includes("login.php");
|
|
19
|
+
}
|
|
20
|
+
async authGet(url) {
|
|
21
|
+
const res = await this.http.get(url);
|
|
22
|
+
if (this.credentials && this.isSessionExpired(res.body)) {
|
|
23
|
+
await this.login(this.credentials);
|
|
24
|
+
return this.http.get(url);
|
|
25
|
+
}
|
|
26
|
+
return res;
|
|
14
27
|
}
|
|
15
28
|
async getPersonalData() {
|
|
16
|
-
const { body } = await this.
|
|
29
|
+
const { body } = await this.authGet(`${STUDENT_BASE}/personal_data.php`);
|
|
17
30
|
const vals = extractScriptValues(body, "form_personal_data");
|
|
18
31
|
return {
|
|
19
32
|
lastName: vals.fam ?? "",
|
|
@@ -32,7 +45,7 @@ export class LkClient {
|
|
|
32
45
|
};
|
|
33
46
|
}
|
|
34
47
|
async getGroupId() {
|
|
35
|
-
const { body } = await this.
|
|
48
|
+
const { body } = await this.authGet(`${STUDENT_BASE}/tt.php`);
|
|
36
49
|
const match = body.match(/tt\.chuvsu\.ru\/index\/grouptt\/gr\/(\d+)/);
|
|
37
50
|
return match ? parseInt(match[1]) : null;
|
|
38
51
|
}
|
package/dist/tt/client.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { CacheEntry } from "../common/cache.js";
|
|
2
|
-
import
|
|
2
|
+
import { Period } from "../common/types.js";
|
|
3
3
|
import type { Faculty, Group, FullScheduleDay, ScheduleFilter, Lesson, ScheduleWeekDay, TtClientOptions, CacheConfig } from "./types.js";
|
|
4
4
|
export declare class TtClient {
|
|
5
5
|
private http;
|
|
6
6
|
private educationType;
|
|
7
7
|
private cache;
|
|
8
|
+
private loginMode;
|
|
8
9
|
constructor(opts?: TtClientOptions);
|
|
9
10
|
private get pertt();
|
|
10
11
|
clearCache(category?: keyof CacheConfig): void;
|
|
@@ -15,6 +16,10 @@ export declare class TtClient {
|
|
|
15
16
|
password: string;
|
|
16
17
|
}): Promise<void>;
|
|
17
18
|
loginAsGuest(): Promise<void>;
|
|
19
|
+
private isSessionExpired;
|
|
20
|
+
private relogin;
|
|
21
|
+
private authGet;
|
|
22
|
+
private authPost;
|
|
18
23
|
getGroupSchedule(opts: {
|
|
19
24
|
groupId: number;
|
|
20
25
|
period?: Period;
|
|
@@ -30,7 +35,7 @@ export declare class TtClient {
|
|
|
30
35
|
getScheduleForDate(opts: {
|
|
31
36
|
groupId: number;
|
|
32
37
|
date: Date;
|
|
33
|
-
|
|
38
|
+
subgroup?: number;
|
|
34
39
|
period?: Period;
|
|
35
40
|
}): Promise<Lesson[]>;
|
|
36
41
|
getScheduleForWeek(opts: {
|
|
@@ -41,11 +46,11 @@ export declare class TtClient {
|
|
|
41
46
|
}): Promise<ScheduleWeekDay[]>;
|
|
42
47
|
getCurrentLesson(opts: {
|
|
43
48
|
groupId: number;
|
|
44
|
-
|
|
49
|
+
subgroup?: number;
|
|
45
50
|
}): Promise<Lesson | null>;
|
|
46
|
-
getCurrentPeriod(opts
|
|
47
|
-
|
|
48
|
-
}):
|
|
51
|
+
getCurrentPeriod(opts?: {
|
|
52
|
+
date?: Date;
|
|
53
|
+
}): Period;
|
|
49
54
|
getFaculties(): Promise<Faculty[]>;
|
|
50
55
|
getGroupsForFaculty(opts: {
|
|
51
56
|
facultyId: number;
|
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 {
|
|
4
|
+
import { parseGroupButtons, parseFacultyButtons, parseTeacherButtons, parseFullSchedule, } from "./parse.js";
|
|
5
5
|
import { filterSlots, getMonday, getWeekdayName, getWeekNumber, getSemesterStart, getSemesterWeeks, slotsToLessons, } from "./schedule.js";
|
|
6
6
|
const BASE = "https://tt.chuvsu.ru";
|
|
7
7
|
const AUTH_URL = `${BASE}/auth`;
|
|
@@ -9,6 +9,7 @@ export class TtClient {
|
|
|
9
9
|
http = new HttpClient();
|
|
10
10
|
educationType;
|
|
11
11
|
cache;
|
|
12
|
+
loginMode = null;
|
|
12
13
|
constructor(opts) {
|
|
13
14
|
this.educationType = opts?.educationType ?? 1 /* EducationType.HigherEducation */;
|
|
14
15
|
if (opts?.cache == null) {
|
|
@@ -19,7 +20,6 @@ export class TtClient {
|
|
|
19
20
|
schedule: opts.cache,
|
|
20
21
|
faculties: opts.cache,
|
|
21
22
|
groups: opts.cache,
|
|
22
|
-
currentPeriod: opts.cache,
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
25
|
else {
|
|
@@ -52,12 +52,43 @@ export class TtClient {
|
|
|
52
52
|
if (res.status !== 302) {
|
|
53
53
|
throw new AuthError("TT login failed");
|
|
54
54
|
}
|
|
55
|
+
this.loginMode = { type: "credentials", ...opts };
|
|
55
56
|
}
|
|
56
57
|
async loginAsGuest() {
|
|
57
58
|
const res = await this.http.post(AUTH_URL, { guest: "Войти гостем", hfac: "0", pertt: this.pertt }, false);
|
|
58
59
|
if (res.status !== 302) {
|
|
59
60
|
throw new AuthError("TT guest login failed");
|
|
60
61
|
}
|
|
62
|
+
this.loginMode = { type: "guest" };
|
|
63
|
+
}
|
|
64
|
+
isSessionExpired(body) {
|
|
65
|
+
return body.includes('name="wname"');
|
|
66
|
+
}
|
|
67
|
+
async relogin() {
|
|
68
|
+
if (!this.loginMode)
|
|
69
|
+
return;
|
|
70
|
+
if (this.loginMode.type === "credentials") {
|
|
71
|
+
await this.login(this.loginMode);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
await this.loginAsGuest();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async authGet(url) {
|
|
78
|
+
const res = await this.http.get(url);
|
|
79
|
+
if (this.loginMode && this.isSessionExpired(res.body)) {
|
|
80
|
+
await this.relogin();
|
|
81
|
+
return this.http.get(url);
|
|
82
|
+
}
|
|
83
|
+
return res;
|
|
84
|
+
}
|
|
85
|
+
async authPost(url, data) {
|
|
86
|
+
const res = await this.http.post(url, data);
|
|
87
|
+
if (this.loginMode && this.isSessionExpired(res.body)) {
|
|
88
|
+
await this.relogin();
|
|
89
|
+
return this.http.post(url, data);
|
|
90
|
+
}
|
|
91
|
+
return res;
|
|
61
92
|
}
|
|
62
93
|
// --- Schedule ---
|
|
63
94
|
async getGroupSchedule(opts) {
|
|
@@ -68,14 +99,10 @@ export class TtClient {
|
|
|
68
99
|
const url = `${BASE}/index/grouptt/gr/${opts.groupId}`;
|
|
69
100
|
let body;
|
|
70
101
|
if (opts.period !== undefined) {
|
|
71
|
-
({ body } = await this.
|
|
102
|
+
({ body } = await this.authPost(url, { htype: String(opts.period) }));
|
|
72
103
|
}
|
|
73
104
|
else {
|
|
74
|
-
({ body } = await this.
|
|
75
|
-
}
|
|
76
|
-
const period = parsePeriodFromPage(body);
|
|
77
|
-
if (period !== null) {
|
|
78
|
-
this.cache?.set("currentPeriod", String(opts.groupId), period);
|
|
105
|
+
({ body } = await this.authGet(url));
|
|
79
106
|
}
|
|
80
107
|
const data = parseFullSchedule(body);
|
|
81
108
|
this.cache?.set("schedule", cacheKey, data);
|
|
@@ -97,7 +124,9 @@ export class TtClient {
|
|
|
97
124
|
const semesterStart = getSemesterStart({ period });
|
|
98
125
|
const startMonday = getMonday(semesterStart);
|
|
99
126
|
const date = new Date(startMonday);
|
|
100
|
-
date.setDate(startMonday.getDate() +
|
|
127
|
+
date.setDate(startMonday.getDate() +
|
|
128
|
+
(week - 1) * 7 +
|
|
129
|
+
(weekday === 0 ? 6 : weekday - 1));
|
|
101
130
|
return date;
|
|
102
131
|
}
|
|
103
132
|
const now = new Date();
|
|
@@ -109,9 +138,7 @@ export class TtClient {
|
|
|
109
138
|
return date;
|
|
110
139
|
}
|
|
111
140
|
async getScheduleForDay(opts) {
|
|
112
|
-
const period = opts.period ??
|
|
113
|
-
(await this.getCurrentPeriod({ groupId: opts.groupId })) ??
|
|
114
|
-
1;
|
|
141
|
+
const period = opts.period ?? this.getCurrentPeriod();
|
|
115
142
|
const slots = await this.getFilteredSlots({
|
|
116
143
|
groupId: opts.groupId,
|
|
117
144
|
weekday: opts.weekday,
|
|
@@ -123,23 +150,21 @@ export class TtClient {
|
|
|
123
150
|
}
|
|
124
151
|
async getScheduleForDate(opts) {
|
|
125
152
|
const weekday = opts.date.getDay();
|
|
126
|
-
const period = opts.period ??
|
|
127
|
-
const effectiveFilter = {
|
|
128
|
-
if (
|
|
153
|
+
const period = opts.period ?? this.getCurrentPeriod({ date: opts.date });
|
|
154
|
+
const effectiveFilter = { subgroup: opts.subgroup };
|
|
155
|
+
if (effectiveFilter.week == null) {
|
|
129
156
|
effectiveFilter.week = getWeekNumber({ period, date: opts.date });
|
|
130
157
|
}
|
|
131
158
|
const slots = await this.getFilteredSlots({
|
|
132
159
|
groupId: opts.groupId,
|
|
133
160
|
weekday,
|
|
134
161
|
filter: effectiveFilter,
|
|
135
|
-
period
|
|
162
|
+
period,
|
|
136
163
|
});
|
|
137
164
|
return slotsToLessons(slots, opts.date);
|
|
138
165
|
}
|
|
139
166
|
async getScheduleForWeek(opts) {
|
|
140
|
-
const period = opts.period ??
|
|
141
|
-
(await this.getCurrentPeriod({ groupId: opts.groupId })) ??
|
|
142
|
-
1;
|
|
167
|
+
const period = opts.period ?? this.getCurrentPeriod();
|
|
143
168
|
const week = opts.week ?? getWeekNumber({ period });
|
|
144
169
|
const effectiveFilter = { ...opts.filter, week };
|
|
145
170
|
const semesterWeeks = getSemesterWeeks({ period, weekCount: week });
|
|
@@ -166,7 +191,7 @@ export class TtClient {
|
|
|
166
191
|
const lessons = await this.getScheduleForDate({
|
|
167
192
|
groupId: opts.groupId,
|
|
168
193
|
date: now,
|
|
169
|
-
|
|
194
|
+
subgroup: opts.subgroup,
|
|
170
195
|
});
|
|
171
196
|
const timeMinutes = now.getHours() * 60 + now.getMinutes();
|
|
172
197
|
for (const lesson of lessons) {
|
|
@@ -179,24 +204,28 @@ export class TtClient {
|
|
|
179
204
|
return null;
|
|
180
205
|
}
|
|
181
206
|
// --- Period ---
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
207
|
+
getCurrentPeriod(opts) {
|
|
208
|
+
const date = opts?.date ?? new Date();
|
|
209
|
+
const month = date.getMonth();
|
|
210
|
+
const day = date.getDate();
|
|
211
|
+
// Dec 25+ and Jan → Winter session (зимняя сессия)
|
|
212
|
+
if (month === 0 || (month === 11 && day >= 25))
|
|
213
|
+
return 2 /* Period.WinterSession */;
|
|
214
|
+
// Feb–May → Spring semester (весенний семестр)
|
|
215
|
+
if (month >= 1 && month <= 4)
|
|
216
|
+
return 3 /* Period.SpringSemester */;
|
|
217
|
+
// Jun–Aug → Summer session (летняя сессия)
|
|
218
|
+
if (month >= 5 && month <= 7)
|
|
219
|
+
return 4 /* Period.SummerSession */;
|
|
220
|
+
// Sep – Dec 24 → Fall semester (осенний семестр)
|
|
221
|
+
return 1 /* Period.FallSemester */;
|
|
193
222
|
}
|
|
194
223
|
// --- Search / Discovery ---
|
|
195
224
|
async getFaculties() {
|
|
196
225
|
const cached = this.cache?.get("faculties", "all");
|
|
197
226
|
if (cached)
|
|
198
227
|
return cached;
|
|
199
|
-
const { body } = await this.
|
|
228
|
+
const { body } = await this.authGet(`${BASE}/`);
|
|
200
229
|
const data = parseFacultyButtons(body);
|
|
201
230
|
this.cache?.set("faculties", "all", data);
|
|
202
231
|
return data;
|
|
@@ -206,7 +235,7 @@ export class TtClient {
|
|
|
206
235
|
const cached = this.cache?.get("groups", cacheKey);
|
|
207
236
|
if (cached)
|
|
208
237
|
return cached;
|
|
209
|
-
const { body } = await this.
|
|
238
|
+
const { body } = await this.authPost(`${BASE}/`, {
|
|
210
239
|
hfac: String(opts.facultyId),
|
|
211
240
|
pertt: this.pertt,
|
|
212
241
|
});
|
|
@@ -215,7 +244,7 @@ export class TtClient {
|
|
|
215
244
|
return data;
|
|
216
245
|
}
|
|
217
246
|
async searchGroup(opts) {
|
|
218
|
-
const { body } = await this.
|
|
247
|
+
const { body } = await this.authPost(`${BASE}/`, {
|
|
219
248
|
grname: opts.name,
|
|
220
249
|
findgr: "найти",
|
|
221
250
|
hfac: "0",
|
|
@@ -224,7 +253,7 @@ export class TtClient {
|
|
|
224
253
|
return parseGroupButtons(body);
|
|
225
254
|
}
|
|
226
255
|
async searchTeacher(opts) {
|
|
227
|
-
const { body } = await this.
|
|
256
|
+
const { body } = await this.authPost(`${BASE}/`, {
|
|
228
257
|
techname: opts.name,
|
|
229
258
|
findtech: "найти",
|
|
230
259
|
hfac: "0",
|
package/dist/tt/schedule.d.ts
CHANGED
package/dist/tt/schedule.js
CHANGED
|
@@ -11,7 +11,7 @@ export function getWeekdayName(weekday) {
|
|
|
11
11
|
return WEEKDAY_NAMES[weekday] ?? "";
|
|
12
12
|
}
|
|
13
13
|
export function filterSlots(slots, filter) {
|
|
14
|
-
if (
|
|
14
|
+
if (filter?.subgroup == null && filter?.week == null)
|
|
15
15
|
return slots;
|
|
16
16
|
return slots
|
|
17
17
|
.map((slot) => ({
|
|
@@ -25,7 +25,7 @@ function filterEntries(entries, filter) {
|
|
|
25
25
|
if (filter?.subgroup && e.subgroup && e.subgroup !== filter.subgroup) {
|
|
26
26
|
return false;
|
|
27
27
|
}
|
|
28
|
-
if (filter?.week) {
|
|
28
|
+
if (filter?.week != null) {
|
|
29
29
|
if (e.weeks.from > 0 &&
|
|
30
30
|
(filter.week < e.weeks.from || filter.week > e.weeks.to)) {
|
|
31
31
|
return false;
|
package/dist/tt/types.d.ts
CHANGED