gdut.js 12 KB

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