gdut.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  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 >= 8 || currentMonth <= 2 ? 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}-${year + 1}学年 ${semester === 1 ? "秋季" : "春季"}(第${semester}学期)`;
  37. presetSemestersName.push(semesterName);
  38. }
  39. }
  40. try {
  41. const selectedIndex = await window.shiguangBridgePromise.showSingleSelection(
  42. "选择要导入的学期",
  43. JSON.stringify(presetSemestersName),
  44. 2
  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. 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. window.shiguangBridge.showToast(`添加课程失败: ${error.message}`);
  159. return null;
  160. }
  161. }
  162. async function checkSemesterIsOpened(semesterId) {
  163. console.log(`正在检查学期 ${semesterId} 是否已开放课表查询...`);
  164. const url = `${Strings.GET_ALL_COURSES_HTML_URL}?xnxqdm=${semesterId}`;
  165. const response = await fetch(url, {
  166. method: 'GET',
  167. headers: {
  168. 'Referer': Strings.GET_ALL_COURSES_HTML_URL_REFERRER
  169. },
  170. credentials: 'include'
  171. });
  172. const html = await response.text();
  173. // 如果不包含"未开放"文字,说明已开放
  174. return !html.includes("本学期课表还未开放,请稍后查询!");
  175. }
  176. function parseCourses(rawCourses){
  177. console.log(`正在解析课程数据,共 ${rawCourses.length} 条原始记录...`);
  178. const courses = [];
  179. for (const raw of rawCourses) {
  180. const sectionMatch = raw.jcdm.match(/\d{2}/g);
  181. if (!sectionMatch) continue;
  182. const sections = sectionMatch.map(Number);
  183. const startSection = sections[0];
  184. const endSection = sections[sections.length - 1];
  185. // 周次
  186. const week = Number(raw.zc);
  187. if (isNaN(week)) continue;
  188. courses.push({
  189. name: raw.kcmc.trim(),
  190. teacher: (raw.teaxms || "").trim(),
  191. position: (raw.jxcdmc || "").trim(),
  192. day: Number(raw.xq),
  193. startSection: startSection,
  194. endSection: endSection,
  195. weeks: [week],
  196. isCustomTime: false
  197. });
  198. }
  199. return courses;
  200. }
  201. async function saveCourses(courses){
  202. try {
  203. console.log("正在尝试导入课程...");
  204. const result = await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(courses));
  205. if (result === true) {
  206. console.log("课程导入成功!");
  207. } else {
  208. console.log("课程导入未成功,结果:" + result);
  209. window.shiguangBridge.showToast("课程导入失败,请查看日志。");
  210. }
  211. } catch (error) {
  212. console.error("导入课程时发生错误:", error);
  213. window.shiguangBridge.showToast("导入课程失败: " + error.message);
  214. }
  215. }
  216. async function setPresetTimeSlots() {
  217. const presetTimeSlots = [
  218. { "number": 1, "startTime": "08:30", "endTime": "09:15" },
  219. { "number": 2, "startTime": "09:20", "endTime": "10:05" },
  220. { "number": 3, "startTime": "10:25", "endTime": "11:10" },
  221. { "number": 4, "startTime": "11:15", "endTime": "12:00" },
  222. { "number": 5, "startTime": "13:50", "endTime": "14:35" },
  223. { "number": 6, "startTime": "14:40", "endTime": "15:25" },
  224. { "number": 7, "startTime": "15:30", "endTime": "16:15" },
  225. { "number": 8, "startTime": "16:30", "endTime": "17:15" },
  226. { "number": 9, "startTime": "17:20", "endTime": "18:05" },
  227. { "number": 10, "startTime": "18:30", "endTime": "19:15" },
  228. { "number": 11, "startTime": "19:20", "endTime": "20:05" },
  229. { "number": 12, "startTime": "20:10", "endTime": "20:55" },
  230. { "number": 13, "startTime": "21:00", "endTime": "21:45" },
  231. { "number": 14, "startTime": "21:50", "endTime": "22:35" }
  232. ];
  233. try {
  234. console.log("正在尝试导入预设时间段...");
  235. const result = await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(presetTimeSlots));
  236. if (result === true) {
  237. console.log("预设时间段导入成功!");
  238. } else {
  239. console.log("预设时间段导入未成功,结果:" + result);
  240. window.shiguangBridge.showToast("预设时间段导入失败,请查看日志。");
  241. }
  242. } catch (error) {
  243. console.error("导入时间段时发生错误:", error);
  244. window.shiguangBridge.showToast("导入时间段失败: " + error.message);
  245. }
  246. }
  247. async function saveConfig(config) {
  248. try {
  249. console.log("正在尝试导入课表配置...");
  250. const configJsonString = JSON.stringify(config);
  251. const result = await window.shiguangBridgePromise.saveCourseConfig(configJsonString);
  252. if (result === true) {
  253. console.log("课表配置导入成功!");
  254. } else {
  255. console.log("课表配置导入未成功,结果:" + result);
  256. window.shiguangBridge.showToast("课表配置导入失败,请查看日志。");
  257. }
  258. } catch (error) {
  259. console.error("导入课表配置时发生错误:", error);
  260. window.shiguangBridge.showToast("导入课表配置失败: " + error.message);
  261. }
  262. }
  263. /**
  264. * 编排这些异步操作,并在用户取消时停止后续执行。
  265. */
  266. async function runImportFlow() {
  267. var result = await stepDescriptionAlert();
  268. if (!result) {
  269. console.log("用户取消了操作,停止后续执行。");
  270. return; // 用户取消,立即退出函数
  271. }
  272. const semesterId = await selectSemesterSelection();
  273. if (!semesterId) {
  274. console.log("用户取消了学期选择,停止后续执行。");
  275. return; // 用户取消,立即退出函数
  276. }
  277. const startDate = await fetchStartDate(semesterId);
  278. const courses = await fetchCourses(semesterId);
  279. if (!courses) {
  280. console.log(`未能获取课程数据,停止后续执行。`);
  281. return; // 获取课程失败,立即退出函数
  282. }
  283. const config = {
  284. semesterStartDate: startDate.toISOString().split('T')[0], // 转换为 YYYY-MM-DD 格式
  285. semesterTotalWeeks: 20,
  286. defaultClassDuration: 45,
  287. defaultBreakDuration: 5,
  288. firstDayOfWeek: 1
  289. }
  290. await saveConfig(config);
  291. await saveCourses(courses);
  292. await setPresetTimeSlots();
  293. window.shiguangBridge.showToast(`成功导入 ${courses.length} 门课程!`);
  294. // 发送最终的生命周期完成信号
  295. window.shiguangBridge.notifyTaskCompletion();
  296. }
  297. // 入口函数,开始执行导入流程
  298. runImportFlow();