Facebook Chat API

Facebook Chat API

6788fdb0026244e87e8251e39fb4fca5.jpg

今天介紹一下 Facebook Chat API。
這東西搞了我好久,因為 Facebook 的文件是有名的寫得不清不楚又變來變去的。所以最後還用 sniffer 去觀察封包,畫流程圖,最後才把搞定!希望這個文件對開發 Facebook (以下簡稱 FB) 的人有幫助!
先提一下 FB Chat API 的作用好了。現在看到大多數的 FB 應用程式,特別是遊戲。總是喜歡有許多邀請朋友、貼塗鴉牆等行為,但是這些行為都 “不即時”。所以如果您需要做一個 “即時” 的應用程式,也就是可以即時找到正在線上的使用者,並且發訊息給他的話,那你就需要用到 FB Chat API 了。
FB 的 SDK 說明在這邊:https://developers.facebook.com/docs/chat/ 如果你把它看完,而且就可以把程式寫好,那你應該是 XMPP 的高手中的高手了!這樣請跟我聯絡,因為我還有很多雖然作出來,但是不太懂的地方需要請教了!
在這個案子中我 google 了很多文件,但是有用的不多,而且很多都是過時的文件,所以需要去改程式。以下會說明所用到的相關函式。
 
XMPP 及 strophe.js
在開始使用 FB chat api 前不得不先說一下,FB 的聊天功能是走標準的 XMPP,但是 “認證” 的部分則是 FB 修改過的 tag ,叫做 X-FACEBOOK-PLATFORM 。就是這個搞了很久。
網路上找到關於 XMPP 的 javascript 作最好的應該就是 strophe.js 了!不過只提供 XMPP 一般的認證,也就是輸入帳號及密碼來做認證。有興趣的人可以到這裡 (profxmpp) 下載程式,把它傳到你自己的網站上就可以測試喔!
測試的方式就是輸入你在 facebook 的帳號密碼,ex: your-name@chat.facebook.com,記得,網域是 chat.facebook.com,而 @ 前面的名稱就是要看你自己的設定了。以下就是畫面,請自行測試。
 
fac7374c7c67a5923870984c4c95fcb9.png
 
strophe.js plugin for Facebook.
 
現在要進入重點了。我找到曾經有人寫過 strophe.js 的 plugin,可以用在 FB 上面。很高興地抓下來測試,卻發現不能用… 查了很久,google 了很久也沒有答案!所以只好拿出以前研究 protocol 的精神,慢慢研究 code 跟 sniffer 了!
(code tracing + sniffer 中…)
 
叮~~~
 
最後發現,原來 Facebook 改掉了抓取 access token 的參數。所以只要小改就可以了!
在 facebook.js 中大約 Line: 102~109 改成以下:
var responseText = "";
responseText += 'api_key=' + this.apiKey;
responseText += '&call_id=0';
responseText += '&method=' + method;
responseText += '&nonce=' + nonce;
responseText += '&access_token=' + this.sessionKey;
responseText += '&v=' + '1.0';
等這邊改好之後,就是要進入主程式了。以下分享重要的幾個部分,相信熟悉 javascript 的人都可以搞定的!
Initial Strophe.js
 

<script>
FB.init({ 
    appId: '<?php echo $app_id;?>',
    status: true, 
    cookie: true,
    xfbml: true,
    oauth: true});
 
var g_accessToken;
 
FB.getLoginStatus(function(response)
{
    if(response.status=='connected')
    {
        if (response.authResponse) {
            syslog("User is connected to the application.");
            g_accessToken = response.authResponse.accessToken;
          }
        CreateStrophe('<?php echo $app_id;?>', '<?php echo $app_secret;?>');
    }
});
</script>
相關 Function​
<script>
var g_app_id,g_app_secret;
 
function on_roster(iq) {
 
    $(iq).find('item').each(function () {
        var jid = $(this).attr('jid');
        var name = $(this).attr('name') || jid;
         
        $('#contacts').append(new Option(name , jid, true, true));
        $("#contacts option[value='"+jid+"']").attr("disabled","");
    });
     
    syslog('add ' + $('#contacts option').length + ' contacts');
     
    // set up presence handler and send initial presence
    connectionFB.addHandler(on_presence, null, "presence");
    connectionFB.addHandler(on_message, null, "message", "chat");
}
 
