funkwhale/docs/developers/plugins.rst

166 wiersze
5.3 KiB
ReStructuredText

Funkwhale plugins
=================
Starting with Funkwhale 1.0, it is now possible to implement new features
via plugins.
Some plugins are maintained by the Funkwhale team (e.g. this is the case of the ``scrobbler`` plugin),
or by third-parties.
Installing a plugin
-------------------
To install a plugin, ensure its directory is present in the ``FUNKWHALE_PLUGINS_PATH`` directory.
Then, add its name to the ``FUNKWHALE_PLUGINS`` environment variable, like this::
FUNKWHALE_PLUGINS=myplugin,anotherplugin
We provide a command to make it easy to install third-party plugins::
python manage.py fw plugins install https://pluginurl.zip
.. note::
If you use the command, you will still need to append the plugin name to ``FUNKWHALE_PLUGINS``
Types of plugins
----------------
There are two types of plugins:
1. Plugins that are accessible to end-users, a.k.a. user-level plugins. This is the case of our Scrobbler plugin
2. Pod-level plugins that are configured by pod admins and are not tied to a particular user
Additionally, user-level plugins can be regular plugins or source plugins. A source plugin provides
a way to import files from a third-party service, e.g via webdav, FTP or something similar.
Hooks and filters
-----------------
Funkwhale includes two kind of entrypoints for plugins to use: hooks and filters. B
Hooks should be used when you want to react to some change. For instance, the ``LISTENING_CREATED`` hook
notify each registered callback that a listening was created. Our ``scrobbler`` plugin has a callback
registered to this hook, so that it can notify Last.fm properly:
.. code-block:: python
from config import plugins
from .funkwhale_startup import PLUGIN
@plugins.register_hook(plugins.LISTENING_CREATED, PLUGIN)
def notify_lastfm(listening, conf, **kwargs):
# do something
Filters work slightly differently, and expect callbacks to return a value that will be used by Funkwhale.
For instance, the ``PLUGINS_DEPENDENCIES`` filter can be used as a way to install additional dependencies needed by your plugin:
.. code-block:: python
# funkwhale_startup.py
# ...
from config import plugins
@plugins.register_filter(plugins.PLUGINS_DEPENDENCIES, PLUGIN)
def dependencies(dependencies, **kwargs):
return dependencies + ["django_prometheus"]
To sum it up, hooks are used when you need to react to something, and filters when you need to alter something.
Writing a plugin
----------------
Regardless of the type of plugin you want to write, lots of concepts are similar.
First, a plugin need three files:
- a ``__init__.py`` file, since it's a Python package
- a ``funkwhale_startup.py`` file, that is loaded during Funkwhale initialization
- a ``funkwhale_ready.py`` file, that is loaded when Funkwhale is configured and ready
So your plugin directory should look like this::
myplugin
├── funkwhale_ready.py
├── funkwhale_startup.py
└── __init__.py
Now, let's write our plugin!
``funkwhale_startup.py`` is where you declare your plugin and it's configuration options:
.. code-block:: python
# funkwhale_startup.py
from config import plugins
PLUGIN = plugins.get_plugin_config(
name="myplugin",
label="My Plugin",
description="An example plugin that greets you",
version="0.1",
# here, we write a user-level plugin
user=True,
conf=[
# this configuration options are editable by each user
{"name": "greeting", "type": "text", "label": "Greeting", "default": "Hello"},
],
)
Now that our plugin is declared and configured, let's implement actual functionality in ``funkwhale_ready.py``:
.. code-block:: python
# funkwhale_ready.py
from django.urls import path
from rest_framework import response
from rest_framework import views
from config import plugins
from .funkwhale_startup import PLUGIN
# Our greeting view, where the magic happens
class GreetingView(views.APIView):
permission_classes = []
def get(self, request, *args, **kwargs):
# retrieve plugin configuration for the current user
conf = plugins.get_conf(PLUGIN["name"], request.user)
if not conf["enabled"]:
# plugin is disabled for this user
return response.Response(status=405)
greeting = conf["conf"]["greeting"]
data = {
"greeting": "{} {}!".format(greeting, request.user.username)
}
return response.Response(data)
# Ensure our view is known by Django and available at /greeting
@plugins.register_filter(plugins.URLS, PLUGIN)
def register_view(urls, **kwargs):
return urls + [
path('greeting', GreetingView.as_view())
]
And that's pretty much it. Now, login, visit https://yourpod.domain/settings/plugins, set a value in the ``greeting`` field and enable the plugin.
After that, you should be greeted properly if you go to https://yourpod.domain/greeting.
Hooks reference
---------------
.. autodata:: config.plugins.LISTENING_CREATED
Filters reference
-----------------
.. autodata:: config.plugins.PLUGINS_DEPENDENCIES
.. autodata:: config.plugins.PLUGINS_APPS
.. autodata:: config.plugins.MIDDLEWARES_BEFORE
.. autodata:: config.plugins.MIDDLEWARES_AFTER
.. autodata:: config.plugins.URLS