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