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 modulefoo
therefore creates event plugins namedfoo
(orfoo/Default
) andfoo/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 theMessageType
, such ason_privmsg
. If one exists, it is called with aMessage
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 toTrue
. Inside the callback, each message’soutgoing
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’snickname
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:
on_empty_subcommand(msg)
, if no arguments are present. The default implementation raises aUserVisibleError
asking the user to provide a valid subcommand.on_subcommand_KEYWORD(msg, remaining_args)
, if such a method exists.- Otherwise,
on_invalid_subcommand(msg, keyword, remaining_args)
, which by default raises an “unrecognized command”UserVisibleError
.
on_cmdhelp
is similarly delegated toon_subcmdhelp
methods:on_empty_subcmdhelp(msg)
, if no arguments are present. The default implementation lists all available subcommands.on_subcmdhelp_KEYWORD(msg)
, if such a method exists.- Otherwise,
on_invalid_subcmdhelp(msg, keyword)
, which by default simply callson_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¶
...