Blather Build Status

XMPP DSL (and more) for Ruby written on EventMachine and Nokogiri.

Features

  • evented architecture
  • uses Nokogiri
  • simplified starting point

Project Pages

Usage

Installation

gem install blather

Example

Blather comes with a DSL that makes writing XMPP bots quick and easy. See the examples directory for more advanced examples.

require 'blather/client'

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

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

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

The above example is for a standalone script, and can be run as a command line program. If you wish to integrate Blather into an existing application, you will need to avoid blather/client and instead do something like this:

require 'blather/client/dsl'

module App
  extend Blather::DSL

  def self.run
    EM.run { client.run }
  end

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

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

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

trap(:INT) { EM.stop }
trap(:TERM) { EM.stop }

App.run

If you need to ensure that Blather does not block the rest of your application, run the reactor in a new thread:

Thread.new { App.run }

You can additionally send messages like so:

App.say 'foo@bar.com', 'Hello there!'

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. Likewise, a PubSub::Items stanza has its own identifier :pubsub_items but it's also a :pubsub_node, :iq and :staza. Any or each of these could be used to handle the PubSub::Items stanza. 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.

The entire handler hierarchy can be seen below.

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

Additionally, handlers may be 'guarded'. That is, they may have conditions set declaratively, against which the stanza must match in order to trigger the handler.

# Will only be called for messages where #chat? responds positively
# and #body == 'exit'
message :chat?, :body => 'exit'

Non-Stanza Handlers

So far there are two non-stanza related handlers.

# Called after the connection has been connected. It's good for initializing
# your system.
# DSL:
when_ready {}
# Client:
client.register_handler(:ready) {}

# Called after the connection has been terminated. Good for teardown or
# automatic reconnection.
# DSL:
disconnected {}
# Client
client.register_handler(:disconnected) {}
# The following will reconnect every time the connection is lost:
disconnected { client.connect }

Handler Guards

Guards are a concept borrowed from Erlang. They help to better compartmentalize handlers.

There are a number of guard types and one bit of special syntax. Guards act like AND statements. Each condition must be met if the handler is to be used.

# Equivalent to saying (stanza.chat? && stanza.body)
message :chat?, :body

The different types of guards are:

# Symbol
#   Checks for a non-false reply to calling the symbol on the stanza
#   Equivalent to stanza.chat?
message :chat?

# Hash with any value (:body => 'exit')
#   Calls the key on the stanza and checks for equality
#   Equivalent to stanza.body == 'exit'
message :body => 'exit'

# Hash with regular expression (:body => /exit/)
#   Calls the key on the stanza and checks for a match
#   Equivalent to stanza.body.match /exit/
message :body => /exit/

# Hash with array (:name => [:gone, :forbidden])
#   Calls the key on the stanza and check for inclusion in the array
#   Equivalent to [:gone, :forbidden].include?(stanza.name)
stanza_error :name => [:gone, :fobidden]

# Proc
#   Calls the proc passing in the stanza
#   Checks that the ID is modulo 3
message proc { |m| m.id % 3 == 0 }

# Array
#   Use arrays with the previous types effectively turns the guard into
#   an OR statement.
#   Equivalent to stanza.body == 'foo' || stanza.body == 'baz'
message [{:body => 'foo'}, {:body => 'baz'}]

# XPath
#   Runs the xpath query on the stanza and checks for results
#   This guard type cannot be combined with other guards
#   Equivalent to !stanza.find('/iq/ns:pubsub', :ns => 'pubsub:namespace').empty?
#   It also passes two arguments into the handler block: the stanza and the result
#   of the xpath query.
iq '/iq/ns:pubsub', :ns => 'pubsub:namespace' do |stanza, xpath_result|
  # stanza will be the original stanza
  # xpath_result will be the pubsub node in the stanza
end

Filters

Blather provides before and after filters that work much the way regular handlers work. Filters come in a before and after flavor. They're called in order of definition and can be guarded like handlers.

before { |s| "I'm run before any handler" }
before { |s| "I'm run next" }

before(:message) { |s| "I'm only run in front of message stanzas" }
before(nil, :id => 1) { |s| "I'll only be run when the stanza's ID == 1" }

# ... handlers

after { |s| "I'm run after everything" }

Handlers Hierarchy

stanza
|- 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

On the Command Line

Default usage is:

[blather_script] [options] node@domain.com/resource password [host] [port]

Command line options:

-D, --debug       Run in debug mode (you will see all XMPP communication)
-d, --daemonize   Daemonize the process
    --pid=[PID]   Write the PID to this file
    --log=[LOG]   Write to the [LOG] file instead of stdout/stderr
-h, --help        Show this message
-v, --version     Show version

Health warning

Some parts of Blather will allow you to do stupid things that don't conform to XMPP spec. You should exercise caution and read the relevant specifications (indicated in the preamble to most relevant classes).

Spec compliance

Blather provides support in one way or another for many XMPP specifications. Below is a list of specifications and the status of support for them in Blather. This list may not be correct. If the list indicates a lack of support for a specification you wish to use, you are encouraged to check that this is correct. Likewise, if you find an overstatement of Blather's spec compliance, please point this out. Also note that even without built-in support for a specification, you can still manually construct and parse stanzas alongside use of Blather's built-in helpers.

Specification Support Name Notes
RFC 6120 Full XMPP: Core
RFC 6121 Full XMPP: Instant Messaging and Presence
RFC 6122 Full XMPP: Address Format
XEP-0001 N/A XMPP Extension Protocols
XEP-0002 N/A Special Interest Groups (SIGs)
XEP-0004 Partial Data Forms
XEP-0009 None Jabber-RPC
XEP-0012 None Last Activity
XEP-0013 None Flexible Offline Message Retrieval
XEP-0016 None Privacy Lists
XEP-0019 N/A Streamlining the SIGs
XEP-0020 Partial Feature Negotiation
XEP-0027 None Current Jabber OpenPGP Usage
XEP-0030 Partial Service Discovery
XEP-0033 None Extended Stanza Addressing
XEP-0045 Partial Multi-User Chat
XEP-0047 None In-Band Bytestreams
XEP-0048 None Bookmarks
XEP-0049 None Private XML Storage
XEP-0050 Partial Ad-Hoc Commands
XEP-0053 None XMPP Registrar Function
XEP-0054 None vcard-temp
XEP-0055 None Jabber Search
XEP-0059 None Result Set Management
XEP-0060 Partial Publish-Subscribe
XEP-0065 None SOCKS5 Bytestreams
XEP-0066 None Out of Band Data
XEP-0068 None Field Standardization for Data Forms
XEP-0070 None Verifying HTTP Requests via XMPP
XEP-0071 Partial XHTML-IM
XEP-0072 None SOAP Over XMPP
XEP-0076 None Malicious Stanzas
XEP-0077 None In-Band Registration
XEP-0079 None Advanced Message Processing
XEP-0080 None User Location
XEP-0082 None XMPP Date and Time Profiles
XEP-0083 None Nested Roster Groups
XEP-0084 None User Avatar
XEP-0085 Partial Chat State Notifications
XEP-0092 None Software Version
XEP-0095 Partial Stream Initiation
XEP-0096 Partial SI File Transfer
XEP-0100 None Gateway Interaction
XEP-0106 None JID Escaping
XEP-0107 None User Mood
XEP-0108 None User Activity
XEP-0114 Full Jabber Component Protocol
XEP-0115 Partial Entity Capabilities
XEP-0118 None User Tune
XEP-0122 None Data Forms Validation
XEP-0124 None Bidirectional-streams Over Synchronous HTTP (BOSH)
XEP-0126 None Invisibility
XEP-0127 None Common Alerting Protocol (CAP) Over XMPP
XEP-0128 None Service Discovery Extensions
XEP-0130 None Waiting Lists
XEP-0131 None Stanza Headers and Internet Metadata
XEP-0132 None Presence Obtained via Kinesthetic Excitation (POKE)
XEP-0133 None Service Administration
XEP-0134 None XMPP Design Guidelines
XEP-0136 None Message Archiving
XEP-0137 None Publishing Stream Initiation Requests
XEP-0138 None Stream Compression
XEP-0141 None Data Forms Layout
XEP-0143 None Guidelines for Authors of XMPP Extension Protocols
XEP-0144 N/A Roster Item Exchange
XEP-0145 None Annotations
XEP-0146 None Remote Controlling Clients
XEP-0147 None XMPP URI Scheme Query Components
XEP-0148 None Instant Messaging Intelligence Quotient (IM IQ)
XEP-0149 None Time Periods
XEP-0153 None vCard-Based Avatars
XEP-0155 None Stanza Session Negotiation
XEP-0156 None Discovering Alternative XMPP Connection Methods
XEP-0157 None Contact Addresses for XMPP Services
XEP-0158 None CAPTCHA Forms
XEP-0160 None Best Practices for Handling Offline Messages
XEP-0163 Partial Personal Eventing Protocol
XEP-0166 None Jingle
XEP-0167 None Jingle RTP Sessions
XEP-0169 None Twas The Night Before Christmas (Jabber Version)
XEP-0170 None Recommended Order of Stream Feature Negotiation
XEP-0171 None Language Translation
XEP-0172 None User Nickname
XEP-0174 None Serverless Messaging
XEP-0175 None Best Practices for Use of SASL ANONYMOUS
XEP-0176 None Jingle ICE-UDP Transport Method
XEP-0177 None Jingle Raw UDP Transport Method
XEP-0178 None Best Practices for Use of SASL EXTERNAL with Certificates
XEP-0182 N/A Application-Specific Error Conditions
XEP-0183 None Jingle Telepathy Transport
XEP-0184 None Message Delivery Receipts
XEP-0185 None Dialback Key Generation and Validation
XEP-0191 None Blocking Command
XEP-0198 None Stream Management
XEP-0199 Partial XMPP Ping
XEP-0201 None Best Practices for Message Threads
XEP-0202 None Entity Time
XEP-0203 Partial Delayed Delivery
XEP-0205 None Best Practices to Discourage Denial of Service Attacks
XEP-0206 None XMPP Over BOSH
XEP-0207 None XMPP Eventing via Pubsub
XEP-0220 None Server Dialback
XEP-0221 None Data Forms Media Element
XEP-0222 None Persistent Storage of Public Data via PubSub
XEP-0223 None Persistent Storage of Private Data via PubSub
XEP-0224 None Attention
XEP-0227 None Portable Import/Export Format for XMPP-IM Servers
XEP-0229 None Stream Compression with LZW
XEP-0231 None Bits of Binary
XEP-0233 None Domain-Based Service Names in XMPP SASL Negotiation
XEP-0234 None Jingle File Transfer
XEP-0239 None Binary XMPP
XEP-0242 None XMPP Client Compliance 2009
XEP-0243 None XMPP Server Compliance 2009
XEP-0245 None The /me Command
XEP-0249 None Direct MUC Invitations
XEP-0256 None Last Activity in Presence
XEP-0258 None Security Labels in XMPP
XEP-0260 None Jingle SOCKS5 Bytestreams Transport Method
XEP-0261 None Jingle In-Band Bytestreams Transport Method
XEP-0262 None Use of ZRTP in Jingle RTP Sessions
XEP-0263 None ECO-XMPP
XEP-0266 None Codecs for Jingle Audio
XEP-0267 None Server Buddies
XEP-0270 None XMPP Compliance Suites 2010
XEP-0273 None Stanza Interception and Filtering Technology (SIFT)
XEP-0277 None Microblogging over XMPP
XEP-0278 None Jingle Relay Nodes
XEP-0280 None Message Carbons
XEP-0288 None Bidirectional Server-to-Server Connections
XEP-0292 None vCard4 Over XMPP
XEP-0293 None Jingle RTP Feedback Negotiation
XEP-0294 None Jingle RTP Header Extensions Negotiation
XEP-0295 None JSON Encodings for XMPP
XEP-0296 None Best Practices for Resource Locking
XEP-0297 None Stanza Forwarding
XEP-0298 None Delivering Conference Information to Jingle Participants (Coin)
XEP-0299 None Codecs for Jingle Video
XEP-0300 None Use of Cryptographic Hash Functions in XMPP
XEP-0301 None In-Band Real Time Text
XEP-0302 None XMPP Compliance Suites 2012
XEP-0303 None Commenting
XEP-0304 None Whitespace Keepalive Negotiation
XEP-0305 None XMPP Quickstart
XEP-0306 None Extensible Status Conditions for Multi-User Chat
XEP-0307 None Unique Room Names for Multi-User Chat
XEP-0308 None Last Message Correction
XEP-0309 None Service Directories
XEP-0310 None Presence State Annotations
XEP-0311 None MUC Fast Reconnect
XEP-0312 None PubSub Since

Contributions

All contributions are welcome, even encouraged. However, contributions must be well tested. If you send me a branch name to merge that'll get my attention faster than a change set made directly on master.

Author

Copyright

Copyright (c) 2012 Adhearsion Foundation Inc. See LICENSE for details.