jxust.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. // 江西理工大学(jxust.edu.cn)拾光课程表适配脚本
  2. // 非该大学开发者适配,开发者无法及时发现问题
  3. // 出现问题请提联系开发者或者提交pr更改,这更加快速
  4. // 预设节次时间
  5. const TimeSlots = [
  6. { "number": 1, "startTime": "08:30", "endTime": "09:15" },
  7. { "number": 2, "startTime": "09:20", "endTime": "10:05" },
  8. { "number": 3, "startTime": "10:25", "endTime": "11:10" },
  9. { "number": 4, "startTime": "11:15", "endTime": "12:00" },
  10. { "number": 5, "startTime": "14:00", "endTime": "14:45" },
  11. { "number": 6, "startTime": "14:50", "endTime": "15:35" },
  12. { "number": 7, "startTime": "15:55", "endTime": "16:40" },
  13. { "number": 8, "startTime": "16:45", "endTime": "17:30" },
  14. { "number": 9, "startTime": "19:00", "endTime": "19:45" },
  15. { "number": 10, "startTime": "19:50", "endTime": "20:35" }
  16. ];
  17. // 课表配置
  18. const CourseConfig = {
  19. "semesterTotalWeeks": 20
  20. };
  21. // 解析周次 (parseWeeks)
  22. function parseWeeks(weeksStr) {
  23. const weeks = [];
  24. if (!weeksStr) return weeks;
  25. const cleanedStr = weeksStr.replace(/\(周\)|\[.*?节\]|<\/?[a-z]+[^>]*>/ig, '').trim();
  26. if (cleanedStr === '') return weeks;
  27. cleanedStr.split(',').forEach(part => {
  28. const rangeMatch = part.match(/^(\d+)-(\d+)$/);
  29. if (rangeMatch) {
  30. const start = parseInt(rangeMatch[1]);
  31. const end = parseInt(rangeMatch[2]);
  32. for (let i = start; i <= end; i++) {
  33. weeks.push(i);
  34. }
  35. } else {
  36. const singleWeek = parseInt(part);
  37. if (!isNaN(singleWeek)) {
  38. weeks.push(singleWeek);
  39. }
  40. }
  41. });
  42. return [...new Set(weeks)].sort((a, b) => a - b);
  43. }
  44. // 核心解析函数 (parseCourseTable)
  45. function parseCourseTable(htmlContent) {
  46. const parser = new DOMParser();
  47. const doc = parser.parseFromString(htmlContent, "text/html");
  48. const courseList = [];
  49. const table = doc.getElementById('kbtable');
  50. if (!table) {
  51. AndroidBridge.showToast("错误:未找到课表表格 (id=kbtable)。");
  52. return [];
  53. }
  54. // 节次映射
  55. const sectionMap = [
  56. { start: 1, end: 2 },
  57. { start: 3, end: 4 },
  58. { start: 5, end: 6 },
  59. { start: 7, end: 8 },
  60. { start: 9, end: 10 }
  61. ];
  62. const rows = table.querySelectorAll('tr');
  63. for (let i = 1; i < rows.length; i++) {
  64. const row = rows[i];
  65. const mapIndex = i - 1;
  66. if (mapIndex >= sectionMap.length) continue;
  67. const sections = sectionMap[mapIndex];
  68. const cells = row.querySelectorAll('td');
  69. for (let j = 0; j < cells.length; j++) {
  70. const cell = cells[j];
  71. const dayOfWeek = j + 1;
  72. const detailDiv = cell.querySelectorAll('div')[1];
  73. if (!detailDiv) continue;
  74. const rawContent = detailDiv.innerHTML.trim();
  75. if (rawContent === '' || rawContent.replace(/&nbsp;|<[^>]*>/ig, '').trim() === '') continue;
  76. // 多个课程块以分隔符处理
  77. const courseBlocks = rawContent.split('---------------------<br>');
  78. courseBlocks.forEach(blockHtml => {
  79. if (blockHtml.trim() === '') return;
  80. const cleanedBlock = blockHtml.replace(/<br\/?>/gi, '\n').trim();
  81. const nameMatch = cleanedBlock.match(/^(.*?)\n/);
  82. let name = (nameMatch && nameMatch[1].trim()) || "未知课程";
  83. name = name.replace(/<span[^>]*>.*?<\/span>|<\/?[a-z]+[^>]*>/ig, '').trim();
  84. const teacherMatch = cleanedBlock.match(/<font title="老师">([^<]+?)<\/font>/i);
  85. const teacher = (teacherMatch && teacherMatch[1].trim()) || "暂无教师";
  86. const positionMatch = cleanedBlock.match(/<font title="教室">([^<]+?)<\/font>/i);
  87. const position = (positionMatch && positionMatch[1].trim()) || "暂无教室";
  88. const weeksSectionMatch = cleanedBlock.match(/<font title="周次\(节次\)">([^<]+?)<\/font>/i);
  89. const weeksSectionStr = (weeksSectionMatch && weeksSectionMatch[1].trim()) || "";
  90. const weeksArray = parseWeeks(weeksSectionStr);
  91. if (weeksArray.length === 0) {
  92. return;
  93. }
  94. const course = {
  95. name: name,
  96. teacher: teacher,
  97. position: position,
  98. day: dayOfWeek,
  99. startSection: sections.start,
  100. endSection: sections.end,
  101. weeks: weeksArray
  102. };
  103. courseList.push(course);
  104. });
  105. }
  106. }
  107. return courseList;
  108. }
  109. // 网络请求函数
  110. async function fetchCourseHtml() {
  111. AndroidBridge.showToast("正在获取课表数据...");
  112. const URL = "https://jw.jxust.edu.cn/jsxsd/xskb/xskb_list.do";
  113. try {
  114. const response = await fetch(URL, {
  115. "method": "GET",
  116. "credentials": "include"
  117. });
  118. if (!response.ok) {
  119. throw new Error(`网络请求失败,状态码: ${response.status}`);
  120. }
  121. const text = await response.text();
  122. AndroidBridge.showToast("课表数据获取成功,开始解析...");
  123. return text;
  124. } catch (error) {
  125. AndroidBridge.showToast(`网络请求异常: ${error.message}`);
  126. return null;
  127. }
  128. }
  129. async function importPresetTimeSlots() {
  130. try {
  131. await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(TimeSlots));
  132. AndroidBridge.showToast("预设时间段导入成功!");
  133. return true;
  134. } catch (error) {
  135. AndroidBridge.showToast("导入时间段失败: " + error.message);
  136. return false;
  137. }
  138. }
  139. async function saveConfig() {
  140. try {
  141. await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify(CourseConfig));
  142. AndroidBridge.showToast("课表配置更新成功!");
  143. return true;
  144. } catch (error) {
  145. AndroidBridge.showToast("保存配置失败: " + error.message);
  146. return false;
  147. }
  148. }
  149. async function saveCourses(parsedCourses) {
  150. if (parsedCourses.length === 0) {
  151. AndroidBridge.showToast("未解析到任何课程数据,跳过保存。");
  152. return true;
  153. }
  154. try {
  155. await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(parsedCourses));
  156. AndroidBridge.showToast(`成功导入 ${parsedCourses.length} 门课程!`);
  157. return true;
  158. } catch (error) {
  159. AndroidBridge.showToast(`保存失败: ${error.message}`);
  160. return false;
  161. }
  162. }
  163. async function runImportFlow() {
  164. const LOGIN_URL_START = "https://authserver.jxust.edu.cn/authserver/login";
  165. if (window.location.href.startsWith(LOGIN_URL_START)) {
  166. AndroidBridge.showToast("错误:当前页面为登录页,请先完成登录后再尝试导入!");
  167. return;
  168. }
  169. const alertConfirmed = await window.AndroidBridgePromise.showAlert(
  170. "开始导入",
  171. "请确保您已登录教务系统",
  172. "确定"
  173. );
  174. if (!alertConfirmed) {
  175. AndroidBridge.showToast("用户取消了导入。");
  176. return;
  177. }
  178. // 获取 HTML
  179. const htmlContent = await fetchCourseHtml();
  180. if (htmlContent === null) {
  181. AndroidBridge.showToast("导入终止。");
  182. return;
  183. }
  184. // 解析课程数据
  185. const parsedCourses = parseCourseTable(htmlContent);
  186. if (parsedCourses.length === 0) {
  187. AndroidBridge.showToast("解析失败或未发现有效课程。导入终止。");
  188. return;
  189. }
  190. // 导入时间段数据
  191. await importPresetTimeSlots();
  192. // 导入课表配置
  193. if (!await saveConfig()) return;
  194. // 课程数据保存
  195. if (!await saveCourses(parsedCourses)) return;
  196. // 流程成功
  197. AndroidBridge.showToast("所有任务已完成!课表已导入成功!");
  198. AndroidBridge.notifyTaskCompletion();
  199. }
  200. // 启动导入流程
  201. runImportFlow();