function on_message(message) {
    var from = $(message).attr('from');
     
    if($('#contacts option[value='+from+']').length = 0)
        var name = from;
    else
        var name = $('#contacts option[value='+from+']').text();
         
    var body = $(message).find("html > body");
     
     if (body.length === 0) {
        body = $(message).find('body');
        if (body.length > 0) {
            body = body.text()
        } else {
            body = null;
        }
    } else {
        body = body.contents();
    }
      
    if(body!=null)
    {
        syslog( '<font color="blue">' + name + ' says:' + body + '</font>');
    }
     
    return true;
}
 
function on_presence(presence) {
     
    var ptype = $(presence).attr('type');
    var from = $(presence).attr('from');
    if($('#contacts option[value='+from+']').length = 0)
        return;
         
     var name = $('#contacts option[value='+from+']').text();
 
    if (ptype === 'subscribe') {
        syslog('jid: ' + from + ' (' + name + ') has subscribed.');
         
    } else if (ptype !== 'error') {
         
        if (ptype === 'unavailable') {
            // set offline
            syslog('jid: ' + from + ' ( ' + name + ' ) is offline');
        } else {
            var show = $(presence).find("show").text();
            $("#contacts option[value='"+from+"']").attr("disabled","");
            if (show === "" || show === "chat") {
                // set online
                syslog('jid: ' + from + ' ( ' + name + ' ) is online');
            } else {
                // set away
                syslog('jid: ' + from + ' ( ' + name + ' ) is away');
            }
        }
 
    }
 
    return true;
}
 
function syslog(msg)
{
    console.log(msg);
    $('#syslog').append(msg+'<br />');
 
}
 
 
function CreateStrophe(app_id, app_secret)
{
    g_app_id = app_id;
    g_app_secret = app_secret;
    connectionFB = new Strophe.Connection("http://bosh.metajack.im:5280/xmpp-httpbind");
    connectionFB.facebookConnect("your-name@chat.facebook.com", onConnectFacebook, 300, 1, app_id, app_secret, g_accessToken);
}
 
function reconnect()
{
    connectionFB.facebookConnect("your-name@chat.facebook.com", onConnectFacebook, 300, 1, g_app_id, g_app_secret, g_accessToken);
}
 
 
function onConnectFacebook(resp)
{
    // syslog('onConnectFacebook (' + resp + ')');
     
    switch(resp)
    {
        case Strophe.Status.CONNECTED:
            $('#btnSend').show('slow');
             
            var iq = $iq({type: 'get'}).c('query', {xmlns: 'jabber:iq:roster'});
            connectionFB.sendIQ(iq, on_roster);
 
            syslog('You can send the messages now!');
             
            syslog('Retriving contacts...');
        break;
         
        case Strophe.Status.ERROR:
            syslog('onConnect: ERROR!');
        break;
 
        case Strophe.Status.CONNECTING:
            syslog('onConnect: CONNECTING!');
        break;
 
        case Strophe.Status.CONNFAIL:
            syslog('onConnect: CONNFAIL!');
            reconnect();
        break;
 
        case Strophe.Status.AUTHENTICATING:
            syslog('onConnect: AUTHENTICATING!');
        break;
 
        case Strophe.Status.AUTHFAIL:
            syslog('onConnect: AUTHFAIL!');
            reconnect();
        break;
 
        case Strophe.Status.DISCONNECTED:
            syslog('onConnect: DISCONNECTED!');
            reconnect();
        break;
         
        case Strophe.Status.DISCONNECTING:
            syslog('onConnect: DISCONNECTING!');
        break;
         
        case Strophe.Status.ATTACHED:
            syslog('onConnect: ATTACHED!');
        break;
         
    }
}
function sendMsg(jid, text)
{
        var message = $msg({to: jid,
                            "type": "chat"})
            .c('body').t(text).up()
            .c('active', {xmlns: "http://jabber.org/protocol/chatstates"});
 
        connectionFB.send(message);
 
}
</script>
關於 email 如何取得,這部分只要是有開發 FB App 經驗的應該都不是問題,所以就不細說了。而權限部分則記得增加以下幾個權限!
 
user_online_presence, friends_online_presence, xmpp_login, read_friendlists
這邊有一個要注意的事情就是,需要自己架一個 XMPP Server 然後啟用 BOSH 掛在 apache proxy 裡面,這部分我還沒有完成,暫時先用別人的。(http://bosh.metajack.im:5280/xmpp-httpbind)
最好還是自己架,別人的有時候怪怪的,有時通有時不通,等我架好測試好在跟大家分享了!