// ===== 登录检查 ===== const checkLogin = () => { const hostnameOk = window.location.hostname === "jwgln.cqjtu.edu.cn"; const nameEl = document.querySelector(".userInfo span:last-child"); const nameOk = nameEl && nameEl.innerText.trim().length > 0; return hostnameOk && nameOk; }; const getUserName = () => { const el = document.querySelector(".userInfo span:last-child"); return el ? el.innerText.trim() : ""; }; // ===== 工具函数 ===== // 周次展开: "1-9,11-16(单周)" → [1,3,5,7,9,11,13,15] function parseWeeks(weekStr) { weekStr = weekStr.replace(/\[\d+(?:-\d+)?节\]$/, ""); const typeMatch = weekStr.match(/\(([^)]+)\)$/); const weekType = typeMatch ? typeMatch[1] : "周"; const pureWeekStr = weekStr.replace(/\([^)]+\)$/, ""); const weekRanges = pureWeekStr.split(","); let weeks = []; for (const range of weekRanges) { const parts = range.split("-"); const start = Number(parts[0]); const end = parts.length > 1 ? Number(parts[1]) : start; for (let i = start; i <= end; i++) { weeks.push(i); } } if (weekType === "单周") { weeks = weeks.filter((w) => w % 2 === 1); } else if (weekType === "双周") { weeks = weeks.filter((w) => w % 2 === 0); } return weeks; } // 提取节次: "1-9,11-16(周)[01-02节]" → { start: 1, end: 2 } function parseSections(weekStr) { const match = weekStr.match(/\[(\d+)(?:-(\d+))?节\]/); if (!match) return null; const start = Number(match[1]); const end = match[2] ? Number(match[2]) : start; return { start, end }; } // 格式化分钟为 HH:MM function formatTime(minutes) { const h = Math.floor(minutes / 60); const m = minutes % 60; return `${String(h).padStart(2, "0")}:${String(m).padStart(2, "0")}`; } // 计算时间槽 function calculateTimeSlots(sectionNumbers, startH, startM, endH, endM) { const n = sectionNumbers.length; const totalMinutes = endH * 60 + endM - (startH * 60 + startM); const rawDuration = Math.floor(totalMinutes / n); const z = Math.floor(rawDuration / 5) * 5; const interval = n > 1 ? Math.floor((totalMinutes - z * n) / (n - 1)) : 0; return sectionNumbers.map((num, idx) => { const offset = idx * (z + interval); const start = startH * 60 + startM + offset; const end = start + z; return { number: num, startTime: formatTime(start), endTime: formatTime(end), }; }); } // 根据学期值计算开学日期, 就是个简单计算,谁还不能自己手动调了,鬼知道教务处要怎么安排后续的学期 function getSemesterStartDate(semesterValue) { const year = parseInt(semesterValue.substring(0, 4)); const termType = semesterValue.slice(-1); if (termType === "1") { return `${year}-09-08`; } else { return `${year + 1}-03-02`; } } // 从timeSlots计算课程配置 function getSemesterConfig(timeSlots) { if (timeSlots.length === 0) { return {}; } const [sH, sM] = timeSlots[0].startTime.split(":").map(Number); const [eH, eM] = timeSlots[0].endTime.split(":").map(Number); const classDuration = eH * 60 + eM - (sH * 60 + sM); const defaultClassDuration = Math.round(classDuration / 5) * 5; let defaultBreakDuration = 5; if (timeSlots.length >= 2) { const [e1H, e1M] = timeSlots[0].endTime.split(":").map(Number); const [s2H, s2M] = timeSlots[1].startTime.split(":").map(Number); defaultBreakDuration = s2H * 60 + s2M - (e1H * 60 + e1M); } return { defaultClassDuration, defaultBreakDuration, semesterTotalWeeks: 18, firstDayOfWeek: 1, }; } // 在主文档和frames中查找元素 function findElementInFrames(selector) { let el = document.querySelector(selector); if (el) return el; for (let i = 0; i < window.frames.length; i++) { try { const frameDoc = window.frames[i].document; if (frameDoc) { el = frameDoc.querySelector(selector); if (el) return el; } } catch (e) {} } return null; } // ===== 主解析函数 ===== function parseSchedule() { let table = findElementInFrames("#timetable"); if (!table) { AndroidBridge.showToast("请去往学期理论课表界面"); return []; } const courses = []; const timeSlots = []; const tbody = table.querySelector("tbody"); if (!tbody) { const rows = table.querySelectorAll("tr"); if (rows.length > 0) { return parseRowsDirectly(rows); } AndroidBridge.showToast("课表结构异常"); return []; } const rows = tbody.querySelectorAll("tr"); for (let i = 1; i < rows.length; i++) { const row = rows[i]; const th = row.querySelector("th"); if (!th) continue; const thMatch = th.innerText.match(/\(([\d,]+)小节\)/); if (!thMatch) continue; const sectionNumbers = thMatch[1].split(",").map(Number); const baseStart = sectionNumbers[0]; const baseEnd = sectionNumbers[sectionNumbers.length - 1]; // 提取时间范围并计算时间槽 const timeMatch = th.innerText.match(/(\d+):(\d+)-(\d+):(\d+)/); if (timeMatch) { const startH = Number(timeMatch[1]); const startM = Number(timeMatch[2]); const endH = Number(timeMatch[3]); const endM = Number(timeMatch[4]); const slots = calculateTimeSlots( sectionNumbers, startH, startM, endH, endM, ); timeSlots.push(...slots); } const tds = row.querySelectorAll("td"); for (let day = 1; day <= 7 && day <= tds.length; day++) { const td = tds[day - 1]; const allKbDivs = td.querySelectorAll("div.kbcontent"); const kbDivs = Array.from(allKbDivs).filter((div) => { const style = div.getAttribute("style") || ""; return !style.includes("display:none"); }); if (kbDivs.length === 0) continue; for (const kbDiv of kbDivs) { const html = kbDiv.innerHTML; const parts = html.split("---------------------
"); for (const part of parts) { const firstFontMatch = part.match( /([^<]*)<\/font>/, ); if (!firstFontMatch) continue; const name = firstFontMatch[1].trim(); if (!name) continue; const teacherMatch = part.match( /]*>([^<]*)<\/font>/, ); const teacher = teacherMatch ? teacherMatch[1].trim() : "未知"; const weeksMatch = part.match( /]*>([^<]*)<\/font>/, ); if (!weeksMatch) continue; const weeksStr = weeksMatch[1].trim(); const posMatch = part.match( /]*>([^<]*)<\/font>/, ); const position = posMatch ? posMatch[1].trim() : ""; let startSection = baseStart; let endSection = baseEnd; const sectionInfo = parseSections(weeksStr); if (sectionInfo) { startSection = sectionInfo.start; endSection = sectionInfo.end; } const weeks = parseWeeks(weeksStr); if (weeks.length === 0) continue; courses.push({ name, teacher, position, day, startSection, endSection, weeks, }); } } } } return { courses, timeSlots }; } // 直接解析table的rows(无tbody的情况) function parseRowsDirectly(rows) { const courses = []; const timeSlots = []; for (let i = 1; i < rows.length; i++) { const row = rows[i]; const th = row.querySelector("th"); if (!th) continue; const thMatch = th.innerText.match(/\(([\d,]+)小节\)/); if (!thMatch) continue; const sectionNumbers = thMatch[1].split(",").map(Number); const baseStart = sectionNumbers[0]; const baseEnd = sectionNumbers[sectionNumbers.length - 1]; const timeMatch = th.innerText.match(/(\d+):(\d+)-(\d+):(\d+)/); if (timeMatch) { const startH = Number(timeMatch[1]); const startM = Number(timeMatch[2]); const endH = Number(timeMatch[3]); const endM = Number(timeMatch[4]); const slots = calculateTimeSlots( sectionNumbers, startH, startM, endH, endM, ); timeSlots.push(...slots); } const tds = row.querySelectorAll("td"); for (let day = 1; day <= 7 && day <= tds.length; day++) { const td = tds[day - 1]; const allKbDivs = td.querySelectorAll("div.kbcontent"); const kbDivs = Array.from(allKbDivs).filter((div) => { const style = div.getAttribute("style") || ""; return !style.includes("display:none"); }); if (kbDivs.length === 0) continue; for (const kbDiv of kbDivs) { const html = kbDiv.innerHTML; const parts = html.split("---------------------
"); for (const part of parts) { const firstFontMatch = part.match( /([^<]*)<\/font>/, ); if (!firstFontMatch) continue; const name = firstFontMatch[1].trim(); if (!name) continue; const teacherMatch = part.match( /]*>([^<]*)<\/font>/, ); const teacher = teacherMatch ? teacherMatch[1].trim() : "未知"; const weeksMatch = part.match( /]*>([^<]*)<\/font>/, ); if (!weeksMatch) continue; const weeksStr = weeksMatch[1].trim(); const posMatch = part.match( /]*>([^<]*)<\/font>/, ); const position = posMatch ? posMatch[1].trim() : ""; let startSection = baseStart; let endSection = baseEnd; const sectionInfo = parseSections(weeksStr); if (sectionInfo) { startSection = sectionInfo.start; endSection = sectionInfo.end; } const weeks = parseWeeks(weeksStr); if (weeks.length === 0) continue; courses.push({ name, teacher, position, day, startSection, endSection, weeks, }); } } } } return { courses, timeSlots }; } // ===== 保存函数 ===== async function saveCourses(courses) { await window.AndroidBridgePromise.saveImportedCourses( JSON.stringify(courses), ); } async function saveTimeSlots(timeSlots) { await window.AndroidBridgePromise.savePresetTimeSlots( JSON.stringify(timeSlots), ); } async function saveConfig(config) { await window.AndroidBridgePromise.saveCourseConfig(JSON.stringify(config)); } // ===== 主流程 ===== (async () => { if (!checkLogin()) { AndroidBridge.showToast("尚未登录,请先登录!"); return; } const { courses, timeSlots } = parseSchedule(); if (courses.length === 0) { AndroidBridge.showToast("未解析到任何课程"); return; } // 获取学期配置 const semesterSelect = findElementInFrames("#xnxq01id"); const courseConfigData = getSemesterConfig(timeSlots); if (semesterSelect) { courseConfigData.semesterStartDate = getSemesterStartDate( semesterSelect.value, ); } console.log("准备保存课程:", courses.length, "门"); console.log("准备保存时间槽:", timeSlots.length, "个"); console.log("准备保存配置:", JSON.stringify(courseConfigData)); await saveCourses(courses); await saveTimeSlots(timeSlots); await saveConfig(courseConfigData); AndroidBridge.showToast(`导入成功!${courses.length}门课程`); AndroidBridge.notifyTaskCompletion(); })();