挖掘微信Web版通信的全過程

weixin

昨天是週末,在家閒得無聊,於是去weiphone.com逛了一圈,偶然發現有人發了一帖叫《微信 for Mac》,這勾起了我的好奇心,國內做Mac開發的人確實很少,對於那些能夠獨自開發一些Mac第三方工具的開發者我都表示很敬畏,於是點進去看了一個究竟,如果你們好奇也可以點進去看個明白,我最終得出的結論就是:坑爹呢這是!直接用一個WebView去加載了wx.qq.com這個網頁也敢自稱是微信For Mac?對於這種欺騙用戶的行爲我十分不屑,同時也讓我在思考在微信不提供API的環境下開發一款原生的微信Mac版本是否可行,最有可能的就是去分析微信Web版本的通信過程,然後在程序中模擬這個流程,在我苦苦研究了一個下午之後,終於摸透了這個過程,並用程序實現了大部分功能,下面就詳細解說一下微信Web版的流程:

1.微信服務器返回一個會話ID

微信Web版本不使用用戶名和密碼登錄,而是採用二維碼登錄,所以服務器需要首先分配一個唯一的會話ID,用來標識當前的一次登錄,通過請求地址:

https://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這個值是當前距離林威治標準時間的毫秒)

服務器會返回如下的字符串:

window.QRLogin.code = 200; window.QRLogin.uuid = “DeA6idundY9VKn”;

而這個DeA6idundY9VKn字符串就是微信服務器返回給我們的ID。

2.通過會話ID獲得二維碼

既然微信Web版本是通過二維碼進行登錄,如何獲得這個隨機的二維碼呢?答案就是利用剛纔獲得的ID去請求服務器生成的二維碼,通過上面的ID我們組合得到以下的URL地址:

https://login.weixin.qq.com/qrcode/DeA6idundY9VKn?t=webwx

該請求返回的便是我們需要的二維碼,此時需要用戶在微信的手機版本中掃描這個二維碼(我就搞不明白微信官方是如何想的,登錄Web版本竟然還需要手機微信去配合登錄,難道沒有考慮我被迫選擇Web微信就是因爲手機不在身邊這樣的情形麼?)

3.輪詢手機端是否已經掃描二維碼並確認在Web端登錄

當獲得二維碼之後,就需要用戶去手機端去掃描二維碼,並獲得用戶的授權,此時我們並不知道用戶何時完成這個操作,所以我們只有輪詢,而輪詢的地址就是:

https://login.weixin.qq.com/cgi-bin/mmwebwx-bin/login?uuid=DeA6idundY9VKn&tip=1&_=1377482045264(注意UUID和最後時間這兩個參數)

如果服務器返回:

window.code=201;

則說明此時用戶在手機端已經完成掃描,但還沒有點擊確認;

如果服務器返回:

window.redirect_uri=一個URL地址

則說明此時用戶已經在手機端完成了授權過程,保存下這個URL地址下一步驟中使用。

4.訪問登錄地址,獲得uin和sid

通過訪問上一步驟中獲得的URL地址,可以在服務器返回的Cookies中獲得到wxuin和wxsid這兩個值,這兩值在後續的通信過程中都要使用到這兩個值,並且Cookies中也需要包括這兩項。

5.初使化微信信息

前面的步驟算是完成了這個複雜的登錄過程,如果我們需要使用微信就需要獲得當前用戶的信息、好友列表等,還有一個關鍵的就是同步信息(後續與服務器輪詢中需要使用同步信息),通過訪問以下的鏈接:

https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=1377482058764(r依然是時間)

訪問該鏈接需要使用POST,並且在Body中帶上以下的JSON信息:

1
2
{"BaseRequest":
{"Uin":"2545437902","Sid":"QfLp+Z+FePzvOFoG","Skey":"","DeviceID":"e1615250492"}}

這個JSON串中Uin和Sid分別是上面步驟中獲得的那兩個Cookie值,DeviceID是一個本地生成的隨機字符串(分析了官方的總是e+一串數字,所以我們也保持這樣的格式)。

