Ibid Plugin Tutorial — Ibid v0.1.1 documentation

### Navigation

- index
- modules |
- next |
- previous |
- Ibid v0.1.1 documentation »

# Ibid Plugin Tutorial

This will guide you through the process of creating an Ibid plugin so you can add your own features.

## Getting Started

### Install an Ibid

Before we can write a plugin, we need a working base Ibid install.

### The Testing Environment

Rather than working on a live bot and having to reload modules a lot, when developing for Ibid, we usually use `ibid-plugin`, a minimal, fast, testing environment.

It looks like this:

    user@box ~/botdir $ ibid-plugin
    ... Messages about loading plugins
    Query: hello
    Response: Huh?
    Query: help
    Response: I can help you with: looking things up.
    Ask me "help me with ..." for more details.
    Query: help me with looking things up
    Response: I use the following features for looking things up: help
    Ask me "how do I use ..." for more details.

To exit, press `Control-C` or `Control-D`.

As you can see, there is almost nothing loaded. It can’t even respond to “hello”, the code for that is in the factoid module. If you want to load the factoid module, you can tell `ibid-plugin` to load it on startup, by adding it as a parameter:

    user@box ~/botdir $ ibid-plugin factoid
    ... Messages about loading plugins
    Query: hi
    Response: good morning

Well, actually there are some administrative functions, which don’t show up in the overall help. You could have asked the bot to “load factoid”:

    Query: help admin
    Response: I use the following features for administrative functions:
    config, core, die, help, plugins, sources and version
    Ask me "how do I use ..." for more details.
    Query: how do I use plugins
    Response: Lists, loads and unloads plugins. You can use it like this:
      list plugins
      (load|unload|reload) <plugin|processor>
    Query: load factoid
    DEBUG:core.reloader:Loading Processor: factoid.Forget
    DEBUG:core.reloader:Loading Processor: factoid.Get
    DEBUG:core.reloader:Loading Processor: factoid.Modify
    DEBUG:core.reloader:Loading Processor: factoid.Search
    DEBUG:core.reloader:Loading Processor: factoid.Set
    DEBUG:core.reloader:Loading Processor: factoid.StaticFactoid
    DEBUG:core.reloader:Loading Processor: factoid.Utils
    DEBUG:core.reloader:Loaded factoid plugin
    Response: factoid reloaded
    Query: hi
    Response: howsit

Try talking to it too fast, it’ll start ignoring you. This makes sense for a real chat channel, but not debugging. You can tell the ignorer not to load by adding it as a parameter followed by `-`:

    user@box ~/botdir $ ibid-plugin factoid core.Ignore-

If you want all the normal modules loaded, you can add the `-c` option, but it’ll take quite a bit longer to start up:

    user@box ~/botdir $ ibid-plugin -c
    ... Screenfulls of messages
    Query: hello
    Response: sup

Play with that a bit. It isn’t exactly the same as a full bot, there are a few things that won’t work, but it’s good enough for testing. Some examples:

- Karma, because it’s disabled for private conversations by default. You can switch to public mode with `-p`.
- The games, because they require some advanced Twisted functionality (as well as other channel members).

## Ibid Theory

Ibid is divided into two main parts (excluding the Ibid core code): Sources and Plugins.

The sources speak IRC, Jabber, e-Mail, etc. There is one source for each network that the bot is connected to. When someone says something in an IRC channel, the IRC source for that network will create an Event. The event is passed to the plugins, which each take a turn to look at it and decide if they want to do anything. If a plugin decides to reply, the event is sent back to the source to dispatch the reply.

Events are also used for, private messages from users to the bot, people joining and leaving channels, etc. but most plugins don’t need to deal with anything except message events, directed to the bot.

Ibid comes with some plugins for pre- and post-processing of events (such as logging), and some for features.

## Plugin Writing Time

### Processors and Handlers

Let’s see what that looks like in practice. Here’s a simple hello world plugin. Create a directory called `ibid/plugins` in the botdir. In that directory, create a file called `tutorial.py` with the following contents:

    from ibid.plugins import Processor, handler

    class HelloWorld(Processor):
       @handler
       def hello(self, event):
          event.addresponse(u'Hello World!')

A plugin can contain multiple Processors. Each one is a self-contained part of the event handling chain. It can register an interest in certain types of event, or a specific place in the chain, but for most plugins the defaults are sufficient.

Inside the processor, any functions decorated with `@handler` will get a chance to look at the event. If it choses to add a response to the event, the response will be returned to the user.

Note

Ibid uses unicode strings and to catch mistakes, you’ll get a warning if you pass a normal string as a response, so try to get in the habit of using unicode.

Test it out, anything you say to the bot should provoke a “Hello World!” response:

    user@box ~/botdir $ ibid-plugin tutorial
    ... Messages about loading plugins
    Query: hello
    Response: Hello World!

