Dennis Potter
ca86ade0fa
Before this commit, the name of the bot had to be hard coded into every plugin. Now, the name of the bot to which it should be sensitive can be set in the global config.hjson. Furthermore, the default sensitivity of a plugin is only recognized at the beginning of a sentence and is case insensitive. Finally, the possibility to add aliases for plugins is added. |
||
---|---|---|
images | ||
matrix_bot_api | ||
plugins | ||
.gitignore | ||
.gitmodules | ||
aliases.hjson.template | ||
config.hjson.template | ||
install.sh | ||
LICENSE | ||
plugable-matrix-bot.service.template | ||
README.md | ||
requirements.txt | ||
run.py |
The core of this chatbot is based on github.com/shawnanastasio/python-matrix-bot-api which was published under GPL-3.0. It is heavily modified to create an API to easily add plugins. This bot is intended to operate on the Matrix network.
This bot was orginally created to serve two purposes. Firstly, and mainly, it serves as virtual assistent in the Matrix based communication platform of members of the Dutch student association Alcuinus. Secondly, the bot's easy to use API makes the development of a simple plugin an approachable first project for new members of Alcuinus' IT working groups during their training.
Content
Installation & Requirements:
First, clone this repository:
git clone https://git.dennispotter.eu/Dennis/plugable-matrix-bot
In order for the bot framework to work, Python 3, virtualenv, and pip must be installed on your computer or server.
Automatic installation (Linux)
On Linux, the bash script install.sh
can be used to install the bot.
install.sh : Creates a virtualenv, an initial config.hjson, and installs
all required packages
install.sh service: Runs ./install and additionally adds a systemd service.
This command will ask you for root credentials!
install.sh help : Prints this message.
Manual installation
Create a virtual environment by entering the directory and running:
virtualenv .
Enter the virtual environment
source bin/activate
and install all required packages
pip3 install -r requirements.txt
Now, you need to create a configuration file config.hjson
. The template config.hjson.template
can be used for this purpose.
Finally, the bot can be started by executing the following command within the virtual environment:
./run.py
Plugins
The bot has different plugins that can be activated by sending [Keyword in bot's config.hjson] [Keyword(s) in plugin's config.hjson]
to a room in which the bot is present. Below, under List of available plugins, you can first find a list of already available plugins. Then, under API, you can find a description on how to develop a new plugin.
List of available plugins:
- Hello plugin: An example plugin that interacts with users that send "Hello bot" to the bot.
- WooCommerce coupon plugin: a plugin that sets up a coupon on installation and sends this coupon to a user that asks for it. The plugin supports an expiration period. After this period, it will renew the coupon.
- Admidio events plugin: a plugin that interacts with an Admidio installation.
API
To interact with rooms, the Matrix Python SDK can be used.
Mandatory/recommended files
To create a plugin, a few files must or can be present. The tree below shows the general structure of a plugin. A new plugin must be placed in a directory that has the name of that plugin. The main class (more on that later) of the plugin must be defined in a Python file with that same name. The plugin may not use bot's main configuration file. All configuration must be placed in a separate config.hjson
. More information on Hjson can be found on their website.
A subdirectory messages
is used to store messages. Within this directory, a file help
may be present. The content of this file must be returned with a method of the plugin class (more on that later). Furthermore, for every language a file messages.<language>.hjson
with all messages should exist.
The optional file requirements.txt
is used to define all dependencies. The framework will automatically install them before the first time the plugin is used. This happens even before the setup()
function is invoked (more on that later).
Although recommended, the help file is also not mandatory. If no help message for a plugin should appear when sending a help request to the bot, this file can be ommitted. The Plugin's help()
method should return 0 in that case.
.
└── plugins
├── <plugin1>
│ ├── __init__.py
│ ├── <plugin1>.py
│ ├── README.md
│ ├── config.hjson
│ ├── requirements.txt
│ └── messages
│ ├── messages.<language>.hjson
│ └── help
├── <plugin2>
╎
╎
└── <pluginN>
Help template
The help function must be created in HTML. The header must be the string that is used to activate the plugin (<callback>
). Every feature that is listed must start with the string that is used to activate that particular feature.
The code belows shows an example of such a help function.
<h5><i><callback></i></h5>
<ul>
<li><i>feature1</i>: Lorem ipsum dolor sit amet,</li>
<li><i>feature2</i>: consectetur adipiscing elit.</li>
<li><i>feature3</i>: Nunc ac volutpat nisi, id hendrerit tellus.</li>
</ul>
<br />
<Additional information>
Plugin class template
The code below shows the template for a simple plugin with a few features. The name of the class of every plugin must be Plugin
.
- It has a method
__init__()
which is executed every time the bot is started. Usually, this should load the configuration file, it should set the sensitivity on which it should execute certain callback functions, it should save the parent object, start additional threads, and execute the setup. - The python file may contain a function
setup()
. (Thus not a method of the class!) The framework makes sure that this function is executed once, the first time the plugin is used.setup()
can be used, for example, to create SQLite tables. All data should be saved in subdirectory for that plugin indata
. See the example below, where<plugin1>
initiates an SQLite database.
.
├── plugins
└── data
├── <plugin1>
│ └── plugin_data.db
├── <plugin2>
╎
╎
└── <pluginN>
- It has one method
thread_method1()
which is spawned and runs continuously in the background. Please keep performance in mind when doing this!*. For example, is it actually necessary to continuously run the thread, or is execution only necessary every 5 minutes? - For every sensitivity that is defined, a method must be defined that acts on it. The methods get a
room
andevent
object from the parent bot. These can be used to interact with the room. A full documentation on the Matrix Python SDK can be found here. - The Plugin class must contain a method
help()
which only returns the content ofplugin1/messages/help
CONFIG_LOCATION = os.path.join(os.path.dirname(__file__), 'config.hjson')
MESSAGES_DIR = os.path.join(os.path.dirname(__file__), 'messages')
HELP_LOCATION = MESSAGES_DIR + '/help'
MESSAGES_LOCATION = MESSAGES_DIR + /messages.dutch.hjson'
class Plugin:
""" Description of event plugin """
def __init__(self, bot):
# Load the configuration
with open(CONFIG_LOCATION) as hjson_data:
self.config = hjson.load(hjson_data)
# Load all messages for this plugin
with open(MESSAGES_LOCATION) as hjson_data:
self.messages = hjson.load(hjson_data)
# Define sensitivity
self.handler = []
self.handler.append(MRegexHandler("Peter <plugin1> <feature1>",
self.callback1))
self.handler.append(MRegexHandler("Peter <plugin1> <feature2>",
self.callback2))
# Save parent bot
self.bot = bot
# Start thread to check events
self.thread1 = threading.Thread(target=self.thread1_method)
self.thread1.start()
def thread1_method(self):
"""This function continuously loops"""
while not self.bot.cancel:
# Do something. For example, call other methods.
# Sleep
time.sleep(60)
def callback1(self, room, event):
"""Act on first sensitivity"""
room.send_text("You typed 'Peter <plugin1> <feature1>'")
def callback2(self, room, event):
"""Act on second sensitivity"""
room.send_text("You typed 'Peter <plugin1> <feature2>'")
def help(self):
return open(HELP_LOCATION, mode="r").read()
def setup():
"""This function only runs once, ever. It installs the plugin"""
# Install some stuff, e.g., initialize an SQLite database
Additional tips
- Use f-strings if possible.