Facebook Chat API

Update: It’s been quite a while since I wrote this post and I don’t think the code below even works anymore because I haven’t kept up with Facebook’s API changes. So I’ve put the code up on github so anyone can fork it and make fixes if they like.

Update: Simon just mentioned that when using facebook’s new profile layouts this code will break. You can find the fix in his comment. Also note, as he says, you only need this fix if you’ve changed to facebook’s new profile layouts.

Update: Ryan just notified me that facebook has made a change and you must now pass force_render=1 to presence/update.php to retrieve the buddy list. I’ve updated the example client below.

Update: Facebook recently made a breaking change to their API to require post_form_id to be passed to presence/update.php. I’m assuming they did this to prevent CSRF attacks against it. I’ve updated the client code below to reflect these changes.

Facebook Chat was released recently and I was interested in looking under the hood at how its protocol works. It turned out to be simpler than I expected…

Receiving Messages

Facebook uses a Comet and JSON approach to receive incoming messages. Comet is a technique where you open a long running HTTP connection to a server (usually AJAX). The connection stays open until the server has something to tell you at which point it sends the data through the connection and the connection can be closed (or in some cases remain open). JSON is just a way of representing data in Javascript. Let’s look at how it works.

ANYTHING can be anything, Facebook has setup *.channel[NUM].facebook.com as a catch-all subdomain. So no matter what you put there it will resolve to the same IP. The ANYTHING in the path can also be anything. I assume both the ANYTHING values are used to prevent the browser from caching these requests.

NUM represents the channel number you are using. It appears there are 20 channels (01-20), as that range of subdomains resolve and the rest don’t. I am assuming that a “channel” is just one of Facebook’s comet interface servers. Every time you log in you are assigned to a specific channel and it doesn’t change throughout your session. It may also be fixed per user, I’m not sure about this though.

UID is just your user id. SEQ is the sequence number you are requesting from the server. If you give an out of range seq number the server will tell you what the latest seq number is so that you can request it. If you give an old seq number the server will show you the (old) messages that correspond with that number. Requesting http://0.channel06.facebook.com/x/0/false/p_MYID=-1 will immediately respond telling me I should re-request with a seq number of 0 (or whatever my current seq number is) since the seq number -1 is never valid.

After we build the correct URL and make the connection, it will just “sit there”. This is how Comet connections work. The connection will remain inactive in a “requesting” state until someone sends us a message. If a message is not received in less than a minute (approx 55 seconds) Facebook will close the connection and instruct you to reopen it. I assume they do this to get around browser timeout issues. If a message is received you will get the message data through the connection which is then closed. After a message is received the seq number should be incremented for the next request. After either a message is received or the connection times out a new connection will immediately be established.

// request http://0.channel06.facebook.com/x/0/false/p_MYID=0 times out
for (;;);{"t":"continue"}
// request http://0.channel06.facebook.com/x/0/false/p_MYID=0 receives a message!
for (;;);{"t":"msg","c":"p_MYID","ms":[{"type":"msg","msg":{"text":"yo","time":1209557234412,"clientTime":1209557232415,"msgID":"4177168544"},"from":FRIENDSID,"to":MYID,"from_name":"Myfriends Name","to_name":"My name","from_first_name":"Myfriends","to_first_name":"My"}]}
// request http://0.channel06.facebook.com/x/0/false/p_MYID=1 for the next message

Sending Messages

Sending messages is a lot simpler. All you need to do is make a POST request to http://www.facebook.com/ajax/chat/send.php with a few simple values: msg_text with the text of the message, to with the uid of the recipient, msg_id with a random unique number id for the message, client_time with the current time in microseconds, and post_form_id which can be found as a hidden form value on any facebook page. Facebook will respond with some JSON indicating there was no error and the message will be sent!

Buddy List
The list of your friends who are online is polled about every 3 minutes with a POST request to http://www.facebook.com/ajax/presence/update.php. The ‘userInfos’ hash contains the actual buddy list. Some extra info is provided to let you know how the buddy list has changed since the last request.

This is great, thanks! Unfortunately, I haven’t been able to get it to run… I’m very new to ruby (this is the first I have seen it) so that may have something to do with it. I run it as directed but get the following error:

%20 (decimal value 32) is the URI escape sequence for a space (‘ ‘) not an ampersand. %26 is the correct escape. You shouldn’t be doing this manually anyway. Whatever language you’re using should have a CGI escaping function which performs all necessary escaping for you.

