| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- // 西南交通大学新教务系统课表导入脚本
- // 适配页面:https://yhxt.swjtu.edu.cn/study/teach/course/stu-course-list
- (() => {
- const SWJTU_SCHEDULE_API =
- "https://yhxt.swjtu.edu.cn/yethan/common/course-schedule/student-course-schedule";
- const SWJTU_SEMESTER_TOTAL_WEEKS = 19;
- const MAX_CLASS_TIME_FIELDS = 40;
- const SWJTU_TIME_SLOTS = [
- { number: 1, startTime: "08:00", endTime: "08:45" },
- { number: 2, startTime: "08:50", endTime: "09:35" },
- { number: 3, startTime: "09:50", endTime: "10:35" },
- { number: 4, startTime: "10:40", endTime: "11:25" },
- { number: 5, startTime: "11:30", endTime: "12:15" },
- { number: 6, startTime: "14:00", endTime: "14:45" },
- { number: 7, startTime: "14:50", endTime: "15:35" },
- { number: 8, startTime: "15:40", endTime: "16:25" },
- { number: 9, startTime: "16:40", endTime: "17:25" },
- { number: 10, startTime: "17:30", endTime: "18:15" },
- { number: 11, startTime: "19:30", endTime: "20:15" },
- { number: 12, startTime: "20:20", endTime: "21:05" },
- { number: 13, startTime: "21:10", endTime: "21:55" },
- ];
- const SWJTU_COURSE_CONFIG = {
- semesterStartDate: null,
- semesterTotalWeeks: SWJTU_SEMESTER_TOTAL_WEEKS,
- defaultClassDuration: 45,
- firstDayOfWeek: 1,
- };
- const DAY_MAP = {
- 一: 1,
- 二: 2,
- 三: 3,
- 四: 4,
- 五: 5,
- 六: 6,
- 日: 7,
- 天: 7,
- };
- function normalizeText(text) {
- return String(text || "")
- .replace(/\u00a0/g, " ")
- .replace(/ /gi, " ")
- .replace(/[\t\r\n]+/g, " ")
- .replace(/ +/g, " ")
- .trim();
- }
- function cleanValue(value) {
- const text = normalizeText(value);
- if (!text || text.toLowerCase() === "null" || text === "无") return "";
- return text;
- }
- function getBridge() {
- return {
- bridge: window.AndroidBridge || window.shiguangBridge,
- promise: window.AndroidBridgePromise || window.shiguangBridgePromise,
- };
- }
- function showToast(message) {
- const { bridge } = getBridge();
- if (bridge?.showToast) {
- bridge.showToast(message);
- } else {
- console.log(message);
- }
- }
- async function showAlert(title, message) {
- const { promise } = getBridge();
- if (promise?.showAlert) {
- await promise.showAlert(title, message, "确定");
- } else {
- console.warn(`${title}: ${message}`);
- }
- }
- function getCookieValue(name) {
- const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
- const match = document.cookie.match(
- new RegExp(`(?:^|;\\s*)${escapedName}=([^;]*)`),
- );
- return match ? decodeURIComponent(match[1]) : "";
- }
- function getStorageValue(name) {
- try {
- return localStorage.getItem(name) || sessionStorage.getItem(name) || "";
- } catch (error) {
- console.warn("读取本地登录态失败:", error);
- return "";
- }
- }
- function getYToken() {
- const candidates = [
- getCookieValue("ytoken"),
- getStorageValue("ytoken"),
- getStorageValue("YToken"),
- getStorageValue("token"),
- getStorageValue("TOKEN"),
- ];
- return candidates.map(cleanValue).find(Boolean) || "";
- }
- async function fetchSemesterSchedule() {
- const ytoken = getYToken();
- if (!ytoken) {
- throw new Error("未找到 ytoken,请确认已登录西南交通大学新教务系统。");
- }
- const response = await fetch(SWJTU_SCHEDULE_API, {
- method: "GET",
- credentials: "include",
- headers: {
- Accept: "application/json, text/plain, */*",
- "Content-Type": "application/json",
- ytoken,
- },
- });
- if (!response.ok) {
- throw new Error(`课表接口请求失败:HTTP ${response.status}`);
- }
- const payload = await response.json();
- if (payload.code !== "00000") {
- throw new Error(payload.message || `课表接口返回异常:${payload.code}`);
- }
- if (Array.isArray(payload.data)) return payload.data;
- if (Array.isArray(payload.data?.list)) return payload.data.list;
- throw new Error("课表接口返回结构异常:data 不是课程数组。");
- }
- function parseWeeks(weekText) {
- const weeks = [];
- const text = normalizeText(weekText)
- .replace(/第/g, "")
- .replace(/[[【]/g, "(")
- .replace(/[\]】]/g, ")");
- const parity = text.includes("单") ? "单" : text.includes("双") ? "双" : "";
- const cleanedText = text
- .replace(/[单双]/g, "")
- .replace(/[()()]/g, "")
- .replace(/周/g, "");
- cleanedText.split(/[,,、;]/).forEach((segment) => {
- const match = normalizeText(segment).match(
- /^(\d+)(?:\s*[-~—至]\s*(\d+))?$/,
- );
- if (!match) return;
- const start = Number(match[1]);
- const end = match[2] ? Number(match[2]) : start;
- if (!Number.isFinite(start) || !Number.isFinite(end) || start > end)
- return;
- for (let week = start; week <= end; week++) {
- if (parity === "单" && week % 2 !== 1) continue;
- if (parity === "双" && week % 2 !== 0) continue;
- if (!weeks.includes(week)) weeks.push(week);
- }
- });
- return weeks.sort((a, b) => a - b);
- }
- function parseClassTime(classTime) {
- const text = cleanValue(classTime);
- if (!text) return [];
- const results = [];
- const pattern =
- /((?:第?\d+(?:\s*[-~—至]\s*\d+)?(?:\s*[,,、]\s*第?\d+(?:\s*[-~—至]\s*\d+)?)*\s*周(?:\s*[((]?\s*[单双]\s*[))]?\s*周?)?))\s*(?:星期|周)([一二三四五六日天])\s*第?\s*(\d+)(?:\s*[-~—至]\s*(\d+))?\s*节/g;
- let match = pattern.exec(text);
- while (match !== null) {
- const weeks = parseWeeks(match[1]);
- const day = DAY_MAP[match[2]];
- const startSection = Number(match[3]);
- const endSection = match[4] ? Number(match[4]) : startSection;
- if (
- weeks.length > 0 &&
- day &&
- Number.isFinite(startSection) &&
- Number.isFinite(endSection) &&
- startSection <= endSection
- ) {
- results.push({ weeks, day, startSection, endSection });
- }
- match = pattern.exec(text);
- }
- return results;
- }
- function buildTeacher(item) {
- const names = [item.staffName, item.staffNameOther]
- .flatMap((value) => cleanValue(value).split(/[,,、;;]/))
- .map(cleanValue)
- .filter(Boolean);
- return Array.from(new Set(names)).join(",") || "未指定";
- }
- function buildCourses(apiCourses) {
- const courses = [];
- const skipped = [];
- apiCourses.forEach((item) => {
- const name = cleanValue(item.courseName || item.name || item.kcmc);
- if (!name) return;
- const teacher = buildTeacher(item);
- let hasSpecificSchedule = false;
- let hasAnySchedule = false;
- for (let index = 1; index <= MAX_CLASS_TIME_FIELDS; index++) {
- const classTime = cleanValue(item[`classTime${index}`]);
- const classPlace = cleanValue(item[`classPlace${index}`]);
- if (!classTime) continue;
- hasAnySchedule = true;
- const parsedTimes = parseClassTime(classTime);
- if (parsedTimes.length === 0) continue;
- hasSpecificSchedule = true;
- parsedTimes.forEach((time) => {
- courses.push({
- name,
- teacher,
- position: classPlace || cleanValue(item.campusName) || "未指定",
- day: time.day,
- startSection: time.startSection,
- endSection: time.endSection,
- weeks: time.weeks,
- });
- });
- }
- if (hasAnySchedule && !hasSpecificSchedule) {
- skipped.push(name);
- }
- });
- return { courses, skipped };
- }
- async function saveImportResult(courses, skipped) {
- const { bridge, promise } = getBridge();
- if (!promise?.saveImportedCourses) {
- throw new Error(
- "未找到保存课表的 Bridge,请在拾光 App 或测试插件中执行。",
- );
- }
- await promise.saveCourseConfig(JSON.stringify(SWJTU_COURSE_CONFIG));
- await promise.savePresetTimeSlots(JSON.stringify(SWJTU_TIME_SLOTS));
- await promise.saveImportedCourses(JSON.stringify(courses));
- if (skipped.length > 0) {
- showToast(
- `成功导入 ${courses.length} 条课程,另有 ${skipped.length} 门无具体节次课程已跳过`,
- );
- } else {
- showToast(`成功导入 ${courses.length} 条课程`);
- }
- bridge?.notifyTaskCompletion?.();
- }
- async function importSwjtuSchedule() {
- try {
- showToast("正在通过西南交通大学新教务接口获取课表...");
- const apiCourses = await fetchSemesterSchedule();
- const { courses, skipped } = buildCourses(apiCourses);
- if (courses.length === 0) {
- throw new Error(
- `接口返回 ${apiCourses.length} 门课程,但没有解析到带星期和节次的上课安排。`,
- );
- }
- await saveImportResult(courses, skipped);
- return true;
- } catch (error) {
- console.error("SWJTU 新教务课表导入失败:", error);
- await showAlert("导入失败", `解析或保存课表失败:${error.message}`);
- return false;
- }
- }
- void importSwjtuSchedule();
- })();
|