An python-based IRC bot using Twisted. Original inspiration came from [thepeopleseason/olga](https://github.com/thepeopleseason/olga)Why re-implement another bot? Because olga is written in perl, and I wanted something a bit more sane to look at.

## Requirements

All requirements for helga are listed in ``requirements.txt``. However, there is a singleexternal requirement, and that is MongoDB. You don't need it, per se, but many of the includedplugins use MongoDB for storing consistent state between restarts.

Once you have performed the above steps, there will be a ``helga`` executableplaced in the ``bin`` dir of your virtualenv. Run helga by calling this:

$ /path/to/venv/bin/helga

Note that this uses the default settings file, ``helga.settings`` to start. You can, and should, useyour own custom setttings. This file must a be an importable python file on $PYTHONPATH. To run helgawith your custom settings file, set an environment variable ``HELGA_SETTINGS`` to be a python import path:

$ export HELGA_SETTINGS=path.to.mysettings

This will preserve any defaults in ``helga.settings``, but you can override at will.

### Local Development

The included Vagrantfile will let you spin up a VM to run both MongoDB and an IRC serverfor local development. Once you've followed the previous instructions for installing helga,simply ``vagrant up``. This will forward host ports 6667 (irc) and 27017 (mongo) to the guest.At this point, simply runing ``helga`` from the command line will connect to this VM.

## Plugins

### Overview

Helga supports plugins outside of the core source code. Plugins have a minimal API, but thereare some basic rules that should be followed. All core plugin implementations can be foundin ``helga.plugins.core``. The basic requirement for plugins is that they have a ``process``attribute that is a callable and determines if the plugin should handle a message, anda ``run`` method that actually performs the legwork of what the plugin should do. By convention,the ``process`` method should accept four arguments:

- **client**: an instance of ``helga.comm.Client``- **channel**: the channel on which the message was received- **nick**: the current nick of the message sender- **message**: the message string itself

The ``run`` is a bit different as it is up to the plugin implementation itself to decide whatarguments are necessary to generate a response. This method should be called by ``process`` andshould return one of:

- None or empty string, if no response is to be sent over IRC- Non-empty string for a single line response- List of strings for multiline responses

Really, as long as you follow the above conventions, you can write plugins however you wish.However, you should try to keep plugins simple and use the included decorators ``command``,``match``, and ``preprocessor`` (explained later). However, if you prefer writing a pluginas a class, you can subclass the included ``Plugin`` base class, provided you have followedthe above rules. Here is a simple example:

**NOTE** the previous example is not the preferred way. You should use the includeddecorators instead (shown below).

### Plugin Types

For the most part, there are two main types of plugins: commands and matches. Commands are pluginsthat require a user to specifically ask for helga to perform some action. For example,``helga haiku`` or ``helga google something to search``. Matches are on the other hand areintended to be autoresponders that give some extra meaning or context to what a user has said.For example, if helga matches for a string "foo":

<sduncan> i'm talking about foo in this message <helga> sduncan is talking about foo

For the sake of simplicity, there are two convenient decorators for authoring these typesof plugins (which is usually the case). For example:

You may notice in the above example that each decorated function accepts different arguments.For commands, there are two additional arguments ``cmd`` and ``args``. The former is the parsedcommand that was used to run the method (which could be "foo" in the above case, or the alias"foobar"). The latter is a list of whitespace delimited strings that follow the parsed commend.For example ``helga foo a b c`` would mean the args param would be ``['a', 'b', 'c']``.

For the match plugin, the single additional argument is ``matches`` which is for the most part,the result of ``re.findall``. However, the ``@match`` decorator accepts a callable in place ofa regex string. This callable should accept one argument: the message being processed. It shouldreturn a value that can be evaluated for truthiness and will be passed to the decorated functionas the ``matches`` parameter.

### Preprocessors

Plugins can also be message preprocessors. These are callables that may perform some modificationon an incoming message prior to that message being delivered to any plugins. Preprocessors shouldaccept arguments (in order) for ``client``, ``channel``, ``nick``, and ``message`` and shouldreturn a three-tuple consisting of (in order) ``channel``, ``nick``, and ``message``. To declarea function as a preprocessor, a convenient decorator can be used:

Some plugins do both matching and act as a command. For this reason, plugin decorators are chainable.However, remember that different plugin types expect decorated functions to accept different arguments.It is best to accept ``*args`` for these:

You can control the priority in which a plugin is run. Note though, that preprocessors will alwaysrun first. A priority value should be an integer value. There are no limits or bounds for this value,but know that a higher value will mean a higher priority. If you are writing ``Plugin`` subclassstyle plugins, you will need to set a ``priority`` attribute of your object. This is done automaticallyif you call ``super(MyClass, self).__init__(priority=some_value)`` in your class's ``__init__``.

However, if you are using the preferred decorator style for writing plugins, you can supply a ``priority``keyword argument to the decorator:

```pythonfrom helga import command, match, preprocessor

@preprocessor(priority=10)def foo_preprocess(*args): pass

@command('foo', priority=20)def foo_command(*args): pass

@match(r'foo', priority=30)def foo_match(*args): pass```

For convenience, there are constants that can be used for setting priorities:

Also, each decorator/plugin type has its own default value for priority:

- Preprocessors have default priority of ``PRIORITY_NORMAL``- Commands have default priority of ``PRIORITY_NORMAL``- Matches have default priority of ``PRIORITY_LOW``

### Publishing plugins

Helga uses setuptools entry points for plugin loading. Once you've written a plugin you wish to use,you will need to make sure your python package's setup.py contains an entry_point under the groupname ``helga_plugins``. For example:

Note that if you are using decorated function for a plugin, you will want to specify the method namefor your entry point, i.e. ``mylib.mymodule:myfn``.

### Webhooks

As of helga version 1.3, there is an included plugin for exposing an HTTP server to support webhooks.This might be useful if you need to have a public facing HTTP service that you would like to use toperform some sort of announcement on a particular channel. This is also very extensible and should allowyou to create new webhooks in a very similar way plugins are created. This plugin is enabled by defaultand requires two settings: ``WEBHOOKS_PORT`` and ``WEBHOOKS_CREDENTIALS``. The former is of course theport on which to run this service. The latter should be a list of tuples in the form of (username, password).These are used to perform HTTP basic authentication on any webhook that requires it.

Webhook plugins work by declaring routes. This will not only feel similar to helga's decorator styleplugins, but it will also feel very similar to anyone who has used something like Flask. There are twoprimary decorators you will need to get started: ``route``, which declares a function as a route endpoint,and ``authenticated``, which ensures that the route function cannot be called without proper HTTP basicauthentication. Both of these can be imported from ``helga.plugins.webhooks``. For example:

@route('/bar', methods=['POST'])def bar(request, irc_client): # This will not require auth, and will only accept POST pass```

NOTE: For authenticated routes, you MUST specify ``@authenticated`` as the first decorator. This may bechanged in the future.

The route decorator accepts two arguments: 1) a path regular expression and 2) an optional list ofHTTP methods to accept. If you do not specify a list of HTTP methods, only GET requests will be served.All regex paths must be named groups and they will be passed as keyword arguments.

To register a new webhook plugin, you must declare an entry_point much in the same way normal pluginsare done. However, the entry_point group name is ``helga_webhooks``. For example: