upc.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. // 中国石油大学(华东)(upc.edu.cn) 拾光课程表适配脚本
  2. // 非该大学开发者适配,开发者无法及时发现问题
  3. // 出现问题请提联系开发者或者提交pr更改,这更加快速
  4. /**
  5. * 年份输入验证函数
  6. * @param {string} input 用户输入的年份
  7. * @returns {boolean|string} 验证通过返回false,失败返回错误提示
  8. */
  9. window.validateYearInput = function(input) {
  10. return /^[0-9]{4}$/.test(input) ? false : "请输入四位数字的学年!";
  11. };
  12. // 数据解析函数
  13. /**
  14. * 将周次字符串解析为数字数组
  15. */
  16. function parseWeeks(weekStr) {
  17. const weeks = [];
  18. if (!weekStr) return weeks;
  19. // 适配 "1-9,11-17(周)[01-02节]" 或 "12-15(周)"
  20. const pureWeekData = weekStr.split('(')[0];
  21. pureWeekData.split(',').forEach(seg => {
  22. if (seg.includes('-')) {
  23. const [s, e] = seg.split('-').map(Number);
  24. if (!isNaN(s) && !isNaN(e)) {
  25. for (let i = s; i <= e; i++) weeks.push(i);
  26. }
  27. } else {
  28. const w = parseInt(seg);
  29. if (!isNaN(w)) weeks.push(w);
  30. }
  31. });
  32. return [...new Set(weeks)].sort((a, b) => a - b);
  33. }
  34. /**
  35. * 转换课程 HTML 格式为应用模型
  36. */
  37. function parseTimetableToModel(doc) {
  38. const timetable = doc.getElementById('timetable');
  39. if (!timetable) return [];
  40. const results = [];
  41. const rows = Array.from(timetable.querySelectorAll('tr')).slice(1);
  42. rows.forEach(row => {
  43. const cells = row.querySelectorAll('td');
  44. if (cells.length < 7) return;
  45. cells.forEach((cell, dayIndex) => {
  46. let day;
  47. if (dayIndex === 0) {
  48. day = 7;
  49. } else {
  50. day = dayIndex;
  51. }
  52. const detailDivs = cell.querySelectorAll('.kbcontent, .kbcontent1');
  53. detailDivs.forEach(detailDiv => {
  54. const rawHtml = detailDiv.innerHTML.trim();
  55. if (rawHtml === "" || rawHtml === "&nbsp;") return;
  56. const courseBlocks = rawHtml.split(/---------------------|----------------------/);
  57. courseBlocks.forEach(block => {
  58. if (block.replace(/&nbsp;|<br\/?>/g, '').trim() === "") return;
  59. const tempDiv = document.createElement('div');
  60. tempDiv.innerHTML = block;
  61. let name = "";
  62. const nameFont = tempDiv.querySelector('font:not([title])');
  63. if (nameFont) {
  64. name = nameFont.innerText.trim();
  65. }
  66. // 提取教师、周次、地点
  67. const teacher = tempDiv.querySelector('font[title="教师"]')?.innerText.trim() || "未知教师";
  68. const weekInfo = tempDiv.querySelector('font[title="周次(节次)"]')?.innerText.trim() || "";
  69. const position = tempDiv.querySelector('font[title="教室"]')?.innerText.trim() || "未知地点";
  70. let start = 0, end = 0;
  71. if (weekInfo) {
  72. const secMatch = weekInfo.match(/\[(\d+)(?:-(\d+))?节\]/);
  73. if (secMatch) {
  74. start = parseInt(secMatch[1]);
  75. end = secMatch[2] ? parseInt(secMatch[2]) : start;
  76. }
  77. }
  78. if (name && start > 0) {
  79. results.push({
  80. "name": name,
  81. "teacher": teacher,
  82. "weeks": parseWeeks(weekInfo),
  83. "position": position,
  84. "day": day,
  85. "startSection": start,
  86. "endSection": end
  87. });
  88. }
  89. });
  90. });
  91. });
  92. });
  93. return results;
  94. }
  95. /**
  96. * 保存课表全局配置
  97. */
  98. async function saveAppConfig() {
  99. const config = {
  100. "firstDayOfWeek": 7
  101. };
  102. return await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify(config));
  103. }
  104. /**
  105. * 保存时间段配置
  106. */
  107. async function saveAppTimeSlots() {
  108. const timeSlots = [
  109. { "number": 1, "startTime": "08:00", "endTime": "08:45" },
  110. { "number": 2, "startTime": "08:50", "endTime": "09:35" },
  111. { "number": 3, "startTime": "09:55", "endTime": "10:40" },
  112. { "number": 4, "startTime": "10:45", "endTime": "11:30" },
  113. { "number": 5, "startTime": "11:35", "endTime": "12:20" },
  114. { "number": 6, "startTime": "14:00", "endTime": "14:45" },
  115. { "number": 7, "startTime": "14:50", "endTime": "15:35" },
  116. { "number": 8, "startTime": "15:55", "endTime": "16:40" },
  117. { "number": 9, "startTime": "16:45", "endTime": "17:30" },
  118. { "number": 10, "startTime": "19:00", "endTime": "19:45" },
  119. { "number": 11, "startTime": "19:50", "endTime": "20:35" },
  120. { "number": 12, "startTime": "20:40", "endTime": "21:25" }
  121. ];
  122. return await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
  123. }
  124. /**
  125. * 获取并让用户选择学期 ID
  126. */
  127. async function getSelectedSemesterId() {
  128. const currentYear = new Date().getFullYear();
  129. const year = await window.AndroidBridgePromise.showPrompt(
  130. "选择学年", "请输入起始学年(如 2025-2026 应输入 2025):", String(currentYear), "validateYearInput"
  131. );
  132. if (!year) return null;
  133. const semesterIndex = await window.AndroidBridgePromise.showSingleSelection(
  134. "选择学期", JSON.stringify(["第一学期", "第二学期"]), 0
  135. );
  136. if (semesterIndex === null) return null;
  137. return `${year}-${parseInt(year) + 1}-${semesterIndex + 1}`;
  138. }
  139. // 流程控制
  140. async function runImportFlow() {
  141. try {
  142. const confirmed = await window.AndroidBridgePromise.showAlert(
  143. "导入提示",
  144. "脚本将获取当前教务系统的课表数据。请确保您已登录。是否继续?",
  145. "确认并开始"
  146. );
  147. if (!confirmed) return;
  148. const semesterId = await getSelectedSemesterId();
  149. if (!semesterId) {
  150. AndroidBridge.showToast("用户取消了学期选择");
  151. return;
  152. }
  153. AndroidBridge.showToast("正在请求教务数据...");
  154. const response = await fetch("https://jwxt-443.webvpn.upc.edu.cn/jsxsd/xskb/xskb_list.do", {
  155. method: "POST",
  156. headers: { "Content-Type": "application/x-www-form-urlencoded" },
  157. body: `cj0701id=&zc=&demo=&xnxq01id=${semesterId}`,
  158. credentials: "include"
  159. });
  160. if (!response.ok) throw new Error("网络请求失败,请检查登录状态");
  161. const html = await response.text();
  162. const finalCourses = parseTimetableToModel(new DOMParser().parseFromString(html, "text/html"));
  163. if (finalCourses.length === 0) {
  164. AndroidBridge.showToast("未发现课程数据,请检查该学期是否有课或登录是否过期");
  165. return;
  166. }
  167. await saveAppConfig();
  168. await saveAppTimeSlots();
  169. await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(finalCourses));
  170. AndroidBridge.showToast(`成功导入 ${finalCourses.length} 门课程!`);
  171. AndroidBridge.notifyTaskCompletion();
  172. } catch (error) {
  173. console.error(error);
  174. AndroidBridge.showToast("异常: " + error.message);
  175. }
  176. }
  177. // 启动导入流程
  178. runImportFlow();