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
///