diff --git a/Mimante/MainWindow.xaml b/Mimante/MainWindow.xaml index 357feda..bd5d078 100644 --- a/Mimante/MainWindow.xaml +++ b/Mimante/MainWindow.xaml @@ -413,7 +413,7 @@ - + diff --git a/Mimante/MainWindow.xaml.cs b/Mimante/MainWindow.xaml.cs index 69716a3..4d18aa8 100644 --- a/Mimante/MainWindow.xaml.cs +++ b/Mimante/MainWindow.xaml.cs @@ -107,17 +107,24 @@ namespace AutoBidder private void ExecuteGridStart(ViewModels.AuctionViewModel? vm) { if (vm == null) return; - vm.IsActive = true; vm.IsPaused = false; Log($"[START] Asta avviata: {vm.Name}"); UpdateGlobalControlButtons(); + vm.IsActive = true; + vm.IsPaused = false; + Log($"[START] Asta avviata: {vm.Name}"); + UpdateGlobalControlButtons(); } private void ExecuteGridPause(ViewModels.AuctionViewModel? vm) { if (vm == null) return; - vm.IsPaused = true; Log($"[PAUSA] Asta in pausa: {vm.Name}"); UpdateGlobalControlButtons(); + vm.IsPaused = true; + Log($"[PAUSA] Asta in pausa: {vm.Name}"); + UpdateGlobalControlButtons(); } private void ExecuteGridStop(ViewModels.AuctionViewModel? vm) { if (vm == null) return; - vm.IsActive = false; Log($"[STOP] Asta fermata: {vm.Name}"); UpdateGlobalControlButtons(); + vm.IsActive = false; + Log($"[STOP] Asta fermata: {vm.Name}"); + UpdateGlobalControlButtons(); } private async Task ExecuteGridBidAsync(ViewModels.AuctionViewModel? vm) { @@ -626,7 +633,7 @@ namespace AutoBidder SaveAuctions(); UpdateTotalCount(); - Log($"[+] Asta aggiunta: {displayName}"); + // AuctionMonitor already emits a global log entry for added auctions } catch (Exception ex) { @@ -696,7 +703,7 @@ namespace AutoBidder SaveAuctions(); UpdateTotalCount(); - Log($"[+] Asta aggiunta: {name}"); + // AuctionMonitor already emits a global log entry for added auctions } catch (Exception ex) { @@ -964,38 +971,50 @@ namespace AutoBidder // Verifica validità cookie con chiamata API (eseguita in thread di background) Task.Run(() => - { - try - { - var success = _auctionMonitor.UpdateUserInfoAsync().GetAwaiter().GetResult(); - var updatedSession = _auctionMonitor.GetSession(); - - Dispatcher.Invoke(() => - { - if (success) - { - RemainingBidsText.Text = (updatedSession?.RemainingBids ?? 0).ToString(); - Log($"[OK] Cookie valido - Crediti disponibili: {updatedSession?.RemainingBids ?? 0}"); - } - else - { - Log($"[WARN] Cookie potrebbe essere scaduto - Riconfigura sessione"); - MessageBox.Show( - "Il cookie salvato potrebbe essere scaduto.\nRiconfigura la sessione con 'Configura Sessione'.", - "Sessione Scaduta", - MessageBoxButton.OK, - MessageBoxImage.Warning); - } - }); - } - catch (Exception ex) - { - Dispatcher.Invoke(() => - { - Log($"[WARN] Errore verifica sessione: {ex.Message}"); - }); - } - }); + { + try + { + var success = _auctionMonitor.UpdateUserInfoAsync().GetAwaiter().GetResult(); + var updatedSession = _auctionMonitor.GetSession(); + + Dispatcher.Invoke(() => + { + if (success) + { + RemainingBidsText.Text = (updatedSession?.RemainingBids ?? 0).ToString(); + Log($"[OK] Cookie valido - Crediti disponibili: {updatedSession?.RemainingBids ?? 0}"); + } + else + { + // Secondary fallback: try scraping user data from HTML to ensure cookie is truly invalid + var htmlUser = _auctionMonitor.GetUserDataFromHtmlAsync().GetAwaiter().GetResult(); + if (htmlUser != null) + { + Dispatcher.Invoke(() => + { + SetUserBanner(htmlUser.Username, htmlUser.RemainingBids); + Log($"[OK] Dati utente rilevati via HTML - Utente: {htmlUser.Username}, Puntate residue: {htmlUser.RemainingBids}"); + }); + } + else + { + // Both checks failed: log a warning but do NOT show intrusive MessageBox on startup + Dispatcher.Invoke(() => + { + Log($"[WARN] Impossibile verificare sessione: cookie non valido o rete assente. Riconfigura la sessione se persistono i problemi."); + }); + } + } + }); + } + catch (Exception ex) + { + Dispatcher.Invoke(() => + { + Log($"[WARN] Errore verifica sessione: {ex.Message}"); + }); + } + }); } else { @@ -1107,7 +1126,7 @@ namespace AutoBidder private void GridStartAuction_Click(object sender, RoutedEventArgs e) { if (sender is Button btn && btn.CommandParameter is AuctionViewModel vm) - { + { vm.IsActive = true; vm.IsPaused = false; Log($"[START] Asta avviata: {vm.Name}"); diff --git a/Mimante/Models/AuctionInfo.cs b/Mimante/Models/AuctionInfo.cs index debacb9..951835f 100644 --- a/Mimante/Models/AuctionInfo.cs +++ b/Mimante/Models/AuctionInfo.cs @@ -46,6 +46,10 @@ namespace AutoBidder.Models [System.Text.Json.Serialization.JsonIgnore] public List AuctionLog { get; set; } = new(); + // Flag runtime: indica che è in corso un'operazione di final attack per questa asta + [System.Text.Json.Serialization.JsonIgnore] + public bool IsAttackInProgress { get; set; } = false; + /// /// Aggiunge una voce al log dell'asta /// diff --git a/Mimante/Services/AuctionMonitor.cs b/Mimante/Services/AuctionMonitor.cs index 90d7278..5f27460 100644 --- a/Mimante/Services/AuctionMonitor.cs +++ b/Mimante/Services/AuctionMonitor.cs @@ -310,9 +310,10 @@ namespace AutoBidder.Services return; } - // Log stato solo per aste attive (riduci spam) + // Log stato solo per aste attive (riduci spam) - keep detailed per-auction log if (state.Status == AuctionStatus.Running) { + // Detailed info stays in auction log only auction.AddLog($"API OK - Timer: {state.Timer:F2}s, EUR{state.Price:F2}, {state.LastBidder}, {state.PollingLatencyMs}ms"); } else if (state.Status == AuctionStatus.Paused) @@ -326,75 +327,92 @@ namespace AutoBidder.Services // Aggiorna storico e bidders UpdateAuctionHistory(auction, state); - // OTTIMIZZAZIONE PUNTATA + // FINAL-ATTACK PROTOCOL: when the remaining timer is below our latency threshold (<= 0.5s) + // we stop the normal polling loop for this auction and send a single minimal bid request. if (state.Status == AuctionStatus.Running && !auction.IsPaused && ShouldBid(auction, state)) { - // Se timer è esattamente 0 - if (Math.Abs(state.Timer) < 0.001) + // Use latency threshold (0.5s default) - treat as critical window + var latencyThreshold = 0.5; // seconds + if (!auction.IsAttackInProgress && state.Timer <= latencyThreshold) { - auction.AddLog($"[TRIGGER] Timer 0, attendo delay {auction.DelayMs}ms..."); - OnLog?.Invoke($"[BID] [{auction.AuctionId}] Timer 0, attendo delay..."); - if (auction.DelayMs > 0) - await Task.Delay(auction.DelayMs, token); - // Rileggi stato asta - var stateAfterDelay = await _apiClient.PollAuctionStateAsync(auction.AuctionId, auction.OriginalUrl, token); - if (stateAfterDelay != null && Math.Abs(stateAfterDelay.Timer) < 0.001 && stateAfterDelay.LastBidder == state.LastBidder) + // Enter attack state for this auction to avoid concurrent triggers + auction.IsAttackInProgress = true; + + try { - auction.AddLog($"[BID] Condizioni OK dopo delay, invio puntata..."); - OnLog?.Invoke($"[BID] [{auction.AuctionId}] PUNTATA dopo delay!"); - var result = await _apiClient.PlaceBidAsync(auction.AuctionId); + // Log detailed info into auction log + auction.AddLog($"[ATTACK] Final attack: Timer {state.Timer:F3}s <= {latencyThreshold}s -> executing final bid..."); + + // Place final minimal bid (one GET with auctionID & submit=1) + var finalResult = await _apiClient.PlaceBidFinalAsync(auction.AuctionId, auction.OriginalUrl); + auction.LastClickAt = DateTime.UtcNow; - OnBidExecuted?.Invoke(auction, result); - if (result.Success) + OnBidExecuted?.Invoke(auction, finalResult); + + if (finalResult.Success) { - auction.AddLog($"[OK] PUNTATA OK: {result.LatencyMs}ms -> EUR{result.NewPrice:F2}"); - OnLog?.Invoke($"[OK] [{auction.AuctionId}] Puntata riuscita {result.LatencyMs}ms"); + // Success: log concise global, detailed per-auction + auction.AddLog($"[OK] Final bid OK: {finalResult.LatencyMs}ms -> EUR{finalResult.NewPrice:F2}"); + OnLog?.Invoke($"[OK] Puntata riuscita su {auction.Name} ({auction.AuctionId}): {finalResult.LatencyMs}ms"); + + // After success, an expiration may be extended by server (T_exp = now + 8s) + // We simply resume normal monitoring to observe new timer } else { - auction.AddLog($"[FAIL] PUNTATA FALLITA: {result.Error}"); - OnLog?.Invoke($"[FAIL] [{auction.AuctionId}] ERRORE: {result.Error}"); + // Failure cases: log and do not attempt immediate retries + auction.AddLog($"[FAIL] Final bid failed: {finalResult.Error}"); + OnLog?.Invoke($"[FAIL] Puntata fallita su {auction.Name} ({auction.AuctionId}): {finalResult.Error}"); } + + // Always add history entry auction.BidHistory.Add(new BidHistory { - Timestamp = result.Timestamp, - EventType = result.Success ? BidEventType.MyBid : BidEventType.OpponentBid, + Timestamp = finalResult.Timestamp, + EventType = finalResult.Success ? BidEventType.MyBid : BidEventType.OpponentBid, Bidder = "Tu", - Price = stateAfterDelay.Price, - Timer = stateAfterDelay.Timer, - LatencyMs = result.LatencyMs, - Success = result.Success, - Notes = result.Success ? $"EUR{result.NewPrice:F2}" : result.Error + Price = state.Price, + Timer = state.Timer, + LatencyMs = finalResult.LatencyMs, + Success = finalResult.Success, + Notes = finalResult.Success ? $"EUR{finalResult.NewPrice:F2}" : finalResult.Error }); } - else + finally { - auction.AddLog($"[SKIP] Puntata non inviata: timer/utente cambiato dopo delay"); + auction.IsAttackInProgress = false; } + + // Done with final attack; skip the older branch below + return; } - else - { - // Logica normale per timer > 0 - auction.AddLog($"[TRIGGER] CONDIZIONI OK - Timer {state.Timer:F2}s <= {auction.TimerClick}s"); - auction.AddLog($"[BID] Invio puntata..."); - OnLog?.Invoke($"[BID] [{auction.AuctionId}] PUNTATA a {state.Timer:F2}s!"); + + // Otherwise fallback to normal early-bid behavior + if (Math.Abs(state.Timer) < 0.001) + { + // Put detailed info into auction log but avoid noisy global log lines + auction.AddLog($"[TRIGGER] Timer 0, attendo delay {auction.DelayMs}ms e invio puntata direttamente..."); + if (auction.DelayMs > 0) - { await Task.Delay(auction.DelayMs, token); - } + + // Direct bid - API client already writes detailed request/response into auction.AddLog via subscription var result = await _apiClient.PlaceBidAsync(auction.AuctionId); auction.LastClickAt = DateTime.UtcNow; OnBidExecuted?.Invoke(auction, result); + + // Add concise global log (single line) and keep extended details inside auction log if (result.Success) { auction.AddLog($"[OK] PUNTATA OK: {result.LatencyMs}ms -> EUR{result.NewPrice:F2}"); - OnLog?.Invoke($"[OK] [{auction.AuctionId}] Puntata riuscita {result.LatencyMs}ms"); + OnLog?.Invoke($"[OK] Puntata riuscita su {auction.Name} ({auction.AuctionId}): {result.LatencyMs}ms"); } else { auction.AddLog($"[FAIL] PUNTATA FALLITA: {result.Error}"); - OnLog?.Invoke($"[FAIL] [{auction.AuctionId}] ERRORE: {result.Error}"); + OnLog?.Invoke($"[FAIL] Puntata fallita su {auction.Name} ({auction.AuctionId}): {result.Error}"); } + auction.BidHistory.Add(new BidHistory { Timestamp = result.Timestamp, @@ -406,7 +424,44 @@ namespace AutoBidder.Services Success = result.Success, Notes = result.Success ? $"EUR{result.NewPrice:F2}" : result.Error }); - } + } + else + { + // Normal early-bid path: schedule immediate delay then place bid + auction.AddLog($"[TRIGGER] CONDIZIONI OK - Timer {state.Timer:F2}s <= {auction.TimerClick}s"); + + if (auction.DelayMs > 0) + { + await Task.Delay(auction.DelayMs, token); + } + + var result = await _apiClient.PlaceBidAsync(auction.AuctionId); + auction.LastClickAt = DateTime.UtcNow; + OnBidExecuted?.Invoke(auction, result); + + if (result.Success) + { + auction.AddLog($"[OK] PUNTATA OK: {result.LatencyMs}ms -> EUR{result.NewPrice:F2}"); + OnLog?.Invoke($"[OK] Puntata riuscita su {auction.Name} ({auction.AuctionId}): {result.LatencyMs}ms"); + } + else + { + auction.AddLog($"[FAIL] PUNTATA FALLITA: {result.Error}"); + OnLog?.Invoke($"[FAIL] Puntata fallita su {auction.Name} ({auction.AuctionId}): {result.Error}"); + } + + auction.BidHistory.Add(new BidHistory + { + Timestamp = result.Timestamp, + EventType = result.Success ? BidEventType.MyBid : BidEventType.OpponentBid, + Bidder = "Tu", + Price = state.Price, + Timer = state.Timer, + LatencyMs = result.LatencyMs, + Success = result.Success, + Notes = result.Success ? $"EUR{result.NewPrice:F2}" : result.Error + }); + } } } catch (Exception ex) diff --git a/Mimante/Services/BidooApiClient.cs b/Mimante/Services/BidooApiClient.cs index e8bfc6d..b497a16 100644 --- a/Mimante/Services/BidooApiClient.cs +++ b/Mimante/Services/BidooApiClient.cs @@ -425,6 +425,78 @@ namespace AutoBidder.Services } } + /// + /// Place a minimal final bid using the simpler payload required by the final-attack protocol. + /// Uses: ?auctionID=[ID]&submit=1 + /// + public async Task PlaceBidFinalAsync(string auctionId, string? auctionUrl = null) + { + var result = new BidResult + { + AuctionId = auctionId, + Timestamp = DateTime.UtcNow + }; + try + { + Log($"[BID FINAL] Placing final bid minimal payload", auctionId); + var url = "https://it.bidoo.com/bid.php"; + var payload = $"auctionID={WebUtility.UrlEncode(auctionId)}&submit=1"; + Log($"[BID REQUEST] GET {url}?{payload}", auctionId); + var getUrl = url + "?" + payload; + var request = new HttpRequestMessage(HttpMethod.Get, getUrl); + var referer = !string.IsNullOrEmpty(auctionUrl) ? auctionUrl : $"https://it.bidoo.com/asta/nome-prodotto-{auctionId}"; + AddAuthHeaders(request, referer, auctionId); + if (!request.Headers.Contains("Origin")) + { + request.Headers.Add("Origin", "https://it.bidoo.com"); + } + var startTime = DateTime.UtcNow; + var response = await _httpClient.SendAsync(request); + result.LatencyMs = (int)(DateTime.UtcNow - startTime).TotalMilliseconds; + Log($"[BID RESPONSE] Status: {(int)response.StatusCode} {response.StatusCode}", auctionId); + Log($"[BID RESPONSE] Latency: {result.LatencyMs}ms", auctionId); + var responseText = await response.Content.ReadAsStringAsync(); + result.Response = responseText; + Log($"[BID RESPONSE] Body length: {responseText.Length} bytes", auctionId); + if (!string.IsNullOrEmpty(responseText)) + { + var preview = responseText.Length > 80 ? responseText.Substring(0, 80) + "..." : responseText; + Log($"[BID RESPONSE] Preview: {preview}", auctionId); + } + if (responseText.StartsWith("ok", StringComparison.OrdinalIgnoreCase)) + { + result.Success = true; + var parts = responseText.Split('|'); + if (parts.Length > 1 && double.TryParse(parts[1], out var priceIndex)) + { + result.NewPrice = priceIndex * 0.01; + } + Log("[BID SUCCESS] ✓ Final bid placed successfully", auctionId); + } + else if (responseText.StartsWith("error", StringComparison.OrdinalIgnoreCase) || responseText.StartsWith("no|", StringComparison.OrdinalIgnoreCase)) + { + result.Success = false; + var parts = responseText.Split('|'); + result.Error = parts.Length > 1 ? parts[1] : responseText; + Log($"[BID ERROR] Server returned error: {result.Error}", auctionId); + } + else + { + result.Success = false; + result.Error = string.IsNullOrEmpty(responseText) ? $"HTTP {(int)response.StatusCode}" : responseText; + Log($"[BID ERROR] Unexpected response format: {result.Error}", auctionId); + } + return result; + } + catch (Exception ex) + { + result.Success = false; + result.Error = ex.Message; + Log($"[BID EXCEPTION] {ex.GetType().Name}: {ex.Message}", auctionId); + return result; + } + } + /// /// Determina lo stato dell'asta basandosi su Status, LastBidder, Timer ///