qqhru.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. // 齐齐哈尔大学(qqhru.edu.cn)拾光课程表适配脚本
  2. // 非该大学开发者适配,开发者无法及时发现问题
  3. // 出现问题请提联系开发者或者提交pr更改,这更加快速
  4. // 验证学年输入(4位数字)
  5. function validateYear(year) {
  6. if (!year || year.trim().length === 0) return "学年不能为空!";
  7. const yearRegex = /^\d{4}$/;
  8. if (!yearRegex.test(year)) return "请输入正确的4位数字学年(例如:2025)";
  9. return false;
  10. }
  11. // 解析 classWeek 字符串 (支持不定长度)
  12. function parseWeekString(weekStr) {
  13. let weeks = [];
  14. if (!weekStr) return weeks;
  15. for (let i = 0; i < weekStr.length; i++) {
  16. if (weekStr[i] === '1') weeks.push(i + 1);
  17. }
  18. return weeks;
  19. }
  20. // 格式化时间 (0800 -> 08:00)
  21. function formatTime(timeStr) {
  22. if (timeStr && timeStr.length === 4) {
  23. return timeStr.substring(0, 2) + ":" + timeStr.substring(2);
  24. }
  25. return timeStr;
  26. }
  27. async function promptUserToStart() {
  28. return await window.AndroidBridgePromise.showAlert(
  29. "教务系统课表导入",
  30. "导入前请确保您已在浏览器中成功登录教务系统",
  31. "好的,开始导入"
  32. );
  33. }
  34. /**
  35. * 获取学年
  36. */
  37. async function getAcademicYear() {
  38. return await window.AndroidBridgePromise.showPrompt(
  39. "学年设置",
  40. "请输入要导入课程的起始学年(例如 2025-2026 应输入2025):",
  41. "",
  42. "validateYear"
  43. );
  44. }
  45. /**
  46. * 获取学期
  47. */
  48. async function selectSemester() {
  49. const semesters = ["1(第一学期)", "2(第二学期)"];
  50. return await window.AndroidBridgePromise.showSingleSelection(
  51. "选择学期",
  52. JSON.stringify(semesters),
  53. -1
  54. );
  55. }
  56. /**
  57. * 动态获取当前教务系统的合法数据接口 API
  58. */
  59. async function fetchValidApiUrl() {
  60. try {
  61. const indexUrl = "http://111.43.36.164/student/courseSelect/calendarSemesterCurriculum/index";
  62. const response = await fetch(indexUrl, { method: "GET", credentials: "include" });
  63. const htmlText = await response.text();
  64. const match = htmlText.match(/url:\s*["']([^"']*\/ajaxStudentSchedule\/past\/callback)["']/);
  65. if (match && match[1]) {
  66. return match[1];
  67. }
  68. throw new Error("未能从页面中解析出有效的API路径");
  69. } catch (e) {
  70. console.error("解析动态API失败:", e);
  71. return null;
  72. }
  73. }
  74. /**
  75. * 网络请求和数据解析
  76. */
  77. async function fetchAndParseJwData(academicYear, semesterIndex) {
  78. try {
  79. AndroidBridge.showToast("正在获取教务初始化凭证...");
  80. const targetApiSubUrl = await fetchValidApiUrl();
  81. if (!targetApiSubUrl) {
  82. throw new Error("初始化凭证获取失败,请确认是否处于登录状态");
  83. }
  84. const semesterValue = parseInt(semesterIndex) + 1;
  85. const endYear = parseInt(academicYear) + 1;
  86. const planCode = `${academicYear}-${endYear}-${semesterValue}-1`;
  87. AndroidBridge.showToast("正在获取教务数据...");
  88. const fullApiUrl = `http://111.43.36.164${targetApiSubUrl}`;
  89. const response = await fetch(fullApiUrl, {
  90. "headers": { "content-type": "application/x-www-form-urlencoded; charset=UTF-8" },
  91. "body": `&planCode=${planCode}`,
  92. "method": "POST",
  93. "credentials": "include"
  94. });
  95. const data = await response.json();
  96. if (!data) throw new Error("服务器未返回任何数据");
  97. if (data.errorMessage) throw new Error(data.errorMessage);
  98. if (!data.dateList || !Array.isArray(data.dateList)) {
  99. throw new Error("未能获取到课程列表,请检查是否已登录或该学期是否有课");
  100. }
  101. // 解析时间段
  102. const timeSlots = (data.jcsjbs || []).map(item => ({
  103. number: parseInt(item.jc),
  104. startTime: formatTime(item.kssj),
  105. endTime: formatTime(item.jssj)
  106. }));
  107. // 解析课程
  108. let courses = [];
  109. data.dateList.forEach(plan => {
  110. if (plan && plan.selectCourseList && Array.isArray(plan.selectCourseList)) {
  111. plan.selectCourseList.forEach(c => {
  112. const teacher = (c.attendClassTeacher || "").replace(/\* /g, "").trim();
  113. if (c.timeAndPlaceList && Array.isArray(c.timeAndPlaceList)) {
  114. c.timeAndPlaceList.forEach(tp => {
  115. courses.push({
  116. name: c.courseName,
  117. teacher: teacher,
  118. position: (tp.teachingBuildingName || "") + (tp.classroomName || ""),
  119. day: tp.classDay,
  120. startSection: tp.classSessions,
  121. endSection: tp.classSessions + tp.continuingSession - 1,
  122. weeks: parseWeekString(tp.classWeek),
  123. });
  124. });
  125. }
  126. });
  127. }
  128. });
  129. if (courses.length === 0) {
  130. throw new Error("该学期暂无排课数据");
  131. }
  132. return { courses, timeSlots };
  133. } catch (e) {
  134. AndroidBridge.showToast("同步失败: " + e.message);
  135. return null;
  136. }
  137. }
  138. /**
  139. * 保存数据到应用
  140. */
  141. async function saveToApp(result) {
  142. const courseSuccess = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(result.courses));
  143. if (!courseSuccess) return false;
  144. if (result.timeSlots && result.timeSlots.length > 0) {
  145. await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(result.timeSlots));
  146. }
  147. await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify({
  148. semesterTotalWeeks: 20
  149. }));
  150. return true;
  151. }
  152. /**
  153. * 流程控制
  154. */
  155. async function runImportFlow() {
  156. // 公告
  157. const alertResult = await promptUserToStart();
  158. if (!alertResult) return;
  159. // 获取学年
  160. const academicYear = await getAcademicYear();
  161. if (academicYear === null) {
  162. AndroidBridge.showToast("导入已取消");
  163. return;
  164. }
  165. // 获取学期
  166. const semesterIndex = await selectSemester();
  167. if (semesterIndex === null) {
  168. AndroidBridge.showToast("导入已取消");
  169. return;
  170. }
  171. // 请求与解析
  172. const result = await fetchAndParseJwData(academicYear, semesterIndex);
  173. if (!result || result.courses.length === 0) return;
  174. // 保存并结束
  175. if (await saveToApp(result)) {
  176. AndroidBridge.showToast(`成功导入 ${result.courses.length} 个课程时段`);
  177. AndroidBridge.notifyTaskCompletion();
  178. }
  179. }
  180. // 启动
  181. runImportFlow();