Writing a Halibot Module
Wed 22 April 2015 by Eric RichterFor those who are unfamiliar with Halibot, it is a modular XMPP Bot that I wrote in Python. It can be found on Github here, with slightly more information on how it functions. I will go into more detail maybe in a future post on the design, but this post will focus on developing a simple module to demonstrate its simplicity, and highlight some of its functionality. A working knowledge of Python 3 is recommended for this, as well as familiarity with JSON.
Module Structure
Directory
Before going into code, first allow me to explain the structure of a module, and the modules directory.
Halibot expects all modules to be in a specific modules/ directory, and each in their own subdirectory.
So, to start this tutorial, create a subdirectory inside of modules called hello.
$ cd modules/
$ mkdir hello
module.json
Inside of this directory, Halibot expects there to be a file called module.json.
This file will defines what files provide what module.
See, these directories are actually a "Module Group", as it can provide more than one module to Halibot, in different files.
The following is a bare bones module.json file:
{
"modules": [
{
"name": "Hello",
"path": "hello.py"
}
]
}
Things to point out here:
the modules key refers to a list of objects corresponding to each module this group exposes.
In each of these objects, a name and path key is required.
The name key should refer to the name of the module that will appear in the modules list, and by convention should also correspond to the object name that inherits from the XMPPModule class.
path should be the path to the python source file that contains the XMPPModule-inheriting class.
So, the above example file states that there is only one module provided in this directory, called Hello and contained in hello.py.
It is important to point out that the path is relative to the module's directory, so the actual full path is modules/hello/hello.py.
Other Files
If you want to seperate your module code from other logic, you use multiple files and import them like any other Python source. An example of this can be found in the Halibot-module implementation of the IRC game Acronyms here.
Module Code
Halibot modules have a small amount of boilerplate require by design.
Halibot expects an object that implements the XMPPModule class, and that's about it.
So the required boilerplate is the following:
from module import XMPPModule
class Hello(XMPPModule):
pass
To start receiving messages, simply implement the method handleMessage in this class.
Consider the following sample code:
class Hello(XMPPModule):
def handleMessage(self, msg):
self.xmpp.reply(msg, "You said " + msg["body"])
A few important things to point out from this example:
- msg argument - this is an unmodified message object generated from SleekXMPP. It is simply a dictionary, and refer to their documentation for usage.
- self.xmpp - this is a reference that is part of the XMPPModule class that refers to the bot object itself. Use this to interact with the bot, including sending messages.
- reply - A convenient method implemented in the bot to reply to a message object. Takes in a message object, and a string to send.
This sample code will work for both single user chat and multi-user chats (MUCs), because handleMessage is called by default by both handlers.
This makes things simple if both cases should be handled similarly.
reply was implemented with the same thought, if the message object came from a MUC, then it will send the string in the second argument back to the same MUC.
Back to the example: What does this module do?
Every time a user sends a message to either the bot individually, or in a MUC that the bot is it, the bot will respond with "You said " followed by a repeat of the message.
Anything you do in the handleMessage method is just Python, so you could easily implement a command structure by checking the content of the message, for example.
Let's say we only want to respond if someone says "!hello".
from module import XMPPModule
import halutils
class Hello(XMPPModule):
def handleMessage(self, msg):
cmd, args = halutils.splitArgList(msg)
if cmd == "!hello":
self.xmpp.reply(msg, "Hello, {}!".format(halutils.getSensibleName(msg)))
Wait, what's this halutils thing?
It is a library provided to make managing the message objects simpler, and a bit more abstract.
So, splitArgList will return a tuple of the first word (assuming separated by space), and a list of the rest of the words.
In the previous example, receiving a message "!hello world foo" would return the following tuple: ("!hello",["world", "foo"]).
Next, we compare against the string, and conditionally reply if the command (first word) is "!hello".
This time, we reply with the senders name, as provided by getSensibleName.
This function returns a string with exactly that: a sensible name.
If the message was received from a MUC, then it will return that user's nickname, otherwise the JID without the "@xmpp.server.net" bit.
Conclusion
That's it!
Module development is pretty simple, and does not require much extra effort.
In most cases, only a little extra work is required, and the XMPPModule and halutils library does most of the heavy lifting.
I may continue this series in the future with more advanced module development, but for the most part, the module.py file in the source is fairly self-documenting.
Considering taking a look at some of the reference modules here other examples of what can be done with the API.
SSL and XMPP
So we have an XMPP server, and it even communicates with others. But what happens when we get super paranoid, and want to make sure nobody can snoop on the network's traffic? The answer is SSL! Due the amount of setup for this one, I am going to pass ...
read moreConfiguring XMPP Federation (s2s)
Continuing my series on XMPP server configuration, this post will be on setting up server-to-server message passing using Prosody. It is assumed that there are two servers set up and configured in a similar fashion to the previous post.
About s2s
Well, like the last post, first allow me to ...
read moreSetting up an XMPP Server
This will be the first in a series of posts about XMPP, servers, clients, and other cool stuff. So to start this off, I will walk through the steps I took to set up COSI's XMPP server. There is a lot of XMPP server software available, and I will ...
read more