Write a plugin
You can write plugins to extend the features of your Funkwhale pod. Follow the instructions in this guide to get started with your first plugin.
Before you begin
Before you start writing your plugin, you need to understand the following core concepts:
We’ll explain each of these concepts in the next few sections
Scopes
Plugins fall into two different scopes:
User-level plugins that are configured by end-users for their own use
Pod-level plugins that are configured by pod admins and are not connected to a particular user
User-level plugins can also be used to import files from a third-party service, such as cloud storage or FTP.
Hooks
Hooks are entrypoints that allow your plugin to listen to changes. You can create hooks to react to different events that occur in the Funkwhale application.
An example of this can be seen in our Scrobbler plugin. We register a LISTENING_CREATED
hook to notify any registered callback function when a listening is recorded. When a user listens to a track, the notfy_lastfm
function fires.
from config import plugins
from .funkwhale_startup import PLUGIN
@plugins.register_hook(plugins.LISTENING_CREATED, PLUGIN)
def notify_lastfm(listening, conf, **kwargs):
# do something
Available hooks
- LISTENING_CREATED = 'listening_created'
Called when a track is being listened
Filters
Filters are entrypoints that allow you to modify or add information. When you use the register_filter
decorator, your function should return a value to be used by the server.
In this example, the PLUGINS_DEPENDENCIES
filter is used to install additional dependencies required by your plugin. The dependencies
function returns the additional dependency django_prometheus
to request the dependency be installed by the server.
# funkwhale_startup.py
# ...
from config import plugins
@plugins.register_filter(plugins.PLUGINS_DEPENDENCIES, PLUGIN)
def dependencies(dependencies, **kwargs):
return dependencies + ["django_prometheus"]
Available filters
- PLUGINS_DEPENDENCIES = 'plugins_dependencies'
Called with an empty list, use this filter to append pip dependencies to the list for installation.
- PLUGINS_APPS = 'plugins_apps'
Called with an empty list, use this filter to append apps to INSTALLED_APPS
- MIDDLEWARES_BEFORE = 'middlewares_before'
Called with an empty list, use this filter to prepend middlewares to MIDDLEWARE
- MIDDLEWARES_AFTER = 'middlewares_after'
Called with an empty list, use this filter to append middlewares to MIDDLEWARE
- URLS = 'urls'
Called with an empty list, use this filter to register new urls and views
Write your plugin
Once you know what type of plugin you want to write and what entrypoint you want to use, you can start writing your plugin.
Plugins are made up of the following 3 files:
__init__.py
- indicates that the directory is a Python packagefunkwhale_startup.py
- the file that loads during Funkwhale initializationfunkwhale_ready.py
- the file that loads when Funkwhale is configured and ready
Declare your plugin
You need to declare your plugin and its configuration options so that Funkwhale knows how to load the plugin. To do this, you must declare a new plugins
instance in your funkwhale_startup.py
file.
Your plugins
should include the following information:
Parameter |
Data type |
Description |
---|---|---|
|
String |
The name of your plugin, used in the |
|
String |
The readable label that appears in the Funkwhale frontend |
|
String |
A meaningful description of your plugin and what it does |
|
String |
The version number of your plugin |
|
Boolean |
Whether the plugin is a user-level plugin or a pod-level plugin. See scopes for more information |
|
Array of Objects |
A list of configuration options |
In this example, we declare a new user-level plugin called “My Plugin”. The user can configure a greeting
in the plugin configuration.
# 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",
user=True,
conf=[
# This configuration option is editable by each user
{"name": "greeting", "type": "text", "label": "Greeting", "default": "Hello"},
],
)
Write your plugin logic
Once you’ve declared your plugin, you can write the plugin code in your funkwhale_ready.py
file.
Note
You must import your plugin declaration from your funkwhale_startup.py
file.
In this example, we create a simple API endpoint that returns a greeting to the user. To do this:
We create a new APIView class that accepts a
GET
requestWe read the greeting value from the plugin
conf
We return the greeting value with the user’s username
We register this view at the endpoint
/greeting
# funkwhale_ready.py
from django.urls import path
from rest_framework import response
from rest_framework import views
from config import plugins
# Import the plugin declaration from funkwhale_startup
from .funkwhale_startup import PLUGIN
# Create a new APIView class
class GreetingView(views.APIView):
permission_classes = []
# Register a GET response
def get(self, request, *args, **kwargs):
# Check the conf value of the plugin for the user
conf = plugins.get_conf(PLUGIN["name"], request.user)
if not conf["enabled"]:
# Return an error code if the user hasn't enabled the plugin
return response.Response(status=405)
# Set the greeting value to the user's configured greeting
greeting = conf["conf"]["greeting"]
data = {
# Append the user's username to the greeting
"greeting": "{} {}!".format(greeting, request.user.username)
}
# Return the greeting
return response.Response(data)
# Register the new APIView at the /greeting endpoint
@plugins.register_filter(plugins.URLS, PLUGIN)
def register_view(urls, **kwargs):
return urls + [
path('greeting', GreetingView.as_view())
]
Result
Here is an example of how the above plugin works:
User “Harry” enables the plugin
“Harry” changes the greeting to “You’re a wizard”
“Harry” visits the
/greeting
endpoint in their browserThe browser returns the message “You’re a wizard Harry”