MENU

网页微信通讯协议分析

• April 12, 2017 • Technology阅读设置

背景

  最近接了朋友的一个委托:他们团队计划开发一款微信与自然语言处理相关的软件,但因技术人员有限,所以委托我帮忙封装微信功能。故花了点时间先在C#上把网页微信的简单功能封装了出来,而后因为要写文档,故而顺手写了这篇博文。
  JavaScript坑很多,我自己又不是写js的,所以每次涉及到js都会踩很多坑,这次全靠@大越哥(任意门->Gayhub)把我从几个坑里拉了起来,在这次分析中为我提供了不少JavaScript方面的支持。
  不得不说,JavaScript的坑都很出人意料,也很有趣。

  这次分析与封装是在网页微信(2017/04/12最后版本)上进行的,分析了我这次项目中用到的功能,可满足登录与收发信,过段时间如果有机会会补上一些其它常用的功能。


高能预警

  文中所提到的“阻塞”,不是指它阻塞了网页微信的运行,我想表达的是对这个请求而言,它进入了一个等待直至服务器有所返回。
  如果你有注意到截图中的时间的话,会发现这些截图不是同一时间截的,因为我确实是分很多个部分很多个时间段编辑的,因而我需要多次登录微信,这导致了下文中skey,ticket等值在不同部分中不一样,但其实网页微信中很多重要信息在初始化后就不会改变,或者很长一段时间有效,请不要被文中不一样的值误导而产生疑惑:)


正文

【0】关于域名

  网页微信有几个重要的域名,我们看到的有两个,一是wx.qq.com,二是wx2.qq.com,在我们扫描二维码等待登录的过程中,我们所在的是wx.qq.com的页面,当扫码成功后会通过返回的一个跳转链接跳到wx2.qq.com。
  wx.qq.com下又有login.wx.qq.com与res.wx.qq.com两个子域名,login.wx.qq.com负责处理扫码登录过程中的请求,而res.wx.qq.com存放着网页微信的一些重要(对于我们分析很重要233)资源。
  还有一个login.weixin.qq.com,我们需要在它上面获取到扫描登录用的二维码。


【1】vendor.js与index.js

  首先我们登录wx.qq.com抓包,它会加载两个重要的js文件,vendor_8863a98.js与index_40649b7.js,vendor中是angularjs框架,而index中实现了网页微信的大部分功能,我们先把这俩货拿下来。

https://res.wx.qq.com/a/wx_fed/webwx/res/static/vendor/vendor_8863a98.js
https://res.wx.qq.com/a/wx_fed/webwx/res/static/js/index_40649b7.js


【2】webwxstatreport


  webwxstatreport这个API用来向wx.qq.com报告当前运行环境的一些信息,每隔一段时间都会有一次上报,payload由一个BaseRequest与报告的信息List构成,List中的内容“丰富多彩”,POST后拿到一个空返回,返回文件头中也没有有价值的东西,我封装的时候没有封装这个API,暂时没遇到什么问题。


【3】jslogin

GET

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&_=1491922242477

appid: 固定常量:wx782c26e4c19acffb,可能会随版本号更新变更
redirect_uri:https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage
lang : 语言,很多地方用到,我封装的时候全用的常量zh_CN
_ : 当前的时间戳Date.now(),这个时间戳好像不会严查

 window.QRLogin.code = 200; window.QRLogin.uuid = "YYICtyGrFA==";

  jslogin用于获取一个uuid,uuid可以理解为当前登录会话的ID,将会在整个登录过程中用到,在登录成功跳转后被抛弃。

  通过发送这个GET请求,我们获取到了一个用于登录的uuid “YYICtyGrFA==”


【4】qrcode

GET:

https://login.weixin.qq.com/qrcode/YYICtyGrFA==

Response:

  一张登录用的二维码图片

  这个请求很明显,就是用我们刚刚在jslogin中获取到的uuid来获取一个扫描登录用的二维码图片。


【5】login

GET:

https://login.wx.qq.com/cgi-bin/mmwebwx-bin/login? loginicon=true&uuid=YYICtyGrFA==&tip=1&r=-1568591252&_=1491922242478

loginicon :网页微信默认为true,当它为true时,扫描后会返回扫描微信的头像的DataURL形式
uuid:就是上面我们获取到的uuid
tip:第一次get的时候tip为1,而后11次均为0
r:当前时间戳的位非值~Date.now()
_:当前时间戳Date.now()

window.code=400;
window.code=408;
window.code=201;window.userAvatar = 'data:img/jpg;base64,........(省略了头像数据)';
window.code=200;window.redirect_uri="https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=A2xh2T6chZdWmqEDspgl5g0V@qrticket_0&uuid=Qer15sbmcA==&lang=zh_CN&scan=1491968930";

window.code的定义:
200:登录成功
201:扫描成功
400:微信错误,一般在超过12次阻塞仍未登录成功时出现,此时需要更新uuid
408:阻塞超时,未接收到事件,需进入下一个阻塞

  这是一个阻塞等待的GET,用于等待用户扫描二维码,及扫描后的确定登录,阻塞超时为25秒,当25秒后用户仍未扫描(408),则再发一个GET,如此循环,直到超过12次阻塞用户仍未登录成功,或用户成功登录后停止阻塞循环。

  正常情况下,在扫描头像后,会收到一个201返回,此时若参数中的loginicon为true,还会带上一个DataURL的微信头像。然后再进入阻塞,当用户在手机微信上点击“登录”后,会收到一个200返回并拿到跳转URL。特殊的,有时候你可能收不到201返回,即因为延迟等原因,在你扫描二维码后没能收到扫描状态返回,而你又点了登录,此时将直接拿到200返回与跳转链接,若仍想拿到头像,可通过改变tip参数拿到(当前tip=0则置为1,为1则置为0)。

  此时我们拿到了一个跳转链接

https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=A2xh2T6chZdWmqEDspgl5g0V@qrticket_0&uuid=Qer15sbmcA==&lang=zh_CN&scan=1491968930

至此,wx.qq.com的任务完成。


【6】webwxnewloginpage

GET:

https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=A2xh2T6chZdWmqEDspgl5g0V@qrticket_0&uuid=Qer15sbmcA==&lang=zh_CN&scan=1491968930

