Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/plugin install #69

Open
wants to merge 108 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
108 commits
Select commit Hold shift + click to select a range
15313ab
plugins: restructure plugin utils to be in an addons module
onerandomusername Aug 23, 2021
0c67d0d
feat: add rudimentary download from zip URLS
onerandomusername Aug 23, 2021
f59ce83
chore: have coverage.py ignore type_checking ifs
onerandomusername Aug 24, 2021
98abe99
addons: create addon source converter
onerandomusername Aug 24, 2021
90643a0
add addon models file
onerandomusername Aug 24, 2021
07976cc
major: refactor plugin and addon structure
onerandomusername Aug 28, 2021
b7ab4c3
major: don't require regex matches to be passed to create a Plugin
onerandomusername Aug 28, 2021
5899a1d
feat: make plugin converter work again
onerandomusername Aug 28, 2021
3544367
plugins: add @local plugin source
onerandomusername Aug 28, 2021
3ae5c0e
chore: ensure zip_url does not contain https?://
onerandomusername Aug 29, 2021
40c5b25
minor: seperate syncing logic from a command
onerandomusername Aug 29, 2021
f0cb103
feat: add downloading plugins
onerandomusername Aug 29, 2021
75bae57
minor: move addons out of utils
onerandomusername Aug 29, 2021
4605b14
ci: make isort not fight with black
onerandomusername Aug 29, 2021
8ede9db
major: refactor plugin installation
onerandomusername Aug 29, 2021
ca919df
dependencies: use atoml
onerandomusername Aug 29, 2021
5be54b0
rework: remove sources from being part of a plugin
onerandomusername Aug 30, 2021
7ada61c
plugins: add method to get list of plugins from toml
onerandomusername Aug 30, 2021
8720942
tests: remove skips on implemented tests
onerandomusername Aug 30, 2021
9a69e88
chore: annotate as much of tests as viable
onerandomusername Aug 30, 2021
32aa170
plugins: fix min_bot_version
onerandomusername Aug 30, 2021
9a32c0f
tests: start testing addon utils
onerandomusername Aug 30, 2021
f8966cf
Merge branch 'main' into feat/plugin-install
onerandomusername Aug 30, 2021
dd787f9
Merge branch 'main' into feat/plugin-install
onerandomusername Aug 31, 2021
68ed612
fix: close session after test
onerandomusername Aug 31, 2021
0aa4a9f
chore: move plugin_helpers to modmail/plugins
onerandomusername Sep 1, 2021
5052b61
dependencies: drop toml for atoml
onerandomusername Sep 1, 2021
dbb1511
fix: don't use None when typing __init__ return
onerandomusername Sep 1, 2021
5adac3a
minor: don't use kwargs
onerandomusername Sep 2, 2021
462f4ab
Merge branch 'main' into feat/plugin-install
onerandomusername Sep 2, 2021
f2ce9f3
refactor plugin downloading to be easier to read and use
onerandomusername Sep 4, 2021
cf09817
chore: use temp directory for caching zips
onerandomusername Sep 4, 2021
afd4b18
plugin-install: refactor to use zip stream and only install the provi…
onerandomusername Sep 5, 2021
4503797
minor: remove admins from being able to manage plugins
onerandomusername Sep 5, 2021
8501636
plugins: allow spaces in plugin names, and dashes in refs
onerandomusername Sep 5, 2021
eb73921
parse plugin toml for plugin metadata
onerandomusername Sep 5, 2021
005fdb5
chore: prevent plugin manager from being unloaded
onerandomusername Sep 5, 2021
ebb51f1
Merge branch 'main' into feat/plugin-install
onerandomusername Sep 5, 2021
fd252c6
remove toml from requirements.txt
onerandomusername Sep 5, 2021
52e2fe0
tests: use pytest.mark.raises instead of xfail
onerandomusername Sep 5, 2021
b032c74
fix[plugins]: allow installation by name or folder name
onerandomusername Sep 5, 2021
795a611
add a parameter to walk only a sub folder
onerandomusername Sep 5, 2021
d8a059c
minor: BotModes -> BotModeEnum
onerandomusername Sep 6, 2021
2053f30
remove test command
onerandomusername Sep 6, 2021
b352071
changes: document plugin install and uninstall system
onerandomusername Sep 6, 2021
997a20c
plugins: disable specific manager commands if plugin dev mode is disa…
onerandomusername Sep 6, 2021
fb9d20c
chore: rename to @ local to match the ref format
onerandomusername Sep 6, 2021
ac46ac9
add list of installed plugins to bot instance
onerandomusername Sep 6, 2021
ece505b
move dev commands to a dev group
onerandomusername Sep 6, 2021
a67522e
fix: actually use provided refs
onerandomusername Sep 6, 2021
781f625
feat: add enabling, disabling, uninstalling plugins
onerandomusername Sep 6, 2021
dd463ec
plugins: refactor to allow local plugins
onerandomusername Sep 7, 2021
3ce64a6
rewrite plugin models to require folder_name instead of name
onerandomusername Sep 7, 2021
a9f99dd
fix[local-plugins]: don't add plugins not in local.toml
onerandomusername Sep 7, 2021
da19a07
plugin-toml: add directory as an alias for folder
onerandomusername Sep 7, 2021
c9fcf01
minor: allow local plugins to be disabled in local.toml and fix some …
onerandomusername Sep 8, 2021
c01185a
major: plugin loading error handling and proper replies
onerandomusername Sep 8, 2021
0b73254
chore: remove the Responses class and switch to using the responses m…
onerandomusername Sep 10, 2021
e98ddae
breaking: move the plugin helpers file
onerandomusername Sep 10, 2021
c874b5a
minor: fix responses.py logger
onerandomusername Sep 10, 2021
68490db
fix: handle status of plugins with no files
onerandomusername Sep 10, 2021
7f5341c
breaking: restructure how extensions are stored
onerandomusername Sep 11, 2021
fff1e90
implement partial plugin restructure
onerandomusername Sep 11, 2021
c163723
breaking: finish refactoring PLUGINS global
onerandomusername Sep 11, 2021
5c9e61e
minor: add plugin suggesting on plugin converter
onerandomusername Sep 11, 2021
49c3d59
priortise plugins folder names over names
onerandomusername Sep 11, 2021
1cf6233
minor: add message param to responses to allow editing a previous res…
onerandomusername Sep 12, 2021
43eb516
allow plugins to declare dependencies
onerandomusername Sep 12, 2021
c9a3c9f
commit: fix: use pip to install in dockerfile instead
onerandomusername Sep 12, 2021
8a2194d
chore: fix comments and lower score cutoff for plugin suggestions
onerandomusername Sep 12, 2021
9ea3be4
fix: don't send a success message on error
onerandomusername Sep 12, 2021
44e2e44
chore: add plugin as an alias
onerandomusername Sep 12, 2021
063e55a
docs: add addon and plugin documentation
onerandomusername Sep 12, 2021
abab87c
minor: add missing link
onerandomusername Sep 13, 2021
5a6f889
minor: move bot mode determine to utils.cogs
onerandomusername Sep 13, 2021
dafb22c
docs: add guide on ExtMetadata and BOT_MODE
onerandomusername Sep 13, 2021
dc5e38d
docs: enhance plugin documentation
onerandomusername Sep 13, 2021
6bc98dd
chore: fix invalid attr bug
onerandomusername Sep 13, 2021
72fd8a7
docs: move changelog above security
onerandomusername Sep 13, 2021
9784648
fix: logging typos
onerandomusername Sep 15, 2021
d70cd3d
fix: get rid of requirements.txt, switch to constraints.txt
onerandomusername Sep 15, 2021
ece99be
fix: ignore root warning when installing dependencies with pip
onerandomusername Sep 16, 2021
141db4a
fix a bug with set changing size during iteration
onerandomusername Sep 17, 2021
c7e5376
minor: add a missing 'e' for grammar
onerandomusername Sep 17, 2021
6ff682d
docs: improve wording of opening sentences and don't duplicate myself
onerandomusername Sep 21, 2021
4401f34
fix: declare required arguments as required
onerandomusername Sep 23, 2021
d5e18c4
Merge branch 'main' into feat/plugin-install
onerandomusername Oct 12, 2021
f7ffd7d
fix: also export modmail/constraints.txt
onerandomusername Oct 12, 2021
fdda962
Merge branch 'main' into feat/plugin-install
onerandomusername Nov 17, 2021
1f49b79
chore: fix bugs arised from merge
onerandomusername Nov 18, 2021
74c2d78
fix: use discord.py advanced converters for Plugin models
onerandomusername Nov 18, 2021
b3e3070
fix: patch plugin folder during tests
onerandomusername Nov 18, 2021
34031e4
fix: use WindowsSelectorEventLoopPolicy on windows
onerandomusername Nov 18, 2021
1c88421
ci: run tests with verboseness, not quietness
onerandomusername Nov 18, 2021
8be2332
chore: fix windows dns lookup
onerandomusername Nov 18, 2021
6e34994
fix: use pathlib to determine module name
onerandomusername Nov 18, 2021
c8e12bc
chore: use a url parsing library instead of regex
onerandomusername Nov 19, 2021
0b89af0
tests: add aioresponses for aiohttp response mocking
onerandomusername Nov 19, 2021
d96d7a7
fix: mark failing test as xfail
onerandomusername Nov 19, 2021
550764d
deps: upgrade rapidfuzz to wheel supported version
onerandomusername Nov 19, 2021
bb9d3f8
scripts: add diff output to export_requirements
onerandomusername Nov 19, 2021
20d722f
Revert "ci: run tests with verboseness, not quietness"
onerandomusername Nov 21, 2021
a773b44
chore: don't use __all__ in __init__.py
onerandomusername Nov 21, 2021
9987b26
chore: make requested changes and address review
onerandomusername Nov 21, 2021
1d32910
review: address
onerandomusername Feb 24, 2022
bc1a835
Merge branch 'main' into feat/plugin-install
onerandomusername Apr 21, 2022
6214cdd
fix: readd rapidfuzz
onerandomusername Apr 21, 2022
bfba8e2
Merge branch 'main' into feat/plugin-install
onerandomusername Apr 21, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ repos:
- id: generate_requirements.txt
name: Generate requirements.txt
entry: python -m scripts.export_requirements --docs
files: '(pyproject.toml|poetry.lock|requirements.txt|scripts\/export\_requirements\.py|docs\/.requirements.txt)$'
files: '(pyproject.toml|poetry.lock|requirements.txt|constraints.txt|scripts\/export\_requirements\.py|docs\/.requirements.txt)$'
language: python
pass_filenames: false
require_serial: true
Expand Down
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ FROM python:3.9-slim
# Set pip to have cleaner logs and no saved cache
ENV PIP_NO_CACHE_DIR=false

