[php tech.] MSN Bot step by step part 2 Connect HOWTO

連線,其實並不是一件簡單的事情。要怎麼樣讓一個程序,能夠持續的跟伺服器溝通,卻不會因為逾時等問題導致連線中斷,這就是即時通訊比較麻煩的地方。首先,我們首先需要做的,就是跟 MSN 的服務做溝通的動作,這個溝通動作有詳細的命令,請參閱:http://msnpiki.msnfanatic.com/index.php/Reference:Commands

注意,溝通的動作如果送太多次,會被 MSN 服務給擋掉喔!所以請勿輕易用固定 IP 的主機嘗試。

首先,跟 MSN 服務溝通的第一步,我們使用 PHP 的 fsockopen 來與服務器做溝通。並且告訴他我們所使用的通訊協定版本。

<?php
    $id = 1;
    $fp = fsockopen('messenger.hotmail.com', 1863, $errno, $errstr, 5);
    stream_set_timeout($fp, 2);
    if (!$fp) {
        echo "Can't connect to messenger.hotmail.com:1863, error => $errno, $errstr";
        exit;
    }
    fwrite($fp, "VER $id MSNP15 CVR0\r\n");
    fclose($fp);

你沒有看錯,這就是連線的第一步,單純的告訴 MSN 服務器,我要使用 MSNP15 的 Protocol。接下來呢,我們要告訴他關於我們(客戶端)的一些訊息,使用 CVR 這個指令來完成。

<?php
   $id = 1;
   $fp = fsockopen('messenger.hotmail.com', 1863, $errno, $errstr, 5);
   stream_set_timeout($fp, 2);
   if (!$fp) {
       echo "Can't connect to messenger.hotmail.com:1863, error => $errno, $errstr";
       exit;
   }
   fwrite($fp, "VER $id MSNP15 CVR0\r\n");
    while (!feof($fp)) {
        $data = fgets($fp, 4096);

        $id++;
        $code = substr($data, 0, 3);
        switch ($code) {
            case 'VER':
                fwrite($fp, "CVR $id 0x0409 winnt 5.1 i386 MSMSGS 8.1.0178 msmsgs [email protected]\r\n");
            break;
            default:
                fwrite($fp, "OUT\r\n");
        }
    }
    fclose($fp);

嗯?就這樣,你也沒看錯,我用了 CVR 來告訴他我所使用的帳號以及我的相關資訊。關於 CVR 能夠傳送甚麼東西,請參考這裡的說明:http://msnpiki.msnfanatic.com/index.php/Reference:Commands。

接下來,我要告訴他我是誰。這時候要先送個 USR 指令跟他說。

<?php
   $id = 1;
   $fp = fsockopen('messenger.hotmail.com', 1863, $errno, $errstr, 5);
   stream_set_timeout($fp, 2);
   if (!$fp) {
       echo "Can't connect to messenger.hotmail.com:1863, error => $errno, $errstr";
       exit;
   }
   fwrite($fp, "VER $id MSNP15 CVR0\r\n");
    while (!feof($fp)) {
       $data = fgets($fp, 4096);
       $id++;
       $code = substr($data, 0, 3);
       switch ($code) {
           case 'VER':
               fwrite($fp, "CVR $id 0x0409 winnt 5.1 i386 MSMSGS 8.1.0178 msmsgs [email protected]\r\n");
           break;
            case 'CVR':
                fwrite($fp, "USR $id SSO I [email protected]\r\n");
            break;
           default:
               fwrite($fp, "OUT\r\n");
       }
   }
    fclose($fp);

跟他說完我是誰之後,接下來的動作便是檢查是否已經登入了 Passport.NET 的網絡。登入的動作就是上一篇所說的 SSO 的動作(如果是使用 MSNP9 的話則是 TWN),如果已經登入了,那我們就需要取得 Passport.NET 的 token 來丟給 MSN 服務,讓他知道我已經登入,並且取得 ticket 了。另外,這裡需要一些特別的運算,我後面再提。

