Miglioramenti logica e gestione attacco finale

- Aggiunta proprietà `FinalAttackThresholdSec` (0.8s) in `AuctionInfo.cs`.
- Implementata strategia di "quick re-poll" in `AuctionMonitor.cs` per confermare stato critico prima dell'attacco finale.
- Migliorata gestione delle eccezioni in `BidooApiClient.cs` con log dettagliati e tentativi alternativi.
- Registrazione del numero di offerte rimanenti dopo successo in `BidooApiClient.cs`.
- Ottimizzati messaggi di log per maggiore chiarezza e trasparenza.
- Rimossa logica obsoleta e aggiunti ritardi minimi tra tentativi di polling rapido.
This commit is contained in:
Alberto Balbo
2025-10-29 17:12:46 +01:00
parent cb30e1fb08
commit daf9ea31fc
3 changed files with 118 additions and 24 deletions

View File

@@ -50,6 +50,11 @@ namespace AutoBidder.Models
[System.Text.Json.Serialization.JsonIgnore] [System.Text.Json.Serialization.JsonIgnore]
public bool IsAttackInProgress { get; set; } = false; public bool IsAttackInProgress { get; set; } = false;
// Quando viene considerato il "final attack" (secondi)
// Se il timer dell'asta scende sotto questo valore, viene eseguita la puntata finale.
// Default 0.8s per anticipare leggermente rispetto al valore precedente di 0.5s.
public double FinalAttackThresholdSec { get; set; } = 0.8;
/// <summary> /// <summary>
/// Aggiunge una voce al log dell'asta /// Aggiunge una voce al log dell'asta
/// </summary> /// </summary>

View File

