cquet_01.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. (async function () {
  2. function toast(msg) {
  3. try {
  4. if (window.AndroidBridge && typeof window.AndroidBridge.showToast === "function") {
  5. window.AndroidBridge.showToast(String(msg));
  6. }
  7. } catch (_) {}
  8. }
  9. async function fail(message, error) {
  10. var detail = String(message || "导入失败");
  11. if (error) {
  12. detail += "\n" + (error.stack || error.message || String(error));
  13. }
  14. try {
  15. console.error(detail, error || "");
  16. } catch (_) {}
  17. try {
  18. if (window.AndroidBridgePromise && typeof window.AndroidBridgePromise.showAlert === "function") {
  19. await window.AndroidBridgePromise.showAlert("提示", detail, "确定");
  20. }
  21. } catch (_) {}
  22. throw new Error(detail);
  23. }
  24. function sleep(ms) {
  25. return new Promise(function (resolve) { setTimeout(resolve, ms); });
  26. }
  27. async function waitFor(cond, timeout, interval) {
  28. var start = Date.now();
  29. timeout = timeout || 15000;
  30. interval = interval || 300;
  31. while (Date.now() - start < timeout) {
  32. try {
  33. var value = await cond();
  34. if (value) return value;
  35. } catch (_) {}
  36. await sleep(interval);
  37. }
  38. return null;
  39. }
  40. function normalizeText(text) {
  41. return String(text || "")
  42. .replace(/\u00a0/g, " ")
  43. .replace(/\r/g, "\n")
  44. .replace(/\t/g, " ")
  45. .replace(/[ ]+\n/g, "\n")
  46. .replace(/\n[ ]+/g, "\n")
  47. .replace(/[ ]{2,}/g, " ")
  48. .replace(/\n{3,}/g, "\n\n")
  49. .trim();
  50. }
  51. function getAccessibleDocuments() {
  52. var docs = [];
  53. function pushDoc(doc) {
  54. if (doc && docs.indexOf(doc) === -1) docs.push(doc);
  55. }
  56. function scoreDoc(doc) {
  57. try {
  58. var score = 0;
  59. var href = String((doc.location && doc.location.href) || "");
  60. var title = normalizeText(doc.title || "");
  61. var text = normalizeText((doc.body && doc.body.innerText) || "");
  62. if (/\/xskb\/xskb_list\.do/i.test(href)) score += 100;
  63. if (/学期理论课表/.test(title)) score += 50;
  64. if (/学期理论课表/.test(text)) score += 30;
  65. if (doc.querySelector("#xnxq01id")) score += 20;
  66. if (doc.querySelector("#zc")) score += 10;
  67. if (getCourseTable(doc)) score += 10;
  68. return score;
  69. } catch (_) {
  70. return 0;
  71. }
  72. }
  73. pushDoc(document);
  74. Array.from(document.querySelectorAll("iframe")).forEach(function (iframe) {
  75. try {
  76. var doc = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document);
  77. if (doc) pushDoc(doc);
  78. } catch (_) {}
  79. });
  80. docs.sort(function (a, b) { return scoreDoc(b) - scoreDoc(a); });
  81. return docs;
  82. }
  83. function getCourseTable(doc) {
  84. return doc.querySelector("#kbtable") ||
  85. doc.querySelector("#tab1") ||
  86. doc.querySelector("table.kb_table") ||
  87. doc.querySelector("table.kbtable") ||
  88. doc.querySelector("table");
  89. }
  90. function isScheduleDoc(doc) {
  91. try {
  92. var text = normalizeText((doc.body && doc.body.innerText) || "");
  93. if (/登录|用户名|密码/.test(text) && !/课表/.test(text)) return false;
  94. if (/学期理论课表|我的课表/.test(text) && getCourseTable(doc)) return true;
  95. if (doc.querySelector("#xnxq01id") && getCourseTable(doc)) return true;
  96. return false;
  97. } catch (_) {
  98. return false;
  99. }
  100. }
  101. function parseDayFromHeader(text) {
  102. var m = normalizeText(text).match(/星期([一二三四五六日天])/);
  103. if (!m) return 0;
  104. return { "一": 1, "二": 2, "三": 3, "四": 4, "五": 5, "六": 6, "日": 7, "天": 7 }[m[1]] || 0;
  105. }
  106. function parseWeekText(weekText) {
  107. var text = normalizeText(weekText);
  108. if (!text) return [];
  109. text = text.replace(/(/g, "(").replace(/)/g, ")");
  110. text = text.replace(/\s+/g, "");
  111. text = text.replace(/周次[::]?/g, "");
  112. var odd = /单/.test(text);
  113. var even = /双/.test(text);
  114. text = text.replace(/\((?:单|双)\)/g, "");
  115. text = text.replace(/[单双]/g, "");
  116. text = text.replace(/\(周\)/g, "");
  117. text = text.replace(/周/g, "");
  118. text = text.replace(/[;;]/g, ",");
  119. var result = [];
  120. var seen = {};
  121. text.split(/[,,]/).map(function (x) { return x.trim(); }).filter(Boolean).forEach(function (part) {
  122. var range = part.match(/^(\d+)-(\d+)$/);
  123. if (range) {
  124. var start = parseInt(range[1], 10);
  125. var end = parseInt(range[2], 10);
  126. if (start > end) {
  127. var t = start;
  128. start = end;
  129. end = t;
  130. }
  131. for (var i = start; i <= end; i++) {
  132. if (odd && i % 2 === 0) continue;
  133. if (even && i % 2 !== 0) continue;
  134. if (!seen[i]) {
  135. seen[i] = true;
  136. result.push(i);
  137. }
  138. }
  139. return;
  140. }
  141. var single = part.match(/^(\d+)$/);
  142. if (single) {
  143. var w = parseInt(single[1], 10);
  144. if (odd && w % 2 === 0) return;
  145. if (even && w % 2 !== 0) return;
  146. if (!seen[w]) {
  147. seen[w] = true;
  148. result.push(w);
  149. }
  150. }
  151. });
  152. result.sort(function (a, b) { return a - b; });
  153. return result;
  154. }
  155. function parseSectionText(text) {
  156. var raw = normalizeText(text).replace(/(/g, "(").replace(/)/g, ")");
  157. if (!raw) return null;
  158. var m = raw.match(/\[(\d+(?:-\d+)*)节\]/) || raw.match(/\[(\d+(?:[-,,]\d+)*)小节\]/);
  159. if (!m) return null;
  160. var nums = (m[1].match(/\d+/g) || []).map(function (x) { return parseInt(x, 10); }).filter(function (n) { return !isNaN(n); });
  161. if (!nums.length) return null;
  162. return {
  163. startSection: Math.min.apply(null, nums),
  164. endSection: Math.max.apply(null, nums)
  165. };
  166. }
  167. function extractWeekAndSectionLine(lines) {
  168. for (var i = 0; i < lines.length; i++) {
  169. if (/\(周\)/.test(lines[i]) && /\[\d+(?:-\d+)*节\]/.test(lines[i])) {
  170. return { index: i, text: lines[i] };
  171. }
  172. }
  173. return null;
  174. }
  175. function isMeaninglessLine(line) {
  176. var text = normalizeText(line);
  177. if (!text) return true;
  178. if (/^(学期理论课表|理论课表|实践课表|课表查询|筛选|放大|时间模式[::]?.*|周次[::]?.*)$/.test(text)) return true;
  179. return false;
  180. }
  181. function splitCoursesInCell(doc, cell) {
  182. function getCellLines() {
  183. var text = normalizeText(cell.innerText || cell.textContent || "");
  184. if (!text) return [];
  185. return text.split("\n").map(function (x) { return normalizeText(x); }).filter(Boolean).filter(function (line) {
  186. return !isMeaninglessLine(line) && !/^[-]{3,}$/.test(line);
  187. });
  188. }
  189. function splitByParagraphs() {
  190. var ps = Array.from(cell.querySelectorAll("p"));
  191. if (!ps.length) return [];
  192. return ps.map(function (p) {
  193. return normalizeText(p.innerText || p.textContent || "");
  194. }).filter(Boolean).filter(function (t) {
  195. return /\(周\)/.test(t) && /\[(?:\d+(?:[-,,]\d+)*)节\]/.test(t);
  196. });
  197. }
  198. function splitBySequentialLines(lines) {
  199. var blocks = [];
  200. var i = 0;
  201. while (i < lines.length) {
  202. var line = lines[i];
  203. if (!line) {
  204. i++;
  205. continue;
  206. }
  207. if (!/\(周\)/.test(line) || !/\[(?:\d+(?:[-,,]\d+)*)节\]/.test(line)) {
  208. i++;
  209. continue;
  210. }
  211. var parts = [];
  212. if (i - 2 >= 0) {
  213. parts.push(lines[i - 2]);
  214. parts.push(lines[i - 1]);
  215. } else if (i - 1 >= 0) {
  216. parts.push(lines[i - 1]);
  217. }
  218. parts.push(line);
  219. if (i + 1 < lines.length) parts.push(lines[i + 1]);
  220. var cleaned = [];
  221. parts.forEach(function (p) {
  222. p = normalizeText(p);
  223. if (!p) return;
  224. if (/^[-]{3,}$/.test(p)) return;
  225. cleaned.push(p);
  226. });
  227. if (cleaned.length) {
  228. var last = cleaned[cleaned.length - 1];
  229. if (/\(周\)/.test(last) && i + 1 < lines.length) cleaned.push(lines[i + 1]);
  230. blocks.push(cleaned.join("\n"));
  231. }
  232. i += 2;
  233. }
  234. return blocks;
  235. }
  236. function splitCompactText(text) {
  237. var lines = text.split("\n").map(function (x) { return normalizeText(x); }).filter(Boolean);
  238. if (!lines.length) return [];
  239. return splitBySequentialLines(lines);
  240. }
  241. var byP = splitByParagraphs();
  242. if (byP.length) return byP;
  243. var text2 = normalizeText(cell.innerText || cell.textContent || "");
  244. if (!text2) return [];
  245. var lineBlocks = splitBySequentialLines(getCellLines());
  246. if (lineBlocks.length) return lineBlocks;
  247. var normalized = text2
  248. .replace(/([\u4e00-\u9fa5A-Za-z0-9()()《》·,,、\-\s]+?)\s+([0-9]+(?:-[0-9]+)?(?:[,,][0-9]+(?:-[0-9]+)?)*(?:\((?:单|双)\))?\(周\)\[[0-9\-,,]+节\])/g, function (_, a, b) {
  249. return normalizeText(a) + "\n" + normalizeText(b);
  250. })
  251. .replace(/(\[[0-9\-,,]+节\])\s*([^\n\[]+)/g, function (_, a, b) {
  252. return a + "\n" + normalizeText(b);
  253. })
  254. .replace(/\s{2,}/g, "\n");
  255. var compactBlocks = splitCompactText(normalized);
  256. if (compactBlocks.length) return compactBlocks;
  257. return [];
  258. }
  259. function parseCourseBlock(text, day) {
  260. var raw = normalizeText(text);
  261. if (!raw) return null;
  262. raw = raw.replace(/(/g, "(").replace(/)/g, ")");
  263. if (!/\(周\)/.test(raw) || !/\[(?:\d+(?:[-,,]\d+)*)节\]/.test(raw)) return null;
  264. var lines = raw.split("\n").map(function (x) { return normalizeText(x); }).filter(Boolean);
  265. if (!lines.length) return null;
  266. var wsIndex = -1;
  267. for (var li = 0; li < lines.length; li++) {
  268. if (/\(周\)/.test(lines[li]) && /\[(?:\d+(?:[-,,]\d+)*)节\]/.test(lines[li])) {
  269. wsIndex = li;
  270. break;
  271. }
  272. }
  273. if (wsIndex <= 0) {
  274. for (var i = 0; i < lines.length; i++) {
  275. var line = lines[i];
  276. if (!/\(周\)/.test(line)) continue;
  277. var weekPartMatch = line.match(/([0-9,,\-]+(?:\((?:单|双)\))?\(周\))/);
  278. var sectionPartMatch = line.match(/(\[(?:\d+(?:[-,,]\d+)*)节\])/);
  279. if (weekPartMatch && sectionPartMatch) {
  280. var prefix = normalizeText(line.slice(0, line.indexOf(weekPartMatch[1])));
  281. var suffix = normalizeText(line.slice(line.indexOf(sectionPartMatch[1]) + sectionPartMatch[1].length));
  282. var rebuilt = [];
  283. if (prefix) rebuilt.push(prefix);
  284. rebuilt.push(weekPartMatch[1] + sectionPartMatch[1]);
  285. if (suffix) rebuilt.push(suffix);
  286. lines.splice.apply(lines, [i, 1].concat(rebuilt));
  287. wsIndex = prefix ? i + 1 : i;
  288. break;
  289. }
  290. }
  291. }
  292. if (wsIndex <= 0) return null;
  293. var wsLine = lines[wsIndex];
  294. var weekMatch = wsLine.match(/([0-9,,\-]+(?:\((?:单|双)\))?\(周\))/);
  295. if (!weekMatch) return null;
  296. var weeks = parseWeekText(weekMatch[1]);
  297. if (!weeks.length) return null;
  298. var section = parseSectionText(wsLine);
  299. if (!section) return null;
  300. var name = lines[0] || "";
  301. if (!name) return null;
  302. if (/^[\d,\-,()\[\]单双周节小节]+$/.test(name)) return null;
  303. var courseNature = "";
  304. var natureMatch = name.match(/\[(必修|选修)\]/);
  305. if (natureMatch) {
  306. courseNature = natureMatch[1] === "必修" ? "required" : "elective";
  307. name = name.replace(/\[(必修|选修)\]/g, "").trim();
  308. }
  309. name = name.replace(/\[(\d+)\]/g, "").trim();
  310. if (!name) return null;
  311. var beforeWeek = lines.slice(1, wsIndex);
  312. var teacher = "";
  313. var noteParts = [];
  314. if (beforeWeek.length) {
  315. if (beforeWeek.length === 1) {
  316. teacher = beforeWeek[0];
  317. } else {
  318. teacher = beforeWeek[beforeWeek.length - 1] || "";
  319. noteParts = beforeWeek.slice(0, -1);
  320. }
  321. }
  322. if (/^\[[0-9]+(?:-[0-9]+)?\]班$/.test(teacher) || /^\d+$/.test(teacher) || /\(周\)|\[(?:\d+(?:[-,,]\d+)*)节\]/.test(teacher)) {
  323. noteParts.push(teacher);
  324. teacher = "";
  325. }
  326. var position = "";
  327. for (var j = wsIndex + 1; j < lines.length; j++) {
  328. var nextLine = lines[j];
  329. if (!nextLine) continue;
  330. position = nextLine;
  331. break;
  332. }
  333. noteParts = noteParts.filter(Boolean).map(function (x) { return x.trim(); }).filter(Boolean);
  334. var course = {
  335. name: name,
  336. teacher: teacher || "",
  337. position: position || "",
  338. day: Number(day),
  339. startSection: section.startSection,
  340. endSection: section.endSection,
  341. weeks: weeks
  342. };
  343. course.location = course.position;
  344. course.dayOfWeek = course.day;
  345. course.startWeek = weeks[0];
  346. course.endWeek = weeks[weeks.length - 1];
  347. if (courseNature) course.courseNature = courseNature;
  348. if (weeks.length && weeks.every(function (w) { return w % 2 === 1; })) course.isOddWeek = true;
  349. if (weeks.length && weeks.every(function (w) { return w % 2 === 0; })) course.isEvenWeek = true;
  350. if (noteParts.length) course.note = noteParts.join(" ");
  351. return course;
  352. }
  353. function dedupeCourses(courses) {
  354. var map = {};
  355. var result = [];
  356. courses.forEach(function (c) {
  357. var key = [
  358. c.name, c.teacher, c.position, c.day, c.startSection, c.endSection, (c.weeks || []).join(",")
  359. ].join("||");
  360. if (!map[key]) {
  361. map[key] = true;
  362. result.push(c);
  363. }
  364. });
  365. return result;
  366. }
  367. function parseTimeSlots(doc) {
  368. var table = getCourseTable(doc);
  369. if (!table) return [];
  370. var rows = Array.from(table.querySelectorAll("tr"));
  371. var result = [];
  372. var seen = {};
  373. function toMin(v) {
  374. var p = String(v).split(":");
  375. return parseInt(p[0], 10) * 60 + parseInt(p[1], 10);
  376. }
  377. function toHHMM(mins) {
  378. mins = Math.round(mins);
  379. var h = Math.floor(mins / 60);
  380. var m = mins % 60;
  381. return String(h).padStart(2, "0") + ":" + String(m).padStart(2, "0");
  382. }
  383. rows.forEach(function (row, idx) {
  384. if (idx === 0) return;
  385. var firstCell = row.cells && row.cells[0];
  386. if (!firstCell) return;
  387. var txt = normalizeText(firstCell.innerText || firstCell.textContent || "");
  388. var secMatch = txt.match(/(\d+(?:,\d+)*)节/);
  389. var timeMatch = txt.match(/(\d{1,2}:\d{2})\s*-\s*(\d{1,2}:\d{2})/);
  390. if (!secMatch || !timeMatch) return;
  391. var nums = secMatch[1].split(",").map(function (x) { return parseInt(x, 10); }).filter(function (n) { return !isNaN(n); });
  392. if (!nums.length) return;
  393. var start = timeMatch[1].padStart(5, "0");
  394. var end = timeMatch[2].padStart(5, "0");
  395. var startMin = toMin(start);
  396. var endMin = toMin(end);
  397. var step = (endMin - startMin) / nums.length;
  398. nums.forEach(function (num, index) {
  399. if (seen[num]) return;
  400. var item = {
  401. number: num,
  402. section: num,
  403. startTime: toHHMM(startMin + step * index),
  404. endTime: toHHMM(index === nums.length - 1 ? endMin : (startMin + step * (index + 1)))
  405. };
  406. seen[num] = true;
  407. result.push(item);
  408. });
  409. });
  410. result.sort(function (a, b) { return a.number - b.number; });
  411. return result;
  412. }
  413. function parseCourseConfig(doc) {
  414. var config = {
  415. firstDayOfWeek: 1,
  416. semesterStartDate: null
  417. };
  418. var termSelect = doc.querySelector("#xnxq01id");
  419. if (termSelect) {
  420. var selectedOption = termSelect.options && termSelect.selectedIndex >= 0 ? termSelect.options[termSelect.selectedIndex] : null;
  421. var termValue = normalizeText((selectedOption && (selectedOption.value || selectedOption.text)) || termSelect.value || "");
  422. if (termValue) {
  423. config.term = termValue;
  424. var m = termValue.match(/^(\d{4})-(\d{4})-(\d)$/);
  425. if (m) {
  426. config.schoolYear = m[1] + "-" + m[2];
  427. config.termName = "第" + m[3] + "学期";
  428. }
  429. }
  430. }
  431. var weekSelect = doc.querySelector("#zc");
  432. if (weekSelect) {
  433. var maxWeek = 0;
  434. Array.from(weekSelect.options || []).forEach(function (opt) {
  435. var n = parseInt(opt.value, 10);
  436. if (!isNaN(n) && n > maxWeek) maxWeek = n;
  437. });
  438. if (maxWeek > 0) {
  439. config.totalWeeks = maxWeek;
  440. config.semesterTotalWeeks = maxWeek;
  441. }
  442. }
  443. config.defaultClassDuration = 45;
  444. config.defaultBreakDuration = 10;
  445. return config;
  446. }
  447. function parseFromTable(doc) {
  448. var table = getCourseTable(doc);
  449. if (!table) throw new Error("未找到课表表格");
  450. var rows = Array.from(table.querySelectorAll("tr"));
  451. if (rows.length < 2) throw new Error("课表表格行数不足");
  452. var headerRow = rows[0];
  453. var headerCells = Array.from((headerRow && headerRow.cells) || []);
  454. if (headerCells.length < 8 && rows[0].querySelectorAll("th,td").length >= 8) {
  455. headerCells = Array.from(rows[0].querySelectorAll("th,td"));
  456. }
  457. if (headerCells.length < 8) throw new Error("课表表头异常");
  458. var dayMap = {};
  459. for (var i = 1; i < headerCells.length; i++) {
  460. var day = parseDayFromHeader(headerCells[i].innerText || headerCells[i].textContent || "");
  461. if (day) dayMap[i] = day;
  462. }
  463. if (Object.keys(dayMap).length < 7) throw new Error("星期列识别不完整");
  464. var courses = [];
  465. for (var r = 1; r < rows.length; r++) {
  466. var cells = Array.from(rows[r].cells || []);
  467. if (cells.length < 8) continue;
  468. for (var c = 1; c <= 7 && c < cells.length; c++) {
  469. var dayNum = dayMap[c];
  470. if (!dayNum) continue;
  471. var cell = cells[c];
  472. var cellText = normalizeText(cell.innerText || cell.textContent || "");
  473. if (!cellText || isMeaninglessLine(cellText)) continue;
  474. var blocks = splitCoursesInCell(doc, cell);
  475. blocks.forEach(function (block) {
  476. var parsed = parseCourseBlock(block, dayNum);
  477. if (parsed) courses.push(parsed);
  478. });
  479. }
  480. }
  481. return {
  482. courses: dedupeCourses(courses),
  483. timeSlots: parseTimeSlots(doc),
  484. config: parseCourseConfig(doc)
  485. };
  486. }
  487. function toFinalCourse(course) {
  488. var weeks = (course.weeks || []).map(function (x) { return Number(x); }).filter(function (x) { return !isNaN(x); }).sort(function (a, b) { return a - b; });
  489. var day = Number(course.day);
  490. var finalCourse = {
  491. name: String(course.name || "").trim(),
  492. teacher: String(course.teacher || "").trim(),
  493. position: String(course.position || "").trim(),
  494. day: day,
  495. startSection: Number(course.startSection),
  496. endSection: Number(course.endSection),
  497. weeks: weeks
  498. };
  499. finalCourse.location = finalCourse.position;
  500. finalCourse.dayOfWeek = day;
  501. finalCourse.startWeek = weeks.length ? weeks[0] : 0;
  502. finalCourse.endWeek = weeks.length ? weeks[weeks.length - 1] : 0;
  503. finalCourse.customWeeks = weeks.slice();
  504. if (course.courseNature) finalCourse.courseNature = course.courseNature;
  505. if (course.note) finalCourse.note = String(course.note).trim();
  506. if (course.isOddWeek) finalCourse.isOddWeek = true;
  507. if (course.isEvenWeek) finalCourse.isEvenWeek = true;
  508. return finalCourse;
  509. }
  510. async function main() {
  511. if (!window.AndroidBridgePromise || !window.AndroidBridge || typeof window.AndroidBridge.notifyTaskCompletion !== "function") {
  512. throw new Error("桥接接口不可用");
  513. }
  514. var targetDoc = await waitFor(function () {
  515. var docs = getAccessibleDocuments();
  516. for (var i = 0; i < docs.length; i++) {
  517. var doc = docs[i];
  518. try {
  519. var href = String((doc.location && doc.location.href) || "");
  520. if (/\/xskb\/xskb_list\.do/i.test(href) && getCourseTable(doc)) return doc;
  521. } catch (_) {}
  522. }
  523. for (var j = 0; j < docs.length; j++) {
  524. if (isScheduleDoc(docs[j])) return docs[j];
  525. }
  526. return null;
  527. }, 20000, 300);
  528. if (!targetDoc) {
  529. throw new Error("当前页面不是可解析的课表页,或课表 iframe 未加载完成");
  530. }
  531. var parsed = parseFromTable(targetDoc);
  532. if (!parsed || !parsed.courses || !parsed.courses.length) {
  533. throw new Error("未解析到任何课程,请确认当前学期课表已加载且不是空课表");
  534. }
  535. var finalCourses = parsed.courses.map(toFinalCourse).filter(function (c) {
  536. return c.name &&
  537. c.day >= 1 && c.day <= 7 &&
  538. c.startSection > 0 &&
  539. c.endSection >= c.startSection &&
  540. Array.isArray(c.weeks) &&
  541. c.weeks.length > 0;
  542. });
  543. if (!finalCourses.length) {
  544. throw new Error("课程字段转换后为空,无法保存");
  545. }
  546. await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(finalCourses));
  547. if (parsed.timeSlots && parsed.timeSlots.length) {
  548. await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(parsed.timeSlots));
  549. }
  550. if (parsed.config) {
  551. await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify(parsed.config));
  552. }
  553. toast("课表导入成功,共" + finalCourses.length + "门课程");
  554. window.AndroidBridge.notifyTaskCompletion();
  555. }
  556. try {
  557. await main();
  558. } catch (error) {
  559. await fail("解析学期理论课表失败", error);
  560. }
  561. })();