* @version 1.0.0 * ============================================================================= */ namespace Pterodactyl\Services; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\DB; use Exception; class ModpackApiService { /** * Fetch the latest version info for a modpack from its platform API. * * @param string $platform Platform name: modrinth, curseforge, ftb, technic * @param string $modpackId Platform-specific modpack identifier * @return array Contains: name, version * @throws Exception If platform unknown or API call fails */ public function fetchLatestVersion(string $platform, string $modpackId): array { return match($platform) { 'modrinth' => $this->checkModrinth($modpackId), 'curseforge' => $this->checkCurseForge($modpackId), 'ftb' => $this->checkFTB($modpackId), 'technic' => $this->checkTechnic($modpackId), default => throw new Exception("Unknown platform: {$platform}") }; } /** * Query Modrinth API for latest modpack version. * * NO API KEY REQUIRED - uses User-Agent for identification. * Rate limit: 300 requests/minute (generous) * * @param string $projectId Modrinth project ID or slug * @return array Contains: name, version * @throws Exception If API request fails */ private function checkModrinth(string $projectId): array { $headers = ['User-Agent' => 'FirefrostGaming/ModpackChecker/1.0']; $response = Http::withHeaders($headers) ->get("https://api.modrinth.com/v2/project/{$projectId}"); if (!$response->successful()) { throw new Exception('Modrinth API request failed: ' . $response->status()); } $project = $response->json(); $versionResponse = Http::withHeaders($headers) ->get("https://api.modrinth.com/v2/project/{$projectId}/version"); if (!$versionResponse->successful()) { throw new Exception('Modrinth versions API failed: ' . $versionResponse->status()); } $versions = $versionResponse->json(); return [ 'name' => $project['title'] ?? 'Unknown', 'version' => $versions[0]['version_number'] ?? 'Unknown', ]; } /** * Query CurseForge API for latest modpack version. * * REQUIRES API KEY - configured in admin panel. * Rate limit: ~1000 requests/day for personal keys. * * @param string $modpackId CurseForge project ID (numeric) * @return array Contains: name, version * @throws Exception If API key missing or request fails */ private function checkCurseForge(string $modpackId): array { $apiKey = DB::table('settings') ->where('key', 'modpackchecker::curseforge_api_key') ->value('value'); if (empty($apiKey)) { throw new Exception('CurseForge API key not configured'); } $response = Http::withHeaders([ 'x-api-key' => $apiKey, 'Accept' => 'application/json', ])->get("https://api.curseforge.com/v1/mods/{$modpackId}"); if (!$response->successful()) { throw new Exception('CurseForge API request failed: ' . $response->status()); } $data = $response->json()['data'] ?? []; return [ 'name' => $data['name'] ?? 'Unknown', 'version' => $data['latestFiles'][0]['displayName'] ?? 'Unknown', ]; } /** * Query Feed The Beast (FTB) API for latest modpack version. * * NO API KEY REQUIRED - modpacks.ch is public. * * @param string $modpackId FTB modpack ID (numeric) * @return array Contains: name, version * @throws Exception If API request fails */ private function checkFTB(string $modpackId): array { $response = Http::get("https://api.modpacks.ch/public/modpack/{$modpackId}"); if (!$response->successful()) { throw new Exception('FTB API request failed: ' . $response->status()); } $data = $response->json(); return [ 'name' => $data['name'] ?? 'Unknown', 'version' => $data['versions'][0]['name'] ?? 'Unknown', ]; } /** * Query Technic Platform API for latest modpack version. * * NO API KEY REQUIRED - but requires dynamic build number. * The build number is cached for 12 hours to reduce API calls. * * "RV-Ready" approach: Technic blocks old build numbers, so we * dynamically fetch the current stable launcher build. * * @param string $slug Technic modpack slug (URL-friendly name) * @return array Contains: name, version * @throws Exception If API request fails */ private function checkTechnic(string $slug): array { // Cache the build number for 12 hours to prevent rate limits $latestBuild = Cache::remember('modpackchecker_technic_build', 43200, function () { $versionResponse = Http::get('https://api.technicpack.net/launcher/version/stable4'); return $versionResponse->successful() ? ($versionResponse->json('build') ?? 999) : 999; }); $response = Http::withHeaders([ 'User-Agent' => 'FirefrostGaming/ModpackChecker/1.0', 'Accept' => 'application/json', ])->get("https://api.technicpack.net/modpack/{$slug}?build={$latestBuild}"); if (!$response->successful()) { throw new Exception('Technic API request failed: ' . $response->status()); } $data = $response->json(); return [ 'name' => $data['displayName'] ?? $data['name'] ?? 'Unknown', 'version' => $data['version'] ?? 'Unknown', ]; } }