Ottimizzazioni e introduzione protocollo final attack

- Rimosso binding di `MaxClicks` in `MainWindow.xaml` e aggiunto evento `TextChanged`.
- Migliorata leggibilità del codice in `MainWindow.xaml.cs` con rientri e ritorni a capo.
- Evitata duplicazione dei log per aste aggiunte in `MainWindow.xaml.cs`.
- Migliorata gestione della validità del cookie con fallback su scraping HTML.
- Aggiunta proprietà `IsAttackInProgress` in `AuctionInfo.cs` per gestire lo stato di attacco finale.
- Introdotto protocollo di "final attack" in `AuctionMonitor.cs` per puntate critiche sotto 0,5s.
- Migliorata gestione dei log e comportamento delle puntate normali in `AuctionMonitor.cs`.
- Aggiunto metodo `PlaceBidFinalAsync` in `BidooApiClient.cs` per puntate ottimizzate.
- Ridotti log ridondanti e migliorata gestione degli errori.
This commit is contained in:
Alberto Balbo
2025-10-28 23:27:53 +01:00
parent 717dc44b3b
commit cb30e1fb08
5 changed files with 229 additions and 79 deletions

View File

@@ -413,7 +413,7 @@
<UniformGrid Columns="2" Margin="0,0,0,12">
<StackPanel Margin="0,0,4,0">
<TextBlock Text="Max Clicks (0=inf)" FontSize="10" Margin="0,0,0,4" Foreground="#999" />
<TextBox x:Name="SelectedMaxClicks" Text="{Binding MaxClicks, Mode=TwoWay}" Background="#1a1a1a" Foreground="#FFF" Padding="8" FontSize="12" BorderBrush="#444" BorderThickness="1" />
<TextBox x:Name="SelectedMaxClicks" Text="0" TextChanged="SelectedMaxClicks_TextChanged" Background="#1a1a1a" Foreground="#FFF" Padding="8" FontSize="12" BorderBrush="#444" BorderThickness="1" />
</StackPanel>
<StackPanel />
</UniformGrid>

View File

@@ -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)
{
@@ -979,12 +986,24 @@ namespace AutoBidder
}
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);
// 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.");
});
}
}
});
}

View File

@@ -46,6 +46,10 @@ namespace AutoBidder.Models
[System.Text.Json.Serialization.JsonIgnore]
public List<string> 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;
/// <summary>
/// Aggiunge una voce al log dell'asta
/// </summary>

View File

@@ -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,42 +327,99 @@ 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
// Use latency threshold (0.5s default) - treat as critical window
var latencyThreshold = 0.5; // seconds
if (!auction.IsAttackInProgress && state.Timer <= latencyThreshold)
{
// Enter attack state for this auction to avoid concurrent triggers
auction.IsAttackInProgress = true;
try
{
// 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, finalResult);
if (finalResult.Success)
{
// 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
{
// 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 = finalResult.Timestamp,
EventType = finalResult.Success ? BidEventType.MyBid : BidEventType.OpponentBid,
Bidder = "Tu",
Price = state.Price,
Timer = state.Timer,
LatencyMs = finalResult.LatencyMs,
Success = finalResult.Success,
Notes = finalResult.Success ? $"EUR{finalResult.NewPrice:F2}" : finalResult.Error
});
}
finally
{
auction.IsAttackInProgress = false;
}
// Done with final attack; skip the older branch below
return;
}
// Otherwise fallback to normal early-bid behavior
if (Math.Abs(state.Timer) < 0.001)
{
auction.AddLog($"[TRIGGER] Timer 0, attendo delay {auction.DelayMs}ms...");
OnLog?.Invoke($"[BID] [{auction.AuctionId}] Timer 0, attendo delay...");
// 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);
// 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)
{
auction.AddLog($"[BID] Condizioni OK dopo delay, invio puntata...");
OnLog?.Invoke($"[BID] [{auction.AuctionId}] PUNTATA dopo delay!");
// 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,
EventType = result.Success ? BidEventType.MyBid : BidEventType.OpponentBid,
Bidder = "Tu",
Price = stateAfterDelay.Price,
Timer = stateAfterDelay.Timer,
Price = state.Price,
Timer = state.Timer,
LatencyMs = result.LatencyMs,
Success = result.Success,
Notes = result.Success ? $"EUR{result.NewPrice:F2}" : result.Error
@@ -369,32 +427,29 @@ namespace AutoBidder.Services
}
else
{
auction.AddLog($"[SKIP] Puntata non inviata: timer/utente cambiato dopo delay");
}
}
else
{
// Logica normale per timer > 0
// Normal early-bid path: schedule immediate delay then place bid
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!");
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] [{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,

View File

@@ -425,6 +425,78 @@ namespace AutoBidder.Services
}
}
/// <summary>
/// Place a minimal final bid using the simpler payload required by the final-attack protocol.
/// Uses: ?auctionID=[ID]&submit=1
/// </summary>
public async Task<BidResult> 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;
}
}
/// <summary>
/// Determina lo stato dell'asta basandosi su Status, LastBidder, Timer
///