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:
@@ -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>
|
||||
|
||||
@@ -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.");
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user