# Update pip
RUN pip install -U pip

# Create the working directory
WORKDIR /modmail

Expand Down
48 changes: 48 additions & 0 deletions docs/addons/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Addons

Addons are our built-in system to extend the features of the bot in an officially supported manner.

Modmail, in its most basic form, is simple: relay messages to and from users to staff members.
However, we acknowledge that its not a one-size-fits-all solution.
Some communities need a few more features than others.
That's where the addon system fills the void.

The addon system currently supports only one kind of addon, plugins.
This guide will help you set up a respository to create your own addons.
Once its set up, please refer to the [plugin creation guide][making-plugins] for more details.

!!!note
This guide is for those who want to **write** addons. If you are looking to use an addon, please view our guide [on installing them][installation].

## Guides

- [Installation]
- [Repo Setup](#repo-setup)
- [Creating Plugins][making-plugins]

## Repo Setup

In order to be able to install addons, a few things are required.
Each addon type will have its own requirements in addition to the following.

### Overall File Structure

At the base of the addon system is the source. Sources have a folder structure like the following:

```sh
.
├── Plugins/
└── README.md
```

In this structure, this repository is holding addons of a plugin type. The structure of the Plugins folder itself is detailed in the [creating plugins guide][making-plugins].

### Hosting

All addons must be hosted on either github or gitlab as of now.

!!!note
Addons currently do not automatically update, and need to be re-installed on each run. This will be fixed once the database client exists.

[installation]: ./installation.md
[making-plugins]: ./plugins.md
65 changes: 65 additions & 0 deletions docs/addons/installation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Installation

!!!note
If you are looking to write addons, check out our [writing addons][addon-guide] guide.

## Plugins

Plugins are discord.py extensions which expand the functionality of the bot beyond its core feature: relaying messages back and forth between staff and members.

We've done our best to make this system easy to use for both novice and experienced developers--installing plugins should require no programming knowledge at all.

By default, modmail will install plugins hosted on [github.com](https://github.com), but also supports installing from [gitlab](https://gitlab.com).

This may look complex, but it supports a wide variety of options, as demonstrated below

```fix
?plugin install [git-host] <user>/<repo> <name> [@ref]
?plugin install <link> <name> [@ref]
```

### Git-host style

> `[git-host] <user>/<repo> <name> [@ref]`

#### Git-host (Optional)

Valid options are:

- `github`
- `gitlab`

Default:

- `github`

#### User/Repo

This is the user and the respository hosted on a valid git-host.

In the link <https://github.com/discord-modmail/addons>, the user and repo are `discord-modmail/addons`.

#### Name

This is the addon name, it is not allowed to contain `@`.
By default, this is the plugin folder name, unless it is defined in the plugin.toml file.
A repository should provide a list of their plugins either in a plugin readme, or the full repository readme.

#### Ref

This is the git reference, leave blank to use the repository default.
If you would like to use a specific commit, branch, or tag, then provide it preceeded by a `@`.
For example, to use tagged version 1.2, `@v1.2` would install from that tag.

### Link

> `<link> <name> [@ref]`

If the above githost format seems too complicated, its possible to just copy the url to the repo
(ex. https://github.com/discord-modmail/addons) and use that for the link.

The name of the plugin still must be provided, however.
The @ref can also be provided, if installating a specific version is desired.


[addon-guide]: ./README.md
134 changes: 134 additions & 0 deletions docs/addons/plugins.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# Creating Plugins

If you are looking to write a feature to extend the functionality of your modmail bot, plugins are *the*
supported way to add additional code to modmail.

In short, plugins are discord.py extensions which expand the functionality of the bot beyond its built-in duties.


!!!Tip
This builds on the [addon structure documentation][addon-guide]. Please ensure you have a solid understanding of the basic repository structure beforehand.

!!!note
This guide is **not** how to install plugins, please view our [installation guide][installation] for that.

## File Structure Overview

This details the structure of a plugin addon.

```sh
Plugins/
├── react_to_contact
│ ├── listener.py
│ └── react_to_contact.py
├── verify_contact
│ └── verify_contact.py
└── plugin.toml
```

Even though there are three `.py` files, this repository contains two plugins. Each top level folder in the Plugins folder contains one plugin.
The number of py files in each plugins folder does not matter, there are still two plugins here.

One plugin here is named `react_to_contact`, the other is `verify_contact`

However, those are not user friendly names. It would be a lot easier for the end user to reference with `React to Contact`, and for the user interface to refer to it as such.

To do so, a name can be provided in the plugin.toml file.

## plugin.toml

There are several variables which can be configured by providing a plugin.toml file.

If you don't already know what toml is, [check out their docs](https://toml.io/)

!!!tip
`plugin.toml` is supplemental to the list of folders. This means that all plugins in the repository are installable at any time. Providing a plugin.toml does not mean that any plugins *not* in the toml are not included anymore.

This has the advantage of being able to use `plugin.toml` to change the name of one plugin, without having to add all other plugins to the toml.


### Options

A full `plugin.toml` for the above repository may look like this:

```toml
[[plugins]]
name = 'React to Contact'
description = 'Provides a permanent message where the user can react to open a thread'
directory = 'react_to_contact'

[[plugins]]
name = 'Verify Contact'
description = 'Prevents the user from accidently opening a thread by asking if they are sure.'
directory = 'verify_contact'
```

The name and directory are the only keys in use today,
the description is not yet used.

The `directory` key is required, if wanting to set any other settings for a plugin.

!!!tip
`directory` is aliased to `folder`. Both keys are valid, but if the `directory` key exists it will be used and `folder` will be ignored.

Name is optional, and defaults to the directory if not provided.

!!!warning
Capitals matter. Both the `plugin.toml` file and `[[plugins]]` table ***must*** be lowercase.
This also goes for all keys and directory arguments--they must match the capitials of the existing directory.

### Dependencies

If the dependencies that the bot is installed with, it is possible to declare a dependency and it will be installed when installing the plugin.

!!! Waring
For the most part, you won't need to use this. But if you absolutely must use an additional dependency which isn't part of the bot, put it in this array.

This is an array of arguments which should be just like they are being passed to pip.

```toml
[[plugins]]
directory = 'solar_system'
dependencies = ['earthlib==0.2.2']
```

This will install earthlib 0.2.2.

## Code

Now that we have an understanding of where the plugin files go, and how to configure them, its time to write their code.

### `PluginCog`

All plugin cogs ***must*** inherit from `PluginCog`.

If plugin cogs do not inherit from this class, they will fail to load.

A majority of the needed modmail classes have been imported into helpers for your convinence.

```python
from modmail.addons import helpers

# Cog
helpers.PluginCog

# Extension Metadata
helpers.ExtMetadata

### For Typehints
# bot class
helpers.ModmailBot

# logger
helpers.ModmailLogger
```

### `ExtMetadata`

There is a system where extensions can declare load modes.

There is a longer write up on it [here][ext_metadata].

[addon-guide]: ./README.md
[ext_metadata]: /contributing/creating_an_extension/#bot_mode-and-extmetadata
[installation]: ./installation.md#plugins
2 changes: 2 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- Threads system (#53)
- Plugin installation and uninstall system (#69)
- Messages can now be relayed between a user and a server.
- NOTE: There is not a database yet, so none of these messages are stored.
- Added Dispatcher system, although it is not hooked into important features like thread creation yet. (#71)
Expand Down Expand Up @@ -43,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Running the bot is still the same method, but it loads extensions and plugins now.
- `bot.start()` can also be used if already in a running event loop. Keep in mind using it will require
handling loop errors, as run() does this automatically.
- Disabled some plugin management commands if PLUGIN_DEV mode is not set (#69)

### Internal

Expand Down
83 changes: 83 additions & 0 deletions docs/contributing/creating_an_extension.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Creating an Extension

Welcome!

Please note that extensions are cogs are different things. Extensions are files which add features to the bot,
and a cog is way to group commands and features within a file.

This is an addendum to the [discord.py guide](https://discordpy.readthedocs.io/en/master/ext/commands/extensions.html) on how to write extensions.
This guide below details additional information which is not part of discord.py.

**There is one major change from discord.py**:
Cogs **must** inherit from `ModmailCog`.
If this does not happen, the bot will let you know.

ModmailCog is defined in `modmail/utils/cogs.py`.

## BOT_MODE and `ExtMetadata`

In general, an extension does not need to use the feature of an extension metadata.

On every extension of the bot, an `EXT_METADATA` constant should exist, and it should be an instance of `ExtMetadata`.
The `ExtMetadata` class is defined in `modmail/utils/cogs.py`, along with `BotModeEnum`.

It should be sufficent to have an EXT_METADATA variable declared at the top of the file as an instance of ExtMetadata.

```python
from modmail.utils.cogs import ExtMetadata

EXT_METADATA = ExtMetadata()
```

### `ExtMetadata`

The purpose of ExtMetadata is to define metadata about an extension. Currently, it supports two items of metadata.

- `load_if_mode`
- used to determine if this extension should be loaded at runtime.
- `no_unload` (Not supported by plugins)
- prevents an extension from being unloaded by the `?ext unload` command. This is mainly used to keep the extension manager from unloading itself.

`no_unload` is pretty self explanatory, pass either True or False and the extension will either be blocked from being unloaded, or allowed to unload.
This only has an impact if the current bot mode is DEVELOP. Note that this does prevent the developer from *reloading* the extension.

### `load_if_mode`

`load_if_mode` currently has three modes, which each have their own uses.:

- `PRODUCTION`
- The default mode, the bot is always in this mode.
- `DEVELOP`
- Bot developer. Enables the extension management commands.
- `PLUGIN_DEV`
- Plugin developer. Enables lower-level plugin commands.

!!!tip
To enable these modes, set the corresponding environment variable to a truthy value. eg `DEVELOP=1` in your project `.env` file will enable the bot developer mode.

To set an extension to only load on one cog, set the load_if_mode param when initialising a ExtMetadata object.

```python
from modmail.utils.cogs import BotModeEnum, ExtMetadata

EXT_METADATA = ExtMetadata(load_if_mode=BotModeEnum.DEVELOP)
```

*This is not a complete extension and will not run if tested!*

This `EXT_METADATA` variable above declares the extension will only run if a bot developer is running the bot.

However, we may want to load our extension normally but have a command or two which only load in specific modes.

### `BOT_MODE`

The bot exposes a BOT_MODE variable which contains a bitmask of the current mode. This is created with the BotModeEnum.
This allows code like this to determine if the bot mode is a specific mode.

```python
from modmail.utils.cogs import BOT_MODE, BotModeEnum

is_plugin_dev_enabled = BOT_MODE & BotModeEnum.PLUGIN_DEV
```

This is used in the plugin_manager extension to determine if the lower-level commands which manage plugin extensions directly should be enabled.
16 changes: 12 additions & 4 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,18 @@ plugins:

# Page tree
nav:
- Home: README.md
- Contributing: contributing.md
- Security: security.md
- Changelog: changelog.md
- Home: README.md
- Changelog: changelog.md
- Security: security.md
- Addons:
- Overview: addons/README.md
- Installation: addons/installation.md
- Creating Plugins: addons/plugins.md
- Contributing:
- Guidelines: contributing.md
- Creating an Extension: contributing/creating_an_extension.md



# Extensions
markdown_extensions:
Expand Down
Empty file added modmail/addons/__init__.py
Empty file.
Loading