getEggVariable($server, 'MODPACK_PLATFORM'); $modpackId = $this->getEggVariable($server, 'MODPACK_ID'); // Also check platform-specific variables if (empty($modpackId)) { if ($curseforgeId = $this->getEggVariable($server, 'CURSEFORGE_ID')) { $platform = 'curseforge'; $modpackId = $curseforgeId; } elseif ($modrinthId = $this->getEggVariable($server, 'MODRINTH_PROJECT_ID')) { $platform = 'modrinth'; $modpackId = $modrinthId; } elseif ($ftbId = $this->getEggVariable($server, 'FTB_MODPACK_ID')) { $platform = 'ftb'; $modpackId = $ftbId; } elseif ($technicSlug = $this->getEggVariable($server, 'TECHNIC_SLUG')) { $platform = 'technic'; $modpackId = $technicSlug; } } // 2. Fallback to file fingerprinting if not set if (empty($platform) || empty($modpackId)) { $detected = $this->detectFromFiles($server); if (!empty($detected['platform'])) { $platform = $detected['platform']; $modpackId = $detected['id'] ?? null; } } // 3. If still nothing, return unknown if (empty($platform) || empty($modpackId)) { return response()->json([ 'success' => false, 'status' => 'unknown', 'message' => 'Could not detect modpack. Set MODPACK_PLATFORM and MODPACK_ID in startup variables.', ]); } // 4. Query the appropriate API $versionData = $this->checkVersion($platform, $modpackId); return response()->json([ 'success' => true, 'server_uuid' => $server->uuid, 'platform' => $platform, 'modpack_id' => $modpackId, 'modpack_name' => $versionData['name'] ?? 'Unknown', 'current_version' => $versionData['current'] ?? 'Unknown', 'latest_version' => $versionData['latest'] ?? 'Unknown', 'status' => $versionData['status'] ?? 'unknown', 'error' => $versionData['error'] ?? null, ]); } /** * Get an egg variable value for a server */ private function getEggVariable(Server $server, string $name): ?string { $variable = $server->variables() ->whereHas('variable', function ($query) use ($name) { $query->where('env_variable', $name); }) ->first(); return $variable?->variable_value; } /** * Attempt to detect modpack from files */ private function detectFromFiles(Server $server): array { // Try CurseForge manifest.json try { $content = $this->fileRepository->setServer($server)->getContent('/manifest.json'); $json = json_decode($content, true); if (isset($json['projectID'])) { return [ 'platform' => 'curseforge', 'id' => (string) $json['projectID'], ]; } } catch (\Exception $e) { // File doesn't exist or Wings unreachable } // Try Modrinth modrinth.index.json try { $content = $this->fileRepository->setServer($server)->getContent('/modrinth.index.json'); $json = json_decode($content, true); if (isset($json['name'])) { // Modrinth index doesn't contain project ID directly // We detect it's Modrinth but need manual ID return [ 'platform' => 'modrinth', 'id' => null, 'name' => $json['name'] ?? null, ]; } } catch (\Exception $e) { // File doesn't exist } return []; } /** * Check version against platform API */ private function checkVersion(string $platform, string $modpackId): array { return match ($platform) { 'curseforge' => $this->checkCurseForge($modpackId), 'modrinth' => $this->checkModrinth($modpackId), 'technic' => $this->checkTechnic($modpackId), 'ftb' => $this->checkFTB($modpackId), default => ['status' => 'error', 'error' => 'Unknown platform: ' . $platform], }; } /** * Check CurseForge API */ private function checkCurseForge(string $modpackId): array { $apiKey = $this->blueprint->dbGet('modpackchecker', 'curseforge_api_key'); if (empty($apiKey)) { return [ 'status' => 'error', 'error' => 'CurseForge API key not configured. Add it in Admin > Extensions > ModpackChecker.', ]; } try { $response = Http::withHeaders([ 'x-api-key' => $apiKey, 'Accept' => 'application/json', ])->timeout(10)->get("https://api.curseforge.com/v1/mods/{$modpackId}"); if ($response->status() === 401 || $response->status() === 403) { return ['status' => 'error', 'error' => 'Invalid CurseForge API key.']; } if ($response->status() === 404) { return ['status' => 'error', 'error' => 'Modpack not found or delisted.']; } if (!$response->successful()) { return ['status' => 'error', 'error' => 'CurseForge API error: ' . $response->status()]; } $data = $response->json(); $name = $data['data']['name'] ?? 'Unknown'; $latestFile = $data['data']['latestFiles'][0] ?? null; $latestVersion = $latestFile['displayName'] ?? 'Unknown'; return [ 'name' => $name, 'latest' => $latestVersion, 'status' => 'up_to_date', // Can't determine current without server-side tracking ]; } catch (\Exception $e) { return ['status' => 'error', 'error' => 'Failed to connect to CurseForge: ' . $e->getMessage()]; } } /** * Check Modrinth API */ private function checkModrinth(string $modpackId): array { try { // First get project info $projectResponse = Http::withHeaders([ 'User-Agent' => 'ModpackChecker/1.0.0 (firefrostgaming.com)', ])->timeout(10)->get("https://api.modrinth.com/v2/project/{$modpackId}"); if ($projectResponse->status() === 404) { return ['status' => 'error', 'error' => 'Modpack not found on Modrinth.']; } if (!$projectResponse->successful()) { return ['status' => 'error', 'error' => 'Modrinth API error: ' . $projectResponse->status()]; } $projectData = $projectResponse->json(); $name = $projectData['title'] ?? 'Unknown'; // Get versions $versionResponse = Http::withHeaders([ 'User-Agent' => 'ModpackChecker/1.0.0 (firefrostgaming.com)', ])->timeout(10)->get("https://api.modrinth.com/v2/project/{$modpackId}/version"); if (!$versionResponse->successful()) { return [ 'name' => $name, 'latest' => 'Unknown', 'status' => 'error', 'error' => 'Could not fetch versions.', ]; } $versions = $versionResponse->json(); $latestVersion = $versions[0]['version_number'] ?? 'Unknown'; return [ 'name' => $name, 'latest' => $latestVersion, 'status' => 'up_to_date', ]; } catch (\Exception $e) { return ['status' => 'error', 'error' => 'Failed to connect to Modrinth: ' . $e->getMessage()]; } } /** * Check Technic API */ private function checkTechnic(string $slug): array { try { $response = Http::timeout(10) ->get("https://api.technicpack.net/modpack/{$slug}?build=1"); if ($response->status() === 404) { return ['status' => 'error', 'error' => 'Modpack not found on Technic.']; } if (!$response->successful()) { return ['status' => 'error', 'error' => 'Technic API error: ' . $response->status()]; } $data = $response->json(); if (isset($data['error'])) { return ['status' => 'error', 'error' => $data['error']]; } $name = $data['displayName'] ?? $data['name'] ?? 'Unknown'; $latestVersion = $data['version'] ?? 'Unknown'; return [ 'name' => $name, 'latest' => $latestVersion, 'status' => 'up_to_date', ]; } catch (\Exception $e) { return ['status' => 'error', 'error' => 'Failed to connect to Technic: ' . $e->getMessage()]; } } /** * Check FTB (modpacks.ch) API */ private function checkFTB(string $modpackId): array { try { $response = Http::timeout(10) ->get("https://api.modpacks.ch/public/modpack/{$modpackId}"); if ($response->status() === 404) { return ['status' => 'error', 'error' => 'Modpack not found on FTB.']; } if (!$response->successful()) { return ['status' => 'error', 'error' => 'FTB API error: ' . $response->status()]; } $data = $response->json(); if (isset($data['status']) && $data['status'] === 'error') { return ['status' => 'error', 'error' => $data['message'] ?? 'Unknown FTB error']; } $name = $data['name'] ?? 'Unknown'; $versions = $data['versions'] ?? []; $latestVersion = !empty($versions) ? ($versions[0]['name'] ?? 'Unknown') : 'Unknown'; return [ 'name' => $name, 'latest' => $latestVersion, 'status' => 'up_to_date', ]; } catch (\Exception $e) { return ['status' => 'error', 'error' => 'Failed to connect to FTB: ' . $e->getMessage()]; } } }