Future me, I presume that you are reading this because you want to add some new functionality to Mal but can’t remember how to do so. If so, don’t worry, current you has your back. Continue reading to jog your memory and inevitably cringe at this hacked together and likely very poorly thought out system. Otherwise, in the unlikely event that you are not me, then I apologize for this mess of a “plugin system” and wish you the best of luck in your endeavors.
All plugins start in plugins.py, located in the root of Mal’s directory. In this folder, you should create a function with the name of the command’s invocation. For example, if you wanted to write a plugin to be called with the message “;status system” then you would create a function with the name “status“. The primary reason for this implementation is that it is simple to program, but it also enforces some good practices like ensuring that you don’t try to map an existing command again.
The function that you write in this file should be short. If you can write your entire plugin in 2-4 lines, then by all means, include it in its entirety in plugins.py. Otherwise, you should move your plugin’s actual code to a new file in the tools subdirectory and then import, call and possibly return a function from that file. This is how most of the plugins are written.
In both approaches, your function should take in a single argument with the name ‘slackMessage‘. The argument when called will always be of type SlackMessage, which has 3 fields: channel, user and message. The implementation of this class is incredibly basic and can be found in the file tools/classes.py.
In the immediate line after your function’s declaration, you should start the line with four octothorpes (# symbols) and a short description of your plugin. This is parsed to generate Mal’s help message.
A complete function for a plugin that calls a function from a file in the tools subdirectory should look like the following:
def waifu(slackMessage): ####The waifu command. import tools.waifu return tools.waifu.postWaifu(slackMessage)
A plugin that handles all logic in the plugins.py file should look as follows:
def flip(slackMessage): ####Randomly choose between Mike/Lily (No/Yes). import random return random.choice(["Lily!", "Mike."])
What To Return
The other major part of writing a plugin for Mal is ensuring you return something valid. There are three things the main loop in mal.py expects you to possibly return.
- A string – if you return a string all the way up to the main loop, it will be sent using the default method of sending a message to Slack in the channel (currently the send_msg() method provided by slacksocket) where the command was sent by the default bot account.
- A instance of the Conversation class – if your plugin requires further input from the user, you should return a Conversation object with all the necessary info for Mal to work with in monitoring future messages. A write up of the conversation framework will hopefully be posted in the future.
- Something falsy – If your plugin requires nothing further to be done upon completion, then you need only return something falsy. This is typically done by returning the empty string (since that was the expected behavior prior to my learning about the idea of falsy in Python).
If you return something other than the 3 options described above, Mal will probably crash. This is bad, but I don’t really have any intention of changing that behavior at this time.
It’s not perfect, it’s actually only barely functional, but this system is still a huge improvement over the if-else ladder in Mal’s original implementation that checked inconsistent and sometimes contradictory conditions and then responded in a way that had no predictability.
At least this way, I can pester someone to ‘write a plugin for Mal’ and actually have that request be something doable.