-
Notifications
You must be signed in to change notification settings - Fork 2
Modul API DE
🌐 English · Deutsch
Alles, was ein Modul bei MediaForge registrieren kann, an einer Stelle. Die
vollständige Referenz — mit einem lauffähigen Beispielmodul je Einstiegspunkt —
steht in
.examples/thirdparties/README.md
im Repository; diese Seite ist die Übersicht dazu.
Ein Modul ist ein Ordner unter web/thirdparties/<name>/ mit einer Funktion
register(app). Alles Folgende wird von dort aufgerufen.
| Funktion | Modul | Was sie hinzufügt |
|---|---|---|
register_thirdparty |
web.thirdparties.registry |
Das Modul selbst: Menüeintrag, Einstellungskarte, Dashboard-Widget, extra_settings-Felder, Ein/Aus-Schalter. Siehe „Wo eine Einstellungskarte landet" unten |
register_provider |
providers |
Eine Inhaltsquelle (Seite) mit eigenen Serien-/Staffel-/Episoden-Klassen |
register_search_source |
search |
Eine zusätzliche Quelle für die globale Suche |
register_home_feed_source |
home_feed |
Zeilen und einen Chip für diese Quelle auf der neuen Startseite |
register_home_panel |
home_panels |
Einen Knopf in der Leiste der Startseite, mit eigenem Feld und optionalem Zähler-Badge |
register_hoster |
extractors |
Einen Hoster/Extractor, der eine Embed-URL zu einem Stream auflöst |
register_site_mirrors |
mirrors |
Mirror-Domains einer Seite, mit automatischem Failover |
register_cineinfo_source |
web.cineinfo.registry |
Eine Metadatenquelle neben TMDB |
register_subtitle_source |
subtitle_sources |
Eine externe Untertitelquelle im Download-Pfad, neben der eingebauten OpenSubtitles-Abfrage |
register_monitor_site |
web.uptime_monitor |
Eine Karte auf dem UpTime-Dashboard, geprüft wie die eingebauten Seiten |
register_notification_channel |
web.thirdparties.registry |
Einen Benachrichtigungskanal neben Telegram/Pushover/Discord/ntfy |
register_event_hook |
web.thirdparties.registry |
Einen Callback auf App-Ereignisse (Download fertig, Queue-Eintrag fehlgeschlagen, …) |
register_background_worker |
web.thirdparties.registry |
Einen Worker-Thread, der mit dem Modul startet und stoppt |
register_backup_category |
web.backup |
Eigene Daten im Voll-/Teil-Backup |
register_sensitive_keys |
web.db |
Einstellungsschlüssel, die verschlüsselt gespeichert werden |
register_ui_pref_key |
web.db |
Eine UI-Einstellung pro Benutzerkonto |
register_restart_handler (web.restart) gehört nicht zu dieser API — die
Funktion ist Core-intern und wird nur von web/app.py aufgerufen.
Keine Registrierungen — das sind Core-Dienste, die ein Modul aufruft. Alles
andere in web/, das mit einem Unterstrich beginnt, ist Core-intern und kann
sich jederzeit ändern; wer so etwas importieren möchte, fragt besser nach einer
öffentlichen Variante.
| Funktion | Modul | Zweck |
|---|---|---|
lookup_media |
web.tmdb_cache |
Gecachte, ratenbegrenzte TMDB-Abfrage für einen Titel oder eine IMDB-ID |
is_tmdb_configured |
web.tmdb_cache |
Ob überhaupt ein TMDB-API-Key hinterlegt ist |
get_setting / set_setting
|
web.db |
Eine einzelne Einstellung lesen/schreiben |
get_json_setting / set_json_setting
|
web.db |
Eine Listen- oder Dict-Einstellung lesen/schreiben |
from ...web.tmdb_cache import lookup_media
info = lookup_media("Dark", media_type="tv", require_confident=True)
if info:
tmdb_id = info["tmdb_id"]
plot = info["overview"]Liefert das Metadaten-Dict (tmdb_id, media_type, title, overview,
genres, providers, fsk, vote_average, trailer_key, recommendations,
raw_details) oder None, wenn TMDB nicht konfiguriert ist, nichts
gefunden wurde oder das Ergebnis herausgefiltert wurde. Es gibt keinen
{"found": False}-Fall, den man prüfen müsste.
-
media_type="movie"/"tv"verlangt genau diese Art von Treffer. -
require_confident=Trueverlangt, dass der zurückgegebene Titel wirklich zum angefragten passt. Die TMDB-Suche antwortet auf fast jede Anfrage mit irgendetwas — also einschalten, sobald das Ergebnis angezeigt wird und nicht nur die ID benutzt wird. - API-Key, Anbieterland und UI-Sprache werden automatisch aufgelöst.
cineinfo_tmdb_api_keyselbst auszulesen, ist nicht nötig.
Ergebnisse werden 24 h gecacht und prozessweit ratenbegrenzt; die Funktion in
einer Schleife aufzurufen, ist also in Ordnung. Sie macht blockierendes
Netzwerk-I/O — größere Mengen gehören in einen
register_background_worker, nicht in einen Request-Handler.
from ...web.db import get_json_setting, set_json_setting
rooms = get_json_setting("module:my_mod:rooms", [])
rooms.append(name)
set_json_setting("module:my_mod:rooms", rooms)json.dumps/json.loads rund um set_setting bitte nicht selbst bauen. Ein
fehlender, leerer, ungültiger oder falsch geformter Wert wird geloggt und
liefert den Standardwert zurück — eine kaputte Zeile liest sich damit als „nicht
gesetzt", statt mitten im Request eine Ausnahme zu werfen. Der Standardwert wird
kopiert, nie geteilt, und Umlaute bleiben lesbar gespeichert.
Jeder Einstellungsschlüssel wird für sich geschrieben — set_setting ist ein
Upsert auf genau einen Schlüssel, das Speichern eines Werts kann also nie einen
anderen leeren. Einen Sammel-Aufruf „alle meine Einstellungen schreiben" gibt es
bewusst nicht.
Der Download-Pfad sammelt Untertitel in drei Durchgängen: die Fassungen von
yt-dlp, die Player-Konfiguration des Hosters und — nur für die Sprachen, die
dann noch fehlen — eine externe Abfrage. OpenSubtitles.com ist die eingebaute
Umsetzung dieses dritten Durchgangs; register_subtitle_source erlaubt es einem
Modul, seinen eigenen Dienst (einen privaten Server, einen Fansub-Index, eine
kostenpflichtige API) in denselben Schritt einzuhängen.
from ....subtitle_sources import register_subtitle_source
def fetch(video_path, have_langs, meta):
# have_langs: ISO-639-2/B-Tags, die die Datei bereits hat -- diese nie
# erneut laden. meta: {"query", "season", "episode", "imdb_id",
# "tmdb_id"}, jeder Wert moeglicherweise None. Zurueckgegeben werden die
# geschriebenen Sidecar-Dateien, benannt als <Videoname>.<Sprache>.<Endung>;
# der bestehende Mux-Pfad nimmt sie von dort auf.
if "ger" in have_langs:
return []
return []
register_subtitle_source(MODULE_ID, "myservice", "My Subtitle Service", fetch)-
item_idist die id, die das Modul bereits anregister_thirdparty()übergeben hat. Die Registrierung wird darüber verwaltet, also entferntunregister_module()sie automatisch, sobald das Modul deaktiviert oder deinstalliert wird; der Modulmanager führt die Fähigkeit alssubtitle_source→ „Untertitelquelle" auf. -
source_iddarf weder mit einer eingebauten Quelle (RESERVED_SOURCE_IDS = {"opensubtitles"}) noch mit der Quelle eines anderen Moduls kollidieren — beides löst eine Ausnahme aus. -
fetchläuft im Queue-Worker zwischen Download und ffmpeg-Mux und hält damit genau diese Episode auf: ein paar HTTP-Anfragen mit kurzen Timeouts, mehr nicht. Die Funktion darf nicht werfen — Ausnahmen werden gefangen und geloggt, eine Quelle, die jedes Mal wirft, ist aber nur Ballast. - Gegenstücke:
unregister_subtitle_source(item_id),thirdparty_subtitle_source_ids(),iter_subtitle_sources().
Referenzmodul: .examples/thirdparties/example_subtitle_source/.
settings_host wählt die Seite; die Seite wählt den Platz. settings_tab
ist ein Wunsch, den der Host überstimmen darf — angewendet bei der
Registrierung durch registry._placed_tab(), damit alles Nachgelagerte die
tatsächliche Platzierung sieht:
settings_host |
Karte landet | settings_tab |
|---|---|---|
"integrations" (Standard) |
Immer auf dem Third-Party-Tab | Wird ignoriert, stillschweigend |
"notifications" |
Immer ein eigener Tab | Eine eingebaute Kanal-Id oder der blanke Standard wird zu module_<item_id>; eine eigene Id bleibt |
"monitoring" |
Immer ein eigener Tab | Dieselbe Regel |
"settings" |
Modulmanager → Modul-Einstellungen | Wird ignoriert (die Seite gruppiert nach Host) |
„Was haben meine Module dieser Seite hinzugefügt?" hat pro Seite genau eine Antwort — und hört auf, eine zu sein, sobald sich ein Modul auf dem CineInfo-Tab oder in Telegrams Panel verstecken kann. Ein Modul, das einen eigenen Tab will, wählt einen Host, der ihm einen gibt, keine Tab-Id.
Unabhängig davon steht jede Modul-Karte zusätzlich unter Modulmanager →
Modul-Einstellungen, nach Host gruppiert. Keine Kopie: beide Stellen fahren
über dieselbe /api/settings/thirdparty/<id>-API.
Ein "secret"-Feld, ein Schlüssel in MODULE_SENSITIVE_SETTINGS und alles, was
an register_sensitive_keys() übergeben wird, liegt verschlüsselt in der
Datenbank, und die generische Settings-API liefert statt des Werts eine Maske
(registry.SECRET_MASK) — ein PUT, das die Maske zurückschickt, bedeutet
„unverändert". Das gilt für jeden Schlüssel, den MediaForge als sensibel
kennt, nicht nur für Felder mit type="secret": ein als normales Textfeld
angezeigtes Secret wird ebenfalls maskiert. Im Modul selbst ändert sich nichts,
get_setting() entschlüsselt wie gewohnt. Wer einen solchen Wert auf einer
eigenen Seite ausgibt, sollte es genauso halten — ein gespeichertes Secret
gehört nie ins HTML.
Statt eigenem CSS besser das vorhandene Vokabular nutzen:
-
Formular-Elemente (
forms.css):.chb-mainfür Checkboxen,.togglefür Ein/Aus-Schalter,.mf-segmented,.mf-multiselect,.mf-chip. -
Layout und Inhalt (
mf_components.css, global geladen):.mf-search,.mf-toolbar,.mf-poster-grid,.mf-timeline,.mf-progress,.mf-empty,.mf-pagination-bar. -
Farben (
variables.css): niemals Hex-Werte hart schreiben. Status-Pills nutzen das Paar--success+--success-bg(analog--warning,--error,--info); beide Hälften sind je Theme definiert. -
Multi-Select (
mf_multiselect.js, auf jeder Seite geladen):data-mf-multiselectan ein.mf-multiselect-Wurzelelement schreiben — Öffnen/Schließen, das Zusammenfassungs-Label im Trigger, Klick nach außen bzw. Escape und die Positionierung (wird in scrollenden Containern nicht abgeschnitten) laufen dann von selbst, ohne Init-Aufruf, also auch für später per JS gerendertes Markup. Den Text überdata-none-label/data-many-label/data-max-namessteuern und aufmf-multiselect-change/mf-multiselect-closeam Wurzelelement lauschen (detail: {values, labels}, bubbelnd). Helfer:window.mfMultiSelect.values(),.labels(),.refresh(),.open(),.close(),.closeAll(). -
Escaping (
mf_escape.js, auf jeder Seite geladen):window.mfEscape()für alles, was ins HTML geht — quote-sicher, deckt also auch Attribute ab — undwindow.mfSafeUrl()fürhref/src. Keinen eigenen Escaper schreiben. -
Polling (
mf_poll.js):window.mfPoll(fn, ms)stattsetInterval, damit der Timer pausiert, solange der Tab versteckt ist.
Modul-Templates verwenden {{ _('...') }} wie der Core; ein Modul bringt
seinen eigenen Katalog unter <modul>/translations/<locale>/LC_MESSAGES/ mit.
Nach dem Bearbeiten einer .po muss pybabel compile laufen — ohne diesen
Schritt bleibt die Änderung zur Laufzeit wirkungslos.
Die Startseite hat unter dem Suchfeld eine Knopfleiste und darunter ein Feld, dessen Inhalt vom gewählten Knopf abhängt (Warteschlange, Aktivität, Mediathek und — für Admins — Speicher und System). Ein Modul ergänzt seinen eigenen:
from mediaforge.home_panels import register_home_panel
def my_panel():
return {
"stats": [{"label": "Wartet", "value": "3", "tone": "warn"}],
"items": [{"title": "Irgendwas", "sub": "vor 2 Min.",
"percent": 40, "href": "/mymodule", "tone": "ok"}],
"link": {"href": "/mymodule", "label": "Öffnen"},
"empty": "Nichts zu tun.",
}
register_home_panel(
item_id="mymodule", # die id aus register_thirdparty()
panel_id="mymodule", # eindeutig; die eingebauten ids sind reserviert
label="Mein Modul",
view=my_panel, # wird nur beim Öffnen aufgerufen
badge=lambda: 3, # OPTIONAL, läuft bei jedem Startseitenaufruf
badge_label="{} Aufträge warten", # OPTIONAL, der Tooltip zum Badge
admin_only=False,
icon="M3 6h18M3 12h18", # OPTIONAL, SVG-Pfaddaten für einen 24x24-Pfad
)Was man wissen sollte:
-
Ein Badge ohne
badge_labelist ein Rätsel. Der Text wird zum Tooltip des Knopfes und zu seinem zugänglichen Namen,{}wird durch die Zahl ersetzt. Der eingebaute System-Knopf kam ohne aus, und seine „58" wurde für eine Versionsnummer und für einen Fehlercode gehalten, bevor jemand herausfand, dass sie fehlgeschlagene Downloads zählt. Übersetzt schicken, wielabel. -
viewist faul,badgenicht. Der Feldinhalt wird geholt, wenn der Nutzer das Feld öffnet, und danach alle 20 s aktualisiert (nur solange der Tab sichtbar ist). Das Badge läuft bei jedem Aufruf der Startseite für jedes registrierte Feld — also ein billiger COUNT, niemals ein Netzwerkaufruf. -
Text, kein Markup. Jeder String wird clientseitig escaped. Unbekannte
Schlüssel fallen weg,
percentwird auf 0–100 begrenzt, undhrefmuss ein seiteninterner Pfad sein (/library); absolute oder protokollrelative URLs werden entfernt. Höchstens 12 Einträge und 6 Kennzahlen pro Feld. -
actionstatthreffür Modals. Manches ist keine Seite: die Warteschlange ist ein Modal ausbase.html, eine Route/queuegibt es nicht."action": "queue"öffnet den Warteschlangen-Hub; die Liste der erlaubten Aktionen ist fest (PANEL_ACTIONS), ein Feld kann also keine eigene JS-Funktion benennen. - Deine Texte übersetzt du selbst. Die eingebauten Felder schicken i18n-Schlüssel, die das Template auflöst; ein Modul schickt fertigen Text, weil der Core einen Text nicht übersetzen kann, den er nie gesehen hat.
-
admin_only=Truewird serverseitig durchgesetzt, in der Liste und in der Feld-Route — ein Nicht-Admin bekommt 403, nicht nur einen versteckten Knopf. - Ein Feld, das eine Ausnahme wirft, zerlegt die Seite nicht: die Leiste funktioniert weiter, das Feld meldet sich als nicht verfügbar. Genauso bei einem Badge (zählt dann als 0).
- Das Aufräumen läuft über
item_idautomatisch — wird das Modul deaktiviert, verschwindet der Knopf.
Welches Feld zuletzt offen war, steht am Konto — die Startseite kommt so zurück, wie man sie verlassen hat.
🇬🇧 English
Users
- Installation
- Getting Started
- Migration from AniWorld
- Configuration
- Web UI
- Download System
- Download History
- AutoSync
- Calendar
- Library
- Authentication
- Notifications
- Integrations
- SyncPlay
- Anime4K Upscaling
- Encoding
- Modules
- Theme Packs
- Backup
- Docker
- Supported Sites
Developers
🇩🇪 Deutsch
Benutzer
- Installation
- Erste Schritte
- Umzug von AniWorld
- Konfiguration
- Web-UI
- Download-System
- Download-Verlauf
- AutoSync
- Kalender
- Bibliothek
- Authentifizierung
- Benachrichtigungen
- Integrationen
- SyncPlay
- Anime4K-Upscaling
- Encoding
- Module
- Theme-Pakete
- Backup
- Docker
- Unterstützte Seiten
Entwickler