Zum Inhalt

Plugin-Entwicklerhandbuch

Dieses Handbuch erklärt, wie Plugins für Bibliogon entwickelt werden. Plugins erweitern die Plattform mit neuen Funktionen, ohne den Kern zu verändern.

Architekturüberblick

Bibliogon verwendet PluginForge (PyPI) als Plugin-Framework, basierend auf pluggy. Plugins sind eigenständige Python-Pakete, die über Entry Points entdeckt werden.

Frontend (React) -> Backend (FastAPI) -> PluginForge -> Dein Plugin

Jedes Plugin kann: - API-Endpunkte hinzufügen (FastAPI-Router) - Hooks implementieren (Inhaltstransformation, Exportformate) - UI-Erweiterungen deklarieren (Seitenleistenaktionen, Toolbar-Buttons, Einstellungen, Seiten) - Eigene Konfiguration mitbringen (YAML)

Verzeichnisstruktur

plugins/bibliogon-plugin-{name}/
  bibliogon_{name}/
    __init__.py
    plugin.py          # Plugin-Klasse (erforderlich)
    routes.py          # FastAPI-Router (optional)
    {modul}.py         # Geschäftslogik-Module
  tests/
    test_{name}.py
  pyproject.toml       # Paketmetadaten + Entry Point (erforderlich)

Namenskonventionen: - Plugin-Ordner: bibliogon-plugin-{name} (Kebab-Case) - Python-Paket: bibliogon_{name} (Snake-Case) - Plugin-Name im Code: {name} (Kleinbuchstaben, z.B. "help", "export", "grammar")

Minimales Plugin

pyproject.toml

[tool.poetry]
name = "bibliogon-plugin-meinplugin"
version = "1.0.0"
description = "Mein eigenes Bibliogon-Plugin"
authors = ["Dein Name"]
license = "MIT"
packages = [{include = "bibliogon_meinplugin"}]

[tool.poetry.dependencies]
python = "^3.11"
pluginforge = "^0.5.0"
fastapi = "^0.115.0"

[tool.poetry.plugins."bibliogon.plugins"]
meinplugin = "bibliogon_meinplugin.plugin:MeinPlugin"

Der Entry Point [tool.poetry.plugins."bibliogon.plugins"] ist der Mechanismus, über den PluginForge das Plugin entdeckt.

Plugin im Backend registrieren

Für gebündelte Plugins (jedes Plugin, das innerhalb des Bibliogon-Repositorys unter plugins/ ausgeliefert wird) muss zusätzlich ein Path-Dependency-Eintrag in backend/pyproject.toml angelegt werden, damit das Backend-Poetry-Environment das Plugin installiert und dessen Entry Points auffindbar werden:

[tool.poetry.dependencies]
# ...vorhandene Einträge...
bibliogon-plugin-myplugin = {path = "../plugins/bibliogon-plugin-myplugin", develop = true}

Anschließend poetry lock und poetry install im Verzeichnis backend/ ausführen. Wenn dieser Schritt vergessen wird, ist das Plugin in CI unsichtbar (lokal funktioniert es für alle, deren venv die dist-info aus einer früheren Installation noch enthält, aber frische Checkouts und der CI-Runner laden nur, was in pyproject.toml deklariert ist). ZIP-distribuierte Drittanbieter-Plugins sind ausgenommen, weil sie zur Laufzeit über sys.path installiert werden, nicht zur Setup-Zeit.

plugin.py

from typing import Any
from pluginforge import BasePlugin


class MeinPlugin(BasePlugin):
    name = "meinplugin"
    version = "1.0.0"
    api_version = "1"
    license_tier = "core"           # In Bibliogon ist "core" der einzige verwendete Wert; alle Plugins sind frei nutzbar.
    depends_on: list[str] = []      # z.B. ["export"] wenn Export-Plugin benötigt

    def activate(self) -> None:
        """Wird beim Laden des Plugins aufgerufen."""
        from .routes import set_config
        set_config(self.config)

    def get_routes(self) -> list[Any]:
        """FastAPI-Router zurückgeben."""
        from .routes import router
        return [router]

    def get_frontend_manifest(self) -> dict[str, Any] | None:
        """UI-Erweiterungen deklarieren. None wenn kein UI."""
        return None

routes.py

from fastapi import APIRouter

router = APIRouter(prefix="/meinplugin", tags=["meinplugin"])

_config: dict = {}

def set_config(config: dict) -> None:
    global _config
    _config = config


@router.get("/hello")
def hello():
    return {"message": "Hallo von meinem Plugin!"}

Regeln: - routes.py enthält NUR Endpunkt-Definitionen, die an Service-Funktionen delegieren - Geschäftslogik gehört in separate Module (z.B. service.py, analyzer.py) - Kein direkter Datenbankzugriff in Routes; Service-Funktionen verwenden - Pydantic v2 für alle Request/Response-Schemas

