说明:文章内容全部截选自实验楼项目教程【基于 QQRobot 和图灵机器人实现的 QQ 群聊机器人】.
相信大家平时可能也在各种 QQ 群里遇到过一种叫做 QQ 群机器人的存在,其大多是基于腾讯 SmartQQ 协议实现的,在 github 上有很多关于此的开源项目。
这个项目就用 QQ 机器人,配合图灵机器人的 api,实现一个实现自动回复,自定义回复,满足群里日常聊天互动所需的群聊。
效果图:
开发前准备工作
1. QQRobot 源码
项目地址:https://github.com/zeruniverse/QQRobot
clone or download 到本地。
打开 Xfce 终端(指实验楼在线开发环境):
$ cd Code
$ git clone https://github.com/zeruniverse/QQRobot
2. 图灵机器人
访问图灵机器人官网。注册一个账号。
2.1. 创建机器人
选择 QQ 机器人。
2.2 设置机器人
2.3 得到 APIkey
3. 代码配置
3.1 机器人 API
编辑 Code\QQRobot\
目录下的 QQbot.py
,修改其第34行,加入刚刚申请到的图灵机器人 APIkey。
tulingkey='图灵 API'
3.2 群监控
编辑 Code\QRobot\
目录下的 groupfollow.txt
,将需要机器人监控、回复的群的名字写入,每行一个群名。(注意 : 中文群名格式为 utf-8。)
3.3 额外操作
在实验继续操作之前,需要先用 webqq 登录一下,扫码才有效果,http://web2.qq.com/ ,这一步很重要,之前大家做实验失败很多就是这个原因。群聊的效果不好,建议就试试私聊的效果。
3.4 启动
在 Code\QQRobot\
目录下执行命令,:
$ sudo nohup python2 QQBot.py >qbot.log&
如果没有问题,会在当前目录下生成一个名为 v.png
的二维码图片。等待二维码时间可能比较久。通过手机 QQ 扫描该图片,完成登陆。扫描后二维码消失。
$ cat log.log
执行可以输出运行 LOG,查看程序运行过程。
3.5 功能
机器人会根据指令做出相应动作:
- 群聊智能回复,在群中通过发送
!ai
问题语句,r如:!ai
谁最帅?,则机器人向AI平台请求问题的回复并回复到群,带有!ai
关键字时优先触发此功能。 - 群聊学习功能,在群中通过发送
!learn {ha}{哈哈}
语句,则机器人检测到发言中包含"ha"时将自动回复"哈哈"。!delete {ha}{哈哈}
可以删除该内容。学习内容会自动储存在database.群号.save文件
。!deleteall
可删除该群所有记录。 - 群聊复读功能,检测到群聊中连续两个回复内容相同,将自动复读该内容1次。
3.6 注意
如果绑定成功后,无法自动回复,查看一下日志文件 log.log
,原因可以如下:
-
{"errmsg":"","retcode":103}
: 这是登录时验证失败,用你的浏览器登录一遍 webqq,然后再重试,类似于账号被保护的原因。 - 如果账号被怀疑被盗号(异地登陆),群聊消息会发不出去。表现为程序能收到群聊消息,群聊消息发送返回值为发送成功,但其他群成员无法看到您发出的消息。大约登陆10分钟后您会收到QQ提醒提示账号被盗,要求改密码,同时账号被临时冻结。但是只要按腾讯的要求,修改一下密码,接触冻结之后就可以继续用了。
- QQ 会临时封禁机器人的临时对话回复和群回复,原理未知,每次封禁约为10分钟。表现为发送消息返回值 retcode 为 0 但其他人无法看到。
ps. 目前 linux 上能够运行的大多数 QQ 机器人都是基于 webQQ 协议实现的,稳定性很差。如果确实需要这个功能,推荐使用 windows 上的一些成熟版本(基于逆向破解的安卓 QQ 协议),稳定性要好得多,而且不容易被腾讯封禁。
QQRobot 分析
1 登录
自2015年10月开始,Web QQ 废除了原先的用户名/密码登录,取而代之的是手机 QQ 扫二维码的登录方式。
目前的协议规定的登录过程如下:
- 获取二维码
- 确认二维码已被扫描
- 获取鉴权参数 ptwebqq
- 获取鉴权参数 vfwebqq
- 获取鉴权参数 uin 和 psessionid
登录过程要求获得如下参数,用于其他接口:
- ptwebqq:保存在 Cookie 中的鉴权信息
- vfwebqq:类似于 Token 的鉴权信息
- psessionid:类似于 SessionId 的鉴权信息
- clientid:设备 id,为固定值53999199
- uin:登录用户 id(其实就是当前登录的 QQ 号)
1.1 获取二维码
请求方式: Get
url:https://ssl.ptlogin2.qq.com/ptqrshow?appid=501004106&e=0&l=M&s=5&d=72&v=4&t=0.1
1.2 获取二维码扫描状态
请求方式:Get
url:
https://ssl.ptlogin2.qq.com/ptqrlogin?webqq_type=10&remember_uin=1&login2qq=1&aid=501004106 &u1=http%3A%2F%2Fw.qq.com%2Fproxy.html%3Flogin2qq%3D1%26webqq_type%3D10 &ptredirect=0&ptlang=2052&daid=164&from_ui=1&pttype=1&dumy=&fp=loginerroralert &action=0-0-157510&mibao_css=m_webqq&t=1&g=1&js_type=0&js_ver=10143&login_sig=&pt_randsalt=0
referer:
https://ui.ptlogin2.qq.com/cgi-bin/login?daid=164&target=self&style=16&mibao_css=m_webqq&appid=501004106&enable_qlogin=0&no_verifyimg=1 &s_url=http%3A%2F%2Fw.qq.com%2Fproxy.html&f_url=loginerroralert &strong_login=1&login_state=10&t=20131024001
返回内容:
- 扫描前(未失效):
ptuiCB('66','0','','0','二维码未失效。(3203423232)','');
- 扫描后,认证前:
ptuiCB('66','0','','0','二维码认证中。(3203423232)','');
- 认证后:
ptuiCB('66','0','','0','http://ptlogin4.web2.qq.com/check_sig?xxxxxx','');
这个请求可以直接轮训请求,直到认证成功后,将返回的地址保存下来用作下次请求。
1.3 获取 ptwebqq
请求方式:Get
url:
刚才获得的地址
referer:
http://s.web2.qq.com/proxy.html?v=20130916001&callback=1&id=1
这个请求成功后的 Http 状态码是302,之后需要将 Cookie 中的 ptwebqq 保存下来。
1.4 获取 vfwebqq
请求方式:Get
url:
http://s.web2.qq.com/api/getvfwebqq?ptwebqq=#{ptwebqq}&clientid=53999199&psessionid=&t=0.1
referer:
http://s.web2.qq.com/proxy.html?v=20130916001&callback=1&id=1
url 中需要填入上一步获取到的 ptwebqq,请求成功后会返回一个 JSON,将 result.vfwebqq 保存下来。
1.5 获取psessionid和uin
请求方式:Post
url:
http://d1.web2.qq.com/channel/login2
referer:
http://d1.web2.qq.com/proxy.html?v=20151105001&callback=1&id=2
表单数据只有一个,Key 为 r,Value 是一个 JSON,内容为:
{
"ptwebqq": "#{ptwebqq}",
"clientid": 53999199,
"psessionid": "",
"status": "online",
}
其中真正的动态参数只有 ptwebqq,请求成功后会返回一个 JSON,将 result.uin 和 result.psessionid 保存下来。
需要注意的是,这里也返回了一个 result.vfwebqq,但是这个值是无用的。
登录流程就是完成这五个参数的获取:
- ptwebqq:在流程三中通过从 Cookie 中获得
- vfwebqq:在流程四中通过返回的 JSON中获得(在流程五中也会返回一个,不要使用那个)
- uin:在流程五中通过返回的 JSON 中获得(其实就是 qq 号)
- psessionid:在流程五中通过返回的 JSON 中获得
- clientid:固定值为 53999199
登录成功后一般可以维持 2 天左右,并且如今 Web QQ 是不会出现顶号情况的,但是多终端登录后在接收消息等接口可能会出现冲突的情况。
收发消息
1 轮训消息
请求方式:Post
url:
http://d1.web2.qq.com/channel/poll2
referer:
http://d1.web2.qq.com/proxy.html?v=20151105001&callback=1&id=2
请求参数只有一个 r,值是一个 JSON,内容为:
{
"ptwebqq": "#{ptwebqq}",
"clientid": 53999199,
"psessionid": "#{psessionid}",
"key": ""
}
ptwebqq 和 psessionid 都是登录后获得的参数。
请求成功后返回的内容为:
{
"result": [
{
"poll_type": "message",
"value": {
"content": [
[
"font",
{
"color": "000000",
"name": "微软雅黑",
"size": 10,
"style": [
0,
0,
0
]
}
],
"好啊"
],
"from_uin": 3785096088,
"msg_id": 25477,
"msg_type": 0,
"time": 1450686775,
"to_uin": 931996776
}
}
],
"retcode": 0
}
poll_type 为 message 表示这是个好友消息。from_uin 是用户的编号,可以用于发消息,但不是 qq 号。to_uin 是接受者的编号,同时也是 qq 号。time 为消息的发送时间,content[0] 为字体,后面为消息的内容。
如果为群消息,返回内容为:
{
"result": [
{
"poll_type": "group_message",
"value": {
"content": [
[
"font",
{
"color": "000000",
"name": "微软雅黑",
"size": 10,
"style": [
0,
0,
0
]
}
],
"好啊",
],
"from_uin": 2323421101,
"group_code": 2323421101,
"msg_id": 50873,
"msg_type": 0,
"send_uin": 3680220215,
"time": 1450687625,
"to_uin": 931996776
}
}
],
"retcode": 0
}
其中 poll_type 会变成 group_message,group_code 和 from_uin 都为群的编号,可以用于发群消息,但不是群号。send_uin 为发信息的用户的编号。其他的字段和上面的相同。
如果是讨论组消息, poll_type 会变为 discu_message,did 为讨论组的编号,其他的字段都和群消息相同。
{
"result": [
{
"poll_type": "discu_message",
"value": {
"content": [
[
"font",
{
"color": "000000",
"name": "微软雅黑",
"size": 10,
"style": [
0,
0,
0
]
}
],
"好啊",
],
"from_uin": 2322423201,
"did": 2322423201,
"msg_id": 50873,
"msg_type": 0,
"send_uin": 3680220215,
"time": 1450687625,
"to_uin": 931996776
}
}
],
"retcode": 0
}
注意:
- 服务端收到这个请求后,如果没有新消息,会一直保持住链接,所以遇到 ReadTimeout 异常是正常的
- Web QQ 无法接受图片、@别人、自定义表情等消息,消息内容只有默认表情和文字 如果消息内容为表情,content[1] 的内容就不是 String 类型了,而是一个 JSONArray 类型,里面有表情的编号,所以 content 的长度有可能大于 2,代表着消息的内容为文字和表情的混排,content[1] 开始的每一位都是分割后的文字或表情
- 这个请求有时候会返回retcode的值为103,此时需要登录 Smart QQ,确认能收到消息后点击设置-退出登录,就会恢复正常了
- 在这里接受到的 uin、group_code 等并不是固定的,而是会改变的,所以不要长时间保存这些信息,
2 发送消息给好友
请求方式:Post
url:
http://d1.web2.qq.com/channel/send_buddy_msg2
referer:
http://d1.web2.qq.com/proxy.html?v=20151105001&callback=1&id=2
请求参数只有一个 r,值是一个 JSON,内容为:
{
"to": #{user_id},
"content": [
"#{msg}",
[
"font",
{
"name": "宋体",
"size": 10,
"style": [
0,
0,
0
],
"color": "000000"
}
]
].to_string(),
"face": 522,
"clientid": 53999199,
"msg_id": 65890001,
"psessionid": "#{psessionid}",
}
psessionid 是登录后获取的参数,msg 是需要发送的内容,to是用户编号,msg_id 只要是一个比较大的数字即可。
如果发送成功,会返回如下数据:
{
"errCode": 0,
"msg": "send ok"
}
3 发送群消息
请求方式:Post
url:
http://d1.web2.qq.com/channel/send_qun_msg2
referer:
http://d1.web2.qq.com/proxy.html?v=20151105001&callback=1&id=2
请求参数和上面几乎一样,只是将 to 替换成了 group_uin:
{
"group_uin": #{group_uin},
"content": [
"#{msg}",
[
"font",
{
"name": "宋体",
"size": 10,
"style": [
0,
0,
0
],
"color": "000000"
}
]
].to_string(),
"face": 522,
"clientid": 53999199,
"msg_id": 65890001,
"psessionid": "#{psessionid}",
}
4 发送讨论组消息
请求方式:Post
url:
http://d1.web2.qq.com/channel/send_discu_msg2
referer:
http://d1.web2.qq.com/proxy.html?v=20151105001&callback=1&id=2
格式也是一样的,只是替换为了 did:
{
"did": #{discuss_id},
"content": [
"#{msg}",
[
"font",
{
"name": "宋体",
"size": 10,
"style": [
0,
0,
0
],
"color": "000000"
}
]
].to_string(),
"face": 522,
"clientid": 53999199,
"msg_id": 65890001,
"psessionid": "#{psessionid}",
}
由于篇幅有限,还有以下内容没能分享:
- 好友相关
- 获取好友列表
- 获取好友在线状态
- 通过uin获得QQ号
- 获取好友详细信息
- 群和讨论组相关
- 获取群列表
- 获取群详细信息
- 获取讨论组列表
- 获取讨论组详细信息
- 其他
- 获取最近会话列表
- 获取当前登录用户信息
如果想要查看完整内容呢,点击【基于 QQRobot 和图灵机器人实现的 QQ 群聊机器人】,即可查看完整教程内容了~
这个项目教程主要是教你部署图灵机器人与 QQ 机器人开源框架的结合,大家在部署过程中,可以增进对 SmartQQ 协议的了解,如果感兴趣的话,可以根据该框架,开发一下其他有意思的插件。