基於nodejs的http模塊通過smartqq實現自動收發qq消息的程序


      背景:2月1日我們實驗室的qq群引入了一個聊天機器人,可實現簽到,打劫,玩游戲(如24點,猜字謎等)等的功能,簽到,打劫成功,游戲勝利(如24點回答正確)可獲得積分,寒假時未曾關注群所以開學時自己毫無積分,而其他同學都已為富一方,尤其是某學長積分竟達十萬之巨,(簽到一次100左右,24點回答正確100),遂疑惑,問之,學長曰:無他,刷分耳。於是在4月9日,參考了下學長的思路(http://www.zhangzaizai.com/2017/02/08/xiaozi-helper/),便開始試着開發一個刷分的輔助。

  技術:我的后端平台是nodejs,其他框架不是學得很深所以用最底層的http模塊來發送post請求。用明文傳輸信息的smartqq來作為post的接受端。

  步驟

  1.   了解只需發送兩種post請求,一是代表發送信息的send_qun_msg2,可以看到在F12下post信息十分詳細,那么在nodejs中照本宣科即可。send_qun_msg2
    var http = require('http');
    exports.post = function (contents) {
        var options = {
            host: "d1.web2.qq.com",
            method: "POST",
            path: "/channel/send_qun_msg2",
            headers: {
                'Host': 'd1.web2.qq.com',
                'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0',
                'Accept': '*/*',
                'Accept-Language': 'en-US,en;q=0.5',
                'Content-Type': 'application/x-www-form-urlencoded',
                'Referer': 'https://d1.web2.qq.com/cfproxy.html?v=20151105001&callback=1',
                'Content-Length': contents.length,
                'Cookie': '',/*出於安全考慮隱去cookie*/
                'Connection': 'keep-alive'
            }
        };
        var req = http.request(options, function (res) {});
        req.write(contents);
        req.end();
    };

     

     


    我為了操作方便把它封裝在了模塊中。

  2. 然而這看似簡單的代碼實際上出過很大的問題,這是我已經整理過得代碼,但是在初期我的content並不是外部的參數,而是自己定義的一個JSON(如下
    var rr = {
        "group_uin": 4111913875,
        "content": "[\"開始24點\",[\"font\",{\"name\":\"宋體\",\"size\":10,\"style\":[0,0,0],\"color\":\"000000\"}]]",
        "face": 210,
        "clientid": 53156849,/*出於安全考慮隱去真實數字,換以同長數字*/
        "msg_id": 62220001,
        "psessionid": ""/*出於安全考慮隱去psessionid*/
    };

     

     

     

    ),但是傳輸老失敗,后來詢問學長,發現實際傳輸的內容並不是直接JSON,而需要經過URLencode加密,於是我調用了nodejs自帶的encodeURI函數把上述加密后傳輸(),后即成功
    var contents2 = encodeURI('r=' + JSON.stringify(rr));

     

     

     
  3. 但馬上我就碰到了另一個問題,我的響應是一堆亂碼,雖然給群里發送消息並不需要考慮響應,但是我post的方式肯定是一樣的,也就意味着我接受群里消息時也是一頓亂碼,就更別談處理了。這個問題大概花了我兩天時間,一開始懷疑是charset,utf-8的問題,於是在請求頭里各種改(比如request.setCharacterEncoding("utf-8")),但是並沒有用,又懷疑smartqq用的根本不是utf-8,一查是的(如下圖)smartqq的頁面信息
  4. 然后繼續想,這期間查閱了不少網站,比如(http://blog.csdn.net/zsr_251/article/details/49993911)於是猜測問題在buffer的拼接上,幾經周折嘗試各種方法(如
    var buf=Buffer.concat(chunks,size);
     var str=iconv.decode(buf,'utf-8');

     

     

     

     
    )還是不行。
  5. 最后打算對於請求頭信息一個一個分析發現了這個
    'Accept-Encoding': 'gzip, deflate, br',

     

     

     

     
    於是尋求相關信息,發現(http://www.jb51.net/article/61721.htm)這個網站,嘗試着解壓了下響應體,如
    zlib.unzip(body, function (err, buffer) {
    console.log(buffer.toString());
    }) 

     

     

     

     

       6. 然后就ok了,當屏幕上出現send ok時心情還是蠻激動的,然后十分興奮的告訴學長,只聽學長一聲“你可以發請求頭要求它不做gzip壓縮的”,遂刪去Accept-Encoding。發送消息至此就算成功了。在此向某個被我拿來做實驗的群說聲抱歉,我不是故意水的。。

  接下來是接受消息。

      在smartqq上接受消息的原理是不斷post poll2當有人發給你消息時post就會響應,響應內容即為接受內容。那么模擬它即可。

  此post跟上述的並沒有太大的不同,但是要注意option的path要變了,以及content-length固定不變,option的代碼如下,

var options = {
    host: "d1.web2.qq.com",
    method: "POST",
    path: "/channel/poll2",/*注意此處*/
    headers: {
        'Host': 'd1.web2.qq.com',
        'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0',
        'Accept': '*/*',
        'Accept-Language': 'en-US,en;q=0.5',
        'Content-Type': 'application/x-www-form-urlencoded',
        'Referer': 'https://d1.web2.qq.com/cfproxy.html?v=20151105001&callback=1',
        'Content-Length': '395',/*我認為由於參數不同你們的不一定是395,自己抓包即可獲得長度*/
        'Cookie': '',/*出於安全考慮隱去cookie*/
        'Connection': 'keep-alive'
    }
};

 

 

 

   重點在於對響應的處理。當我輸入“開始24點”后小紫(機器人)會返回這樣一段話:"24點開始!數字(共有6種解法):8, 8, 8, 5將以上數字加減乘除(+,-,*,/)得出24,並列出算式即可。如發送:24點 (4-2)*12/1",當我回答正確后會返回:"很強悍!算式正確。增加100積分當前數字(共有20種解法):9, 9, 3, 5將以上數字加減乘除(+,-,*,/)得出24,並列出算式即可。如發送:24點 (4-2)*12/1",我要做的是正確獲取四個數字並避免其他信息干擾,同時不能在沒獲得信息,即沒人互動時程序整個掛掉(會返回一個HTML網頁),還要根據4個數字計算得出正確的字符串。

  1. 從24點算法開始,我在(http://www.99cankao.com/numbers/24game.php)網頁上直接找到了計算24點的代碼(如下圖)24點,把輸入的交互改成參數傳入,並把返回值改成字符串后就可以直接用了,我把它封裝在了calc模塊中方便調用,代碼如下。
    function tdisoper(f0, f1, f2, f3) {
    this[0] = f0;
    this[1] = f1;
    this[2] = f2;
    this[3] = f3;
    }
    disoper = new tdisoper("-", "+", "/", "*");
    function oper(f, m, n) {
    if (f === 3) return (m * n);
    if (f === 2) return (m / n);
    if (f === 1) return (parseFloat(m) + parseFloat(n));
    if (f === 0) return (m - n);
    }
    function tb(i1, i2, i4, i8) {
    this[1] = i1;
    this[2] = i2;
    this[4] = i4;
    this[8] = i8;
    }
    function valid(a,b,c,d) {
    var result = '';
    n = 1;
    b = new tb(a, b, c, d);
    k = 0;
    var output = "";
    for (i1 = 1; i1 <= 8; i1 *= 2)
    for (i2 = 1; i2 <= 8; i2 *= 2)
    for (i3 = 1; i3 <= 8; i3 *= 2)
    for (i4 = 1; i4 <= 8; i4 *= 2) {
    if ((i1 | i2 | i3 | i4) !== 0xf) continue;
    for (f1 = 0; f1 <= 3; f1++)
    for (f2 = 0; f2 <= 3; f2++)
    for (f3 = 0; f3 <= 3; f3++) {
    m = oper(f3, oper(f2, oper(f1, b[i1], b[i2]), b[i3]), b[i4]);
    if (Math.abs(m - 24) < 1e-5) {
    result = result + "((" + b[i1] + disoper[f1] + b[i2] + ")" + disoper[f2] + b[i3] + ")" + disoper[f3] + b[i4];
    if ((n !== 0) && (++k >= n)) return result;
    }
    m = oper(f1, b[i1], oper(f3, oper(f2, b[i2], b[i3]), b[i4]));
    if (Math.abs(m - 24) < 1e-5) {
    result = result + b[i1] + disoper[f1] + "((" + b[i2] + disoper[f2] + b[i3] + ")" + disoper[f3] + b[i4] + ")";
    if ((n !== 0) && (++k >= n)) return result;
    }
    m = oper(f3, oper(f1, b[i1], oper(f2, b[i2], b[i3])), b[i4]);
    if (Math.abs(m - 24) < 1e-5) {
    result = result + "(" + b[i1] + disoper[f1] + "(" + b[i2] + disoper[f2] + b[i3] + "))" + disoper[f3] + b[i4];
    if ((n !== 0) && (++k >= n)) return result;
    }
    m = oper(f1, b[i1], oper(f2, b[i2], oper(f3, b[i3], b[i4])));
    if (Math.abs(m - 24) < 1e-5) {
    result = result + b[i1] + disoper[f1] + "(" + b[i2] + disoper[f2] + "(" + b[i3] + disoper[f3] + b[i4] + "))";
    if ((n !== 0) && (++k >= n)) return result;
    }
    m = oper(f2, oper(f1, b[i1], b[i2]), oper(f3, b[i3], b[i4]));
    if (Math.abs(m - 24) < 1e-5) {
    result = result + "(" + b[i1] + disoper[f1] + b[i2] + ")" + disoper[f2] + "(" + b[i3] + disoper[f3] + b[i4] + ")";
    if ((n !== 0) && (++k >= n)) return result;
    }
    
                                }
                    }
    return result;
    
    exports.cal = function(a,b,c,d){
    return valid(a,b,c,d);
    };

     

     

  2. 處理響應體並發送,代碼如下,注釋基本解釋清楚,如有疑問歡迎提問。
    var req = http.request(options, function (res) {
        res.on('data', function (body) {
            if (body.toString().search('<html>') === -1) {/*此處是為了避免長時間未收到數據導致程序直接崩潰*/
                var str;
                str = JSON.parse(body.toString());/*解析響應體為JSON格式*/
                console.log("接受消息  " + str.result[0].value.content);/*獲得接受內容*/
                if (str.result[0].value.send_uin === 8888888/*此處出於安全考慮。。*/) {
                    var usethen;/*含有關鍵4個數字的字符串*/
                    if (str.result[0].value.content.length >= 6)/*避免無關信息干擾*/
                        usethen = str.result[0].value.content[5];
                    if (usethen!==undefined) {
                        var use = usethen.match(/\u89e3[^\n]*\u4ee5/g);/*利用正則表達式獲取從'解'到'以'的字符串*/
                        var number;
                        if (use!==undefined)
                            number = use[0].match(/[0-9]/g);  /*利用正則獲取4個重要數字*/
                        console.log("獲得數字" + number);
                        if (number !== undefined) {
                            var result = calc.cal(number[0], number[1], number[2], number[3]);
                            console.log("得到結果" + result);
                            var reg = /["][^\n]*[",]/;
                            rr.content = rr.content.replace('開始24點', "24點 " + result);/*rr就是上面的rr*/
                            contents2 = encodeURI('r=' + JSON.stringify(rr));
                            contents2 = contents2.replace('+', '%2b');/*為什么有這四個,我要放在下面講*/
                            contents2 = contents2.replace('+', '%2b');
                            contents2 = contents2.replace('+', '%2b');
                            contents2 = contents2.replace('+', '%2b');
                            post.post(contents2);
                        }
                    }
                }
            }
        });
        res.on('err', function (err) {
            console.log(err);
        })
    });
    req.write(contents);
    req.end();

     

    為什么我要寫四個contents = contents.replace('+','%2b')呢,因為encodeURI函數不會轉換加號等特殊字符,導致我發送出去的字符串一直沒有加號,這里又卡了我一下。如有更好的方法,希望有大神教與我。

  3. 最后是輪詢發送poll2的問題,我一開始一直有error顯示write after end,后來發現是我沒有給req重新賦值,相當於請求結束后繼續write自然有錯,於是把賦值寫在了輪詢中。

至此,程序編的差不多了,作為並不熟練的noder,自然是有很多瑕疵,也希望有大神能夠指出。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM