利用網頁版微信API做一個微信機器人

noahlam發表於2019-02-16

本文不涉及到 AI 的知識,如果你是衝著 AI 來的,那麼可能會讓你失望了.

前一陣子一個朋友找我,問我能不能搞一個微信自動加好友的軟體,(在普通人眼裡,程式設計師就是專門寫木馬病毒外掛軟體的三流黑客.不會寫那就連三流都不是.

所以為了證明我是三流黑客,我隨便百度了兩個現成的給他.本來事情到這裡應該結束了的,不過本著探索的精神,想順便了解一下這種外掛的原理,於是百歌谷度了一下,
最終原理沒找到,倒是找到幾個有意思的 github 倉庫,利用網頁版的微信 API 做第三方微信.

先看個效果?

<img src=”https://raw.githubusercontent…; width=”300″ />
<img src=”https://raw.githubusercontent…; width=”300″ />

步驟

我們看看大致步驟

  1. 獲取 UUID
  2. 根據 UUID 獲取二維碼
  3. 掃碼登陸, 獲取登陸資訊
  4. 拿登陸資訊換初始化資料
  5. 拿資料初始化
  6. 獲取好友列表和訊息列表
  7. 傳送訊息

以下為具體過程,不感興趣的可以直接拉到末尾檢視原始碼倉庫

需要注意的是,每一步的請求所使用的方法(POST/GET) 和 Content-Type 都是不一樣的,下面我都有標註,如果有請求不通的請參考 gtihub 原始碼.

一、獲取 UUID

介面地址 https://wx.qq.com/jslogin

請求方法 POST

引數型別(content-type) application/x-www-form-urlencoded

引數

{
    appid: `wx782c26e4c19acffb`,
    fun: `new`,
    lang: `zh_CN`,
     _: new Date().valueOf()
}

除了最後一個當前時間戳不是固定的,其他的3個引數都是寫死的,照抄即可,呼叫成功的話,會到一個字串 window.QRLogin.code = 200; window.QRLogin.uuid = "obizONtqZA==";, 需要自己想辦法擷取到 window.QRLogin.uuid = 後面的那串字元,即 UUID.

二、獲取二維碼

這一步很簡單,有了 UUID 後,我們可以直接請求 `https://wx.qq.com/qrcode/` + UUID 獲取到二維碼. 獲取到二維碼以後,先別急著去掃描二維碼,因為我們要先去監聽二維碼的掃描狀態,這樣我們才能知道什麼時候被登陸.

請求方式 GET 無需引數

三、監聽二維碼的掃描結果

介面地址 https://wx.qq.com/cgi-bin/mmw…

請求方法 GET

引數型別(content-type) application/x-www-form-urlencoded

引數

{
    tip: 0,
    uuid: `obizONtqZA==`,
    _: new Date().valueOf(),
    loginicon: true
}

tip 取值 0 或 1, 監聽分2個階段,第一階段,監聽使用者是否掃碼,tip 為 0,第二階段,監聽使用者是否在微信上點確認登陸,tip 為 1.

uuid 就是第一步獲取到的那個 UUID

_ 當前時間戳

loginicon 我猜應該是否掃碼完返回使用者頭像,都填 true 即可.

返回結果,當你掃描二維碼的時候,介面會返回你一個這樣的物件

{
    `window.code`: 201,
    `window.userAvatar`: 頭像的 base64 地址
}

得到的 code 是 201, 說明已掃碼,但並不代表已登陸,還需要繼續監聽是否在手機微信上點選 確認登陸 按鈕(重複上面步驟,把 引數裡的 tip 改為 1 即可)

這步如果成功的話,會返回一個如下物件

{
    `window.code`: `200`,
    `window.redirect_uri`: `https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxnewloginpage?ticket=ARD37_ikx-Kakd2i0W-f-E7q@qrticket_0&uuid=4f6yOkV4AA==&lang=zh_CN&scan=1548300672` }
}

四、獲取初始化資料(敏感資料)

上一步獲取到的資料裡面的 window.redirect_uri 裡包含了一個 url 和一些 查詢引數,直接請求這個地址好像沒辦法成功,需要將 url 和 引數拆分,然後加入其他引數

介面地址 就是上面的 url

請求方法 GET

引數型別(content-type) application/x-www-form-urlencoded

引數

{
    ticket: 上面得到的 ticket,
    uuid: 上面得到的 uuid,
    lang: `zh_CN`,  // 固定
    scan: 上面得到的 scan,
    fun: `new` // 固定
}

這一步的返回的頭部裡面,會有個 cookie ,需要存起來,接來來得到請求頭裡面要帶上這個 cookie,另外就是一個 xml 格式的 敏感的資訊,也是要存起來.

tip: xml 格式可以用 xml2js 轉換成 json.

五、初始化

呼,到這一步,終於接近登陸成功了,只需再呼叫以下介面,初始化以下

介面地址 https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxinit?r=${~(new Date().valueOf())}

請求方法 POST

引數型別(content-type) application/json

引數

{
    BaseRequest: {
        DeviceID: `e747337466044216`, // 這個好像隨便填都可以
        Sid: 上一步獲取到的 wxsid,
        Uin: 上一步獲取到的 wxuin,
        Skey: 上一步獲取到的 skey
    }
}

