| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255 |
- // 南通大学拾光课程表适配脚本
- // 通过正方教务V9个人/班级课表接口获取课程,并转换为导入格式
- (function () {
- const WINTER_TIME_SLOTS = [
- { number: 1, startTime: "07:50", endTime: "08:30" },
- { number: 2, startTime: "08:40", endTime: "09:20" },
- { number: 3, startTime: "09:35", endTime: "10:15" },
- { number: 4, startTime: "10:30", endTime: "11:10" },
- { number: 5, startTime: "11:20", endTime: "12:00" },
- { number: 6, startTime: "13:30", endTime: "14:10" },
- { number: 7, startTime: "14:20", endTime: "15:00" },
- { number: 8, startTime: "15:20", endTime: "16:00" },
- { number: 9, startTime: "16:10", endTime: "16:50" },
- { number: 10, startTime: "18:30", endTime: "19:10" },
- { number: 11, startTime: "19:20", endTime: "20:00" },
- { number: 12, startTime: "20:10", endTime: "20:50" }
- ];
- const SUMMER_TIME_SLOTS = [
- { number: 1, startTime: "07:50", endTime: "08:30" },
- { number: 2, startTime: "08:40", endTime: "09:20" },
- { number: 3, startTime: "09:35", endTime: "10:15" },
- { number: 4, startTime: "10:30", endTime: "11:10" },
- { number: 5, startTime: "11:20", endTime: "12:00" },
- { number: 6, startTime: "14:00", endTime: "14:40" },
- { number: 7, startTime: "14:50", endTime: "15:30" },
- { number: 8, startTime: "15:50", endTime: "16:30" },
- { number: 9, startTime: "16:40", endTime: "17:20" },
- { number: 10, startTime: "19:00", endTime: "19:40" },
- { number: 11, startTime: "19:50", endTime: "20:30" },
- { number: 12, startTime: "20:40", endTime: "21:20" }
- ];
- function parseSections(value) {
- const numbers = String(value || "").match(/\d+/g);
- if (!numbers || numbers.length === 0) return null;
- const startSection = Number(numbers[0]);
- const endSection = Number(numbers[numbers.length - 1]);
- if (!Number.isInteger(startSection) || !Number.isInteger(endSection) || startSection < 1 || endSection < startSection) return null;
- return { startSection, endSection };
- }
- // 将周次文本转换为周数数组
- function parseWeeks(value) {
- const weeks = new Set();
- String(value || "").replace(/(/g, "(").replace(/)/g, ")").split(/[,,、;]/).forEach((part) => {
- const numbers = part.match(/\d+/g);
- if (!numbers) return;
- const start = Number(numbers[0]);
- const end = Number(numbers[numbers.length - 1]);
- const odd = part.includes("单");
- const even = part.includes("双");
- for (let week = start; week <= end; week += 1) {
- if (odd && week % 2 === 0) continue;
- if (even && week % 2 !== 0) continue;
- weeks.add(week);
- }
- });
- return [...weeks].sort((left, right) => left - right);
- }
- function normalizeStartDate(value) {
- const match = String(value || "").match(/(\d{4})[-\/.年](\d{1,2})[-\/.月](\d{1,2})/);
- if (!match) return null;
- return `${match[1]}-${match[2].padStart(2, "0")}-${match[3].padStart(2, "0")}`;
- }
- function findStartDate(value) {
- if (!value || typeof value !== "object") return normalizeStartDate(value);
- if (Array.isArray(value)) {
- const firstWeek = value.find((item) => String(item?.zs) === "1" || String(item?.zsmc) === "1") || value[0];
- return normalizeStartDate(firstWeek?.zrq || firstWeek?.zcrq || firstWeek?.rq);
- }
- const firstWeekDate = normalizeStartDate(value.zrq || value.zcrq || value.rq);
- if (firstWeekDate) return firstWeekDate;
- for (const [key, item] of Object.entries(value)) {
- const date = item && typeof item === "object" ? findStartDate(item) : null;
- if (date) return date;
- }
- return null;
- }
- async function fetchSemesterStartDate() {
- const xnm = document.querySelector("#xnm")?.value;
- const xqm = document.querySelector("#xqm")?.value;
- if (!xnm || !xqm) {
- console.warn("NTU semester start date: missing xnm or xqm", { xnm, xqm });
- return null;
- }
- const url = new URL("/jwglxt/kbcx/xskbcxZccx_cxZcByXnxq.html?gnmkdm=N2154", window.location.origin);
- try {
- const response = await fetch(url.href, {
- headers: {
- accept: "application/json, text/javascript, */*; q=0.01",
- "content-type": "application/x-www-form-urlencoded;charset=UTF-8",
- "x-requested-with": "XMLHttpRequest"
- },
- body: `xnm=${encodeURIComponent(xnm)}&xqm=${encodeURIComponent(xqm)}`,
- method: "POST",
- credentials: "include"
- });
- const rawText = await response.text();
- let data;
- try {
- data = JSON.parse(rawText);
- } catch {
- console.warn("NTU semester start date: response is not JSON", response.status, rawText);
- return null;
- }
- console.log("NTU semester start date response:", data);
- const startDate = findStartDate(data);
- console.log("NTU semester start date:", startDate || "not found");
- return startDate;
- } catch (error) {
- console.warn("NTU semester start date request failed:", error);
- return null;
- }
- }
- // 将课程字段转换为导入协议字段
- function normalizeCourses(rawCourses) {
- const uniqueCourses = new Map();
- rawCourses.forEach((rawCourse) => {
- const name = String(rawCourse.kcmc || "").replace(/[■◆▲]/g, "").trim();
- const teacher = String(rawCourse.xm || "未知").trim() || "未知";
- const position = String(rawCourse.cdmc || "待定").trim() || "待定";
- const day = Number(rawCourse.xqj);
- const sections = parseSections(rawCourse.jcs || rawCourse.jc);
- const weeks = parseWeeks(rawCourse.zcd);
- if (!name || !sections || !Number.isInteger(day) || day < 1 || day > 7 || weeks.length === 0) return;
- const course = { name, teacher, position, day, startSection: sections.startSection, endSection: sections.endSection, weeks };
- const key = [name, teacher, position, day, sections.startSection, sections.endSection, weeks.join(",")].join("|");
- if (!uniqueCourses.has(key)) uniqueCourses.set(key, course);
- });
- return [...uniqueCourses.values()].sort((left, right) => left.day - right.day || left.startSection - right.startSection || left.name.localeCompare(right.name));
- }
- // 合并同一课程的重复节次
- function mergeAndDistinctCourses(courses) {
- if (courses.length <= 1) return courses;
- const list = courses.map((course) => ({ ...course, weeks: [...new Set(course.weeks)].sort((left, right) => left - right) }));
- const sameBase = (left, right) => left.name === right.name && left.teacher === right.teacher && left.position === right.position && left.day === right.day;
- list.sort((left, right) => left.name.localeCompare(right.name) || left.teacher.localeCompare(right.teacher) || left.position.localeCompare(right.position) || left.day - right.day || left.startSection - right.startSection);
- const merged = [];
- for (const course of list) {
- const previous = merged[merged.length - 1];
- if (previous && sameBase(previous, course) && previous.startSection === course.startSection && previous.endSection === course.endSection) {
- previous.weeks = [...new Set([...previous.weeks, ...course.weeks])].sort((left, right) => left - right);
- } else if (previous && sameBase(previous, course) && previous.weeks.join(",") === course.weeks.join(",") && previous.endSection + 1 === course.startSection) {
- previous.endSection = course.endSection;
- } else {
- merged.push(course);
- }
- }
- return merged;
- }
- // 个人课表
- async function fetchApiCourses() {
- const xnm = document.querySelector("#xnm")?.value;
- const xqm = document.querySelector("#xqm")?.value;
- if (!xnm || !xqm) return null;
- const url = new URL("/jwglxt/kbcx/xskbcx_cxXsgrkb.html?gnmkdm=N2151", window.location.origin);
- try {
- const response = await fetch(url.href, {
- method: "POST",
- headers: { "content-type": "application/x-www-form-urlencoded;charset=UTF-8", "x-requested-with": "XMLHttpRequest" },
- body: `xnm=${encodeURIComponent(xnm)}&xqm=${encodeURIComponent(xqm)}&kzlx=ck&xsdm=&kclbdm=`,
- credentials: "include"
- });
- const data = await response.json();
- console.log("NTU course API response:", data);
- const rawCourses = data?.kbList || data?.datas?.kbList || data?.rows;
- if (!Array.isArray(rawCourses) || !rawCourses.length) return null;
- return mergeAndDistinctCourses(normalizeCourses(rawCourses));
- } catch (error) {
- console.warn("NTU course API request failed:", error);
- return null;
- }
- }
- // 班级课表
- async function fetchClassApiCourses() {
- const pageMap = window.api?.data?.map || {};
- const readValue = (key) => pageMap[key] ?? document.querySelector(`#${key}`)?.value ?? "";
- const requestFields = [
- "xnm",
- "xqm",
- "njdm_id",
- "zyh_id",
- "bh_id",
- "tjkbzdm",
- "tjkbzxsdm",
- "zxszjjs"
- ];
- const body = new URLSearchParams();
- requestFields.forEach((key) => {
- const value = readValue(key);
- if (value !== "" && value !== null && typeof value !== "undefined") body.set(key, String(value));
- });
- if (!body.get("xnm") || !body.get("xqm") || !body.get("bh_id")) {
- console.warn("NTU class course API parameters are incomplete", Object.fromEntries(body));
- return null;
- }
- const url = new URL("/jwglxt/kbdy/bjkbdy_cxBjKb.html", window.location.origin);
- try {
- const response = await fetch(url.href, {
- method: "POST",
- headers: { "content-type": "application/x-www-form-urlencoded;charset=UTF-8", "x-requested-with": "XMLHttpRequest" },
- body,
- credentials: "include"
- });
- const data = await response.json();
- console.log("NTU class course API response:", data);
- const rawCourses = data?.kbList || data?.datas?.kbList || data?.rows;
- if (!Array.isArray(rawCourses) || !rawCourses.length) return null;
- return mergeAndDistinctCourses(normalizeCourses(rawCourses));
- } catch (error) {
- console.warn("NTU class course API request failed:", error);
- return null;
- }
- }
- async function runImport() {
- try {
- if (!await window.shiguangBridgePromise.showAlert("南通大学课表导入", "导入前请确保已登录南通大学教务系统。", "开始导入")) return;
- const formAction = document.querySelector("#ajaxForm")?.getAttribute("action") || "";
- const isPersonalPage = /xskbcx_cxXskbcxIndex/i.test(formAction) || /\/kbcx\/xskbcx/i.test(window.location.pathname);
- const pageCourses = isPersonalPage ? await fetchApiCourses() : await fetchClassApiCourses();
- if (pageCourses?.length) {
- const pageWeeks = pageCourses.flatMap((course) => course.weeks);
- const semesterStartDate = await fetchSemesterStartDate();
- if (!semesterStartDate) throw new Error("未获取到开学日期,无法判断令时");
- const startMonth = Number(semesterStartDate.slice(5, 7));
- if (!Number.isInteger(startMonth) || startMonth < 1 || startMonth > 12) throw new Error(`开学日期格式无效:${semesterStartDate}`);
- const timeSlots = startMonth <= 6 ? WINTER_TIME_SLOTS : SUMMER_TIME_SLOTS;
- const courseConfig = { semesterTotalWeeks: Math.max(19, ...pageWeeks), defaultClassDuration: 40, firstDayOfWeek: 1 };
- courseConfig.semesterStartDate = semesterStartDate;
- await window.shiguangBridgePromise.saveCourseConfig(JSON.stringify(courseConfig));
- await window.shiguangBridgePromise.savePresetTimeSlots(JSON.stringify(timeSlots));
- await window.shiguangBridgePromise.saveImportedCourses(JSON.stringify(pageCourses));
- window.shiguangBridge.showToast(`导入成功:${pageCourses.length} 条课程安排`);
- window.shiguangBridge.notifyTaskCompletion();
- return;
- }
- window.shiguangBridge.showToast("当前页面未找到课表,请先打开个人或班级课表页面。");
- } catch (error) {
- console.error("NTU adapter error", error);
- window.shiguangBridge.showToast(`导入失败:${error.message}`);
- }
- }
- runImport();
- })();
|