Fork me on GitHub

Blather

Simpler XMPP

XMPP DSL for Ruby written on EventMachine and Nokogiri.

Getting Started

Blather comes with a DSL that makes writing XMPP bots quick and easy:

require 'rubygems'
require 'blather/client'

setup 'echo@jabber.local', 'echo'

# Auto approve subscription requests
subscription :request? do |s|
  write s.approve!
end

# Echo back what was said
message :chat?, :body do |m|
  write m.reply
end

Handlers

Handlers let Blather know how you'd like each type of stanza to be well.. handled. Each type of stanza has an associated handler which is part of a handler hierarchy. In the example above we're handling message and subscription stanzas.

XMPP is built on top of three main stanza types (presence, message, and iq). All other stanzas are built on these three base types. This creates a natural hierarchy of handlers. For example a subscription stanza is a type of presence stanza and can be processed by a subscription handler or a presence handler. If you've done any DOM programming you'll be familiar with this.

Incoming stanzas will be handled by the first handler found. Unlike the DOM this will stop the handling bubble unless the handler returns false.

Example:

Here we have a presence handler and a subscription handler. When this script receives a subscription stanza the subscription handler will be notified first. If that handler doesn't know what to do it can return false and let the stanza bubble up to the presence handler.

# Handle all presence stanzas
presence do |stanza|
  # do stuff
end

# Handle all subscription stanzas
subscription do |stanza|
  # do stuff
end

Guards

If you played with Erlang at all the next section should look somewhat familiar. The idea behind guards was borrowed directly from Erlang.

Guards allow multiple instances of a handler to be created that handle different versions of the same stanza. XMPP is a message passing protocol. It only follows that an XMPP DSL should borrow from message passing languages.

Guards come in 4 flavors with one extra bit of syntax sugar. Chained guards are considered to be combined with &&.

Symbols
Symbols are called directly on the stanza. Any method can be called this way. If it returns non-false (nil or false) it will be handled.
message :chat?, :body
is equivalent to "handle a message stanza where
(stanza.chat? && stanza.body) is true"
Hash with value
Hashes check for equality. The key is called on the stanza and checked for equality against the value
message :body => 'exit'
is equivalent to "handle a message stanza where stanza.body == 'chat'"
Hash with regular expression
Like hash-with-value the key is called on the stanza and matched against the regex
message :body => /blather/
is equivalent to "handle a message stanza where stanza.body =~ /blather/"
Hash with array
Hash with array (:name => [:gone, :forbidden])
stanza_error :name => [:gone, :fobidden]
is equivalent to [:gone, :forbidden].include?(stanza.name)
Block
As you might expect blocks will have the stanza passed to them where complex logic can be run.
message lambda { |stanza| stanza.id % 3 == 0 }
is equivalent to "handle a message stanza where stanza.id is a divisor of 3"
XPath
Runs the xpath query on the stanza and returns the result if it's non-empty. This guard cannot be combined with other guards.
iq '/iq/ns:pubsub', :ns => 'pubsub:namespace'
is equivalent to !stanza.find('/iq/ns:pubsub', :ns => 'pubsub:namespace').empty?
Sugar (combining with Array):
All guard types can be combined in an array turning the combination from && to ||.
message [{:body => 'foo'}, {:id => 10}]
is equivalent to "handle a message stanza where
stanza.body == "foo" or stanza.id == 10

Command Line Arguments

Without specifying a setup in the script the default command line arguments are:

Usage: [blather_script] [options] node@domain.com/resource password [host] [port]
    -D, --debug                      Run in debug mode
    -d, --daemonize                  Daemonize the process
        --pid=[PID]                  Write the PID to this file
        --log=[LOG]                  Write stdout/stderr to the [LOG] file
    -h, --help                       Show this message
    -v, --version                    Show version

Current Handler Heirarchy

iq
|-- pubsub_node
|   |-- pubsub_affiliations
|   |-- pubsub_create
|   |-- pubsub_items
|   |-- pubsub_publish
|   |-- pubsub_retract
|   |-- pubsub_subscribe
|   |-- pubsub_subscription
|   |-- pubsub_subscriptions
|   `-- pubsub_unsubscribe
|-- pubsub_owner
|   |-- pubsub_delete
|   `-- pubsub_purge
`-- query
    |-- disco_info
    |-- disco_items
    `-- roster

message
`-- pubsub_event

presence
|-- status
`-- subscription

error
|-- argument_error
|-- parse_error
|-- sasl_error
|-- sasl_unknown_mechanism
|-- stanza_error
|-- stream_error
|-- tls_failure
`-- unknown_response_error