一. 引言
我们都知道,微信提供了多种登录的方式,包括手机端、电脑端以及web端。
web端的登录,我们用Python程序完全可以模拟出来~~(如果你不知道,那也没关系,稍微了解下Python request session即可)
而所谓的机器人实际上就是后台一个智能的程序,类似“微软小冰”,“iPhone siri”。今天我们要用的是一个开放的机器人API,“图灵机器人”
下面就让我们一步步分析如何,通过模拟web端微信登录+“图灵机器人” 实现一个微信机器人
二. 深入分析
1. web版微信不是用用户名密码而是用扫描二维码登录,如何实现的呢?
让我们登录https://wx.qq.com/,查看此时的网络请求情况 如下图所示
1). 实际上客户端会先发送一个js get请求,请求url为https://login.wx.qq.com/jslogin?appid=wx782c26e4c19acffb&redirect_uri=https%3A%2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&fun=new&lang=zh_CN&_=1469852355025
仔细分析这个请求,会发现有已下几个参数
appid: wx782c26e4c19acffb //这个值不变,表示来自微信网页版
redirect_uri: https%3A%2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage //这个也是一个固定值
fun: new //固定值位new
lang: zh_CN //表示中文
_: 1469852355025 //13位时间戳
2).然后服务端返回数据,window.QRLogin.code = 200; window.QRLogin.uuid = "IatVataLfQ==";
2. 多刷新几次,你会发现服务端的返回值中window.QRLogin.uuid的值每次都在变化。
实际上uuid是服务端用来标识一次登录的通信id
2. 当我们拿到uuid后,就需要获取二维码,继续查看当前的网络请求
1). 客户端继续发送一个js get 请求,url为https://login.weixin.qq.com/qrcode/IatVataLfQ==
仔细分析这个请求,会发现qrcode后跟着的值就是从上一个请求拿到的uuid值
2). 当拿到二维码之后,还需用微信客户端进行扫描(god,都有客户端了为什么还需用用web登录~~~~)
3. ok,拿出手机扫描屏幕的二维码,继续查看网络请求
1). 当我们在APP上点击登录按钮之后,实际上客户端是向服务端发送了一个js的get 请求,url为https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid=od5FW4ipFw==&tip=0&r=-979422099&_=1469857970642
仔细分析这个请求,会发现有以下几个参数
uuid: od5FW4ipFw== //从上面请求得到的数据
tip: 0 //表示等待用户扫描确认
r: -979422099 //随机9位数字
_: 1469857970642 //13位时间戳
2). 这个请求,返回结果如下所示
window.code=200;
window.redirect_uri="https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=AUyRV3zm5RBTWt-mEvvDz8oz@qrticket_0&uuid=od5FW4ipFw==&lang=zh_CN&scan=1469858238";
code=200表示的是成功,redirect_uri表示需要我们继续请求的url
4. 继续上一个请求得到的redirect_uri
1). ok,分析这个uri请求https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=AUyRV3zm5RBTWt-mEvvDz8oz@qrticket_0&uuid=od5FW4ipFw==&lang=zh_CN&scan=1469858238&fun=new&version=v2,对应参数如下所示
uuid: 同前面
scan: 1469858238 //表示用户扫描的时间戳,10位
其它参数保持不变即可
2). 这个请求会返回,我们登陆所需要的信息,返回值是一个xml数据,如下所示
<error><ret>0</ret><message>OK</message><skey>@crypt_b13bcf4_edeadfd5xxxxd5e6b289b614fac25e5ac</skey><wxsid>HON+SKvxxxxTihHV</wxsid><wxuin>8xxxx5640</wxuin><pass_ticket>um3UATy9MNzcwDDkVT4xxxxMn5B25G%2FcYIAVbpHnF8vU23yMflmUCFsZkMKbIJIP</pass_ticket><isgrayscale>1</isgrayscale></error>
(为了我的隐私,我把返回值做了一定的打码~~)
ret: 表示请求返回状态码,0表示成功
skey和wxsid以及wxuin都是具体微信用户的信息,不会变的,在后续的通信过程中需要用到
pass_ticket: 这个值在初始化登录页面的时候需要用到
5. 现在我们已经拿到了用户认证相关的信息,包括skey和wxsid以及wxuin,需要初始化登录页面
什么是初始化登录页面,也就是我们平时登录APP客户端看到的那个页面,我们需要发送一个请求到服务端拿到数据,获取到常用的联系人和微信公众号
如下图所示,这个请求是一个post请求,需要我们带一些用户认证相关的信息到服务端
1). ok,让我们分析一下这个请求url: https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=-979155549&pass_ticket=um3UATy9MNzcwDDkVT4xxxxMn5B25G%252FcYIAVbpHnF8vU23yMflmUCFsZkMKbIJIP
r: 随机的9位数据
pass_ticke: 从上一步请求返回值中获取的数据
2). post请求所需要的data,如下所示
{
'BaseResponse': {
'Uin': wxuin,
'Sid': wxsid,
'Skey': skey,
'DeviceID': //15位随机串 'e'+str(random.random())[2:17]
}
}
3). 请求返回数据,如下所示
Ret: 0表示返回成功,ContactList表示的是联系人列表。
返回数据包含了当前登录账户的相关信息,比如wxuid,昵称 ...
{ "BaseResponse": { "Ret": 0, "ErrMsg": "" } , "Count": 10, "ContactList": [{ "Uin": 0, "UserName": "filehelper", "NickName": "xxxx", "HeadImgUrl": "/cgi-bin/mmwebwx-bin/webwxgeticon?seq=653892799&username=filehelper&skey=@crypt_b13bcf4_edeadfd5615d5e6b289b614fac25e5ac", "ContactFlag": 1, "MemberCount": 0, "MemberList": [], "RemarkName": "", "HideInputBarFlag": 0, "Sex": 0, "Signature": "", "VerifyFlag": 0, "OwnerUin": 0, "PYInitial": "WJCSZS", "PYQuanPin": "wenjianchuanshuzhushou", "RemarkPYInitial": "", "RemarkPYQuanPin": "" ...
6. 登录成功,接下来我们要做的就是开启消息状态通知。
ok,继续看此时的网络请求,会发现客户端向服务端发送了一个post请求
https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxstatusnotify?pass_ticket=um3UATy9MNzcwDDkVT42sVxMn5xxxx%252FcYIAVbpHnF8vU23yMflmUCFsZkMKbIJIP
1). 对于这个请求,相信大家都不会感到陌生,就只有一个参数pass_ticket,同上
2). post请求,需要的data模式如下
{
'BaseResponse': {
'Uin': wxuin,
'Sid': wxsid,
'Skey': skey,
'DeviceID': //15位随机串 'e'+str(random.random())[2:17]
}
'ClientMsgId': 13位时间戳,
'Code': 3 //固定值
'FromUserName': userNmae, //从初始化登录信息那边取到
'ToUserName': userNmae, //从初始化登录信息那边取到
}
3. 请求返回一个json数据
{
"BaseResponse": {
"Ret": 0,
"ErrMsg": ""
},
"MsgID": "4049260553244269433"
}
Ret:0表示的是请求返回成功状态
7. ok,到目前为止我们以及成功登录了微信并且开启了消息通知
让我继续查看网页客户端,会发现有非常多如下图所示的请求。从请求名称中,我们知道这些请求在进行 同步刷新,轮询检查服务端的消息
1). 分析下,当前get请求,https://webpush.wx.qq.com/cgi-bin/mmwebwx-bin/synccheck?r=1469858460705&skey=%40crypt_b13bcf4_edeadfdxxxxx5e6b289b614fac25e5ac&sid=HON%2BSKxxxxxxxxxV&uin=828xxxx40&deviceid=e477405243870570&synckey=1_653921573%7C2_653921810%7C3_653921702%7C11_653919729%7C13_653890051%7C201_1469858241%7C1000_1469856425%7C1001_1469851411&_=1469857970652
a. r --> 13位时间戳
b. skey 同上,需要url quote
c. sid 同上
d. devicedid 同上
e. synckey由 初始化登录页面信息返回串中Sync的list列表组成, 需要url quote
f. _ 13位时间戳
2). 请求返回json数据,如下所示window.synccheck={retcode:"0",selector:"0"}
retcode:
a. 0 正常
b. 1100 失败/登出微信
c. 1101 从其它设备登录微信网页版
selector:
a. 0 正常
b. 2 新的消息
c. 7 手机操作了微信
8. ok,万事具备,只需要知道如何获取消息和发送消息即可了。
让我们先看一下,当我们在网页上收到消息的时候,不断轮询的同步刷新请求会返回,window.synccheck={retcode:"0",selector:"2"} 或者是 window.synccheck={retcode:"0",selector:"6"}
1). 这个时候,我们发现客户端会向服务端发送一个post请求,拉取新的消息数据,如下图所示
post请求: https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=HONxxxxxqSwTihHV&skey=@crypt_b13bcf4_edeadfd5615d5xxxxx9b614fac25e5ac&pass_ticket=um3UATy9MNzcwDDkVTxxxxxMn5B25G%252FcYI
请求所带的参数同上
2). 请求会返回json数据,包含具体的消息数据和类型
{ "BaseResponse": { "Ret": 0, "ErrMsg": "" } , "AddMsgCount": 1, "AddMsgList": [{ "MsgId": "4937824389423381364", "FromUserName": "@ca2e7ef4exxxxxd71ab5fd68ac405b70", "ToUserName": "@c26fa48f87634xxxxxx1ada0a4fa30f", "MsgType": 1, "Content": "[投é™]", "Status": 3, "ImgStatus": 1, "CreateTime": 1469861910, "VoiceLength": 0, "PlayLength": 0, "FileName": "", "FileSize": "", "MediaId": "", "Url": "", "AppMsgType": 0, "StatusNotifyCode": 0, "StatusNotifyUserName": "", "RecommendInfo": { "UserName": "", "NickName": "", "QQNum": 0, "Province": "", "City": "", "Content": "", "Signature": "", "Alias": "", "Scene": 0, "VerifyFlag": 0, "AttrStatus": 0, "Sex": 0, "Ticket": "", "OpCode": 0 } , "ForwardFlag": 0, "AppInfo": { "AppID": "", "Type": 0 } , "HasProductId": 0, "Ticket": "", "ImgHeight": 0, "ImgWidth": 0, "SubMsgType": 0, "NewMsgId": 4937824389423381364 } ], "ModContactCount": 0, "ModContactList": [], "DelContactCount": 0, "DelContactList": [], "ModChatRoomMemberCount": 0, "ModChatRoomMemberList": [], "Profile": { "BitFlag": 0, "UserName": { "Buff": "" } , "NickName": { "Buff": "" } , "BindUin": 0, "BindEmail": { "Buff": "" } , "BindMobile": { "Buff": "" } , "Status": 0, "Sex": 0, "PersonalCard": 0, "Alias": "", "HeadImgUpdateFlag": 0, "HeadImgUrl": "", "Signature": "" } , "ContinueFlag": 0, "SyncKey": { "Count": 8, "List": [{ "Key": 1, "Val": 653921573 } ,{ "Key": 2, "Val": 653921823 } ,{ "Key": 3, "Val": 653921702 } ,{ "Key": 11, "Val": 653919729 } ,{ "Key": 13, "Val": 653890051 } ,{ "Key": 201, "Val": 1469861910 } ,{ "Key": 1000, "Val": 1469856425 } ,{ "Key": 1001, "Val": 1469851411 } ] } , "SKey": "" }a. BaseResponse,Ret位0表示返回成功
10. 最后让我们来看一下,如何发送一个消息
1). 当我发送一个消息给好友的时候,实际是执行了一次post请求,如下图
url: https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?pass_ticket=um3UATy9MNzcwDDkVT42sVxxxxxxxxG%252FcYIAVbpHnF8vU23yMflmUCFsZkMKbIJIP
2). post请求需要的data如下所示
{
'BaseResponse': {
'Uin': wxuin,
'Sid': wxsid,
'Skey': skey,
'DeviceID': //15位随机串 'e'+str(random.random())[2:17]
}
'Type': //消息类型,同上
'Content': //消息内容
'FromUserName': //发送用户
'ToUserName': //接受用户
'LocalID': //13位时间戳+4位随机数
'ClientMsgId': //同LocalId
}
3). 当发送成功之后,服务端返回json数据
{
"BaseResponse": {
"Ret": 0,
"ErrMsg": ""
},
"MsgID": "2882629525509760458",
"LocalID": "14698626523310328"
}
Ret:0表示发送成功
ok,到此我们就知道了整个微信网页版从登陆到数据发送的整个过程。
现在我们只剩下最后一步,机器人了。
我们要实现的是,收到消息后自动根据消息进行回复。
这个我们只需要在收到消息的时候,利用收到的消息调用“图灵机器人”API获取智能回答的数据,然后发送给朋友即可。
图灵机器人,http://www.tuling123.com/
二. 效果分析
我申请了个图灵机器人,取名为[【呆萌小白】
下面是简单的和机器人的对话(无聊~~)