-
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. Die Registrierung genügt: GET /api/search/sources listet sie, und die WebUI fächert jedes Stichwort auf genau diese Liste auf — die Quelle bekommt also ihren eigenen Ergebnisabschnitt, einen Chip unter dem Suchfeld und eine An/Aus-Zeile in Einstellungen → Quellen. adult=True stellt sie hinter die 18+-Bestätigung; enabled_key= nutzt einen Einstellungsschlüssel, den das Modul schon besitzt; media_types= deklariert, was die Seite überhaupt führt (siehe unten). Die site_id muss zu [a-z0-9][a-z0-9_-]{1,39} passen |
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 und eine Zeile im DNS-Test (beide lesen denselben _MONITOR_SITES-Eintrag) -- ein Aufruf deckt beides ab. enabled_setting_default=False übergeben, wenn der enabled_setting_key opt-in ist; Standard ist True (eine installierte Modul-Quelle ist an) |
register_image_hosts |
web.routes.image_proxy |
Eigene Bild-CDN-Host(s)/Domain(s) auf der Allowlist des /api/img-Proxys -- erforderlich, damit eine vom Modul zurückgegebene poster_url überhaupt lädt, da der Proxy nur von erlaubten Hosts abruft |
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/.
register_search_source(
item_id, site_id, search_fn,
label=None, adult=False, enabled_key=None,
media_types=None, # z. B. ["movies"] oder ["series"]
)media_types ist eine Liste aus beliebigen der Werte "movies", "series" und
"adult". Die Angabe ist optional; ohne sie gelten Filme und Serien — denn
eine Quelle, die aus einer Abfrage herausfällt, ist eine Quelle, die
stillschweigend nicht funktioniert, während eine Quelle, die nach einem
Medientyp gefragt wird, den sie nicht hat, einfach nichts zurückgibt.
Genutzt wird es überall dort, wo eine Abfrage schon weiß, was sie sucht, und keine Anfrage an eine Quelle verschwenden soll, die es gar nicht haben kann. Aktueller Fall ist die „Streams finden"-Suche auf der Seerr-Anfragenseite: Eine Filmanfrage fragt nur Filmquellen, eine Serienanfrage nur Serienquellen. Die Deklaration spart der eigenen Seite also eine Anfrage pro solcher Suche — das normale Suchfeld fragt weiterhin jede aktive Quelle, denn ein Stichwort sagt nichts darüber, was es ist.
Serverseitig stehen die Entsprechungen der eingebauten Quellen in
BUILTIN_SOURCE_MEDIA_TYPES in web/source_policy.py, und
GET /api/search/sources liefert media_types pro Quelle mit, damit ein Client
dieselbe Entscheidung treffen kann.
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) |
Modulmanager → Modul-Einstellungen | Wird auf "thirdparty" normalisiert und gespeichert, rendert aber nichts mehr |
"settings" |
Modulmanager → Modul-Einstellungen | Wird ignoriert (die Seite gruppiert nach Host) |
"notifications" |
Ein eigener Tab plus die Liste der Modul-Einstellungen | Eine eingebaute Kanal-Id oder der blanke Standard wird zu module_<item_id>; eine eigene Id bleibt |
"monitoring" |
Ein eigener Tab plus die Liste der Modul-Einstellungen | Dieselbe Regel |
Der Third-Party-Tab der Integrationsseite rendert überhaupt keine Modul-Karten mehr — er ist jetzt nur noch für die Integrationen, die MediaForge selbst mitbringt (Crunchyroll, Fernsehserien.de, ComicVine …). Ein Modul wird im Modulmanager installiert, aktualisiert und entfernt, also wird es dort auch konfiguriert; zwei Seiten, die dieselben Werte bearbeiten, sind zwei Stellen zum Nachsehen und eine zum Vergessen.
„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.
Jede Modul-Karte steht unter Modulmanager → Modul-Einstellungen, nach Host
gruppiert — bei notifications/monitoring zusätzlich zur Karte auf der
eigenen Seite. Keine Kopie: beide Stellen fahren über dieselbe
/api/settings/thirdparty/<id>-API.
Der Knopf „Modul öffnen" im Modulmanager folgt derselben Teilung: Er verlinkt
für die Hosts integrations und settings auf /module-settings?open=<id> und
nur für notifications/monitoring auf <Seite>?open=<id>#<Tab>.
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. -
Das geteilte Serien-/Detail-Modal (
templates/shared_modals.html): Ein schlichtes{% include "shared_modals.html" %}im eigenen Template genügt — die<option>-Werte für Sprache und Hoster (lang_labels,sto_lang_labels,supported_providers), die es braucht, kommen jetzt aus einem globalen Context-Processor, also ohne View-Argumente und ohne Änderung an der Route. Vorher übergaben vier Routen sie und alle anderen nicht — das ergab zwei leere Auswahlfelder, ohne irgendeinen Fehler.supported_providerswird pro Request gelesen, ein überregister_hoster()ergänzter Hoster erscheint also ohne Neustart.
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
badge_suffix="", # OPTIONAL, eine Einheit ("%", "GB"), max. 4 Zeichen
badge_tone="info", # OPTIONAL: info | err | level | muted
admin_only=False,
icon="M3 6h18M3 12h18", # OPTIONAL, SVG-Pfaddaten für einen 24x24-Pfad
)Was man wissen sollte:
-
Ein Badge ist eine Art von Zahl —
badge_tonesagt welche.info(Standard) ist eine To-do-Zahl,errheißt "etwas ist kaputt",levelist ein Prozentwert (ab 90 gelb, ab 95 rot; zusammen mitbadge_suffix="%"verwenden),mutedist eine reine Gesamtzahl. Ohne Einheit liest man jedes Badge als "so viele Dinge warten auf mich" — bei einem Füllstand ist das falsch, und genau deshalb hatte der eingebaute Speicher-Knopf vorher gar kein Badge. -
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
- Operations
- Audit Log
- Rules & Languages
- 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
- Betrieb
- Audit-Log
- Regeln & Sprachen
- Docker
- Unterstützte Seiten
Entwickler