HTTP/1.1 301 Moved Permanently
Connection: keep-alive
Location: /
Content-Type: text/plain
Set-Cookie: wxuin=3486595268; Domain=wx2.qq.com; Path=/; Expires=Wed, 12-Apr-2017 15:49:02 GMT
Set-Cookie: wxsid=9VgC1hwTN5h4IC3F; Domain=wx2.qq.com; Path=/; Expires=Wed, 12-Apr-2017 15:49:02 GMT
Set-Cookie: wxloadtime=1491968942; Domain=wx2.qq.com; Path=/; Expires=Wed, 12-Apr-2017 15:49:02 GMT
Set-Cookie: mm_lang=zh_CN; Domain=wx2.qq.com; Path=/; Expires=Wed, 12-Apr-2017 15:49:02 GMT
Set-Cookie: webwx_data_ticket=gSeCzIvvA4iJu9FYuV0ViZE7; Domain=.qq.com; Path=/; Expires=Wed, 12-Apr-2017 15:49:02 GMT
Set-Cookie: webwx_auth_ticket=CIsBEJnw7ekMGoABStiIhEG/h2T0e0BVVWf0BwgBnOO8Zq75MCoPdDDDjoIXALDu4wiacJRLDwqS7O6xL5SjglyWDgzSWL/W8h/0a2qCbUjwTu41WZvBrZCTsg2qQIb6lCMilYwPbxBjvV8bcuXkTZi+h2V9+2hzc8yyTyGpIGlbraLOGN+VlK5W/VM=; Domain=wx2.qq.com; Path=/; Expires=Sat, 10-Apr-2027 03:49:02 GMT
Content-Length: 292

  通过GET我们在第5步中获取到的跳转链接,我们能得到一个301跳转响应,响应头中,对wx2.qq.com进行了set cookie,其中有几个比较重要的cookie:

wxuin:微信uin
wxsid:微信sid
webwx_data_ticket:
webwx_auth_ticket:两个ticket我还没用到,以后写

  然后通过Location重定向到http://wx2.qq.com/,wx2.qq.com就是网页微信前端页面了。

  在某些情况下,这个跳转链接会有些特别,请看【8】


【7】webwxinit

POST:

https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=-1615293529


{"BaseRequest":{"Uin":"xuin=130910983","Sid":"9VgC1hwTN5h4IC3F","Skey":"","DeviceID":"e425021266901048"}}

