@emiran/omu-ubys 0.1.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.cjs ADDED
@@ -0,0 +1,1347 @@
1
+ 'use strict';
2
+
3
+ var cheerio = require('cheerio');
4
+ var got = require('got');
5
+ var toughCookie = require('tough-cookie');
6
+
7
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
8
+
9
+ var got__default = /*#__PURE__*/_interopDefault(got);
10
+
11
+ // src/client.ts
12
+
13
+ // src/errors.ts
14
+ var UBYSError = class extends Error {
15
+ code;
16
+ details;
17
+ constructor(message, options = {}) {
18
+ super(message, { cause: options.cause });
19
+ this.name = new.target.name;
20
+ this.code = options.code ?? "UBYS_ERROR";
21
+ this.details = options.details;
22
+ }
23
+ };
24
+ var AuthenticationError = class extends UBYSError {
25
+ constructor(message = "Authentication failed.", options = {}) {
26
+ super(message, { ...options, code: options.code ?? "AUTHENTICATION_ERROR" });
27
+ }
28
+ };
29
+ var LoginError = class extends AuthenticationError {
30
+ constructor(message = "Login failed.", options = {}) {
31
+ super(message, { ...options, code: options.code ?? "LOGIN_ERROR" });
32
+ }
33
+ };
34
+ var SessionExpiredError = class extends AuthenticationError {
35
+ constructor(message = "UBYS session expired.", options = {}) {
36
+ super(message, { ...options, code: options.code ?? "SESSION_EXPIRED" });
37
+ }
38
+ };
39
+ var NetworkError = class extends UBYSError {
40
+ constructor(message = "Network request failed.", options = {}) {
41
+ super(message, { ...options, code: options.code ?? "NETWORK_ERROR" });
42
+ }
43
+ };
44
+ var HttpError = class extends UBYSError {
45
+ statusCode;
46
+ constructor(statusCode, message = `Unexpected HTTP status: ${statusCode}`, options = {}) {
47
+ super(message, { ...options, code: options.code ?? "HTTP_ERROR" });
48
+ this.statusCode = statusCode;
49
+ }
50
+ };
51
+ var ParseError = class extends UBYSError {
52
+ constructor(message = "Failed to parse UBYS response.", options = {}) {
53
+ super(message, { ...options, code: options.code ?? "PARSE_ERROR" });
54
+ }
55
+ };
56
+ var NotImplementedUBYSError = class extends UBYSError {
57
+ constructor(message = "This UBYS feature is planned but not implemented yet.", options = {}) {
58
+ super(message, { ...options, code: options.code ?? "NOT_IMPLEMENTED" });
59
+ }
60
+ };
61
+ var ClientClosedError = class extends UBYSError {
62
+ constructor(message = "This UBYS client has been closed.", options = {}) {
63
+ super(message, { ...options, code: options.code ?? "CLIENT_CLOSED" });
64
+ }
65
+ };
66
+
67
+ // src/http/constants.ts
68
+ var UBYS_BASE_URL = "https://ubys.omu.edu.tr";
69
+ var OMU_CAFETERIA_URL = "https://sks.omu.edu.tr/gunun-yemegi/";
70
+ var DEFAULT_HEADERS = {
71
+ "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
72
+ "accept-language": "tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7",
73
+ "accept-encoding": "gzip, deflate, br",
74
+ connection: "keep-alive",
75
+ "sec-ch-ua": '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
76
+ "sec-ch-ua-mobile": "?0",
77
+ "sec-ch-ua-platform": '"Windows"'
78
+ };
79
+ var AJAX_HEADERS = {
80
+ "x-requested-with": "XMLHttpRequest"
81
+ };
82
+
83
+ // src/auth/login.ts
84
+ var CSRF_TOKEN_PATTERN = /name="__RequestVerificationToken".*?value="([^"]+)"/;
85
+ function extractCsrfToken(html) {
86
+ const match = html.match(CSRF_TOKEN_PATTERN);
87
+ if (!match?.[1]) {
88
+ throw new ParseError("CSRF token could not be extracted from the UBYS login page.");
89
+ }
90
+ return match[1];
91
+ }
92
+ async function loginWithCredentials(session, username, password) {
93
+ const homepage = await session.get("/");
94
+ session.assertOk(homepage);
95
+ const csrfToken = extractCsrfToken(homepage.body);
96
+ const response = await session.post("Account/Login", {
97
+ form: {
98
+ __RequestVerificationToken: csrfToken,
99
+ username,
100
+ password
101
+ },
102
+ headers: {
103
+ ...AJAX_HEADERS,
104
+ "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
105
+ referer: `${session.baseUrl}/`
106
+ }
107
+ });
108
+ let data;
109
+ try {
110
+ data = JSON.parse(response.body);
111
+ } catch (error) {
112
+ throw new LoginError("UBYS returned an unexpected login response.", { cause: error });
113
+ }
114
+ if (data.errorType === "error" || data.errorMessage) {
115
+ throw new LoginError(data.errorMessage ?? "Invalid username or password.", {
116
+ details: {
117
+ statusCode: response.statusCode
118
+ }
119
+ });
120
+ }
121
+ return data;
122
+ }
123
+ var UBYSSession = class {
124
+ baseUrl;
125
+ cookieJar;
126
+ client;
127
+ closed = false;
128
+ constructor(options = {}) {
129
+ this.baseUrl = options.baseUrl ?? UBYS_BASE_URL;
130
+ this.cookieJar = new toughCookie.CookieJar();
131
+ const config = {
132
+ prefixUrl: this.baseUrl,
133
+ cookieJar: this.cookieJar,
134
+ headers: {
135
+ ...DEFAULT_HEADERS,
136
+ ...options.headers
137
+ },
138
+ followRedirect: true,
139
+ resolveBodyOnly: false,
140
+ throwHttpErrors: false,
141
+ timeout: {
142
+ request: options.timeoutMs ?? 3e4
143
+ },
144
+ retry: {
145
+ limit: 1,
146
+ methods: ["GET", "POST"]
147
+ },
148
+ https: {
149
+ rejectUnauthorized: true
150
+ }
151
+ };
152
+ this.client = got__default.default.extend(config);
153
+ }
154
+ async get(pathOrUrl, options = {}) {
155
+ return this.request(pathOrUrl, { ...options, method: "GET" });
156
+ }
157
+ async post(pathOrUrl, options = {}) {
158
+ return this.request(pathOrUrl, { ...options, method: "POST" });
159
+ }
160
+ get isClosed() {
161
+ return this.closed;
162
+ }
163
+ async close() {
164
+ if (this.closed) {
165
+ return;
166
+ }
167
+ await new Promise((resolve, reject) => {
168
+ this.cookieJar.removeAllCookies((error) => {
169
+ if (error) {
170
+ reject(error);
171
+ return;
172
+ }
173
+ resolve();
174
+ });
175
+ });
176
+ this.closed = true;
177
+ }
178
+ assertOk(response, allowedStatusCodes = [200]) {
179
+ if (!allowedStatusCodes.includes(response.statusCode)) {
180
+ throw new HttpError(response.statusCode, `Unexpected HTTP status: ${response.statusCode}`, {
181
+ details: {
182
+ url: response.url
183
+ }
184
+ });
185
+ }
186
+ }
187
+ assertNotLoginRedirect(response) {
188
+ if (response.url.includes("/Account/Login")) {
189
+ throw new SessionExpiredError();
190
+ }
191
+ }
192
+ async request(pathOrUrl, options) {
193
+ this.ensureOpen();
194
+ try {
195
+ if (/^https?:\/\//.test(pathOrUrl)) {
196
+ return await this.client(pathOrUrl, options);
197
+ }
198
+ const normalizedPath = pathOrUrl.replace(/^\//, "");
199
+ return await this.client(normalizedPath, options);
200
+ } catch (error) {
201
+ throw new NetworkError("Failed to reach UBYS.", { cause: error });
202
+ }
203
+ }
204
+ ensureOpen() {
205
+ if (this.closed) {
206
+ throw new ClientClosedError();
207
+ }
208
+ }
209
+ };
210
+
211
+ // src/utils/base64.ts
212
+ function decodeBase64Json(value, context) {
213
+ try {
214
+ const json = Buffer.from(value, "base64").toString("utf8");
215
+ return JSON.parse(json);
216
+ } catch (error) {
217
+ throw new ParseError(`Failed to decode ${context}.`, { cause: error });
218
+ }
219
+ }
220
+
221
+ // src/parsers/profile.ts
222
+ var STUDENT_INFO_PATTERN = /StudentInfo\s*=\s*JSON\.parse\(Base64\.decode\(["']([^"']+)["']\)\)/;
223
+ var SAP_ID_PATTERN = /selectAcademicProgram\('([^']+)'\)/;
224
+ function extractStudentInfo(html) {
225
+ const match = html.match(STUDENT_INFO_PATTERN);
226
+ if (!match?.[1]) {
227
+ if (isLoginPage(html)) {
228
+ throw new SessionExpiredError();
229
+ }
230
+ throw new ParseError("StudentInfo could not be found in dashboard HTML.");
231
+ }
232
+ return decodeBase64Json(match[1], "StudentInfo payload");
233
+ }
234
+ function extractSapId(html) {
235
+ const match = html.match(SAP_ID_PATTERN);
236
+ return match?.[1];
237
+ }
238
+ function toUserProfile(studentNumber, studentInfo, sapId, displayName) {
239
+ const program = studentInfo.Programs?.[0];
240
+ const person = studentInfo.Person?.Person;
241
+ const fallbackName = [person?.Name, person?.Surname].filter(Boolean).join(" ").trim();
242
+ return {
243
+ name: displayName ?? fallbackName,
244
+ studentNumber,
245
+ faculty: extractFaculty(program?.UnitName),
246
+ department: program?.AcademicProgramName ?? "",
247
+ ...sapId ? { sapId } : {},
248
+ ...program?.StudentId !== void 0 ? { studentId: program.StudentId } : {},
249
+ ...program?.RecordSemester !== void 0 ? { semesterId: program.RecordSemester } : {},
250
+ ...program?.RecordYear !== void 0 ? { academicYear: program.RecordYear } : {},
251
+ ...program?.GANO !== void 0 ? { cumulativeGpa: program.GANO } : {}
252
+ };
253
+ }
254
+ function isLoginPage(html) {
255
+ return html.includes("/Account/Login") || html.toLowerCase().includes("login-form");
256
+ }
257
+ function extractFaculty(unitName) {
258
+ if (!unitName) {
259
+ return "";
260
+ }
261
+ return unitName.split(" - ")[0]?.trim() ?? unitName;
262
+ }
263
+
264
+ // src/parsers/advisor.ts
265
+ function parseAdvisor(html) {
266
+ if (isLoginPage(html)) {
267
+ throw new SessionExpiredError();
268
+ }
269
+ const $ = cheerio.load(html);
270
+ let name;
271
+ let email;
272
+ $("dt").each((_, element) => {
273
+ const label = $(element).text().trim();
274
+ const value = $(element).next("dd").text().trim();
275
+ if (!value) {
276
+ return;
277
+ }
278
+ if (!name && label.includes("Ad Soyad")) {
279
+ name = value;
280
+ }
281
+ if (!email && (label.includes("Mail") || label.includes("E-Posta"))) {
282
+ email = value.replace(/^mailto:/i, "");
283
+ }
284
+ });
285
+ if (!name) {
286
+ return null;
287
+ }
288
+ return {
289
+ name,
290
+ ...email ? { email } : {}
291
+ };
292
+ }
293
+ function parseCafeteriaMenu(html) {
294
+ const $ = cheerio.load(html);
295
+ const table = $("div.reading table.has-fixed-layout").first();
296
+ const items = [];
297
+ let date = "";
298
+ table.find("tbody tr").each((_, element) => {
299
+ const cells = $(element).find("td");
300
+ const label = cells.eq(0).text().trim();
301
+ const value = cells.eq(1).text().trim();
302
+ if (!label || !value) {
303
+ return;
304
+ }
305
+ if (label.toLowerCase() === "tarih") {
306
+ date = value;
307
+ return;
308
+ }
309
+ items.push({
310
+ name: value,
311
+ category: label
312
+ });
313
+ });
314
+ if (items.length === 0) {
315
+ return null;
316
+ }
317
+ return {
318
+ date: date || "Bugun",
319
+ items
320
+ };
321
+ }
322
+ function decodeHtmlEntities(value) {
323
+ return cheerio.load(`<div>${value}</div>`)("div").text();
324
+ }
325
+
326
+ // src/utils/parse.ts
327
+ function parseNumber(value) {
328
+ const normalized = value.trim().replace(/\s+/g, "").replace(",", ".");
329
+ if (!normalized || normalized === "-") {
330
+ return void 0;
331
+ }
332
+ const parsed = Number(normalized);
333
+ return Number.isFinite(parsed) ? parsed : void 0;
334
+ }
335
+
336
+ // src/parsers/class-details.ts
337
+ var LETTER_GRADE_PATTERN = /\b(AA|BA|BB|CB|CC|DC|DD|FD|FF)\b/i;
338
+ function parseClassDetail(html) {
339
+ if (html.includes("/Account/Login")) {
340
+ throw new SessionExpiredError();
341
+ }
342
+ const $ = cheerio.load(html);
343
+ const exams = parseExamTable($);
344
+ const instructor = parseInstructor($);
345
+ const status = parseStatus($);
346
+ const letterGradeMatch = status?.match(LETTER_GRADE_PATTERN);
347
+ const letterGrade = letterGradeMatch?.[1]?.toUpperCase();
348
+ const classAverage = parseClassAverage($);
349
+ const survey = parseSurveyRequirement($);
350
+ if (exams.length === 0 && !instructor && !status && classAverage === void 0 && !survey) {
351
+ throw new ParseError("No class detail content could be parsed from class detail page.");
352
+ }
353
+ return {
354
+ exams,
355
+ students: [],
356
+ ...instructor ? { instructor } : {},
357
+ ...classAverage !== void 0 ? { classAverage } : {},
358
+ ...status ? { status } : {},
359
+ ...letterGrade ? { letterGrade } : {},
360
+ ...survey ? { survey } : {}
361
+ };
362
+ }
363
+ function parseSurveyPageStatus(html) {
364
+ if (html.includes("/Account/Login")) {
365
+ throw new SessionExpiredError();
366
+ }
367
+ const $ = cheerio.load(html);
368
+ const alertText = textOrUndefined($("div.alert").first().text());
369
+ if (!alertText) {
370
+ return void 0;
371
+ }
372
+ return {
373
+ message: alertText,
374
+ expired: /suresi dolmustur|süresi dolmuştur/i.test(alertText)
375
+ };
376
+ }
377
+ function parseStudentList(html) {
378
+ if (html.includes("/Account/Login")) {
379
+ throw new SessionExpiredError();
380
+ }
381
+ const $ = cheerio.load(html);
382
+ const students = [];
383
+ $("table.table-hover tbody tr").each((_, row) => {
384
+ const cells = $(row).find("td");
385
+ if (cells.length < 3) {
386
+ return;
387
+ }
388
+ const image = cells.eq(0).find("img");
389
+ const id = image.attr("data-user-id")?.trim();
390
+ if (!id) {
391
+ return;
392
+ }
393
+ const imageUrl = textOrUndefined(image.attr("src") ?? "");
394
+ const name = decodeText(cells.eq(1).text());
395
+ const surname = decodeText(cells.eq(2).text());
396
+ if (!name || !surname) {
397
+ return;
398
+ }
399
+ students.push({
400
+ id,
401
+ name,
402
+ surname,
403
+ ...imageUrl ? { imageUrl } : {}
404
+ });
405
+ });
406
+ return students;
407
+ }
408
+ function parseExamTable($) {
409
+ const tables = $("table").toArray();
410
+ const examTable = tables.find((table) => {
411
+ const headerText = $(table).find("thead").text();
412
+ return headerText.includes("S\u0131nav Tipi") || headerText.includes("Sinav Tipi") || headerText.includes("S\u0131nav Notu") || headerText.includes("Sinav Notu");
413
+ });
414
+ if (!examTable) {
415
+ return [];
416
+ }
417
+ const exams = [];
418
+ $(examTable).find("tbody tr").each((_, row) => {
419
+ const cells = $(row).find("td");
420
+ if (cells.length < 4) {
421
+ return;
422
+ }
423
+ const examType = decodeText(cells.eq(0).text());
424
+ const name = decodeText(cells.eq(1).text()) || examType;
425
+ const date = textOrUndefined(cells.eq(2).text());
426
+ const score = parseNumber(cells.eq(3).text());
427
+ const ranking = cells.length >= 6 ? textOrUndefined(cells.eq(5).text()) : void 0;
428
+ const average = cells.length >= 7 ? parseNumber(cells.eq(6).text()) : void 0;
429
+ if (!examType) {
430
+ return;
431
+ }
432
+ exams.push({
433
+ examType,
434
+ name,
435
+ ...date ? { date } : {},
436
+ ...score !== void 0 ? { score } : {},
437
+ ...average !== void 0 ? { average } : {},
438
+ ...ranking ? { ranking } : {}
439
+ });
440
+ });
441
+ return exams;
442
+ }
443
+ function parseInstructor($) {
444
+ const instructorElement = $("div.instructor").first();
445
+ if (instructorElement.length === 0) {
446
+ return void 0;
447
+ }
448
+ const name = textOrUndefined(instructorElement.find("div.name").first().text()) ?? textOrUndefined(instructorElement.find("a").first().text());
449
+ if (!name) {
450
+ return void 0;
451
+ }
452
+ const imageUrl = textOrUndefined(instructorElement.find("img").first().attr("src") ?? "");
453
+ return {
454
+ name,
455
+ ...imageUrl ? { imageUrl } : {}
456
+ };
457
+ }
458
+ function parseStatus($) {
459
+ const statusElement = $("div.success-status").first();
460
+ if (statusElement.length === 0) {
461
+ return void 0;
462
+ }
463
+ const directText = normalizeWhitespace(
464
+ statusElement.contents().toArray().filter((node) => node.type === "text").map((node) => node.nodeValue).join(" ")
465
+ );
466
+ if (directText) {
467
+ return directText;
468
+ }
469
+ const candidates = statusElement.find("span, strong, b, p, h1, h2, h3, h4, h5, h6").toArray().map((node) => normalizeWhitespace($(node).text())).filter(Boolean);
470
+ return candidates[0];
471
+ }
472
+ function parseSurveyRequirement($) {
473
+ const surveyLink = $("div.success-status a[href*='/MES/Application/Public/Participant']").first();
474
+ if (surveyLink.length === 0) {
475
+ return void 0;
476
+ }
477
+ const message = textOrUndefined(surveyLink.text()) ?? textOrUndefined($("div.success-status").first().text()) ?? "Survey completion is required before grades can be shown.";
478
+ const href = surveyLink.attr("href")?.trim();
479
+ return {
480
+ required: true,
481
+ message,
482
+ ...href ? { url: href } : {}
483
+ };
484
+ }
485
+ function parseClassAverage($) {
486
+ const averageSources = [
487
+ $("div.instructor div.avarage").first(),
488
+ $("div.instructor").first(),
489
+ $("body")
490
+ ];
491
+ for (const source of averageSources) {
492
+ if (source.length === 0) {
493
+ continue;
494
+ }
495
+ const match = normalizeWhitespace(source.text()).match(/(?:Not\s+)?Ortalamas[ıi]\s*:?\s*([\d.,]+)/i);
496
+ if (match?.[1]) {
497
+ const parsed = parseNumber(match[1]);
498
+ if (parsed !== void 0) {
499
+ return parsed;
500
+ }
501
+ }
502
+ }
503
+ return void 0;
504
+ }
505
+ function decodeText(value) {
506
+ return normalizeWhitespace(decodeHtmlEntities(value));
507
+ }
508
+ function textOrUndefined(value) {
509
+ const text = decodeText(value);
510
+ return text && text !== "-" ? text : void 0;
511
+ }
512
+ function normalizeWhitespace(value) {
513
+ return value.replace(/\s+/g, " ").trim();
514
+ }
515
+ var CLASS_ID_PATTERN = /classId=([^&'"\s]+)/;
516
+ function parseGrades(html) {
517
+ if (html.includes("/Account/Login")) {
518
+ throw new SessionExpiredError();
519
+ }
520
+ const $ = cheerio.load(html);
521
+ const semesters = [];
522
+ $("div.panel-default").each((_, panelElement) => {
523
+ const panel = $(panelElement);
524
+ const semesterName = panel.find("div.panel-heading").first().text().trim();
525
+ const rows = panel.find("tbody tr").toArray();
526
+ if (!semesterName || rows.length === 0) {
527
+ return;
528
+ }
529
+ const courses = [];
530
+ for (let index = 0; index < rows.length; index += 1) {
531
+ const row = rows[index];
532
+ if (!row) {
533
+ continue;
534
+ }
535
+ const cells = $(row).find("td");
536
+ if (cells.length === 0 || cells.eq(0).attr("rowspan") !== "2") {
537
+ continue;
538
+ }
539
+ const nextRow = rows[index + 1];
540
+ const exams = nextRow ? parseExamRow($, nextRow) : [];
541
+ courses.push(parseCourseRow($, row, exams));
542
+ if (nextRow) {
543
+ index += 1;
544
+ }
545
+ }
546
+ if (courses.length > 0) {
547
+ semesters.push({
548
+ name: semesterName,
549
+ courses
550
+ });
551
+ }
552
+ });
553
+ if (semesters.length === 0) {
554
+ throw new ParseError("No semester grade panels could be parsed from grades page.");
555
+ }
556
+ return semesters;
557
+ }
558
+ function parseCourseRow($, row, exams) {
559
+ const cells = $(row).find("td");
560
+ const codeCell = cells.eq(0);
561
+ const nameCell = cells.eq(1);
562
+ const creditCell = cells.eq(2);
563
+ const passingGradeCell = cells.eq(6);
564
+ const letterGradeCell = cells.eq(7);
565
+ const statusCell = cells.eq(8);
566
+ const code = codeCell.text().trim();
567
+ const linkHref = codeCell.find("a").attr("href");
568
+ const classIdMatch = linkHref?.match(CLASS_ID_PATTERN);
569
+ const classId = classIdMatch?.[1] ? decodeURIComponent(classIdMatch[1]) : void 0;
570
+ const credit = parseNumber(creditCell.text());
571
+ const passingGrade = parseNumber(passingGradeCell.text());
572
+ const letterGrade = textOrUndefined2(letterGradeCell.text());
573
+ const status = textOrUndefined2(statusCell.text());
574
+ return {
575
+ code,
576
+ name: decodeText2(nameCell.text()),
577
+ ...credit !== void 0 ? { credit } : {},
578
+ ...passingGrade !== void 0 ? { passingGrade } : {},
579
+ ...letterGrade ? { letterGrade } : {},
580
+ ...status ? { status } : {},
581
+ ...classId ? { classId } : {},
582
+ ...exams.length > 0 ? { exams } : {}
583
+ };
584
+ }
585
+ function parseExamRow($, row) {
586
+ const exams = [];
587
+ $(row).find("td").each((_, cell) => {
588
+ const text = decodeText2($(cell).text());
589
+ if (!text) {
590
+ return;
591
+ }
592
+ for (const examType of ["Vize", "Final", "Butunleme", "B\xFCt\xFCnleme"]) {
593
+ const pattern = new RegExp(`${examType}[:\\s]*(\\d+(?:[.,]\\d+)?)`, "i");
594
+ const match = text.match(pattern);
595
+ if (!match?.[1]) {
596
+ continue;
597
+ }
598
+ const score = parseNumber(match[1]);
599
+ if (score === void 0) {
600
+ continue;
601
+ }
602
+ exams.push({
603
+ examType,
604
+ name: examType,
605
+ score
606
+ });
607
+ break;
608
+ }
609
+ });
610
+ return exams;
611
+ }
612
+ function decodeText2(value) {
613
+ return decodeHtmlEntities(value).trim();
614
+ }
615
+ function textOrUndefined2(value) {
616
+ const text = decodeText2(value);
617
+ return text || void 0;
618
+ }
619
+
620
+ // src/parsers/messages.ts
621
+ function parseChatSearchResponse(responseBody) {
622
+ const raw = parsePaginatedResponse(responseBody, "chat search");
623
+ return {
624
+ pageNumber: raw.PageNumber ?? 0,
625
+ pageSize: raw.PageSize ?? 0,
626
+ totalNumberOfPages: raw.TotalNumberOfPages ?? 0,
627
+ totalNumberOfRecords: raw.TotalNumberOfRecords ?? 0,
628
+ nextPageUrl: raw.NextPageUrl ?? null,
629
+ items: (raw.Items ?? []).map(mapChat)
630
+ };
631
+ }
632
+ function parseChatRecipientSearchResponse(responseBody) {
633
+ const raw = parsePaginatedResponse(responseBody, "chat recipient search");
634
+ return {
635
+ pageNumber: raw.PageNumber ?? 0,
636
+ pageSize: raw.PageSize ?? 0,
637
+ totalNumberOfPages: raw.TotalNumberOfPages ?? 0,
638
+ totalNumberOfRecords: raw.TotalNumberOfRecords ?? 0,
639
+ nextPageUrl: raw.NextPageUrl ?? null,
640
+ items: (raw.Items ?? []).map((item) => mapChatRecipient(item))
641
+ };
642
+ }
643
+ function parseChatMessageSearchResponse(responseBody) {
644
+ const raw = parsePaginatedResponse(responseBody, "chat message search");
645
+ return {
646
+ pageNumber: raw.PageNumber ?? 0,
647
+ pageSize: raw.PageSize ?? 0,
648
+ totalNumberOfPages: raw.TotalNumberOfPages ?? 0,
649
+ totalNumberOfRecords: raw.TotalNumberOfRecords ?? 0,
650
+ nextPageUrl: raw.NextPageUrl ?? null,
651
+ items: (raw.Items ?? []).map((item) => mapChatMessageReceipt(item))
652
+ };
653
+ }
654
+ function parsePaginatedResponse(responseBody, context) {
655
+ if (responseBody.includes("/Account/Login")) {
656
+ throw new SessionExpiredError();
657
+ }
658
+ try {
659
+ return JSON.parse(responseBody);
660
+ } catch (error) {
661
+ throw new ParseError(`Failed to parse ${context} response.`, { cause: error });
662
+ }
663
+ }
664
+ function mapChat(raw) {
665
+ return {
666
+ id: raw.Id ?? "",
667
+ name: decodeText3(raw.Name),
668
+ isGroup: raw.IsGroup ?? false,
669
+ createdOn: raw.CreatedOn ?? "",
670
+ createdBy: raw.CreatedBy ?? 0,
671
+ ...raw.EncryptedCreatedBy !== void 0 ? { encryptedCreatedBy: raw.EncryptedCreatedBy } : {},
672
+ ...raw.ChatRecepientId ? { chatRecepientId: raw.ChatRecepientId } : {},
673
+ ...raw.LastMessageSendedOn ? { lastMessageSendedOn: raw.LastMessageSendedOn } : {},
674
+ ...raw.LastMessageReadedOn ? { lastMessageReadedOn: raw.LastMessageReadedOn } : {},
675
+ recipients: (raw.ChatRecepients ?? []).map(mapChatRecipient)
676
+ };
677
+ }
678
+ function mapChatRecipient(raw) {
679
+ return {
680
+ id: raw.Id ?? "",
681
+ chatId: raw.ChatId ?? "",
682
+ ...raw.UserEncryptId ? { userEncryptId: raw.UserEncryptId } : {},
683
+ userId: raw.UserId ?? 0,
684
+ personId: raw.PersonId ?? 0,
685
+ ...raw.PersonEncryptId ? { personEncryptId: raw.PersonEncryptId } : {},
686
+ createdOn: raw.CreatedOn ?? "",
687
+ createdBy: raw.CreatedBy ?? 0,
688
+ ...raw.EncryptedCreatedBy !== void 0 ? { encryptedCreatedBy: raw.EncryptedCreatedBy } : {},
689
+ ...raw.LastMessageId ? { lastMessageId: raw.LastMessageId } : {},
690
+ ...raw.LastMessageSendedOn ? { lastMessageSendedOn: raw.LastMessageSendedOn } : {},
691
+ ...raw.RecepientName ? { recipientName: decodeText3(raw.RecepientName) } : {},
692
+ ...raw.RecepientSurname ? { recipientSurname: decodeText3(raw.RecepientSurname) } : {},
693
+ chatMessages: (raw.ChatMessages ?? []).map(mapChatMessageReceipt)
694
+ };
695
+ }
696
+ function mapChatMessageReceipt(raw) {
697
+ return {
698
+ id: raw.Id ?? "",
699
+ chatRecepientId: raw.ChatRecepientId ?? "",
700
+ messageId: raw.MessageId ?? "",
701
+ ...raw.ReadedOn !== void 0 ? { readedOn: raw.ReadedOn } : {},
702
+ createdOn: raw.CreatedOn ?? "",
703
+ createdBy: raw.CreatedBy ?? 0,
704
+ ...raw.EncryptedCreatedBy !== void 0 ? { encryptedCreatedBy: raw.EncryptedCreatedBy } : {},
705
+ ...raw.SenderName ? { senderName: decodeText3(raw.SenderName) } : {},
706
+ ...raw.SenderSurname ? { senderSurname: decodeText3(raw.SenderSurname) } : {},
707
+ isEveryBodyReaded: raw.IsEveryBoydReaded ?? false,
708
+ message: mapChatMessage(raw.Message)
709
+ };
710
+ }
711
+ function mapChatMessage(raw) {
712
+ return {
713
+ id: raw?.Id ?? "",
714
+ ...raw?.ParentId !== void 0 ? { parentId: raw.ParentId } : {},
715
+ subject: decodeText3(raw?.Subject),
716
+ ...raw?.FileArchiveId !== void 0 ? { fileArchiveId: raw.FileArchiveId } : {},
717
+ createdOn: raw?.CreatedOn ?? "",
718
+ createdBy: raw?.CreatedBy ?? 0,
719
+ ...raw?.EncryptedCreatedBy !== void 0 ? { encryptedCreatedBy: raw.EncryptedCreatedBy } : {},
720
+ recipientReadedOnList: raw?.RecepientReadedOnList ?? {}
721
+ };
722
+ }
723
+ function decodeText3(value) {
724
+ return value ? decodeHtmlEntities(value).trim() : "";
725
+ }
726
+ var WEEKLY_SCHEDULE_PATTERN = /_weeklySchedulle\s*=\s*JSON\.parse\(Base64\.decode\(["']([^"']+)["']\)\)/;
727
+ var GENERIC_BASE64_JSON_PATTERN = /JSON\.parse\(Base64\.decode\(["']([^"']+)["']\)\)/;
728
+ var YEAR_PATTERN = /wcsReportYear(?:"|'|\))?[^\d]*(20\d{2})/i;
729
+ var SEMESTER_PATTERN = /wcsReportSemester(?:"|'|\))?[^\d]*(\d{4,})/i;
730
+ var DAY_MAP = {
731
+ 1: "Pazartesi",
732
+ 2: "Sali",
733
+ 3: "Carsamba",
734
+ 4: "Persembe",
735
+ 5: "Cuma",
736
+ 6: "Cumartesi",
737
+ 7: "Pazar"
738
+ };
739
+ function extractWeeklyScheduleConfig(html, now = /* @__PURE__ */ new Date()) {
740
+ const $ = cheerio.load(html);
741
+ const semesterId = extractSemesterValue($, now) ?? extractInputValue($, "#wcsReportSemester") ?? html.match(SEMESTER_PATTERN)?.[1];
742
+ const yearValue = extractSelectValue($, "#wcsReportYear") ?? extractInputValue($, "#wcsReportYear") ?? html.match(YEAR_PATTERN)?.[1];
743
+ const year = yearValue ? Number(yearValue) : inferAcademicYear(now);
744
+ if (!semesterId) {
745
+ throw new ParseError("Weekly schedule semester id could not be found in dashboard HTML.");
746
+ }
747
+ if (!Number.isFinite(year)) {
748
+ throw new ParseError("Weekly schedule year could not be determined from dashboard HTML.");
749
+ }
750
+ return {
751
+ semesterId,
752
+ year
753
+ };
754
+ }
755
+ function buildWeeklyScheduleRequest(studentInfo, config) {
756
+ const program = studentInfo.Programs?.[0];
757
+ if (!program?.StudentId) {
758
+ throw new ParseError("Student program data is incomplete for weekly schedule request.");
759
+ }
760
+ return {
761
+ co: {
762
+ InstructorId: null,
763
+ SemesterId: config.semesterId,
764
+ StudentId: program.StudentId,
765
+ WeeklyScheduleType: 1,
766
+ WorkcenterId: null,
767
+ Year: config.year,
768
+ GetExamPlans: true,
769
+ IsAnnual: false
770
+ },
771
+ reportViewType: 0,
772
+ isPartial: true
773
+ };
774
+ }
775
+ function parseWeeklySchedule(responseText) {
776
+ if (responseText.includes("/Account/Login")) {
777
+ throw new SessionExpiredError();
778
+ }
779
+ const base64Value = responseText.match(WEEKLY_SCHEDULE_PATTERN)?.[1] ?? responseText.match(GENERIC_BASE64_JSON_PATTERN)?.[1];
780
+ if (!base64Value) {
781
+ throw new ParseError("Could not find weekly schedule data in response.");
782
+ }
783
+ const data = decodeBase64Json(base64Value, "weekly schedule payload");
784
+ const items = parseScheduleData(data);
785
+ if (items.length === 0) {
786
+ throw new ParseError("No weekly schedule items could be parsed from response.");
787
+ }
788
+ return items;
789
+ }
790
+ function parseScheduleData(data) {
791
+ if (Array.isArray(data)) {
792
+ return dedupeAndSort(
793
+ data.flatMap((entry) => {
794
+ if (!entry || typeof entry !== "object") {
795
+ return [];
796
+ }
797
+ return parseEventContainer(entry);
798
+ })
799
+ );
800
+ }
801
+ if (data && typeof data === "object" && "Days" in data) {
802
+ return dedupeAndSort(parseDayContainer(data));
803
+ }
804
+ return [];
805
+ }
806
+ function parseEventContainer(container) {
807
+ return (container.Events ?? []).map((event) => {
808
+ const courseName = normalizeText(event.CourseName ?? "");
809
+ if (!courseName) {
810
+ return null;
811
+ }
812
+ const dayIndex = event.DayOfWeek ?? 0;
813
+ const courseCode = extractCourseCode(courseName);
814
+ return {
815
+ day: DAY_MAP[dayIndex] ?? `Gun ${dayIndex}`,
816
+ startTime: formatTime(event.StartHour, event.StartMinute),
817
+ endTime: formatTime(event.FinishHour, event.FinishMinute),
818
+ courseName,
819
+ ...courseCode ? { courseCode } : {},
820
+ ...normalizeText(event.WorkCenterName ?? "") ? { classroom: normalizeText(event.WorkCenterName ?? "") } : {},
821
+ ...normalizeText(event.InstructorName ?? "") ? { instructor: normalizeText(event.InstructorName ?? "") } : {}
822
+ };
823
+ }).filter((item) => item !== null);
824
+ }
825
+ function parseDayContainer(container) {
826
+ return (container.Days ?? []).flatMap((day, index) => {
827
+ const dayName = DAY_MAP[index + 1] ?? `Gun ${index + 1}`;
828
+ const slots = day.Slots ?? day.slots ?? [];
829
+ return slots.map((slot) => {
830
+ const courseName = normalizeText(slot.CourseName ?? "");
831
+ if (!courseName) {
832
+ return null;
833
+ }
834
+ return {
835
+ day: dayName,
836
+ startTime: normalizeText(slot.StartTime ?? ""),
837
+ endTime: normalizeText(slot.EndTime ?? ""),
838
+ courseName,
839
+ ...normalizeText(slot.CourseCode ?? "") ? { courseCode: normalizeText(slot.CourseCode ?? "") } : {},
840
+ ...normalizeText(slot.Classroom ?? "") ? { classroom: normalizeText(slot.Classroom ?? "") } : {},
841
+ ...normalizeText(slot.Instructor ?? "") ? { instructor: normalizeText(slot.Instructor ?? "") } : {}
842
+ };
843
+ }).filter((item) => item !== null);
844
+ });
845
+ }
846
+ function dedupeAndSort(items) {
847
+ const seen = /* @__PURE__ */ new Set();
848
+ const uniqueItems = [];
849
+ for (const item of items) {
850
+ const key = [item.day, item.startTime, item.endTime, item.courseName].join("|");
851
+ if (seen.has(key)) {
852
+ continue;
853
+ }
854
+ seen.add(key);
855
+ uniqueItems.push(item);
856
+ }
857
+ return uniqueItems.sort((left, right) => {
858
+ const leftDay = getDayOrder(left.day);
859
+ const rightDay = getDayOrder(right.day);
860
+ if (leftDay !== rightDay) {
861
+ return leftDay - rightDay;
862
+ }
863
+ return left.startTime.localeCompare(right.startTime);
864
+ });
865
+ }
866
+ function extractCourseCode(courseName) {
867
+ const firstPart = courseName.split(" ")[0]?.trim();
868
+ return firstPart && firstPart.length <= 10 ? firstPart : void 0;
869
+ }
870
+ function formatTime(hour, minute) {
871
+ const safeHour = typeof hour === "number" ? hour : 0;
872
+ const safeMinute = typeof minute === "number" ? minute : 0;
873
+ return `${String(safeHour).padStart(2, "0")}:${String(safeMinute).padStart(2, "0")}`;
874
+ }
875
+ function getDayOrder(day) {
876
+ const entry = Object.entries(DAY_MAP).find(([, label]) => label === day);
877
+ return entry ? Number(entry[0]) : 99;
878
+ }
879
+ function normalizeText(value) {
880
+ return value.replace(/\s+/g, " ").trim();
881
+ }
882
+ function extractSelectValue($, selector) {
883
+ const select = $(selector).first();
884
+ if (select.length === 0) {
885
+ return void 0;
886
+ }
887
+ const selectedValue = select.find("option[selected]").first().attr("value")?.trim();
888
+ if (selectedValue) {
889
+ return selectedValue;
890
+ }
891
+ const firstValue = select.find("option").first().attr("value")?.trim();
892
+ return firstValue || void 0;
893
+ }
894
+ function extractSemesterValue($, now) {
895
+ const select = $("#wcsReportSemester").first();
896
+ if (select.length === 0) {
897
+ return void 0;
898
+ }
899
+ const selectedValue = select.find("option[selected]").first().attr("value")?.trim();
900
+ if (selectedValue) {
901
+ return selectedValue;
902
+ }
903
+ const options = select.find("option").toArray().map((option) => ({
904
+ value: $(option).attr("value")?.trim(),
905
+ label: normalizeText($(option).text()).toLowerCase()
906
+ }));
907
+ const inferredTerm = inferCurrentTerm(now);
908
+ const matchingOption = options.find((option) => option.value && option.label.includes(inferredTerm));
909
+ if (matchingOption?.value) {
910
+ return matchingOption.value;
911
+ }
912
+ return options[0]?.value || void 0;
913
+ }
914
+ function extractInputValue($, selector) {
915
+ const value = $(selector).first().attr("value")?.trim();
916
+ return value || void 0;
917
+ }
918
+ function inferAcademicYear(now) {
919
+ const year = now.getFullYear();
920
+ const month = now.getMonth();
921
+ return month >= 8 ? year : year - 1;
922
+ }
923
+ function inferCurrentTerm(now) {
924
+ const month = now.getMonth();
925
+ if (month >= 1 && month <= 6) {
926
+ return "bahar";
927
+ }
928
+ if (month === 7) {
929
+ return "yaz";
930
+ }
931
+ return "guz";
932
+ }
933
+ var TRANSCRIPT_DATA_PATTERN = /_jsm\s*=\s*JSON\.parse\(Base64\.decode\(["']([^"']+)["']\)\)/;
934
+ function parseTranscript(html) {
935
+ if (html.includes("/Account/Login")) {
936
+ throw new SessionExpiredError();
937
+ }
938
+ const scriptMatch = html.match(TRANSCRIPT_DATA_PATTERN);
939
+ if (scriptMatch?.[1]) {
940
+ const parsed = parseTranscriptScriptPayload(scriptMatch[1]);
941
+ if (parsed.length > 0) {
942
+ return parsed;
943
+ }
944
+ }
945
+ const parsedFromTable = parseTranscriptTables(html);
946
+ if (parsedFromTable.length > 0) {
947
+ return parsedFromTable;
948
+ }
949
+ throw new ParseError("No transcript data could be parsed from transcript page.");
950
+ }
951
+ function parseTranscriptScriptPayload(base64Value) {
952
+ const payload = decodeBase64Json(base64Value, "transcript payload");
953
+ const transcript = payload.Transcripts?.[0];
954
+ if (!transcript?.Semesters?.length) {
955
+ return [];
956
+ }
957
+ return transcript.Semesters.map((semester) => ({
958
+ name: normalizeText2(semester.SemesterName ?? ""),
959
+ ...typeof semester.SemesterAverage === "number" ? { gpa: semester.SemesterAverage } : {},
960
+ courses: (semester.Courses ?? []).map((course) => toTranscriptCourse(course)).filter((course) => course !== null)
961
+ })).filter((semester) => semester.name || semester.courses.length > 0);
962
+ }
963
+ function parseTranscriptTables(html) {
964
+ const $ = cheerio.load(html);
965
+ const tableElements = $("table#test, table").toArray().filter((table) => $(table).find("caption").length > 0);
966
+ return tableElements.map((table) => {
967
+ const captionText = normalizeText2($(table).find("caption").first().text());
968
+ const rows = $(table).find("tbody tr").toArray();
969
+ const courses = [];
970
+ let gpa;
971
+ for (const row of rows) {
972
+ const cells = $(row).find("td");
973
+ if (cells.length < 2) {
974
+ continue;
975
+ }
976
+ const rowText = normalizeText2($(row).text()).toLowerCase();
977
+ if (rowText.includes("ortalama") || rowText.includes("gpa")) {
978
+ for (const cell of cells.toArray()) {
979
+ const parsed = parseNumber($(cell).text());
980
+ if (parsed !== void 0 && parsed >= 0 && parsed <= 4) {
981
+ gpa = parsed;
982
+ break;
983
+ }
984
+ }
985
+ continue;
986
+ }
987
+ const code = normalizeText2(cells.eq(0).text());
988
+ const name = normalizeText2(cells.eq(1).text());
989
+ const credit = cells.length > 2 ? parseNumber(cells.eq(2).text()) : void 0;
990
+ const letterGrade = cells.length > 3 ? textOrUndefined3(cells.eq(3).text()) : void 0;
991
+ if (!code && !name) {
992
+ continue;
993
+ }
994
+ courses.push({
995
+ code,
996
+ name,
997
+ ...credit !== void 0 ? { credit } : {},
998
+ ...letterGrade ? { letterGrade } : {}
999
+ });
1000
+ }
1001
+ if (!captionText && courses.length === 0) {
1002
+ return null;
1003
+ }
1004
+ return {
1005
+ name: captionText,
1006
+ ...gpa !== void 0 ? { gpa } : {},
1007
+ courses
1008
+ };
1009
+ }).filter((semester) => semester !== null);
1010
+ }
1011
+ function toTranscriptCourse(course) {
1012
+ const code = normalizeText2(course.CourseCode ?? "");
1013
+ const name = normalizeText2(course.CourseName ?? "");
1014
+ const letterGrade = normalizeText2(course.Grade ?? "");
1015
+ if (!code && !name) {
1016
+ return null;
1017
+ }
1018
+ return {
1019
+ code,
1020
+ name,
1021
+ ...typeof course.Credit === "number" ? { credit: course.Credit } : {},
1022
+ ...letterGrade ? { letterGrade } : {}
1023
+ };
1024
+ }
1025
+ function textOrUndefined3(value) {
1026
+ const text = normalizeText2(value);
1027
+ return text || void 0;
1028
+ }
1029
+ function normalizeText2(value) {
1030
+ return decodeHtmlEntities(value).replace(/\s+/g, " ").trim();
1031
+ }
1032
+
1033
+ // src/client.ts
1034
+ var UBYSClient = class {
1035
+ session;
1036
+ credentials;
1037
+ closed = false;
1038
+ state = {
1039
+ authenticated: false
1040
+ };
1041
+ constructor(options = {}) {
1042
+ this.session = new UBYSSession(options);
1043
+ this.credentials = {
1044
+ username: options.username,
1045
+ password: options.password
1046
+ };
1047
+ }
1048
+ get isLoggedIn() {
1049
+ return this.state.authenticated;
1050
+ }
1051
+ get isClosed() {
1052
+ return this.closed;
1053
+ }
1054
+ async login(username = this.credentials.username, password = this.credentials.password) {
1055
+ if (!username || !password) {
1056
+ throw new SessionExpiredError("Username and password are required for login.", {
1057
+ code: "MISSING_CREDENTIALS"
1058
+ });
1059
+ }
1060
+ const login = await loginWithCredentials(this.session, username, password);
1061
+ const dashboardHtml = await this.fetchDashboardHtml();
1062
+ const studentInfo = extractStudentInfo(dashboardHtml);
1063
+ const sapId = extractSapId(dashboardHtml);
1064
+ const profile = toUserProfile(username, studentInfo, sapId, login.longUsername?.trim());
1065
+ this.credentials.username = username;
1066
+ this.credentials.password = password;
1067
+ this.state = {
1068
+ authenticated: true,
1069
+ studentNumber: username,
1070
+ login,
1071
+ dashboardHtml,
1072
+ studentInfo,
1073
+ profile,
1074
+ ...sapId ? { sapId } : {}
1075
+ };
1076
+ }
1077
+ async getProfile() {
1078
+ this.ensureAuthenticated();
1079
+ if (this.state.profile) {
1080
+ return this.state.profile;
1081
+ }
1082
+ const dashboardHtml = await this.fetchDashboardHtml();
1083
+ const studentInfo = extractStudentInfo(dashboardHtml);
1084
+ const sapId = extractSapId(dashboardHtml);
1085
+ const profile = toUserProfile(this.state.studentNumber ?? "", studentInfo, sapId, this.state.login?.longUsername?.trim());
1086
+ this.state.dashboardHtml = dashboardHtml;
1087
+ this.state.studentInfo = studentInfo;
1088
+ if (sapId) {
1089
+ this.state.sapId = sapId;
1090
+ } else {
1091
+ delete this.state.sapId;
1092
+ }
1093
+ this.state.profile = profile;
1094
+ return profile;
1095
+ }
1096
+ async getAdvisor() {
1097
+ this.ensureAuthenticated();
1098
+ if (!this.state.sapId) {
1099
+ await this.getProfile();
1100
+ }
1101
+ if (!this.state.sapId) {
1102
+ return null;
1103
+ }
1104
+ const response = await this.session.get("AIS/Student/Home/AdvisorInfo", {
1105
+ searchParams: {
1106
+ sapId: this.state.sapId
1107
+ },
1108
+ headers: {
1109
+ ...AJAX_HEADERS
1110
+ }
1111
+ });
1112
+ this.session.assertOk(response);
1113
+ this.session.assertNotLoginRedirect(response);
1114
+ return parseAdvisor(response.body);
1115
+ }
1116
+ async getCafeteriaMenu() {
1117
+ const response = await this.session.get(OMU_CAFETERIA_URL, {
1118
+ https: {
1119
+ rejectUnauthorized: false
1120
+ }
1121
+ });
1122
+ this.session.assertOk(response);
1123
+ return parseCafeteriaMenu(response.body);
1124
+ }
1125
+ async getMessages(options = {}) {
1126
+ this.ensureAuthenticated();
1127
+ const response = await this.session.post("Conversation/SearchChat", {
1128
+ json: {
1129
+ Keyword: options.keyword ?? "",
1130
+ PageNumber: options.pageNumber ?? 0,
1131
+ PageSize: options.pageSize ?? 10
1132
+ },
1133
+ headers: {
1134
+ ...AJAX_HEADERS,
1135
+ origin: this.session.baseUrl,
1136
+ referer: `${this.session.baseUrl}/Conversation/Index`
1137
+ }
1138
+ });
1139
+ this.session.assertOk(response);
1140
+ this.session.assertNotLoginRedirect(response);
1141
+ return parseChatSearchResponse(response.body);
1142
+ }
1143
+ async getChatRecipients(options) {
1144
+ this.ensureAuthenticated();
1145
+ const response = await this.session.post("Conversation/SearchChatRecepient", {
1146
+ json: {
1147
+ ChatIds: options.chatIds,
1148
+ PageNumber: options.pageNumber ?? 0,
1149
+ PageSize: options.pageSize ?? 20
1150
+ },
1151
+ headers: {
1152
+ ...AJAX_HEADERS,
1153
+ origin: this.session.baseUrl,
1154
+ referer: `${this.session.baseUrl}/Conversation/Index`
1155
+ }
1156
+ });
1157
+ this.session.assertOk(response);
1158
+ this.session.assertNotLoginRedirect(response);
1159
+ return parseChatRecipientSearchResponse(response.body);
1160
+ }
1161
+ async getChatMessages(options) {
1162
+ this.ensureAuthenticated();
1163
+ const response = await this.session.post("Conversation/SearchChatMessage", {
1164
+ json: {
1165
+ ChatRecepientIds: options.chatRecepientIds,
1166
+ Keyword: options.keyword ?? "",
1167
+ PageNumber: options.pageNumber ?? 0,
1168
+ PageSize: options.pageSize ?? 50
1169
+ },
1170
+ headers: {
1171
+ ...AJAX_HEADERS,
1172
+ origin: this.session.baseUrl,
1173
+ referer: `${this.session.baseUrl}/Conversation/Index`
1174
+ }
1175
+ });
1176
+ this.session.assertOk(response);
1177
+ this.session.assertNotLoginRedirect(response);
1178
+ return parseChatMessageSearchResponse(response.body);
1179
+ }
1180
+ async getGrades() {
1181
+ this.ensureAuthenticated();
1182
+ if (!this.state.sapId) {
1183
+ await this.getProfile();
1184
+ }
1185
+ const response = await this.session.get("AIS/Student/Class/Index", {
1186
+ searchParams: {
1187
+ history: "true",
1188
+ ...this.state.sapId ? { sapid: this.state.sapId } : {}
1189
+ }
1190
+ });
1191
+ this.session.assertOk(response);
1192
+ this.session.assertNotLoginRedirect(response);
1193
+ return parseGrades(response.body);
1194
+ }
1195
+ async getClassDetails(classId) {
1196
+ this.ensureAuthenticated();
1197
+ const detailResponse = await this.session.get("AIS/Student/Class/ClassDetail", {
1198
+ searchParams: {
1199
+ classId
1200
+ }
1201
+ });
1202
+ this.session.assertOk(detailResponse);
1203
+ this.session.assertNotLoginRedirect(detailResponse);
1204
+ const detail = parseClassDetail(detailResponse.body);
1205
+ if (detail.survey?.required && detail.survey.url) {
1206
+ try {
1207
+ const surveyResponse = await this.session.get(detail.survey.url.replace(/^\//, ""));
1208
+ this.session.assertNotLoginRedirect(surveyResponse);
1209
+ if (surveyResponse.statusCode === 200) {
1210
+ const surveyStatus = parseSurveyPageStatus(surveyResponse.body);
1211
+ if (surveyStatus) {
1212
+ detail.survey = {
1213
+ ...detail.survey,
1214
+ ...surveyStatus
1215
+ };
1216
+ }
1217
+ }
1218
+ } catch (error) {
1219
+ if (!isBestEffortClassDetailError(error)) {
1220
+ throw error;
1221
+ }
1222
+ }
1223
+ }
1224
+ try {
1225
+ const studentResponse = await this.session.get("AIS/Student/Class/CourseStudents", {
1226
+ searchParams: {
1227
+ classId
1228
+ }
1229
+ });
1230
+ this.session.assertNotLoginRedirect(studentResponse);
1231
+ if (studentResponse.statusCode === 200) {
1232
+ const students = parseStudentList(studentResponse.body);
1233
+ if (students.length > 0) {
1234
+ return {
1235
+ ...detail,
1236
+ students
1237
+ };
1238
+ }
1239
+ }
1240
+ } catch (error) {
1241
+ if (!isBestEffortClassDetailError(error)) {
1242
+ throw error;
1243
+ }
1244
+ return detail;
1245
+ }
1246
+ return detail;
1247
+ }
1248
+ async getTranscript() {
1249
+ this.ensureAuthenticated();
1250
+ if (!this.state.dashboardHtml) {
1251
+ this.state.dashboardHtml = await this.fetchDashboardHtml();
1252
+ }
1253
+ if (!this.state.sapId) {
1254
+ const sapId = extractSapId(this.state.dashboardHtml);
1255
+ if (sapId) {
1256
+ this.state.sapId = sapId;
1257
+ }
1258
+ }
1259
+ const transcriptPath = this.resolveTranscriptPath(this.state.dashboardHtml, this.state.sapId);
1260
+ const response = await this.session.get(transcriptPath);
1261
+ this.session.assertOk(response);
1262
+ this.session.assertNotLoginRedirect(response);
1263
+ return parseTranscript(response.body);
1264
+ }
1265
+ async getWeeklySchedule() {
1266
+ this.ensureAuthenticated();
1267
+ if (!this.state.studentInfo) {
1268
+ await this.getProfile();
1269
+ }
1270
+ if (!this.state.studentInfo) {
1271
+ throw new NotImplementedUBYSError("Student info is not available for `getWeeklySchedule()`. Try logging in again.");
1272
+ }
1273
+ if (!this.state.dashboardHtml) {
1274
+ this.state.dashboardHtml = await this.fetchDashboardHtml();
1275
+ }
1276
+ const scheduleConfig = extractWeeklyScheduleConfig(this.state.dashboardHtml);
1277
+ const payload = buildWeeklyScheduleRequest(this.state.studentInfo, scheduleConfig);
1278
+ const response = await this.session.post("AIS/Student/Home/GetWeeklySchedule", {
1279
+ json: payload,
1280
+ headers: {
1281
+ ...AJAX_HEADERS
1282
+ }
1283
+ });
1284
+ this.session.assertOk(response);
1285
+ this.session.assertNotLoginRedirect(response);
1286
+ return parseWeeklySchedule(response.body);
1287
+ }
1288
+ clearSession() {
1289
+ this.ensureOpen();
1290
+ this.state = {
1291
+ authenticated: false
1292
+ };
1293
+ }
1294
+ async close() {
1295
+ if (this.closed) {
1296
+ return;
1297
+ }
1298
+ this.state = {
1299
+ authenticated: false
1300
+ };
1301
+ this.closed = true;
1302
+ await this.session.close();
1303
+ }
1304
+ ensureAuthenticated() {
1305
+ this.ensureOpen();
1306
+ if (!this.state.authenticated) {
1307
+ throw new SessionExpiredError("Not logged in. Call `login()` first.");
1308
+ }
1309
+ }
1310
+ ensureOpen() {
1311
+ if (this.closed || this.session.isClosed) {
1312
+ throw new ClientClosedError();
1313
+ }
1314
+ }
1315
+ async fetchDashboardHtml() {
1316
+ const response = await this.session.get("AIS/Student/Home/Index");
1317
+ this.session.assertOk(response);
1318
+ this.session.assertNotLoginRedirect(response);
1319
+ return response.body;
1320
+ }
1321
+ resolveTranscriptPath(dashboardHtml, sapId) {
1322
+ if (dashboardHtml) {
1323
+ const $ = cheerio.load(dashboardHtml);
1324
+ const href = $("a[href*='/AIS/Student/Transcript/Index']").first().attr("href");
1325
+ if (href) {
1326
+ return href.replace(/&amp;/g, "&").replace(/^\//, "");
1327
+ }
1328
+ }
1329
+ return sapId ? `AIS/Student/Transcript/Index?sapId=${encodeURIComponent(sapId)}` : "AIS/Student/Transcript/Index";
1330
+ }
1331
+ };
1332
+ function isBestEffortClassDetailError(error) {
1333
+ return error instanceof HttpError || error instanceof NetworkError || error instanceof ParseError;
1334
+ }
1335
+
1336
+ exports.AuthenticationError = AuthenticationError;
1337
+ exports.ClientClosedError = ClientClosedError;
1338
+ exports.HttpError = HttpError;
1339
+ exports.LoginError = LoginError;
1340
+ exports.NetworkError = NetworkError;
1341
+ exports.NotImplementedUBYSError = NotImplementedUBYSError;
1342
+ exports.ParseError = ParseError;
1343
+ exports.SessionExpiredError = SessionExpiredError;
1344
+ exports.UBYSClient = UBYSClient;
1345
+ exports.UBYSError = UBYSError;
1346
+ //# sourceMappingURL=index.cjs.map
1347
+ //# sourceMappingURL=index.cjs.map