Shameless Plug: Browse anonymously to protect your browsing habits from data tracking sites with a VPN Service.
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: Turns out Adium utilized my post in adding Facebook chat to their client, cool!
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.
A connection is opened to: http://%5BANYTHING%5D.channel%5BNUM%5D.facebook.com/x/%5BANYTHING%5D/false/p_%5BUID%5D=%5BSEQ%5D. First let’s look at the url and what the different placeholders represent.
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.
// request http://0.channel06.facebook.com/x/0/false/p_MYID=-1
for (;;);{"t":"refresh", "seq":0}
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!
// POST request to: http://www.facebook.com/ajax/chat/send.php?msg_text=hey&msg_id=1234567890&to=FRIENDSID&client_time=1209558664256&post_form_id=297fdbad61d3b1d88f0c311cda25bbbc
for (;;);{"error":0,"errorSummary":"","errorDescription":"No error.","payload":[]}
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.
// POST request to http://www.facebook.com/ajax/presence/update.php?buddy_list=1
for (;;);{"error":0,"errorSummary":"","errorDescription":"No error.","payload":{"buddy_list":{"listChanged":true,"availableCount":1,"nowAvailableList":{"UID1":{"i":false}},"wasAvailableIDs":[],"userInfos":{"UID1":{"name":"Buddy 1","firstName":"Buddy","thumbSrc":"http:\/\/static.ak.fbcdn.net\/pics\/q_default.gif","status":null,"statusTime":0,"statusTimeRel":""},"UID2":{"name":"Buddi 2","firstName":"Buddi","thumbSrc":"http:\/\/static.ak.fbcdn.net\/pics\/q_default.gif","status":null,"statusTime":0,"statusTimeRel":""}},"forcedRender":true},"time":1209560380000}}
Notifications
Notifications are polled through the same mechanism as the buddy list. You need to pass ¬ifications=1 to update.php to receive info about them. Other than that I won’t be covering them here.
The Fun Part
So here’s a very simple implementation of a Facebook Chat client written in Ruby. It will require you have the json and mechanize gems installed.
require 'rubygems'
require 'mechanize'
require 'json'
require 'ostruct'
require 'pp'
class FacebookChat
def initialize(email, pass); @email, @pass = email, pass; end
def login
@agent = WWW::Mechanize.new
@agent.user_agent_alias = 'Windows IE 7'
f = @agent.get("http://facebook.com/login.php").forms.first
f.fields.name("email").value = @email
f.fields.name("pass").value = @pass
f.submit
body = @agent.get("http://www.facebook.com/home.php").body
# parse info out of facebook home page
@uid = %r{<a href=".+?/profile.php\?id=(\d+)" class="profile_nav_link">Profile</a>}.match(body)[1].to_i
@channel = %r{"channel(\d+)"}.match(body)[1]
@post_form_id = %r{<input type="hidden" id="post_form_id" name="post_form_id" value="([^"]+)}.match(body)[1]
end
def wait_for_messages
determine_initial_seq_number unless @seq
begin
json = parse_json @agent.get(get_message_url(@seq)).body
end while json["t"] == "continue" # no messages yet, keep waiting
@seq += 1
json["ms"].select{|m| m['type'] == 'msg'}.map do |msg|
info = msg.delete 'msg'
msg['text'] = info['text']
msg['time'] = Time.at(info['time']/1000)
OpenStruct.new msg
end.reject {|msg| msg.from == @uid } # get rid of messages from us
end
def send_message(uid, text)
r = @agent.post "http://www.facebook.com/ajax/chat/send.php",
'msg_text' => text,
'msg_id' => rand(999999999),
'client_time' => (Time.now.to_f*1000).to_i,
'to' => uid,
'post_form_id' => @post_form_id
end
def buddy_list
json = parse_json(@agent.post("http://www.facebook.com/ajax/presence/update.php",
'buddy_list' => 1, 'force_render' => 1, 'post_form_id' => @post_form_id, 'user' => @uid).body)
json['payload']['buddy_list']['userInfos'].inject({}) do |hash, (uid, info)|
hash.merge uid => info['name']
end
end
private
def determine_initial_seq_number
# -1 will always be a bad seq number so fb will tell us what the correct one is
json = parse_json @agent.get(get_message_url(-1)).body
@seq = json["seq"].to_i
end
def get_message_url(seq)
"http://0.channel#{@channel}.facebook.com/x/0/false/p_#{@uid}=#{seq}"
end
# get rid of initial js junk, like 'for(;;);'
def parse_json(s)
JSON.parse s.sub(/^[^{]+/, '')
end
end
if __FILE__ == $0
fb = FacebookChat.new(ARGV.shift, ARGV.shift)
fb.login
puts "Buddy List:"
pp fb.buddy_list
Thread.abort_on_exception = true
Thread.new do
puts usage = "Enter message as <facebook_id> <message> (eg: 124423 hey man wassup?) or type 'buddy' for buddy list"
loop do
case gets.strip
when 'buddy' then pp fb.buddy_list
when /^(\d+) (.+)$/
uid, text = $1.to_i, $2
fb.send_message(uid, text)
else
puts usage
end
end
end
# message receiving loop
loop do
fb.wait_for_messages.each do |msg|
puts "[#{msg.time.strftime('%H:%M')}] #{msg.from_name} (#{msg.from}): #{msg.text}"
end
end
end
Run with: ruby chat.rb [email] [password]
[...] facebook, network, ruby — Tags: facebook, network, ruby — coderrr @ 2:39 pm moved here Possibly related posts: (automatically generated)Not in the face!Facebook App [...]
Pingback by Facebook Chat Protocol « coderrr — May 6, 2008 @ 7:38 pm
Nice entry man, I was planning to write a chat client to facebook, i will use your explanation, thanks!
Comment by hammady — May 8, 2008 @ 11:36 am
Thanks for posting this! I’ve added support to Adium for Facebook chat using the information you provided here :)
Comment by Evan Schoenberg — May 9, 2008 @ 4:16 am
Waiting for bitlbee to support fb
Comment by torspo — May 14, 2008 @ 6:49 pm
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:
epernice@epernice-laptop:~$ ruby chat.rb myemail mypassword
Buddy List:
/usr/lib/ruby/1.8/mechanize.rb:642:in `html_unescape’: private method `gsub’ called for :post_form_id:Symbol (NoMethodError)
from /usr/lib/ruby/1.8/mechanize/form_elements.rb:13:in `initialize’
from /usr/lib/ruby/1.8/mechanize.rb:231:in `new’
from /usr/lib/ruby/1.8/mechanize.rb:231:in `post’
from /usr/lib/ruby/1.8/mechanize.rb:230:in `each’
from /usr/lib/ruby/1.8/mechanize.rb:230:in `post’
from chat.rb:51:in `buddy_list’
from chat.rb:81
Any one have an idea??
Comment by Eric — May 16, 2008 @ 7:44 pm
The problem is I was using :symbols instead of ‘strings’ as the keys for the posted form data.
Not sure why you get this error and others don’t. It may have to do with you having an older version of mechanize. Either way I’ve fixed the problem and reposted the code.
Give it a try now.
Comment by coderrr — May 16, 2008 @ 11:25 pm
Is it possible to write the same app using java or c#?
Comment by tfr — June 9, 2008 @ 9:27 am
[...] messages and a simple POST for sending them. He whipped up a quick proof-of-concept in Ruby and documented the findings on his [...]
Pingback by Andrew Hite » Blog Archive » The “Hidden” Facebook Chat API — June 12, 2008 @ 12:08 am
What do you do when you send a message containing the ‘&’ character? This messes up my POST request to http://www.facebook.com/ajax/chat/send.php and I can’t think of a way around it.
Comment by Ryan — June 13, 2008 @ 7:23 pm
Nevermind, found a solution. Replace all ‘&’ with ‘%20′ :)
Silly me
Comment by Ryan — June 13, 2008 @ 7:42 pm
Ryan:
%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.
Comment by coderrr — June 13, 2008 @ 7:54 pm
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! :)
Comment by Ryan — June 14, 2008 @ 12:37 am
[...] this opportunity as well to say I would never have been able to get this far if it wasn’t for this blog post by coderrr. I will keep this blog updated as I make more [...]
Pingback by MeltingIce Design » Blog Archive » Coming Soon: Facebook Chat for iPhone — June 14, 2008 @ 3:46 pm
How about a Miranda-IM plug-in fot this?
Nice article, now to find more APIs
Comment by Daniel — June 16, 2008 @ 5:11 pm
great work on this, i’m finishing up a python implementation as i type this. However there is one thing i don’t get, why do there json responses have a preceeding for loop?
Comment by Jonathan — June 18, 2008 @ 10:17 pm
Jonathan:
I’m really not sure, but if I had to guess, it would be because some browsers will give an error or return nothing when eval()ing a lone javascript object. The empty for loop gets around this.
That’s just a guess.
Comment by coderrr — June 19, 2008 @ 2:31 am
Just thought I would keep you updated, Facebook recently changed a little something. When retrieving a buddy list, you also need force_render=true in the POST request.
Comment by Ryan — June 21, 2008 @ 4:06 am
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_iOf 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. :)
Comment by Simon — July 23, 2008 @ 12:39 am
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.
Comment by Walter — August 1, 2008 @ 4:36 pm
hi, thank you very much for publishing this snippet!
With this code you always seem to be idle. Has anybody found out, what request to send, so that face recognize’s you as online?
greets
Comment by chicosabroso — August 10, 2008 @ 10:47 am
hei, i figured out a way by myself to be online all the time.
just send a message to yourself.
greetz
Comment by chicosabroso — August 10, 2008 @ 6:37 pm
jam
kjgb
Comment by Anonymous — May 6, 2009 @ 8:26 am
It’s needful..
Comment by iaqrimon — September 4, 2008 @ 6:11 pm
Facebook Chat API in Python…
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…
Trackback by ConstantThought — September 18, 2008 @ 3:48 am
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
Comment by ralph — September 29, 2008 @ 1:35 am
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"}]}
Any help would be greatly appreciated.
Thanks in advance.
Comment by marlon — October 9, 2008 @ 12:56 am
Never mind, it was my fault, I was sending bad parameters to send.php
Comment by marlon — October 9, 2008 @ 9:15 am
Were you able to write it in java?
I am trying to do the same.
But it doesnt show me online in my friend’s buddy list.
Any workaround?
Please help.
P.S: I am sending a message to myself too.
But still its not working :( :(
Comment by ms — September 4, 2009 @ 3:15 am
Thanks alot for this post, helped me alot and I have already rewritten it to Java.
by the way the pattern “channel(\d+)” is not anymore found in the home page, facebook have removed it from there, I think
Comment by Michael Youssef — October 29, 2008 @ 9:39 am
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
Comment by sek700i — October 29, 2008 @ 8:44 pm
@sek700i
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.
Comment by coderrr — October 29, 2008 @ 8:49 pm
facebook is a very safe site 4 smart peaple u should not give ur dtails just 2 anybody
Comment by bulelwa — November 17, 2008 @ 11:59 am
[...] Chat API (Unofficial) : Facebook doesn’t have an API for its chat but Coderrr created a hack using the existing Facebook API. The resource is now available but doesn’t [...]
Pingback by List of APIs for your Social Networks — February 18, 2009 @ 4:32 pm
[...] Chat API (Unofficial) : Facebook doesn’t have an API for its chat but Coderrr created a hack using the existing Facebook API. The resource is now available but doesn’t [...]
Pingback by List of APIs for chat — April 9, 2009 @ 1:32 pm
[...] [...]
Pingback by CT-City Yeni Projemiz - Webmaster Forumu - Webmaster Zone — May 24, 2009 @ 2:34 am
Maybe login parsing could be easier/faster when parsing the mobile page:
http://m.facebook.com/login.php?http
The uid and post_form_id can get extracted from that page too.
~Chris
Comment by cimnine — July 10, 2009 @ 12:04 pm
[...] [...]
Pingback by FriendsFlow (facebook client) - Page 4 - PreCentral Forums — August 20, 2009 @ 12:16 pm
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:
self.uid= int(re.match(“”".+Presence\(“(\d+)”"”, body, re.S).group(1))
print “ID=”+`self.uid`
self.channel= re.match(“”".+channel(\d+)”"”, body, re.S).group(1)
print “Channel=”+self.channel
self.post_form_id = re.match(“”".+<input type="hidden" id="post_form_id" name="post_form_id" value="([^"]+).+""", body, re.S).group(1)
print "FormID="+self.post_form_id
Comment by Andreas — October 9, 2009 @ 1:22 pm
Hi, my name abidin
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 use ruby for Linux
ruby 1.8.7 (2008-08-11 patchlevel 72) [i486-linux]
(Ubuntu)
Comment by abidin — October 20, 2009 @ 7:41 am
[...] Chat API (Unofficial) : Facebook doesn’t have an API for its chat but Coderrr created a hack using the existing Facebook API. The resource is now available but doesn’t [...]
Pingback by Raj Anand » List of APIs for your Social Networks — November 16, 2009 @ 7:28 pm
Nice implementation, this will give me background to build my own web based chat using facebook, hopefully as a plugin for my chat script
Comment by jason — December 5, 2009 @ 2:08 pm
I need the script for facebook or yahoo chat integration
Comment by Md Mazaharul — January 13, 2010 @ 2:40 pm
Hi, can you comment the part of code of buddy list function
I’m a Perl programmer, and I not know ruby…
Thanks for paper.
Comment by imerge — February 7, 2010 @ 9:07 pm
[...] facebook discuss api [...]
Pingback by BitComet Ultra Accelerator - Free Software, Shareware Downloads and More — February 12, 2010 @ 4:28 pm
[...] Probabilmente lo studio del protocollo lo avrei tenuto per me visto che in giro si trova buona documentazione, se non fosse per il fatto che ho notato qualcosa di interessante nell’uso del typing. Non si [...]
Pingback by Typing e la Privacy di Facebook Chat – Gianni Amato — May 12, 2010 @ 12:56 am
Hello There,
Such a nice work you have done! Really appreciate!!
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.
Thanks.
Comment by Hitesh — June 15, 2010 @ 1:08 pm
You probably want to mention that this is superseded by the XMPP chat functionality they have now exposed, check it out:
developers.facebook.com/docs/chat
It’s an official “chat api”, properly supported and not hacked out or subject to breaking changes.
Comment by David — September 16, 2010 @ 2:10 pm
http://0.100.channel.facebook.com/x/3033635267/4230476693/false/p_100000583792573=16
i get a lot of this errors, its been a month now and this is the only thing i see, i dont know how to fix it, even after reading this page. does anyone know how to do it? step by step?
eric
Comment by ericlove797 — November 6, 2010 @ 8:47 am
The site and this article will be always suggested by me as one of the most impressivepoint of this topic.
Comment by wzór zaświadczenia o ukończeniu szkolenia bhp — December 17, 2010 @ 11:31 am
I just know that facebook provide it
Comment by manga indonesia — March 12, 2011 @ 3:45 am
i have create a facebook chat application for sale if anyone want this application contact me on my email – personalid@live.in or skype – sunny.gandass
Comment by bharat — March 28, 2011 @ 4:21 am
Hi
Can u make it like…..
an ajax simple chat using facebook connect?! and list of online users by aside? that would be so cool
Comment by omer — June 1, 2011 @ 12:13 pm
i cant get into my facebook account at all????????????????????????????????????????????mar
Comment by marion walker — March 23, 2012 @ 6:05 pm
cant get my facebook
Comment by marion walker — March 23, 2012 @ 6:05 pm
Reblogged this on fozg.
Comment by fozg — August 10, 2012 @ 9:26 pm
Howdy! I’m at work browsing your blog from my new iphone! Just wanted to say I love reading your blog and look forward to all your posts! Carry on the superb work!
Comment by fitness Magazine — November 25, 2012 @ 2:56 am
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!
Comment by Anonymous — January 15, 2013 @ 4:28 pm