# Plugins

Plugins can add additional functionality into the modmail bot. You can view a list of approved plugins via the `?plugins registry` command or take a look at the [`plugins/registry.json`](https://github.com/modmail-dev/modmail/blob/master/plugins/registry.json).

## Guidelines

To get approved and officially verified, you need to ensure you follow these guidelines:

* Supporting Python 3.10 (and above).
* No malicious intent.
* The plugin cannot be a feature pending to be added into [Modmail](https://github.com/modmail-dev/modmail/issues). You can submit a PR to add it to the core Modmail.
* Core Modmail still needs to 100% function.
* 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](https://discordpy.readthedocs.io/en/stable/) for the bot and plugins take the form of [Cogs](https://discordpy.readthedocs.io/en/stable/ext/commands/cogs.html).

Short example:

```py
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.** (Note: private repositories are supported, but they require extra setup, see [Private Plugins](https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token)). The repository folder structure has to be as follows:

```yaml
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

```py
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`](https://github.com/fourjr/modmail-plugins) or any of the plugins in our [registry](https://github.com/modmail-dev/modmail/blob/master/plugins/registry.json).

#### Branch parameter

The branch parameter is **optional** (default to `master`) 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
```

{% hint style="info" %}
You can also use this as a way to have a private plugin installed on your bot if you host locally or on a VPS, and do not intend to update your plugin frequently. If you plan on even semi-frequent updates, you should use the private plugins process listed below.
{% endhint %}

#### Best Practices

1. Create a development branch
2. Push to it until you are confident that your code is stable
3. Merge it into `master` using pull requests or `git merge -v dev --squash`
4. Update your plugin!

### Private Plugins

* Obtain a [Github Personal Access Token](https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token) 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:

```python
def __init__(self, bot):  # in the class init
    self.coll = bot.api.get_plugin_partition(self)
```

`self.coll` is a [motor.motor\_asyncio.AsyncIOMotorCollection](https://motor.readthedocs.io/en/stable/api-asyncio/asyncio_motor_collection.html)

### Additional PIP requirements

Create a [`requirements.txt` file](https://pip.pypa.io/en/stable/user_guide/#requirements-files) 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 the `genesis_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.

```py
@commands.Cog.listener()
async def on_thread_ready(self, thread, creator, category, initial_message):
    msg = thread.genesis_message
    ... # do stuff
```

### Approval request

Create a [Pull Request](https://github.com/modmail-dev/modmail/pulls) adding your plugin into [`plugins/registry.json`](https://github.com/modmail-dev/modmail/blob/master/plugins/registry.json) and we will take a look at it.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.modmail.dev/usage-guide/plugins.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
