Aggiornamento alla versione 3.0.0

- Rimosse funzionalità legacy legate a WebView2.
- Introdotto uno stile globale per i pulsanti.
- Semplificata l'interfaccia con gestione tramite griglia unica.
- Aggiunti comandi per avviare, mettere in pausa e fermare aste.
- Introdotta gestione manuale dei cookie tramite dialog.
- Aggiunti dialog per configurare sessione e aggiungere aste.
- Migliorata la persistenza con salvataggio sicuro (DPAPI).
- Rifattorizzate statistiche per utilizzare `BidHistory` e `BidderStats`.
- Ottimizzato il polling per ridurre il carico di sistema.
- Aggiornata esportazione CSV con dati più dettagliati.
- Introdotti nuovi modelli dati per utente e banner aste.
- Rimossi file di test manuale e codice obsoleto.
- Aggiornata documentazione per riflettere le modifiche.
- Aggiunta nuova icona dell'applicazione.
- Migliorata la sicurezza eliminando il salvataggio in chiaro dei cookie.
This commit is contained in:
Alberto Balbo
2025-10-28 12:45:08 +01:00
parent fef7b909e7
commit 717dc44b3b
32 changed files with 1039 additions and 1471 deletions
+107 -67
View File
@@ -8,8 +8,8 @@ using AutoBidder.Models;
namespace AutoBidder.Services
{
/// <summary>
/// Servizio centrale per monitoraggio multi-asta
/// Gestisce polling API Bidoo e trigger dei click
/// Servizio centrale per monitoraggio aste
/// Solo HTTP, nessuna modalità, browser o multi-click
/// </summary>
public class AuctionMonitor
{
@@ -79,6 +79,30 @@ namespace AutoBidder.Services
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();
}
public void AddAuction(AuctionInfo auction)
{
lock (_auctions)
@@ -112,12 +136,6 @@ namespace AutoBidder.Services
}
}
public Task<bool> InitializeCookies(Microsoft.Web.WebView2.Wpf.WebView2 webView)
{
// Non più utilizzato - usa InitializeSession invece
return Task.FromResult(false);
}
public void Start()
{
if (_monitoringTask != null && !_monitoringTask.IsCompleted)
@@ -251,14 +269,17 @@ namespace AutoBidder.Services
{
// 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)
{
auction.AddLog("ERRORE: Nessun dato ricevuto da API");
OnLog?.Invoke($"[ERRORE] [{auction.AuctionId}] API non ha risposto");
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 ||
@@ -305,63 +326,86 @@ namespace AutoBidder.Services
// Aggiorna storico e bidders
UpdateAuctionHistory(auction, state);
// Verifica se puntare (solo se asta Running, NON se in pausa)
if (state.Status == AuctionStatus.Running && ShouldBid(auction, state) && !auction.IsPaused)
{
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!");
// Attendi ritardo configurato
if (auction.DelayMs > 0)
// OTTIMIZZAZIONE PUNTATA
if (state.Status == AuctionStatus.Running && !auction.IsPaused && ShouldBid(auction, state))
{
// Se timer è esattamente 0
if (Math.Abs(state.Timer) < 0.001)
{
await Task.Delay(auction.DelayMs, token);
}
// Esegui puntata API
var result = await _apiClient.PlaceBidAsync(auction.AuctionId);
// Aggiorna contatori
if (result.Success)
{
auction.MyClicks++;
auction.LastClickAt = DateTime.UtcNow;
}
// Notifica risultato
OnBidExecuted?.Invoke(auction, result);
// 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");
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)
{
auction.AddLog($"[BID] Condizioni OK dopo delay, invio puntata...");
OnLog?.Invoke($"[BID] [{auction.AuctionId}] PUNTATA dopo delay!");
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");
}
else
{
auction.AddLog($"[FAIL] PUNTATA FALLITA: {result.Error}");
OnLog?.Invoke($"[FAIL] [{auction.AuctionId}] ERRORE: {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,
LatencyMs = result.LatencyMs,
Success = result.Success,
Notes = result.Success ? $"EUR{result.NewPrice:F2}" : result.Error
});
}
else
{
auction.AddLog($"[SKIP] Puntata non inviata: timer/utente cambiato dopo delay");
}
}
else
{
auction.AddLog($"[FAIL] PUNTATA FALLITA: {result.Error}");
OnLog?.Invoke($"[FAIL] [{auction.AuctionId}] ERRORE: {result.Error}");
}
// Storico
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
});
// Se abbiamo raggiunto il numero massimo di click per l'asta, metti in pausa le puntate (ma continua il monitor)
if (auction.MaxClicks > 0 && auction.MyClicks >= auction.MaxClicks)
{
auction.IsPaused = true;
auction.AddLog($"[PAUSA] Massimo click ({auction.MaxClicks}) raggiunto - Puntate disabilitate");
OnLog?.Invoke($"[PAUSA] [{auction.AuctionId}] MaxClicks raggiunti");
// 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!");
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");
}
else
{
auction.AddLog($"[FAIL] PUNTATA FALLITA: {result.Error}");
OnLog?.Invoke($"[FAIL] [{auction.AuctionId}] ERRORE: {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
});
}
}
}
@@ -393,10 +437,6 @@ namespace AutoBidder.Services
return false;
}
// Max clicks per auction
if (auction.MaxClicks > 0 && auction.MyClicks >= auction.MaxClicks)
return false;
return true;
}
+155 -159
View File
@@ -12,28 +12,7 @@ namespace AutoBidder.Services
{
/// <summary>
/// Servizio completo API Bidoo (polling, puntate, info utente)
/// 100% API-based con simulazione completa del comportamento browser
///
/// FLUSSO DI AUTENTICAZIONE:
/// - Cookie principale: __stattr (non PHPSESSID)
/// - Il cookie deve essere estratto dal browser dopo login manuale
/// - Tutti i request devono includere: Cookie + X-Requested-With: XMLHttpRequest
///
/// CHIAMATE GET (SOLO LETTURA - No CSRF Token):
/// 1. Polling asta: GET data.php?ALL={id}&LISTID=0
/// 2. Info utente: GET ajax/get_auction_bids_info_banner.php
///
/// CHIAMATE POST (AZIONI - Richiedono CSRF Token):
/// 1. Piazza puntata: POST bid.php
/// - Step 1: GET pagina asta HTML per estrarre bid_token
/// - Step 2: POST con payload: a={id}&bid_type=manual&bid_token={token}&time_sent={ts}
/// - Step 3: Analizza risposta: "ok|..." o "error|..."
///
/// SIMULAZIONE BROWSER:
/// - User-Agent: Chrome su Windows
/// - Headers CORS: Sec-Fetch-*
/// - Referer: URL pagina asta
/// - Content-Type: application/x-www-form-urlencoded (per POST)
/// Solo HTTP, nessuna modalità, browser o multi-click
/// </summary>
public class BidooApiClient
{
@@ -91,14 +70,13 @@ namespace AutoBidder.Services
}
/// <summary>
/// Inizializza sessione con cookie string (fallback)
/// Inizializza sessione con cookie string (manuale)
/// </summary>
public void InitializeSessionWithCookie(string cookieString, string username)
{
_session.CookieString = cookieString;
_session.Username = username;
Log($"[SESSION] Cookie impostato ({cookieString.Length} chars)");
Log($"[SESSION] Cookie impostato manualmente ({cookieString.Length} chars)");
Log($"[SESSION] Username: {username}");
}
@@ -108,20 +86,12 @@ namespace AutoBidder.Services
/// </summary>
private void AddAuthHeaders(HttpRequestMessage request, string? referer = null, string? auctionId = null)
{
// 1. AUTENTICAZIONE (priorità: CookieString completa, poi Token singolo)
// Il cookie principale per Bidoo è __stattr (non PHPSESSID)
// 1. AUTENTICAZIONE (solo cookie manuale)
if (!string.IsNullOrWhiteSpace(_session.CookieString))
{
// Usa la stringa cookie completa (es: "__stattr=eyJyZWZ...")
request.Headers.Add("Cookie", _session.CookieString);
Log("[AUTH] Using full cookie string", auctionId);
}
else if (!string.IsNullOrWhiteSpace(_session.AuthToken))
{
// Fallback: se abbiamo solo il token, assumiamo sia __stattr
request.Headers.Add("Cookie", $"__stattr={_session.AuthToken}");
Log("[AUTH] Using __stattr token", auctionId);
}
else
{
Log("[AUTH WARN] No authentication method available!", auctionId);
@@ -244,40 +214,28 @@ namespace AutoBidder.Services
{
var startTime = DateTime.UtcNow;
var url = $"https://it.bidoo.com/data.php?ALL={auctionId}&LISTID=0";
Log($"[API REQUEST] GET {url}", auctionId);
var request = new HttpRequestMessage(HttpMethod.Get, url);
var referer = !string.IsNullOrEmpty(auctionUrl)
? auctionUrl
: $"https://it.bidoo.com/auction.php?a=asta_{auctionId}";
Log($"[API REQUEST] Using referer: {referer}", auctionId);
AddAuthHeaders(request, referer, auctionId);
var response = await _httpClient.SendAsync(request, token);
var latency = (int)(DateTime.UtcNow - startTime).TotalMilliseconds;
Log($"[API RESPONSE] Status: {(int)response.StatusCode} {response.StatusCode}", auctionId);
Log($"[API RESPONSE] Latency: {latency}ms", auctionId);
Log($"[API RESPONSE] Content-Type: {response.Content.Headers.ContentType}", auctionId);
var responseText = await response.Content.ReadAsStringAsync();
Log($"[API RESPONSE] Body ({responseText.Length} bytes): {responseText}", auctionId);
if (!response.IsSuccessStatusCode)
{
Log($"[API ERROR] Non-success status code: {response.StatusCode}", auctionId);
string reason = response.StatusCode == System.Net.HttpStatusCode.RequestTimeout ? "timeout" : "errore HTTP";
Log($"[ERRORE] [{auctionId}] API non ha risposto (motivo: {reason})", null); // globale
Log($"API non ha risposto: {response.StatusCode} ({reason})", auctionId); // asta
return null;
}
return ParsePollingResponse(auctionId, responseText, latency);
var state = ParsePollingResponse(auctionId, responseText, latency);
return state;
}
catch (Exception ex)
{
Log($"[API EXCEPTION] Polling {auctionId}: {ex.GetType().Name} - {ex.Message}", auctionId);
Log($"[API EXCEPTION] StackTrace: {ex.StackTrace}", auctionId);
Log($"[ERRORE] [{auctionId}] API non ha risposto (motivo: eccezione)", null); // globale
Log($"API non ha risposto: {ex.GetType().Name} - {ex.Message}", auctionId); // asta
return null;
}
}
@@ -286,156 +244,71 @@ namespace AutoBidder.Services
{
try
{
Log($"[PARSE] Starting parse for auction {auctionId}", auctionId);
Log($"[PARSE] Raw response: {response}", auctionId);
// Step 1: Estrai Server Timestamp (può avere formato "timestamp*..." o "timestamp|flag*")
string serverTimestamp;
string mainData;
// Cerca il separatore '*' che divide timestamp da dati
var starIndex = response.IndexOf('*');
if (starIndex == -1)
{
Log("[PARSE ERROR] No '*' separator found in response", auctionId);
return null;
}
var timestampPart = response.Substring(0, starIndex);
mainData = response.Substring(starIndex + 1);
// Il timestamp può contenere '|' (es: "1761120002|1")
// Prendiamo solo la prima parte numerica
if (timestampPart.Contains('|'))
{
serverTimestamp = timestampPart.Split('|')[0];
Log($"[PARSE] Extended format detected: {timestampPart}", auctionId);
}
else
{
serverTimestamp = timestampPart;
}
Log($"[PARSE] Server Timestamp: {serverTimestamp}", auctionId);
Log($"[PARSE] Main Data: {mainData}", auctionId);
// Step 2: Estrai dati asta tra parentesi quadre [...]
var bracketStart = mainData.IndexOf('[');
var bracketEnd = mainData.IndexOf(']');
if (bracketStart == -1 || bracketEnd == -1)
{
Log("[PARSE ERROR] Missing brackets in auction data", auctionId);
return null;
}
var auctionData = mainData.Substring(bracketStart + 1, bracketEnd - bracketStart - 1);
Log($"[PARSE] Auction Data extracted: {auctionData}", auctionId);
// Step 3: Split per ';' per ottenere i campi principali
// Nota: la stringa può contenere '|' per separare bid history, quindi ci fermiamo al primo '|' o ','
var firstSeparator = auctionData.IndexOfAny(new[] { '|', ',' });
var coreData = firstSeparator > 0 ? auctionData.Substring(0, firstSeparator) : auctionData;
var fields = coreData.Split(';');
Log($"[PARSE] Core fields count: {fields.Length}", auctionId);
for (int i = 0; i < Math.Min(fields.Length, 10); i++)
{
Log($"[PARSE] Field[{i}] = '{fields[i]}'", auctionId);
}
if (fields.Length < 5)
{
Log($"[PARSE ERROR] Expected at least 5 core fields, got {fields.Length}", auctionId);
return null;
}
var state = new AuctionState
{
AuctionId = auctionId,
SnapshotTime = DateTime.UtcNow,
PollingLatencyMs = latency
};
// Step 4: Parse campi principali
// Field 0: AuctionID (verifica)
Log($"[PARSE] Auction ID from response: {fields[0]} (expected: {auctionId})", auctionId);
// Field 1: Status (ON/OFF)
var status = fields[1].Trim().ToUpperInvariant();
// Determiniamo lo stato in base a: Status API + LastBidder + Timer
// Parsing di Field 4 (LastBidder) anticipato per logica stato
string lastBidder = fields[4].Trim();
bool hasWinner = !string.IsNullOrEmpty(lastBidder);
bool iAmWinner = hasWinner && lastBidder.Equals(_session.Username, StringComparison.OrdinalIgnoreCase);
state.Status = DetermineAuctionStatus(status, hasWinner, iAmWinner, ref state);
Log($"[PARSE] Status: {status} -> {state.Status}", auctionId);
// Field 2: Expiry Timestamp (CRITICO per timing)
// IMPORTANTE: Usa il ServerTimestamp dalla risposta, NON il tempo locale!
// Formato: ServerTimestamp*[..;ExpiryTimestamp;..]
// Timer = ExpiryTimestamp - ServerTimestamp
if (long.TryParse(serverTimestamp, out var serverTs) && long.TryParse(fields[2], out var expiryTs))
{
// Calcolo corretto usando il tempo del server
var timerSeconds = (double)(expiryTs - serverTs);
state.Timer = Math.Max(0, timerSeconds);
Log($"[PARSE] Server Timestamp: {serverTs}", auctionId);
Log($"[PARSE] Expiry Timestamp: {expiryTs}", auctionId);
Log($"[PARSE] Timer (Expiry - Server): {expiryTs} - {serverTs} = {timerSeconds:F2}s", auctionId);
Log($"[PARSE] ⏱️ Final Timer: {state.Timer:F2}s", auctionId);
// DEBUG: Verifica se il timer sembra sbagliato
if (state.Timer > 60)
{
Log("[PARSE WARN] Timer unusually high! Check data", auctionId);
}
}
else
{
Log($"[PARSE WARN] Failed to parse timestamps - Server: {serverTimestamp}, Expiry: {fields[2]}", auctionId);
}
// Field 3: Price Index (0.01 EUR per index)
if (int.TryParse(fields[3], out var priceIndex))
{
state.Price = priceIndex * 0.01;
Log($"[PARSE] Price Index: {priceIndex} -> €{state.Price:F2}", auctionId);
}
else
{
Log($"[PARSE WARN] Failed to parse price index: {fields[3]}", auctionId);
}
// Field 4: Last Bidder
state.LastBidder = fields[4].Trim();
state.IsMyBid = !string.IsNullOrEmpty(_session.Username) &&
state.LastBidder.Equals(_session.Username, StringComparison.OrdinalIgnoreCase);
Log($"[PARSE] 👤 Last Bidder: {state.LastBidder}", auctionId);
Log($"[PARSE] Is My Bid: {state.IsMyBid} (my username: {_session.Username})", auctionId);
// Field 5 (optional): Additional flags or data
if (fields.Length > 5)
{
Log($"[PARSE] Additional data fields present: {fields.Length - 5}", auctionId);
}
state.ParsingSuccess = true;
Log($"[PARSE SUCCESS] Timer: {state.Timer:F2}s, Price: €{state.Price:F2}, Bidder: {state.LastBidder}, Status: {state.Status}", auctionId);
// Log only summary on success
Log($"[PARSE SUCCESS] Timer: {state.Timer:F2}s, Price: €{state.Price:F2}, Bidder: {state.LastBidder}, Status: {state.Status}", auctionId);
return state;
}
catch (Exception ex)
{
Log($"[PARSE EXCEPTION] {ex.GetType().Name}: {ex.Message}", auctionId);
Log($"[PARSE EXCEPTION] StackTrace: {ex.StackTrace}", auctionId);
return null;
}
}
@@ -457,9 +330,9 @@ namespace AutoBidder.Services
Log($"[USER INFO RESPONSE] Status: {(int)response.StatusCode} {response.StatusCode}");
Log($"[USER INFO RESPONSE] Latency: {latency}ms");
var responseText = await response.Content.ReadAsStringAsync();
Log($"[USER INFO RESPONSE] Body: {responseText}");
Log($"[USER INFO RESPONSE] Body length: {responseText.Length}");
if (!response.IsSuccessStatusCode)
{
@@ -484,39 +357,33 @@ namespace AutoBidder.Services
AuctionId = auctionId,
Timestamp = DateTime.UtcNow
};
try
{
Log($"[BID] Placing bid via direct GET to bid.php (no token)", auctionId);
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}";
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 ({responseText.Length} bytes): {responseText}", auctionId);
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;
@@ -525,8 +392,7 @@ namespace AutoBidder.Services
{
result.NewPrice = priceIndex * 0.01;
}
Log("[BID SUCCESS] ✓ Bid placed successfully via GET", auctionId);
Log("[BID SUCCESS] ✓ Bid placed successfully", auctionId);
}
else if (responseText.StartsWith("error", StringComparison.OrdinalIgnoreCase) || responseText.StartsWith("no|", StringComparison.OrdinalIgnoreCase))
{
@@ -547,7 +413,6 @@ namespace AutoBidder.Services
result.Error = string.IsNullOrEmpty(responseText) ? $"HTTP {(int)response.StatusCode}" : responseText;
Log($"[BID ERROR] Unexpected response format: {result.Error}", auctionId);
}
return result;
}
catch (Exception ex)
@@ -555,7 +420,7 @@ namespace AutoBidder.Services
result.Success = false;
result.Error = ex.Message;
Log($"[BID EXCEPTION] {ex.GetType().Name}: {ex.Message}", auctionId);
Log($"[BID EXCEPTION] StackTrace: {ex.StackTrace}", auctionId);
Log($"[BID EXCEPTION] StackTrace available in debug logs", auctionId);
return result;
}
}
@@ -623,5 +488,136 @@ namespace AutoBidder.Services
{
_httpClient?.Dispose();
}
/// <summary>
/// Ottiene dati utente (nome, puntate residue, saldo, id) tramite chiamata AJAX leggera
/// </summary>
public async Task<UserData?> GetUserDataAsync()
{
try
{
var url = "https://it.bidoo.com/update_credits_status.php?submit=1";
Log($"[USER STATUS REQUEST] GET {url}");
var request = new HttpRequestMessage(HttpMethod.Get, url);
AddAuthHeaders(request, "https://it.bidoo.com/");
var response = await _httpClient.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
Log($"[USER STATUS RESPONSE] Body: {responseString}");
var userData = new UserData();
var trimmed = responseString.Trim();
// Caso: solo numero
if (int.TryParse(trimmed, out int bids))
{
userData.RemainingBids = bids;
return userData;
}
// Caso: HTML <span id="bids_count">125</span>
if (trimmed.StartsWith("<span") && trimmed.Contains("id=\"bids_count\""))
{
var start = trimmed.IndexOf('>');
var end = trimmed.IndexOf("</span>");
if (start >= 0 && end > start)
{
var value = trimmed.Substring(start + 1, end - start - 1);
if (int.TryParse(value, out bids))
{
userData.RemainingBids = bids;
return userData;
}
}
}
// Caso: stringa delimitata da pipe
var parts = trimmed.Split('|');
if (parts.Length >= 2)
{
userData.Username = parts[0];
if (int.TryParse(parts[1], out bids))
{
userData.RemainingBids = bids;
}
if (parts.Length >= 3 && double.TryParse(parts[2], out double cash))
{
userData.CashBalance = cash;
}
if (parts.Length >= 4 && int.TryParse(parts[3], out int userId))
{
userData.UserId = userId;
}
return userData;
}
// Se non riconosciuto
Log($"[USER STATUS PARSE ERROR] Formato non riconosciuto: {responseString}");
return null;
}
catch (Exception ex)
{
Log($"[USER STATUS EXCEPTION] {ex.GetType().Name}: {ex.Message}");
return null;
}
}
/// <summary>
/// Ottiene info banner utente (aste vinte, bonus, ecc.) tramite chiamata AJAX
/// </summary>
public async Task<UserBannerInfo?> GetUserBannerInfoAsync()
{
try
{
var url = "https://it.bidoo.com/ajax/get_auction_bids_info_banner.php";
Log($"[USER BANNER REQUEST] GET {url}");
var request = new HttpRequestMessage(HttpMethod.Get, url);
AddAuthHeaders(request, "https://it.bidoo.com/");
var response = await _httpClient.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
Log($"[USER BANNER RESPONSE] Body: {responseString}");
if (!response.IsSuccessStatusCode || string.IsNullOrWhiteSpace(responseString))
return null;
var info = System.Text.Json.JsonSerializer.Deserialize<UserBannerInfo>(responseString);
return info;
}
catch (Exception ex)
{
Log($"[USER BANNER EXCEPTION] {ex.GetType().Name}: {ex.Message}");
return null;
}
}
/// <summary>
/// Estrae nome utente e puntate residue dall'HTML di bids_history.php
/// </summary>
public async Task<UserData?> GetUserDataFromHtmlAsync()
{
try
{
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);
var html = await response.Content.ReadAsStringAsync();
Log($"[USER HTML RESPONSE] Body length: {html.Length}");
var userData = new UserData();
// Estrai nome utente
var userMatch = System.Text.RegularExpressions.Regex.Match(html, @"<a class=""pers_lnk""[^>]*>([^<]+)</a>");
if (userMatch.Success)
{
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))
{
userData.RemainingBids = bids;
}
if (!string.IsNullOrEmpty(userData.Username) && userData.RemainingBids > 0)
return userData;
return null;
}
catch (Exception ex)
{
Log($"[USER HTML EXCEPTION] {ex.GetType().Name}: {ex.Message}");
return null;
}
}
}
}
+1
View File
@@ -10,6 +10,7 @@ namespace AutoBidder.Services
/// <summary>
/// Gestore persistenza sessione Bidoo
/// Salva in modo sicuro il cookie di autenticazione per riutilizzo
/// Il cookie deve essere inserito manualmente dall'utente (copiato dal browser)
/// </summary>
public class SessionManager
{