Please visit the project website for more information: http://impybot.sourceforge.net/
IMPyBot (impybot) is a bot (IM automatic response) framework for XMPP protocol. It provides a plug-in system, so that the implementation of a utility is isolated from implementation of the bot. This provides great convenience to manage the utilities without touching the bot itself. The plug-in system is sophisticated that it enables developer to write really large and powerful plug-ins. Meanwhile, it also provides a simple interface such that users with little Python experience can write useful utilities as well.
The figure below illustrates the structure of IMPyBot framework. As shown, the plug-ins are isolated from the bot itself. Plugin developers who are only interested in writing utilities like weather querier or calculators does not need to know the details of the bot at all. What is more important, this provides an easier way to organize and manage all the plugins.
+------------------------------------------------------------+ | | | The Plug-ins | | +--------------------------------------------+ | | | +---------+ +------------+ +------------+ | | | | | weather | | dictionary | | calculator | | | | | +---------+ +------------+ +------------+ | | | +--------------------------------------------+ | | | | | | plug in | | v | | +--------------------------------------------+ | | | The XMPP Bot Framework | | | +--------------------------------------------+ | | | ^ | | | | | | v | | | +--------------------------------------------+ | | | Internet | | | +--------------------------------------------+ | | | | | +------------------------------------------------------------+ Figure 1. Illustration of the IMPyBot Framework
sudo python setup.py install
setup.py installNOTE for Windows Vista and Windows 7 users: you must open the command windows as administrator.
You can also use IMPyBot without installing it to the system location.
run_bot.py -j "username" -p "password"
To write your own utilities (we call it a plugin), simply follow these TWO steps:
Create a text file called my_first_plugin.py
, with codes look
like the following:
Invoke the command line tool to run a bot and tell it where your plugin is:
python -m run_bot -j "jid@server.com" -p "password" -m my_first_plugin.py
Congratulations on your first Instant Messaging Application!
IMPyBot can be invoked from the command line through the application
run_bot.py
. To simply start the bot, use the following command:
python run_bot.py -j "your_id@server.com" -p "your_pswd"
Then the bot will start with the account and password you specified.
For detailed usage of run_bot.py
, invoke it with the -h
options:
python run_bot.py -h
Besides using the command line to configure the bot, system wide and user level configuration files are also accepted. The following table summarizes the name of configure files used on different operation systems.
Windows | Unix/Linux | |
System | None |
/etc/impybotrc |
User | %USERPROFILE%\impybot.ini |
~/.impybotrc |
The configuration file is XML formated and should looks like the following:
1 <impybot>auth
, attributes jid
and password
specifies
the JID and password the bot uses to log on. Attributes server
and port
specifies the alternative IP and port to be used and
are optional.
proxy
specifies the proxy the bot should use. It is also
optional.
plugin
corresponds to the command line option -m
,
and are used to specifies the paths to the modules and packages in
which the plugins are defined. plugin
s are optional.
plugin_stores
corresponds to the command line
arguments. They are used to specify the directories where the plugin
modules and packages are stored. plugin_stores
s are optional.
One would notice that the bot, although having been running, actually will do nothing to the incoming events such as users' chat activities, status changes. This is because, designed with "Keep It Simple, Stupid" (KISS) in mind, the bot itself does nothing except handling the connections. All actions that depend on specific incoming message patterns, status changes, are implemented by the plugins using the module's powerful and easy-to-use plugin system (detailed in the next section). By splitting various functionalities from the implementation of the bot, it is easy to manage these functionalities, it is also easy for developers who are only interested in implementing such functionalities.
Assume that we already have some plugins available in a directory
/path/to/plugin_stores/
. The plugins in this directory include:
weather.py
: a weather query utility.
dict.py
: a dictionary look up utility.
calculator.py
: a calculator utility.
How do we tell the bot to use these plugins when we invoke the bot?
Simply pass plugin_stores
to run_bot.py
:
python run_bot.py -j "jid@srv.com" -p "pswd" /path/to/plugin_stores
You can pass as many plugin stores as you want to run_bot.py
.
Now when some user send a message to jid@srv.com
like:
weather Miami, FL
The bot will invoke the weather plugin, retrieve the weather data for Miami, Florida, and then send it back to the user.
After initialization, the bot looks for the paths of all known plugins, imports their containing modules or packages, and registers them with the specified priorities. When an event (an incoming message, or a status change) comes, the bot invokes the plugins one-by-one in the order specified by the plugins' priorities. Each plugin has a chance to be executed for the event, and the plugin itself has the right to decide whether or not an action should be performed on it. If non of the plugins decides to take an action on the incoming message, the bot will invoke the built-in fallback plugin for the message. By default, it will tell the sender of the message that it cannot understand what he/she is saying.
Note that a plugin, in the case that it takes an action on the incoming message, has the right to tell the bot to stop invoking the plugins with lower priorities, which haven't had a chance to be invoked by the bot. This "stop request" is only valid during this "plugin-execution round" and this event, and is not valid for the following events.
The "stop request" feature is for advanced plugins only and in most cases should not be used.
Most users of IMPyBot might be merely interested in writing their own utilities. In IMPyBot framework, this is done by writing a "plugin".
IMPyBot provides a powerful yet easy-to-use plugin framework. Even some of the bot's built-in functionalities are implemented as built-in plugins. Currently, IMPyBot has three types of plugin classes that can be used, each of which aimed for different purpose. The figure below gives an overview of these plugins and illustrates their relationships.
+--------------------------------------------------------+ | | | +----------------------+ +----------------------+ | | | impybot.SimplePlugin | | impybot.RePlugin | | | +----------------------+ +----------------------+ | | | | | | | | | | v v | | +--------------------------------------------------+ | | | impybot.Plugin | | | +--------------------------------------------------+ | | | +--------------------------------------------------------+ Figure 2. Plugin system inheritance
As shown in Figure 2, impybot.Plugin is the base class for all plugins. It is the most general purpose plugin framework and provides the most flexibility to the plugin developers. impybot.SimplePlugin is a easy-to-use, command-based plugin. The developer simply specifies a sequence of command strings he/she wants to match, and a callback function that will be called when any of the command strings are matched. impybot.RePlugin is more advanced and flexible than SimplePlugin. It is regular expression based. The plugin developer specifies the regular expression pattern he/she wants to match and provides a callback function which will be called on a match.
The use of SimplePlugin is simple. The plugin developer only needs
to derive a class from impybot.SimplePlugin
and sets a few class
attributes and a callback method. Next we will give an example of
using SimplePlugin to echo users' inputs.
As an overview, there are only five things you need to do for writing such a plugin:
impybot
module
impybot.SimplePlugin
command
attribute as a tuple of command strings you want
to match
handle_match()
: the callback method.
impybot.register()
For the above plugin, if "tom.jerry@gmail.com" sends the bot the following instant message:
echo echo arg1 arg 2 hello world!!! 你好 吗?
The bot will reply to him/her with the following message:
(An empty line) arg1 arg2 world!!! 吗?
Now let's go through the codes and gives a more detailed explanation.
The command
attribute defined on line 13 specifies the command
strings that can trigger the callback actions of this plugin. In the
above example, the plugin's callback actions will be triggered if
someone sends the bot a message with any of the strings in attribute
command
appears at the beginning of ANY line.
In the attribute command
, you can use either the str type, or
the unicode type. If only ONE command string are to be used,
command
can be that string in this case: a tuple or a list is
NOT necessary.
There are two cases of matches:
In other words, one of the command strings DIRECTLY followed by non-space characters will not match.
Note that a line with echo
alone still matches. Also note that in
你好 吗?
, the space is NOT an ASCII space. This also counts.
Users from non-English world would be very happy with this feature!
If the input hits a match, then the callback method handle_match() will be called by the bot framework. And the matched parts of the input and the sender's JID will be passed to this method. The interface of this callback method likes like:
def handle_match( self, matched, sender )
matched
is a tuple of matched parts. Elements in matched
tuple is a unicode string. For lines with only a command string, the
corresponding matched
elements is a unicode empty stirng. Note
that for inputs like command arg1 arg2
, the corresponding
matched
element is arg1 arg2
as one string. This is
because IMPyBot does not know how you will use the matched part,
thus it will not simply assume you want space-separated arguments.
sender
is a unicode string, representing the message sender's
"email address" (or, JID). For advanced users: sender will always be
the bare JID, which means you cannot get the resource part from
sender
. If you need the resource part, access the raw message
object (an xmpp.Message object) via self.__message
and call its
method getFrom()
.
The return value of the callback method will be sent back to
sender
. If you do not have anything to send, or you just want to
ignore this input, simply return None or "", or just return
.
There are two ways to represent what you want to send. A string or a unicode string is enough in most cases. But if you need to return a multi-line message, with string normally it is necessary to do something like:
reply = [] # do something... reply.append('something') # do something else.... reply.append('something else') #finally: return "\n".join(reply)
Alternatively, you can use class self.Response
:
reply = self.Response() # do something... reply.append_line('something') # do something else... reply.append_line('something else') # finally: return reply
Yes! You can directly return a self.Response
type object.
self.Response
does not only consist of a couple of convenience
methods. It exists because of an important reason - to tell the bot
to stop matching other plugins with lower priorities. More on this
later.
THIS IS VERY IMPORTANT. After you finish writing your plugin class, don't forget to register it so that the bot will know of it and will execute it when there is a proper user input. The way to register your plugin is by calling the register() function:
impybot.register(SomePluginClass, priority = some_priority)
Register your plugin class out of the class's scope. Or to say, the indentation of this line of code MUST be the same as where you declare your plugin class.
Normally the optional priority
argument does not need to be
worried - a default priority value will be assigned to your plugin.
In case that you need your plugin to have higher or lower
priorities, specify it here. A smaller integer indicates a higher
priority - think it as "niceness". Plugins with higher priorities
will be executed before ones with lower priorities.
The acceptable range of priorities are from 0 to 20. Don't
use priorities out of this range. register
does not allow it.
And doing so may ruin your own bot applications.
Advanced Python uses may be attemped to use class decorators for the registering purpose. However, considering the backward compatibility with lower versions of Python, I have to make the decision of not using decorators. I might later provide it as an optional feature.