Writing event plugins

Event plugins are Omnipresence’s primary extension mechanism. This page details how to add new functionality by creating your own plugins. For information on the built-in plugins shipped with the Omnipresence distribution, see Built-in plugins.

class omnipresence.plugin.EventPlugin(bot)[source]

A container for callbacks that Omnipresence fires when IRC messages are received.

Omnipresence locates event plugin classes by their Python module and class names, as detailed in Configuration. For convenience, a plugin key containing only a module name implies the class name Default. The following code in the top-level module foo therefore creates event plugins named foo (or foo/Default) and foo/Other:

from omnipresence.plugin import EventPlugin

class Default(EventPlugin):
    pass

class Other(EventPlugin):
    pass

When a message is received, Omnipresence looks for a plugin method named on_ followed by the name of the MessageType, such as on_privmsg. If one exists, it is called with a Message object as the sole parameter. For example, the following plugin sends a private message to greet incoming channel users:

class Default(EventPlugin):
    def on_join(self, message):
        greeting = 'Hello, {}!'.format(message.actor.nick)
        message.connection.msg(message.venue, greeting)

Callbacks that need to execute blocking code can return a Twisted Deferred object:

class Default(EventPlugin):
    def on_privmsg(self, message):
        d = some_deferred_task()
        d.addCallback(lambda s: message.connection.msg(message.venue, s))
        return d

By default, callbacks are not fired for outgoing events generated by bot messages, in order to reduce the probability of accidental response loops. To change this behavior, set the outgoing attribute of a callback method to True. Inside the callback, each message’s outgoing attribute can be used to determine its direction of transit:

from twisted.python import log

class Default(EventPlugin):
    def on_privmsg(self, message):
        direction = 'Outgoing' if message.outgoing else 'Incoming'
        log.msg('%s message: %r' % (direction, message))
    on_privmsg.outgoing = True

Note

Since most servers echo joins, parts, and quits back to clients, callbacks registered for these actions will always fire once on bot actions, twice if enabled for outgoing messages. You may wish to compare the message’s actor and the connection’s nickname attributes to distinguish bot actions in these cases:

class Default(EventPlugin):
    def on_join(self, message):
        if message.actor.matches(message.connection.nickname):
            the_bot_joined()
        else:
            someone_else_joined()

Command replies

Any plugin with an on_command callback can be assigned one or more keywords in its configuration. Unlike most other callbacks, whose return values are ignored, any value returned from on_command becomes the command reply, and is sent as either a channel message addressed to the command target or a private notice depending on how the command was invoked. A command reply may take one of the following forms:

  • None, in which case no reply is shown to the target user, not even a “no results” message. This is useful for commands that have other visible side effects, such as changing the channel topic or mode.
  • A byte or Unicode string. Long strings are broken into chunks of up to CHUNK_LENGTH bytes and treated as a sequence.
  • A sequence of strings. Any reply strings containing more than MAX_REPLY_LENGTH bytes are truncated on display.
  • An iterator yielding either strings or Deferred objects that yield strings. Long reply strings are truncated as with sequence replies.
  • A Deferred object yielding any of the above.

Unless the reply is None, the first reply is immediately shown to the target user, and any remaining replies are placed in a buffer for later retrieval using the .more command. Newlines inside replies are displayed as a slash surrounded by spaces.

The following example plugin implements an infinite counter:

from itertools import count
from omnipresence.plugin import EventPlugin

class Default(EventPlugin):
    def on_command(self, msg):
        return count()

Error reporting

By default, if an error occurs inside an on_command callback, Omnipresence replies with a generic error message and logs the full traceback to the twistd log file. This behavior can be changed with the show_errors configuration option. If you wish to always show detailed information for an error, raise a UserVisibleError:

exception omnipresence.plugin.UserVisibleError(*args)[source]

Raise this inside a command callback if you need to return an error message to the user, regardless of whether or not the show_errors configuration option is enabled. Errors are always given as replies to the invoking user, even if command redirection is requested.

Help strings

To provide a help string for the .help command, return it from the on_cmdhelp callback. The incoming Message‘s content attribute contains any additional arguments to .help, allowing help subtopics:

def on_cmdhelp(self, msg):
    if msg.content == 'detailed':
        return '\x02detailed\x02 - Show more information.'
    if msg.content == 'terse':
        return '\x02terse\x02 - Show less information.'
    return ('[\x02detailed\x02|\x02terse\x02] - Do some stuff. '
            'For more details, see help for \x02{}\x02 \x1Faction\x1F.'
            .format(msg.subaction))

Note that the command keyword is automatically prepended to the help string on display.

Omnipresence’s built-in plugins provide help strings of the form usage - Help text., where the usage string is formatted as follows:

  • Strings to be typed literally by the user are bolded using \x02.
  • Strings representing command arguments are underlined using \x1F.
  • Optional components are surrounded by brackets ([optional]).
  • Alternatives are separated by vertical bars (this|that|other).

Configuration options

Use the Message.settings wrapper to look up the value of a configuration variable:

def on_command(self, msg):
    return msg.settings.get('foo.bar', 'default value')

The value of a configuration variable may change while the bot is running (see Reloading). If a plugin needs to update its internal state on these changes, it can do so by defining a configure callback, which is passed the current bot settings:

def configure(self, settings):
    self.process(settings.get('foo.bar'))

By convention, plugin configuration variable names should share a common prefix ending with a period (.). Undotted names are reserved for Omnipresence core variables.

Command base classes

Omnipresence provides classes for common types of command plugins. As with the standard EventPlugin class, they are intended to be subclassed, not instantiated.

class omnipresence.plugin.SubcommandEventPlugin(bot)[source]

A base class for command plugins that invoke subcommands given in the first argument by invoking one of the following methods:

  1. on_empty_subcommand(msg), if no arguments are present. The default implementation raises a UserVisibleError asking the user to provide a valid subcommand.
  2. on_subcommand_KEYWORD(msg, remaining_args), if such a method exists.
  3. Otherwise, on_invalid_subcommand(msg, keyword, remaining_args), which by default raises an “unrecognized command” UserVisibleError.

on_cmdhelp is similarly delegated to on_subcmdhelp methods:

  1. on_empty_subcmdhelp(msg), if no arguments are present. The default implementation lists all available subcommands.
  2. on_subcmdhelp_KEYWORD(msg), if such a method exists.
  3. Otherwise, on_invalid_subcmdhelp(msg, keyword), which by default simply calls on_empty_subcmdhelp.

As with on_cmdhelp, the subcommand keyword is automatically added to the help string, after the containing command’s keyword and before the rest of the string.

Writing tests

...