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