@@ -335,37 +335,82 @@ namespace AutoBidder.Services
var latencyThreshold = 0.5; // seconds var latencyThreshold = 0.5; // seconds
if (!auction.IsAttackInProgress && state.Timer <= latencyThreshold) if (!auction.IsAttackInProgress && state.Timer <= latencyThreshold)
{ {
// Enter attack state for this auction to avoid concurrent triggers // Quick re-poll strategy: perform a couple of fast re-polls to confirm that the timer
// is still in the critical window and that the lastBidder did not change.
auction.IsAttackInProgress = true; auction.IsAttackInProgress = true;
AuctionState? lastConfirmedState = state;
try try
{ {
// Log detailed info into auction log auction.AddLog($"[ATTACK] Candidate final attack: Timer {state.Timer:F3}s <= {latencyThreshold}s. Performing quick re-polls to confirm...");
auction.AddLog($"[ATTACK] Final attack: Timer {state.Timer:F3}s <= {latencyThreshold}s -> executing final bid...");
int attempts = 2;
// Place final minimal bid (one GET with auctionID & submit=1) for (int i = 0; i < attempts; i++)
var finalResult = await _apiClient.PlaceBidFinalAsync(auction.AuctionId, auction.OriginalUrl); {
try
{
using var cts = CancellationTokenSource.CreateLinkedTokenSource(token);
// small timeout for quick verification
cts.CancelAfter(TimeSpan.FromMilliseconds(400));
var quickState = await _apiClient.PollAuctionStateAsync(auction.AuctionId, auction.OriginalUrl, cts.Token);
if (quickState != null)
{
auction.AddLog($"[ATTACK] Quick re-poll #{i + 1}: Timer {quickState.Timer:F3}s, Bidder: {quickState.LastBidder}");
// If bidder changed to someone else, abort
if (!string.Equals(quickState.LastBidder, state.LastBidder, StringComparison.OrdinalIgnoreCase))
{
auction.AddLog($"[ATTACK] Aborting final attack: last bidder changed from '{state.LastBidder}' to '{quickState.LastBidder}'");
return;
}
// If timer increased above threshold, abort
if (quickState.Timer > latencyThreshold)
{
auction.AddLog($"[ATTACK] Aborting final attack: quickState.Timer {quickState.Timer:F3}s > threshold {latencyThreshold}s");
return;
}
// Confirmed critical window
lastConfirmedState = quickState;
break; // proceed to place bid
}
else
{
auction.AddLog($"[ATTACK] Quick re-poll #{i + 1} returned no data (timeout/error).\n");
}
}
catch (Exception exQuick)
{
auction.AddLog($"[ATTACK] Quick re-poll #{i + 1} exception: {exQuick.GetType().Name} - {exQuick.Message}");
}
// tiny delay between attempts
await Task.Delay(30, token);
}
// If no quickState confirmed but initial state indicated critical window, proceed but warn
if (lastConfirmedState == null)
{
auction.AddLog("[ATTACK] No quick re-poll confirmed state. Proceeding with final bid based on initial observation (risk of false positive).");
}
// Place final bid using the same request format as manual bids to mimic manual behavior
auction.AddLog($"[ATTACK] Executing final bid (using manual-format payload) for {auction.AuctionId} (confirmed: { (lastConfirmedState != null) })...");
var finalResult = await _apiClient.PlaceBidAsync(auction.AuctionId, auction.OriginalUrl);
auction.LastClickAt = DateTime.UtcNow; auction.LastClickAt = DateTime.UtcNow;
OnBidExecuted?.Invoke(auction, finalResult); OnBidExecuted?.Invoke(auction, finalResult);
if (finalResult.Success) if (finalResult.Success)
{ {
// Success: log concise global, detailed per-auction
auction.AddLog($"[OK] Final bid OK: {finalResult.LatencyMs}ms -> EUR{finalResult.NewPrice:F2}"); 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"); 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 else
{ {
// Failure cases: log and do not attempt immediate retries
auction.AddLog($"[FAIL] Final bid failed: {finalResult.Error}"); auction.AddLog($"[FAIL] Final bid failed: {finalResult.Error}");
OnLog?.Invoke($"[FAIL] Puntata fallita su {auction.Name} ({auction.AuctionId}): {finalResult.Error}"); OnLog?.Invoke($"[FAIL] Puntata fallita su {auction.Name} ({auction.AuctionId}): {finalResult.Error}");
} }
// Always add history entry
auction.BidHistory.Add(new BidHistory auction.BidHistory.Add(new BidHistory
{ {
Timestamp = finalResult.Timestamp, Timestamp = finalResult.Timestamp,
@@ -382,8 +427,7 @@ namespace AutoBidder.Services
{ {
auction.IsAttackInProgress = false; auction.IsAttackInProgress = false;
} }
// Done with final attack; skip the older branch below
return; return;
} }

View File

@@ -234,8 +234,12 @@ namespace AutoBidder.Services
} }
catch (Exception ex) catch (Exception ex)
{ {
Log($"[ERRORE] [{auctionId}] API non ha risposto (motivo: eccezione)", null); // globale // Global concise message
Log($"API non ha risposto: {ex.GetType().Name} - {ex.Message}", auctionId); // asta Log($"[ERRORE] [{auctionId}] API non ha risposto (verificare dettagli nel log asta)", null);
// Detailed per-auction log with full exception and context
var details = ex.ToString();
details = "[API EXCEPTION] " + details;
Log(details, auctionId);
return null; return null;
} }
} }
@@ -392,7 +396,17 @@ namespace AutoBidder.Services
{ {
result.NewPrice = priceIndex * 0.01; result.NewPrice = priceIndex * 0.01;
} }
Log("[BID SUCCESS] ✓ Bid placed successfully", auctionId); // Parse remaining bids from response if present: ok|324|...
var parts2 = responseText.Split('|');
if (parts2.Length > 1 && int.TryParse(parts2[1], out var remaining))
{
_session.RemainingBids = remaining;
Log($"[BID SUCCESS] ✓ Bid placed successfully - Remaining bids: {remaining}", auctionId);
}
else
{
Log("[BID SUCCESS] ✓ Bid placed successfully", auctionId);
}
} }
else if (responseText.StartsWith("error", StringComparison.OrdinalIgnoreCase) || responseText.StartsWith("no|", StringComparison.OrdinalIgnoreCase)) else if (responseText.StartsWith("error", StringComparison.OrdinalIgnoreCase) || responseText.StartsWith("no|", StringComparison.OrdinalIgnoreCase))
{ {
@@ -413,14 +427,38 @@ namespace AutoBidder.Services
result.Error = string.IsNullOrEmpty(responseText) ? $"HTTP {(int)response.StatusCode}" : responseText; result.Error = string.IsNullOrEmpty(responseText) ? $"HTTP {(int)response.StatusCode}" : responseText;
Log($"[BID ERROR] Unexpected response format: {result.Error}", auctionId); Log($"[BID ERROR] Unexpected response format: {result.Error}", auctionId);
} }
// If initial attempt failed or returned unexpected format, try alternate payload once
if (!result.Success)
{
Log($"[BID] Initial attempt failed for {auctionId}. Trying alternate payload (auctionID=...)\n", auctionId);
try
{
var alt = await PlaceBidFinalAsync(auctionId, auctionUrl);
// Merge alt result into result (prefer alt)
return alt;
}
catch (Exception exAlt)
{
Log($"[BID] Alternate attempt threw: {exAlt.GetType().Name} - {exAlt.Message}", auctionId);
}
}
return result; return result;
} }
catch (Exception ex) catch (Exception ex)
{ {
result.Success = false; result.Success = false;
result.Error = ex.Message; result.Error = ex.Message;
Log($"[BID EXCEPTION] {ex.GetType().Name}: {ex.Message}", auctionId); // Generic global-style hint (via auction log event, AuctionMonitor will emit concise global message)
Log($"[BID EXCEPTION] StackTrace available in debug logs", auctionId); Log($"[BID EXCEPTION] Errore durante il piazzamento della puntata: {ex.GetType().Name}. Vedere log asta per dettagli.", auctionId);
// Detailed per-auction info
var sb = new System.Text.StringBuilder();
sb.AppendLine("[BID EXCEPTION DETAILED]");
sb.AppendLine(ex.ToString());
sb.AppendLine($"RequestUri: { (auctionUrl ?? "https://it.bidoo.com/bid.php") }");
sb.AppendLine($"HttpClient.Timeout: {_httpClient.Timeout.TotalSeconds}s");
sb.AppendLine($"CookiePresent: {!string.IsNullOrEmpty(_session.CookieString)} (length: {(_session.CookieString?.Length ?? 0)})");
Log(sb.ToString(), auctionId);
return result; return result;
} }
} }
@@ -492,7 +530,14 @@ namespace AutoBidder.Services
{ {
result.Success = false; result.Success = false;
result.Error = ex.Message; result.Error = ex.Message;
Log($"[BID EXCEPTION] {ex.GetType().Name}: {ex.Message}", auctionId); Log($"[BID EXCEPTION] Errore durante il piazzamento della puntata (final): {ex.GetType().Name}. Vedere log asta per dettagli.", auctionId);
var sb = new System.Text.StringBuilder();
sb.AppendLine("[BID FINAL EXCEPTION DETAILED]");
sb.AppendLine(ex.ToString());
sb.AppendLine($"RequestUri: { (auctionUrl ?? "https://it.bidoo.com/bid.php") }");
sb.AppendLine($"HttpClient.Timeout: {_httpClient.Timeout.TotalSeconds}s");
sb.AppendLine($"CookiePresent: {!string.IsNullOrEmpty(_session.CookieString)} (length: {(_session.CookieString?.Length ?? 0)})");
Log(sb.ToString(), auctionId);
return result; return result;
} }
} }