Aggiornamento alla versione 4.0.0
* Aggiunto `BooleanToOpacityConverter` per gestire opacità dinamica. * Introdotto nuovo sistema di timing con `BidBeforeDeadlineMs`. * Aggiunta opzione `CheckAuctionOpenBeforeBid` per maggiore sicurezza. * Implementato polling adattivo (10ms-1000ms) e cooldown di 800ms. * Migliorata gestione pulsanti globali con supporto `AUTO-START`/`AUTO-STOP`. * Fix per il tasto `Canc` e focus automatico sul `DataGrid`. * Fix per avvio singola asta senza necessità di "Avvia Tutti". * Aggiornati formati CSV/JSON/XML con nuovi campi. * Migliorata gestione cookie con endpoint unico `buy_bids.php`. * Miglioramenti UI/UX: tooltip, formattazione prezzi, feedback visivo. * Aggiornata documentazione e changelog per la versione 4.0.0.
This commit is contained in:
+174
-263
@@ -9,7 +9,7 @@ namespace AutoBidder.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Servizio centrale per monitoraggio aste
|
||||
/// Solo HTTP, nessuna modalità, browser o multi-click
|
||||
/// Sistema di timing ottimizzato: punta solo se necessario, poco prima della scadenza
|
||||
/// </summary>
|
||||
public class AuctionMonitor
|
||||
{
|
||||
@@ -21,13 +21,12 @@ namespace AutoBidder.Services
|
||||
public event Action<AuctionState>? OnAuctionUpdated;
|
||||
public event Action<AuctionInfo, BidResult>? OnBidExecuted;
|
||||
public event Action<string>? OnLog;
|
||||
public event Action<string>? OnResetCountChanged; // Notifica cambio contatore reset
|
||||
public event Action<string>? OnResetCountChanged;
|
||||
|
||||
public AuctionMonitor()
|
||||
{
|
||||
_apiClient = new BidooApiClient();
|
||||
|
||||
// Subscribe to detailed per-auction logs from API client
|
||||
_apiClient.OnAuctionLog += (auctionId, message) =>
|
||||
{
|
||||
try
|
||||
@@ -45,59 +44,38 @@ namespace AutoBidder.Services
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inizializza sessione con token di autenticazione
|
||||
/// </summary>
|
||||
public void InitializeSession(string authToken, string username)
|
||||
{
|
||||
_apiClient.InitializeSession(authToken, username);
|
||||
OnLog?.Invoke($"[OK] Sessione configurata per: {username}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inizializza sessione con cookie (fallback legacy)
|
||||
/// </summary>
|
||||
public void InitializeSessionWithCookie(string cookieString, string username)
|
||||
{
|
||||
_apiClient.InitializeSessionWithCookie(cookieString, username);
|
||||
OnLog?.Invoke($"[OK] Sessione configurata (cookie) per: {username}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aggiorna info utente (puntate rimanenti)
|
||||
/// </summary>
|
||||
public async Task<bool> UpdateUserInfoAsync()
|
||||
{
|
||||
return await _apiClient.UpdateUserInfoAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottieni sessione corrente
|
||||
/// </summary>
|
||||
public BidooSession GetSession()
|
||||
{
|
||||
return _apiClient.GetSession();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene dati utente (nome, puntate residue, saldo, id) tramite chiamata AJAX leggera
|
||||
/// </summary>
|
||||
public async Task<UserData?> GetUserDataAsync()
|
||||
{
|
||||
return await _apiClient.GetUserDataAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene info banner utente (aste vinte, bonus, ecc.) tramite chiamata AJAX
|
||||
/// </summary>
|
||||
public async Task<UserBannerInfo?> GetUserBannerInfoAsync()
|
||||
{
|
||||
return await _apiClient.GetUserBannerInfoAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Estrae nome utente e puntate residue dall'HTML di bids_history.php
|
||||
/// </summary>
|
||||
public async Task<UserData?> GetUserDataFromHtmlAsync()
|
||||
{
|
||||
return await _apiClient.GetUserDataFromHtmlAsync();
|
||||
@@ -167,9 +145,6 @@ namespace AutoBidder.Services
|
||||
List<AuctionInfo> activeAuctions;
|
||||
lock (_auctions)
|
||||
{
|
||||
// Filtra aste che devono ancora essere monitorate
|
||||
// Include aste attive anche se messe in pausa: vogliamo continuare a monitorarle
|
||||
// ma non inviare puntate per quelle in pausa.
|
||||
activeAuctions = _auctions.Where(a =>
|
||||
a.IsActive &&
|
||||
!IsAuctionTerminated(a)
|
||||
@@ -189,16 +164,14 @@ namespace AutoBidder.Services
|
||||
// Ottimizzazione polling per aste in pausa
|
||||
bool anyPaused = false;
|
||||
DateTime now = DateTime.Now;
|
||||
int pauseDelayMs = 1000; // default
|
||||
int pauseDelayMs = 1000;
|
||||
foreach (var a in activeAuctions)
|
||||
{
|
||||
if (a.IsPaused)
|
||||
{
|
||||
anyPaused = true;
|
||||
// Se tra le 00:00 e le 09:55 polling ogni 60s
|
||||
if (now.Hour < 9 || (now.Hour == 9 && now.Minute < 55))
|
||||
pauseDelayMs = 60000;
|
||||
// Negli ultimi 5 minuti prima delle 10 polling ogni 5s
|
||||
else if (now.Hour == 9 && now.Minute >= 55)
|
||||
pauseDelayMs = 5000;
|
||||
}
|
||||
@@ -218,13 +191,14 @@ namespace AutoBidder.Services
|
||||
|
||||
int delayMs = lowestTimer switch
|
||||
{
|
||||
< 1 => 5, // Iper-veloce: polling ogni 5ms (0-1s rimanenti)
|
||||
< 2 => 20, // Ultra-veloce: polling ogni 20ms (1-2s)
|
||||
< 3 => 50, // Molto veloce: polling ogni 50ms (2-3s)
|
||||
< 5 => 100, // Veloce: polling ogni 100ms (3-5s)
|
||||
< 10 => 200, // Medio: polling ogni 200ms (5-10s)
|
||||
< 30 => 500, // Lento: polling ogni 500ms (10-30s)
|
||||
_ => 1000 // Molto lento: polling ogni 1s (>30s)
|
||||
< 0.5 => 10, // Ultra-critico: polling ogni 10ms
|
||||
< 1 => 20, // Iper-veloce: polling ogni 20ms
|
||||
< 2 => 50, // Ultra-veloce: polling ogni 50ms
|
||||
< 3 => 100, // Molto veloce: polling ogni 100ms
|
||||
< 5 => 150, // Veloce: polling ogni 150ms
|
||||
< 10 => 300, // Medio: polling ogni 300ms
|
||||
< 30 => 500, // Lento: polling ogni 500ms
|
||||
_ => 1000 // Molto lento: polling ogni 1s
|
||||
};
|
||||
|
||||
await Task.Delay(delayMs, token);
|
||||
@@ -241,16 +215,11 @@ namespace AutoBidder.Services
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifica se un'asta è terminata e non deve più essere monitorata
|
||||
/// </summary>
|
||||
private bool IsAuctionTerminated(AuctionInfo auction)
|
||||
{
|
||||
// Se l'ultima entry nello storico indica uno stato finale, ferma polling
|
||||
var lastHistory = auction.BidHistory.LastOrDefault();
|
||||
if (lastHistory != null)
|
||||
{
|
||||
// Controlla se c'è una nota che indica fine asta
|
||||
if (lastHistory.Notes != null &&
|
||||
(lastHistory.Notes.Contains("VINTA") ||
|
||||
lastHistory.Notes.Contains("Persa") ||
|
||||
@@ -267,7 +236,6 @@ namespace AutoBidder.Services
|
||||
{
|
||||
try
|
||||
{
|
||||
// Poll tramite API Bidoo (passa anche l'URL originale per referer corretto)
|
||||
var state = await _apiClient.PollAuctionStateAsync(auction.AuctionId, auction.OriginalUrl, token);
|
||||
|
||||
if (state == null)
|
||||
@@ -277,10 +245,8 @@ namespace AutoBidder.Services
|
||||
return;
|
||||
}
|
||||
|
||||
// Aggiorna la latenza per la DataGrid
|
||||
auction.PollingLatencyMs = state.PollingLatencyMs;
|
||||
|
||||
// Se l'asta è terminata, segnala e disattiva polling
|
||||
if (state.Status == AuctionStatus.EndedWon ||
|
||||
state.Status == AuctionStatus.EndedLost ||
|
||||
state.Status == AuctionStatus.Closed)
|
||||
@@ -288,13 +254,10 @@ namespace AutoBidder.Services
|
||||
string statusMsg = state.Status == AuctionStatus.EndedWon ? "VINTA" :
|
||||
state.Status == AuctionStatus.EndedLost ? "Persa" : "Chiusa";
|
||||
|
||||
// Mark auction inactive immediately to stop further polling
|
||||
auction.IsActive = false;
|
||||
|
||||
auction.AddLog($"[ASTA TERMINATA] {statusMsg}");
|
||||
OnLog?.Invoke($"[FINE] [{auction.AuctionId}] Asta {statusMsg} - Polling fermato");
|
||||
|
||||
// Aggiungi entry nello storico per marcare come terminata
|
||||
auction.BidHistory.Add(new BidHistory
|
||||
{
|
||||
Timestamp = DateTime.UtcNow,
|
||||
@@ -305,222 +268,148 @@ namespace AutoBidder.Services
|
||||
Notes = $"Asta {statusMsg}"
|
||||
});
|
||||
|
||||
// Notifica UI e fermati
|
||||
OnAuctionUpdated?.Invoke(state);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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");
|
||||
// Log RIMOSSO per ridurre verbosità - polling continuo non necessita log
|
||||
// Solo eventi importanti (bid, reset, errori) vengono loggati
|
||||
}
|
||||
else if (state.Status == AuctionStatus.Paused)
|
||||
{
|
||||
auction.AddLog($"[PAUSA] Asta in pausa - Timer: {state.Timer:F2}s, EUR{state.Price:F2}");
|
||||
// Log solo primo cambio stato, non ad ogni polling
|
||||
var lastLog = auction.AuctionLog.LastOrDefault();
|
||||
if (lastLog == null || !lastLog.Contains("[PAUSA]"))
|
||||
{
|
||||
auction.AddLog($"[PAUSA] Asta in pausa - Timer: {state.Timer:F3}s, EUR{state.Price:F2}");
|
||||
}
|
||||
}
|
||||
|
||||
// Notifica aggiornamento UI
|
||||
OnAuctionUpdated?.Invoke(state);
|
||||
|
||||
// Aggiorna storico e bidders
|
||||
UpdateAuctionHistory(auction, state);
|
||||
|
||||
// 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))
|
||||
// NUOVA LOGICA: Punta solo se siamo vicini alla deadline E nessun altro ha appena puntato
|
||||
if (state.Status == AuctionStatus.Running && !auction.IsPaused && !auction.IsAttackInProgress)
|
||||
{
|
||||
// Use latency threshold (0.5s default) - treat as critical window
|
||||
var latencyThreshold = 0.5; // seconds
|
||||
if (!auction.IsAttackInProgress && state.Timer <= latencyThreshold)
|
||||
if (ShouldBid(auction, state))
|
||||
{
|
||||
// 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;
|
||||
AuctionState? lastConfirmedState = state;
|
||||
try
|
||||
{
|
||||
auction.AddLog($"[ATTACK] Candidate final attack: Timer {state.Timer:F3}s <= {latencyThreshold}s. Performing quick re-polls to confirm...");
|
||||
|
||||
int attempts = 2;
|
||||
for (int i = 0; i < attempts; i++)
|
||||
{
|
||||
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;
|
||||
OnBidExecuted?.Invoke(auction, finalResult);
|
||||
|
||||
if (finalResult.Success)
|
||||
{
|
||||
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");
|
||||
}
|
||||
else
|
||||
{
|
||||
auction.AddLog($"[FAIL] Final bid failed: {finalResult.Error}");
|
||||
OnLog?.Invoke($"[FAIL] Puntata fallita su {auction.Name} ({auction.AuctionId}): {finalResult.Error}");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return;
|
||||
await ExecuteBidStrategy(auction, state, token);
|
||||
}
|
||||
|
||||
// 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] 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
|
||||
});
|
||||
}
|
||||
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)
|
||||
{
|
||||
auction.AddLog($"[EXCEPTION] ERRORE: {ex.Message}");
|
||||
auction.AddLog($"[EXCEPTION] {ex.Message}");
|
||||
OnLog?.Invoke($"[EXCEPTION] [{auction.AuctionId}] {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Strategia di puntata ottimizzata: punta solo quando necessario
|
||||
/// </summary>
|
||||
private async Task ExecuteBidStrategy(AuctionInfo auction, AuctionState state, CancellationToken token)
|
||||
{
|
||||
// Calcola il tempo rimanente in millisecondi
|
||||
double timerMs = state.Timer * 1000;
|
||||
|
||||
// Se siamo nella finestra di puntata (timer <= BidBeforeDeadlineMs)
|
||||
if (timerMs <= auction.BidBeforeDeadlineMs)
|
||||
{
|
||||
auction.IsAttackInProgress = true;
|
||||
|
||||
try
|
||||
{
|
||||
auction.AddLog($"[STRATEGIA] Finestra di puntata raggiunta: {timerMs:F0}ms <= {auction.BidBeforeDeadlineMs}ms");
|
||||
|
||||
// Controlla se qualcun altro ha puntato di recente
|
||||
var lastBidTime = GetLastBidTime(auction, state.LastBidder);
|
||||
if (lastBidTime.HasValue)
|
||||
{
|
||||
var timeSinceLastBid = DateTime.UtcNow - lastBidTime.Value;
|
||||
if (timeSinceLastBid.TotalMilliseconds < 500)
|
||||
{
|
||||
auction.AddLog($"[STRATEGIA] Puntata recente di {state.LastBidder} ({timeSinceLastBid.TotalMilliseconds:F0}ms fa), attendo...");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Esegui la puntata
|
||||
await ExecuteBid(auction, state, token);
|
||||
}
|
||||
finally
|
||||
{
|
||||
auction.IsAttackInProgress = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Esegue la puntata con verifica opzionale dello stato dell'asta
|
||||
/// </summary>
|
||||
private async Task ExecuteBid(AuctionInfo auction, AuctionState state, CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Se richiesto, verifica prima che l'asta sia ancora aperta
|
||||
if (auction.CheckAuctionOpenBeforeBid)
|
||||
{
|
||||
auction.AddLog("[PRE-CHECK] Verifica stato asta...");
|
||||
var preCheckState = await _apiClient.PollAuctionStateAsync(auction.AuctionId, auction.OriginalUrl, token);
|
||||
|
||||
if (preCheckState == null)
|
||||
{
|
||||
auction.AddLog("[PRE-CHECK] FALLITO: Nessuna risposta");
|
||||
return;
|
||||
}
|
||||
|
||||
if (preCheckState.Status != AuctionStatus.Running)
|
||||
{
|
||||
auction.AddLog($"[PRE-CHECK] ABORTITO: Asta non running (status: {preCheckState.Status})");
|
||||
return;
|
||||
}
|
||||
|
||||
auction.AddLog($"[PRE-CHECK] OK - Timer: {preCheckState.Timer:F3}s");
|
||||
}
|
||||
|
||||
// Esegui la puntata
|
||||
var result = await _apiClient.PlaceBidAsync(auction.AuctionId, auction.OriginalUrl);
|
||||
auction.LastClickAt = DateTime.UtcNow;
|
||||
OnBidExecuted?.Invoke(auction, result);
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
auction.AddLog($"[BID OK] Latenza: {result.LatencyMs}ms -> EUR{result.NewPrice:F2}");
|
||||
OnLog?.Invoke($"[OK] Puntata riuscita su {auction.Name} ({auction.AuctionId}): {result.LatencyMs}ms");
|
||||
}
|
||||
else
|
||||
{
|
||||
auction.AddLog($"[BID FAIL] {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)
|
||||
{
|
||||
auction.AddLog($"[BID EXCEPTION] {ex.Message}");
|
||||
OnLog?.Invoke($"[BID EXCEPTION] [{auction.AuctionId}] {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private bool ShouldBid(AuctionInfo auction, AuctionState state)
|
||||
{
|
||||
// Timer check
|
||||
if (state.Timer > auction.TimerClick)
|
||||
return false;
|
||||
|
||||
// Price check
|
||||
if (auction.MinPrice > 0 && state.Price < auction.MinPrice)
|
||||
return false;
|
||||
@@ -528,54 +417,77 @@ namespace AutoBidder.Services
|
||||
if (auction.MaxPrice > 0 && state.Price > auction.MaxPrice)
|
||||
return false;
|
||||
|
||||
// Cooldown check (evita click multipli ravvicinati)
|
||||
// Reset count check
|
||||
if (auction.MinResets > 0 && auction.ResetCount < auction.MinResets)
|
||||
return false;
|
||||
|
||||
if (auction.MaxResets > 0 && auction.ResetCount >= auction.MaxResets)
|
||||
return false;
|
||||
|
||||
// Max clicks check
|
||||
int myBidsCount = auction.BidHistory.Count(b => b.EventType == BidEventType.MyBid);
|
||||
if (auction.MaxClicks > 0 && myBidsCount >= auction.MaxClicks)
|
||||
return false;
|
||||
|
||||
// Cooldown check (evita puntate multiple ravvicinate)
|
||||
if (auction.LastClickAt.HasValue)
|
||||
{
|
||||
var timeSinceLastClick = DateTime.UtcNow - auction.LastClickAt.Value;
|
||||
if (timeSinceLastClick.TotalSeconds < 1)
|
||||
if (timeSinceLastClick.TotalMilliseconds < 800)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private DateTime? GetLastBidTime(AuctionInfo auction, string bidder)
|
||||
{
|
||||
if (string.IsNullOrEmpty(bidder))
|
||||
return null;
|
||||
|
||||
if (auction.BidderStats.TryGetValue(bidder, out var info))
|
||||
{
|
||||
return info.LastBidTime;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void UpdateAuctionHistory(AuctionInfo auction, AuctionState state)
|
||||
{
|
||||
// Traccia l'ultima puntata per rilevare cambi
|
||||
var lastHistory = auction.BidHistory.LastOrDefault();
|
||||
var lastPrice = lastHistory?.Price ?? 0;
|
||||
var lastBidder = lastHistory?.Bidder;
|
||||
|
||||
bool isNewBid = false;
|
||||
|
||||
// Nuova puntata = CAMBIO PREZZO (più affidabile)
|
||||
// Ogni incremento di prezzo significa che qualcuno ha puntato
|
||||
// Nuova puntata = CAMBIO PREZZO
|
||||
if (state.Price > lastPrice && state.Price > 0)
|
||||
{
|
||||
isNewBid = true;
|
||||
}
|
||||
|
||||
// Fallback: cambio utente (se il prezzo è uguale ma l'utente cambia)
|
||||
if (!isNewBid &&
|
||||
!string.IsNullOrEmpty(lastBidder) &&
|
||||
!string.IsNullOrEmpty(state.LastBidder) &&
|
||||
!lastBidder.Equals(state.LastBidder, StringComparison.OrdinalIgnoreCase))
|
||||
// Fallback: cambio utente
|
||||
if (!isNewBid &&
|
||||
!string.IsNullOrEmpty(lastBidder) &&
|
||||
!string.IsNullOrEmpty(state.LastBidder) &&
|
||||
!lastBidder.Equals(state.LastBidder, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
isNewBid = true;
|
||||
}
|
||||
|
||||
if (isNewBid)
|
||||
{
|
||||
auction.ResetCount++;
|
||||
auction.BidHistory.Add(new BidHistory
|
||||
{
|
||||
isNewBid = true;
|
||||
}
|
||||
|
||||
if (isNewBid)
|
||||
{
|
||||
auction.ResetCount++;
|
||||
auction.BidHistory.Add(new BidHistory
|
||||
{
|
||||
Timestamp = DateTime.UtcNow,
|
||||
EventType = BidEventType.Reset,
|
||||
Bidder = state.LastBidder,
|
||||
Price = state.Price,
|
||||
Timer = state.Timer,
|
||||
Notes = $"Puntata: EUR{state.Price:F2}"
|
||||
});
|
||||
Timestamp = DateTime.UtcNow,
|
||||
EventType = BidEventType.Reset,
|
||||
Bidder = state.LastBidder,
|
||||
Price = state.Price,
|
||||
Timer = state.Timer,
|
||||
Notes = $"Puntata: EUR{state.Price:F2}"
|
||||
});
|
||||
|
||||
// Aggiorna statistiche bidder
|
||||
if (!string.IsNullOrEmpty(state.LastBidder))
|
||||
@@ -592,7 +504,6 @@ namespace AutoBidder.Services
|
||||
auction.BidderStats[state.LastBidder].LastBidTime = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
// Notifica cambio reset count per aggiornare UI
|
||||
OnResetCountChanged?.Invoke(auction.AuctionId);
|
||||
}
|
||||
}
|
||||
|
||||
+226
-147
@@ -90,7 +90,7 @@ namespace AutoBidder.Services
|
||||
if (!string.IsNullOrWhiteSpace(_session.CookieString))
|
||||
{
|
||||
request.Headers.Add("Cookie", _session.CookieString);
|
||||
Log("[AUTH] Using full cookie string", auctionId);
|
||||
// Log rimosso per ridurre verbosità
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -98,7 +98,6 @@ namespace AutoBidder.Services
|
||||
}
|
||||
|
||||
// 2. HEADERS BROWSER-LIKE (anti-detection)
|
||||
|
||||
// User-Agent realistico (Chrome su Windows)
|
||||
request.Headers.Add("User-Agent",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36");
|
||||
@@ -131,7 +130,7 @@ namespace AutoBidder.Services
|
||||
request.Headers.Add("Referer", "https://it.bidoo.com/");
|
||||
}
|
||||
|
||||
Log("[HEADERS] Browser-like headers added (anti-bot)", auctionId);
|
||||
// Log rimosso per ridurre verbosità - headers sempre aggiunti
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -321,7 +320,8 @@ namespace AutoBidder.Services
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = "https://it.bidoo.com/ajax/get_auction_bids_info_banner.php";
|
||||
// OTTIMIZZATO: Usa buy_bids.php che contiene tutti i dati in un'unica chiamata
|
||||
var url = "https://it.bidoo.com/buy_bids.php";
|
||||
|
||||
Log($"[USER INFO REQUEST] GET {url}");
|
||||
|
||||
@@ -332,24 +332,133 @@ namespace AutoBidder.Services
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
var latency = (int)(DateTime.UtcNow - startTime).TotalMilliseconds;
|
||||
|
||||
Log($"[USER INFO RESPONSE] Status: {(int)response.StatusCode} {response.StatusCode}");
|
||||
Log($"[USER INFO RESPONSE] Latency: {latency}ms");
|
||||
Log($"[USER INFO RESPONSE] Status: {(int)response.StatusCode} {response.StatusCode}, Latency: {latency}ms");
|
||||
|
||||
var responseText = await response.Content.ReadAsStringAsync();
|
||||
Log($"[USER INFO RESPONSE] Body length: {responseText.Length}");
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
Log($"[USER INFO ERROR] HTTP {response.StatusCode}");
|
||||
Log($"[USER INFO ERROR] HTTP {response.StatusCode} - Cookie potrebbe essere scaduto o non valido");
|
||||
return false;
|
||||
}
|
||||
|
||||
var html = await response.Content.ReadAsStringAsync();
|
||||
Log($"[USER INFO RESPONSE] Body length: {html.Length} chars");
|
||||
|
||||
// Verifica se la risposta contiene HTML valido
|
||||
if (html.Length < 100)
|
||||
{
|
||||
Log($"[USER INFO ERROR] Risposta troppo corta ({html.Length} chars) - possibile errore server");
|
||||
return false;
|
||||
}
|
||||
|
||||
_session.LastAccountUpdate = DateTime.UtcNow;
|
||||
return true;
|
||||
// Parsa l'oggetto JavaScript BidooCnf.userObj
|
||||
bool foundData = false;
|
||||
|
||||
// Estrai ID utente: BidooCnf.userObj.id = '6707664';
|
||||
var idMatch = System.Text.RegularExpressions.Regex.Match(html, @"BidooCnf\.userObj\.id\s*=\s*'([^']+)'");
|
||||
if (idMatch.Success)
|
||||
{
|
||||
if (int.TryParse(idMatch.Groups[1].Value, out int userId))
|
||||
{
|
||||
_session.UserId = userId;
|
||||
Log($"[USER INFO PARSED] User ID: {userId}");
|
||||
foundData = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"[USER INFO WARN] User ID non trovato");
|
||||
}
|
||||
|
||||
// Estrai email: BidooCnf.userObj.email = 'albertobalbo96@gmail.com';
|
||||
var emailMatch = System.Text.RegularExpressions.Regex.Match(html, @"BidooCnf\.userObj\.email\s*=\s*'([^']+)'");
|
||||
if (emailMatch.Success)
|
||||
{
|
||||
_session.Email = emailMatch.Groups[1].Value;
|
||||
Log($"[USER INFO PARSED] Email: {_session.Email}");
|
||||
foundData = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"[USER INFO WARN] Email non trovata");
|
||||
}
|
||||
|
||||
// Estrai username: BidooCnf.userObj.username = 'sirbietole23';
|
||||
var usernameMatch = System.Text.RegularExpressions.Regex.Match(html, @"BidooCnf\.userObj\.username\s*=\s*'([^']+)'");
|
||||
if (usernameMatch.Success)
|
||||
{
|
||||
_session.Username = usernameMatch.Groups[1].Value;
|
||||
Log($"[USER INFO PARSED] Username: {_session.Username}");
|
||||
foundData = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"[USER INFO WARN] Username non trovato");
|
||||
}
|
||||
|
||||
// Estrai telefono: BidooCnf.userObj.phone = '00393665920653';
|
||||
var phoneMatch = System.Text.RegularExpressions.Regex.Match(html, @"BidooCnf\.userObj\.phone\s*=\s*'([^']+)'");
|
||||
if (phoneMatch.Success)
|
||||
{
|
||||
_session.Phone = phoneMatch.Groups[1].Value;
|
||||
Log($"[USER INFO PARSED] Phone: {_session.Phone}");
|
||||
foundData = true;
|
||||
}
|
||||
|
||||
// Estrai puntate residue dall'HTML: <span id="divSaldoBidMobile">206</span>
|
||||
var bidsPatterns = new[]
|
||||
{
|
||||
@"<span[^>]*id=""divSaldoBidMobile""[^>]*>(\d+)</span>",
|
||||
@"<span[^>]*id=""divSaldoBidBottom""[^>]*>(\d+)</span>",
|
||||
@"<span[^>]*id=""divSaldoBid""[^>]*>(\d+)</span>"
|
||||
};
|
||||
|
||||
bool foundBids = false;
|
||||
foreach (var pattern in bidsPatterns)
|
||||
{
|
||||
var bidsMatch = System.Text.RegularExpressions.Regex.Match(html, pattern);
|
||||
if (bidsMatch.Success && int.TryParse(bidsMatch.Groups[1].Value, out int bids))
|
||||
{
|
||||
_session.RemainingBids = bids;
|
||||
Log($"[USER INFO PARSED] Remaining bids: {bids}");
|
||||
foundData = true;
|
||||
foundBids = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundBids)
|
||||
{
|
||||
Log($"[USER INFO WARN] Puntate residue non trovate");
|
||||
}
|
||||
|
||||
// Estrai credito Bidoo Shop: <span class="cbstotal">15.00</span>
|
||||
var creditMatch = System.Text.RegularExpressions.Regex.Match(html, @"<span[^>]*class=""cbstotal""[^>]*>([\d.]+)</span>");
|
||||
if (creditMatch.Success && double.TryParse(creditMatch.Groups[1].Value, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out double credit))
|
||||
{
|
||||
_session.ShopCredit = credit;
|
||||
Log($"[USER INFO PARSED] Shop credit: €{credit:F2}");
|
||||
foundData = true;
|
||||
}
|
||||
|
||||
if (foundData)
|
||||
{
|
||||
_session.LastAccountUpdate = DateTime.UtcNow;
|
||||
Log($"[USER INFO SUCCESS] Dati estratti correttamente da buy_bids.php");
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"[USER INFO ERROR] NESSUN dato trovato nell'HTML - cookie probabilmente non valido");
|
||||
// Salva snippet per debug
|
||||
var htmlSnippet = html.Substring(0, Math.Min(500, html.Length)).Replace("\n", " ").Replace("\r", "");
|
||||
Log($"[USER INFO DEBUG] Snippet HTML: {htmlSnippet}...");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"[USER INFO EXCEPTION] {ex.GetType().Name}: {ex.Message}");
|
||||
Log($"[USER INFO EXCEPTION] StackTrace: {ex.StackTrace}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -363,10 +472,8 @@ namespace AutoBidder.Services
|
||||
};
|
||||
try
|
||||
{
|
||||
Log($"[BID] Placing bid via direct GET to bid.php", auctionId);
|
||||
var url = "https://it.bidoo.com/bid.php";
|
||||
var payload = $"AID={WebUtility.UrlEncode(auctionId)}&sup=0&shock=0";
|
||||
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}";
|
||||
@@ -375,19 +482,14 @@ namespace AutoBidder.Services
|
||||
{
|
||||
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;
|
||||
@@ -397,50 +499,43 @@ namespace AutoBidder.Services
|
||||
result.NewPrice = priceIndex * 0.01;
|
||||
}
|
||||
// Parse remaining bids from response if present: ok|324|...
|
||||
var parts2 = responseText.Split('|');
|
||||
if (parts2.Length > 1 && int.TryParse(parts2[1], out var remaining))
|
||||
if (parts.Length > 1 && int.TryParse(parts[1], out var remaining))
|
||||
{
|
||||
_session.RemainingBids = remaining;
|
||||
Log($"[BID SUCCESS] ✓ Bid placed successfully - Remaining bids: {remaining}", auctionId);
|
||||
Log($"[BID SUCCESS] Puntata piazzata - Crediti residui: {remaining}", auctionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log("[BID SUCCESS] ✓ Bid placed successfully", auctionId);
|
||||
Log("[BID SUCCESS] Puntata piazzata", 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);
|
||||
var errorMsg = parts.Length > 1 ? parts[1] : responseText;
|
||||
|
||||
// Pulisci messaggio errore da HTML
|
||||
if (errorMsg.Contains("<br>") || errorMsg.Contains("<a"))
|
||||
{
|
||||
var cleanMsg = System.Text.RegularExpressions.Regex.Replace(errorMsg, "<[^>]+>", "");
|
||||
errorMsg = cleanMsg.Split(new[] { "<br>", "\n" }, StringSplitOptions.RemoveEmptyEntries)[0].Trim();
|
||||
}
|
||||
|
||||
result.Error = errorMsg;
|
||||
Log($"[BID ERROR] {errorMsg}", auctionId);
|
||||
}
|
||||
else if (responseText.Contains("alive"))
|
||||
{
|
||||
result.Success = false;
|
||||
result.Error = "Keep-alive response (not a bid response)";
|
||||
Log($"[BID WARN] Received keep-alive instead of bid confirmation", auctionId);
|
||||
Log($"[BID WARN] Ricevuto keep-alive invece di conferma bid", auctionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Success = false;
|
||||
result.Error = string.IsNullOrEmpty(responseText) ? $"HTTP {(int)response.StatusCode}" : responseText;
|
||||
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);
|
||||
}
|
||||
result.Error = string.IsNullOrEmpty(responseText) ? $"HTTP {(int)response.StatusCode}" : "Formato risposta inatteso";
|
||||
Log($"[BID ERROR] Formato risposta inatteso: HTTP {(int)response.StatusCode}", auctionId);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -449,95 +544,7 @@ namespace AutoBidder.Services
|
||||
{
|
||||
result.Success = false;
|
||||
result.Error = ex.Message;
|
||||
// Generic global-style hint (via auction log event, AuctionMonitor will emit concise global message)
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <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] 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);
|
||||
Log($"[BID EXCEPTION] {ex.GetType().Name}: {ex.Message}", auctionId);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -607,7 +614,7 @@ namespace AutoBidder.Services
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene dati utente (nome, puntate residue, saldo, id) tramite chiamata AJAX leggera
|
||||
/// OTTIMIZZATO: Estrae ID utente, username e saldo disponibile tramite chiamata AJAX leggera
|
||||
/// </summary>
|
||||
public async Task<UserData?> GetUserDataAsync()
|
||||
{
|
||||
@@ -708,31 +715,103 @@ namespace AutoBidder.Services
|
||||
{
|
||||
var url = "https://it.bidoo.com/bids_history.php";
|
||||
Log($"[USER HTML REQUEST] GET {url}");
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
AddAuthHeaders(request, "https://it.bidoo.com/");
|
||||
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
|
||||
Log($"[USER HTML RESPONSE] Status: {(int)response.StatusCode} {response.StatusCode}");
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
Log($"[USER HTML ERROR] HTTP {response.StatusCode} - Cookie potrebbe essere scaduto");
|
||||
return null;
|
||||
}
|
||||
|
||||
var html = await response.Content.ReadAsStringAsync();
|
||||
Log($"[USER HTML RESPONSE] Body length: {html.Length}");
|
||||
Log($"[USER HTML RESPONSE] Body length: {html.Length} chars");
|
||||
|
||||
// Verifica se la risposta contiene HTML valido
|
||||
if (html.Length < 100 || !html.Contains("<!DOCTYPE") && !html.Contains("<html"))
|
||||
{
|
||||
Log($"[USER HTML ERROR] Risposta non contiene HTML valido (possibile redirect o errore)");
|
||||
return null;
|
||||
}
|
||||
|
||||
var userData = new UserData();
|
||||
// Estrai nome utente
|
||||
var userMatch = System.Text.RegularExpressions.Regex.Match(html, @"<a class=""pers_lnk""[^>]*>([^<]+)</a>");
|
||||
if (userMatch.Success)
|
||||
bool foundUsername = false;
|
||||
bool foundBids = false;
|
||||
|
||||
// Estrai nome utente - pattern multipli per maggiore robustezza
|
||||
var usernamePatterns = new[]
|
||||
{
|
||||
userData.Username = userMatch.Groups[1].Value.Trim();
|
||||
}
|
||||
// Estrai puntate residue
|
||||
var bidsMatch = System.Text.RegularExpressions.Regex.Match(html, @"<span id=""divSaldoBidBottom""[^>]*>(\d+)</span>");
|
||||
if (bidsMatch.Success && int.TryParse(bidsMatch.Groups[1].Value, out int bids))
|
||||
@"<a class=""pers_lnk""[^>]*>([^<]+)</a>",
|
||||
@"<a[^>]*class=""pers_lnk""[^>]*>([^<]+)</a>",
|
||||
@"<span[^>]*class=""username""[^>]*>([^<]+)</span>",
|
||||
@"BidooCnf\.userObj\.username\s*=\s*'([^']+)'"
|
||||
};
|
||||
|
||||
foreach (var pattern in usernamePatterns)
|
||||
{
|
||||
userData.RemainingBids = bids;
|
||||
var userMatch = System.Text.RegularExpressions.Regex.Match(html, pattern);
|
||||
if (userMatch.Success)
|
||||
{
|
||||
userData.Username = userMatch.Groups[1].Value.Trim();
|
||||
foundUsername = true;
|
||||
Log($"[USER HTML PARSED] Username trovato: {userData.Username}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!string.IsNullOrEmpty(userData.Username) && userData.RemainingBids > 0)
|
||||
|
||||
if (!foundUsername)
|
||||
{
|
||||
Log($"[USER HTML ERROR] Username NON trovato nell'HTML");
|
||||
// Salva un estratto dell'HTML per debug (primi 500 caratteri)
|
||||
var htmlSnippet = html.Substring(0, Math.Min(500, html.Length)).Replace("\n", " ").Replace("\r", "");
|
||||
Log($"[USER HTML DEBUG] Snippet HTML: {htmlSnippet}...");
|
||||
}
|
||||
|
||||
// Estrai puntate residue - pattern multipli
|
||||
var bidsPatterns = new[]
|
||||
{
|
||||
@"<span[^>]*id=""divSaldoBidBottom""[^>]*>(\d+)</span>",
|
||||
@"<span[^>]*id=""divSaldoBidMobile""[^>]*>(\d+)</span>",
|
||||
@"<span[^>]*id=""divSaldoBid""[^>]*>(\d+)</span>",
|
||||
@"<div[^>]*class=""bids[_-]count""[^>]*>(\d+)</div>"
|
||||
};
|
||||
|
||||
foreach (var pattern in bidsPatterns)
|
||||
{
|
||||
var bidsMatch = System.Text.RegularExpressions.Regex.Match(html, pattern);
|
||||
if (bidsMatch.Success && int.TryParse(bidsMatch.Groups[1].Value, out int bids))
|
||||
{
|
||||
userData.RemainingBids = bids;
|
||||
foundBids = true;
|
||||
Log($"[USER HTML PARSED] Puntate residue trovate: {bids}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundBids)
|
||||
{
|
||||
Log($"[USER HTML ERROR] Puntate residue NON trovate nell'HTML");
|
||||
}
|
||||
|
||||
// Ritorna dati solo se almeno username è stato trovato
|
||||
if (foundUsername)
|
||||
{
|
||||
Log($"[USER HTML SUCCESS] Dati estratti: {userData.Username}, {userData.RemainingBids} puntate");
|
||||
return userData;
|
||||
}
|
||||
|
||||
Log($"[USER HTML FAILED] Impossibile estrarre dati utente dall'HTML");
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"[USER HTML EXCEPTION] {ex.GetType().Name}: {ex.Message}");
|
||||
Log($"[USER HTML EXCEPTION] StackTrace: {ex.StackTrace}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user