1.打开页面后,向服务器发送一个get请求,服务器会返回一个uuid
2.凭借这个uuid就可以获得用于登录的二维码
3.在获取到二维码和用户扫描二维码并确认登录之间,客户端会每隔一段时间向服务器发送一个get请求来查看这个二维码是否被扫描。如果二维码被扫描,返回window.code=201;
,否则返回window.code=408;
。如果这个二维码长时间没有被扫描,那么这个二维码就会失效。另外,如果二维码被扫描了,但是用户一直没有在手机上点确定登录,那么一段时间后,服务器也会返回window.code=408;
。一旦用户点了确定登录,那么服务器就会返回window.code=200;
。
4.登录后,客户端会向服务器请求到cookie
、pass_ticket
这类东西用来标识用户。
5.客户端就凭借着上面这些东西向服务器请求用户信息和联系人列表。
6.接着,客户端开启状态通知,每隔一段时间就向服务器发送请求,以此来确定客户端当前状态,比如是否有新的消息或者用户已经登出了微信。
API | 获取uuid |
---|---|
url | =xxx”>https://login.wx.qq.com/jslogin?appid=wx782c26e4c19acffb&redirect_uri=https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage&fun=new&lang=zh_CN&=xxx |
method | get |
说明 | _ :时间戳 |
响应:
window.QRLogin.code = 200; window.QRLogin.uuid = "xxx";
API | 获得二维码 |
---|---|
url | https://login.weixin.qq.com/qrcode/uuid |
method | get |
说明 | uuid 即为上面获得的uuid |
API | 检查二维码扫描状态 |
---|---|
url | =xxx”>https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login?loginicon=true&uuid=xxx&tip=1&r=xxx&=xxx |
method | get |
说明 | _ 和r 好像都是时间戳,不过不发r 好像也没关系 |
响应:
//二维码未被扫描或者二维码被扫描但用户长时间未点击确认登录时
window.code=408;
//二维码被扫描时
window.code=201;window.userAvatar = 'xxx';
//用户点击确认登录时
window.code=200;
window.redirect_uri="xxx";
API | 登录获取cookie |
---|---|
url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=xxx&uuid=xxx&lang=zh_CN&scan=xxx&fun=new&version=v2&lang=zh_CN |
method | get |
说明 | ticket scan 在window.redirect_uri="xxx"; 里面 |
响应:
<error>
<ret>0ret>
<message>OKmessage>
<skey>xxxskey>
<wxsid>xxxwxsid>
<wxuin>xxxwxuin>
<pass_ticket>xxxpass_ticket>
<isgrayscale>1isgrayscale>
error>
API | 初始化 |
---|---|
url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=xxx&pass_ticket=xxx |
method | post |
params | {"BaseRequest":{"Uin":"xxx","Sid":"xxx","Skey":"xxx","DeviceID":"xxx"}} |
请求头 | Content-Type application/json;charset=utf-8 |
说明 | DeviceID 是e后面跟着一个15字节的随机数,例:e159973572418266 |
响应:
{
"BaseResponse": {
"Ret": 0,
"ErrMsg": ""
},
"Count": 11,
"ContactList": [...],
"SyncKey": {
"Count": 4,
"List": [
{
"Key": 1,
"Val": 635705559
},
...
]
},
"User": {
"Uin": xxx,
"UserName": xxx,
"NickName": xxx,
"HeadImgUrl": xxx,
"RemarkName": "",
"PYInitial": "",
"PYQuanPin": "",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"HideInputBarFlag": 0,
"StarFriend": 0,
"Sex": 1,
"Signature": "Apt-get install B",
"AppAccountFlag": 0,
"VerifyFlag": 0,
"ContactFlag": 0,
"WebWxPluginSwitch": 0,
"HeadImgFlag": 1,
"SnsFlag": 17
},
"ChatSet": xxx,
"SKey": xxx,
"ClientVersion": 369297683,
"SystemTime": 1453124908,
"GrayScale": 1,
"InviteStartCount": 40,
"MPSubscribeMsgCount": 2,
"MPSubscribeMsgList": [...],
"ClickReportInterval": 600000
}
API | 开启状态通知 |
---|---|
url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxstatusnotify?pass_ticket=xxx |
method | post |
请求头 | Content-Type:application/json;charset=utf-8 |
params:
{
"BaseRequest":
{
"Uin": "xxx",
"Sid": "xxx",
"Skey": "xxx",
"DeviceID": "xxx"
},
"ClientMsgId": "时间戳",
"Code": 3,
"FromUserName": "自己的ID",
"ToUserName": "自己的ID"
}
响应:
{
"BaseResponse": {
"Ret": 0,
"ErrMsg": ""
},
"MsgID":xxx
}
API | 获取联系人信息 |
---|---|
url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?pass_ticket=xxx&r=xxx&seq=0&skey=xxx |
method | get |
响应:
{
"BaseResponse": {
"Ret": 0,
"ErrMsg": ""
},
"MemberCount": 334,
"MemberList": [
{
"Uin": 0,
"UserName": xxx,
"NickName": "Urinx",
"HeadImgUrl": xxx,
"ContactFlag": 3,
"MemberCount": 0,
"MemberList": [],
"RemarkName": "",
"HideInputBarFlag": 0,
"Sex": 0,
"Signature": "xxx",
"VerifyFlag": 8,
"OwnerUin": 0,
"PYInitial": "URINX",
"PYQuanPin": "Urinx",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"StarFriend": 0,
"AppAccountFlag": 0,
"Statues": 0,
"AttrStatus": 0,
"Province": "",
"City": "",
"Alias": "Urinxs",
"SnsFlag": 0,
"UniFriend": 0,
"DisplayName": "",
"ChatRoomId": 0,
"KeyWord": "gh_",
"EncryChatRoomId": ""
},
...
],
"Seq": 0
}
API | 获取联系人信息 |
---|---|
url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxbatchgetcontact?type=ex&r=xxx0&pass_ticket=xxx |
method | post |
请求头 | Content-Type:application/json;charset=utf-8 |
说明 | UserName是要查询具体信息的用户名,EncryChatRoomId是该用户所属的群用户名。 |
params:
{
"BaseRequest": {
"Uin": "xxx",
"Sid": "xxx",
"Skey": "xxx",
"DeviceID": "xxx"
},
"Count":xx,
"List":{
0:{
"EncryChatRoomId":
"UserName":"xxx"
},
...
}
}
这两种方法的区别在于webwxbatchgetcontact返回的数据中会有群成员信息
API | 心跳检查 |
---|---|
url | =xxx”>https://webpush.wx.qq.com/cgi-bin/mmwebwx-bin/synccheck?r=xxx&skey=xxx&sid=xxx&deviceid=xxx&synckey=xxx&=xxx |
method | get |
要把初始化中得到的SyncKey整理成key_val|key_val这种格式,例如:1_694411380|2_694413141|3_694412799|1000_1526698782
响应(String):
window.synccheck={retcode:"xxx",selector:"xxx"}
retcode:
0 正常
1100 失败/登出微信
selector:
0 正常
2 新的消息
7 进入/离开聊天界面
API | 获取最新消息 |
---|---|
url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=xxx&skey=xxx&lang=zh_CN&pass_ticket=xxx |
method | post |
请求头 | Content-Type:application/json;charset=utf-8 |
说明 | rr :时间戳取反。 |
params:
{
"BaseRequest": {
"Uin": "xxx",
"Sid": "xxx",
"Skey": "xxx",
"DeviceID": "xxx"
},
"rr":xx,
"SyncKey":{
"Count":xx,
"List":{
0:{
"Key":xx
"Val":xx
},
...
}
}
}
响应:
BaseResponse
AddMsgCount:新增消息数
AddMsgList:新增消息列表
ModContactCount: 变更联系人数目
ModContactList: 变更联系人列表
SyncKey:新的synckey列表
当webwxsync发送完后会发送一个syncheck请求向服务器表示webwxsync响应已经收到了,这时syncheck用的synckey
是发送webwxsync时收到的。若synccheck返回为window.synccheck={retcode:"0",selector:"0"}
,则继续发出相同请求。若返回不为以上返回值,继续POST请求webwxsync。
API | 发送消息 |
---|---|
url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?lang=zh_CN&pass_ticket=xxx |
method | post |
请求头 | Content-Type:application/json;charset=utf-8 |
params:
{
"BaseRequest": {
"Uin": "xxx",
"Sid": "xxx",
"Skey": "xxx",
"DeviceID": "xxx"
},
"Msg":{
"Type": 1 文字消息,
"Content": 要发送的消息,
"FromUserName": 自己的ID,
"ToUserName": 好友的ID,
"LocalID": 与clientMsgId相同,
"ClientMsgId": 时间戳左移4位随后补上4位随机数
},
"Scene": 0
}
响应:
{
"BaseResponse": {
"Ret": 0,
"ErrMsg": ""
}
,
"MsgID": "xxx",
"LocalID": "xxx"
}
微信web协议分析和实现微信机器人
Web 微信与基于Node的微信机器人实现
wechat-api