|
|
昨天是周末,在家闲得无聊,于是去weiphone逛了一圈,偶然发现有人发了一帖叫《微信 for Mac》,这勾起了我的好奇心,国内做Mac开发的人确实很少,对于那些能够独自开发一些Mac第三方工具的开发者我都表示很敬畏,于是点进去看了一个究竟,如果你们好奇也可以点进去看个明白,我最终得出的结论就是:坑爹呢这是!直接用一个WebView去加载了wx.qq.com这个网页也敢自称是微信For Mac?对于这种欺骗用户的行为我十分不屑,同时也让我在思考在微信不提供API的环境下开发一款原生的微信Mac版本是否可行,最有可能的就是去分析微信Web版本的通信过程,然后在程序中模拟这个流程,在我苦苦研究了一个下午之后,终于摸透了这个过程,并用程序实现了大部分功能,下面就详细解说一下微信Web版的流程:$ r' y" ?% W# l# h. o1 m
7 [! T% A b- n; i ^
1.微信服务器返回一个会话ID
5 C- D& r: H! B7 n
; U c- [% h7 U" T4 O% R微信Web版本不使用用户名和密码登录,而是采用二维码登录,所以服务器需要首先分配一个唯一的会话ID,用来标识当前的一次登录,通过请求地址:
; z( L- Q. B7 S, ]- G, S
/ j! N6 y' ~) W1 l; xhttps://login.weixin.qq.com/jslogin?appid=wx782c26e4c19acffb&redirect_uri=https%3A%2F%2Fwx.qq.com%2Fcgi-bin%2Fmmwebwx-bin%2Fwebwxnewloginpage&fun=new&lang=zh_CN&_=1377482012272(其中1377482012272这个值是当前距离林威治标准时间的毫秒)
& K: e6 f( J# [
) ^* s% s N8 w( x4 S; b! f5 b3 f服务器会返回如下的字符串:
( _ v5 ^- I) Y# k5 }8 G) L& i8 x9 E$ w" p$ t; u6 h& Z
window.QRLogin.code = 200; window.QRLogin.uuid = “DeA6idundY9VKn”;2 t2 \. i; I& t# \' x: r
' f' o% A4 ?8 X d而这个DeA6idundY9VKn字符串就是微信服务器返回给我们的ID。( }' Z% u) [8 m6 U
3 O( {" ^8 }+ L" |/ V8 m
2.通过会话ID获得二维码- B G& f6 A+ t( S6 C& h5 ~
6 C \9 `, h" r( k! F7 k既然微信Web版本是通过二维码进行登录,如何获得这个随机的二维码呢?答案就是利用刚才获得的ID去请求服务器生成的二维码,通过上面的ID我们组合得到以下的URL地址:1 S: Q+ n) E9 o% s9 }, k0 S; y
$ A$ | y6 ~8 j% q; z: R
https://login.weixin.qq.com/qrcode/DeA6idundY9VKn?t=webwx2 U# R" D j2 @. @# ^( ?3 L( E
8 p! r; c1 F& N7 g5 {, E- p
该请求返回的便是我们需要的二维码,此时需要用户在微信的手机版本中扫描这个二维码(我就搞不明白微信官方是如何想的,登录Web版本竟然还需要手机微信去配合登录,难道没有考虑我被迫选择Web微信就是因为手机不在身边这样的情形么?)
- g4 j3 d2 S- D8 T0 `2 k) Q0 F- z1 C, M. i: {2 P4 O
3.轮询手机端是否已经扫描二维码并确认在Web端登录
2 C) _, k9 _+ ]) ^* T* p# k a: Z1 n' l3 N2 W J: m" ^
当获得二维码之后,就需要用户去手机端去扫描二维码,并获得用户的授权,此时我们并不知道用户何时完成这个操作,所以我们只有轮询,而轮询的地址就是:: n! z% l+ D8 h
3 d, p+ l; ?7 x
https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?uuid=DeA6idundY9VKn&tip=1&_=1377482045264(注意UUID和最后时间这两个参数), F: C0 C1 |* ]: N' K
, k5 A2 _2 [ V6 E如果服务器返回:' `1 A- ?( U }+ A6 i4 D
# _0 T, ~5 C' G$ S* ?) I4 Ewindow.code=201;
2 D; v4 x" W$ H* d4 Y- x. ^# K2 q0 H% J! P- a! W- j
则说明此时用户在手机端已经完成扫描,但还没有点击确认;8 V4 c* F2 `' ^ I" x
" |: ^3 \; \9 L9 P7 n
如果服务器返回:
" Y8 p: w; Q; v, C" H) [
: E/ @4 R' M4 k. ^window.redirect_uri=一个URL地址
/ C% @* k) S d+ e& N$ ?8 g8 H, ~4 {9 I! ?7 D" L ` v
则说明此时用户已经在手机端完成了授权过程,保存下这个URL地址下一步骤中使用。0 x& f6 t# s* \0 j6 P4 D' R
3 k8 d) u! V5 k8 Y" f
4.访问登录地址,获得uin和sid
1 e) Z4 O2 T: U. C' N! c7 r7 I( W) _6 Y
通过访问上一步骤中获得的URL地址,可以在服务器返回的Cookies中获得到wxuin和wxsid这两个值,这两值在后续的通信过程中都要使用到这两个值,并且Cookies中也需要包括这两项。
2 I( ]8 Q1 P- g6 U6 x4 N) {( y- `4 O' h ]" s
5.初使化微信信息) @( |; g. G' B- L6 g
{- n" M+ F) K0 n( j2 f前面的步骤算是完成了这个复杂的登录过程,如果我们需要使用微信就需要获得当前用户的信息、好友列表等,还有一个关键的就是同步信息(后续与服务器轮询中需要使用同步信息),通过访问以下的链接:
$ ~7 x+ W6 ]( b9 g" h: a; s6 [/ x) f: x
https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=1377482058764(r依然是时间)" b m# @- u' O+ N4 k, k& p
% Q8 s+ i! R ?$ G. s U访问该链接需要使用POST,并且在Body中带上以下的JSON信息:, A) H1 N! _7 N0 a5 K2 k
; G3 Q$ F9 E2 D4 [/ Z
1
* g# B4 D6 `, ^$ g# {5 a4 ^2
% C. I5 @7 T# A9 |( {5 D{"BaseRequest":2 P; v) t) I1 e# T, B5 O/ o
{"Uin":"2545437902","Sid":"QfLp+Z+FePzvOFoG","Skey":"","DeviceID":"e1615250492"}}
- x& r7 U: x8 t7 Z这个JSON串中Uin和Sid分别是上面步骤中获得的那两个Cookie值,DeviceID是一个本地生成的随机字符串(分析了官方的总是e+一串数字,所以我们也保持这样的格式)。6 X% Z# ~) M6 ^9 i8 \; h- B
Y2 _) P1 ^5 M
服务器就会返回一个很长的JSON串,这其中包括:BaseResponse中的值用来表示请求状态码,ContactList主要用来表示联系人(此列表不全,只包括了类似通讯录助手、文件助手、微信团队和一些公众帐号等,后面会通过另一接口去获得更全面的信息),SyncKey是用户与服务器同步的信息,User就是当前登录用户自己的信息。
w$ p0 }" A) e3 H$ @/ d) |
$ x: y3 v% O- J) \" }6.获得所有的好友列表4 L" B8 w2 p8 z" r! {* |2 X
! S, `9 @+ @, X, n# i
在上一步骤中已经获得了部分好友和公众帐号,如果需要获得完整的好友信息,就需要访问以下的链接:
0 b/ W2 C# x1 \4 S, v/ `; x( W7 q1 L/ d! z$ F
https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?r=1377482079876(r依然是时间)
# O4 y4 c- k) a
; v C% v' T# X+ P7 P8 M; N访问该链接同样需要POST方式,但Body为空JSON:{},服务器对身份的判定是通过Cookies,所以需要保持之前访问的Cookies不被修改(在Objective-C中会自动保存相关的Cookies,无需程序特殊处理),在返回的JSON串中,MemberList中就包含了所有的好友信息。
+ z! R$ y( b3 h" C: g) q! f2 u O! l9 M! U
7.保持与服务器的信息同步
" a$ ] E% O1 V; N% S; n$ M
% c8 V: Q# d/ B与服务器保持同步需要在客户端做轮询,该轮询的URL如下:- e, n; V4 Q, y! Y' K+ E
5 T8 G1 E* u' v( r* g$ D3 j0 z6 P# r
https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck?callback=jQuery18309326978388708085_1377482079946&r=1377482079876&
: c" B8 W! d+ I6 b. V" d2 v, s/ `; Rsid=QfLp+Z+FePzvOFoG&uin=2545437902&deviceid=e1615250492&synckey=(见以下说明)&_=1377482079876
7 V X1 \1 r2 n4 A" C5 `7 R& ~7 m' ?* s! h9 J
其中的参数r和_都是time,sid,uin,deviceid与上面步骤的值相对应,此处的synkey是上步步骤获得的同步键值,但需要按一定的规则组合成以下的字符串:
* S* Q2 f/ c1 t; b' \! W8 ~" b* b/ f4 n' x3 {/ J+ U; V
1_124125|2_452346345|3_65476547|1000_5643635& n4 Z% V8 J: n+ t* O* ^" P" C
$ c0 c- G+ k) r- j/ C
就是将键和值用_隔开,不同的键值对用|隔开,但记得|需要URL编码成%7C,通过访问上面的地址,会返回如下的字符串:
+ V Z: T+ E9 F2 }
: Y: W0 Z( E5 X* T- xwindow.synccheck={retcode:”0”,selector:”0”}; o! J! @9 R0 v9 T1 u, w
6 T6 u) r$ m. m f
如果retcode中的值不为0,则说明与服务器的通信有问题了,但具体问题我就无法预测了,selector中的值表示客户端需要作出的处理,目前已经知道当为6的时候表示有消息来了,就需要去访问另一个接口获得新的消息。' i9 S; ], {8 `2 v* L' L0 T
6 A( C g( Q' \! i! p% M. b* H
8.获得别人发来的消息0 f8 O9 o0 w2 T! X' B/ E# W
' ~' O+ S \. ^) h当一个步骤中知道有新消息时,就需要去获取消息内容,通过访问以下的链接:
, Y0 ]- {0 l1 h6 e% C! O) z
- W# M1 K8 K. Z# a9 k* v) [https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=QfLp+Z+FePzvOFoG&r=13774820798767 H+ \" U, U6 W: e
& [4 b/ h- w8 ?; n- H1 e: e$ r" T上面链接中的参数sid对应上面步骤中的值,r为时间,访问链接需要使用POST方式,Body中包括JSON串,该JSON串格式如下:
! H- W2 X! c* ^. T
+ \9 F( k8 t3 K* x: ~" f1, J) M: |' B9 k6 \; u
2
1 w- ?4 I$ ^, N6 {- K3
, a" i4 Z( C3 \; @{"BaseRequest" : {"Uin":2545437902,"Sid":"QfLp+Z+FePzvOFoG"},; u2 \. B3 U3 c* m
"SyncKey" : {"Count":4,"List":[{"Key":1,"Val":620310295},{"Key":2,"Val":620310303},{"Key":3,"Val":620310285},{"Key":1000,"Val":1377479086}]},
7 H& [7 `1 n! T/ L+ y. a"rr" :1377482079876};
3 Y+ i% E+ g0 G4 I5 I以下的信息中BaseRequest中包括的Uin与Sid与上面步骤中的值对应,SyncKey也是上面步骤中获得的同步键值对,rr为时间,访问成功之后服务器会返回一个JSON串,其中AddMsgList中是一个数组,包含了所有新消息。
( |4 x4 C: P3 f2 ]* Q! J
( M5 N9 w* R1 V. [, Q! L9.向用户发送消息! h# O: l* W& q. f. ^! W5 _
9 i7 l+ Y" w/ @. D
用户主动发送消息,通过以下的URL地址:" B& a+ S f% w8 l1 S
https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?sid=QfLp+Z+FePzvOFoG&r=1377482079876
/ o! J- K3 M5 \ y上面的sid和r参数不再解释了,访问该URL采用POST方式,在Body中的JSON串形如以下的格式:/ o( u+ x0 q3 ?5 R& M' F
* \( r, p0 e1 l0 R" B h! ]1) K) e9 s1 n) l4 j: N
2
6 w" q+ _& ~/ y0 ]" V* P; l3
0 p) } }/ G" ]* {( d( E4
- j" r& E0 W9 g/ \+ h5: m9 p/ |& E4 C- O
6
% c3 G% J. N7 N- T }7
( l/ O% [7 i1 U3 @8 Z; b8
8 j8 N, v% W) S7 w9; K* h' l r4 R2 O! g$ B3 ~) e
10
7 P/ y' d" H1 p11
( p+ t0 w4 ~8 i. C H126 { n5 | s+ q( L! e
13
! W8 _1 t' M a& p" }2 b$ ^2 P14
7 S8 C' i. h/ T+ y) E6 u15
! r( R, D& V" T1 V3 Z6 B6 [1 u16
0 w! L( M) q, p& J17
! g6 c U& E1 G( ?, v+ S$ U{/ l- r9 v! @) E: s* ~
"BaseRequest":{" f5 s& w0 s1 V3 n3 n x) {
"DeviceID" : "e441551176",4 j4 q- N$ A2 p
"Sid" : "S8wNi91Zry3024eg",' r+ B+ E6 j4 h) p# H
"Skey" : "F820928BBA5D8ECA23448F076D2E8A915E1349E9FB4F4332",! ~; j0 z1 Q# c7 F
"Uin" : "2545437902"# B1 l' }5 {) w' e1 K# c/ d" T
},. l+ {/ a) G) ~( S: L4 U* E
"Msg" : {7 o! \0 s6 e! r7 r2 e
"ClientMsgId" : 1377504862158,* M W/ Y# g: C( r2 n" f# ]
"Content" : "hello",
/ O5 p/ C) |2 e2 b) n "FromUserName" : "wxid_2rrz8g8ezuox22",7 U/ S/ ~! k. q# ^
"LocalID" : 1377504862158,
( V- d' w! U" ? "ToUserName" : "wxid_j4nu420ojhsr21",# `, a* x& H, ?" W) @
"Type" : 1
' N- W6 `6 G% y1 C, _. U$ m1 u2 v },
; F k; V9 h$ v/ g- l3 ^2 G. A4 S "rr" = 13775048644635 R, `2 b1 R$ q, U9 L3 F& y
}# W, E% l: ~* X' h7 J5 C
其中BaseRequest都是授权相关的值,与上面的步骤中的值对应,Msg是对消息的描述,包括了发送人与接收人,消息内容,消息的类型(1为文本),ClientMsgId和LocalID由本地生成。rr可用当前的时间。/ w- z: X1 L5 f# L
在返回JSON结果中BaseResponse描述了发送情况,Ret为0表示发送成功。
& J' W k( S" C* P5 R$ G! ] q6 }
|
|