Files
Mimante/Mimante/Services/BidooApiClient.cs
T
Alberto Balbo f017ec0364 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.
2025-11-19 18:43:40 +01:00

820 lines
36 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Text;
using AutoBidder.Models;
namespace AutoBidder.Services
{
/// <summary>
/// Servizio completo API Bidoo (polling, puntate, info utente)
/// Solo HTTP, nessuna modalità, browser o multi-click
/// </summary>
public class BidooApiClient
{
private readonly HttpClient _httpClient;
private BidooSession _session;
// Event used to push detailed logs into per-auction log in the monitor
public event Action<string, string>? OnAuctionLog;
public BidooApiClient()
{
var handler = new HttpClientHandler
{
UseCookies = false, // Gestiamo manualmente i cookie
AutomaticDecompression = System.Net.DecompressionMethods.All // Decomprimi GZIP/Deflate/Brotli
};
_httpClient = new HttpClient(handler)
{
Timeout = TimeSpan.FromSeconds(3)
};
_session = new BidooSession();
}
// Helper that writes to Console and, when auctionId provided, emits per-auction log event
private void Log(string message, string? auctionId = null)
{
try
{
Console.WriteLine(message);
}
catch { }
if (!string.IsNullOrEmpty(auctionId))
{
try
{
OnAuctionLog?.Invoke(auctionId, message);
}
catch { }
}
}
/// <summary>
/// Inizializza sessione con token di autenticazione
/// </summary>
public void InitializeSession(string authToken, string username)
{
_session.AuthToken = authToken;
_session.Username = username;
Log($"[SESSION] Token impostato ({authToken.Length} chars)");
Log($"[SESSION] Username: {username}");
}
/// <summary>
/// Inizializza sessione con cookie string (manuale)
/// </summary>
public void InitializeSessionWithCookie(string cookieString, string username)
{
_session.CookieString = cookieString;
_session.Username = username;
Log($"[SESSION] Cookie impostato manualmente ({cookieString.Length} chars)");
Log($"[SESSION] Username: {username}");
}
/// <summary>
/// Aggiunge header di autenticazione e browser-like alla richiesta
/// Headers critici per evitare rilevamento come bot
/// </summary>
private void AddAuthHeaders(HttpRequestMessage request, string? referer = null, string? auctionId = null)
{
// 1. AUTENTICAZIONE (solo cookie manuale)
if (!string.IsNullOrWhiteSpace(_session.CookieString))
{
request.Headers.Add("Cookie", _session.CookieString);
// Log rimosso per ridurre verbosità
}
else
{
Log("[AUTH WARN] No authentication method available!", auctionId);
}
// 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");
// Accept headers
request.Headers.Add("Accept", "*/*");
request.Headers.Add("Accept-Language", "it-IT,it;q=0.9,en-US;q=0.8,en;q=0.7");
request.Headers.Add("Accept-Encoding", "gzip, deflate, br");
// Security headers (critici per CORS)
request.Headers.Add("Sec-Fetch-Dest", "empty");
request.Headers.Add("Sec-Fetch-Mode", "cors");
request.Headers.Add("Sec-Fetch-Site", "same-origin");
// Chrome-specific headers
request.Headers.Add("sec-ch-ua", "\"Google Chrome\";v=\"141\", \"Not?A_Brand\";v=\"8\", \"Chromium\";v=\"141\"");
request.Headers.Add("sec-ch-ua-mobile", "?0");
request.Headers.Add("sec-ch-ua-platform", "\"Windows\"");
// XMLHttpRequest identifier (FONDAMENTALE per API AJAX)
request.Headers.Add("X-Requested-With", "XMLHttpRequest");
// Referer (importante per validazione origin)
if (!string.IsNullOrEmpty(referer))
{
request.Headers.Add("Referer", referer);
}
else
{
request.Headers.Add("Referer", "https://it.bidoo.com/");
}
// Log rimosso per ridurre verbosità - headers sempre aggiunti
}
/// <summary>
/// Estrae CSRF/Bid token dalla pagina asta
/// PASSO 1: Ottenere la pagina HTML dell'asta per estrarre il token di sicurezza
/// Il token può essere chiamato: bid_token, csrf_token, _token, etc.
/// </summary>
private async Task<(string? tokenName, string? tokenValue)> ExtractBidTokenAsync(string auctionId, string? auctionUrl = null)
{
try
{
var url = !string.IsNullOrEmpty(auctionUrl) ? auctionUrl : $"https://it.bidoo.com/asta/nome-prodotto-{auctionId}";
Log($"[TOKEN] GET {url}", auctionId);
var request = new HttpRequestMessage(HttpMethod.Get, url);
AddAuthHeaders(request, url, auctionId);
var response = await _httpClient.SendAsync(request);
var html = await response.Content.ReadAsStringAsync();
Log($"[TOKEN] Response: {response.StatusCode}, HTML length: {html.Length}", auctionId);
var patterns = new System.Collections.Generic.List<(string pattern, string name)>
{
// double-quoted input attributes
("(?i)<input[^>]*name=\"bid_token\"[^>]*value=\"([^\"]+)\"", "bid_token"),
("(?i)<input[^>]*value=\"([^\"]+)\"[^>]*name=\"bid_token\"", "bid_token"),
("(?i)<input[^>]*name=\"csrf_token\"[^>]*value=\"([^\"]+)\"", "csrf_token"),
("(?i)<input[^>]*value=\"([^\"]+)\"[^>]*name=\"csrf_token\"", "csrf_token"),
("(?i)<input[^>]*name=\"_token\"[^>]*value=\"([^\"]+)\"", "_token"),
("(?i)<input[^>]*name=\"token\"[^>]*value=\"([^\"]+)\"", "token"),
// single-quoted input attributes
("(?i)<input[^>]*name='bid_token'[^>]*value='([^']+)'", "bid_token"),
("(?i)<input[^>]*value='([^']+)'[^>]*name='bid_token'", "bid_token"),
("(?i)<input[^>]*name='csrf_token'[^>]*value='([^']+)'", "csrf_token"),
("(?i)<input[^>]*value='([^']+)'[^>]*name='csrf_token'", "csrf_token"),
("(?i)<input[^>]*name='_token'[^>]*value='([^']+)'", "_token"),
("(?i)<input[^>]*name='token'[^>]*value='([^']+)'", "token"),
// JavaScript style assignments (double and single quotes)
("(?i)bid_token\\s*[:=]\\s*\"([^\\\"]+)\"", "bid_token"),
("(?i)bid_token\\s*[:=]\\s*'([^']+)'", "bid_token"),
("(?i)csrf_token\\s*[:=]\\s*\"([^\\\"]+)\"", "csrf_token"),
("(?i)csrf_token\\s*[:=]\\s*'([^']+)'", "csrf_token"),
// JSON style
("\"token\"\\s*:\\s*\"([^\\\"]+)\"", "token")
};
foreach (var pattern in patterns)
{
var match = System.Text.RegularExpressions.Regex.Match(html, pattern.pattern, System.Text.RegularExpressions.RegexOptions.IgnoreCase);
if (match.Success)
{
var tokenValue = match.Groups[1].Value;
Log($"[TOKEN] ✓ Token found: {pattern.name} = {tokenValue.Substring(0, Math.Min(20, tokenValue.Length))}...", auctionId);
return (pattern.name, tokenValue);
}
}
Log("[TOKEN] ⚠ No bid token found in HTML", auctionId);
return (null, null);
}
catch (Exception ex)
{
Log($"[TOKEN ERROR] {ex.Message}", auctionId);
return (null, null);
}
}
private Task<(string? tokenName, string? tokenValue)> ExtractBidTokenAsync(string auctionId)
{
return ExtractBidTokenAsync(auctionId, null);
}
public async Task<AuctionState?> PollAuctionStateAsync(string auctionId, string? auctionUrl, CancellationToken token)
{
try
{
var startTime = DateTime.UtcNow;
var url = $"https://it.bidoo.com/data.php?ALL={auctionId}&LISTID=0";
var request = new HttpRequestMessage(HttpMethod.Get, url);
var referer = !string.IsNullOrEmpty(auctionUrl)
? auctionUrl
: $"https://it.bidoo.com/auction.php?a=asta_{auctionId}";
AddAuthHeaders(request, referer, auctionId);
var response = await _httpClient.SendAsync(request, token);
var latency = (int)(DateTime.UtcNow - startTime).TotalMilliseconds;
var responseText = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
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;
}
var state = ParsePollingResponse(auctionId, responseText, latency);
return state;
}
catch (Exception ex)
{
// Global concise message
Log($"[ERRORE] [{auctionId}] API non ha risposto (verificare dettagli nel log asta)", null);
// Detailed per-auction log with full exception and context
var details = ex.ToString();
details = "[API EXCEPTION] " + details;
Log(details, auctionId);
return null;
}
}
private AuctionState? ParsePollingResponse(string auctionId, string response, int latency)
{
try
{
string serverTimestamp;
string mainData;
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);
if (timestampPart.Contains('|'))
{
serverTimestamp = timestampPart.Split('|')[0];
}
else
{
serverTimestamp = timestampPart;
}
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);
var firstSeparator = auctionData.IndexOfAny(new[] { '|', ',' });
var coreData = firstSeparator > 0 ? auctionData.Substring(0, firstSeparator) : auctionData;
var fields = coreData.Split(';');
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
};
var status = fields[1].Trim().ToUpperInvariant();
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);
if (long.TryParse(serverTimestamp, out var serverTs) && long.TryParse(fields[2], out var expiryTs))
{
var timerSeconds = (double)(expiryTs - serverTs);
state.Timer = Math.Max(0, timerSeconds);
}
if (int.TryParse(fields[3], out var priceIndex))
{
state.Price = priceIndex * 0.01;
}
state.LastBidder = fields[4].Trim();
state.IsMyBid = !string.IsNullOrEmpty(_session.Username) &&
state.LastBidder.Equals(_session.Username, StringComparison.OrdinalIgnoreCase);
state.ParsingSuccess = true;
// 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);
return null;
}
}
public async Task<bool> UpdateUserInfoAsync()
{
try
{
// 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}");
var request = new HttpRequestMessage(HttpMethod.Get, url);
AddAuthHeaders(request, "https://it.bidoo.com/");
var startTime = DateTime.UtcNow;
var response = await _httpClient.SendAsync(request);
var latency = (int)(DateTime.UtcNow - startTime).TotalMilliseconds;
Log($"[USER INFO RESPONSE] Status: {(int)response.StatusCode} {response.StatusCode}, Latency: {latency}ms");
if (!response.IsSuccessStatusCode)
{
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;
}
// 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;
}
}
public async Task<BidResult> PlaceBidAsync(string auctionId, string? auctionUrl = null)
{
var result = new BidResult
{
AuctionId = auctionId,
Timestamp = DateTime.UtcNow
};
try
{
var url = "https://it.bidoo.com/bid.php";
var payload = $"AID={WebUtility.UrlEncode(auctionId)}&sup=0&shock=0";
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;
var responseText = await response.Content.ReadAsStringAsync();
result.Response = responseText;
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;
}
// Parse remaining bids from response if present: ok|324|...
if (parts.Length > 1 && int.TryParse(parts[1], out var remaining))
{
_session.RemainingBids = remaining;
Log($"[BID SUCCESS] Puntata piazzata - Crediti residui: {remaining}", auctionId);
}
else
{
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('|');
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] Ricevuto keep-alive invece di conferma bid", auctionId);
}
else
{
result.Success = false;
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;
}
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
///
/// STATI API BIDOO:
/// - ON: Asta attiva e in corso
/// - OFF: Asta terminata definitivamente
/// - STOP: Asta in pausa (tipicamente 00:00-10:00) - riprenderà più tardi
/// </summary>
private AuctionStatus DetermineAuctionStatus(string apiStatus, bool hasWinner, bool iAmWinner, ref AuctionState state)
{
// Gestione stato STOP (pausa notturna)
if (apiStatus == "STOP")
{
// L'asta è iniziata ma è in pausa
// Controlla se c'è già un vincitore temporaneo
if (hasWinner)
{
state.LastBidder = state.LastBidder; // Mantieni il last bidder
return AuctionStatus.Paused;
}
// Pausa senza puntate ancora
return AuctionStatus.Paused;
}
if (apiStatus == "OFF")
{
// Asta terminata definitivamente
if (hasWinner)
{
return iAmWinner ? AuctionStatus.EndedWon : AuctionStatus.EndedLost;
}
return AuctionStatus.Closed;
}
if (apiStatus == "ON")
{
// Asta attiva
if (hasWinner)
{
// Ci sono già puntate → Running
return AuctionStatus.Running;
}
// Nessuna puntata ancora → Pending o Scheduled
// Se timer molto alto (> 30 minuti), è programmata per più tardi
if (state.Timer > 1800) // 30 minuti
{
return AuctionStatus.Scheduled;
}
// Altrimenti sta per iniziare
return AuctionStatus.Pending;
}
return AuctionStatus.Unknown;
}
public BidooSession GetSession() => _session;
public void Dispose()
{
_httpClient?.Dispose();
}
/// <summary>
/// OTTIMIZZATO: Estrae ID utente, username e saldo disponibile 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);
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} 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();
bool foundUsername = false;
bool foundBids = false;
// Estrai nome utente - pattern multipli per maggiore robustezza
var usernamePatterns = new[]
{
@"<a class=""pers_lnk""[^>]*>([^<]+)</a>",
@"<a[^>]*class=""pers_lnk""[^>]*>([^<]+)</a>",
@"<span[^>]*class=""username""[^>]*>([^<]+)</span>",
@"BidooCnf\.userObj\.username\s*=\s*'([^']+)'"
};
foreach (var pattern in usernamePatterns)
{
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 (!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;
}
}
}
}