= Agent XMPP
Agent XMPP is a simple DSL for writing XMPP automated clients that support Messaging, Ad-Hoc Commands and Publish Subscribe
Events. An application that responds to an Ad-Hoc command can be written with few lines of code.
# myapp.rb
require 'rubygems'
require 'agent_xmpp'
execute 'hello' do
'Hello World'
end
Specify the application Jabber ID (JID), password and roster in agent_xmpp.yml.
jid: myapp@nowhere.com
password: pass
roster:
-
jid:you@home.com
Be sure libxml2 headers are available,
sudo apt-get install libxml2-dev
Install the gem
sudo gem install troystribling-agent_xmpp --source=http://gems.github.com
Install the Gajim XMPP Client, http://www.gajim.org, and connect to you@home.com (Be warned that installing Gajim on OS X can be painful.
Linux is recommended. Also, for Ubuntu installs, download the tar ball from the site. The version in the Ubuntu repositories is
older and does not have a completed Ad-Hoc Command implementation).
Run the application
ruby myapp.rb
When started for the first time myapp.rb will automatically send buddy requests to all contacts specified
in agent_xmpp.yml. Accept the buddy request and myapp will appear as a contact in the Gajim roster. Right click on myapp
and select execute commands from the drop down menu. A list of Ad-Hoc Commands will be displayed containing hello.
Select it and click the forward button to execute.
== More Examples
More examples can be found at http://gist.github.com/160338
== Ad-Hoc Command Response Payload
Agent XMPP will map scalars, arrays and hashes returned by execute blocks to jabber:x:data command
response payloads (see XEP-0004 http://xmpp.org/extensions/xep-0004.html for a description of jabber:x:data).
All scalars have to_s applied prior to building the command response payload.
execute 'scalar' do
'scalar'
end
execute 'hash' do
{:a1 => 'v1', :a2 => 'v2'}
end
execute 'scalar_array' do
['v1', 'v2','v3', 'v4']
end
execute 'hash_array' do
{:a1 => ['v11', 'v11'], :a2 => 'v12'}
end
execute 'array_hash' do
[{:a1 => 'v11', :a2 => 'v12'},
{:a1 => 'v21', :a2 => 'v22'},
{:a1 => 'v31', :a2 => 'v32'}]
end
execute 'array_hash_array' do
[{:a1 => ['v11', 'v11'], :a2 => 'v12'},
{:a1 => ['v21', 'v21'], :a2 => 'v22'},
{:a1 => ['v31', 'v31'], :a2 => 'v32'}]
end
a params structure is available within the execute block containing routing information.
== Send Command
Commands may be sent with or without a response callback,
command(:to=>'thatapp@a-place.com/ahost', :node=> 'hello') do |status, data|
puts "COMMAND RESPONSE: #{status}, #{data.inspect}"
end
command(:to=>'thatapp@a-place.com/ahost', :node=> 'bye')
and within execute blocks.
execute 'hash_hello' do
command(:to=>params[:from], :node=> 'hello') do |status, data|
puts "COMMAND RESPONSE: #{status}, #{data.inspect}"
end
{:a1 => 'v1', :a2 => 'v2'}
end
== Publish
Publish nodes are configured in agent_xmpp.yml.
jid: myapp@nowhere.com
password: pass
roster:
-
jid:you@home.com
publish:
-
node: time
title: "Curent Time"
-
node: alarm
title: "Alarms"
The nodes are created if they do not exist and publish methods are generated for each node.
publish_time('The time is:' + Time.now.to_s)
publish_alarm({:severity => :major, :description => "A really bad failure"})
Publish nodes discovered that are not in agent_xmpp.yml will be deleted.
== Publish Options
The following publish options are available with the indicated default values. The options may be changed in
agent_xmpp.yml.
:title => 'event',
:access_model => 'presence',
:publish_model => 'publishers',
:max_items => 20,
:deliver_notifications => 1,
:deliver_payloads => 1,
:persist_items => 1,
:subscribe => 1,
:notify_config => 0,
:notify_delete => 0,
:notify_retract => 0,
See http://xmpp.org/extensions/xep-0060.html#registrar-formtypes-config for a detailed description.
== Subscribe
Declare event blocks in myapp.rb to subscribe to published events .
# myapp.rb
require 'rubygems'
require 'agent_xmpp'
event 'someone@somewhere.com', 'time' do
message(:to=>'someone@somewhere.com', :body=>"Got the event at: " + Time.now.to_s)
end
Agent XMPP will verify subscription to the event and subscribe if required. Subscriptions
discovered that are not declared by an event block will be deleted.
== Receive Chat Messages
Declare chat blocks in myapp.rb to receive and respond to chat messages.
# myapp.rb
require 'rubygems'
require 'agent_xmpp'
chat do
params[:body].reverse
end
If the chat block returns a String a response will be sent to the message sender.
== Send Chat Messages
message(:to=>'thatapp@a-place.com/onahost', :body=>"Hello from #{AgentXmpp.jid.to_s} at " + Time.now.to_s)
== Roster
Roster groups may be specified in agent_xmpp.yml.
jid: myapp@nowhere.com
password: pass
roster:
-
jid:you@home.com
groups: [good group, owners]
-
jid: someone@somewhere.com
groups: [bad group]
Agent XMPP will update the roster maintained on the XMPP server to be consistent with to the roster specified in agent_xmpp.yml.
== Routing Priority
The routing priority may be configured in agent_xmpp.yml. The default value is 1. Valid values are between -127 and 128.
See http://xmpp.org/rfcs/rfc3921.html for a details.
jid: myapp@nowhere.com
password: pass
priority: 128
roster:
-
jid:you@home.com
groups: [good group, owners]
== Major Event Callbacks
Agent XMPP provides callbacks for applications to respond to major events that occur during execution.
# application starting
before_start{}
# connected to server
after_connected{|connection|}
# client restarts when disconnected form server
restarting_client{|connection|}
# a pubsub node was discovered at service
discovered_pubsub_node{|service, node|}
# command nodes were discovered at jid
discovered_command_nodes{|jid, nodes|}
# a presence message of status :available or :unavailable was received from jid
received_presence{|from, status|}
== Authentication
== Development with XMPP Clients
Ad-Hoc Commands, jabber:x:data Forms nor Service Discovery are widely supported by XMPP clients and I have not found
a client that supports Publish-Subscribe. Gajim http://www.gajim.org provides support for Ad-Hoc Commands and jabber:x:data Forms. Service Discovery,
which is useful for Publish-Subscibe development, is supported by Gajim, but Psi http://psi-im.org provides a much better
implementation. Both Gajim and Psi provide an interface for manual entry of XML messages. Since Publish-Subscribe is not supported on
the user interface manual entry of messages is required for development. Example messages can be found at http://gist.github.com/160344
== Logging
By default log messages are written to STDOUT. A log file can be specified with the -l option.
ruby mybot.rb -l file.log
The logger can be accessed and configured.
before_start do
AgentXmpp.logger.level = Logger::WARN
end
== Supported XEPs
XEP-0004 jabber:x:data Forms http://xmpp.org/extensions/xep-0004.html
XEP-0030 Service Discovery http://xmpp.org/extensions/xep-0030.html
XEP-0050 Ad-Hoc Commands http://xmpp.org/extensions/xep-0050.html
XEP-0060 Publish Subscribe http://xmpp.org/extensions/xep-0060.html
XEP-0092 Software Version http://xmpp.org/extensions/xep-0092.html
== Message Processing Callbacks
Message processing callbacks are available to applications to extend the message processing work flow. To receive
callbacks a delegate object must be provided that implements the callbacks of interest.
after_connected do |connection|
connection.add_delegate(YourDelegate)
end
=== Connection
did_connect(connection)
did_disconnect(connection)
did_not_connect(connection)
=== Authentication
did_bind(connection)
did_receive_preauthenticate_features(connection)
did_authenticate(connection)
did_not_authenticate(connection)
did_receive_postauthenticate_features(connection)
did_start_session(connection)
=== Presence
did_receive_presence(connection, presence)
did_receive_presence_subscribe(connection, presence)
did_receive_presence_subscribed(connection, presence)
did_receive_presence_unavailable(connection, presence)
did_receive_presence_unsubscribed(connection, presence)
did_receive_presence_error(pipe, presence)
=== Roster
did_receive_roster_result(connection, stanza)
did_receive_roster_set(connection, stanza)
did_receive_roster_item(connection, roster_item)
did_remove_roster_item(connection, roster_item)
did_receive_all_roster_items(connection)
did_receive_update_roster_item_result(connection, response)
did_receive_update_roster_item_error(connection, response)
did_receive_remove_roster_item(connection, response)
did_receive_remove_roster_item_error(connection, response)
=== Service Discovery
did_receive_version_result(connection, version)
did_receive_version_get(connection, request)
did_receive_version_error(connection, error)
did_receive_discoinfo_get(connection, request)
did_receive_discoinfo_result(connection, discoinfo)
did_receive_discoinfo_error(connection, error)
did_receive_discoitems_result(connection, discoitems)
did_receive_discoitems_get(connection, request)
did_receive_discoitems_error(connection, result)
=== Applications
did_receive_command_set(connection, stanza)
did_receive_message_chat(connection, stanza)
did_receive_message_normal(connection, stanza)
did_receive_pubsub_event(connection, event, to, from)
=== PubSub
did_receive_publish_result(connection, result, node)
did_receive_publish_error(connection, result, node)
did_discover_pubsub_service(connection, jid, ident)
did_discover_pubsub_collection(connection, jid, node)
did_discover_pubsub_leaf(connection, jid, node)
did_discover_user_pubsub_root(pipe, pubsub, node)
did_receive_pubsub_subscriptions_result(connection, result)
did_receive_pubsub_subscriptions_error(connection, result)
did_receive_pubsub_affiliations_result(connection, result)
did_receive_pubsub_affiliations_error(connection, result)
did_discover_user_pubsub_root(connection, result)
did_receive_create_node_result(connection, node, result)
did_receive_create_node_error(connection, node, result)
did_receive_delete_node_result(connection, node, result)
did_receive_delete_node_error(connection, node, result)
did_receive_pubsub_subscribe_result(connection, result, node)
did_receive_pubsub_subscribe_error(connection, result, node)
did_receive_pubsub_subscribe_error_item_not_found(connection, result, node)
did_receive_pubsub_unsubscribe_result(connection, result, node)
did_receive_pubsub_unsubscribe_error(connection, result, node)
=== ERRORS
did_receive_unsupported_message(connection, stanza)
== Copyright
Copyright (c) 2009 Troy Stribling. See LICENSE for details.