Now, you could include code inside your handler to determine if you want to reply to a message or not, but must of the time you are after messages that look like something particular, so we have another decorator, `@match()`, to help you:

    from ibid.plugins import Processor, match

    class HelloWorld(Processor):
        @match(r'^hello$')
        def hello(self, event):
            event.addresponse(u'Hello World!')

Match takes a regular expression as a parameter, and will only run your handler function if the regex matches the event’s message. In this case, it’ll only fire if you say “hello”. It’ll ignore trailing punctuation and whitespace, as that’s removed by the `core.Strip` plugin.

### Match Groups

Time for a more complex example, a multiple dice roller, you can add it as another Processor in your tutorial plugin:

    from random import randint

    from ibid.plugins import Processor, match
    from ibid.utils import human_join

    class Dice(Processor):
        @match(r'^roll\s+(\d+)\s+dic?e$')
        def multithrow(self, event, number):
            number = int(number)
            throws = [unicode(randint(1, 6)) for i in range(number)]
            event.addresponse(u'I threw %s', human_join(throws))

If you still have an `ibid-plugin` open you can “reload tutorial” to reload your plugin.

Any match groups you put in the regex will be passed to the handler as arguments, in this case the number of dice to throw. If you want brackets without creating a match group, you can use the non-grouping syntax `(?: )`.

`ibid.utils` contains many handy helper functions. `human_join()` is the equivalent of `u', '.join()`, with an “and” before the last item.

`addresponse()` takes a second argument for string substitution. If you want to substitute multiple items, use the dict syntax:

    event.addresponse(u'Nobody %(verb)s the %(noun)s!', {
        'verb': u'expects',
        'noun': u'Spanish Inquisition',
    })

### Documentation

At the moment you’ll see that your plugin doesn’t appear in the help system. You can fix that with a little more code:

    from random import randint

    from ibid.plugins import Processor, match
    from ibid.utils import human_join

    features = {}

    features['dice'] = {
        'description': u'Throws multiple dice',
        'categories': ('fun',),
    }

    class Dice(Processor):
        usage = u'roll <number> dice'

        feature = ('dice',)

        @match(r'^roll\s+(\d+)\s+dic?e$')
        def multithrow(self, event, number):
            number = int(number)
            throws = [unicode(randint(1, 6)) for i in range(number)]
            event.addresponse(u'I threw %s', human_join(throws))

The module-level `features` dict specifies descriptions for features (given with the usage description) and categories to place the feature in. You can find a list of available categories in `ibid.categories` and if necessary add a category to it from your module.

The Processor can be linked to a feature by specifying it in the feature attribute. Usage for the Processor’s functions (in BNF) goes in a usage attribute. “reload tutorial” and you should see “dice” appear in the features for “fun stuff”.

## Configuration

Ibid has a configuration system that may be useful for your plugin. Configuration values can be set at runtime or by editing `ibid.ini`.

Let’s make the number of dice sides be configurable:

    from random import randint

    from ibid.config import IntOption
    from ibid.plugins import Processor, match
    from ibid.utils import human_join

    class Dice(Processor):
        sides = IntOption('sides', 'Number of sides to each die', 6)

        @match(r'^roll\s+(\d+)\s+dic?e$')
        def multithrow(self, event, number):
            number = int(number)
            throws = [unicode(randint(1, self.sides)) for i in range(number)]
            event.addresponse(u'I threw %s', human_join(throws))

`IntOption()` creates a configuration value called `plugins.tutorial.sides` with a default value of 6. There are also configuration helpers for other data types.

If you merge the following into your `ibid.ini`, you can change to 21 sided dice:

    [plugins]
       [[tutorial]]
          sides = 21

## Style

Now that you’ve got all the basics, here are some other things you should know about writing Ibid plugins.

### Error Handling

You might have noticed that we haven’t said anything about error handling. That was intentional. All exceptions in plugins are caught at the dispatcher level, and an appropriate response will be returned to the user, as well as tracebacks logged. The only time you should worry about handling errors is if you can recover gracefully or you want to return a specific response (such as an explanation).

### Responses

The general Ibid style is that the bot should be something people can relate to, not too mechanical. So many Ibid responses are playful and maybe a little snarky. Also, many responses aren’t static, but rather chosen from a list of 3 or 4 at random (`random.choice()` is good for that).

## Next Steps

That’s it, you are now more than able to write your own Ibid plugins. Please send us anything you write, it may be useful for other people too.

We wished there was more documentation we could point you at, to help you, but it hasn’t been written yet. So, read some modules to see what’s there, and stick your nose in our IRC channel for help.

### Navigation

- index
- modules |
- next |
- previous |
- Ibid v0.1.1 documentation »