<?php
   $id = 1;
   $fp = fsockopen('messenger.hotmail.com', 1863, $errno, $errstr, 5);
   stream_set_timeout($fp, 2);
   if (!$fp) {
       echo "Can't connect to messenger.hotmail.com:1863, error => $errno, $errstr";
       exit;
   }
   fwrite($fp, "VER $id MSNP15 CVR0\r\n");
   while (!feof($fp)) {
       $data = fgets($fp, 4096);
       $id++;
       $code = substr($data, 0, 3);
       switch ($code) {
           case 'VER':
               fwrite($fp, "CVR $id 0x0409 winnt 5.1 i386 MSMSGS 8.1.0178 msmsgs [email protected]\r\n");
           break;
           case 'CVR':
               fwrite($fp, "USR $id SSO I [email protected]\r\n");
           break;
            case 'USR':
                // 這裡是做登入檢查的地方,現在暫時不做,讓他直接離開。
                fwrite($fp, "OUT\r\n");
            break;
           default:
               fwrite($fp, "OUT\r\n");
       }
   }
   fclose($fp);

我想取得 ticket 的地方並不麻煩,最主要的是要將 login_code 算出來,在 MSNP15:SSO 的地方其實有提到要如何去計算那些值。而在現成的類別裡面,也已經提供了運算的方式,所以我這邊就不再贅述,基本上這些數值運算是 MSN 服務器所規定的,所以你不算也不行。

在取得登入動作之前,還有兩個預設的動作,一個是 XFR,另一個是 GCF,所以我們個別處理。

<?php
   $id = 1;
   $fp = fsockopen('messenger.hotmail.com', 1863, $errno, $errstr, 5);
   stream_set_timeout($fp, 2);
   if (!$fp) {
       echo "Can't connect to messenger.hotmail.com:1863, error => $errno, $errstr";
       exit;
   }
   fwrite($fp, "VER $id MSNP15 CVR0\r\n");
   while (!feof($fp)) {
       $data = fgets($fp, 4096);
       $id++;
       $code = substr($data, 0, 3);
       switch ($code) {
           case 'VER':
               fwrite($fp, "CVR $id 0x0409 winnt 5.1 i386 MSMSGS 8.1.0178 msmsgs [email protected]\r\n");
           break;
           case 'CVR':
               fwrite($fp, "USR $id SSO I [email protected]\r\n");
           break;
           case 'USR':
               // 這裡是做登入檢查的地方,現在暫時不做,讓他直接離開。
               fwrite($fp, "OUT\r\n");
           break;
            case 'XFR':
                @list(/* XFR */, /* id */, /* NS */, $server, /* ... */) = @explode(' ', $data);
                @list($ip, $port) = @explode(':', $server);
                fclose($fp);
                $fp = @fsockopen($ip, $port, $errno, $errstr, 5);
                if (!$fp) {
                    return false;
                }
                stream_set_timeout($fp, 2);
                fwrite($fp, "VER $id MSNP15 CVR0\r\n");
                break;
            break;
            case 'GCF':
                // 這裡會取得非常大量的資料,暫時不做處理,直接讓他離開。
                fwrite($fp, "OUT\r\n");
            break;
           default:
               fwrite($fp, "OUT\r\n");
       }
   }
   fclose($fp);

這樣,就完成了"一次"與服務器完整的連線動作。我這邊提供完整記錄的 log 檔案給大家參考。

VER 1 MSNP15
CVR 2 1.0.0000 1.0.0000 1.0.0000 http://msgr.dlservice.microsoft.com http://download.live.com/?sku=messenger
XFR 3 NS 64.4.34.156:1863 U D
VER 4 MSNP15
CVR 5 1.0.0000 1.0.0000 1.0.0000 http://msgr.dlservice.microsoft.com http://download.live.com/?sku=messenger
GCF 0 7019
<Policies>
<Policy type="ABCH">
<policy>
<set id="push" service="ABCH" priority="200">
      <r id="pushstorage" threshold="180000" />
    </set>
<set id="delaysup" service="ABCH" priority="150">
  <r id="whatsnew" threshold="900000" />
  <r id="whatsnew_storage_ABCH_delay" timer="900000" />
