Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
43 changes: 43 additions & 0 deletions docs/building_plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,53 @@ plugins/{plugin_id}/
├── plugin-info.json # Plugin manifest file
├── icon.png # Plugin icon
├── settings.html # Optional: Plugin settings page (if applicable)
├── api.py # Optional: API routes (see Adding API routes below)
├── render/ # Optional: If generating images from html and css files, store them here
└── {other files/resources} # Any additional files or resources used by the plugin
```

## Adding API routes (optional)

If your plugin needs its own HTTP endpoints (for example, to receive webhooks, to be controlled by external automation, or to offer a small JSON API), you can register a Flask Blueprint. The core will discover and register it at startup so your routes are available without any changes to InkyPi core code.

**When to use this:**

- Your plugin needs endpoints that other systems call (e.g. Home Assistant, Node-RED, IFTTT, or custom scripts).
- You want to receive webhooks or callbacks (e.g. calendar events, GitHub notifications).
- You need a minimal API for your plugin's config or actions (e.g. backup tools, dashboards).

**How it works:** InkyPi calls `register_plugin_blueprints(app)` after loading plugins. For each plugin that implements a `get_blueprint()` class method returning a Flask Blueprint, that blueprint is registered with the app. This is optional; plugins that don't implement `get_blueprint()` are unchanged.

### Implementing API routes

1. **Create a Blueprint** in your plugin directory (e.g. `api.py`):

```python
from flask import Blueprint, request, jsonify

myplugin_bp = Blueprint("myplugin_api", __name__)

@myplugin_bp.route("/myplugin-api/do-something", methods=["POST"])
def do_something():
data = request.get_json() or {}
# Your logic here
return jsonify({"success": True})
```

2. **Expose the Blueprint** from your plugin class by implementing `get_blueprint()`:

```python
class MyPlugin(BasePlugin):
@classmethod
def get_blueprint(cls):
from . import api
return api.myplugin_bp
```

3. **Naming convention:** Use a URL prefix that includes your plugin id (e.g. `/<plugin_id>-api/...`) to avoid clashes with core and other plugins.

The Plugin Manager plugin is a full example: it uses a blueprint to expose install, uninstall, and update endpoints under `/pluginmanager-api/`.

## Prepopulating forms for Plugin Instances

When a plugin is added to a playlist, a "Plugin Instance" is created, and its settings are stored in the `src/config/device.json` file. These settings can be updated from the playlist page, so the form in settings.html should be prepopulated with the existing settings.
Expand Down
5 changes: 4 additions & 1 deletion src/inkypi.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from blueprints.playlist import playlist_bp
from blueprints.apikeys import apikeys_bp
from jinja2 import ChoiceLoader, FileSystemLoader
from plugins.plugin_registry import load_plugins
from plugins.plugin_registry import load_plugins, get_plugin_instance, register_plugin_blueprints
from waitress import serve


Expand Down Expand Up @@ -81,6 +81,9 @@
app.register_blueprint(playlist_bp)
app.register_blueprint(apikeys_bp)


# Register blueprints from plugins (generic mechanism - any plugin can expose blueprints)
register_plugin_blueprints(app)
# Register opener for HEIF/HEIC images
register_heif_opener()

Expand Down
21 changes: 20 additions & 1 deletion src/plugins/plugin_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,23 @@ def get_plugin_instance(plugin_config):
# Initialize the plugin with its configuration
return plugin_class
else:
raise ValueError(f"Plugin '{plugin_id}' is not registered.")
raise ValueError(f"Plugin '{plugin_id}' is not registered.")

def register_plugin_blueprints(app):
"""Register blueprints from plugins that expose them via get_blueprint() method.

This is a generic mechanism that allows any plugin to register Flask blueprints
by implementing a get_blueprint() class method that returns a Blueprint instance.

Args:
app: Flask application instance to register blueprints with
"""
for plugin_id, plugin_instance in PLUGIN_CLASSES.items():
try:
if hasattr(plugin_instance, 'get_blueprint'):
bp = plugin_instance.get_blueprint()
if bp:
app.register_blueprint(bp)
logger.info(f"Registered blueprint for plugin '{plugin_id}'")
except Exception as e:
logger.warning(f"Failed to register blueprint for plugin '{plugin_id}': {e}")