using System; using System.Linq; using System.Net.Http; using System.Text.Json; using System.Threading.Tasks; namespace Gott.Services { public class GoogleCheckResult { public string Status { get; set; } // "ok", "invalid", "disabled", "error" public string Message { get; set; } public string[] Models { get; set; } } public class GoogleQuotaResult { public bool HasQuota { get; set; } public string Error { get; set; } } public static class GoogleKeyChecker { private static async Task CheckQuotaAsync(HttpClient client, string apiKey, string[] models) { try { // Günstigstes Modell wählen (flash > pro > erstbestes) var testModel = models.FirstOrDefault(m => m.Contains("flash", StringComparison.OrdinalIgnoreCase)) ?? models.FirstOrDefault(m => m.Contains("pro", StringComparison.OrdinalIgnoreCase)) ?? models.FirstOrDefault(); if (testModel == null) return new GoogleQuotaResult { HasQuota = false, Error = "Keine Modelle verfügbar" }; var url = "https://generativelanguage.googleapis.com/v1/models/" + testModel + ":generateContent?key=" + apiKey; var payload = new { contents = new[] { new { parts = new[] { new { text = "hi" } } } }, generationConfig = new { maxOutputTokens = 1 } }; var json = JsonSerializer.Serialize(payload); var content = new StringContent(json, System.Text.Encoding.UTF8, "application/json"); var resp = await client.PostAsync(url, content); var statusCode = (int)resp.StatusCode; if (statusCode == 200) return new GoogleQuotaResult { HasQuota = true }; var body = await resp.Content.ReadAsStringAsync(); // 429: Quota oder Rate-Limit if (statusCode == 429) { if (body.Contains("RESOURCE_EXHAUSTED", StringComparison.OrdinalIgnoreCase) && (body.Contains("quota", StringComparison.OrdinalIgnoreCase) || body.Contains("limit_value", StringComparison.OrdinalIgnoreCase))) { // Quota auf 0 oder erschöpft if (body.Contains("\"0\"") || body.Contains("GenerateContent")) return new GoogleQuotaResult { HasQuota = false, Error = "Quota erschöpft (0 RPM)" }; return new GoogleQuotaResult { HasQuota = false, Error = "Quota erschöpft" }; } // Echtes Rate-Limit → Key hat Quota, ist nur temporär blockiert return new GoogleQuotaResult { HasQuota = true }; } // 403: API nicht aktiviert oder Billing-Problem if (statusCode == 403) { if (body.Contains("PERMISSION_DENIED", StringComparison.OrdinalIgnoreCase)) return new GoogleQuotaResult { HasQuota = false, Error = "Keine Berechtigung für generateContent" }; if (body.Contains("billing", StringComparison.OrdinalIgnoreCase)) return new GoogleQuotaResult { HasQuota = false, Error = "Billing nicht aktiviert" }; return new GoogleQuotaResult { HasQuota = false, Error = "Zugriff verweigert (403)" }; } // Andere Fehler → konservativ als "kein Quota" behandeln return new GoogleQuotaResult { HasQuota = false, Error = "API-Fehler (" + statusCode + ")" }; } catch (Exception) { // Bei Netzwerkfehler etc. → konservativ return new GoogleQuotaResult { HasQuota = false, Error = "Quota-Check fehlgeschlagen" }; } } public static async Task CheckKeyAsync(IHttpClientFactory httpFactory, string apiKey) { try { var client = httpFactory.CreateClient(); client.Timeout = TimeSpan.FromSeconds(30); // Gemini / Generative Language API – listet verfügbare Modelle var url = "https://generativelanguage.googleapis.com/v1/models?key=" + apiKey; var resp = await client.GetAsync(url); var statusCode = (int)resp.StatusCode; if (statusCode == 400) return new GoogleCheckResult { Status = "invalid", Message = "API-Key ungültig (400)" }; if (statusCode == 403) { // Key gültig, aber Gemini API nicht aktiviert (z.B. Maps-Key) var errorBody = await resp.Content.ReadAsStringAsync(); if (errorBody.Contains("API_KEY_INVALID", StringComparison.OrdinalIgnoreCase)) return new GoogleCheckResult { Status = "invalid", Message = "API-Key ungültig (403)" }; return new GoogleCheckResult { Status = "disabled", Message = "Key gültig, aber Gemini API nicht aktiviert (403)" }; } if (statusCode == 429) return new GoogleCheckResult { Status = "rate_limited", Message = "Rate Limited (429)" }; if (statusCode >= 400) return new GoogleCheckResult { Status = "error", Message = "API-Fehler (" + statusCode + ")" }; // 200 OK → Key gültig + Gemini API aktiv var body = await resp.Content.ReadAsStringAsync(); var doc = JsonDocument.Parse(body); string[] models = Array.Empty(); if (doc.RootElement.TryGetProperty("models", out var modelsArr)) { var interestingPrefixes = new[] { "gemini" }; models = modelsArr.EnumerateArray() .Where(m => m.TryGetProperty("name", out _)) .Select(m => m.GetProperty("name").GetString()) .Where(n => n != null) .Select(n => n.StartsWith("models/", StringComparison.Ordinal) ? n.Substring(7) : n) .Where(n => interestingPrefixes.Any(p => n.StartsWith(p, StringComparison.OrdinalIgnoreCase))) .Distinct() .OrderBy(x => x) .ToArray(); } // Plan-Erkennung string plan; if (models.Any(m => m.Contains("2.5", StringComparison.OrdinalIgnoreCase) || m.Contains("ultra", StringComparison.OrdinalIgnoreCase))) plan = "Premium"; else if (models.Any(m => m.Contains("pro", StringComparison.OrdinalIgnoreCase))) plan = "Standard"; else if (models.Any(m => m.Contains("flash", StringComparison.OrdinalIgnoreCase))) plan = "Basic"; else plan = "Free"; // Schritt 2: Echten generateContent-Call machen um Quota zu prüfen var quota = await CheckQuotaAsync(client, apiKey, models); var info = models.Length + " Gemini-Modelle · " + plan; if (!quota.HasQuota) { info += " · " + quota.Error; return new GoogleCheckResult { Status = "quota_exceeded", Message = info, Models = models }; } return new GoogleCheckResult { Status = "ok", Message = info, Models = models }; } catch (Exception ex) { return new GoogleCheckResult { Status = "error", Message = ex.Message }; } } } }