wust.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. // 武汉科技大学教务系统课程表导入脚本
  2. (function () {
  3. "use strict";
  4. var SCHEDULE_PATH = "/jsxsd/xskb/xskb_list.do";
  5. function toast(message) {
  6. if (window.shiguangBridge && window.shiguangBridge.showToast) {
  7. window.shiguangBridge.showToast(message);
  8. }
  9. }
  10. function normalizeText(value) {
  11. return (value || "").replace(/\u00a0/g, " ").replace(/\s+/g, " ").trim();
  12. }
  13. function parseWeeks(value) {
  14. var text = normalizeText(value).replace(/\(周\)|周/g, "");
  15. var weeks = new Set();
  16. text.split(",").forEach(function (part) {
  17. var range = part.trim().split("-").map(function (item) {
  18. return parseInt(item, 10);
  19. });
  20. if (!Number.isFinite(range[0])) return;
  21. var end = Number.isFinite(range[1]) ? range[1] : range[0];
  22. for (var week = range[0]; week <= end; week++) weeks.add(week);
  23. });
  24. return Array.from(weeks).sort(function (a, b) { return a - b; });
  25. }
  26. function parsePeriods(value) {
  27. var match = normalizeText(value).match(/\[([^\]]+)\]/);
  28. if (!match) return [];
  29. var numbers = match[1].match(/\d+/g);
  30. if (!numbers || numbers.length === 0) return [];
  31. var start = parseInt(numbers[0], 10);
  32. var end = parseInt(numbers[numbers.length - 1], 10);
  33. var periods = [];
  34. for (var period = start; period <= end; period++) periods.push(period);
  35. return periods;
  36. }
  37. function findScheduleDocument() {
  38. if (document.querySelector("#kbtable")) return document;
  39. return Array.from(document.querySelectorAll("iframe")).reduce(function (found, frame) {
  40. if (found) return found;
  41. try {
  42. var frameDoc = frame.contentDocument || frame.contentWindow.document;
  43. return frameDoc && frameDoc.querySelector("#kbtable") ? frameDoc : null;
  44. } catch (error) {
  45. return null;
  46. }
  47. }, null);
  48. }
  49. function parseCourseBlock(block) {
  50. var holder = document.createElement("div");
  51. holder.innerHTML = block;
  52. var lines = Array.from(holder.querySelectorAll("font[title]")).reduce(function (result, font) {
  53. result[font.getAttribute("title")] = normalizeText(font.textContent);
  54. return result;
  55. }, {});
  56. var firstLine = holder.innerHTML.split(/<br\s*\/?\s*>/i)[0];
  57. var nameHolder = document.createElement("div");
  58. nameHolder.innerHTML = firstLine;
  59. var name = normalizeText(nameHolder.textContent);
  60. var weekPeriod = lines["周次(节次)"] || "";
  61. var periods = parsePeriods(weekPeriod);
  62. var weeks = parseWeeks(weekPeriod.split("[")[0]);
  63. if (!name || weeks.length === 0 || periods.length === 0) return null;
  64. return {
  65. name: name,
  66. teacher: lines["老师"] || "",
  67. position: lines["教室"] || "未指定",
  68. weeks: weeks,
  69. startSection: periods[0],
  70. endSection: periods[periods.length - 1]
  71. };
  72. }
  73. function parseCourses(doc) {
  74. var table = doc.querySelector("#kbtable");
  75. if (!table) return [];
  76. var courses = new Map();
  77. Array.from(table.querySelectorAll("div.kbcontent")).forEach(function (div) {
  78. var idParts = (div.id || "").split("-");
  79. var weekday = parseInt(idParts[idParts.length - 2], 10);
  80. if (!Number.isFinite(weekday) || weekday < 1 || weekday > 7) return;
  81. var blocks = div.innerHTML.split(/<br\s*\/?\s*>\s*[-—]{10,}\s*<br\s*\/?\s*>/i);
  82. blocks.forEach(function (block) {
  83. var course = parseCourseBlock(block);
  84. if (!course) return;
  85. course.day = weekday;
  86. var key = [course.name, course.teacher, course.position, weekday, course.weeks.join(",")].join("|");
  87. if (!courses.has(key)) {
  88. courses.set(key, course);
  89. return;
  90. }
  91. var existing = courses.get(key);
  92. existing.startSection = Math.min(existing.startSection, course.startSection);
  93. existing.endSection = Math.max(existing.endSection, course.endSection);
  94. });
  95. });
  96. return Array.from(courses.values()).sort(function (a, b) {
  97. return a.day - b.day || a.startSection - b.startSection || a.name.localeCompare(b.name);
  98. });
  99. }
  100. function parseTimeSlots(doc) {
  101. var slots = new Map();
  102. Array.from(doc.querySelectorAll("#kbtable tr")).forEach(function (row) {
  103. var header = row.querySelector("th");
  104. if (!header) return;
  105. var match = normalizeText(header.textContent).match(/^(\d+).*?(\d{1,2}:\d{2})\s*[-~至]\s*(\d{1,2}:\d{2})/);
  106. if (!match) return;
  107. var number = parseInt(match[1], 10);
  108. if (!slots.has(number)) {
  109. slots.set(number, { number: number, startTime: match[2].padStart(5, "0"), endTime: match[3].padStart(5, "0") });
  110. }
  111. });
  112. return Array.from(slots.values()).sort(function (a, b) { return a.number - b.number; });
  113. }
  114. function getSemesterId(doc) {
  115. var select = doc.querySelector("#xnxq01id");
  116. return select ? select.value : "";
  117. }
  118. async function fetchScheduleDocument(semesterId) {
  119. var body = "cj0701id=&zc=&demo=&xnxq01id=" + encodeURIComponent(semesterId) + "&sfFD=1&wkbkc=1";
  120. var response = await fetch(SCHEDULE_PATH, {
  121. method: "POST",
  122. headers: { "Content-Type": "application/x-www-form-urlencoded" },
  123. credentials: "include",
  124. body: body
  125. });
  126. if (!response.ok) throw new Error("课表请求失败:HTTP " + response.status);
  127. return new DOMParser().parseFromString(await response.text(), "text/html");
  128. }
  129. async function saveData(courses, timeSlots) {
  130. var maxWeek = Math.max.apply(null, courses.map(function (course) {
  131. return Math.max.apply(null, course.weeks);
  132. }));
  133. var config = {
  134. semesterTotalWeeks: Number.isFinite(maxWeek) && maxWeek > 0 ? maxWeek : 20,
  135. firstDayOfWeek: 1,
  136. defaultClassDuration: 45,
  137. defaultBreakDuration: 5
  138. };
  139. if (window.shiguangBridgePromise.saveCourseConfig) {
  140. await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify(config));
  141. }
  142. if (timeSlots.length > 0 && window.shiguangBridgePromise.savePresetTimeSlots) {
  143. await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
  144. }
  145. return await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
  146. }
  147. async function runImport() {
  148. try {
  149. var currentDoc = findScheduleDocument() || document;
  150. var semesterId = getSemesterId(currentDoc);
  151. var scheduleDoc = currentDoc.querySelector("#kbtable") ? currentDoc : await fetchScheduleDocument(semesterId);
  152. var courses = parseCourses(scheduleDoc);
  153. if (courses.length === 0) {
  154. toast("未解析到课程,请确认已登录且课表页面已加载完成");
  155. return;
  156. }
  157. var saved = await saveData(courses, parseTimeSlots(scheduleDoc));
  158. if (!saved) {
  159. toast("课程导入失败");
  160. return;
  161. }
  162. toast("成功导入 " + courses.length + " 门课程");
  163. if (window.shiguangBridge.notifyTaskCompletion) window.shiguangBridge.notifyTaskCompletion();
  164. } catch (error) {
  165. console.error("WUST import failed:", error);
  166. toast("导入失败:" + error.message);
  167. }
  168. }
  169. if (window.shiguangBridgePromise && window.shiguangBridgePromise.saveImportedCourses) {
  170. runImport();
  171. }
  172. })();