Good point, thanks for reminding me. The other issue I was having was with opening a comet session with Facebook. I found a jQuery plugin to help me out, but I’m not sure how the actual process works. I think you usually connect to a comet server, and then subscribe to a channel, but I’m not sure how that would work in this scenario. Unfortunately my Ruby knowledge is somewhat limited, and I am working on a PHP project, so if you have any tips, they would be GREATLY appreciated. Thanks! :)

Hey, this is great stuff! Found my way here because Adium broke today, due to the new facebook profile layouts. I’ve been poking and got your code to work with the new profiles. (NOTE: this is only if you have changed your profile to the new layout on the facebook webpage.) Changes:

Change all the urls from http://www.facebook.com/* to http://www.new.facebook.com/* EXCEPT for the login page (because on the new login page, the username/password form is no longer the first one on the page and I don’t know ruby well enough to get around that one)

Then change the line the parses the body to get the UID to: @uid = %r{<div id="fb_menu_profile" class="fb_menu"><div class="fb_menu_title"><a href=".+?/profile.php\?id=(\d+)">}.match(body)[1].to_i

Of course, then the program ONLY works for the new facebook. You could probably do a check to see where login resolves to and move forward from there, but that is more than I wanted to do. :)

I wish this was a php script. I have no knowledge of Ruby, but even so I would not be sure it would be usable on my web site. A php script might be better. Wish I could interface my web site to face book though the chat and other applications to allow two way movement between my Facebook page and my web site in a seemless manner. As well as having a chat module for phpnuke.

This is a minimal Python interface to Facebook’s chat feature. You can send and receive messages, get the list of online friends, etc. It would certainly be a good place to start building a Facebook Chat transport for Jabber/XMPP, especially if coup…

Nice Work, thanks a lot ! I am working on an android interface but the libfacebookchat released in C by eion robb (http://code.google.com/p/pidgin-facebookchat/) was not clear enough for the “communication” part. (I’m also wondering if he also used your task…)
you’re the best ,)
cheers

I’m trying to write a Java client using apache http library, but it seems almost impossible to do it without an embedded javascript-aware browser.

That’s because of the presence cookie, which is a json object and is created by javascript, and not set by the server.

Using only a httpclient, when I send a message I get back:
{“error”:1356004,”errorSummary”:”Unexpected error”,”errorDescription”:”An unexpected error occurred. Please try again.”,”errorIsWarning”:false,”payload”:null,”bootload”:[{“name”:”js\/common.js.pkg.php”,”type”:”js”,”src”:”http:\/\/static.ak.fbcdn.net\/rsrc.php\/pkg\/102\/125690\/js\/common.js.pkg.php”}]}

Hey, I am not expert in this area, I am more familiar with c++. Just wanted to know if there is any way in open chat session to obtain the other users IP adress. Could you send me answer on my mail. thx

There should be no way to get another users IP with chat since facebook is effectively acting as a proxy for everyone to communicate with each other. Which means facebook is the only one who knows everyone’s IP.

For what it’s worth, here are the patterns which seem to work okay right now. The ones in the code above did not work for me. These are for the python version of the script, but I bet you’re all able to copy them into the Ruby code:

I just write your code without any changes.
But when run it,
———————————
nokideen@sya-mlink-kpsg:~/workspace/rbfbookchat$ ruby facebook_chat.rb nokideen@nfors.com asdf1234
facebook_chat.rb:15:in `login': undefined method `fields’ for nil:NilClass (NoMethodError)
from facebook_chat.rb:82

I would like to ask a few questions. Facebook chat api is open for facebook applications only??

In your post you have not provided any application key or secret key or ID.

This does’t looks possible as facebook have a special permission parameter xmpp_presense , that comes under Extended permission for facebook applications. I am not sure if you can fetch the online user information and presence like this.

Also, a request, If it is possible. Can you please write a post regarding Facebook CHAT api to get the presence of online friends.

Facebook documentation is not good at this point.

If you can just let me know the parameters I need to pass to get a friends presence on chat through xmpp. That would really help me, I am developing an application, but I dont work on phython, I am a php guy.

You are so awesome! I do not suppose I have read through something like this before. So good to discover somebody with a few original thoughts on this subject. Seriously.. many thanks for starting this up. This site is one thing that is required on the internet, someone with some originality!