@emiran/omu-ubys 0.1.0 → 0.2.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 CHANGED
@@ -1029,6 +1029,167 @@ function textOrUndefined3(value) {
1029
1029
  function normalizeText2(value) {
1030
1030
  return decodeHtmlEntities(value).replace(/\s+/g, " ").trim();
1031
1031
  }
1032
+ function parseSurveyPage(html, surveyUrl) {
1033
+ if (html.includes("/Account/Login")) {
1034
+ throw new SessionExpiredError();
1035
+ }
1036
+ const $ = cheerio.load(html);
1037
+ const description = $(".panel-body p").first().text().trim();
1038
+ let surveyId = "";
1039
+ let surveyUserId = "";
1040
+ let redirectUrl = "";
1041
+ $("script").each((_, script) => {
1042
+ const content = $(script).html() ?? "";
1043
+ const base64Match = content.match(
1044
+ /var\s+_jsm\s*=\s*null;\s*\(function\(\)\s*\{\s*_jsm\s*=\s*JSON\.parse\(Base64\.decode\(["']([^"']+)["']\)/
1045
+ );
1046
+ if (base64Match) {
1047
+ try {
1048
+ const encoded = base64Match[1];
1049
+ if (!encoded) return;
1050
+ const decoded = Buffer.from(encoded, "base64").toString("utf-8");
1051
+ const jsmObj = JSON.parse(decoded);
1052
+ if (typeof jsmObj["SurveyId"] === "number" && !surveyId) {
1053
+ surveyId = String(jsmObj["SurveyId"]);
1054
+ }
1055
+ const survey = jsmObj["Survey"];
1056
+ if (typeof survey?.["Id"] === "number" && !surveyId) {
1057
+ surveyId = String(survey["Id"]);
1058
+ }
1059
+ const surveyUser = jsmObj["SurveyUser"];
1060
+ if (typeof surveyUser?.["Id"] === "number" && !surveyUserId) {
1061
+ surveyUserId = String(surveyUser["Id"]);
1062
+ }
1063
+ if (typeof jsmObj["RedirectPageUrl"] === "string" && !redirectUrl) {
1064
+ redirectUrl = jsmObj["RedirectPageUrl"];
1065
+ }
1066
+ } catch {
1067
+ }
1068
+ }
1069
+ if (!surveyId) {
1070
+ const m = content.match(/Survey\s*:\s*\{\s*Id\s*:\s*(\d+)/);
1071
+ if (m) surveyId = m[1] ?? "";
1072
+ }
1073
+ if (!surveyUserId) {
1074
+ const m = content.match(/SurveyUser\s*:\s*\{\s*Id\s*:\s*(\d+)/);
1075
+ if (m) surveyUserId = m[1] ?? "";
1076
+ }
1077
+ if (!redirectUrl) {
1078
+ const m = content.match(/RedirectPageUrl\s*:\s*["']([^"']+)["']/);
1079
+ if (m) redirectUrl = m[1] ?? "";
1080
+ }
1081
+ });
1082
+ if (!surveyId) {
1083
+ surveyId = $("#surveyParent").attr("data-survey-id") ?? "";
1084
+ }
1085
+ if (!surveyUserId) {
1086
+ const m = surveyUrl.match(/[?&]Id=(\d+)/i);
1087
+ if (m) surveyUserId = m[1] ?? "";
1088
+ }
1089
+ if (!redirectUrl) {
1090
+ try {
1091
+ const urlObj = new URL(surveyUrl);
1092
+ redirectUrl = urlObj.searchParams.get("RedirectPageUrl") ?? "";
1093
+ } catch {
1094
+ }
1095
+ }
1096
+ if (!surveyId && !surveyUserId) {
1097
+ throw new ParseError("Could not extract surveyId or surveyUserId from survey page.", {
1098
+ details: { surveyUrl }
1099
+ });
1100
+ }
1101
+ const pages = parseSurveyPages($);
1102
+ return {
1103
+ url: surveyUrl,
1104
+ title: "Ders De\u011Ferlendirme Anketi",
1105
+ description,
1106
+ surveyId,
1107
+ surveyUserId,
1108
+ ...redirectUrl ? { redirectUrl } : {},
1109
+ pages
1110
+ };
1111
+ }
1112
+ function parseSurveyPages($) {
1113
+ const pages = [];
1114
+ $(".survey-page").each((_, pageEl) => {
1115
+ const pageId = $(pageEl).attr("data-page-id") ?? "";
1116
+ if (!pageId) return;
1117
+ const pageTitleText = $(pageEl).text().trim();
1118
+ const pageBody = $(pageEl).closest(".panel").find(".panel-body");
1119
+ const questions = [];
1120
+ const choiceLabels = [];
1121
+ const table = pageBody.find("table");
1122
+ if (table.length > 0) {
1123
+ table.find("thead th").each((idx, th) => {
1124
+ const choiceText = $(th).text().trim();
1125
+ if (choiceText && idx > 0) {
1126
+ choiceLabels.push(choiceText);
1127
+ }
1128
+ });
1129
+ table.find("tbody tr[data-question-id]").each((_2, row) => {
1130
+ const questionId = $(row).attr("data-question-id") ?? "";
1131
+ const questionText = $(row).find("td").first().text().trim();
1132
+ const isRequired = $(row).hasClass("forRequired");
1133
+ const questionChoices = [];
1134
+ $(row).find('input[type="radio"]').each((idx, input) => {
1135
+ const choiceId = $(input).attr("data-choise-id") ?? "";
1136
+ if (choiceId) {
1137
+ questionChoices.push({
1138
+ id: choiceId,
1139
+ text: choiceLabels[idx] ?? `Option ${idx + 1}`
1140
+ });
1141
+ }
1142
+ });
1143
+ if (questionId && questionText) {
1144
+ questions.push({
1145
+ id: questionId,
1146
+ text: questionText,
1147
+ required: isRequired,
1148
+ choices: questionChoices
1149
+ });
1150
+ }
1151
+ });
1152
+ }
1153
+ pages.push({
1154
+ id: pageId,
1155
+ title: pageTitleText,
1156
+ questions,
1157
+ choiceLabels
1158
+ });
1159
+ });
1160
+ return pages;
1161
+ }
1162
+ function buildSurveySubmitBody(surveyData, answers) {
1163
+ const choises = Object.entries(answers).map(([questionId, choiceId]) => ({
1164
+ Id: 0,
1165
+ SelectedOrder: null,
1166
+ SurveyQuestionId: parseInt(questionId, 10),
1167
+ SurveyUserId: parseInt(surveyData.surveyUserId, 10),
1168
+ SurveyQuestionChoiseId: parseInt(choiceId, 10),
1169
+ Text: "",
1170
+ IsIrrevelant: false
1171
+ }));
1172
+ return {
1173
+ choises,
1174
+ RedirectPageUrl: surveyData.redirectUrl ?? "",
1175
+ surveyId: parseInt(surveyData.surveyId, 10)
1176
+ };
1177
+ }
1178
+ function extractSurveyCsrfToken(html) {
1179
+ const $ = cheerio.load(html);
1180
+ const token = $('input[name="__RequestVerificationToken"]').val();
1181
+ return typeof token === "string" && token ? token : void 0;
1182
+ }
1183
+ function parseSurveyLinks(html, baseUrl) {
1184
+ const $ = cheerio.load(html);
1185
+ const links = [];
1186
+ $('a[href*="MES/Application/Public/Participant"]').each((_, el) => {
1187
+ const href = $(el).attr("href");
1188
+ if (!href) return;
1189
+ links.push(href.startsWith("http") ? href : `${baseUrl}${href}`);
1190
+ });
1191
+ return links;
1192
+ }
1032
1193
 
1033
1194
  // src/client.ts
1034
1195
  var UBYSClient = class {
@@ -1285,6 +1446,89 @@ var UBYSClient = class {
1285
1446
  this.session.assertNotLoginRedirect(response);
1286
1447
  return parseWeeklySchedule(response.body);
1287
1448
  }
1449
+ /**
1450
+ * Check whether there are any mandatory portal-level surveys blocking access.
1451
+ * Navigates to the student home page and detects PortalSurveyManagement redirects.
1452
+ * If surveys are found they are fetched and returned.
1453
+ */
1454
+ async getPendingSurveys() {
1455
+ this.ensureAuthenticated();
1456
+ const homeResponse = await this.session.get("AIS/Student/Home/Index");
1457
+ this.session.assertOk(homeResponse);
1458
+ const responseUrl = homeResponse.url;
1459
+ const html = homeResponse.body;
1460
+ const redirectedToSurvey = responseUrl.includes("PortalSurveyManagement");
1461
+ const htmlMentionsSurvey = html.includes("Zorunlu olan anketi");
1462
+ if (!redirectedToSurvey && !htmlMentionsSurvey) {
1463
+ return { hasPendingSurvey: false };
1464
+ }
1465
+ if (redirectedToSurvey) {
1466
+ let requestedUrl = null;
1467
+ try {
1468
+ requestedUrl = new URL(responseUrl).searchParams.get("RequestedUrl");
1469
+ } catch {
1470
+ }
1471
+ if (requestedUrl) {
1472
+ const retryResponse = await this.session.get(requestedUrl);
1473
+ if (!retryResponse.url.includes("PortalSurveyManagement")) {
1474
+ return { hasPendingSurvey: false };
1475
+ }
1476
+ }
1477
+ const surveyLinks = parseSurveyLinks(html, this.session.baseUrl);
1478
+ if (surveyLinks.length > 0) {
1479
+ const surveys = [];
1480
+ for (const link of surveyLinks) {
1481
+ try {
1482
+ const survey = await this.fetchSurvey(link);
1483
+ surveys.push(survey);
1484
+ } catch {
1485
+ }
1486
+ }
1487
+ return { hasPendingSurvey: true, ...surveys.length > 0 ? { surveys } : {} };
1488
+ }
1489
+ }
1490
+ return { hasPendingSurvey: true };
1491
+ }
1492
+ /**
1493
+ * Fetch and parse a single MES survey participant page.
1494
+ *
1495
+ * @param surveyUrl - Full URL (e.g. https://ubys.omu.edu.tr/MES/Application/Public/Participant?Id=…)
1496
+ */
1497
+ async fetchSurvey(surveyUrl) {
1498
+ this.ensureAuthenticated();
1499
+ const response = await this.session.get(surveyUrl);
1500
+ this.session.assertOk(response);
1501
+ this.session.assertNotLoginRedirect(response);
1502
+ return parseSurveyPage(response.body, surveyUrl);
1503
+ }
1504
+ /**
1505
+ * Submit answers for a survey.
1506
+ *
1507
+ * @param surveyData - The SurveyData returned by `fetchSurvey()`
1508
+ * @param answers - Map of questionId → choiceId
1509
+ */
1510
+ async submitSurvey(surveyData, answers) {
1511
+ this.ensureAuthenticated();
1512
+ const pageResponse = await this.session.get(surveyData.url);
1513
+ this.session.assertOk(pageResponse);
1514
+ this.session.assertNotLoginRedirect(pageResponse);
1515
+ const csrfToken = extractSurveyCsrfToken(pageResponse.body);
1516
+ if (!csrfToken) {
1517
+ throw new Error("CSRF token not found on survey page.");
1518
+ }
1519
+ const body = buildSurveySubmitBody(surveyData, answers);
1520
+ const finishUrl = `${this.session.baseUrl}/MES/Application/Public/FinishSurvey`;
1521
+ const submitResponse = await this.session.post(finishUrl, {
1522
+ body: JSON.stringify(body),
1523
+ headers: {
1524
+ "content-type": "application/json; charset=UTF-8",
1525
+ "x-requested-with": "XMLHttpRequest",
1526
+ "x-requestverificationtoken": csrfToken,
1527
+ referer: surveyData.url
1528
+ }
1529
+ });
1530
+ this.session.assertOk(submitResponse);
1531
+ }
1288
1532
  clearSession() {
1289
1533
  this.ensureOpen();
1290
1534
  this.state = {