Mods verwalten
Mods verwalten
Vollständige Anleitung zum Installiert-Tab — Anzeigen, Filtern, Aktualisieren, Aktivieren/Deaktivieren, Deinstallieren, Upload-Erkennung, ZIP-Export und alle möglichen Fehler.
Liste installierter Mods
Zwei Quellen:
- Browser-installiert — Vollständiges Tracking (Anbieter, Version, Mod-ID, Download-URL, Update-Erkennung)
- Manuell hochgeladen — Auto-erkannt durch Scanner, als „Manuell / Upload" mit eingeschränktem Tracking
Spalten
| Spalte | Beschreibung |
|---|---|
| Icon | Plattform-Logo (leer für Uploads) |
| Name | Aus JAR-Metadaten oder Dateiname (Versions-Regex entfernt) |
| Status | Aktiv, Deaktiviert oder Unbekannt |
| Anbieter | Modrinth, CurseForge, Modtale oder Upload |
| Version | Installierte Versionsnummer |
| Installiert am | Erster Installationszeitpunkt |
| Neueste Version | Neueste verfügbare auf Plattform (falls erfasst) |
Upload-Erkennung
scanAndRegisterUploadedMods() läuft automatisch:
- Vergleicht Dateien auf der Festplatte (
/mods/) mit Datenbankeinträgen - Neue Dateien: Registriert mit Anbieter
'upload'. JAR wird geöffnet für Namensextraktion - Gelöschte Dateien: Aus Datenbank entfernt (Bereinigung)
- Gleichzeitigkeitsschutz: Atomarer Lock mit 10-Sekunden-TTL
- Einschränkungen: Upload-Mods haben keine Auto-Update-Erkennung
Filtern
| Filter | Zeigt |
|---|---|
| Alle anzeigen | Alle Mods |
| Aktiv | Nur aktivierte Mods |
| Deaktiviert | Nur deaktivierte Mods (mit -bak-Suffix) |
| Updates verfügbar | Nur Mods mit neueren Plattformversionen |
Mods mit Updates werden oben angezeigt, dann alphabetisch.
Mods aktualisieren
Wie Updates erkannt werden
- Plugin fragt Quellplattform per gespeicherter mod_id und provider ab
- Update-Metadaten 24 Stunden gecacht pro Mod
'upload'-Anbieter werden immer übersprungen- Versionsvergleich nutzt Stringvergleich (nicht semantische Versionierung)
Bei gefundenem Update
- „Update verfügbar"-Badge im Installiert-Tab
- Konsolen-Widget: „Mod-Updates verfügbar — Jetzt aktualisieren"
- Spalte „Neueste Version" zeigt neue Versionsnummer
Update durchführen
- Update neben dem Mod klicken
- Neue Version von Plattform heruntergeladen
- Alte Version automatisch entfernt (falls aktiviert)
- Benachrichtigung: „Mod-Installation gestartet"
- Server neustarten um Update zu laden
Konsolen-Widget
- Synchronisiert Plugins beim Laden
- Update-Check mit 15-Minuten-Cache
- Warnung-Badge mit Weiterleitung zum Mod Browser
Mods aktivieren / deaktivieren
Mechanismus im Detail
Dateiumbennung mit -bak-Suffix via Lesen+Schreiben+Löschen (für Daemon-API-Kompatibilität):
| Aktion | Schritte | Beispiel |
|---|---|---|
| Deaktivieren | 1. Datei lesen 2. Als {name}-bak schreiben 3. Original löschen |
sodium-1.0.jar → sodium-1.0.jar-bak |
| Aktivieren | 1. -bak-Datei lesen 2. Als Originalname schreiben 3. -bak löschen |
sodium-1.0.jar-bak → sodium-1.0.jar |
Hinweis: Drei Daemon-API-Aufrufe pro Umschaltvorgang. Server muss ggf. gestoppt werden.
Benachrichtigungen
| Aktion | Erfolg | Fehler |
|---|---|---|
| Deaktivieren | „Mod deaktiviert: [Name]" | „Deaktivierung fehlgeschlagen" + Fehlerbeschreibung |
| Aktivieren | „Mod aktiviert: [Name]" | „Aktivierung fehlgeschlagen" + Fehlerbeschreibung |
Anwendungsfälle
- Absturz-Diagnose: Mods einzeln deaktivieren um Absturzursache zu finden
- Leistungstests: Messen mit/ohne bestimmte Mods
- Konflikt-Erkennung: Verdächtigen Mod deaktivieren
- Vorbereitung: Mods deaktiviert installieren, alle während Wartung aktivieren
Mods deinstallieren
Vorgang
- Deinstallieren klicken → Warnung bestätigen
- JAR-Datei aus
/mods/gelöscht - Datenbankeintrag entfernt
- Benachrichtigung: „Mod deinstalliert"
- Server neustarten um Mod vollständig zu entladen
Was NICHT entfernt wird
| Nicht entfernt | Beispiel | Bereinigung |
|---|---|---|
| Konfigurationsdateien | config/sodium.json |
Dateimanager |
| World-Daten | Modifizierte Chunks/Entities | Nicht rückgängig machbar |
| Server-Daten | SQLite/JSON-Speicher | Dateimanager |
| Log-Einträge | Mod-Ausgaben in Logs | Automatische Rotation |
Mods-Ordner öffnen
Springt zu /mods/ im Dateimanager. Nützlich für:
- Tatsächliche Dateien auf der Festplatte prüfen
- Manuelle Uploads
- Konfigurations-Verzeichnisse nach Deinstallation bereinigen
-bak-Erweiterungen deaktivierter Mods prüfen
ZIP-Export
Vorgang
- Alle als ZIP herunterladen klicken
- Benachrichtigung: „ZIP-Download gestartet"
- Server komprimiert alle Dateien in
/mods/ - JWT-signierte URL generiert (läuft nach 30 Minuten ab)
- Download startet automatisch
Enthält: Alle Mods (aktiv + deaktiviert)
Bei Fehler
- Benachrichtigung: „ZIP-Download fehlgeschlagen"
- Temporären Speicherplatz prüfen
- Große Sammlungen (100+ Mods) können Timeout verursachen
Vollständige Fehlerreferenz
„Installierte Mods konnten nicht gelesen werden"
/mods/-Verzeichnis existiert nicht → Server einmal starten- Verzeichnis nicht lesbar → Daemon-Berechtigungen prüfen
- Festplatte voll
- Daemon-Verbindung unterbrochen
„Mod konnte nicht deaktiviert/aktiviert werden"
- Berechtigungen: Lesen + Schreiben + Löschen auf
/mods/erforderlich - Datei gesperrt: Laufender Server sperrt Dateien → Server zuerst stoppen
- Gleichzeitiger Zugriff: Backup/Dateimanager greift gleichzeitig zu
- Datei nicht gefunden: Extern umbenannt oder gelöscht
- Daemon-Fehler: Jeder der 3 Schritte (Lesen/Schreiben/Löschen) kann fehlschlagen
„Alte Versionen konnten nicht gelöscht werden"
- Alte Datei vom laufenden Server gesperrt → Server stoppen
- Dateiberechtigungsproblem
- Datei bereits manuell entfernt
storage/logs/laravel.logprüfen
Updates werden nicht angezeigt
- Update-Metadaten 24 Stunden gecacht → Cache leeren
'upload'-Mods werden immer übersprungen- Versionsvergleich ist stringbasiert
- Plattform hat Update evtl. noch nicht propagiert
„Deinstallation fehlgeschlagen"
- Datei bereits manuell gelöscht
- Datei vom Serverprozess gesperrt → stoppen und erneut versuchen
- Daemon-Verbindungsproblem
Mod lässt Server nach Installation abstürzen
- Konsolenausgabe / Absturzbericht prüfen — nennt den problematischen Mod
- Mod deaktivieren (nicht löschen!) + neustarten zur Bestätigung
- Abhängigkeiten installiert?
- Mod-Loader-Kompatibilität prüfen (Fabric auf Forge = Absturz)
- Ältere stabile Version versuchen
- Java-Version prüfen (17 für MC 1.18+, 21 für MC 1.21+)
- Issue-Tracker des Mods auf der Plattform prüfen
„Cache leeren fehlgeschlagen"
- Fehler in Benachrichtigung zeigt
$e->getMessage() - SafeCacheService
flush()konnte Schlüssel nicht entfernen storage/framework/cache/muss existieren und beschreibbar sein (0755)- Log: „Error clearing cache and scanning mods"
Interne Provider-Funktionen (Referenz)
Dieser Abschnitt dokumentiert den internen PHP-Code jeder Provider-Integration. Das Verständnis dieser Muster hilft bei der Fehlerdiagnose und erklärt, wie Mods gesucht, aufgelöst und installiert werden.
CurseForge Provider — Vollständige Referenz
API-Endpunkt-Parameter — GET /v1/mods/search
| Parameter | Typ | Pflicht | Standard | Beschreibung |
|---|---|---|---|---|
gameId |
int | Ja | — | Spiel-Kennung (432 = Minecraft, 70216 = Hytale) |
classId |
int | Ja | 9137 | Inhaltsklasse (9137 = Mods, 5 = Plugins, 4471 = Modpacks) |
searchFilter |
string | Nein | — | Volltextsuchbegriff |
sortField |
int | Nein | 2 | Sortierfeld-ID (1–12, siehe Sortiertabelle oben) |
sortOrder |
string | Nein | desc |
asc oder desc |
modLoaderType |
int | Nein | — | Loader-Enum (1=Forge, 4=Fabric, 5=Quilt, 6=NeoForge) |
gameVersion |
string | Nein | — | Minecraft-Versionsfilter (z.B. 1.20.1) |
pageSize |
int | Nein | 20 | Ergebnisse pro Seite (max 50) |
index |
int | Nein | 0 | Offset für Paginierung |
Antwort-Schema — Suchergebnisse
| Feld | Typ | Beschreibung |
|---|---|---|
data[].id |
int | Eindeutige CurseForge Mod-ID |
data[].name |
string | Mod-Anzeigename |
data[].slug |
string | URL-sicherer Slug für Mod-Seitenlink |
data[].summary |
string | Kurzbeschreibung |
data[].downloadCount |
int | Gesamte Download-Anzahl |
data[].logo.url |
string | Mod-Icon-URL |
data[].categories[].name |
string | Kategorie-Labels |
data[].latestFilesIndexes[].gameVersion |
string | Minecraft-Version |
data[].latestFilesIndexes[].fileId |
int | Neueste Datei für diese Version |
data[].dateModified |
string | ISO 8601 Zeitstempel der letzten Aktualisierung |
data[].authors[].name |
string | Autor-Anzeigename |
pagination.totalCount |
int | Gesamtergebnisse (max 10.000) |
pagination.index |
int | Aktueller Seiten-Offset |
Rate-Limits
| Stufe | Limit | Hinweise |
|---|---|---|
| Kostenloser Key (Standard) | ~1.000 Anfragen/Stunde | Ausreichend für normalen Gebrauch |
| Bei Überschreitung | HTTP 429 | Warten gemäß Retry-After-Header (Sekunden) |
// Such-Anfrage — der vollständige interne Ablauf
$response = Http::withHeaders(['x-api-key' => $apiKey])
->timeout($timeout)
->get('https://api.curseforge.com/v1/mods/search', [
'gameId' => $gameId, // 432 für Minecraft, 70216 für Hytale
'classId' => 9137, // Mods-Klasse (NICHT 5=Plugins, NICHT 4471=Modpacks!)
'searchFilter' => $query,
'sortField' => $query ? 1 : 2, // Featured beim Suchen, Popularität beim Browsen
'sortOrder' => 'desc',
'modLoaderType' => $loaderEnum,
'gameVersion' => $mcVersion,
'pageSize' => $perPage,
'index' => ($page - 1) * $perPage,
]);
// Antwort-Verarbeitung — wie Ergebnisse für die Oberfläche aufbereitet werden
$results = $response->json('data', []);
$mods = collect($results)->map(function ($mod) {
// Unterstützte MC-Versionen aus gameVersions per Regex extrahieren
$versions = collect($mod['latestFilesIndexes'] ?? [])
->pluck('gameVersion')
->filter(fn ($v) => preg_match('/^\d+\.\d+/', $v))
->unique()->sort()->values();
return [
'id' => $mod['id'],
'name' => $mod['name'],
'slug' => $mod['slug'],
'summary' => $mod['summary'],
'author' => $mod['authors'][0]['name'] ?? 'Unknown',
'downloads' => $mod['downloadCount'],
'icon_url' => $mod['logo']['url'] ?? null,
'updated_at' => $mod['dateModified'],
'versions' => $versions->toArray(),
'provider' => 'curseforge',
];
});
// Abhängigkeitsauflösung — rekursiv mit relationType-Prüfung
foreach ($file['dependencies'] as $dep) {
if ($dep['relationType'] === 3) { // 3 = Pflicht-Abhängigkeit
$depMod = $this->fetchMod($dep['modId']);
$this->installMod($depMod); // Rekursiv — löst Sub-Abhängigkeiten auf
}
// relationType 1 = EmbeddedLibrary (übersprungen)
// relationType 2 = OptionalDependency (übersprungen)
// relationType 4 = Tool (übersprungen)
// relationType 5 = Incompatible (übersprungen)
// relationType 6 = Include (übersprungen)
}
// Manueller-Download-Erkennung — CurseForge-Autoreneinschränkung
if (empty($file['downloadUrl'])) {
$mod['requires_manual_download'] = true;
// Nutzer sieht „Auf CurseForge herunterladen"-Link statt Installations-Button
// Das ist eine CurseForge-Autorenrichtlinie, kein Plugin-Bug
}
CurseForge Provider Fehler-Zuordnung
| Fehler / Log-Nachricht | Ursache | Auswirkung | Lösung |
|---|---|---|---|
"CurseForge API key not set" |
Kein Key in Einstellungen oder Env | CurseForge-Tab ausgeblendet | CURSEFORGE_API_KEY setzen oder in Plugin-Einstellungen eingeben |
| HTTP 401 Unauthorized | Key abgelaufen oder widerrufen | Alle CurseForge-Anfragen schlagen fehl | Auf console.curseforge.com neu generieren |
| HTTP 403 Forbidden | Key hat nicht ausreichende Berechtigungen | Suche funktioniert, Aktionen können fehlschlagen | Neuen Key mit vollen Rechten erstellen |
| HTTP 429 Too Many Requests | Rate-Limit überschritten | Anfragen temporär blockiert | 60s warten, Cache-TTL erhöhen, Häufigkeit reduzieren |
Leere downloadUrl in Antwort |
Autor hat Direkt-Download deaktiviert | Auto-Installation nicht möglich | Nutzer muss von CurseForge-Website manuell herunterladen |
"CurseForge request failed" |
Netzwerk-Timeout oder DNS-Fehler | Provider nicht verfügbar | Firewall-Regeln für api.curseforge.com prüfen |
| Abhängigkeitsschleife erkannt | Zirkuläre relationType=3-Kette |
Installation hängt oder stürzt ab | Auf Mod-Seite melden — Mod-Metadaten-Fehler |
Falsche classId-Ergebnisse |
Mods gesucht aber Plugins erhalten | Falscher Inhaltstyp angezeigt | classId=9137 für Mods verifizieren (nicht 5 oder 4471) |
Leeres gameVersions-Array |
Mod hat keine versions-getaggten Dateien | Versionsfilter gibt nichts zurück | Versionsfilter deaktivieren, alle durchsuchen |
Modrinth Provider — Vollständige Referenz
API-Endpunkt-Parameter — GET /v2/search
| Parameter | Typ | Pflicht | Standard | Beschreibung |
|---|---|---|---|---|
query |
string | Nein | — | Volltextsuchbegriffe |
facets |
string (JSON) | Nein | — | Filter-Array (siehe Facetten unten) |
limit |
int | Nein | 20 | Ergebnisse pro Seite (max 100) |
offset |
int | Nein | 0 | Paginierungs-Offset |
index |
string | Nein | relevance |
Sortierung: relevance, downloads, follows, newest, updated |
Facetten-Struktur
Facetten sind ein JSON-Array aus Arrays. Innere Arrays werden ODER-verknüpft, äußere Arrays werden UND-verknüpft:
// Wie das Plugin Facetten intern aufbaut
$facets = [
['project_type:mod'], // MUSS ein Mod sein
['server_side:required', 'server_side:optional'], // Server-kompatibel
];
// Optionale Facetten werden bedingt hinzugefügt:
if ($mcVersion) {
$facets[] = ["versions:$mcVersion"]; // Muss diese MC-Version unterstützen
}
if ($loader && !in_array($loader, ['all', 'any'])) {
$facets[] = ["categories:$loader"]; // Muss diesen Loader unterstützen
}
// ⚠ 'all' und 'any' werden ausgeschlossen — Modrinth akzeptiert diese nicht als Facetten
$params['facets'] = json_encode($facets);
Antwort-Schema — Suchergebnisse
| Feld | Typ | Beschreibung |
|---|---|---|
hits[].project_id |
string | Eindeutige Modrinth-Projekt-ID (z.B. AANobbMI) |
hits[].slug |
string | URL-sicherer Projekt-Slug |
hits[].title |
string | Projekt-Anzeigename |
hits[].description |
string | Kurzbeschreibung |
hits[].downloads |
int | Gesamte Download-Anzahl |
hits[].icon_url |
string | Projekt-Icon-URL |
hits[].author |
string | Autor-Benutzername |
hits[].date_modified |
string | ISO 8601 letzte Aktualisierung |
hits[].versions |
string[] | Unterstützte Minecraft-Versionen |
hits[].categories |
string[] | Tags: fabric, forge, quilt usw. |
total_hits |
int | Gesamtzahl passender Ergebnisse |
limit |
int | Verwendete Seitengröße |
offset |
int | Aktueller Offset |
Rate-Limits
| Stufe | Limit | Hinweise |
|---|---|---|
| Ohne Authentifizierung | 300 Anfragen/Minute pro IP | Plugin nutzt standardmäßig keine Authentifizierung |
| Mit PAT-Token | Höhere Limits | Wird vom Plugin nicht genutzt |
| Bei Überschreitung | HTTP 429 | Warten gemäß ratelimit-reset-Header |
// Antwort-Verarbeitung — wie Modrinth-Ergebnisse zugeordnet werden
$hits = $response->json('hits', []);
$mods = collect($hits)->map(function ($hit) {
return [
'id' => $hit['project_id'],
'name' => $hit['title'],
'slug' => $hit['slug'],
'summary' => $hit['description'],
'author' => $hit['author'],
'downloads' => $hit['downloads'],
'icon_url' => $hit['icon_url'] ?? null,
'updated_at' => $hit['date_modified'],
'versions' => $hit['versions'] ?? [],
'provider' => 'modrinth',
];
});
// Versions-Abruf — mit Loader- und Versionsfilterung
$versions = Http::get("https://api.modrinth.com/v2/project/{$projectId}/version", [
'loaders' => json_encode([$loader]), // z.B. ["fabric"]
'game_versions' => json_encode([$mcVersion]), // z.B. ["1.20.1"]
])->json();
// Jede Version enthält:
// - id, version_number, name, date_published
// - files[0].url = direkter CDN-Download-Link
// - files[0].hashes.sha1 = Integritäts-Hash
// - dependencies[] = Pflicht-/Optionale Abhängigkeiten
// Abhängigkeitsauflösung — mit zwei Strategien
foreach ($version['dependencies'] as $dep) {
if ($dep['dependency_type'] === 'required') {
if (!empty($dep['version_id'])) {
// Strategie 1: Exakte Version angegeben
$depVersion = Http::get("https://api.modrinth.com/v2/version/{$dep['version_id']}")->json();
} else {
// Strategie 2: Neueste kompatible Version für das Projekt suchen
$depVersions = Http::get("https://api.modrinth.com/v2/project/{$dep['project_id']}/version", [
'loaders' => json_encode([$loader]),
'game_versions' => json_encode([$mcVersion]),
])->json();
$depVersion = $depVersions[0] ?? null; // Neueste kompatible
}
if ($depVersion) {
$this->installVersion($depVersion);
}
}
// 'optional' und 'incompatible' Typen werden ignoriert
}
Modrinth Provider Fehler-Zuordnung
| Fehler / Log-Nachricht | Ursache | Auswirkung | Lösung |
|---|---|---|---|
| HTTP 429 Rate Limited | >300 Anfragen/Min von deiner IP | Anfragen ~60s blockiert | Browsing-Geschwindigkeit reduzieren, Cache-TTL erhöhen |
| Keine Ergebnisse für MC-Version | Keine serverseitigen Dateien für Version+Loader | Leere Browser-Seite | Anderen Versions- oder Loader-Filter versuchen |
| SHA-1-Hash stimmt nicht überein | Download während Übertragung beschädigt | Datei-Integritätsprüfung schlägt fehl | Cache leeren und Download wiederholen |
version_id nicht gefunden (404) |
Abhängigkeit verweist auf gelöschte Version | Abhängigkeits-Installation fehlgeschlagen | Abhängigkeits-Mod manuell installieren |
| Facetten-Parse-Fehler | Ungültiges JSON im Facetten-Parameter | API gibt 400 zurück | Cache leeren — Plugin hat möglicherweise fehlerhafte Facetten gecacht |
| Download-CDN-Timeout | cdn.modrinth.com langsam oder offline |
Datei-Download fehlgeschlagen | MODS_REQUEST_TIMEOUT erhöhen, später erneut versuchen |
Modtale Provider — Vollständige Referenz
API-Endpunkt-Parameter — GET /api/v1/mods
| Parameter | Typ | Pflicht | Standard | Beschreibung |
|---|---|---|---|---|
page |
int | Nein | 0 | 0-basierter Seitenindex (anders als CurseForge/Modrinth!) |
game |
string | Nein | — | Nach Spielname filtern |
⚠ Wichtiger Unterschied: Modtale verwendet 0-basierte Paginierung. Seite 0 = erste Seite. Das Plugin konvertiert von seinem internen 1-basierten System:
max(0, $page - 1).
Antwort-Schema
| Feld | Typ | Beschreibung |
|---|---|---|
data[].id |
int | Eindeutige Modtale Mod-ID |
data[].name |
string | Mod-Anzeigename |
data[].description |
string | Kurzbeschreibung |
data[].thumbnail |
string | Relativer Pfad → cdn.modtale.net/ voranstellen |
data[].download_count |
int | Gesamte Downloads |
data[].updated_at |
string | Zeitstempel der letzten Aktualisierung |
data[].game |
string | Zugehöriger Spielname |
meta.total |
int | Gesamtergebnisse |
meta.current_page |
int | Aktuelle Seite (0-basiert) |
meta.last_page |
int | Letzte Seitennummer |
Rate-Limits
| Stufe | Limit | Hinweise |
|---|---|---|
| Ohne API-Key | Unbekannt (undokumentiert) | Niedrigere Schwelle — kann bei starkem Browsen Limits erreichen |
Mit X-MODTALE-KEY |
Erweiterte Limits | Über MODTALE_API_KEY Env-Variable setzen |
| Bei Überschreitung | HTTP 429 | Kein dokumentierter Retry-After-Header |
// Kompletter Anfrage + Antwort-Verarbeitungsablauf
$response = Http::timeout($timeout)
->when($apiKey, fn ($http) => $http->withHeaders(['X-MODTALE-KEY' => $apiKey]))
->get('https://api.modtale.net/api/v1/mods', [
'page' => max(0, $page - 1), // 1-basiert → 0-basiert konvertieren!
'game' => $gameName,
]);
$results = $response->json('data', []);
$mods = collect($results)->map(function ($mod) {
return [
'id' => $mod['id'],
'name' => $mod['name'],
'summary' => $mod['description'] ?? '',
'author' => $mod['author'] ?? 'Unknown',
'downloads' => $mod['download_count'] ?? 0,
'icon_url' => 'https://cdn.modtale.net/' . ($mod['thumbnail'] ?? ''),
'updated_at' => $mod['updated_at'],
'provider' => 'modtale',
];
});
// CDN-URL-Konstruktion — alle Assets nutzen das CDN-Präfix
$iconUrl = 'https://cdn.modtale.net/' . $mod['thumbnail'];
$downloadUrl = 'https://cdn.modtale.net/' . $file['download_path'];
// ⚠ Keine Abhängigkeitsinformationen — Modtale API liefert keine Abhängigkeitsdaten
// Nutzer müssen Abhängigkeiten für Modtale-Mods manuell installieren
Modtale Provider Fehler-Zuordnung
| Fehler / Log-Nachricht | Ursache | Auswirkung | Lösung |
|---|---|---|---|
| API gibt leere Daten zurück | api.modtale.net offline oder geändert |
Keine Mods angezeigt | Prüfen ob modtale.net erreichbar ist |
| CDN-Download 404 | Datei entfernt oder Pfad geändert | Download fehlgeschlagen | Suche wiederholen — Dateiliste kann sich aktualisieren |
cdn.modtale.net Timeout |
CDN vom Server nicht erreichbar | Icons fehlen + Downloads fehlgeschlagen | Firewall-Regeln, DNS-Auflösung prüfen |
| Keine Abhängigkeitsinformationen | API-Beschränkung (kein Bug) | Abhängigkeiten müssen manuell installiert werden | Nutzer identifiziert und installiert manuell |
| 0-basierte Offset-Verwirrung | Eigener Code nutzt rohe Seitennummern | Falsche Ergebnis-Seite | Immer max(0, $page - 1) Konvertierung verwenden |
| Rate-Limited ohne Key | Zu viele Anfragen ohne API-Key | HTTP 429 für eine Zeit | MODTALE_API_KEY in Einstellungen konfigurieren |
Übergreifende Provider-Muster
Diese Code-Muster gelten für alle Provider:
// Suchabfrage-Bereinigung — schützt gegen Injection bei allen Providern
$query = preg_replace('/[<>{}\'"]+/', '', $query);
// Zeichen < > { } ' " werden vor jedem API-Aufruf entfernt
// Dateinamenerkennung nach Download — 3 Versuche mit Backoff
for ($attempt = 0; $attempt < 3; $attempt++) {
usleep([200000, 300000, 500000][$attempt]); // 200ms → 300ms → 500ms
$files = $this->fileRepository->getDirectory('/mods/');
$newFile = $this->findNewFile($files, $knownFiles);
if ($newFile) break;
}
// Wenn nach 3 Versuchen keine neue Datei erkannt → Warnung geloggt
// UUID-Präfix-Bereinigung — Server fügt uuid_short zu Dateinamen hinzu
$cleanName = preg_replace('/^[a-f0-9]{8}_/', '', $filename);
// z.B. "a1b2c3d4_fabric-api-0.92.jar" → "fabric-api-0.92.jar"
// JAR-Metadaten-Extraktion — liest Mod-Name aus Archiv
$zip = new ZipArchive();
ini_set('memory_limit', '256M'); // JAR-Dateien können groß sein
// Prüft in Reihenfolge: plugin.yml → fabric.mod.json → META-INF/mods.toml
// Maximale Dateigröße: 30MB für Metadaten-Scan
// Gleichzeitiger Scan-Schutz — atomare Sperre verhindert doppelte Scans
$lock = Cache::lock('mod_scan_' . $serverUuid, 10); // 10 Sekunden TTL
if (!$lock->get()) {
return; // Ein anderer Scan läuft bereits
}
Vollständige Benachrichtigungs-Referenz
| Ereignis | Typ | Titel | Nachricht |
|---|---|---|---|
| Version erkannt | Erfolg | Erkennung | „Minecraft-Version erkannt: X.X.X" |
| Version nicht erkannt | Warnung | Erkennung | „Minecraft-Version konnte nicht erkannt werden" |
| Installation gestartet | Info | Installation | „Mod-Installation gestartet" |
| Installation erfolgreich | Erfolg | Installiert | „Mod erfolgreich installiert" (geloggt) |
| Installation fehlgeschlagen | Fehler | Fehler | „Installation fehlgeschlagen" + Details |
| Abhängigkeiten installiert | Info | Abhängigkeiten | „X zusätzliche Bibliotheks-Mods werden installiert…" |
| Mod deaktiviert | Erfolg | Deaktiviert | „Mod deaktiviert: [Name]" |
| Mod aktiviert | Erfolg | Aktiviert | „Mod aktiviert: [Name]" |
| Deaktivierung fehlgeschlagen | Fehler | Fehler | „Deaktivierung fehlgeschlagen" + Fehler |
| Aktivierung fehlgeschlagen | Fehler | Fehler | „Aktivierung fehlgeschlagen" + Fehler |
| Mod deinstalliert | Erfolg | Entfernt | „Mod deinstalliert" |
| Deinstallation fehlgeschlagen | Fehler | Fehler | Fehlerdetails |
| Cache geleert | Erfolg | Cache | „Cache geleert" + Scan-Ergebnisse |
| Cache-Fehler | Fehler | Fehler | „Fehler beim Leeren des Cache" + $e->getMessage() |
| ZIP gestartet | Info | Export | „ZIP-Download gestartet" |
| ZIP fehlgeschlagen | Fehler | Export | „ZIP-Download fehlgeschlagen" |
| CurseForge-Key fehlt | Warnung | Konfiguration | „CurseForge API-Schlüssel nicht gesetzt" |
| Manueller Download | Warnung | Download | „Manueller Download erforderlich" + Link |
HTTP-Fehlercode-Referenz
| HTTP-Status | Quelle | Bedeutung | Maßnahme |
|---|---|---|---|
| 200 | Alle | Erfolg | — |
| 400 | CurseForge | Ungültige Anfrage | gameId/classId-Werte prüfen |
| 401 | CurseForge | API-Key ungültig | Key auf console.curseforge.com erneuern |
| 403 | CurseForge | Fehlende Berechtigungen | Key neu generieren |
| 403 | Plugin | Feature-Flag mods fehlt am Egg |
["mods"] zu Egg-Features hinzufügen |
| 404 | Alle Provider | Ressource nicht gefunden | Mod-ID/Slug prüfen oder Mod wurde entfernt |
| 429 | CurseForge | Rate-Limit erreicht | 60s warten, Cache-Dauer erhöhen |
| 429 | Modrinth | Rate-Limit (Fair Use) | Anfragehäufigkeit reduzieren |
| 500 | Alle Provider | Serverfehler | Später erneut versuchen, Status-Seite prüfen |
| 502/503 | Alle | Gateway/Wartung | Provider ist offline, später erneut versuchen |
| 0 / Timeout | Alle | Verbindungs-Timeout | MODS_REQUEST_TIMEOUT erhöhen, DNS/Firewall prüfen |