Plugin Documentation
This forked version of Modmail maintains its own list of community-made plugins. Run [p]plugin registry
command on your bot to see
a list of submitted plugins available.
General plugin creation guideline
- Compatible with Python versions supported by the bot.
- Plugin must adhere to Discord's Terms of Service (opens in a new tab).
- Cog name cannot be the same as any current class (
Core
,Modmail
). - It cannot have the same name as another approved plugin.
Creating plugins
We use discord.py (opens in a new tab) for the bot and plugins take the form of Cogs (opens in a new tab).
Short example:
from discord.ext import commands
class Hello(commands.Cog):
def __init__(self, bot):
self.bot = bot
@commands.Cog.listener()
async def on_message(self, message):
print(message.content)
@commands.command()
async def say(self, ctx, *, message):
await ctx.send(message)
async def setup(bot):
await bot.add_cog(Hello(bot))
Folder Structure
Your plugin has to be uploaded on Github on a public repository.
private repositories are supported, but they require extra setup, see Private Plugins
The repository folder structure has to be as follows:
root:
plugin_name:
..
plugin_name.py
requirements.txt [optional]
plugin_name:
..
plugin_name.py
requirements.txt [optional]
The plugin will be loaded with something similar to
await bot.load_extension('username.plugin_name.plugin_name')
To install a plugin that is not in the official registry, type:
?plugin add githubusername/plugin_repo/plugin_name[@branch]
An example of a plugin can be seen at fourjr/modmail-plugins
(opens in a new tab) or any of the plugins in our registry (opens in a new tab).
Branch parameter
The branch parameter is optional (default to main
) and can be used to test in-development/unstable plugins with a development branch.
Users will always be updated to the latest version. Thus, if there is a broken plugin on the latest version, users would not be able to use the plugin.
@local (for developers)
To make it easier to develop a plugin, there's a folder named @local
in the plugins folder. You can directly put a folder for each plugin in it.
Using the example cog above, the load command would be
?plugin load @local/hello
Best Practices
- Create a development branch
- Push to it until you are confident that your code is stable
- Merge it into the main branch using pull requests or
git merge -v dev --squash
- Update your plugin!
Private Plugins
- Obtain a Github Personal Access Token (opens in a new tab) with
repo
scope - Include
GITHUB_TOKEN
as a config variable (or in .env) with the token as the value. - Upload your code to a private Github repository.
- Install just like a normal public plugin.
Database Interfacing
Do not interact with bot.api
directly. Fetch a partition and use it:
def __init__(self, bot): # in the class init
self.coll = bot.api.get_plugin_partition(self)
self.coll
is a motor.motor_asyncio.AsyncIOMotorCollection (opens in a new tab)
Additional PIP requirements
Create a requirements.txt
file (opens in a new tab) in the plugin folder. Packages listed here would be installed via something similar to the following command:
python3 -m pip install -r requirements.txt --user -q -q
Exposed Events
The bot dispatches custom events to aid plugin developers to extend Modmail functionality.
Currently, we have these custom coroutines:
Bot.format_channel_name(bot, author, exclude_channel=None, force_null=False)
can be overwritten for custom behaviour.on_plugins_ready()
which is dispatched when all the plugins are fully loaded and ready to be used.on_thread_initiate(thread, creator, category, initial_message)
which is dispatched at the beginning of setup process. It is recommended to use the other events instead.on_thread_create(thread)
which is dispatched when the thread is registered as a thread by Modmail (i.e., when channel topic is edited).on_thread_ready(thread, creator, category, initial_message)
which is dispatched when a thread channel is created and thegenesis_message
(info embed) is sent. It is recommended to use this event.on_thread_close(thread, closer, silent, delete_channel, message, scheduled)
which is dispatched when a thread is closed, after channel deletion.on_thread_reply(thread, from_mod, message, anonymous, plain)
which is dispatched upon any reply.
e.g.
@commands.Cog.listener()
async def on_thread_ready(self, thread, creator, category, initial_message):
msg = thread.genesis_message
... # do stuff
Adding your plugin
Create a Pull Request (opens in a new tab) into the stable branch,
adding your plugin into REGISTRY.json
(opens in a new tab)
following the correct JSON format.