</set>
</policy>
</Policy>
<Policy type="SHIELDS">
<config>
  <shield>
    <cli maj="7" min="0" minbld="0" maxbld="9999" deny="" />
  </shield>
  <block>
    <hashes>
    </hashes>
    <regexp>
      <imtext value="cGhvdG8yMzRcLnppcA==" />
      <imtext value="aW1nMDIxXC56aXA=" />
      <imtext value="dGFueWFiYWJlXC56aXA=" />
      <imtext value="c3R1ZmZcLnppcA==" />
      <imtext value="Zm90b3NcLnppcA==" />
      <imtext value="dHVmb3Rv" />
      <imtext value="Z2V0LW1lc3Nlbmdlcg==" />
      <imtext value="Mm5udmM3" />
      <imtext value="YmxvY2tpbnJpbw==" />
      <imtext value="bWVzc2FnaW5nLW5hbWVz" />
      <imtext value="cGljdHVyYTAwMg==" />
      <imtext value="bWVzc2VuZ2VyLXNjYW4=" />
      <imtext value="c3VtbWVyMjAwOA==" />
      <imtext value="bWVzc2VuZ2VyZGVsZXRlY2hlY2tlcg==" />
      <imtext value="cGhvdG9hbGJ1bTIwMDc=" />
      <imtext value="aW1hZ2UwMjlcLnppcA==" />
      <imtext value="Zm90b183ODFcLnppcA==" />
      <imtext value="Y2hpcnN0bWFzLTIwMDdcLnppcA==" />
      <imtext value="cGhvdG8yMDA3LTEyXC56aXA=" />
      <imtext value="aW1nNS0yMDA3XC56aXA=" />
      <imtext value="bXlwaWN0dXJlcy0wMTA4XC56aXA=" />
      <imtext value="d3d3XC5la2FzdGFtb251XC5pbmZv" />
      <imtext value="d3d3XC5tc24tZnJpZW5kXC5jb20=" />
      <imtext value="d3d3XC5zb250YXJpaFwuaW5mbw==" />
      <imtext value="cGhvdG9zMS0yMDA4XC56aXA=" />
      <imtext value="aGFwcHkyMDA4XC5leGU=" />
      <imtext value="aGFwcHlfMjAwOFwuZXhl" />
      <imtext value="aGFwcHktMjAwOFwuZXhl" />
      <imtext value="bmV3X3llYXJzX2xldHRlcl9mbGFzaFwuZXhl" />
      <imtext value="d3d3XC5tc25saXN0c3RhdHVzXC5jb20=" />
      <imtext value="d3d3XC5nb2xkd2luZG9zMjAwMFwuY29t" />
      <imtext value="d3d3XC5tc253ZWJpbWFnZXNcLmNvbQ==" />
      <imtext value="d3d3XC5tYWlubXNuXC5jb20=" />
      <imtext value="d3d3XC5idXJhc2lzZW5pbnllcmluXC5pbmZv" />
      <imtext value="bWFpbm1zblwuY29t" />
      <imtext value="d3d3XC5lbXJldFwuaW5mbw==" />
      <imtext value="d3d3XC5tc25zcHlcLmV1" />
      <imtext value="bWFpbm1zblwubmV0" />
      <imtext value="aW1hZ2VzXC5pZG9ob3N0XC5jb20=" />
      <imtext value="Zm1jb25zdWx0aW5n" />
      <imtext value="bXNuZ2FsbGVyeVwubXNcLmZ1bnBpY1wuZGU=" />
      <imtext value="ZS1hZnlvbmthcmFoaXNhclwuaW5mbw==" />
      <imtext value="YWNpc2FsYXZhbnNcLmluZm8=" />
      <imtext value="YWNpbGFzdGlyXC5pbmZv" />
      <imtext value="YW1hem9uaGFsa2lcLmluZm8=" />
      <imtext value="ZWthcnNcLmluZm8=" />
      <imtext value="YmlyZXljaVwuaW5mbw==" />
      <imtext value="cmV1dHlcLmluZm8=" />
      <imtext value="cG9ydGFrYWxsaWRhdmV0XC5pbmZv" />
      <imtext value="YW1hem9uZGFrYXlib2xkdW1cLmluZm8=" />
      <imtext value="ZGFuc2FkaW1pXC5pbmZv" />
      <imtext value="dXlzYWxsaWtcLmluZm8=" />
      <imtext value="YmV6Z2lcLmluZm8=" />
      <imtext value="c3VsYW5kaXJtYQ==" />
      <imtext value="dHVuYWJhbGlnaQ==" />
      <imtext value="eW9ydW5nZXNlbA==" />
      <imtext value="dHVoYWZraW1zZQ==" />
      <imtext value="YWNpc2FsY2FwXC5pbmZv" />
      <imtext value="Ym95YW1hZ3VjdQ==" />
      <imtext value="dHV0dXNrYW5saWs=" />
      <imtext value="YWNpc2FsY2Fw" />
      <imtext value="bXNucHJvZmlsZXNcLm1zXC5mdW5waWNcLmRl" />
      <imtext value="aW1hZ2VzXC5nZXRlbmpveW1lbnRcLm5ldA==" />
      <imtext value="bXltc25nYWxsZXJ5" />
      <imtext value="ZnVucGljXC5kZQ==" />
      <imtext value="aW1hZ2UwMzFcLnppcA==" />
      <imtext value="Zm90bzcyMmE2" />
      <imtext value="cGhvdG8yMVwuemlw" />
      <imtext value="cGljdHVyZTIyXC56aXA=" />
      <imtext value="cGljdHVyZXo5MjNcLnppcA==" />
      <imtext value="cGhvdG9fNjg4LWpwZ1wuemlw" />
      <imtext value="bXlwaG90bzk0XC56aXA=" />
      <imtext value="dmlkZW9cLmV4ZQ==" />
      <imtext value="a3V6ZW5cLmV4ZQ==" />
      <imtext value="bWlyY1wuZXhl" />
      <imtext value="YmFja2Rvb3I6aXJjL2Zsb29kXC5jYw==" />
      <imtext value="Zm90b18wMjdcLnppcA==" />
      <imtext value="Zm90bzcwOGs2XC56aXA=" />
      <imtext value="bXNuYmxvY2tsaXN0XC5jb20=" />
      <imtext value="bXNuLWZyaWVuZFwuY29t" />
      <imtext value="cXVpZW50ZWFkbWl0ZVwuY29t" />
      <imtext value="Y2hlY2ttZXNzZW5nZXJcLm5ldA==" />
      <imtext value="bXNuYmxvY2tlcmxpc3RcLmNvbQ==" />
      <imtext value="bXNubGlzdHN0YXR1c1wuY29t" />
      <imtext value="ZnJpZW5kbHktb2ZmZXJcLmNvbQ==" />
      <imtext value="c2plZ2F0XC5waWNzXC5za2FxXC5pbmZv" />
      <imtext value="c2V0dXBcLmV4ZQ==" />
      <imtext value="aG9zdGlsZWFwcGxldA==" />
      <imtext value="Z3NkYWdkZmdhc2RnczI0NTc0NTIyNDdcLmV4ZQ==" />
      <imtext value="Z3NkZGFzMjQ1ODcyMTRnc2RcLmV4ZQ==" />
      <imtext value="Zm90by0zMTFfanBlZw==" />
      <imtext value="aW1hZ2UyMDZcLmpwZy13d3dcLnBob3Rvc2hhcmVcWzFdXC5jb20=" />
      <imtext value="aW1hZ2UyMDZcLmpwZw==" />
      <imtext value="YnVzaFwuY29t" />
      <imtext value="Y2FtZGFcLmV4ZQ==" />
      <imtext value="eW91dHViZV9naXJpc19wcm9ncmFtaVwuZXhl" />
      <imtext value="Y2gzY2szclwuaW5mbw==" />
      <imtext value="cjU3OWRrYTkyalwuemlw" />
      <imtext value="MjAzXC4xNTVcLjc0XC45MS9lbGliL2Jsb2cvYmxvZ1wuaHRtbA==" />
      <imtext value="aWRcLmI0bmdcLmluZm8=" />
      <imtext value="bW9uY2xvY2hlclwuY29t" />
      <imtext value="aW1hZ2Vzd2l0Y2hcLmluZm8=" />
      <imtext value="c3BsaW50ZXJcLmhvc3RpbWdzXC5pbmZv" />
      <imtext value="dWx0aW1hdGUtc3R1ZmZcLmluZm8=" />
      <imtext value="bmV3ZXN0cGljdHVyZTAzXC5qcGc=" />
      <imtext value="d3d3XC5mcmVlcGxheWxpc3RcLmluZm8=" />
      <imtext value="bm90aWNpYXNkb2JyYXNpbFwuY29tXC5zYXBvXC5wdC9ub3RpY2lhdXJnZW50ZWJyYXNpbG51bWVybzk4MjFcLmNvbQ==" />
      <imtext value="d3d3XC5ob3JueW1hdGNoZXNcLmNvbQ==" />
      <imtext value="d2luZG93c215X3Bob3Rvc18xNTMwMVwuemlw" />
      <imtext value="cGhvdG9zXzE1MzAxXC56aXA=" />
      <imtext value="d2ViY2FtZXJhNHlvdVwubmV0" />
      <imtext value="dXN1YXJpb3NcLmx5Y29zXC5lcy9saXl0cmU=" />
      <imtext value="dGhld29tYW5pemVyXC5uZXQ=" />
      <imtext value="aW1wbGF5XC5jb20=" />
      <imtext value="aW1wbGF5XC5vcmc=" />
      <imtext value="Ym9ieXVwXC5jb20=" />
      <imtext value="YWlydW5pcXVlXC5ib2J5dXBcLmNvbQ==" />
      <imtext value="Ym9iYmxha1wuY29t" />
      <imtext value="cGljcy1hdC10aGUtcGFydHlcLmNvbQ==" />
      <imtext value="Ym9iem9wXC5jb20=" />
      <imtext value="bW9vcnNoXC5jb20=" />
      <imtext value="Ym9ieXVw" />
      <imtext value="d3d3XC5teXNwY1wubmV0" />
      <imtext value="d293YmFtXC5jb20=" />
      <imtext value="Zm90b1wuZXhl" />
      <imtext value="ZHNjMjAwOTAxMTdcLmpwZ1wuZXhl" />
      <imtext value="ZnJpZW5kaW1zXC5jb20=" />
      <imtext value="bm93cG91bmRzXC5jb20=" />
      <imtext value="Y2xpcGRlZXBzXC5jb20=" />
      <imtext value="YmVpbmctc2luZ2xlXC5jb20vaW52aXRhdGlvblwucGhw" />
      <imtext value="aGVhcnRtZWJhZFwuY29tL2RhdGVcLnBocA==" />
      <imtext value="ZGlwaWV0cm8xMFwubm9ucmVjZWl2ZWRtYWlsXC5jb20vY2xpY2tcLnBocA==" />
      <imtext value="bWFrZW1leW91cmxvdmVyXC5jb20vcHVzc3lcLnBocA==" />
    </regexp>
  </block>
</config>
</Policy>
</Policies>
USR 6 SSO S MBI_KEY_OLD n1DPXrGuiTp6hWDiJNCQkLI1weGA2TJl27DlzyApfPhq2TvV0frz0v+tfbpZFHQH

這就是跟 MSN 服務器初步溝通的完整過程,整個連線到這裡是完整的一次連線。連線之後要做甚麼呢?上述從 MSN 服務器拿回來的資料,其實就是跟帳號有關的資訊,不過需要解碼而已。

其實,無論你要做甚麼樣的動作,其實都得透過 Command 來達成,只有某些特定的資料取得,是需要透過 SOAP 來抓取,詳細的資料可以參考這裡:

這些較為特殊的 SOAP 是需要比較特別的方式去處理,不然其他的動作其實都是透過 Command 來完成的。以上,就是 MSN 服務連線的粗淺介紹。

Hina Chen
偏執與強迫症的患者,算不上是無可救藥,只是我已經遇上我的良醫了。
Taipei