switchboard : XMPP :: curl : HTTP

Quick Start

Here's how to install and use Switchboard for a few basic use-cases:

$ # install Switchboard
$ sudo gem install switchboard

$ # list everyone on your roster (buddy list)
$ switchboard --jid client@example.com --password pa55word \
    roster list

$ # add a friend to your roster
$ switchboard --jid client@example.com --password pa55word \
    roster add friend@example.com

$ # listen for PubSub events
$ switchboard --jid subscriber@example.com --password pa55word \
    pubsub --server <pubsub server> listen

"curl for XMPP?"

curl is the ultimate Swiss Army Knife for HTTP. Switchboard aims to be the same for XMPP.

HTTP is (relatively) easy. It's stateless and has fairly limited semantics. However, if you're trying to debug a web service and want to determine what certain ETags, additional headers, or specific content types respond with, curl is your go-to.

XMPP is a bit more complicated; it's stateful, it (usually) requires login credentials, it's asynchronous, and it has many extensions to the core protocol that are in varying levels of use. Traditionally, in order to explore an XMPP service, you'd have to delve into the advanced features of a client like Psi or Synapse, often dropping to the level of entering raw XML to see what happens in response.

Switchboard (and its command-line equivalent, switchboard) simplifies the process of repeatedly making common requests (e.g. roster manipulation, probing, and PubSub node operations) and is easily extended to support new behaviors.

Switchboard background

Copying and pasting XML isn't the least error-prone process I can think of. In addition, I found myself repeatedly testing the same types of stanzas (e.g. PubSub requests) and attempting to implement extensions for which no implementations existed (i.e. OAuth over XMPP).

Diving into xmpp4r's functionality led me to expose additional features to the command-line tool such as PEP and Last Activity to see how difficult it would be.

Since all of its dependencies have been released and are installable via RubyGems now, people who aren't me can finally use Switchboard without much hassle. In honor of this milestone, Switchboard has been updated to 0.1.0. Real mature, I know, but it is still quite useful.

People who are me have been using this tool since last November (in fact, this is the project that inspired "My (public) Git Workflow"). (Some people have been using it to consume Fire Eagle's Location Streams, but it hasn't been for the faint of heart.)

Switchboard's primary strength is certainly the command-line interface, but it turns out that it's a pretty good abstraction over xmpp4r for building clients and servers (as components).

Here are a few projects I've worked on that use Switchboard:

What to do?

Configuration

The first thing you'll probably want to do with Switchboard is to provide some basic configuration so you won't have to constantly enter your login credentials:

$ switchboard config jid jid@example.com
$ switchboard config password pa55word

To get the value of a setting, don't include a value:

$ switchboard config jid # => jid@example.com

Some additional useful settings to set defaults for are:

In general, anything that is referred to in the library in the form OPTIONS["pubsub.node"] can be set as a Switchboard setting.

Roster Manipulation

Roster manipulation is something that can be easily handled by a desktop XMPP client, but sometimes it's more convenient to be able to do it from the command-line.

Rosters can be listed, added to, or removed from. I'll assume you've configured Switchboard with some login credentials.

$ switchboard roster list
$ switchboard roster online
$ switchboard roster add friend1@example.org friend2@example.org
$ switchboard roster remove friend2@example.org enemy@example.org

Probing and Discovery

XMPP provides some pretty heady functionality when it comes to determining what a server is capable of. A disco#info query is the first step to use when determining capabilities:

$ switchboard disco --target jabber.org info

The response to this query includes http://jabber.org/protocol/disco#items, which means that jabber.org supports disco#items queries, which allow you to determine what top-level items (services) are available:

$ switchboard disco --target jabber.org items

This response includes conference.jabber.org. Let's list items available there:

$ switchboard disco --target conference.jabber.org items

Whoa. A list of MUCs (multi-user chats) hosted on conference.jabber.org.

You can do the same thing to list available PubSub nodes if you're running a local copy of ejabberd or another XMPP server that supports it. (Note that you may need to prefix your hostname with pubsub in order to see the nodes.)

PubSub

Switchboard supports more of PubSub than any other extension, mainly because that's been my primary focus of XMPP experimentation. To get a full list of available PubSub commands:

$ switchboard pubsub

A basic sequence of events is to subscribe:

$ switchboard pubsub --server <server> subscribe --node <node>

List subscriptions:

$ switchboard pubsub --server <server> subscriptions

Listen for notifications:

$ switchboard pubsub --server <server> listen

Unsubscribe:

$ switchboard pubsub --server <server> unsubscribe --node <node>

Let's walk through a couple examples.

First, Superfeedr, which bills itself as "real-time feed parsing in the cloud". To begin, you'll need to register and activate your account. Once you've done that, set up a subscription a Twitter search for "xmpp":

$ switchboard --jid <username>@superfeedr.com --password <password> \
    pubsub --server firehoser.superfeedr.com \
    subscribe --node "http://search.twitter.com/search.atom?q=xmpp"

We would next list subscriptions, but Superfeedr uses a non-standard mechanism to do so. Instead, let's listen for new results:

$ switchboard --jid <username>@superfeedr.com --password <password> \
    pubsub --server firehoser.superfeedr.com listen

If you're lucky, you'll get an Atom payload or two. Here's one:

<event xmlns='http://jabber.org/protocol/pubsub#event'>
  <status feed='http://search.twitter.com/search.atom?q=xmpp' xmlns='http://superfeedr.com/xmpp-pubsub-ext'>
    <http code='200'>16933 bytes fetched in 0.600034s</http>
    <next_fetch>2009-07-22T17:26:22Z</next_fetch>
  </status>
  <items node='http://search.twitter.com/search.atom?q=xmpp'>
    <item chunk='1' chunks='1'>
      <entry xmlns='http://www.w3.org/2005/Atom'>
        <title>Great... trillian update has killed my ability to view my xmpp rosters</title>
        <summary>Great... trillian update has killed my ability to view my &lt;b&gt;xmpp&lt;/b&gt; rosters</summary>
        <link href='http://superfeedr.com/entries/tr5gfgstf8oqlcgr5opaeotxk39ovtos0oiat7h12mqfuoxdmgbjz1rnjtzswqvja2dqh8cgg31' rel='alternate' type='text/html'/>
        <published>2009-07-22T17:10:52Z</published>
        <id>tag:search.twitter.com,2005:2781212809</id>
      </entry>
    </item>
    <item chunk='1' chunks='1'>
      <entry xmlns='http://www.w3.org/2005/Atom'>
        <title>usando Tkabber: TKabber is Tcl/Tk Jabber-client with great functionality. It supports MUC, XMPP-statuses a.. http://bit.ly/AsFPg</title>
        <summary>usando Tkabber: TKabber is Tcl/Tk Jabber-client with great functionality. It supports MUC, &lt;b&gt;XMPP&lt;/b&gt;-statuses a.. &lt;a href=&quot;http://bit.ly/AsFPg&quot;&gt;http://bit.ly/AsFPg&lt;/a&gt;</summary>
        <link href='http://superfeedr.com/entries/w6cezbjniqqgga3bd79ruklxhu7f4a6qqpro76hgz50gzccuekgmehz39yb1zi1cclgo83s' rel='alternate' type='text/html'/>
        <published>2009-07-22T17:07:54Z</published>
        <id>tag:search.twitter.com,2005:2781162048</id>
      </entry>
    </item>
  </items>
</event>

It includes a Superfeedr-specific <status/> element with information on the most recent fetch as well as standard Atom feeds contained within standard <item/> elements. This means that you can extract the Atom elements from the PubSub payload and hand it to a feedparser of some variety to work its magic.

Fire Eagle's Location Streams also use PubSub, but subscription management requires that requests are signed using OAuth. This allows 3rd party applications to make requests on behalf of specific users without having to obtain their actual credentials (in short, it's exactly the same use-case for OAuth over HTTP).

Let's start with a subscriptions list request, since we have the credentials ("General Purpose Access Token") immediately after registering a "web" application with Fire Eagle.

$ switchboard pubsub --oauth \
    --oauth-consumer-key <consumer key> \
    --oauth-consumer-secret <consumer secret> \
    --oauth-token <general token> \
    --oauth-token-secret <general token secret> \
    --server fireeagle.com \
    subscriptions

Odds are, you'll have nothing there. Let's change that. Send yourself through the authorization process in order to get a valid OAuth token and secret:

$ oauth --consumer-key <consumer key> \
    --consumer-secret <consumer secret> \
    --access-token-url https://fireeagle.yahooapis.com/oauth/access_token
    --authorize-url https://fireeagle.yahoo.net/oauth/authorize
    --request-token-url https://fireeagle.yahooapis.com/oauth/request_token
    authorize

With that token and secret, subscribe to your Location Stream:

$ switchboard pubsub --oauth \
    --oauth-consumer-key <consumer key> \
    --oauth-consumer-secret <consumer secret> \
    --oauth-token <token> \
    --oauth-token-secret <token secret> \
    --server fireeagle.com \
    subscribe --node "/api/0.1/user/<token>"

Now you'll have a subscription to query for:

$ switchboard pubsub --oauth \
    --oauth-consumer-key <consumer key> \
    --oauth-consumer-secret <consumer secret> \
    --oauth-token <general token> \
    --oauth-token-secret <general token secret> \
    --server fireeagle.com \
    subscriptions

Listen for location updates:

$ switchboard pubsub --server fireeagle.com listen

Update your current location and watch as the update rolls in. If you'd like to visualize updates with Google Earth, check out fire-hydrant on GitHub.

We're done, so we may as well clean up and unsubscribe:

$ switchboard pubsub --oauth \
    --oauth-consumer-key <consumer key> \
    --oauth-consumer-secret <consumer secret> \
    --oauth-token <token> \
    --oauth-token-secret <token secret> \
    --server fireeagle.com \
    unsubscribe --node "/api/0.1/user/<token>"

PEP (Personal Eventing Protocol)

PEP is a specialized version of PubSub, intended to allow individuals to associate data with their JIDs. Switchboard supports publishing of User Tune and User Location.

Due to the ability of XMPP to allow multiple instances of the same account online (identified with different resources), Switchboard can serve up User Tune and User Location for the same account you're already online with. Not every client supports displaying them, but they're fun to play with regardless.

To publish User Tune, you need to be on a Mac, running iTunes, and have the rb-appscript gem installed (sudo gem install rb-appscript). Once that's done:

$ switchboard --resource switchtunes pep tune

To publish User Location, you need to be updating Fire Eagle (Clarke is an excellent background updater for OS X) and have the fire-hydrant gem installed from GitHub (sudo gem install mojodna-fire-hydrant -s http://gems.github.com). Once that's square:

$ switchboard --resource switchfire pep location

More

Switchboard supports more functionality than I've described above. To get a list of general switchboard commands (some of which may have sub-commands):

$ switchboard

Getting Help

In theory, if you want more information about a specific command, you can use switchboard help <command>. For example:

$ switchboard help pubsub

For now, you'll notice that it's not particularly useful. If you'd like to help rectify this, you can implement various help methods that are lying around, such as Switchboard::Commands::PubSub.help.

Extending

Writing new Switchboard commands is really easy, assuming that the primary application logic that you're depending exists elsewhere (i.e. in xmpp4r).

I was on a panel with Peter St. Andre and Jack Moffitt at the Glue Conference in Denver this Spring and we got to talking about tools like Switchboard. Jack wondered how hard it would be to implement something like grep for XMPP.

(Jack is one of the authors of a Python project similar to Switchboard named poetry).

I took a whack at it and it came out like this:

module Switchboard
  module Commands
    class Grep < Switchboard::Command
      description "Search for an XPath expression"

      def self.run!
        expr = ARGV.pop

        switchboard = Switchboard::Client.new
        switchboard.plug!(AutoAcceptJack, NotifyJack)

        switchboard.on_stanza do |stanza|
          # TODO doesn't handle default namespaces properly
          REXML::XPath.each(stanza, expr) do |el|
            puts el.to_s
          end
        end

        switchboard.run!
      end
    end
  end
end

Jacks (not Moffitt) deserve their own discussion, but the thrust of this piece of code is the #on_stanza callback (which yields a REXML::Node object) and the XPath expression. Note that for whatever reason, you can't search for nodes that have a default namespace (e.g. <presence />); I'm going to assume that this is a REXML quirk.

If you want to take a shot at implementing a Switchboard command, Ping should be pretty simple. Alternately, Switchboard doesn't support sending or receiving basic <message /> stanzas from the command-line. Supporting those would make it simple to interact with a service like identi.ca.

How to Help

There are a few simple ways to help out with Switchboard's development:

What's your favorite use for Switchboard?