Recently, Facebook opensourced a tool I wrote in 2007 called bunny1 which lets you write smart bookmarks in python and share them across all the browsers you use and with a group of people (like a company). It has always been pretty hard to explain what bunny1 does but most people who have started using it find it really useful once they’ve started; personally, I now find it painful to use a browser without it. I’ll work through some examples of how bunny1 gets used at Facebook in this post to try to convince you that bunny1 is worth checking out.
A Command Line for Your Location Bar
The basic way that bunny1 works is like a command line for your location bar in your browser. Instead of typing URLs, you can type shorter things that are easier to remember. So, for example, you could type expensereports into the location bar instead of typing in http://www.thirdpartyexpensereportingprovider.com/yourcompany/login.aspx.
Browsers currently solve this problem by having a bookmarks toolbar or favorites menu, and Firefox lets you give “keywords” to your bookmarks which you can enter into the location bar instead of clicking. Firefox lets you parameterize your bookmarks to make something called quicksearches. If you bookmark the URL, http://www.somesite.com/?q=%s and give it the keyword somesite, then if you type somesite somesearch into your location bar, Firefox will go to the URL http://www.somesite.com/?q=somesearch . In 2005, Jon Aquino made a pretty interesting service called YubNub that let people share quicksearches. It works really well for common searches like Google search, Yahoo! search, Wikipedia search, Flickr image search, etc.
bunny1 is similar to YubNub, except that instead of having one global namespace of bookmarks that everyone shares, you can run your own server which means that you can put in bookmarks that are private to you (behind VPN) or give really short names to bookmarks that are really important to you but aren’t important to other people (ex. at Facebook, bz points to the Bugzilla instance for Facebook Platform). The other interesting feature of bunny1 is that all the logic for the commands is done on the server side, which means you can write your bookmarks with just a few lines of python and they are version controlled, and it means that its easier to add complicated logic to the way that inputs are handled. Since YubNub’s database of common quicksearch commands is pretty good for many basic things, the default behavior of bunny1 is to fallback to using YubNub if it doens’t understand what the user has entered. This means that many common commands like searching the PHP documentation (php on YubNub) come for free.
My guess is that the biggest reason that YubNub hasn’t really caught on is because Google Search has pretty much solved the navigation problem for the open web. If I want to schedule an appointment with the California DMV, I just type something like california dmv appointment into Google and I usually end up at the right place. And if I want to look up Jason Priestley on Wikipedia, a Google search for wikipedia Jason Priestley or even just Jason Priestley is a pretty good way to get there. But searching Google for facebook internal wiki coding standards both doesn’t work (since Google can’t index Facebook’s corporate intranet) and is also a lot to type. So I think its generally useful to have a domain-specific YubNub.
If you use quicksearches in Firefox, you’re used to being able to make parameterized bookmarks. Many people have g some search termas a shortcut to searching Google. With bunny1, you can do the same thing, but since the commands are written in python, its easy to put more sensible logic in place where it makes sense. For example, at Facebook, there is a command appabout which can be parameterized with either an id number, API key, or the short canvas name of an app. It’s easy to set this up with just a few lines of code:
def appabout(self, arg):
"""go to the about page for an app"""
return "http://www.facebook.com/apps/application.php?id=%s" % qp(arg)
# check to see if this is a valid API key
if len(arg) == 32:
return "http://www.facebook.com/apps/application.php?api_key=%s" % qp(arg)
return "http://www.facebook.com/app_about.php?app_name=%s" % qp(arg)
A simpler example would be that the fb command either searches Facebook for a search term, or if none is given, just goes to the Facebook homepage.
It’s also pretty easy to write in more complicated syntactical elements into your commands. A few examples that are really useful at Facebook are:
If you just put something like r146630 into your location bar, it will take you to that revision in our Trac instance. This is really convenient when copy/pasting things from e-mails, etc.
Switching between staging environments
When doing development at Facebook, its pretty common to want to view a specific page on the live site, using the current tip of the trunk of the codebase, or in a particular sandbox. Switching between these requires changing some parts in the middle of the subdomain, which is often painful to do manually. Using bunny1, you can just put something like @live, @dev, @sb, @ccheever.someserver, or @beta before the URL in the location bar to jump between these different environments very quickly. This speeds up investigation and debugging of some common problems.
Referring to people.
One thing that’s pretty common at Facebook is wanting to go to a particular person’s profile. It’s not too terrible to do this on facebook.com since we have typeahead search, but it’s really nice to be able to type p vernal into the location bar and go right to Vernal’s profile. In the Facebook instance of bunny1, there is a decorator that makes it really easy to mark commands as ones that should be parameterized by people, so you can also do wall (corson) (j wang) to see the wall-to-wall between Dan and Jwang. The code for a command like this just looks something like this:
def profile(self, arg):
"""goes to a profile of user"""
return "http://www.facebook.com/profile.php?id=%s" % qp(arg)
p = profile
and the name to user id substitution happens automatically. Since the commands are all written in python code, its easy to make abstractions that make writing similar useful commands really easy.
It’s also pretty useful to have shortcuts for searching different services in a corporate setting. At Facebook, some of the most popular are iw for searching the Facebook Internal Wiki, c for searching the internal bugs database, mla for searching the archives of mailing lists, fb for searching the website, bz for searching the Facebook Platform Public Bugzilla Database, and devwiki for searching the Facebook Developer Wiki, and a few others as well. Making it take less time to execute these searches actually makes a meaningful difference in productivity for people who do any of these operations many times each day.
There are also a fair number of commands that aren’t just bookmarks or searches but have side effects. An example of this would be the merge command which lets you request the merge of a particular commit into the release branch.
bunny1 is similar in many ways to a fantastic Firefox add-on from Mozilla Labs called Ubiquity. Ubiquity is pretty much bunny1 on steroids and absolutely something you should install if you use Firefox. The main difference is that Ubiquity is a Firefox add-on so it can do very powerful things like access your e-mail contacts, but it only works on Firefox. In the fullness of time, I expect that a version of Ubiquity will be released for pretty much every browser and the product will continue to mature, and everyone will use Ubiquity for everything. But for right now, there are a couple reasons that I still use bunny1.
bunny1 works pretty well across all browsers while Ubiquity only works in Firefox. Almost every day, I use Firefox, Safari, IE, Chrome, sometimes Opera, and occasionally more exotic browsers like WebKit nightly builds. It’s really nice that I can use bunny1 with all of them.
Also, since bunny1 is a server, my bookmarks are always in sync across the different computers that I use. There are ways to set up syncing of things like Firefox configuration across different machines and Ubiquity subscriptions also help with this problem, but getting these things right takes a lot more effort than just hooking up to the same bunny1 server.
Since the UI for bunny1 is typically the location bar, my cursor is there already when I open a new tab. And since bunny1 is scoped, its normal to make any command that you use frequently have a really short name even if you don’t think everyone in the world would use that command a lot, (ex. iw for searching Facebook’s internal wiki).
Its also slightly less work to add commands to bunny1. One point of comparison is that Jono DiCarlo has a good screencast showing how to write Ubiquity commands where he works through the example of writing a command to find your congressional representatives by zip code in about 10 minutes; I added an equivalent command to the bunny1.org instance of bunny1 in just about one minute. (It probably would have taken 2-3 minutes if I had been explaining and screencasting and it would definitely be possible to streamline the process described in the screencast, but there is just less stuff you have to do to make a command in bunny1). These days at least, most of what people want to do with tools like these is go to a specific URL (ex. the YouTube subscription center) or go to a specific URL with a given parameter (ex. search Google for some phrase), so having it be as easy and fast as possible to add these kinds of commands is a really desirable property.
When I run Firefox, I actually use Ubiquity and bunny1 at the same time, and use bunny1 for navigating and searching common sites, and use Ubiquity for fancy things like embedding a Google map in my e-mail or translating in-page or e-mailing my grandmother my entire image browsing history.
The Facebook bunny1 Setup
At Facebook, there is an instance of bunny1 called lolbunny that runs on a few machines that are publicly accessible (for redundancy; the resources it requires are negligible. The first time that a person accesses the instance, he or she must be inside Facebook’s VPN. If this is the case, a special cookie is set on the browser which lets the user access lolbunny.
We made lolbunny publicly accessible so people could use it even when they weren’t on the VPN. It does mean that someone could steal the cookie and get access to lolbunny, but since lolbunny mostly just serves up redirects, that would just give you some vague ideas about the URL structure of the Facebook intranet but not access to any really sensitive information.
I’ve been planning to run my own bunny1 server on one of the domains I own, and then have that fallback to Facebook’s server for commands it doesn’t understand, but since I don’t do much navigation to things that aren’t either work or common enough to be handled by YubNub, I haven’t gotten around to doing this yet. At times, I’ve run a instance of bunny1 locally on my Mac, but it’s annoying to make sure the server is running between restarts, and it doesn’t achieve the portability that running one instance in the cloud for every browser does.