天猫精灵是阿里巴巴人工智能实验室发布的智能音箱产品,去年“冲鸭打卡100天”活动时购入,100天坚持下来了,等于免费撸。现在开始探索搞机。
目标:使用天猫精灵自定义技能对接教务系统,获取课程表,通过语音方式反馈课程基本信息。
加入开发者平台
在AliGenie开发者平台申请账号,登录后转到智能应用下的技能页面
创建技能
点击创建语音技能,技能名称自定,调用词就是触发技能的关键词。
配置技能
完成创建后点击语音交互模型创建,能力申请中的OAuth2在这里不需要用到,无需配置。
1.实体
实体就相当于将一个变量实例化。可以参考官方的过时教程
我们这里要查询课表,需要提供一个必要的参数就是:日期。因此需要创建一个日期的实体。这个实体中的值应包含可能出现的日期:周一到周日,以及今天、明天、后天等。
添加实体值时可以选择下载模板,批量添加。
模板里的“李白”就是实体值,后面的“酒仙|诗仙”是“李白”的同义词。修改之后是这样
再上传文件即可更近一步,可以设置一个实体“上下午”,用来区分上午和下午的指令,这样就可以单独获取上午或者下午的课表
2.意图
实体已经创建好了,现在来创建意图,引用实体
1)创建意图
2)配置意图
- 点击编辑开始配置意图
先想好我们该如何询问天猫精灵。应该是天猫精灵查课表然后加上带实体的参数,所以可以用类似天猫精灵查课表今天课表的表达,如果带上上午或者下午的话,应该是天猫精灵查课表今天上午课表。
单轮对话表达
- 由于我们不是固定的问法,所以选择模板而不是例句。
- 实体的引用方法为
@{实体标识}其他文字
,这里就应该是@{weekday}课表
,@{mornoon}课表
,@{weekday}@{mornoon}课表
添加参数
- 参数((就是天猫精灵获取到我们的意图后,将参数**中的项作为post的参数传递给目标网址。这里参数就是
weekday
和moornoon
。 - 关联实体表示参数值需要从哪个实体的值里面得到。
- 系统默认实体值表示当天猫精灵没有获取到该参数时,使用的默认值。比如天猫精灵查课表今天课表,没有指定上午或者下午,就会默认使用全天。而天猫精灵查课表上午课表,没有指定日期,就会默认使用今天。
技能已经配置得差不多了,接下来要做的就是配置负责接收天猫精灵的服务端
3.回复逻辑
在回复逻辑页面下,找到默认逻辑WEBHOOK,里面有我们之前创建的意图。
1)url
点击详情后,编辑该逻辑,填入url
,即天猫精灵获取我们的指令后发送请求到该url,获取回复,再语音反馈给我们。
这里我使用http://hfutwifi.tqraf.cn/aligenie/course.php 。
其中的Header
部分不需要填写。
2)认证
点击下载认证文件,得到一个txt文件,将其上传至与 http://hfutwifi.tqraf.cn/aligenie/course.php 中的course.php同级的文件夹下。点击提交即可完成配置。
服务器端
1.查看参数
- 前面讲到天猫精灵会请求http://hfutwifi.tqraf.cn/aligenie/course.php 。由于不知道天猫精灵的详细参数(官方貌似也没给,走了不少弯路)。这里我在
course.php
中获取请求信息并写入文件,观察一下。将以下代码写入course.php
中<?php $tmpData = strval(file_get_contents("php://input")); $DataArray = json_decode($tmpData, true); file_put_contents("request.txt", print_r($DataArray, true)); ?>
- 然后回到开发者平台,输入指令进行测试
可以看到weekday
和mornoon
参数都正确解析出来了。
查看course.php
同目录下生成的request.txt
,里面记录了天猫精灵请求的详细参数。Array ( [sessionId] => 0fac795b-d807-4692-a01f-414622936431 [utterance] => 查课表今日课表 [requestData] => Array() [botId] => 73593 [domainId] => 37616 [skillId] => 54556 [skillName] => 查课表 [intentId] => 67783 [intentName] => get_course [slotEntities] => Array ( [0] => Array ( [intentParameterId] => 61180 [intentParameterName] => weekday [originalValue] => 今日 [standardValue] => 今天 [liveTime] => 0 [createTimeStamp] => 1588145450299 [slotName] => weekday:weekday [slotValue] => 今天 ) [1] => Array ( [intentParameterId] => 61179 [intentParameterName] => mornoon [originalValue] => 全天 [standardValue] => 全天 [liveTime] => 0 [createTimeStamp] => 1588145450320 [slotName] => mornoon:mornoon [slotValue] => 全天 ) ) [requestId] => 20200429153050278-904398056 [device] => Array() [skillSession] => Array ( [skillSessionId] => c09d7dc6-c04d-4628-a6f2-911e95cbd295 [newSession] => 1 ) )
- 可以看到,参数包含在
slotEntities
中,slotEntities[0]['slotValue']
和slotEntities[1]['slotValue']
分别是我们想要提取的参数今天和全天
2.抓包获取Cookie
1)为什么抓包
- 想一想,我们从哪里获取课程表?当然是学校的教务网站。但是这大多数时候不是公开的,需要登录获取。
- 如果学校教务系统有手机app的话,那就很方便,因为手机app的cookie大多能够保持很长时间不失效,甚至是永久的。但是网页登录产生的cookie时效往往很短。
- 抓到cookie后,使用php构造请求,获取课程表的内容,经过解析,返回正确的值给天猫精灵。
- 如果没有手机app,那么需要自行构造请求,模拟登录,可以参考以下内容。
2)如何抓包
感觉这个内容有点多,准备另外写一篇。到时候在这里放上链接。也可以自行搜索教程,使用的软件是Fiddler。
3)抓到的请求
完整的请求
POST http://jxglstu.hfut.edu.cn:7070/appservice/home/schedule/getWeekSchedule.action HTTP/1.1 Host: jxglstu.hfut.edu.cn:7070 Connection: keep-alive Content-Length: 106 Accept: application/json, text/plain, */* User-Agent: Mozilla/5.0 (Linux; Android 10; TAS-AN00 Build/HUAWEITAS-AN00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 Mobile Safari/537.36 Content-Type: application/x-www-form-urlencoded X-Requested-With: edu.hfut.eams.mobile Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7 Cookie: JSESSIONID=395721953DA4811C2D2A7E********** userKey=JShUaMgb0cCxYshjoEwKmllsMXqntPn1Uy**********&identity=0&projectId=2&semestercode=036&weekIndx=12
响应
{ "code": 200, "msg": "查询成功!", "salt": null, "obj": { "business_data": [ { "activity_id": "79192", "activity_week": "12,13,14,15,16,17,18,19", "activity_weekstate": "12-19周", "course_name": "微电子封装技术", "end_time": "12:10", "end_unit": 4, "is_conflict": false, "lesson_code": "1011280X--001", "rooms": [ { "campus_name": "翡翠湖校区", "code": "00000235", "floor_name": "翠十二教", "name": "翠十二教302*" } ], "start_time": "10:20", "start_unit": 3, "teachclass_stdcount": 94, "teachers": [ { "code": "2007800051", "name": "***" } ], "weekday": 1 } ], "err_code": "00000", "err_msg": "" }, "token": null, "error": null }
请求的链接为
http://jxglstu.hfut.edu.cn:7070/appservice/home/schedule/getWeekSchedule.action
附带参数为
userKey=JShUaMgb0cCxYshjoEwKmllsMXqntPn1Uy**********&identity=0&projectId=2&semestercode=036&weekIndx=12
获取不同时间的课表,抓取数据包可以明显看到看到参数中
weekIndx
表示教学周。business_data
存放课程详情
3.编写php
现在已经有了足够的信息,可以开始处理请求与数据了。
1)处理收到的请求
以下代码获取请求参数的slotValue
$tmpData = strval(file_get_contents("php://input")); //获取请求参数
$DataArray = json_decode($tmpData, true); //生成关联数组
//file_put_contents("request.txt", print_r($DataArray, true));
$request = $DataArray["slotEntities"]; //提取slotEntities项
if($request[0]["intentParameterName"] == "mornoon") //提取slotValue
{
$mornoon = $request[0]["slotValue"];
$weekday = $request[1]["slotValue"];
}
else
{
$mornoon = $request[1]["slotValue"];
$weekday = $request[0]["slotValue"];
}
2)构造发送的请求
function post($url,$post_data,$location = 0,$reffer = null,$origin = null,$host = null){
$header = array(
'Host: jxglstu.hfut.edu.cn:7070',
'Connection: keep-alive',
'Content-Length: 106',
'Accept: application/json, text/plain, */*',
'Origin: file://',
'User-Agent: Mozilla/5.0 (Linux; Android 10; TAS-AN00 Build/HUAWEITAS-AN00; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/78.0.3904.108 Mobile Safari/537.36',
'Content-Type: application/x-www-form-urlencoded',
'Accept-Encoding: gzip, deflate',
'Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
'Cookie: JSESSIONID=395721953DA4811C2D2A7E**********',
'X-Requested-With: edu.hfut.eams.mobile',
);
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url); //传入url
curl_setopt($curl, CURLOPT_HTTPHEADER, $header); //传入header
curl_setopt($curl, CURLOPT_USERAGENT, "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36");
curl_setopt($curl, CURLOPT_AUTOREFERER, 1); //自动设置referer
curl_setopt($curl, CURLOPT_POST, 1); //开启post
curl_setopt($curl, CURLOPT_ENCODING, "gzip, deflate" );
curl_setopt($curl, CURLOPT_POSTFIELDS, $post_data); //要传送的数据
curl_setopt($curl, CURLOPT_COOKIE, "JSESSIONID=395721953DA4811C2D2A7E**********"); //以变量形式发送cookie
curl_setopt($curl, CURLOPT_TIMEOUT, 30); //设置超时限制,防止死循环
curl_setopt($curl, CURLOPT_HEADER, 0);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$tmpInfo = curl_exec($curl); //发送请求,获取返回数据
curl_close($curl);
return $tmpInfo;
}
$morning = []; //存储上午的课
$afternoon = []; //存储下午的课
//$start_week:教学周第一周的开始时间,用于计算今天是第几个教学周。
$start_week = date("2020-02-17");
$now = date("Y-m-d");
$reply = "";
//$week:计算今天是第几个教学周
$week = ceil(abs(strtotime($now) - strtotime($start_week))/86400/7);
if($weekday == "今天")
$day = date("w");
elseif($weekday == "明天")
{
//周日问明天的课,教学周应+1
if(date("w") == 7)
$week += 1;
$day = date("w",strtotime(date("Y-m-d",strtotime("+1 day"))));
}
elseif($weekday == "后天")
{
//双休日问后天的课,教学周应+1
if(date("w") >= 6)
$week += 1;
$day = date("w",strtotime(date("Y-m-d",strtotime("+2 day"))));
}
else
$day = -1;
$result = post("http://jxglstu.hfut.edu.cn:7070/appservice/home/schedule/getWeekSchedule.action","userKey=JShUaMgb0cCxYshjoEwKmllsMXqntPn1UyZwhXIIr8Y%3D&identity=0&projectId=2&semestercode=036&weekIndx=".$week);
3)解析收到的数据
$course = json_decode($result,true)["obj"]["business_data"]; //提取business_data中的课程信息
foreach($course as $value) //遍历循环判断
{
if($value["weekday"] == $day) //找到要查询的日期
{
if(strtotime($value["start_time"]) - strtotime("12:00") < 0) //表示上午的课
{
if($value["start_time"] == "08:10") //8:10开始的是第一节课
$reply_temp = "第一节 ".$value["course_name"]." ";
else //否则是第二节课
$reply_temp = "第二节 ".$value["course_name"]." ";
array_push($morning,$reply_temp);
}
else //表示下午的课
{
if($value["start_time"] == "14:00") //14:00开始的是第一节课
$reply_temp = "第一节 ".$value["course_name"]." ";
else //否则是第二节课
$reply_temp = "第二节 ".$value["course_name"]." ";
array_push($afternoon, $reply_temp);
}
}
}
4)准备返回的数据
if($morning != [] && ($mornoon == "上午" || $mornoon == "全天")) //上午有课,并且查询了上午(或全天)的课
$reply = $reply."".$weekday."上午 ".implode(" ",$morning); //reply中加入上午的课
if($afternoon != [] && ($mornoon == "下午" || $mornoon == "全天")) //下午有课,并且查询了下午(或全天)的课
$reply = $reply."".$weekday."下午 ".implode(" ",$afternoon); //reply中加入下午的课
if(($morning == [] && $mornoon == "上午") || ($afternoon == [] && $mornoon == "下午")) //上午或者下午没课
$reply = $weekday."".$mornoon."没有课哦";
elseif(($morning == [] && $afternoon == [] && $mornoon == "全天")) //明天都没课
$reply = $weekday."没有课哦";
echo json_encode(array('returnCode'=>'0','returnValue'=>array('reply'=>$reply,'resultType'=>'RESULT','executeCode'=>'SUCCESS')));
- 注意最后返回的数据格式一定要按照官方要求来,否则会出错。
- 以上四段代码即为完整代码,按顺序写入php文件即可,记得前后加上
<?php
和?>
4.最后测试
在技能管理中的测试栏目里发送“查课表”即可查询
测试成功,最后在真机测试里打开开关即可。
如需使用自定音频素材,先在音频素材界面上传音频,得到音频ID
再修改course.php
中最后一行为:
echo json_encode(
array(
'returnCode'=>'0',
'returnValue'=>array(
'reply'=>$reply,
'resultType'=>'RESULT',
'actions'=>array(
array(
"name"=>"audioPlayGenieSource",
"properties"=>array(
"audioGenieId"=>"53953"
)
),
array()
),
'executeCode'=>'SUCCESS'
)
)
);
修改audioGenieId
的值为对应音频ID,多个音频添加多个array()
即可。测试成功会有音频的进度条显示。
- Tips:可以在天猫精灵App中设置组合指令,使询问方式更接近真实对话。如下图