Hooks

Plugins können Hooks implementieren, die in backend/app/hookspecs.py definiert sind. Hooks ermöglichen die Teilnahme an Kernabläufen ohne Änderung des Kerncodes.

Verfügbare Hooks

Hook Zweck Rückgabe
export_formats() Unterstützte Exportformate deklarieren list[dict]
export_execute(book, fmt, options) Export ausführen (erstes Ergebnis gewinnt) Path oder None
chapter_pre_save(content, chapter_id) Inhalt vor dem Speichern transformieren str oder None
content_pre_import(content, language) Markdown beim Import transformieren str oder None

Hook implementieren

In der plugin.py eine Methode mit dem Hook-Namen hinzufügen:

class MeinPlugin(BasePlugin):
    name = "meinplugin"

    def content_pre_import(self, content: str, language: str) -> str | None:
        """Importiertes Markdown vor der Konvertierung bereinigen."""
        cleaned = content.replace("\r\n", "\n")
        return cleaned

Konfiguration

Plugin-Konfiguration liegt unter backend/config/plugins/{name}.yaml.

YAML-Struktur

plugin:
  name: "meinplugin"
  display_name:
    de: "Mein Plugin"
    en: "My Plugin"
  description:
    de: "Beschreibung des Plugins"
    en: "Plugin description"
  version: "1.0.0"
  license: "MIT"
  depends_on: []
  api_version: "1"

settings:
  meine_option: true
  schwellwert: 0.8

Auf Konfiguration zugreifen

def activate(self) -> None:
    schwellwert = self.config.get("settings", {}).get("schwellwert", 0.5)

Sichtbarkeitsregeln für Einstellungen

Jede Einstellung in der YAML muss entweder: 1. Im Plugin-UI editierbar sein (Einstellungen > Plugins > {Name}), ODER 2. Mit # INTERNAL Kommentar markiert sein

Versteckte Einstellungen, die Nutzerverhalten beeinflussen, sind nicht erlaubt.

Frontend-Manifest

Plugins deklarieren UI-Erweiterungen über get_frontend_manifest(). Das Frontend fragt /api/plugins/manifests ab, um alle Erweiterungen zu entdecken.

Verfügbare UI-Slots

Slot Position Anwendungsfall
pages App-Navigation Vollständige Plugin-Seite
sidebar_actions BookEditor-Seitenleiste Aktionsbuttons
toolbar_buttons Editor-Toolbar Formatierungstools
editor_panels Neben dem Editor Seitenpanels
settings_section Einstellungen > Plugins Plugin-Konfiguration
export_options Export-Dialog Formatspezifische Optionen

Beispiel: Seite hinzufügen

def get_frontend_manifest(self) -> dict[str, Any] | None:
    return {
        "pages": [
            {
                "id": "meinplugin",
                "path": "/meinplugin",
                "label": {"de": "Mein Plugin", "en": "My Plugin"},
                "icon": "puzzle",  # lucide-react Icon-Name
            },
        ],
    }

ZIP-Distribution

Plugins von Drittanbietern werden als ZIP-Dateien verteilt und über Einstellungen > Plugins installiert.

ZIP-Struktur

meinplugin.zip
  plugin.yaml          # Erforderlich: Plugin-Metadaten
  bibliogon_meinplugin/
    __init__.py
    plugin.py
    routes.py
  config/
    meinplugin.yaml    # Plugin-Konfiguration

plugin.yaml (erforderlich für ZIP-Plugins)

name: meinplugin
display_name:
  de: "Mein Plugin"
  en: "My Plugin"
version: "1.0.0"
package: bibliogon_meinplugin
entry_class: MeinPlugin

Namensvalidierung

Plugin-Namen müssen dem Muster entsprechen: [a-z][a-z0-9_-]{1,48}[a-z0-9] (3-50 Zeichen, Kleinbuchstaben, Ziffern, Bindestriche, Unterstriche).

Tests

Plugin-Tests liegen unter plugins/bibliogon-plugin-{name}/tests/.

# Tests für ein bestimmtes Plugin
make test-plugin-{name}

# Alle Plugin-Tests
make test-plugins

Vorhandene Plugins als Referenz

Plugin Komplexität Gutes Beispiel für
help Einfach Routes + Config + i18n
ms-tools Mittel Hooks + Per-Book-Einstellungen + UI-Panel
export Komplex Mehrere Formate + Async-Jobs + Scaffolding
audiobook Komplex Externe APIs + SSE-Fortschritt + Persistenz

Beginne mit dem Help-Plugin als Vorlage, dann ms-tools für Hook-Implementierungsmuster.