DaZeus  2.0
 All Classes Namespaces Files Functions Variables Enumerations Enumerator Friends Macros
DaZeus 2 Plugin Protocol

Introduction

The DaZeus 2 plugin protocol links the core bot with the installed plugins, and is used to communicate events and their responses. Also, it exposes DaZeus' own configuration and database APIs, so plugins can easily attach without requiring configuration of their own.

Bindings for this protocol are available for several languages. Of course, patches are very welcome for newly implemented bindings! For help with writing bindings, you can look at the logic in existing bindings – see the main page of this documentation to find their source code. Also, to experiment with the protocol without the use of special libraries, the socat or nc tool can be used as follows, where dazeus.sock is the UNIX socket:

  • socat UNIX-CONNECT:dazeus.sock READLINE
  • nc -U dazeus.sock

The protocol was designed as a trade-off between human and machine readability. It is largely based on the JSON standard (http://www.json.org/), with some additions to allow for quicker reading. At the moment, communication is supported over UNIX and TCP sockets.

Overview

Messages in the protocol look like this:

[size in ASCII][JSON data of given size]

For example, a simple message might look like this:

18{"get":"networks"}

Here, the 18 is sent over the network as normal ASCII bytes, i.e. 0x31 0x38. Reading a size stops at the first {. In order to improve human write- and readability, a \n (newline) or \r (carriage return) occuring anywhere outside of this atomic block is ignored. For example, the above message is equal to this:

\n\r18{"get":"networks"}\n\r

Any other bytes before the JSON, other than \n, \r and ASCII digits, are not allowed. The sender must not send them, the receiver must ignore them or abort the session.

The bot will respond to incoming messages in the order the messages were sent, with the exception that if the plugin is subscribed to events, those events may be sent in the middle of a list of responses. For example, communication like this is possible:

Plugin: [Request 1]
Plugin: [Request 2]
Bot: [Response 1]
Bot: [Event]
Bot: [Response 2]

All JSON data must be objects. Requests will have a get or do field (both are synonyms) and may have a params field; responses will have a got or did field, a success field, and other fields depending on the request. If the success field value is false, there will be an error field with a human readable error string.

Events will have an event field and some params, that depend on the event (see the events section).

Requests

There are three types of requests:

  • Get some information, like known networks or joined channels,
  • Do something on IRC, such as saying something or leaving a channel,
  • Accessing DaZeus internals, such as events or the database.

The "get all known networks" request looks like {"get":"networks"}. The response will have the list of networks in the networks field. The "get all joined channels" request will look like {"get":"channels", "params":["the network"]}. If the network is joined, the response will have a channels field with an array value. There is a similar nick command which retrieves our own nickname on a given network.

A message request will look like:

{"do":"message", "params":["network","channel","message"]}

For a CTCP ACTION (/me), use action instead of the first message:

{"do":"action", "params":["network","channel","message"]}

For joining a channel: (same for leaving a channel)

{"do":"join", "params":["network","channel"]}

For doing a WHOIS request: (the results come in through NUMERIC events, see the events section)

{"do":"whois", "params":["network","nick"]}

The "subscribe to events" request will look like:

{"do":"subscribe", "params":["JOINED","MOTD"]}

To unsubscribe, change subscribe into unsubscribe. See the next section for more information.

To subscribe to commands:

{"do":"command", "params":["helloworld"]}

There are several optional parameters, too. See the Commands section for more information.

For all database actions, the action keyword is property, and the specific action is in the first parameter. For example, to retrieve the count property, the counter example Perl script sends:

{"do":"property", "params":["get","examples.counter.count"]}

To set the value:

{"do":"property", "params":["set","examples.counter.count","2"]}

See the section on properties below for more information.

Events

After subscribing, events can be received anytime outside another existing response. The following events exist:

CONNECT, DISCONNECT, JOIN, PART, QUIT, NICK, MODE, TOPIC, INVITE, KICK,
PRIVMSG, NOTICE, CTCP, CTCP_REP, ACTION, NUMERIC, UNKNOWN, WHOIS, NAMES,
PRIVMSG_ME, CTCP_ME, ACTION_ME, PONG

All events will have have an event field set to one of the above strings, and a params field that will contain an array with parameters depending on the event.

Commands

Plugins can subscribe to commands, so that DaZeus can do the decoding, identification and authorization for them. Of those three goals, two are currently implemented – authorization is a work-in-progress.

To register a 'helloworld' command, send:

{"do":"command", "params":["helloworld"]}
{"do":"command", "params":["helloworld", "networkname"]}
{"do":"command", "params":["helloworld", "networkname", true, "sender"]}
{"do":"command", "params":["helloworld", "networkname", false, "receiver"]}

This will cause the DaZeus core, when it receives such a command (which, depending on core configuration, could be by messaging "helloworld" to it or saying something like "!helloworld" or "DaZeus: helloworld" in a channel), to send COMMAND events to the plugin like:

{'event':'COMMAND', 'params':['network', 'sender', 'channel', 'command', 'arguments', 'arg1', 'arg2' ...]}

When a sender filter is given, the core will only forward commands to plugins if the sender is identified to services. If this is unknown at the time the command is received, the core will only forward the command after a WHOIS confirms the user is identified. This is done automatically by the core, but you may need to be aware that this can cause these commands to come in in a different order than they were received from the network.

Properties

Properties can be sent and retrieved over the DaZeus 2 plugin protocol and act as a database for zero-configuration plugins. Using the property set command a property can be stored:

{"do":"property", "params":["set","examples.counter.count","2"]}

After this, the property can be retrieved using property get:

{"do":"property", "params":["get","examples.counter.count"]}

This will result in the following response, after the previous two commands:

{"did":"property", "variable":"examples.counter.count", "value":"2"}

Next to these commands, property unset is available which looks like this:

{"do":"property", "params":["unset","examples.counter.count"]}

Last but not least there is the property keys command, which returns a list of keys in a given namespace:

{"do":"property", "params":["keys","examples.counter"]}

Which could respond:

{"did":"property", "keys":["count"]}

For added functionality, you can look into variable scopes. Using the commands above, variables will be stored and returned "as is" (global scope). But, suppose you want to have a variable especially for a specific IRC channel, or even a specific user. You could use various different names per variable, but scoping allows you to let DaZeus do the work for you.

There are four scopes, in order of specificity: Global scope, network scope, receiver scope and sender scope. When returning the variable, the `closest' variable is returned. For example, if a variable is set on global scope, and overridden on a network scope, variable requests with the same network will return the network scope value; all other requests will return the global scope value.

Scope is given through the scope array field on property requests. Some examples follow:

> {"do":"property", "params":["set","examples.scope.foo", "bar"]}
< {"did":"property", "success":true}
> {"do":"property", "scope":["oftc"], "params":["set","examples.scope.foo", "baz"]}
< {"did":"property", "success":true}
> {"do":"property", "scope":["q"], "params":["get","examples.scope.foo"]}
< {"did":"property", "success":true, "variable":"examples.scope.foo", "value":"bar"}
> {"do":"property", "scope":["oftc"], "params":["get","examples.scope.foo"]}
< {"did":"property", "success":true, "variable":"examples.scope.foo", "value":"baz"}
> {"do":"property", "scope":["oftc"], "params":["unset","examples.scope.foo"]}
< {"did":"property", "success":true}
> {"do":"property", "scope":["oftc"], "params":["get","examples.scope.foo"]}
< {"did":"property", "success":true, "variable":"examples.scope.foo", "value":"bar"}
> {"do":"property", "params":["unset","examples.scope.foo"]}
< {"did":"property", "success":true}
> {"do":"property", "scope":["oftc"], "params":["set","examples.scope.foo", "baz"]}
< {"did":"property", "success":true}
> {"do":"property", "scope":["q"], "params":["get","examples.scope.foo"]}
< {"did":"property", "success":true, "variable":"examples.scope.foo"}