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)
最好還是自己架,別人的有時候怪怪的,有時通有時不通,等我架好測試好在跟大家分享了!
Facebook 討論區載入中...