這裡有 2 個地方跟之前不同的,第一是地址後面要跟一個時間戳,而且這個時間戳還要按位取反,第二個是請求引數是放在 BaseRequest 下面,而不是物件的一級屬性下面.

返回的資料裡面有 2 個資料需要儲存起來,一個是 data.SyncKey, 一個是 res.data.User.UserName,後面都會用到

到此才真正完成登陸,下面如果你不需要好友列表的話,可以直接收取訊息了

六、檢測新訊息

介面地址 https://webpush.wx.qq.com/cgi…

請求方法 GET

引數型別(content-type) application/json

引數

let time = new Date().getTime()

let synckey = ``

let sk = data.SyncKey.List || []   // data.SyncKey 就是上一步獲取到的那個

for (let i = 0; i < sk.length; i++) {
    synckey += `${sk[i].Key}_${sk[i].Val}`
    if (i !== sk.length - 1) synckey += `|`
}

// 傳遞的引數
{
    r: time,
    sid: 第四步拿到的 wxsid,
    uin: 第四步拿到的 wxuin,
    skey: 第四步拿到的 skey,
    deviceid: `e747337466044216`, // 同上一步
    synckey: synckey,
    _: time
}

返回內容的 data 裡面 包含如下內容

window.synccheck={retcode:"0",selector:"2"}

如果 selector 是 2, 說明有新訊息,走下一步,獲取訊息內容

七、獲取訊息內容

介面地址 https://wx.qq.com/cgi-bin/mmw…

請求方法 POST

引數型別(content-type) application/json

引數

{
    BaseRequest: {
        Uin: 第四步拿到的 wxuin,
        Sid: 第四步拿到的 wxsid,
        Skey: 第四步拿到的 skey,
        DeviceID: `e747337466044216`, // 同上一步
    },
    SyncKey: data.SyncKey, // 還記得上一步我們費盡千辛萬苦轉換這個資料嗎? 你沒看錯,這裡不需要轉換,就是這麼神奇
    rr: ~(new Date().valueOf())
}

返回結果裡面有個 data.AddMsgList 就是訊息列表了,還有個 data.SyncCheckKey 就是下次請求的時候用的 SyncKey, 每次都會變的.

AddMsgList 是一個陣列,裡面可能包含多條訊息,訊息的自動比較多,就不一一說明了,這裡說說 2 個比較重要的欄位,其他的欄位有興趣的可以自己列印出來看一下.

FromUserName 對方的微信名,說是微信名,其實是一個 @ 或 @@ 開頭的內部的id, 完全不可讀,據我猜測 @ 開頭的應該是普通好友, @@ 開頭的是群或者公眾號之類的

Content 訊息內容

有了訊息內容,和發訊息的人,我們就可以回覆對方,不過回覆什麼? 當然不可能寫一大堆 if else 或者 switch case 去適應各種情況,不妨網上搜尋一下 價值一個億的ai程式碼 哈哈哈

八、獲取自動回覆內容

這邊我用的是圖靈機器人的 API 地址,當然你也可以用其他的.

介面地址 http://openapi.tuling123.com/…

請求方法 POST

引數型別(content-type) application/json

引數

{

perception: {
    inputText: {
        text: `待回覆的訊息`
    }
},
userInfo: {
    apiKey: tulingApiKey,  // 在圖靈官網申請
    userId: tulingUserId   // 同上

}

你要是懶得去申請的話,可以在我的專案裡面複製, 在 src/global.js 裡面,在返回的內容裡面 data.results[0].values.text 下面可以看到圖靈給你生成的自動回覆內容(results是一個陣列,支援一次回覆多條)

九、回覆訊息

拿到自動回覆以後,我們只需要把它發給你的好友,即完成一次自動對話.

介面地址 https://wx.qq.com/cgi-bin/mmw…

請求方法 POST

引數型別(content-type) application/json

引數

let timeStamp = new Date().getTime() + `` + (9000 * Math.random() + 1000)
{
    BaseRequest: {
      Uin: 同上,
      Sid: 同上,
      Skey: 同上,
      DeviceID: 同上
    },
    Msg: {
      Type: 1,  // 訊息型別 1 是文字訊息,其他的暫時沒用過
      Content: `回覆的內容`,
      FromUserName: `你的使用者名稱,在第五步有拿到`,
      ToUserName: `對方的微信名 第七步的 FromUserName`,
      LocalID: timeStamp,
      ClientMsgId: timeStamp
}

傳送成功的話,會返回如下內容

{
    BaseResponse: { Ret: 0, ErrMsg: `` },
    MsgID: `2033517278669301361`,
    LocalID: ``
}

好了,這樣我們的一個自動回覆機器人就完成了.完整的程式碼在這裡

廣告時間

我們40人的前端團隊常年招兵買馬中,在廈門的和想來廈門的童鞋們,不要吝惜你的簡歷,使勁砸過來 郵箱:atob(`bnVveWFAZ2FvZGluZy5jb20=`), 期待你一起來稿

對本文有意見或者建議,請儘量在 github 上提 issue, 最近比較忙,比較不怎麼逛社群

相關文章