Browse Source

add:添加中国石油大学(北京)克拉玛依校区的适配 (#229)

* Adapt CUPK course schedule script for Karamay campus

This script adapts the course schedule for the Karamay campus of China University of Petroleum (Beijing) to the same academic system as the main campus. It includes functions for user prompts, semester retrieval, course data fetching, and configuration saving.

* Create adapters.yaml

* cupk修改

* 添加中国石油大学北京克拉玛依校区
ARK-VALLEY 3 weeks ago
parent
commit
05d1b10260
3 changed files with 287 additions and 0 deletions
  1. 5 0
      index/root_index.yaml
  2. 9 0
      resources/CUPK/adapters.yaml
  3. 273 0
      resources/CUPK/cupk_01.js

+ 5 - 0
index/root_index.yaml

@@ -381,3 +381,8 @@ schools:
     name: "山东师范大学"
     initial: "S"
     resource_folder: "SDNU"
+
+  - id: "CUPK"
+    name: "中国石油大学(北京)克拉玛依校区"
+    initial: "Z"
+    resource_folder: "CUPK"

+ 9 - 0
resources/CUPK/adapters.yaml

@@ -0,0 +1,9 @@
+# resources/CUPK/adapters.yaml
+adapters:
+  - adapter_id: "CUPK_01"
+    adapter_name: "中国石油大学(北京)克拉玛依校区本科教务系统"
+    category: "BACHELOR_AND_ASSOCIATE"
+    asset_js_path: "cupk_01.js"
+    import_url: "https://eams.cupk.edu.cn/student/"
+    maintainer: "ARK-VALLEY"
+    description: "登录后直接导入既可,无需进入课表页面,注意登录密码不是融合门户密码,初始密码为个人完整身份证号"

+ 273 - 0
resources/CUPK/cupk_01.js

@@ -0,0 +1,273 @@
+// resources/CUPK/CUPK_01.js
+// 中国石油大学(北京)克拉玛依校区拾光课程表适配脚本
+// 由larryyan适配的中国石油大学(北京)本科生拾光课程表适配脚本(CUP_01.js)修改而来
+// 由于克拉玛依校区本科生自2026春季学期起更换为本部同一套教务系统,本克砖鼠鼠遂充当CV工程师完成适配(拿来把你)
+// 在此感谢前人的智慧!
+
+// 1. 显示一个公告信息弹窗
+async function promptUserToStart() {
+    try {
+        console.log("即将显示公告弹窗...");
+        const confirmed = await window.AndroidBridgePromise.showAlert(
+            "重要通知",
+            "导入前请确保您已成功登录教务系统,并选定正确的学期。",
+            "好的,开始"
+        );
+        if (confirmed) {
+            console.log("用户点击了确认按钮。Alert Promise Resolved: " + confirmed);
+            AndroidBridge.showToast("Alert:用户点击了确认!");
+            return true; // 成功时返回 true
+        } else {
+            console.log("用户点击了取消按钮或关闭了弹窗。Alert Promise Resolved: " + confirmed);
+            AndroidBridge.showToast("Alert:用户取消了!");
+            return false; // 用户取消时返回 false
+        }
+    } catch (error) {
+        console.error("显示公告弹窗时发生错误:", error);
+        AndroidBridge.showToast("Alert:显示弹窗出错!" + error.message);
+        return false; // 出现错误时也返回 false
+    }
+}
+
+// 2. 获取学期信息
+async function getSemesterIndex() {
+    try {
+        const response = await fetch(`https://eams.cupk.edu.cn/student/for-std/course-table`)
+        const htmlString = await response.text();
+        const parser = new DOMParser();
+        const dom = parser.parseFromString(htmlString, 'text/html');
+        const selectElement = dom.getElementById('semesters') || dom.getElementById('allSemesters');
+        
+        if (!selectElement) {
+            throw new Error("页面中未找到学期选择框");
+        }
+
+        // 1. 将所有 option 转换为数组
+        const options = Array.from(selectElement.options);
+        
+        // 2. 过滤掉 "全部学期" (value="all"),因为导入课表通常只能导具体的某一学期
+        const validOptions = options.filter(opt => opt.value !== "all");
+        
+        if (validOptions.length === 0) {
+            throw new Error("未解析到有效的学期列表");
+        }
+
+        // 3. 提取用于展示的文本数组和用于请求的 value 数组
+        const semesterTexts = validOptions.map(opt => opt.text);  // 例: ["2025-2026-2", "2025-2026-1", ...]
+        const semesterValues = validOptions.map(opt => opt.value); // 例: ["191", "171", ...]
+
+        // 4. 调用安卓原生弹窗,让用户选择
+        const selectedIndex = await window.AndroidBridgePromise.showSingleSelection(
+            "选择学期", 
+            JSON.stringify(semesterTexts), // 必须是 JSON 字符串
+            0 // 默认选中第一个(通常是最新学期)
+        );
+
+        // 5. 判断用户的选择结果
+        if (selectedIndex !== null && selectedIndex >= 0) {
+            // 根据选中的索引,获取对应的学期 ID (value)
+            const selectedValue = semesterValues[selectedIndex];
+            if (typeof AndroidBridge !== 'undefined' && AndroidBridge.showToast) {
+                AndroidBridge.showToast("已选择学期: " + semesterTexts[selectedIndex]);
+            }
+            return selectedValue; // 成功时返回学期编号 (例如 "191")
+        } else {
+            // 用户取消了选择
+            console.log("用户取消了学期选择");
+            return null;
+        }
+    } catch (error) {
+        console.error("获取学期信息时发生错误:", error);
+        AndroidBridge.showToast("Alert:获取学期信息出错!" + error.message);
+        return null; // 出现错误时返回 null
+    }    
+}
+
+// 3. 获取课程数据
+async function fetchPrintData(semesterIndex) {
+    try {
+        const responds = await fetch(`https://eams.cupk.edu.cn/student/for-std/course-table/semester/${semesterIndex}/print-data`);
+        if (!responds.ok) {
+            throw new Error(`网络请求失败,状态码: ${responds.status}`);
+        }
+        const printData = await responds.json();
+        return printData;
+    } catch (error) {
+        console.error("获取数据时发生错误:", error);
+        AndroidBridge.showToast("Alert:获取数据出错!" + error.message);
+        return null;
+    }
+}
+
+// 4. 导入课程数据
+async function parseCourses(printData) {
+    console.log("正在导入课程数据...");
+
+    const activities = printData.studentTableVms[0].activities;
+    const parsedCourses = activities.map(activity => {
+        // 返回拾光要求的标准结构
+        return {
+            name: activity.courseName,                                      // 课程名称
+            teacher: activity.teachers ? activity.teachers.join(" ") : "",  // 授课教师
+            position: activity.campus ? `${activity.campus} ${activity.room}` : (activity.room || "未知地点"),
+                                                                            // 上课地点
+            day: activity.weekday,                                          // 星期几 (1-7)
+            startSection: activity.startUnit,                               // 开始节次
+            endSection: activity.endUnit,                                   // 结束节次
+            weeks: activity.weekIndexes                                     // 上课周次数组
+        };
+    });
+
+    try {
+        console.log("正在尝试导入课程...");
+        const result = await window.AndroidBridgePromise.saveImportedCourses(JSON.stringify(parsedCourses));
+        if (result === true) {
+            console.log("课程导入成功!");
+            AndroidBridge.showToast("测试课程导入成功!");
+        } else {
+            console.log("课程导入未成功,结果:" + result);
+            AndroidBridge.showToast("测试课程导入失败,请查看日志。");
+        }
+    } catch (error) {
+        console.error("导入课程时发生错误:", error);
+        AndroidBridge.showToast("导入课程失败: " + error.message);
+    }
+}
+
+// 5. 导入预设时间段
+async function importPresetTimeSlots(printData) {
+    console.log("正在准备预设时间段数据...");
+
+    function formatTime(timeInt) {
+        // 将数字转为字符串,并在前面补0直到长度为4
+        const timeStr = timeInt.toString().padStart(4, '0'); 
+        // 截取前两位作为小时,后两位作为分钟,中间加冒号
+        return `${timeStr.slice(0, 2)}:${timeStr.slice(2, 4)}`;
+    }
+
+    const courseUnitList = printData.studentTableVms[0].timeTableLayout.courseUnitList;
+    const presetTimeSlots = courseUnitList.map(unit => {
+        return {
+            number: unit.indexNo,                 // 节次编号
+            startTime: formatTime(unit.startTime), // 开始时间
+            endTime: formatTime(unit.endTime)      // 结束时间
+        };
+    });
+    
+    try {
+        console.log("正在尝试导入预设时间段...");
+        const result = await window.AndroidBridgePromise.savePresetTimeSlots(JSON.stringify(presetTimeSlots));
+        if (result === true) {
+            console.log("预设时间段导入成功!");
+            window.AndroidBridge.showToast("测试时间段导入成功!");
+        } else {
+            console.log("预设时间段导入未成功,结果:" + result);
+            window.AndroidBridge.showToast("测试时间段导入失败,请查看日志。");
+        }
+        return result; // 返回导入结果,供流程控制使用
+    } catch (error) {
+        console.error("导入时间段时发生错误:", error);
+        window.AndroidBridge.showToast("导入时间段失败: " + error.message);
+    }
+}
+
+// 6. 导入课表配置
+async function saveConfig(semesterIndex) {
+    console.log("正在准备配置数据...");
+
+    const responds = await fetch(`https://eams.cupk.edu.cn/student/ws/semester/get/${semesterIndex}`);
+    if (!responds.ok) {
+        throw new Error(`网络请求失败,状态码: ${responds.status}`);
+    }
+    const semesterInfo = await responds.json();
+    const startDate = new Date(semesterInfo.startDate);
+    const endDate = new Date(semesterInfo.endDate);
+    const diffDays = Math.ceil(Math.abs(endDate - startDate) / (1000 * 60 * 60 * 24));
+    const calculatedWeeks = Math.ceil(diffDays / 7);
+    // 注意:只传入要修改的字段,其他字段(如 semesterTotalWeeks)会使用 Kotlin 模型中的默认值
+    const courseConfigData = {
+        "semesterStartDate": semesterInfo.startDate,
+        "semesterTotalWeeks": calculatedWeeks,
+        "defaultClassDuration": 45,
+        "defaultBreakDuration": 5,
+        "firstDayOfWeek": semesterInfo.weekStartOnSunday ? 7 : 1
+    };
+
+    try {
+        console.log("正在尝试导入课表配置...");
+        const configJsonString = JSON.stringify(courseConfigData);
+
+        const result = await window.AndroidBridgePromise.saveCourseConfig(configJsonString);
+
+        if (result === true) {
+            console.log("课表配置导入成功!");
+            AndroidBridge.showToast("测试配置导入成功!开学日期: " + startDate.toISOString().split('T')[0]);
+        } else {
+            console.log("课表配置导入未成功,结果:" + result);
+            AndroidBridge.showToast("测试配置导入失败,请查看日志。");
+        }
+        return result; // 返回导入结果,供流程控制使用
+    } catch (error) {
+        console.error("导入配置时发生错误:", error);
+        AndroidBridge.showToast("导入配置失败: " + error.message);
+    }
+}
+
+
+/**
+ * 编排整个课程导入流程。
+ * 在任何一步用户取消或发生错误时,都会立即退出,AndroidBridge.notifyTaskCompletion()应该只在成功后调用  
+ */
+async function runImportFlow() {
+    AndroidBridge.showToast("课程导入流程即将开始...");
+
+    // 1. 公告和前置检查。
+    const alertConfirmed = await promptUserToStart();
+    if (!alertConfirmed) {
+        return; // 用户取消,立即退出函数
+    }
+    
+    // 2. 获取学期。
+    const semesterIndex = await getSemesterIndex();
+    if (semesterIndex === null) {
+        AndroidBridge.showToast("导入已取消。");
+        // 用户取消,直接退出
+        return;
+    }
+
+    // 3. 获取课程数据
+    const printData = await fetchPrintData(semesterIndex);
+    if (printData === null) {
+        AndroidBridge.showToast("导入已取消。");
+        // 请求失败或无数据,直接退出
+        return;
+    }
+
+    // 4. 解析课程信息。
+    const courses = await parseCourses(printData);
+    if (courses === null) {
+        // 请求失败或无数据,直接退出
+        return;
+    }
+
+    // 5. 导入时间段数据。
+    const timeSlotImportResult = await importPresetTimeSlots(printData);
+    if (!timeSlotImportResult) {
+        // 时间段导入失败,直接退出
+        return;
+    }
+    
+    // 6. 保存配置数据 (例如学期开始日期)
+    const configSaveResult = await saveConfig(semesterIndex);
+    if (!configSaveResult) {
+        // 保存配置失败,直接退出
+        return;
+    }
+
+    // 7. 流程**完全成功**,发送结束信号。
+    AndroidBridge.showToast("所有任务已完成!");
+    AndroidBridge.notifyTaskCompletion();
+}
+
+// 启动所有演示
+runImportFlow();