Yesterday Facebook added the “Live Feed”, which is like the existing News Feed, but automatically updates the page without requiring the user to refresh. It simply polls Facebook’s servers every few seconds, rather than the slightly fancier “comet”-style long-polling they do for Facebook chat:
Polling is perfectly sufficient for this sort of updating (it’s not particularly latency sensitive), but none of this is relevant to the rest of this post anyhow.
As neat as it is, I’m not going to sit around all day watching Facebook, waiting for updates. I want to be notified of updates unobtrusively, at which point I can decide if I want to ignore them or check it out. Growl is perfect for this.
Many OS X apps use Growl to display notifications to the user. They use either Distributed Objects or a UDP protocol called GrowlTalk to post the notifications, neither of which is suitable for a client-side web app. Google Gears promises to provide NotificationAPI at some point, but it’s not currently ready. Fluid also has a notification API, but we need Firefox’s Greasemonkey plugin to inject some JavaScript.
Brian Dunnington wrote a version of Growl for Windows, which adds one neat feature: an HTTP interface. This lets the browser (or anything else the speaks HTTP) post notifications directly. Unfortunately the OS X version of Growl doesn’t have this interface built in, but it’s nearly trivial to create a bridge to the GrowlTalk protocol in Python:
from BaseHTTPServer
import BaseHTTPRequestHandler, HTTPServer
from socket
import AF_INET, SOCK_DGRAM, socket
from urlparse
import urlparse
from cgi
import parse_qs
import simplejson
import netgrowl
class GrowlBridgeHandler(BaseHTTPRequestHandler):
def do_GET(self):
try:
# parse url
u = urlparse(self.path)
if u.path == "/":
# parse query string
q = parse_qs(u.query)
print q
# parse json payload
j = simplejson.loads(q[‘d’][0])
print j
# create and send the notification
p = netgrowl.GrowlNotificationPacket(
description=(j.has_key(‘description’) and j[‘description’] or "Description"),
title=(j.has_key(‘title’) and j[‘title’] or "Title"),
priority=(j.has_key(‘priority’) and j[‘priority’] or 0),
sticky=True) #(j.has_key(‘sticky’) and j[‘sticky’] or False))
growlserver.sendto(p.payload(), addr)
# send a 200 http response
self.send_response(200)
self.send_header(‘Content-type’, ‘text/html’)
self.end_headers()
return
return
except IOError:
self.send_error(404, ‘File Not Found: %s’ % self.path)
def main():
try:
# prepare the growl socket
global addr, growlserver
addr = ("localhost", netgrowl.GROWL_UDP_PORT)
growlserver = socket(AF_INET,SOCK_DGRAM)
print "Assembling registration packet like growlnotify’s (no password)"
p = netgrowl.GrowlRegistrationPacket()
p.addNotification()
print "Sending registration packet"
growlserver.sendto(p.payload(), addr)
# start the http server
httpserver = HTTPServer((”, 9889), GrowlBridgeHandler)
print ‘started growlbridge…’
httpserver.serve_forever()
except KeyboardInterrupt:
print ‘^C received, shutting down server’
httpserver.socket.close()
growlserver.close()
print "Done."
if __name__ == ‘__main__’:
main()
growlbridge.py.gz
Of course this opens up a port on your machine, so you should take the necessary precautions to firewall it. It uses Rui Carmo’s netgrowl and also requires Bob Ippolito’s simplejson.
We can now use Brian’s growl.js library with both Mac OS X and Windows versions of Growl. The next step is to connect it up to Facebook with Greasemonkey:
// ==UserScript==
// @name Facebook News Feed Notifier
// @namespace http://tlrobinson.net/
// @description Notify user of new Facebook News Feed items via Growl
// @include http://*.facebook.com/home.php
// ==/UserScript==
function GM_init() {
var fbNewsFeedNotification =
new Growl.
NotificationType(
"Facebook News Feed",
true);
Growl.
register(
"Facebook", [fbNewsFeedNotification]);
unsafeWindow.HomeFeed.prototype._addStoriesToQueueOriginal = unsafeWindow.HomeFeed.prototype._addStoriesToQueue
unsafeWindow.HomeFeed.prototype._addStoriesToQueue =
function(stories) {
this.
_addStoriesToQueueOriginal(stories);
var testDiv = document.
createElement(
"div");
for (
var i =
0; i < stories.length; i++)
{
testDiv.innerHTML = stories[i];
var spans = testDiv.
getElementsByTagName(
"span");
var message = (spans.length >
0) ? spans[
0].textContent :
"Unknown update";
Growl.
notify(fbNewsFeedNotification,
"Facebook News Feed", message, Growl.Priority.Normal,
false);
}
}
}
// Add growl.js
var GM_GROWL = document.createElement(‘script’);
GM_GROWL.src = ‘http://www.tripthevortex.com/growl/growl.js’;
GM_GROWL.type = ‘text/javascript’;
document.getElementsByTagName(‘head’)[0].appendChild(GM_GROWL);
// Check if growl.js’s loaded
function GM_wait() {
if(typeof unsafeWindow.Growl == ‘undefined’)
{
console.log("waiting");
window.setTimeout(GM_wait, 100);
}
else
{
Growl = unsafeWindow.Growl;
GM_init();
}
}
GM_wait();
This fairly simple userscript loads growl.js, then overwrites one of Facebook’s JavaScript functions that gets called when updating the feed, HomeFeed.prototype._addStoriesToQueue(). The new function should call the original one (so that the feed is still updated), but it should also post a new notification for each new feed story.
That’s about it. Run “python growlbridge.py” if you’re on a Mac (make sure you have netgrowl and simplejson), or Brian Dunnington’s Growl for Windows, install the above Greasemonkey userscript, and open up http://www.new.facebook.com/home.php?tab=2.
Unfortunately, there appears to be some bugs (or new security restrictions) in Firefox 3 that prevents this userscript from working correctly, but it works fine in Firefox 2.
In the future growl.js could be swapped out for the Gears or Fluid notification APIs, or anything else (ideally some standard). Also, hopefully Growl for OS X will have the same HTTP interface built in.