转载自:https://segmentfault.com/a/1190000004471678
1.打开首页,分配一个随机uuid,
2.根据该uuid获取二维码图片。
3.微信客户端扫描该图片,在客户端确认登录。
4.浏览器不停的调用一个接口,如果返回登录成功,则调用登录接口
5.此时可以获取联系人列表,可以发送消息。然后不断调用同步接口。
6.如果同步接口有返回,则可以获取新消息,然后继续调用同步接口。
JAVA源码地址:https://github.com/biezhi/wechat-robot
Python实现:https://github.com/Urinx/WeixinBot
C#实现:https://github.com/sherlockchou86/WeChat.NET
QT实现:https://github.com/xiangzhai/qwx
执行流程
+--------------+ +---------------+ +---------------+
| | | | | |
| Get UUID | | Get Contact | | Status Notify |
| | | | | |
+-------+------+ +-------^-------+ +-------^-------+
| | |
| +-------+ +--------+
| | |
+-------v------+ +-----+--+------+ +--------------+
| | | | | |
| Get QRCode | | Weixin Init +------> Sync Check <----+
| | | | | | |
+-------+------+ +-------^-------+ +-------+------+ |
| | | |
| | +-----------+
| | |
+-------v------+ +-------+--------+ +-------v-------+
| | Confirm Login | | | |
+------> Login +---------------> New Login Page | | Weixin Sync |
| | | | | | |
| +------+-------+ +----------------+ +---------------+
| |
|QRCode Scaned|
+-------------+
WebWechat API
1. 获取UUID(参考方法 getUUID)
API | 获取 UUID |
---|---|
url | https://login.weixin.qq.com/jslogin |
method | GET |
data | URL Encode |
params | appid : wx782c26e4c19acffb fun : new lang: zh_CN _ : 时间戳 |
返回数据(String):
window.QRLogin.code = 200; window.QRLogin.uuid = "xxx"
2. 显示二维码(参考方法 showQrCode)
API | 显示二维码 |
---|---|
url | https://login.weixin.qq.com/qrcode/{uuid} |
method | POST |
params | t : webwx _ : 时间戳 |
3. 等待登录(参考方法 waitForLogin)这里是微信确认登录
API | 二维码扫描登录 |
---|---|
url | https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login |
method | GET |
params | tip : 1:未扫描 0:已扫描 uuid : 获取到的uuid _ : 时间戳 |
返回数据(String):
window.code=xxx;
xxx:
408 登陆超时
201 扫描成功
200 确认登录
当返回200时,还会有
window.redirect_uri="https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=xxx&uuid=xxx&lang=xxx&scan=xxx";
4. 登录获取Cookie(参考方法 login)
API | webwxnewloginpage |
---|---|
url | https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage |
method | GET |
params | ticket : xxx uuid : xxx lang : zh_CN scan : xxx fun : new |
返回数据(XML):
0
OK
xxx
xxx
xxx
xxx
1
在这一步获取xml中的 skey
, wxsid
, wxuin
, pass_ticket
5. 微信初始化(参考方法 wxInit)
API | webwxinit |
---|---|
url | https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxinit |
method | POST |
data | JSON |
header | Content-Type: application/json; charset=UTF-8 |
params | { BaseRequest: { Uin: xxx, Sid: xxx, Skey: xxx, DeviceID: xxx, } } |
返回数据(JSON):
{
"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
}
这一步中获取 SyncKey
, User
后面的消息监听用。
部分分析:
本次ContactList里只有10个好友or群组,应该是最近的10个活跃对象(个数不是固定的);
另外,可以通过UserName来区分好友or群组,一个”@”为好友,两个”@”为群组。
MPSubscribeMsg为公众号推送的阅读文章
User其实就是自己账号信息(用在顶部的头像)
6. 开启微信状态通知(参考方法 wxStatusNotify)
API | webwxstatusnotify |
---|---|
url | https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxstatusnotify |
method | POST |
data | JSON |
header | Content-Type: application/json; charset=UTF-8 |
params | { BaseRequest: { Uin: xxx, Sid: xxx, Skey: xxx, DeviceID: xxx }, Code: 3, FromUserName: 自己的ID, ToUserName: 自己的ID, ClientMsgId: 时间戳 } |
返回数据(JSON):
{
"BaseResponse": {
"Ret": 0,
"ErrMsg": ""
},
...
}
7. 获取联系人列表(参考方法 getContact)
API | webwxgetcontact |
---|---|
url | https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact |
method | POST |
data | JSON |
header | ContentType: application/json; charset=UTF-8 |
params | { BaseRequest: { Uin: xxx, Sid: xxx, Skey: xxx, DeviceID: xxx, } } |
返回数据(JSON):
{
"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": "我是二蛋",
"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
}
PS:
这个列表中包含好友、群组、公众号。
好友和公众号是通过”ContactFlag”区分的,值为1是好友,值为3是公众号。
关于NickName,可以带表情之类,所以有的人昵称是这样的: "NickName": "Wade"
部分字段说明:
"Uin": 0,
"UserName": 用户名称,一个"@"为好友,两个"@"为群组
"NickName": 昵称
"HeadImgUrl":头像图片链接地址
"ContactFlag": 1-好友, 2-群组, 3-公众号
"MemberCount": 成员数量,只有在群组信息中才有效,
"MemberList": 成员列表,
"RemarkName": 备注名称
"HideInputBarFlag": 0,
"Sex": 性别,0-未设置(公众号、保密),1-男,2-女
"Signature": 公众号的功能介绍 or 好友的个性签名
"VerifyFlag": 0,
"OwnerUin": 0,
"PYInitial": 用户名拼音缩写
"PYQuanPin": 用户名拼音全拼
"RemarkPYInitial":备注拼音缩写
"RemarkPYQuanPin": 备注拼音全拼
"StarFriend": 是否为星标朋友 0-否 1-是
"AppAccountFlag": 0,
"Statues": 0,
"AttrStatus": 119911,
"Province": 省
"City": 市
"Alias":
"SnsFlag": 17,
"UniFriend": 0,
"DisplayName": "",
"ChatRoomId": 0,
"KeyWord":
"EncryChatRoomId": ""
请求群组列表(webwxbatchgetcontact)
请求:
url | https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxbatchgetcontact?type=ex&r=xxx&lang=zh_CN&pass_ticket=xxx |
---|---|
method | POST |
params | BaseRequest: { DeviceID:”xxx” Sid:”xxx” Skey:”xxx” Uin:xxx } Count:4 List: [ 0:{UserName: “xxx”, EncryChatRoomId: “”} 1:{UserName: “xxx”, ChatRoomId: “”} … ] |
Q:EncryChatRoomId与ChatRoomId有什么区别?
(1) 第一次请求得到最近一段时间内活跃的群组(但不知道腾讯是怎么定义这段时间的):
POST表单提交的内容:
上面LIST的内容是从微信初始化时(webwxinit)返回的数据中得到的:
返回到结果如下:
里面有群组的名称、群组成员等信息
(2) 第二次请求得到剩下的(最近没有交流的)群组: https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxbatchgetcontact?type=ex&r=xxx&lang=zh_CN&pass_ticket=xxx
POST表单提交的内容:
PS:应该都是在获取好友列表(webwxgetcontact)的返回数据中提取出来的(通过判断”@@”即可)
返回到结果如下:
PS: 里面有群组的名称、群组成员等信息
8.消息检查(参考方法 syncCheck)
API | synccheck |
---|---|
url | https://webpush2.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck |
method | GET |
data | JSON |
header | ContentType: application/json; charset=UTF-8 |
params | { BaseRequest: { Uin: xxx, Sid: xxx, Skey: xxx, DeviceID: xxx, } } |
返回数据(String):
window.synccheck={retcode:"0",selector:"2"}
retcode:
0 正常
1100 失败/登出微信
selector:
0 正常
2 新的消息
4 通过时发现
6 删除时发现,对方通过好友验证
7 进入/离开聊天界面 (可能没有了)
9. 获取最新消息(参考方法 webwxsync)
API | webwxsync |
---|---|
url | https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=xxx&skey=xxx&pass_ticket=xxx |
method | POST |
data | JSON |
header | ContentType: application/json; charset=UTF-8 |
params | { BaseRequest: { Uin: xxx, Sid: xxx, Skey: xxx, DeviceID: xxx }, SyncKey: xxx, rr: 时间戳取反 } |
返回数据(JSON):
{
'BaseResponse': {'ErrMsg': '', 'Ret': 0},
'SyncKey': {
'Count': 7,
'List': [
{'Val': 636214192, 'Key': 1},
...
]
},
'ContinueFlag': 0,
'AddMsgCount': 1,
'AddMsgList': [
{
'FromUserName': '',
'PlayLength': 0,
'RecommendInfo': {...},
'Content': "",
'StatusNotifyUserName': '',
'StatusNotifyCode': 5,
'Status': 3,
'VoiceLength': 0,
'ToUserName': '',
'ForwardFlag': 0,
'AppMsgType': 0,
'AppInfo': {'Type': 0, 'AppID': ''},
'Url': '',
'ImgStatus': 1,
'MsgType': 51,
'ImgHeight': 0,
'MediaId': '',
'FileName': '',
'FileSize': '',
...
},
...
],
'ModChatRoomMemberCount': 0,
'ModContactList': [],
'DelContactList': [],
'ModChatRoomMemberList': [],
'DelContactCount': 0,
...
}
10. 发送消息(参考方法 webwxsendmsg)
API | webwxsendmsg |
---|---|
url | https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?pass_ticket=xxx |
method | POST |
data | JSON |
header | ContentType: 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位随机数 } } |
字段说明:
Type: 1 文字消息,3 图片消息(先把图片上传得到MediaId再调用webwxsendmsg发送),其他消息类型没试。
Content: 要发送的消息(发送图片消息时该字段为MediaId)
FromUserName: 自己的ID
ToUserName: 好友的ID
ClientMsgId: 时间戳左移4位随后补上4位随机数
LocalID: 与clientMsgId相同
返回的数据(JSON):
{
"BaseResponse": {
"Ret": 0,
"ErrMsg": ""
}
,
"MsgID": "5897491620102102783",
"LocalID": "14672043702850802"
}