简介

小爱课程表是一款非常实用的工具,可以帮助我们轻松管理课表信息。然而,不同学校的教务系统接口和数据格式各不相同,因此需要针对具体学校进行适配。本文将详细记录我为河北环境工程学院教务系统适配小爱课程表的完整过程,希望能为大家提供参考。

image.png

本校原有的一个适配方案虽然能用, 但目前存在以下几个问题:

  • 使用的教务系统url为内网, 必须连接校园网才可使用
  • 登录页面不是统一认证的登录入口, 需要额外的账号密码
  • 大概率是采用解析html的方案, 并且无法正常解析出教师信息
  • 课程时间应该没有设置, 默认的会错乱, 需要手动调整
  • 关于教室地点的信息太长, 例如 主校区 综合教学楼B102, 但实际上有效信息只有 B102, 太长可能导致主页小组件页面显示不全
  • image-lIrc.png

image-SaaA.png

image-quRM.png

所以干脆自己适配一个解决以上痛点, 目前已审核通过并上线

image-RdJC.png

参考资料

官方文档:https://open-schedule-prod.ai.xiaomi.com/docs/#/help/

同样是正方教务系统的一个博客指南:https://zhul.in/2024/11/18/mi-ai-class-schedule-adapter-for-zjut/

一、环境搭建(请参考官方文档)

image-PddI.png

  1. 安装浏览器插件下载小米提供的资源包(最新版本为 v0.3.8),解压后安装到 Chromium-based 浏览器中(如 Chrome 或 Edge)。安装完成后,浏览器会多出一个名为「AISchedule」的选项。
  2. 登录教务系统
    打开学校的教务网站,手动登录账号并进入课表页面。确保登录状态有效,以便后续抓包和调试。

二、抓取课表数据

image-mQVg.png

  1. 抓包分析在课表页面刷新后,使用浏览器的开发者工具(F12)查看网络请求,找到包含课表信息的请求。通常这类请求会以 JSON 格式返回数据。
  2. 复制 fetch 请求
    右键点击目标请求,选择「复制为 Fetch 语句」,将其粘贴到控制台中运行,验证是否能获取到课表数据。
fetch("https://jw.hebuee.edu.cn/kbcx/xskbcx_cxXsgrkb.html?gnmkdm=N2151", {
  "headers": {
    "accept": "*/*",
    "accept-language": "zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6",
    "content-type": "application/x-www-form-urlencoded;charset=UTF-8",
    "sec-ch-ua": "\"Chromium\";v=\"134\", \"Not:A-Brand\";v=\"24\", \"Microsoft Edge\";v=\"134\"",
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": "\"Windows\"",
    "sec-fetch-dest": "empty",
    "sec-fetch-mode": "cors",
    "sec-fetch-site": "same-origin",
    "x-requested-with": "XMLHttpRequest"
  },
  "referrer": "https://jw.hebuee.edu.cn/kbcx/xskbcx_cxXskbcxIndex.html?gnmkdm=N2151&layout=default",
  "referrerPolicy": "strict-origin-when-cross-origin",
  "body": "xnm=2024&xqm=12&kzlx=ck&xsdm=",
  "method": "POST",
  "mode": "cors",
  "credentials": "include"
});
  1. 精简请求参数
    删除不必要的请求头和参数,保留核心部分(如 xnmxqm),确保请求能正常返回数据。
fetch("https://jw.hebuee.edu.cn/kbcx/xskbcx_cxXsgrkb.html?gnmkdm=N2151", {
  "headers": {
    "accept": "*/*",
    "content-type": "application/x-www-form-urlencoded;charset=UTF-8",
    "x-requested-with": "XMLHttpRequest"
  },
  "referrerPolicy": "strict-origin-when-cross-origin",
  "body": `xnm=${year}&xqm=${xqm}&kzlx=ck&xsdm=`,
  "method": "POST",
  "mode": "cors",
  "credentials": "include"
});

三、核心代码模块说明

小爱课程表适配需要三个核心代码文件协同工作,各文件作用如下:

文件名称 功能描述 输入输出关系
Provider.js 数据获取层:负责与教务系统交互,获取原始课表数据 输入用户参数 → 输出原始JSON数据
Parser.js 数据解析层:将原始数据转换为小爱课程表标准格式 输入原始JSON → 输出结构化课程数据
Timer.js 配置支撑层:定义课程时间体系(节次时间、学期周数等) 无输入 → 输出时间配置对象

Provider和Timer都是运行在浏览器环境, 而Parser会运行在服务器的nodejs环境, 大概是为了防止被批量抓包代码吧

由于本校的教务系统和参考的教程中的教务系统都是正方教务, 所以代码基本可以直接copy过来小作修改

四、代码实现详解

4.1 Provider.js(数据获取)

Provider.js 的任务是从教务系统获取课表数据并返回。以下是关键代码:

async function scheduleHtmlProvider() {
  // 加载小爱课程表工具库
  await loadTool('AIScheduleTools');
  try {
    // 弹窗获取用户输入的学年(示例默认2024)
    const year = await AISchedulePrompt({
      titleText: '学年',
      tipText: '请输入本学年开始的年份',
      defaultText: '2024',
      validator: value => {
        try {
          const v = parseInt(value);
          if (v < 2000 || v > 2100) {
            return '请输入正确的学年';
          }
          return false;
        } catch (error) {
          return '请输入正确的学年';
        }
      }
    });

    // 弹窗获取学期参数(1/2/3对应不同学期代码)
    const term = await AISchedulePrompt({
      titleText: '学期',
      tipText: '请输入学期(1表示第一学期,2表示第二学期,3表示短学期)',
      defaultText: '2',
      validator: value => {
        if (value === '1' || value === '2' || value === '3') {
          return false;
        }
        return '请输入正确的学期';
      }
    });

    // 学期参数映射(正方系统特殊编码规则)
    const xqm = {
      '1': '3',
      '2': '12',
      '3': '16',
    }[term];

    // 发起 fetch 请求获取课表数据
    const res = await fetch("https://jw.hebuee.edu.cn/kbcx/xskbcx_cxXsgrkb.html?gnmkdm=N2151", {
      "headers": {
        "accept": "*/*",
        "content-type": "application/x-www-form-urlencoded;charset=UTF-8",
        "x-requested-with": "XMLHttpRequest"
      },
      "referrerPolicy": "strict-origin-when-cross-origin",
      "body": `xnm=${year}&xqm=${xqm}&kzlx=ck&xsdm=`,
      "method": "POST",
      "mode": "cors",
      "credentials": "include"
    });

    // 返回获取到的JSON数据
    const ret = await res.json();
    return JSON.stringify(ret.kbList);
  } catch (error) {
    await AIScheduleAlert('请确定你已经登录了教务系统');
    return 'do not continue';
  }
}

4.2 Parser.js(数据解析)

Parser.js 的任务是将获取到的课表数据解析为小爱课程表所需的格式。以下是关键代码:

// 功能说明:将原始数据转换为标准课程格式
function scheduleHtmlParser(json_str) {
  // 字符串转成js对象
  let courses_json;
  try {
    courses_json = JSON.parse(json_str);
  } catch (e) {
    console.error('解析课表数据失败:', e);
    return [];
  }

  const courseInfos = [];

  courses_json.forEach(course => {
    // 核心字段提取
    // kcmc:课程名, cdmc:教室, xm:教师, zcd:周次, jc:节次, xqj:星期几
    const { kcmc, cdmc, xm, zcd, jc, xqj } = course;

    // 处理周数,去掉“周”字并解析范围, 
    // 示例: "1-3周,11-13周,15-16周" -> [1, 2, 3, 11, 12, 13, 15, 16]
    const weeks = zcd.replace(/周/g, '').split(',').flatMap(weekRange => {
      if (weekRange.includes('-')) {
        const [start, end] = weekRange.split('-').map(Number);
        return Array.from({ length: end - start + 1 }, (_, i) => start + i);
      }
      return [Number(weekRange)];
    });

    // 处理节次,去掉“节”字并解析范围, 示例: "3-5节" -> [3,4,5]
    const sections = jc.replace(/节/g, '').split('-').map(Number);

    // 处理星期
    const day = Number(xqj);
    // 处理上课地点, 如果有"综合教学楼"则删去
    // (因为只有这个楼分ABC, 太长反而会导致有用的信息被省略)
    const position = cdmc.replace(/综合教学楼/g, '');

    courseInfos.push({
      name: kcmc,
      position: cdmc,
      teacher: xm,
      weeks: weeks,
      day: Number(xqj),
      sections: sections,
    });
  });

  return courseInfos;
}

4.3 Timer.js(时间配置)

Timer.js 的任务是配置课程时间信息。由于没有找到获取课程时间的方法, 我直接写死了

// 功能说明:定义学校的时间体系
async function scheduleTimer({ providerRes, parserRes } = {}) {
  return {
    totalWeek: 20,
    startSemester: '', // 开学时间的时间戳,13位长度字符串
    startWithSunday: false,
    showWeekend: true,
    forenoon: 4, // 上午课程节数 
    afternoon: 4, // 下午课程节数
    night: 2, // 晚上课程节数
    sections: [
      { section: 1, startTime: '08:00', endTime: '08:45' }, // 第1节
      { section: 2, startTime: '08:55', endTime: '09:40' }, // 第2节
      { section: 3, startTime: '10:10', endTime: '10:55' }, // 第3节
      { section: 4, startTime: '11:05', endTime: '11:50' }, // 第4节
      { section: 5, startTime: '14:00', endTime: '14:45' }, // 第5节
      { section: 6, startTime: '14:55', endTime: '15:40' }, // 第6节
      { section: 7, startTime: '16:10', endTime: '16:55' }, // 第7节
      { section: 8, startTime: '17:05', endTime: '17:50' }, // 第8节
      { section: 9, startTime: '19:00', endTime: '19:45' }, // 第9节
      { section: 10, startTime: '19:55', endTime: '20:40' }, // 第10节
    ],
  };
}

五、调试与测试

image-WRAi.png

  1. 上传代码在小米提供的插件的压缩包中, 有一个localtools文件夹, 用vscode将其打开, 终端运行 npm i安装依赖;终端 node main.js, 此时再把浏览器中插件页面打开的话, 会自动连接.连接后在vscode中保存的代码会自动同步到开发者工具中不过也可以采用直接复制代码的方式手动同步

  2. 本地调试

    image-JGuL.png代码上传后就可以点击开始测试了, 在控制台中查看输出

    image-gZuE.png
    本地测试没问题后, 可以点击真机测试, 这时会生成一个临时可用的适配方案, 可以在手机上进行测试手机上测试导入后会有提示询问你是否导入成功, 如果选择了「完美」, 该测试版将升级为预览版

  3. 上传审核
    测试无误后,点击「提审」按钮,将代码提交到小米服务器进行审核。审核通过后,你的适配方案将对所有用户可见。

总结

小爱课程表的适配过程并不复杂,只要熟悉抓包、解析和配置的基本流程,就能顺利完成。希望本文能帮助到需要适配教务系统的同学。如果在适配过程中遇到问题,可以参考小米官方文档或联系技术支持。

一个敲代码的