An extensible Python chatbot that is compatible with the Matrix protocol
Go to file
2019-01-06 18:13:04 +01:00
images Initial commit of software 2018-12-16 16:38:47 +01:00
matrix_bot_api Added list event function 2019-01-06 16:01:01 +01:00
plugins Fixed small bug in which mysql_db was assumed a class element 2019-01-06 16:46:53 +01:00
.gitignore Initial commit of software 2018-12-16 16:38:47 +01:00
config.json.example Initial commit of software 2018-12-16 16:38:47 +01:00
LICENSE Added first version of README with description on API 2019-01-06 13:13:57 +01:00
README.md Fixed two typos in README 2019-01-06 18:13:04 +01:00
run.py Initial commit of software 2018-12-16 16:38:47 +01:00

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 code has 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:

...

Plugins

The bot has different plugins that can be activated by sending [Keyword in bot's config.json] [Keyword(s) in plugin's config.json] 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:

API

To interact with rooms, the Matrix Python SDK can be used.

Mandatory files

To create a plugin, a few mandatory files must be present. The tree below shows the general structure of a plugin. A new plugin must be placed in a directory with the same name. The main class (more on that later) of the plugin must be defined in a Python file with the same name within that directory. The event may not use bot's main configuration file. All configuration must be placed in a separate config.json.

A subdirectory messages is used to store messages. Within this directory, a file help must 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>.json with all messages should exist.

.
└── plugins
    ├── <plugin1>
    │   ├── __init__.py
    │   ├── <plugin1>.py
    │   ├── README.md
    │   ├── config.json
    │   └── messages
    │       ├── messages.<language>.json
    │       └── 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.
  • It has a method setup() which is only executed once (ever). This can be used, for example, to create SQLite tables. All data should be saved in subdirectory for that plugin in data. 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 and event 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 of plugin1/messages/help
CONFIG_LOCATION = os.path.join(os.path.dirname(__file__), 'config.json')
HELP_LOCATION = os.path.join(os.path.dirname(__file__), 'help')
MESSAGES_LOCATION = os.path.join(os.path.dirname(__file__),
    'messages/messages.dutch.json')

class Plugin:
    """ Description of event plugin """

    def __init__(self, bot):
        # Load the configuration
        with open(CONFIG_LOCATION) as json_data:
            self.config = json.load(json_data)

        # Load all messages for this plugin
        with open(MESSAGES_LOCATION) as json_data:
            self.messages = json.load(json_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()

        self.setup()

    def setup(self):
        """This function only runs once, ever. It installs the plugin"""

        try:
            # Install some stuff, if not already installed
        except sqlite3.OperationalError:
            # For example, when trying to initialize an SQLite DB
            # which already exists, sqlite3.OperationalError is thrown
            pass

    def thread1_method(self):
        """This function continuously loops"""

        while not self.bot.cancel:
            # Do something. For example, call other methods.

            # Sleep
            time.sleep(self.config['update_time_span'] * 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()

Additional tips