gdut.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384
  1. // 文件: gdut.js
  2. if (typeof Strings === 'undefined') {
  3. var Strings = {
  4. BASE_URL: "https://jxfw.gdut.edu.cn",
  5. GET_WEEK_COURSES_API_URL: "https://jxfw.gdut.edu.cn/xsgrkbcx!getKbRq.action",
  6. GET_ALL_COURSES_API_URL: "https://jxfw.gdut.edu.cn/xsgrkbcx!getDataList.action",
  7. GET_ALL_COURSES_HTML_URL: "https://jxfw.gdut.edu.cn/xsgrkbcx!xsAllKbList.action",
  8. GET_ALL_COURSES_HTML_URL_REFERRER: "https://jxfw.gdut.edu.cn/xsgrkbcx!getXsgrbkList.action"
  9. };
  10. }
  11. async function stepDescriptionAlert() {
  12. try {
  13. const confirmed = await window.shiguangBridgePromise.showAlert(
  14. "提示",
  15. "即将执行导入课程操作,确保当前处于已登录状态(无需打开课程表页面)",
  16. "确认"
  17. );
  18. return confirmed;
  19. } catch (error) {
  20. console.error("显示弹窗时发生错误:", error);
  21. return false;
  22. }
  23. }
  24. async function selectSemesterSelection(){
  25. const now = new Date();
  26. const currentYear = now.getFullYear();
  27. const currentMonth = now.getMonth() + 1;
  28. const currentSemester = currentMonth >= 7 || currentMonth <= 1 ? 1 : 2;
  29. const nextSemester = currentSemester === 1 ? 2 : 1;
  30. const nextSemesterYear = currentSemester === 1 ? currentYear : currentYear + 1;
  31. const presetSemetersId = [];
  32. const presetSemestersName = [];
  33. for (let year = nextSemesterYear; year >= nextSemesterYear - 3; year--){
  34. for (let semester = nextSemester; semester >= 1; semester--){
  35. presetSemetersId.push(`${year}0${semester}`);
  36. const semesterName = `${year} 年${semester === 1 ? "秋季" : "春季"} (${year}-${year + 1} 学年第${semester}学期)`;
  37. presetSemestersName.push(semesterName);
  38. }
  39. }
  40. try {
  41. const selectedIndex = await window.shiguangBridgePromise.showSingleSelection(
  42. "选择要导入的学期",
  43. JSON.stringify(presetSemestersName),
  44. 1
  45. );
  46. if (selectedIndex !== null && selectedIndex >= 0 && selectedIndex < presetSemetersId.length) {
  47. console.log("用户选择了: " + presetSemestersName[selectedIndex] + " (索引: " + selectedIndex + ")");
  48. return presetSemetersId[selectedIndex];
  49. } else {
  50. console.log("用户取消了选择。");
  51. return null;
  52. }
  53. } catch (error) {
  54. console.error("显示单选列表弹窗时发生错误:", error);
  55. window.shiguangBridge.showToast("Single Selection:显示列表出错!" + error.message);
  56. return null;
  57. }
  58. }
  59. function extractFirstDay(dateInfoJsonData) {
  60. try {
  61. const jsonArray = JSON.parse(dateInfoJsonData);
  62. const dateInfoArray = jsonArray[1];
  63. // 遍历查找 xqmc === "1"(周一)的项
  64. for (const dateInfo of dateInfoArray) {
  65. if (dateInfo.xqmc === "1" && dateInfo.rq) {
  66. return dateInfo.rq;
  67. }
  68. }
  69. console.error('未找到 xqmc=1 的日期项');
  70. return null;
  71. } catch (error) {
  72. console.error('解析 JSON 失败:', error);
  73. return null;
  74. }
  75. }
  76. async function fetchStartDate(semesterId) {
  77. const url = `${Strings.GET_WEEK_COURSES_API_URL}?xnxqdm=${semesterId}&zc=1`;
  78. try {
  79. console.log(`正在获取学期开始日期。学期代码:${semesterId}`);
  80. const response = await fetch(url, {
  81. method: 'GET',
  82. headers: {
  83. 'Referer': url
  84. },
  85. credentials: 'include'
  86. });
  87. const data = await response.text();
  88. const startDateString = extractFirstDay(data);
  89. // 如果提取失败,返回当前日期
  90. if (startDateString === null) {
  91. // 使用当前日期
  92. return new Date();
  93. }
  94. // 解析日期字符串为 Date 对象
  95. const date = new Date(startDateString);
  96. if (isNaN(date.getTime())) {
  97. console.warn(`日期解析失败: ${startDateString},使用当前日期`);
  98. return new Date();
  99. }
  100. console.log(`成功获取学期开始日期: ${date.toISOString().split('T')[0]}`);
  101. return date;
  102. } catch (error) {
  103. console.error('获取学期开始日期失败,使用当前日期。错误信息:', error);
  104. return new Date();
  105. }
  106. }
  107. async function fetchCourses(semesterId){
  108. try {
  109. console.log(`正在获取学期 ${semesterId} 的课程数据...`);
  110. const rawCourses = [];
  111. // 理论上此处应分页遍历处理,但分页处理将导致教务系统返回错误的数据(课程重复和缺失)。
  112. // 此处假设总课程数量总是小于 1000,并设置一页的最大课程数量为 1000。
  113. const pageSize = 1000;
  114. const formData = new URLSearchParams();
  115. formData.append('zc', '');
  116. formData.append('xnxqdm', semesterId);
  117. formData.append('page', '1');
  118. formData.append('rows', String(pageSize));
  119. formData.append('sort', 'kxh');
  120. formData.append('order', 'asc');
  121. const response = await fetch(Strings.GET_ALL_COURSES_API_URL, {
  122. method: 'POST',
  123. headers: {
  124. 'Content-Type': 'application/x-www-form-urlencoded',
  125. 'Referer': Strings.BASE_URL
  126. },
  127. body: formData.toString(),
  128. credentials: 'include'
  129. });
  130. if (!response.ok) {
  131. throw new Error(`请求失败: ${response.status}`);
  132. }
  133. const rawData = await response.json();
  134. if (rawData.total === 0 || rawData.rows.length === 0) {
  135. console.log(`学期 ${semesterId} 没有找到课程数据。`);
  136. if (await checkSemesterIsOpened(semesterId)) {
  137. throw new Error('该学期没有找到课程!请确认选择了正确的学期。');
  138. }
  139. throw new Error('学期未开放课表查询!');
  140. }
  141. for (const row of rawData.rows) {
  142. for (const existingCourse of rawCourses) {
  143. if (row.xq === existingCourse.xq
  144. && row.zc === existingCourse.zc
  145. && row.kcmc === existingCourse.kcmc
  146. && row.jcdm === existingCourse.jcdm
  147. && row.kxh === existingCourse.kxh) {
  148. console.error(`检查到有与现有课程重复的课程: `);
  149. console.error(row);
  150. console.error(`已有课程:`);
  151. console.error(existingCourse);
  152. window.shiguangBridge.showToast(`导入课程失败: ${row.kcmc} 意外地出现了重复的课程。请联系开发者解决此问题。`);
  153. return null;
  154. }
  155. }
  156. rawCourses.push(row);
  157. }
  158. console.log(`成功获取学期 ${semesterId} 的课程数据,共 ${rawCourses.length} 条记录。`);
  159. const courses = parseCourses(rawCourses);
  160. return courses;
  161. } catch (error) {
  162. console.error('添加课程表失败:', error);
  163. window.shiguangBridge.showToast(`添加课程失败: ${error.message}`);
  164. return null;
  165. }
  166. }
  167. async function checkSemesterIsOpened(semesterId) {
  168. console.log(`正在检查学期 ${semesterId} 是否已开放课表查询...`);
  169. const url = `${Strings.GET_ALL_COURSES_HTML_URL}?xnxqdm=${semesterId}`;
  170. const response = await fetch(url, {
  171. method: 'GET',
  172. headers: {
  173. 'Referer': Strings.GET_ALL_COURSES_HTML_URL_REFERRER
  174. },
  175. credentials: 'include'
  176. });
  177. const html = await response.text();
  178. // 如果不包含"未开放"文字,说明已开放
  179. return !html.includes("本学期课表还未开放,请稍后查询!");
  180. }
  181. function parseCourses(rawCourses){
  182. console.log(`正在转换原始课程数据...`);
  183. const courses = [];
  184. for (const raw of rawCourses) {
  185. const sectionMatch = raw.jcdm.match(/\d{2}/g);
  186. if (!sectionMatch) {
  187. console.error(`课程节次解析失败,原始数据:${raw.jcdm},跳过该课程。`);
  188. continue;
  189. }
  190. const sections = sectionMatch.map(Number);
  191. const startSection = sections[0];
  192. const endSection = sections[sections.length - 1];
  193. // 周次
  194. const week = Number(raw.zc);
  195. if (isNaN(week)) {
  196. console.error(`课程周次解析失败,原始数据:${raw.zc},跳过该课程。`);
  197. continue;
  198. }
  199. const course = {
  200. name: raw.kcmc.trim(),
  201. teacher: (raw.teaxms || "").trim(),
  202. position: (raw.jxcdmc || "").trim(),
  203. day: Number(raw.xq),
  204. startSection: startSection,
  205. endSection: endSection,
  206. weeks: [week],
  207. isCustomTime: false
  208. };
  209. courses.push(course);
  210. }
  211. return courses;
  212. }
  213. async function saveCourses(courses){
  214. try {
  215. console.log("正在尝试导入课程...");
  216. const result = await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
  217. if (result === true) {
  218. console.log("课程导入成功!");
  219. } else {
  220. console.log("课程导入未成功,结果:" + result);
  221. window.shiguangBridge.showToast("课程导入失败,请查看日志。");
  222. }
  223. } catch (error) {
  224. console.error("导入课程时发生错误:", error);
  225. window.shiguangBridge.showToast("导入课程失败: " + error.message);
  226. }
  227. }
  228. async function setPresetTimeSlots() {
  229. const presetTimeSlots = [
  230. { "number": 1, "startTime": "08:30", "endTime": "09:15" },
  231. { "number": 2, "startTime": "09:20", "endTime": "10:05" },
  232. { "number": 3, "startTime": "10:25", "endTime": "11:10" },
  233. { "number": 4, "startTime": "11:15", "endTime": "12:00" },
  234. { "number": 5, "startTime": "13:50", "endTime": "14:35" },
  235. { "number": 6, "startTime": "14:40", "endTime": "15:25" },
  236. { "number": 7, "startTime": "15:30", "endTime": "16:15" },
  237. { "number": 8, "startTime": "16:30", "endTime": "17:15" },
  238. { "number": 9, "startTime": "17:20", "endTime": "18:05" },
  239. { "number": 10, "startTime": "18:30", "endTime": "19:15" },
  240. { "number": 11, "startTime": "19:20", "endTime": "20:05" },
  241. { "number": 12, "startTime": "20:10", "endTime": "20:55" },
  242. { "number": 13, "startTime": "21:00", "endTime": "21:45" },
  243. { "number": 14, "startTime": "21:50", "endTime": "22:35" }
  244. ];
  245. try {
  246. console.log("正在尝试导入预设时间段...");
  247. const result = await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(presetTimeSlots));
  248. if (result === true) {
  249. console.log("预设时间段导入成功!");
  250. } else {
  251. console.log("预设时间段导入未成功,结果:" + result);
  252. window.shiguangBridge.showToast("预设时间段导入失败,请查看日志。");
  253. }
  254. } catch (error) {
  255. console.error("导入时间段时发生错误:", error);
  256. window.shiguangBridge.showToast("导入时间段失败: " + error.message);
  257. }
  258. }
  259. async function saveConfig(config) {
  260. try {
  261. console.log("正在尝试导入课表配置...");
  262. const configJsonString = JSON.stringify(config);
  263. const result = await window.shiguangBridgePromise.saveCourseConfig(configJsonString);
  264. if (result === true) {
  265. console.log("课表配置导入成功!");
  266. } else {
  267. console.log("课表配置导入未成功,结果:" + result);
  268. window.shiguangBridge.showToast("课表配置导入失败,请查看日志。");
  269. }
  270. } catch (error) {
  271. console.error("导入课表配置时发生错误:", error);
  272. window.shiguangBridge.showToast("导入课表配置失败: " + error.message);
  273. }
  274. }
  275. /**
  276. * 编排这些异步操作,并在用户取消时停止后续执行。
  277. */
  278. async function runImportFlow() {
  279. if (window.location.hostname === "authserver.gdut.edu.cn"){
  280. window.shiguangBridge.showToast("执行导入课表操作前,必须先登录到教务系统!");
  281. return;
  282. }
  283. if (window.location.hostname !== "jxfw.gdut.edu.cn") {
  284. window.shiguangBridge.showToast("当前页面不是教务系统页面!");
  285. return;
  286. }
  287. const result = await stepDescriptionAlert();
  288. if (!result) {
  289. console.log("用户取消了操作,停止后续执行。");
  290. return; // 用户取消,立即退出函数
  291. }
  292. const semesterId = await selectSemesterSelection();
  293. if (!semesterId) {
  294. console.log("用户取消了学期选择,停止后续执行。");
  295. return; // 用户取消,立即退出函数
  296. }
  297. const startDate = await fetchStartDate(semesterId);
  298. const courses = await fetchCourses(semesterId);
  299. if (!courses) {
  300. console.log(`未能获取课程数据,停止后续执行。`);
  301. return; // 获取课程失败,立即退出函数
  302. }
  303. const config = {
  304. semesterStartDate: startDate.toISOString().split('T')[0], // 转换为 YYYY-MM-DD 格式
  305. semesterTotalWeeks: 20,
  306. defaultClassDuration: 45,
  307. defaultBreakDuration: 5,
  308. firstDayOfWeek: 1
  309. }
  310. await saveConfig(config);
  311. await saveCourses(courses);
  312. await setPresetTimeSlots();
  313. window.shiguangBridge.showToast(`成功导入 ${courses.length} 门课程!`);
  314. // 发送最终的生命周期完成信号
  315. window.shiguangBridge.notifyTaskCompletion();
  316. }
  317. // 入口函数,开始执行导入流程
  318. runImportFlow();