r:~Date.now()
Uin:上一步set cookie的uin,sid同,skey为空,在响应中可拿到,deviceID在【9】解析
{
"BaseResponse": {
"Ret": 0,
"ErrMsg": ""
}
,
"Count": 6,
"ContactList": [{
"Uin": 0,
"UserName": "filehelper",
"NickName": "文件传输助手",
"HeadImgUrl": "/cgi-bin/mmwebwx-bin/webwxgeticon?seq=0&username=filehelper&skey=@crypt_378a445c_4f1ef4b8422e90fce8b74eebbad48aac",
"ContactFlag": 0,
"MemberCount": 0,
"MemberList": [],
"RemarkName": "",
"HideInputBarFlag": 0,
"Sex": 0,
"Signature": "",
"VerifyFlag": 0,
"OwnerUin": 0,
"PYInitial": "WJCSZS",
"PYQuanPin": "wenjianchuanshuzhushou",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"StarFriend": 0,
"AppAccountFlag": 0,
"Statues": 0,
"AttrStatus": 0,
"Province": "",
"City": "",
"Alias": "",
"SnsFlag": 0,
"UniFriend": 0,
"DisplayName": "",
"ChatRoomId": 0,
"KeyWord": "fil",
"EncryChatRoomId": "",
"IsOwner": 0
}
,{
"Uin": 0,
"UserName": "@9233d4b23c6386d1b418d225ba196f1f",
"NickName": "广外闲置转让",
"HeadImgUrl": "/cgi-bin/mmwebwx-bin/webwxgeticon?seq=601490137&username=@9233d4b23c6386d1b418d225ba196f1f&skey=@crypt_378a445c_4f1ef4b8422e90fce8b74eebbad48aac",
"ContactFlag": 3,
"MemberCount": 0,
"MemberList": [],
"RemarkName": "",
"HideInputBarFlag": 0,
"Sex": 0,
"Signature": "致力于建立属于广外学子的二手物品及闲置物品的交易信息平台~",
"VerifyFlag": 8,
"OwnerUin": 0,
"PYInitial": "GWXZZR",
"PYQuanPin": "guangwaixianzhizhuairang",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"StarFriend": 0,
"AppAccountFlag": 0,
"Statues": 0,
"AttrStatus": 0,
"Province": "广东",
"City": "广州",
"Alias": "gwxzzr",
"SnsFlag": 0,
"UniFriend": 0,
"DisplayName": "",
"ChatRoomId": 0,
"KeyWord": "gh_",
"EncryChatRoomId": "",
"IsOwner": 0
}
,{
"Uin": 0,
"UserName": "weixin",
"NickName": "微信团队",
"HeadImgUrl": "/cgi-bin/mmwebwx-bin/webwxgeticon?seq=601490005&username=weixin&skey=@crypt_378a445c_4f1ef4b8422e90fce8b74eebbad48aac",
"ContactFlag": 3,
"MemberCount": 0,
"MemberList": [],
"RemarkName": "",
"HideInputBarFlag": 0,
"Sex": 0,
"Signature": "微信团队官方帐号",
"VerifyFlag": 56,
"OwnerUin": 0,
"PYInitial": "WXTD",
"PYQuanPin": "weixintuandui",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"StarFriend": 0,
"AppAccountFlag": 0,
"Statues": 0,
"AttrStatus": 4,
"Province": "",
"City": "",
"Alias": "",
"SnsFlag": 0,
"UniFriend": 0,
"DisplayName": "",
"ChatRoomId": 0,
"KeyWord": "wei",
"EncryChatRoomId": "",
"IsOwner": 0
}
,{
"Uin": 0,
"UserName": "@0a7e4be6a5060ea0a2ff34eeca0d5394b0852bf317d0a8ec28f4031006156dfd",
"NickName": "# nullptr",
"HeadImgUrl": "/cgi-bin/mmwebwx-bin/webwxgeticon?seq=601490030&username=@0a7e4be6a5060ea0a2ff34eeca0d5394b0852bf317d0a8ec28f4031006156dfd&skey=@crypt_378a445c_4f1ef4b8422e90fce8b74eebbad48aac",
"ContactFlag": 3,
"MemberCount": 0,
"MemberList": [],
"RemarkName": "",
"HideInputBarFlag": 0,
"Sex": 1,
"Signature": "墨菲大帝狂热者,墨菲定律研究人员",
"VerifyFlag": 0,
"OwnerUin": 0,
"PYInitial": "NULLPTR",
"PYQuanPin": "nullptr",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"StarFriend": 0,
"AppAccountFlag": 0,
"Statues": 0,
"AttrStatus": 234277,
"Province": "",
"City": "",
"Alias": "NoInjection",
"SnsFlag": 1,
"UniFriend": 0,
"DisplayName": "",
"ChatRoomId": 0,
"KeyWord": "",
"EncryChatRoomId": "",
"IsOwner": 0
}
,{
"Uin": 0,
"UserName": "@@04f97b6493624dde99b222761d1b7dd2a7fa9487e934b84111c46602ae44ee9d",
"NickName": "TestGroup",
"HeadImgUrl": "/cgi-bin/mmwebwx-bin/webwxgetheadimg?seq=0&username=@@04f97b6493624dde99b222761d1b7dd2a7fa9487e934b84111c46602ae44ee9d&skey=@crypt_378a445c_4f1ef4b8422e90fce8b74eebbad48aac",
"ContactFlag": 0,
"MemberCount": 3,
"MemberList": [{
"Uin": 0,
"UserName": "@0a7e4be6a5060ea0a2ff34eeca0d5394b0852bf317d0a8ec28f4031006156dfd",
"NickName": "",
"AttrStatus": 0,
"PYInitial": "",
"PYQuanPin": "",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"MemberStatus": 0,
"DisplayName": "",
"KeyWord": ""
}
,{
"Uin": 0,
"UserName": "@ae922a87d31ec4641bb7cde54a94e1c4",
"NickName": "",
"AttrStatus": 0,
"PYInitial": "",
"PYQuanPin": "",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"MemberStatus": 0,
"DisplayName": "",
"KeyWord": "its"
}
,{
"Uin": 0,
"UserName": "@019b8306f406ace5133e4d4b4888738c23d4d9e919ae7ffa91c2362f395e353d",
"NickName": "",
"AttrStatus": 0,
"PYInitial": "",
"PYQuanPin": "",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"MemberStatus": 0,
"DisplayName": "",
"KeyWord": ""
}
],
"RemarkName": "",
"HideInputBarFlag": 0,
"Sex": 0,
"Signature": "",
"VerifyFlag": 0,
"OwnerUin": 0,
"PYInitial": "",
"PYQuanPin": "",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"StarFriend": 0,
"AppAccountFlag": 0,
"Statues": 1,
"AttrStatus": 0,
"Province": "",
"City": "",
"Alias": "",
"SnsFlag": 0,
"UniFriend": 0,
"DisplayName": "",
"ChatRoomId": 0,
"KeyWord": "",
"EncryChatRoomId": "",
"IsOwner": 0
}
,{
"Uin": 0,
"UserName": "@@ad3c1cc5556f5986327140e260ff213fc720d071e7a1317b889606eb97f03f81",
"NickName": "BAB闲置转让[Moue]",
"HeadImgUrl": "/cgi-bin/mmwebwx-bin/webwxgetheadimg?seq=0&username=@@ad3c1cc5556f5986327140e260ff213fc720d071e7a1317b889606eb97f03f81&skey=@crypt_378a445c_4f1ef4b8422e90fce8b74eebbad48aac",
"ContactFlag": 0,
"MemberCount": 8,
"MemberList": [{
"Uin": 0,
"UserName": "@ec8a5bb4d4b4fc79ab56149004ad39cba16ee96171b87a817df5d35275f77229",
"NickName": "",
"AttrStatus": 0,
"PYInitial": "",
"PYQuanPin": "",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"MemberStatus": 0,
"DisplayName": "",
"KeyWord": ""
}
,{
"Uin": 0,
"UserName": "@fc248d459cd6c69eab796242cd9fabd06c521ebce25a75511d671d11755f6919",
"NickName": "",
"AttrStatus": 0,
"PYInitial": "",
"PYQuanPin": "",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"MemberStatus": 0,
"DisplayName": "",
"KeyWord": ""
}
,{
"Uin": 0,
"UserName": "@9007217935e6d167d09c5caef04727e36dbb0714152a3ab0d80c6d2aa5a09abb",
"NickName": "",
"AttrStatus": 0,
"PYInitial": "",
"PYQuanPin": "",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"MemberStatus": 0,
"DisplayName": "",
"KeyWord": ""
}
,{
"Uin": 0,
"UserName": "@60dfd4f4a463c9a776666024f7018727d06f9e09d9464bc48006a560c83bc359",
"NickName": "",
"AttrStatus": 0,
"PYInitial": "",
"PYQuanPin": "",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"MemberStatus": 0,
"DisplayName": "",
"KeyWord": ""
}
,{
"Uin": 0,
"UserName": "@08667b12d37350df4b502c41f59a907ac6e3f10051105082e45aba97ed217c22",
"NickName": "",
"AttrStatus": 0,
"PYInitial": "",
"PYQuanPin": "",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"MemberStatus": 0,
"DisplayName": "",
"KeyWord": ""
}
,{
"Uin": 0,
"UserName": "@0a7e4be6a5060ea0a2ff34eeca0d5394b0852bf317d0a8ec28f4031006156dfd",
"NickName": "",
"AttrStatus": 0,
"PYInitial": "",
"PYQuanPin": "",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"MemberStatus": 0,
"DisplayName": "",
"KeyWord": ""
}
,{
"Uin": 0,
"UserName": "@af83b7552f3831baa5793067d90983c777070283909eb9ab276103fa314bd07f",
"NickName": "",
"AttrStatus": 0,
"PYInitial": "",
"PYQuanPin": "",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"MemberStatus": 0,
"DisplayName": "",
"KeyWord": ""
}
,{
"Uin": 0,
"UserName": "@301de64a95641319418f6e44fc857d58c1963ee2db3157ea67f25ad3e83839e6",
"NickName": "",
"AttrStatus": 0,
"PYInitial": "",
"PYQuanPin": "",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"MemberStatus": 0,
"DisplayName": "",
"KeyWord": ""
}
],
"RemarkName": "",
"HideInputBarFlag": 0,
"Sex": 0,
"Signature": "",
"VerifyFlag": 0,
"OwnerUin": 0,
"PYInitial": "",
"PYQuanPin": "",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"StarFriend": 0,
"AppAccountFlag": 0,
"Statues": 0,
"AttrStatus": 0,
"Province": "",
"City": "",
"Alias": "",
"SnsFlag": 0,
"UniFriend": 0,
"DisplayName": "",
"ChatRoomId": 0,
"KeyWord": "",
"EncryChatRoomId": "",
"IsOwner": 0
}
],
"SyncKey": {
"Count": 4,
"List": [{
"Key": 1,
"Val": 601490136
}
,{
"Key": 2,
"Val": 601490138
}
,{
"Key": 3,
"Val": 601490137
}
,{
"Key": 1000,
"Val": 1491956641
}
]
}
,
"User": {
"Uin": 3486595268,
"UserName": "@019b8306f406ace5133e4d4b4888738c23d4d9e919ae7ffa91c2362f395e353d",
"NickName": "Testor",
"HeadImgUrl": "/cgi-bin/mmwebwx-bin/webwxgeticon?seq=80044119&username=@019b8306f406ace5133e4d4b4888738c23d4d9e919ae7ffa91c2362f395e353d&skey=@crypt_378a445c_4f1ef4b8422e90fce8b74eebbad48aac",
"RemarkName": "",
"PYInitial": "",
"PYQuanPin": "",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"HideInputBarFlag": 0,
"StarFriend": 0,
"Sex": 0,
"Signature": "",
"AppAccountFlag": 0,
"VerifyFlag": 0,
"ContactFlag": 0,
"WebWxPluginSwitch": 0,
"HeadImgFlag": 0,
"SnsFlag": 0
}
,
"ChatSet": "filehelper,@9233d4b23c6386d1b418d225ba196f1f,weixin,@0a7e4be6a5060ea0a2ff34eeca0d5394b0852bf317d0a8ec28f4031006156dfd,@@04f97b6493624dde99b222761d1b7dd2a7fa9487e934b84111c46602ae44ee9d,@@ad3c1cc5556f5986327140e260ff213fc720d071e7a1317b889606eb97f03f81,",
"SKey": "@crypt_378a445c_4f1ef4b8422e90fce8b74eebbad48aac",
"ClientVersion": 637736754,
"SystemTime": 1491968943,
"GrayScale": 1,
"InviteStartCount": 40,
"MPSubscribeMsgCount": 0,
"MPSubscribeMsgList": [],
"ClickReportInterval": 600000
}

  payload中的BaseRequest几乎在每个post中都会出现,用来传递基本的信息(告诉服务器请求来自哪个会话),其结构基本固定,由Uin、Sid、Skey、DeviceID构成,其中Sid、Skey、DeviceID均为字符串,而Uin虽然在webwxinit中为字符串,但在之后的所有POST中它均为一个number。
  如果你足够细心,可能会发现我这次提交的Uin与set cookie中的好像不一样,这是因为我之前登录过另一个微信,这个Uin是我另一个微信的Uin,看起来微信服务端也不会检查这个Uin字符串,但这个字符串为什么跟set cookie中的不一样呢,请看【8】。
  响应的是一个baserequest和一个contactlist,contact的结构将在【10】解析,这里返回的contactlist是最近联系人,就是你点开手机微信的第一版“微信”板块的内容。

