前言

本文将从使用和实现两个方面来介绍小爱课程表~

注意:你的手机系统必须在MIUI12及以上。

使用

  1. 打开小爱语音助手(从下边搜索栏或者语音说小爱同学都能打开)。
  2. 说:打开课程表。
  3. 点最右边标签,选择学校,选择我编写的版本(OSSA王跃霖)。
  4. 找到教务网站导入(需要连接校园网),点进去。
  5. 输入账号密码,点击信息查询标签,再点击学生课表查询标签。
  6. 点击下方一键导入。
  7. 导入成功后,选择当前周数,改成对应周数。

实现

  1. 下载AISchdule DevTools压缩包,解压到本地。
  2. 下载Chrome,打开链接 chrome://extensions/ ,打开开发者模式,导入AISchedule DevTools文件夹。
  3. 在Chrome打开一个新的Tab,打开、登陆自己的教务系统,进入课程页面。
  4. 在网页内右键,选「检查」,打开Chrome开发者工具,会有新增的AISchedule标签,进入后请跟随新手引导,创建「适配项目」。注意:学校url请填写官方url,不要填写ip或者代理!
  5. 当你点击provider/parser函数的代码区时,会跳转到sources中,在此你可以编辑scheduleHtmlProvider函数、scheduleHtmlParser函数,并打断点debug。
  6. 你可以参考注释与示例函数,编写两个适配函数。按ctrl+s/cmd+s保存,然后在网页中右键,选「AISchedule DevTools 运行」,Console中会打印出运行结果,两个函数的输出会分别在新窗口中显示,以便你边参考边修改函数。scheduleHtmlParser函数的输出须符合以下数据结构。注意:代码中无需引用任何模块,不要带有“require”这样的字符
  7. 经过调试,如果在console中看到「All run successfully」,然后你可以上传到手机端,进行E2E自测(端到端自测)。
  8. 在手机端打开课程表的教务导入功能,搜索学校,选择自己提交的适配,你可亲自体验,验证可用性。如果你觉得没问题,请点击反馈按钮「完美」,至此,你的适配已完成,状态为审核中。
  9. 工具中会保存你的历史适配记录和适配学校的状态,如果你的适配项目发布了,会为很多同学提供便利哦~

以上来自小爱课程表 开发者工具 使用文档-本校模式官方提供的文档,下边是关于代码的内容~

本文写于20年09月11日,此期间因为疫情原因,学校分年级上课,各个年级上课时间和休息时间都不一样,所以我就let了四个年级的上下课时间表,心算的各个年级上下课时间,可能存在时间错误的问题,如果你的年级存在问题请联系我。

从官方文档知道,我们只需要编辑两个文件:
scheduleHtmlProvider.js
scheduleHtmlPasrse.js
第一个文件适配得较好,用来截取HTML片段,而你截取的片段将弹出窗口以页面的形式展现,如果你发现弹出窗口中已经包含了课程表,就可以开始编辑第二个文件了。

第二个文件是关键,我们需要通过js拿到我们想要的数据,然后对数据进行操作,这里以方正举例:

function scheduleHtmlParser(html) {
//除函数名外都可编辑
//传入的参数为上一步函数获取到的html
//可使用正则匹配
//可使用解析dom匹配,工具内置了$,跟jquery使用方法一样,直接用就可以了,参考:https://juejin.im/post/5ea131f76fb9a03c8122d6b9
//以下为示例,您可以完全重写或在此基础上更改

let result = []
let bbb = $('#table1 .timetable_con')
let wyl_id = $('.timetable_title h6')[1].children[0].data.split(':')[1]
for (let u = 0; u < bbb.length; u++) {
let re = { sections: [], weeks: [] }
let aaa = $(bbb[u]).find('span')
let week = $(bbb[u]).parent('td')[0].attribs.id
if (week) {
re.day = week.split('-')[0]
}
for (let i = 0; i < aaa.length; i++) {

if (aaa[i].attribs.title == '上课地点') {

for (let j = 0; j < $(aaa[i]).next()[0].children.length; j++) {
re.position = $(aaa[i]).next()[0].children[j].data
}
}
if (aaa[i].attribs.title == '节/周') {

for (let j = 0; j < $(aaa[i]).next()[0].children.length; j++) {

let lesson = $(aaa[i]).next()[0].children[j].data
for (let a = Number(lesson.split(')')[0].split('(')[1].split('-')[0]); a < Number(lesson.split(')')[0].split('(')[1].split('-')[1].split('节')[0]) + 1; a++) {

re.sections.push({ section: a })
}
for (let a = Number(lesson.split(')')[1].split('-')[0]); a < Number(lesson.split(')')[1].split('-')[1].split('周')[0]) + 1; a++) {

re.weeks.push(a)
}
}
}

if (aaa[i].attribs.title == '教师') {

for (let j = 0; j < $(aaa[i]).next()[0].children.length; j++) {
re.teacher = $(aaa[i]).next()[0].children[j].data
}
}

if (aaa[i].attribs.class == 'title') {

for (let j = 0; j < $(aaa[i]).children()[0].children.length; j++) {
re.name = $(aaa[i]).children()[0].children[j].data

}
}

}
result.push(re)
}
console.log(result)

return {courseInfos: result}
}

从代码知道,这里的代码按jQuery的语法去写即可,正常情况下该代码可以正常读取到方正的课程表,只不过没有时间,我们需要的是添加一个时间,这里以我校举例,我校因为疫情原因分年级上课,各个年级上课时间和休息时间都不一样,所以我就let了四个年级的上下课时间表,然后通过js拿到了学号的text,对学号的1、2位进行判断可知该学生所在的年级,let一个列表用来存时间,然后将此二位数字当做判断条件进行赋值,再在最后的return中加入sectionTimes: wyl_sectionTime即可,代码如下:

function scheduleHtmlParser(html) {
function scheduleHtmlParser(html) {
//除函数名外都可编辑
//传入的参数为上一步函数获取到的html
//可使用正则匹配
//可使用解析dom匹配,工具内置了$,跟jquery使用方法一样,直接用就可以了,参考:https://juejin.im/post/5ea131f76fb9a03c8122d6b9
//以下为示例,您可以完全重写或在此基础上更改
// //大一
// const wyl_startTime1 = ["08:00", "08:50", "09:45", "10:35", "13:00", "13:50", "14:45", "15:35", "18:30", "19:20"]
// const wyl_endTime1 = ["08:45", "09:35", "10:30", "11:20", "13:45", "14:35", "15:30", "16:20", "19:15", "20:05"]

// //大二
// const wyl_startTime2 = ["08:10", "09:00", "10:00", "10:50", "13:10", "14:00", "15:00", "15:50", "18:30", "19:20"]
// const wyl_endTime2 = ["08:55", "09:45", "10:45", "11:35", "13:55", "14:45", "15:45", "16:35", "19:15", "20:05"]

// //大三
// const wyl_startTime3 = ["08:20", "09:10", "10:15", "11:05", "13:20", "14:10", "15:15", "16:05", "18:30", "19:20"]
// const wyl_endTime3 = ["09:05", "09:55", "11:00", "11:50", "14:05", "14:55", "16:00", "16:50", "19:15", "20:05"]

// //大四
// const wyl_startTime4 = ["08:30", "09:20", "10:30", "11:20", "13:30", "14:20", "15:30", "16:20", "18:30", "19:20"]
// const wyl_endTime4 = ["09:15", "10:05", "11:15", "12:05", "14:15", "15:05", "16:15", "17:05", "19:15", "20:05"]

//算的头疼,没觉得code能力变强,倒是觉得心算能力增强了

const wyl_startTime = ["08:20", "09:15", "10:20", "11:15", "13:30", "14:25", "15:30", "16:25", "18:10", "19:05"]
const wyl_endTime = ["09:05", "10:00", "11:05", "12:00", "14:15", "15:10", "16:15", "17:10", "18:55", "19:50"]

let result = []
let bbb = $('#table1 .timetable_con')

//学号头两位,取消分年级上课以后这个就没用了- -
//let wyl_id = $('.timetable_title h6')[1].children[0].data.split(':')[1]

for (let u = 0; u < bbb.length; u++) {
let re = { sections: [], weeks: [] }
let aaa = $(bbb[u]).find('span')
let week = $(bbb[u]).parent('td')[0].attribs.id

if (week) {
re.day = week.split('-')[0]
}
for (let i = 0; i < aaa.length; i++) {

if (aaa[i].attribs.title == '上课地点') {

for (let j = 0; j < $(aaa[i]).next()[0].children.length; j++) {
re.position = $(aaa[i]).next()[0].children[j].data
}
}
if (aaa[i].attribs.title == '节/周') {

// for (let j = 0; j < $(aaa[i]).next()[0].children.length; j++) {

// let lesson = $(aaa[i]).next()[0].children[j].data

// for (let a = Number(lesson.split(')')[0].split('(')[1].split('-')[0]); a < Number(lesson.split(')')[0].split('(')[1].split('-')[1].split('节')[0]) + 1; a++) {

// re.sections.push({ section: a })
// }
// //如果这个是空的,就不再到下边进行计算
// //这里需要特判一下周六的形式政策

// let wylt0 = lesson.split(')')[1].split(',')[0]
// let wylt1 = lesson.split(')')[1].split(',')[1]

// if (wylt1 == undefined) {
// let wylfir = Number(lesson.split(')')[1].split('-')[1].split('周')[0]);
// console.log("look!")

// console.log(lesson.split(')')[1])
// for (let a = Number(lesson.split(')')[1].split('-')[0]); a < wylfir + 1; a++) {
// re.weeks.push(a)
// }
// }
// if (lesson[8] == '周' && lesson[9] == ',') {
// re.weeks.push(Number(lesson.split(')')[1].split('周,')[0]))
// re.weeks.push(Number(lesson.split(')')[1].split('周,')[1].split('周')[0]))
// }
// if (lesson[9] == '周' && lesson[10] == ',') {
// re.weeks.push(Number(lesson.split(')')[1].split('周,')[0]))
// re.weeks.push(Number(lesson.split(')')[1].split('周,')[1].split('周')[0]))
// }
// if (lesson.split(')')[1].split('-')[1] == null) continue;
// }

for (let j = 0; j < $(aaa[i]).next()[0].children.length; j++) {
let lesson = $(aaa[i]).next()[0].children[j].data
for (let a = Number(lesson.split(')')[0].split('(')[1].split('-')[0]); a < Number(lesson.split(')')[0].split('(')[1].split('-')[1].split('节')[0]) + 1; a++) {
re.sections.push({ section: a })
}
//
let wylt0 = lesson.split(')')[1].split(',')[0]
let wylt1 = lesson.split(')')[1].split(',')[1]

// console.log("look0!")
// console.log(wylt0)
// console.log(wylt0.length)

//>=4代表把单周扔出去
if (wylt0.length >= 4) {
let wylfir = Number(wylt0.split('-')[1].split('周')[0]);
// console.log("look1!")

// console.log(wylfir)
for (let a = Number(wylt0.split('-')[0]); a < wylfir + 1; a++) {
re.weeks.push(a)
}
}

//处理第二个连贯周
if (wylt1 !== undefined && wylt1.length >= 4) {
let wylfir = Number(wylt1.split('-')[1].split('周')[0]);
// console.log("look2!")
// console.log(wylt1.split('-')[0]);
// console.log(wylfir)
for (let a = Number(wylt1.split('-')[0]); a < wylfir + 1; a++) {
re.weeks.push(a)
}
}

//处理形势与政策
if (wylt1 !== undefined && wylt1.indexOf('-') === -1 && wylt0.indexOf('-') === -1) {
re.weeks.push(wylt0[0])
re.weeks.push(wylt1[0])
}

//处理单周
if (wylt1 === undefined && wylt0.indexOf('-') === -1) {
re.weeks.push(wylt0.split('周')[0])
}


// for (let a = Number(lesson.split(')')[1].split('-')[0]); a < Number(lesson.split(')')[1].split('-')[1].split('周')[0]) + 1; a++) {

// re.weeks.push(a)
// }
}


}

if (aaa[i].attribs.title == '教师') {

for (let j = 0; j < $(aaa[i]).next()[0].children.length; j++) {
re.teacher = $(aaa[i]).next()[0].children[j].data
}
}

if (aaa[i].attribs.class == 'title') {

for (let j = 0; j < $(aaa[i]).children()[0].children.length; j++) {
re.name = $(aaa[i]).children()[0].children[j].data

}
}

}
result.push(re)
}
console.log(result)

let wyl_sectionTime = [{
"section": 1,
"startTime": wyl_startTime[0],
"endTime": wyl_endTime[0]
},
{
"section": 2,
"startTime": wyl_startTime[1],
"endTime": wyl_endTime[1]
},
{
"section": 3,
"startTime": wyl_startTime[2],
"endTime": wyl_endTime[2]
},
{
"section": 4,
"startTime": wyl_startTime[3],
"endTime": wyl_endTime[3]
},
{
"section": 5,
"startTime": wyl_startTime[4],
"endTime": wyl_endTime[4]
},
{
"section": 6,
"startTime": wyl_startTime[5],
"endTime": wyl_endTime[5]
},
{
"section": 7,
"startTime": wyl_startTime[6],
"endTime": wyl_endTime[6]
},
{
"section": 8,
"startTime": wyl_startTime[7],
"endTime": wyl_endTime[7]
},
{
"section": 9,
"startTime": wyl_startTime[8],
"endTime": wyl_endTime[8]
},
{
"section": 10,
"startTime": wyl_startTime[9],
"endTime": wyl_endTime[9]
}
];
// if (wyl_id[0] == 1) {
// if (wyl_id[1] == 7) {
// wyl_sectionTime = [{
// "section": 1,
// "startTime": wyl_startTime4[0],
// "endTime": wyl_endTime4[0]
// },
// {
// "section": 2,
// "startTime": wyl_startTime4[1],
// "endTime": wyl_endTime4[1]
// },
// {
// "section": 3,
// "startTime": wyl_startTime4[2],
// "endTime": wyl_endTime4[2]
// },
// {
// "section": 4,
// "startTime": wyl_startTime4[3],
// "endTime": wyl_endTime4[3]
// },
// {
// "section": 5,
// "startTime": wyl_startTime4[4],
// "endTime": wyl_endTime4[4]
// },
// {
// "section": 6,
// "startTime": wyl_startTime4[5],
// "endTime": wyl_endTime4[5]
// },
// {
// "section": 7,
// "startTime": wyl_startTime4[6],
// "endTime": wyl_endTime4[6]
// },
// {
// "section": 8,
// "startTime": wyl_startTime4[7],
// "endTime": wyl_endTime4[7]
// },
// {
// "section": 9,
// "startTime": wyl_startTime4[8],
// "endTime": wyl_endTime4[8]
// },
// {
// "section": 10,
// "startTime": wyl_startTime4[9],
// "endTime": wyl_endTime4[9]
// }
// ];
// } else if (wyl_id[1] == 8) {
// wyl_sectionTime = [{
// "section": 1,
// "startTime": wyl_startTime3[0],
// "endTime": wyl_endTime3[0]
// },
// {
// "section": 2,
// "startTime": wyl_startTime3[1],
// "endTime": wyl_endTime3[1]
// },
// {
// "section": 3,
// "startTime": wyl_startTime3[2],
// "endTime": wyl_endTime3[2]
// },
// {
// "section": 4,
// "startTime": wyl_startTime3[3],
// "endTime": wyl_endTime3[3]
// },
// {
// "section": 5,
// "startTime": wyl_startTime3[4],
// "endTime": wyl_endTime3[4]
// },
// {
// "section": 6,
// "startTime": wyl_startTime3[5],
// "endTime": wyl_endTime3[5]
// },
// {
// "section": 7,
// "startTime": wyl_startTime3[6],
// "endTime": wyl_endTime3[6]
// },
// {
// "section": 8,
// "startTime": wyl_startTime3[7],
// "endTime": wyl_endTime3[7]
// },
// {
// "section": 9,
// "startTime": wyl_startTime3[8],
// "endTime": wyl_endTime3[8]
// },
// {
// "section": 10,
// "startTime": wyl_startTime3[9],
// "endTime": wyl_endTime3[9]
// }
// ];
// } else if (wyl_id[1] == 9) {
// wyl_sectionTime = [{
// "section": 1,
// "startTime": wyl_startTime2[0],
// "endTime": wyl_endTime2[0]
// },
// {
// "section": 2,
// "startTime": wyl_startTime2[1],
// "endTime": wyl_endTime2[1]
// },
// {
// "section": 3,
// "startTime": wyl_startTime2[2],
// "endTime": wyl_endTime2[2]
// },
// {
// "section": 4,
// "startTime": wyl_startTime2[3],
// "endTime": wyl_endTime2[3]
// },
// {
// "section": 5,
// "startTime": wyl_startTime2[4],
// "endTime": wyl_endTime2[4]
// },
// {
// "section": 6,
// "startTime": wyl_startTime2[5],
// "endTime": wyl_endTime2[5]
// },
// {
// "section": 7,
// "startTime": wyl_startTime2[6],
// "endTime": wyl_endTime2[6]
// },
// {
// "section": 8,
// "startTime": wyl_startTime2[7],
// "endTime": wyl_endTime2[7]
// },
// {
// "section": 9,
// "startTime": wyl_startTime2[8],
// "endTime": wyl_endTime2[8]
// },
// {
// "section": 10,
// "startTime": wyl_startTime2[9],
// "endTime": wyl_endTime2[9]
// }
// ];
// }
// } else if (wyl_id[0] == 2) {
// if (wyl_id[1] == 0) {
// wyl_sectionTime = [{
// "section": 1,
// "startTime": wyl_startTime1[0],
// "endTime": wyl_endTime1[0]
// },
// {
// "section": 2,
// "startTime": wyl_startTime1[1],
// "endTime": wyl_endTime1[1]
// },
// {
// "section": 3,
// "startTime": wyl_startTime1[2],
// "endTime": wyl_endTime1[2]
// },
// {
// "section": 4,
// "startTime": wyl_startTime1[3],
// "endTime": wyl_endTime1[3]
// },
// {
// "section": 5,
// "startTime": wyl_startTime1[4],
// "endTime": wyl_endTime1[4]
// },
// {
// "section": 6,
// "startTime": wyl_startTime1[5],
// "endTime": wyl_endTime1[5]
// },
// {
// "section": 7,
// "startTime": wyl_startTime1[6],
// "endTime": wyl_endTime1[6]
// },
// {
// "section": 8,
// "startTime": wyl_startTime1[7],
// "endTime": wyl_endTime1[7]
// },
// {
// "section": 9,
// "startTime": wyl_startTime1[8],
// "endTime": wyl_endTime1[8]
// },
// {
// "section": 10,
// "startTime": wyl_startTime1[9],
// "endTime": wyl_endTime1[9]
// }
// ];
// }
// }
console.log(wyl_sectionTime)
return {
courseInfos: result,
sectionTimes: wyl_sectionTime
}
}

以上,如果哪里有问题请联系我~

迭代记录:

  • v1.0:上线
  • v1.1:修复了周六形式与政策的bug
  • v2.0:修复了单周的bug,填上注释,修改代码规范,方便重构。
  • v2.1:修复了并列单周如果出现两位数字的时候只会读取第一位,原因是直接把切完的数组的第一位放进去了,也不知道自己当时怎么想的- -,又切了一次,把周切掉放进去就fix啦感谢浙农林的zeyu同学报的bug
  • v3.0:换了一版时间,原来的东西都注释掉了,还是担心学校又改时间,到时候不方便改,也为了给大家留个参考。