Skip to content

Willie tutorial, Part 2

Edward powell edited this page Jul 24, 2013 · 6 revisions

Willie Tutorial Part 2: Introduction to module writing

You have Willie up and running. Now it's time to start writing your own modules. This part of the guide will cover how to do some simple modules.

Hello, World!

It's not a programming tutorial without a "Hello, world". We start by editing a file in ~/.willie/modules. If you've run Willie already, this folder should already exist. Willie will find modules in here by default, but you can add other folders for it to look in by modifying your config file.

A "Hello, world" command is very simple in willie. In the folder I mentioned above, make a file called helloworld.py. In it, put the following and save:

import willie
@willie.module.commands('helloworld')
def helloworld(bot, trigger):
    bot.say('Hello, world!')

The first line imports the willie library. In this module, we only really need this for the next line. This is called a decorator, and associates the command "helloworld" with the function that comes right after it. A command in Willie is triggered when a line said in a channel starts with a period, followed by the command (the prefix can be changed in the config file).

The next line defines a function, which takes two arguments. This is the signature you'll see in nearly every function in Willie modules. The first argument is usually called bot, and it represents the Willie instance you're running. It includes functions like msg and say, which we used above, that make the bot interact with the IRC network. It also includes access to the database and configuration, which will be discussed later. The second argument is usually called trigger, and gives you access to information about the line which "triggered" the "callable", which is what we call the functions that the bot can call.

The last line does exactly as you'd expect; it says "Hello, world!" in the channel where the command was triggered. In other words, if someone says .helloworld, Willie will say Hello, world!.

As a side note, willie.module also has a commands decorator, which you can use to give a callable more than one command. For example, @willie.module.commands('helloworld', 'hiworld') would make this get triggered by either .helloworld or .hiworld.

Simple arguments

Let's take a look at another short example:

from willie import module

@module.commands('echo')
def echo(bot, trigger):
    bot.reply(trigger.group(2))

There are a few new concepts here. One is that I've imported the decorator a bit differently. This is pretty standard in Python. You could also do from willie.module import command, and then just do @command for the decorator. They all work the same way.

The next new thing is willie.reply. This is the same as .say, but it puts the name of the person who triggered it at the beginning. Next, we have trigger.group(2). We'll go more into groups in the next section, but for now you just need to know that, for commands like this, group 1 is the command (echo in this case), and group 2 is everything after it.

So if I say .echo Spam and eggs, Willie will respond Embolalia, Spam and eggs.

Regex Rules

Regular expressions, or regexes, are incredibly powerful tools for matching patterns in text. I'll explain the regexes I use in this section, but you should probably check out a quick guide to them before continuing. This page gives you a good quick overview, and the same site has a reference sheet which comes in handy. This site has a tool to test regexes, and show you what's matching which part of the pattern.

To make a callable trigger on a rule, use the @willie.module.rule decorator. It takes a string with a regex in it, and matches that against the lines it sees. Here's an example:

from willie import module

@module.rule('hello!?')
def hi(bot, trigger):
    bot.say('Hi, ' + trigger.nick)

The rule, hello!?, matches the word "hello", possibly followed by an exclaimation point. So if someone says either "hello" or "hello!", this rule will match, and this function will be called. Willie will then say Hi, Embolalia, where "Embolalia" is the nickname of whoever triggered the callable ( trigger.nick gives you the triggerer's nickname).

A trick you might want to keep in mind is Python's "raw strings". As you'll shortly find, regular expressions contain quite a few backslashes, which can sometimes lead to unexpected results. Putting the letter r before a string means Python interprets that string exactly as it looks. So the string r'\n', for example, is actually a backslash and then an n, and not a newline character.

Nickname rules

Ignore this section for now. It covers stuff that's new and isn't entirely set in stone yet.

Another decorator that takes rules, @willie.module.nickname_command, is provided for convenience. This is basically the same as a regular rule, but it prefixes it with the name of the bot, followed by either a comma or a colon.

So, for example, if the above command had instead had the decorator @module.nickname_command('hello!?'), and if the bot were running with the nickname "Willie", it would have matched on Willie, hello, Willie: hello!, etc.

Regex groups

Using groups in your regular expressions is very helpful. For example, if you wanted to match a NANP phone number, you could use the regular expression \D?(\d{3})\s?\D(\d{3})\D(\d{4}). Going through this piece by piece, we have \D (which matches anything that isn't a digit 0-9) followed by a question mark (which makes it optional). Then we have a group which matches exactly three digits (\d is any digit 0-9, and {3} means to repeat the previous thing three times). Then we have \s, which matches any space character, followed by a question mark to make it optional. Then another non-digit, another 3 digits, another non-digit, and then another four digits. This is a bit liberal, in that it will accept not just normally formatted numbers like (614)555-1234, but also some weird stuff like !614* 555+1234, and it doesn't match numbers given without area codes. Finding a perfect regex for that is left as a reader exercise.

If this were assigned as a rule to a callable, any line which started with a NANP phone number would be matched. (You could add .*, which means any number of any characters, to the beginning to make it match if the number is anywhere in the line.) To get the pieces of the number out, you could use trigger.group(1), trigger.group(2), and trigger.group(3). Or, if for example you wanted to put it into a more common format, you could do '(%s) %s-%s' % trigger.groups(), which will give you (for example) (614) 555-1234.

Documentation

The final basic thing to know is how to document your code in a way Willie can use. This is done using Python's "docstrings". When a string is put immediately below a variable or function, or at the top of a file, without being assigned to anything else, it becomes that variable/function/file's docstring. The first one you'll want to do is one for your file. You should include a brief description of what the functions in the file do, and information about the copyright and licensing on the file. Most of the modules that come with Willie have one that looks something like this:

"""
sample.py - A sample Willie module
Copyright 2013, Edward Powell - embolalia.net
Licensed under the Eiffel Forum License 2.

http://willie.dfbta.net
"""

The triple quotes are Python's way of having a string across multiple lines. Yours doesn't have to look exactly like this, of course, but it'll give you a general idea of what to include.

You should also put a docstring on all of the callables you have. This should be a short message describing what the callable does. This is what Willie will reply with when you use the .help command on something.

There is also a @willie.module.example decorator, which you can give a string containing an example of a valid command. An example of an example might be .t America/New_York for the time command.