ClientVersion应该是手机客户端的版本号
MPSubscribeMsgList返回的是公众号的推送。
User结构包含了当前登录用户的信息,包括uin,头像,昵称,个性签名,username等重要信息
SyncKey:synckey是微信多端消息同步的一个重要数据,它用来与服务器进行消息同步,其由多对Key与Val构成,其大概工作原理是告诉服务器我要接收自Val值(其值可以理解为指定了一个时间点)之后的关于Key的消息,当收到消息后会更新synckey将val指向的时间点后移(大概是这样,但描述不够准确,如果你有更专业的描述,欢迎指正)开始接收新val之后的消息。


【8】webwxnewloginpage的另一个用法

  在【6】中我们提到webwxnewloginpage在某些情况下会有些特殊,而【7】中我们也发现Uin的异常,这个原因是什么呢。

  在index.js中,我们可以看到

  payload中uin的获取是通过accountFactory.getUin()来拿到的,我们跳到accountFactory下,看下getUin的实现:

  如果getUserInfo().Uin未被定义,则通过cookie中的wxuin获取,但是【7】中的Uin跟wxuin这个cookie值不一样,所以很明显是走了getUserInfo().Uin,那八成就是有人调用了setUin()了,我们搜下有哪调用了setUin():

  $on在angular中被用来接收事件,很明显有人传递了t参数给了newLoginPage,我们再看下有哪触发了newLoginPage:

  完美。我们可以看到e.$emit(“newLoginPage”,…)触发了newLoginPage事件,并将Uin等信息传了过去,我们可以看到Uin是 s &&s[1],当s存在时返回s[1]的值,不存在时返回undefined。往上看我们能发现s是一个正则匹配s = t.match(/(.*)</wxuin>/),看起来像是匹配一个xml中的wxuin项,而字符串t是在newLoginPage.then()传入的,我们来看下newLoginPage这个函数的定义:

  看,它在redirect_uri后面加上了字符串”&fun=new&version=v2″然后GET了它,这与我们【6】中直接GET redirect_uri可不一样,而且它是有返回值的,不像【6】中一样是个301,它返回了一个XML:

  xml中wxuin的值在有些时候可能会带上xuin=前缀,这就是为什么webwxinit中的wxuin与其它post中不一样,是个字符串的原因。

  此时我们会获取到一个特殊的pass_ticket,这个passticket在后面很多请求会用到,被追加在一些POST请求的URL后面(假如你触发了【8】这一步的话),但我封装时没加上passticket也没问题:)

  什么时候会出现【8】这一步呢,我还没仔细去研究过,但根据几次触发情景分析,很有可能是当你扫描二维码的页面是wx2.qq.com而不是wx.qq.com时,这种情况出现在你登录过一次然后太长时间没操作时,此时网页微信会自动logout并获取新的二维码等待扫描登录。


【9】DeviceID

  其实没啥好解释的,一个”e”加上15位随机数


【10】Contact 结构


  一个contact结构存着一个联系人的信息,是网页微信中一个重要的数据结构,这个联系人可以是好友、群组、以及公众号、微信应用等。一般情况下会在三个地方用到这个数据结构:

  • webwxinit时返回的最近联系人
  • webwxgetcontact时返回的联系人列表
  • webwxbatchgetcontact时返回的联系人详细信息
Contact结构解析:

Alias:别名,也即是微信号,但不是所有联系人都能获取到微信号
AppAccountFlag:
AttrStatus:
ChatRoomId:聊天室ID
City:联系人所在城市
ContactFlag:CONTACTFLAG常量
DisplayName:一般为空,取nickname
EncryChatRoomId:在batchgetcontact中会出现EncryChatRoomId,但那是群组的username,而不是这里返回的EncryChatRoomId,这里的暂没发现哪用到它
HeadImgUrl:联系人头像链接
HideInputBarFlag:
IsOwner:是否是群组的所有者,0为否,非0为是
KeyWord:关键词,用来在网页微信头像下面的搜索框进行搜索定位该联系人
MemberCount:MemberList中成员的数量
MemberList:一个Member结构数组,在batchgetcontact中用来获取群组的成员信息,其它地方这里一般返回空
NickName:昵称
OwnerUin:
PYInitial:拼音的首字母与所有英文字母的结合,但是在网页微信中好像没有用到
PYQuanPin:
Province:联系人所在省份
RemarkName:备注名
RemarkPYInitial:
RemarkPYQuanPin:
Sex:性别,0为未定义、1为男性、2为女性
Signature:个性签名,或群组的公告,或公众号的说明
SnsFlag:在网页微信中只有一个地方用到:hasPhotoAlbum 用1&snsflag,看名字像是判断是否有相册
StarFriend:是否是星标好友,0为否,非0为是
Statues:CHATROOM_NOTIFY常量
Uin:这个联系人的Uin,一般拿不到,没什么卵用
UniFriend:
UserName:contact中很重要的信息,当你需要与该联系人交互时需要用到,你可以把它理解成唯一的由你指向他的指针。UserName不是固定的,每次登录获取的同一个联系人的UserName都不一样
VerifyFlag:MM_USERATTRVERIFYFALG常量

  上面留空的是没什么用的及网页微信中没用到的,下面列出上文提到的几个常量

CONTACTFLAG_CONTACT: 1,
CONTACTFLAG_CHATCONTACT: 2,
CONTACTFLAG_CHATROOMCONTACT: 4,
CONTACTFLAG_BLACKLISTCONTACT: 8,
CONTACTFLAG_DOMAINCONTACT: 16,
CONTACTFLAG_HIDECONTACT: 32,
CONTACTFLAG_FAVOURCONTACT: 64,
CONTACTFLAG_3RDAPPCONTACT: 128,
CONTACTFLAG_SNSBLACKLISTCONTACT: 256,
CONTACTFLAG_NOTIFYCLOSECONTACT: 512,
CONTACTFLAG_TOPCONTACT: 2048,
CHATROOM_NOTIFY_OPEN: 1,
CHATROOM_NOTIFY_CLOSE: 0,
MM_USERATTRVERIFYFALG_BIZ: 1,
MM_USERATTRVERIFYFALG_FAMOUS: 2,
MM_USERATTRVERIFYFALG_BIZ_BIG: 4,
MM_USERATTRVERIFYFALG_BIZ_BRAND: 8,
MM_USERATTRVERIFYFALG_BIZ_VERIFIED: 16

  这些常量通过与相应的返回进行与运算确定状态。


【11】webwxgetcontact


GET:

https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?r=1491968945618&seq=0&skey=@crypt_378a445c_4f1ef4b8422e90fce8b74eebbad48aac
https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?lang=zh_CN&pass_ticket=Wv1uFx0GqPIyj0FyADSCf3xzBAvCT9HzywjeCSmf1a422R5ScYrSP6SQF73%252F49hL&r=1491971438952&seq=0&skey=@crypt_378a445c_e5801b6f607ca2cbc293fcf0ef15c66e

//这里列出了一个需要passticket的情况,一般情况下不会出现。
r:时间戳
seq:没见过0以外的
skey:登录时拿到的skey
lang:zh_CN
pass_ticket:登录是拿到的passticket,如果有的话

{
"BaseResponse": {
"Ret": 0,
"ErrMsg": ""
}
,
"MemberCount": 4,
"MemberList": [{
"Uin": 0,
"UserName": "weixin",
"NickName": "微信团队",
"HeadImgUrl": "/cgi-bin/mmwebwx-bin/webwxgeticon?seq=601490005&username=weixin&skey=@crypt_378a445c_e5801b6f607ca2cbc293fcf0ef15c66e",
"ContactFlag": 3,
"MemberCount": 0,
"MemberList": [],
"RemarkName": "",
"HideInputBarFlag": 0,
"Sex": 0,
"Signature": "微信团队官方帐号",
"VerifyFlag": 56,
"OwnerUin": 0,
"PYInitial": "WXTD",
"PYQuanPin": "weixintuandui",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"StarFriend": 0,
"AppAccountFlag": 0,
"Statues": 0,
"AttrStatus": 4,
"Province": "",
"City": "",
"Alias": "",
"SnsFlag": 0,
"UniFriend": 0,
"DisplayName": "",
"ChatRoomId": 0,
"KeyWord": "wei",
"EncryChatRoomId": "",
"IsOwner": 0
}
,{
"Uin": 0,
"UserName": "@ecc50ecb64d1ec1b9d2de406f9f85ce096b790e12e46678da51a6e30d51bf828",
"NickName": "# nullptr",
"HeadImgUrl": "/cgi-bin/mmwebwx-bin/webwxgeticon?seq=601490030&username=@ecc50ecb64d1ec1b9d2de406f9f85ce096b790e12e46678da51a6e30d51bf828&skey=@crypt_378a445c_e5801b6f607ca2cbc293fcf0ef15c66e",
"ContactFlag": 3,
"MemberCount": 0,
"MemberList": [],
"RemarkName": "",
"HideInputBarFlag": 0,
"Sex": 1,
"Signature": "墨菲大帝狂热者,墨菲定律研究人员",
"VerifyFlag": 0,
"OwnerUin": 0,
"PYInitial": "NULLPTR",
"PYQuanPin": "nullptr",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"StarFriend": 0,
"AppAccountFlag": 0,
"Statues": 0,
"AttrStatus": 234277,
"Province": "",
"City": "",
"Alias": "NoInjection",
"SnsFlag": 1,
"UniFriend": 0,
"DisplayName": "",
"ChatRoomId": 0,
"KeyWord": "",
"EncryChatRoomId": "",
"IsOwner": 0
}
,{
"Uin": 0,
"UserName": "@@0c068c1d560eb83718a74b3bb75a0e184afd8f12ec076482b9e049fef252c69d",
"NickName": "TestGroup",
"HeadImgUrl": "/cgi-bin/mmwebwx-bin/webwxgetheadimg?seq=601490086&username=@@0c068c1d560eb83718a74b3bb75a0e184afd8f12ec076482b9e049fef252c69d&skey=@crypt_378a445c_e5801b6f607ca2cbc293fcf0ef15c66e",
"ContactFlag": 3,
"MemberCount": 0,
"MemberList": [],
"RemarkName": "",
"HideInputBarFlag": 0,
"Sex": 0,
"Signature": "",
"VerifyFlag": 0,
"OwnerUin": 0,
"PYInitial": "TESTGROUP",
"PYQuanPin": "TestGroup",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"StarFriend": 0,
"AppAccountFlag": 0,
"Statues": 1,
"AttrStatus": 0,
"Province": "",
"City": "",
"Alias": "",
"SnsFlag": 0,
"UniFriend": 0,
"DisplayName": "",
"ChatRoomId": 0,
"KeyWord": "",
"EncryChatRoomId": "",
"IsOwner": 0
}
,{
"Uin": 0,
"UserName": "@255edcb278b3faa727b7672db4ad8207",
"NickName": "广外闲置转让",
"HeadImgUrl": "/cgi-bin/mmwebwx-bin/webwxgeticon?seq=601490137&username=@255edcb278b3faa727b7672db4ad8207&skey=@crypt_378a445c_e5801b6f607ca2cbc293fcf0ef15c66e",
"ContactFlag": 3,
"MemberCount": 0,
"MemberList": [],
"RemarkName": "",
"HideInputBarFlag": 0,
"Sex": 0,
"Signature": "致力于建立属于广外学子的二手物品及闲置物品的交易信息平台~",
"VerifyFlag": 8,
"OwnerUin": 0,
"PYInitial": "GWXZZR",
"PYQuanPin": "guangwaixianzhizhuairang",
"RemarkPYInitial": "",
"RemarkPYQuanPin": "",
"StarFriend": 0,
"AppAccountFlag": 0,
"Statues": 0,
"AttrStatus": 0,
"Province": "广东",
"City": "广州",
"Alias": "gwxzzr",
"SnsFlag": 0,
"UniFriend": 0,
"DisplayName": "",
"ChatRoomId": 0,
"KeyWord": "gh_",
"EncryChatRoomId": "",
"IsOwner": 0
}
],
"Seq": 0
}

  webwxgetcontact用于获取通讯录列表,其中MemberList是由Contact结构体构成的列表。


【12】webwxbatchgetcontact


POST:

https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxbatchgetcontact?type=ex&r=1491971574391&lang=zh_CN&pass_ticket=Wv1uFx0GqPIyj0FyADSCf3xzBAvCT9HzywjeCSmf1a422R5ScYrSP6SQF73%252F49hL

{"BaseRequest":{"Uin":3486595268,"Sid":"9zgK9ARoydoX/FlF","Skey":"@crypt_378a445c_e5801b6f607ca2cbc293fcf0ef15c66e","DeviceID":"e694291016411083"},"Count":1,"List":[{"UserName":"@4e7cb29ab66e9b8d6133727442b74db5","EncryChatRoomId":"@@0c068c1d560eb83718a74b3bb75a0e184afd8f12ec076482b9e049fef252c69d"}]}

UserName:需要获取详细信息的联系人或群组的用户名
EncryChatRoomId 与 ChatRoomId:当ChatRoomId被定义时,这里使用ChatRoomId,若没有,这里则用EncryChatRoomId,若查的UserName位于某个群中,值提供为该群组的EncryChatRoomId或ChatRoomId,一般是群的UserName,否则留空
List:由上述两对值构成的结构体组成的列表

  返回Contact数组,为payload中指定username的详细信息。
  若查询的是群信息,MemberList将包含该群成员的信息,其结构在【13】中解析。
  ContactList最多只会返回50个Member,也就是说如果你payload里面提交了51个或更多,只会返回其中50个的信息,而是否一定是前50个不得而知。


【13】Member结构

AttrStatus:
DisplayName:显示的昵称
KeyWord:还是用于搜索的关键字
NickName:昵称
PYInitial:
PYQuanPin:
RemarkPYInitial:
RemarkPYQuanPin:
Uin:成员的Uin
UserName:成员的UserName

【14】webwxstatusnotify

POST:

https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxstatusnotify?lang=zh_CN&pass_ticket=Wv1uFx0GqPIyj0FyADSCf3xzBAvCT9HzywjeCSmf1a422R5ScYrSP6SQF73%252F49hL

{"BaseRequest":{"Uin":3486595268,"Sid":"9zgK9ARoydoX/FlF","Skey":"@crypt_378a445c_e5801b6f607ca2cbc293fcf0ef15c66e","DeviceID":"e416760097700921"},"Code":3,"FromUserName":"@1b345b8fdf8e7f7345ae67430062f5dd6c2963cf91a95bd0bebfcf5dfabcfc9a","ToUserName":"@1b345b8fdf8e7f7345ae67430062f5dd6c2963cf91a95bd0bebfcf5dfabcfc9a","ClientMsgId":1491971438932}

Code:StatusNotifyCode常量
FromUserName:一般都是当前的UserName
ToUserName:指示这个通知是关于谁的,比如初始化(3)就是自己的username,已读了谁的消息就是那个人的username
{
"BaseResponse": {
"Ret": 0,
"ErrMsg": ""
}
,
"MsgID": "3115729215031025828"
}
StatusNotifyCode常量:

StatusNotifyCode_READED: 1,
StatusNotifyCode_ENTER_SESSION: 2,
StatusNotifyCode_INITED: 3,
StatusNotifyCode_SYNC_CONV: 4,
StatusNotifyCode_QUIT_SESSION: 5,

  其中READED与INITED是由网页微信主动发出的,分别代表消息已读与网页微信初始化,当发送INITED时,from跟to两个username都是当前的username,而当已读某条消息后发送READED时,tousername是与你会话另一个人的username,如果是群则是群的username。
  剩下三个都是接受到的状态,当收到消息时的MsgType是MSGTYPE_STATUSNOTIFY时,说明这条消息是一个状态通知,会对StatusNotifyCode进行判断。

  可以看到,如果是ENTER_SESSION说明是另一个客户端进入了这个会话,即这条消息在别的地方被阅读了,当然因为它的本义是“进入会话”所以如果点开了与一个联系人的对话窗口也会触发这事件。如果是SYNC_CONV则会初始化聊天列表,一般在刚登录的时候会收到一次,再之后还没发现触发情景。而QUIT_SESSION的处理被放空,看起来是一个尚未被应用的状态?


【15】MSGTYPE的常量

MSGTYPE_TEXT: 1,
MSGTYPE_IMAGE: 3,
MSGTYPE_VOICE: 34,
MSGTYPE_VIDEO: 43,
MSGTYPE_MICROVIDEO: 62,
MSGTYPE_EMOTICON: 47,
MSGTYPE_APP: 49,
MSGTYPE_VOIPMSG: 50,
MSGTYPE_VOIPNOTIFY: 52,
MSGTYPE_VOIPINVITE: 53,
MSGTYPE_LOCATION: 48,
MSGTYPE_STATUSNOTIFY: 51,
MSGTYPE_SYSNOTICE: 9999,
MSGTYPE_POSSIBLEFRIEND_MSG: 40,
MSGTYPE_VERIFYMSG: 37,
MSGTYPE_SHARECARD: 42,
MSGTYPE_SYS: 1e4,
MSGTYPE_RECALLED: 10002,

  这命名感觉很容易理解,一般文本信息与【坏笑】【呵呵】之类的表情都是TEXT,图片照片是IMAGE,语音是VOICE,视频是VIDEO,表情包是EMOTICON,微信转账/超时退还是APP,视频或语音邀请是VOIPINVITE但网页微信不提供视频语音功能,发定位的时候是LOCATION,但这时候MsgType是TEXT,SubMsgType才是LOCATION,状态通知是MSGTYPE_STATUSNOTIFY,好友申请是VERIFYMSG,推名片是SHARECARD,收到红包的话值是10000也就是SYS,消息撤回是RECALLED。
  VOIPMSG、VOIPNOTIFY应该是为了兼容不知道什么客户端的,当收到这两个MsgType时会作为VOIPINVITE也就是视频语音邀请处理。
  而TEXT的处理会有些复杂:

"newsapp" == e.FromUserName ? t._newsMsgProcess(e) : e.AppMsgType == confFactory.APPMSGTYPE_RED_ENVELOPES ? (e.MsgType = confFactory.MSGTYPE_APP,t._appMsgProcess(e)) : e.SubMsgType == confFactory.MSGTYPE_LOCATION ? t._locationMsgProcess(e) : t._textMsgProcess(e);

  也就是先判断来源username是否是“newsapp”即新闻推送,如果是则把消息传到newMsgProcess推送新闻,不是的话继续判断AppMsgType是否是APPMSGTYPE_RED_ENVELOPES,是的话设置MsgType为APP并把消息传到appMsgProcess处理,不是的话继续判断SubMsgType是否是LOCATION,即刚刚提到的定位信息,是则传到locationMsgProcess处理位置消息,最后上述都不是的话才作为普通文本消息处理。


【16】synccheck

GET:

https://webpush.wx2.qq.com/cgi-bin/mmwebwx-bin/synccheck?r=1492011379145&skey=%40crypt_b0880df3_948ccda327bb1b3518a0d91e754b8e94&sid=xRz9uo8lLN0ojkdT&uin=130910983&deviceid=e692951215410301&synckey=1_647857157%7C2_647857323%7C3_647857324%7C1000_1491988861&=1492011378637

r:当前时间戳
skey:
sid:
uin:
deviceid:
synckey:记得我们在webwxinit时拿到的synckey吗,我们把它构造成key1_val1|key2_val2....这样的字符串提交,之后用的则是webwxsync中返回的新synckey
_:一开始是vendor.js刚装载时的时间戳,之后每次请求+1,这个定义在vendor.js里
““这个参数追踪过程可烦,我就不贴图了,大概描述下它出现的过程:首先synccheck构造了一个url,这个url并不包括“&=1492011378637”,然后它被传入了vendor.js(vendor中实现了AJAX),在vendor.js中,凡是ajax请求的url都会被加上”“这个参数,它是这么加的:”=”+jt++;,而jt是在刚装载vendor.js时定义的:var jt=ce.now(); ce.now()其实就等同于Date.now();获取了一个时间戳。所以,我们封装”“时,只需要在最开始给它一个初始时间戳就行,而后每获取一次则将其+1。
window.synccheck={retcode:"0",selector:"2"}
retcode:成功为0,1100为loginout,因各种原因被服务器踢出,其它则为synccheck error
selector;0为等待期间没有事件,若不为0说明有事件产生,应调用webwxsync获取变更。

  synccheck是一个长达25秒的阻塞循环,它出现在网页微信初始化后到退出前的整个过程,用于等待事件,如接收消息等,如果在25秒内收到事件通知,则需要调用webwxsync获取事件并更新synckey,否则直接继续下一个synccheck。


【17】webwxsync

POST:

https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=vydxc7J/J/zasaOP&skey=@crypt_378a445c_f889c0ce144f61ae33bbc5853a9f4e6e

{"BaseRequest":{"Uin":3486595268,"Sid":"vydxc7J/J/zasaOP","Skey":"@crypt_378a445c_f889c0ce144f61ae33bbc5853a9f4e6e","DeviceID":"e124233436635460"},"SyncKey":{"Count":8,"List":[{"Key":1,"Val":601490136},{"Key":2,"Val":601490173},{"Key":3,"Val":601490157},{"Key":11,"Val":601490086},{"Key":13,"Val":601490001},{"Key":1000,"Val":1492078501},{"Key":1001,"Val":1492078531},{"Key":1004,"Val":1491805192}]},"rr":-1734867418}

BaseRequest:
SyncKey:序列化后的SyncKey数组
rr:~Date.now()

;聊天消息



AddMsgCount:新消息数量
AddMsgList:新消息列表,Msg结构构成数组,在【18】中解析
BaseResponse:
ContinueFlag:index.js中没用到,暂不知道用来干啥
DelContactCount:
DelContactList:删除了联系人,结构比较简单,一个ContactFlag,CONTACTFLAG常量,因为是被删除的所以好像全是0,以及一个UserName标识被删除的用户
ModChatRoomMemberCount:
ModChatRoomMemberList:由命名推测是群聊成员变更,但在index.js中并未被使用,而实际上网页微信中也没同步群聊成员的变更,怕是负责这个功能的小哥跑路了?
ModContactCount:
ModContactList:各种联系人的变更,如改备注,被拉进群。ModContact结构在【19解析】
Profile:Profile结构,当个人信息有变更,如昵称更改,时会有这消息,包含新的个人信息,感觉好像很少用到,先不解析了。如果有变更,Profile.BitFlag会是PROFILE_BITFLAG_CHANGE(190)
Skey:一般都返回空,推测Skey可能会变更,这里用来更新Skey,但在index.js里这个Skey也没被用到
SyncCheckKey:下一次synccheck用的synckey
SyncKey:下一次webwxsync用的synckey,正常情况下它跟synccheckkey是一样的。

  webwxsync在每次synccheck获取到变更事件后被调用,通过传入synckey获取对应的变更。如收到消息,联系人变动等。获取完毕后继续synccheck。


【18】Msg结构

AppInfo:由AppID与Type组成,同样没出现在index.js里
AppMsgType:APPMSGTYPE常量
Content:消息正文
CreateTime:消息创建时间
FileName:文件名,网页微信和电脑端微信能传送文件,但它还能是app通知之类的名字,比如“转账过期退还通知”,当AppMsgType为AUDIO/VIDEO/URL/ATTACH时会用到
FileSize:如果是传文件,那它就是文件的长度。否则,它什么都不是ヽ(°◇° )ノ
ForwardFlag:没用到,不知道干嘛
FromUserName:消息来源的username
HasProductId:只在一个地方用到,当MsgType为EMOTICON时,会判断HasProductId是否非0,如果非0,则这个表情消息不受支持,作为文本消息处理
ImgHeight:
ImgWidth:表情图片的尺寸
ImgStatus:
MediaId:当AppMsgType为APPMSGTYPE_ATTACH时会用到,构造MMAppMsgDownloadUrl这个看起来像是下载链接的东西时作为mediaid参数
MsgId:这条消息的ID,在撤回消息的时候会用到
MsgType:MSGTYPE常量
NewMsgId:也是一串消息ID,跟MsgId不一样,但index.js中没用到,也不知道干啥用的
OriContent:当这条消息是位置消息时,OriContent包含了这个定位的相关信息,如下
"↵↵ ↵↵"
PlayLength:不知道干啥的
RecommendInfo:如果收到的是好友申请,RecommendInfo结构包含着好友申请的详情
Status:依然不是很清楚
StatusNotifyCode:StatusNotifyCode常量
StatusNotifyUserName:跟通知事件有关的用户名
SubMsgType:MSGTYPE常量,只会被用来判断是不是LOCATION位置消息
Ticket:~不知道
ToUserName:指示这条消息是发给谁的,一般来说就是当前登录的username了
Url:跟这条消息相关的Url,比如LOCATION的这里会是打开腾讯地图的url
VoiceLength:语音消息的长度

  附上RecommendInfo,就不解释了ლ(⌒▽⌒ლ)


【19】ModContact结构

Alias:别名,即微信号,但不是所有有设置微信号的都能拿到
AttrStatus:没用到
ChatRoomOwner:
City:城市名
ContactFlag:CONTACTFLAG常量
ContactType:
HeadImgUpdateFlag:如果非0,说明头像的url改变了
HeadImgUrl:头像url
HideInputBarFlag:idk
KeyWord:搜索用的关键字
MemberCount:
MemberList:如果改变的是群,那这里就是Member结构的数组,列出群成员
NickName:昵称
Province:省份
RemarkName:备注名
Sex:性别
Signature:个人说明
SnsFlag:
Statues:
UserName:用户名
VerifyFlag:只用在一个地方:当它为MM_USERATTRVERIFYFALG_BIZ_BRAND时说明这是个公众号

【20】webwxsendmsg

POST:

https://wx2.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg

{"BaseRequest":{"Uin":3486595268,"Sid":"UpKHjj9A0oO5Sy8D","Skey":"@crypt_378a445c_fbfed86392cbca5245a67d6d00b97b79","DeviceID":"e935506872083698"},"Msg":{"Type":1,"Content":"test","FromUserName":"@86b098d813380f17f0bf9e2f40cb2968343d7b552d77aad380d4bc6b1a6a96c5","ToUserName":"@9e17da566cd989f719f5f98f12360bfbf8815da7db980818c2efa505feb3df33","LocalID":"14921767821050970","ClientMsgId":"14921767821050970"},"Scene":0}

ClientMsgId:17位数字组成的字符串,用来作为这个消息的唯一标识,一般由13位时间戳和4位随机数组成,如下
e.ClientMsgId = e.LocalID = e.MsgId = (utilFactory.now() + Math.random().toFixed(3)).replace(".", "")
Content:消息正文
FromUserName:消息来源,一般是自己的UserName
LocalID:跟ClientMsgId一样
ToUserName:接收消息的UserName
Type:MSGTYPE常量
Scene:ADDSCENE常量,如果没被事先指定,在发送消息时会被设置为0:data.Scene = msg.Scene || 0,所以如果你不知道该写成啥,写0准没错
ADDSCENE:

ADDSCENE_PF_QQ: 4,
ADDSCENE_PF_EMAIL: 5,
ADDSCENE_PF_CONTACT: 6,
ADDSCENE_PF_WEIXIN: 7,
ADDSCENE_PF_GROUP: 8,
ADDSCENE_PF_UNKNOWN: 9,
ADDSCENE_PF_MOBILE: 10,
ADDSCENE_PF_WEB: 33,

  用来发送消息,没啥好解释的好像,不过得注意每条消息的MsgId必须得是唯一的,如果你第二条消息的Id跟第一条一样,那是发不出去的~


有缘再续。

Last Modified: July 20, 2019