| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354 |
- // 适配目标:武汉商学院-超星综合教学管理系统
- (function () {
- function toast(message) {
- if (window.AndroidBridge && typeof window.AndroidBridge.showToast === "function") {
- window.AndroidBridge.showToast(message);
- }
- }
- function sleep(ms) {
- return new Promise((resolve) => setTimeout(resolve, ms));
- }
- async function getTargetDocument() {
- if (location.href.includes("queryKbForXsd")) {
- return document;
- }
- const iframe = document.querySelector("iframe[src*='queryKbForXsd']");
- if (!iframe) return null;
- for (let i = 0; i < 20; i += 1) {
- try {
- const doc = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document);
- if (doc && doc.readyState && doc.readyState !== "loading") {
- return doc;
- }
- } catch (e) {
- // ignore
- }
- await sleep(500);
- }
- return null;
- }
- async function waitForScheduleData(doc, timeoutMs = 15000) {
- const start = Date.now();
- while (Date.now() - start < timeoutMs) {
- const cells = Array.from(doc.querySelectorAll("td.cell, td[id^='Cell']"));
- const filled = cells.filter((cell) => {
- const text = (cell.innerText || cell.textContent || "").trim();
- return text.length > 0 && /周/.test(text);
- });
- if (filled.length > 0) {
- return true;
- }
- await sleep(500);
- }
- return false;
- }
- function uniqueSortedNumbers(nums) {
- const set = new Set(nums.filter((n) => Number.isFinite(n)));
- return Array.from(set).sort((a, b) => a - b);
- }
- function parseWeekText(weekText) {
- if (!weekText) return [];
- let text = String(weekText).trim();
- if (!text) return [];
- let oddOnly = false;
- let evenOnly = false;
- if (text.includes("单")) oddOnly = true;
- if (text.includes("双")) evenOnly = true;
- text = text.replace(/周/g, "");
- text = text.replace(/\s+/g, "");
- text = text.replace(/\(.*?\)/g, "");
- text = text.replace(/(.*?)/g, "");
- const weeks = [];
- const segments = text.split(",").map((s) => s.trim()).filter(Boolean);
- segments.forEach((seg) => {
- if (!seg) return;
- const rangeMatch = seg.match(/^(\d+)-(\d+)$/);
- if (rangeMatch) {
- const start = parseInt(rangeMatch[1], 10);
- const end = parseInt(rangeMatch[2], 10);
- if (!Number.isFinite(start) || !Number.isFinite(end)) return;
- for (let w = start; w <= end; w += 1) {
- weeks.push(w);
- }
- return;
- }
- const single = parseInt(seg, 10);
- if (Number.isFinite(single)) weeks.push(single);
- });
- let filtered = weeks;
- if (oddOnly && !evenOnly) {
- filtered = weeks.filter((w) => w % 2 === 1);
- } else if (evenOnly && !oddOnly) {
- filtered = weeks.filter((w) => w % 2 === 0);
- }
- return uniqueSortedNumbers(filtered);
- }
- function splitCourseBlocks(cellText) {
- const text = cellText.replace(/\r/g, "").trim();
- if (!text) return [];
- return text
- .split(/\n{2,}/)
- .map((block) => block.trim())
- .filter(Boolean);
- }
- function extractWeeksTextFromLine(line) {
- if (!line) return { weeksText: "", rest: line || "" };
- const match = line.match(/(\d+(?:-\d+)?(?:,\d+(?:-\d+)?)*)\s*(?:\((单|双)\))?\s*周/);
- if (!match) return { weeksText: "", rest: line };
- const weeksCore = match[1];
- const oddEven = match[2] ? `(${match[2]})` : "";
- const weeksText = `${weeksCore}${oddEven}周`;
- const rest = line.replace(match[0], "").trim();
- return { weeksText, rest };
- }
- function parseCourseBlock(block) {
- const lines = block
- .split(/\n+/)
- .map((l) => l.trim())
- .filter(Boolean);
- if (!lines.length) return null;
- const name = lines[0] || "";
- let teacher = "";
- let weeksText = "";
- let position = "";
- const weekLineIndex = lines.findIndex((l) => /周/.test(l));
- if (weekLineIndex >= 0) {
- const { weeksText: extractedWeeks, rest } = extractWeeksTextFromLine(
- lines[weekLineIndex]
- );
- weeksText = extractedWeeks;
- if (weekLineIndex === 1) {
- teacher = rest || lines[1];
- }
- }
- if (!teacher && lines.length > 1) {
- teacher = lines[1];
- const { weeksText: extractedWeeks, rest } = extractWeeksTextFromLine(teacher);
- if (extractedWeeks) {
- weeksText = weeksText || extractedWeeks;
- teacher = rest;
- }
- }
- if (!weeksText) {
- for (const line of lines) {
- const { weeksText: extractedWeeks } = extractWeeksTextFromLine(line);
- if (extractedWeeks) {
- weeksText = extractedWeeks;
- break;
- }
- }
- }
- if (weekLineIndex >= 0 && weekLineIndex + 1 < lines.length) {
- position = lines[weekLineIndex + 1];
- }
- if (!position) {
- position =
- lines.find((l) => l !== name && l !== teacher && !/周/.test(l)) || "";
- }
- return {
- name: name || "未知课程",
- teacher: teacher || "",
- weeksText,
- position: position || "",
- };
- }
- function padTime(value) {
- const text = String(value || "").trim();
- const match = text.match(/^(\d{1,2}):(\d{2})$/);
- if (!match) return text;
- const h = match[1].padStart(2, "0");
- return `${h}:${match[2]}`;
- }
- function randomColor() {
- return Math.floor(Math.random() * 12) + 1;
- }
- function createId() {
- if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
- return crypto.randomUUID();
- }
- return `id-${Date.now()}-${Math.random().toString(16).slice(2, 10)}`;
- }
- function mergeCourses(courses) {
- const byKey = new Map();
- courses.forEach((course) => {
- const weeksKey = (course.weeks || []).join(",");
- const key = [course.name, course.teacher, course.position, course.day, weeksKey].join("|");
- if (!byKey.has(key)) byKey.set(key, []);
- byKey.get(key).push({ ...course });
- });
- const merged = [];
- byKey.forEach((items) => {
- items.sort((a, b) => a.startSection - b.startSection);
- let current = null;
- items.forEach((item) => {
- if (!current) {
- current = { ...item };
- return;
- }
- if (item.startSection === current.endSection + 1) {
- current.endSection = Math.max(current.endSection, item.endSection);
- } else {
- merged.push(current);
- current = { ...item };
- }
- });
- if (current) merged.push(current);
- });
- return merged;
- }
- function parseScheduleFromDocument(doc) {
- const cells = Array.from(doc.querySelectorAll("td.cell"));
- const fallbackCells = cells.length ? [] : Array.from(doc.querySelectorAll("td[id^='Cell']"));
- const targetCells = cells.length ? cells : fallbackCells;
- const courses = [];
- const seen = new Set();
- targetCells.forEach((cell) => {
- const id = cell.getAttribute("id") || "";
- const match = id.match(/^Cell(\d)(\d{1,2})$/);
- if (!match) return;
- const day = parseInt(match[1], 10);
- const startSection = parseInt(match[2], 10);
- const rowspan = parseInt(cell.getAttribute("rowspan") || "1", 10);
- const endSection = startSection + Math.max(rowspan, 1) - 1;
- const blocks = splitCourseBlocks(cell.innerText || "");
- blocks.forEach((blockText) => {
- const parsed = parseCourseBlock(blockText);
- if (!parsed) return;
- const weeks = parseWeekText(parsed.weeksText);
- if (!weeks.length) return;
- const key = [
- parsed.name,
- parsed.teacher,
- parsed.position,
- day,
- startSection,
- endSection,
- weeks.join(","),
- ].join("|");
- if (seen.has(key)) return;
- seen.add(key);
- courses.push({
- id: createId(),
- name: parsed.name,
- teacher: parsed.teacher,
- position: parsed.position,
- day,
- startSection,
- endSection,
- color: randomColor(),
- weeks,
- });
- });
- });
- return mergeCourses(courses);
- }
- function parseTimeSlots(doc) {
- const slots = [];
- const seenNumbers = new Set();
- const timeRegex = /(\d{1,2}:\d{2})/g;
- const timeCells = Array.from(
- doc.querySelectorAll("td[data-jcindex], td[data-jcIndex]")
- );
- timeCells.forEach((cell) => {
- const text = (cell.innerText || cell.textContent || "").trim();
- if (!text) return;
- const indexAttr = cell.getAttribute("data-jcindex") || cell.getAttribute("data-jcIndex");
- const numberMatch = text.match(/^(\d{1,2})/);
- const number = parseInt(indexAttr || (numberMatch && numberMatch[1]) || "", 10);
- if (!Number.isFinite(number)) return;
- const times = text.match(timeRegex) || [];
- if (times.length < 2) return;
- if (seenNumbers.has(number)) return;
- seenNumbers.add(number);
- slots.push({
- number,
- startTime: padTime(times[0]),
- endTime: padTime(times[1]),
- });
- });
- return slots.sort((a, b) => a.number - b.number);
- }
- async function run() {
- toast("开始解析课表...");
- const doc = await getTargetDocument();
- if (!doc) {
- toast("未找到课表页面 iframe");
- return;
- }
- await waitForScheduleData(doc);
- const courses = parseScheduleFromDocument(doc);
- const timeSlots = parseTimeSlots(doc);
- if (!courses.length) {
- toast("未解析到课程,请确认课表已加载完成");
- return;
- }
- try {
- const result = await window.AndroidBridgePromise.saveImportedCourses(
- JSON.stringify(courses)
- );
- if (result === true) {
- if (timeSlots.length) {
- await window.AndroidBridgePromise.savePresetTimeSlots(
- JSON.stringify(timeSlots)
- );
- }
- toast("课表导出成功");
- window.AndroidBridge.notifyTaskCompletion();
- } else {
- toast("课表导出失败,请查看控制台日志");
- }
- } catch (error) {
- toast("导出失败: " + error.message);
- }
- }
- run();
- })();
|