今天介紹一下 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,而 @ 前面的名稱就是要看你自己的設定了。以下就是畫面,請自行測試。

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