服務器就會返回一個很長的JSON串,這其中包括:BaseResponse中的值用來表示請求狀態碼,ContactList主要用來表示聯繫人(此列表不全,只包括了類似通訊錄助手、文件助手、微信團隊和一些公衆帳號等,後面會通過另一接口去獲得更全面的信息),SyncKey是用戶與服務器同步的信息,User就是當前登錄用戶自己的信息。

6.獲得所有的好友列表

在上一步驟中已經獲得了部分好友和公衆帳號,如果需要獲得完整的好友信息,就需要訪問以下的鏈接:

https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?r=1377482079876(r依然是時間)

訪問該鏈接同樣需要POST方式,但Body爲空JSON:{},服務器對身份的判定是通過Cookies,所以需要保持之前訪問的Cookies不被修改(在Objective-C中會自動保存相關的Cookies,無需程序特殊處理),在返回的JSON串中,MemberList中就包含了所有的好友信息。

7.保持與服務器的信息同步

與服務器保持同步需要在客戶端做輪詢,該輪詢的URL如下:

https://webpush.weixin.qq.com/cgi-bin/mmwebwx-bin/synccheck?callback=jQuery18309326978388708085_1377482079946&r=1377482079876&
sid=QfLp+Z+FePzvOFoG&uin=2545437902&deviceid=e1615250492&synckey=(見以下說明)&_=1377482079876

其中的參數r和_都是time,sid,uin,deviceid與上面步驟的值相對應,此處的synkey是上步步驟獲得的同步鍵值,但需要按一定的規則組合成以下的字符串:

1_124125|2_452346345|3_65476547|1000_5643635

就是將鍵和值用_隔開,不同的鍵值對用|隔開,但記得|需要URL編碼成%7C,通過訪問上面的地址,會返回如下的字符串:

window.synccheck={retcode:”0”,selector:”0”}

如果retcode中的值不爲0,則說明與服務器的通信有問題了,但具體問題我就無法預測了,selector中的值表示客戶端需要作出的處理,目前已經知道當爲6的時候表示有消息來了,就需要去訪問另一個接口獲得新的消息。

8.獲得別人發來的消息

當一個步驟中知道有新消息時,就需要去獲取消息內容,通過訪問以下的鏈接:

https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsync?sid=QfLp+Z+FePzvOFoG&r=1377482079876

上面鏈接中的參數sid對應上面步驟中的值,r爲時間,訪問鏈接需要使用POST方式,Body中包括JSON串,該JSON串格式如下:

1
2
3
{"BaseRequest" : {"Uin":2545437902,"Sid":"QfLp+Z+FePzvOFoG"},
"SyncKey" : {"Count":4,"List":[{"Key":1,"Val":620310295},{"Key":2,"Val":620310303},{"Key":3,"Val":620310285},{"Key":1000,"Val":1377479086}]},
"rr" :1377482079876};

以下的信息中BaseRequest中包括的Uin與Sid與上面步驟中的值對應,SyncKey也是上面步驟中獲得的同步鍵值對,rr爲時間,訪問成功之後服務器會返回一個JSON串,其中AddMsgList中是一個數組,包含了所有新消息。

9.向用戶發送消息

用戶主動發送消息,通過以下的URL地址:
https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxsendmsg?sid=QfLp+Z+FePzvOFoG&r=1377482079876
上面的sid和r參數不再解釋了,訪問該URL採用POST方式,在Body中的JSON串形如以下的格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
    "BaseRequest":{
        "DeviceID" : "e441551176",
        "Sid" : "S8wNi91Zry3024eg",
        "Skey" : "F820928BBA5D8ECA23448F076D2E8A915E1349E9FB4F4332",
        "Uin" : "2545437902"
    },
    "Msg" : {
        "ClientMsgId" : 1377504862158,
        "Content" : "hello",
        "FromUserName" : "wxid_2rrz8g8ezuox22",
        "LocalID" : 1377504862158,
        "ToUserName" : "wxid_j4nu420ojhsr21",
        "Type" : 1
    },
    "rr" = 1377504864463
}

其中BaseRequest都是授權相關的值,與上面的步驟中的值對應,Msg是對消息的描述,包括了發送人與接收人,消息內容,消息的類型(1爲文本),ClientMsgId和LocalID由本地生成。rr可用當前的時間。
在返回JSON結果中BaseResponse描述了發送情況,Ret爲0表示發送成功。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章