Adding Growl Notifications to Facebook

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:

Live Feed vs 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.

  • http://www.tripthevortex.com/growl brian dunnington

    that is fantastic! this is just the kind of situation that i wanted to address when i added the HTTP interface to Growl for Windows. it is exciting to see someone take it and tweak it and make something cool out of it.

    and don’t despair – i am working with the Growl OSX guys on coming up with a cross-platform standard protocol for sending notifications to Growl from anywhere: local apps, networked apps, websites via javascript or from server-side, even Adobe AIR applications.

  • pete

    Do you have a fix for firefox 3 yet? How about with the new Facebook?

    • http://tlrobinson.net/ tlrobinson

      I’m not actively working on this any more. There are better ways to get Facebook notifications, like through Adium, any many other Twitter/Facebook clients.

      Sorry.

  • raffael

    Or you just use “Glow for FB” :-)!