Files
Mimante/Mimante/Services/BidooBrowserService.cs
Alberto Balbo 77eb9943d0 Gestione avanzata database e rimozione MaxClicks
Aggiunta sezione impostazioni per manutenzione database (auto-salvataggio, pulizia duplicati/incompleti, retention, ottimizzazione). Implementati metodi asincroni in DatabaseService per pulizia e statistiche. Pulizia automatica all’avvio secondo impostazioni. Rimossa la proprietà MaxClicks da modello, UI e logica. Migliorata la sicurezza thread-safe e la trasparenza nella gestione dati. Spostato il badge versione nelle info applicazione.
2026-01-24 01:30:49 +01:00

737 lines
31 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using AutoBidder.Models;
namespace AutoBidder.Services
{
/// <summary>
/// Servizio per navigare le aste pubbliche di Bidoo senza autenticazione
/// Permette di esplorare le categorie e visualizzare le aste disponibili
/// </summary>
public class BidooBrowserService
{
private readonly HttpClient _httpClient;
private readonly List<BidooCategoryInfo> _cachedCategories = new();
private DateTime _categoriesCachedAt = DateTime.MinValue;
private readonly TimeSpan _categoryCacheExpiry = TimeSpan.FromMinutes(30);
public BidooBrowserService()
{
var handler = new HttpClientHandler
{
UseCookies = false,
AutomaticDecompression = System.Net.DecompressionMethods.All
};
_httpClient = new HttpClient(handler)
{
Timeout = TimeSpan.FromSeconds(15)
};
}
/// <summary>
/// Aggiunge headers browser-like per evitare blocchi
/// </summary>
private void AddBrowserHeaders(HttpRequestMessage request, string? referer = null)
{
request.Headers.Add("User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36");
request.Headers.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8");
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");
request.Headers.Add("Cache-Control", "no-cache");
request.Headers.Add("Pragma", "no-cache");
if (!string.IsNullOrEmpty(referer))
{
request.Headers.Add("Referer", referer);
}
}
/// <summary>
/// Ottiene la lista delle categorie disponibili (con cache)
/// </summary>
public async Task<List<BidooCategoryInfo>> GetCategoriesAsync(bool forceRefresh = false, CancellationToken cancellationToken = default)
{
// Controlla cache
if (!forceRefresh && _cachedCategories.Count > 0 && DateTime.UtcNow - _categoriesCachedAt < _categoryCacheExpiry)
{
return _cachedCategories.ToList();
}
var categories = new List<BidooCategoryInfo>();
try
{
var request = new HttpRequestMessage(HttpMethod.Get, "https://it.bidoo.com/");
AddBrowserHeaders(request);
var response = await _httpClient.SendAsync(request, cancellationToken);
response.EnsureSuccessStatusCode();
var html = await response.Content.ReadAsStringAsync(cancellationToken);
// Aggiungi categorie speciali prima
categories.Add(new BidooCategoryInfo { TabId = 3, TagId = 0, DisplayName = "Tutte le aste", Slug = "", IsSpecialCategory = true, Icon = "bi-grid-3x3-gap" });
categories.Add(new BidooCategoryInfo { TabId = 1, TagId = 0, DisplayName = "Aste di Puntate", Slug = "", IsSpecialCategory = true, Icon = "bi-coin" });
categories.Add(new BidooCategoryInfo { TabId = 5, TagId = 0, DisplayName = "Aste Manuali", Slug = "", IsSpecialCategory = true, Icon = "bi-hand-index" });
// Parse categorie dal CategoryMenu
// Pattern: javascript:selectBids(4, true, false, 6); con data-tag="6" e testo "Buoni"
var categoryPattern = new Regex(
@"<a\s+href=""\s*javascript:selectBids\(4,\s*true,\s*false,\s*(\d+)\);\s*""\s+data-tab=""4""\s+data-slug=""([^""]*)""\s+data-tag=""(\d+)""><span[^>]*>([^<]+)</span></a>",
RegexOptions.IgnoreCase | RegexOptions.Singleline);
var matches = categoryPattern.Matches(html);
foreach (Match match in matches)
{
if (match.Success && match.Groups.Count >= 5)
{
int.TryParse(match.Groups[1].Value, out int tagId1);
var slug = match.Groups[2].Value.Trim();
int.TryParse(match.Groups[3].Value, out int tagId2);
var name = match.Groups[4].Value.Trim();
// Usa tagId1 o tagId2 (dovrebbero essere uguali)
var tagId = tagId1 > 0 ? tagId1 : tagId2;
if (tagId > 0 && !string.IsNullOrWhiteSpace(name))
{
categories.Add(new BidooCategoryInfo
{
TabId = 4,
TagId = tagId,
Slug = slug,
DisplayName = name,
IsSpecialCategory = false
});
}
}
}
// Se non abbiamo trovato categorie dal parsing, usa lista predefinita
if (categories.Count <= 3)
{
categories.AddRange(GetDefaultCategories());
}
// Aggiorna cache
_cachedCategories.Clear();
_cachedCategories.AddRange(categories);
_categoriesCachedAt = DateTime.UtcNow;
Console.WriteLine($"[BidooBrowser] Caricate {categories.Count} categorie");
}
catch (Exception ex)
{
Console.WriteLine($"[BidooBrowser] Errore caricamento categorie: {ex.Message}");
// Fallback a categorie predefinite
if (_cachedCategories.Count == 0)
{
categories.AddRange(GetDefaultCategories());
_cachedCategories.AddRange(categories);
}
else
{
return _cachedCategories.ToList();
}
}
return categories;
}
/// <summary>
/// Categorie predefinite come fallback
/// </summary>
private static List<BidooCategoryInfo> GetDefaultCategories()
{
return new List<BidooCategoryInfo>
{
new() { TabId = 4, TagId = 6, DisplayName = "Buoni", Slug = "buoni" },
new() { TabId = 4, TagId = 5, DisplayName = "Smartphone", Slug = "smartphone" },
new() { TabId = 4, TagId = 7, DisplayName = "Apple", Slug = "apple" },
new() { TabId = 4, TagId = 13, DisplayName = "Bellezza", Slug = "bellezza" },
new() { TabId = 4, TagId = 8, DisplayName = "Cucina", Slug = "cucina" },
new() { TabId = 4, TagId = 18, DisplayName = "Casa & Giardino", Slug = "casa_e_giardino" },
new() { TabId = 4, TagId = 11, DisplayName = "Elettrodomestici", Slug = "elettrodomestici" },
new() { TabId = 4, TagId = 9, DisplayName = "Videogame", Slug = "videogame" },
new() { TabId = 4, TagId = 41, DisplayName = "Giocattoli", Slug = "giocattoli" },
new() { TabId = 4, TagId = 14, DisplayName = "Tablet e PC", Slug = "tablet-e-pc" },
new() { TabId = 4, TagId = 20, DisplayName = "Hobby", Slug = "hobby" },
new() { TabId = 4, TagId = 22, DisplayName = "Smartwatch", Slug = "smartwatch" },
new() { TabId = 4, TagId = 37, DisplayName = "Animali Domestici", Slug = "animali_domestici" },
new() { TabId = 4, TagId = 12, DisplayName = "Moda", Slug = "moda" },
new() { TabId = 4, TagId = 10, DisplayName = "Smart TV", Slug = "smart-tv" },
new() { TabId = 4, TagId = 21, DisplayName = "Fai da Te", Slug = "fai_da_te" },
new() { TabId = 4, TagId = 26, DisplayName = "Luxury", Slug = "luxury" },
new() { TabId = 4, TagId = 19, DisplayName = "Cuffie e Audio", Slug = "cuffie-e-audio" },
new() { TabId = 4, TagId = 23, DisplayName = "Back to school", Slug = "back-to-school" },
new() { TabId = 4, TagId = 38, DisplayName = "Prima Infanzia", Slug = "prima-infanzia" }
};
}
/// <summary>
/// Ottiene le aste di una categoria specifica
/// Bidoo usa un sistema AJAX per caricare le aste dinamicamente
/// </summary>
public async Task<List<BidooBrowserAuction>> GetAuctionsAsync(
BidooCategoryInfo category,
int page = 0,
CancellationToken cancellationToken = default)
{
var auctions = new List<BidooBrowserAuction>();
try
{
// Bidoo carica le aste tramite chiamata AJAX a index.php con parametri POST-like in query string
// Il pattern è: index.php?selectBids=1&tab=X&tag=Y&offset=Z
string url;
if (category.IsSpecialCategory)
{
// Categorie speciali: BIDS (1), ALL (3), MANUAL (5)
var tabValue = category.TabId;
url = $"https://it.bidoo.com/index.php?selectBids=1&tab={tabValue}&tag=0&offset={page * 20}";
}
else
{
// Categorie normali: tab=4 + tag specifico
url = $"https://it.bidoo.com/index.php?selectBids=1&tab=4&tag={category.TagId}&offset={page * 20}";
}
Console.WriteLine($"[BidooBrowser] Fetching category '{category.DisplayName}': {url}");
var request = new HttpRequestMessage(HttpMethod.Get, url);
AddBrowserHeaders(request, "https://it.bidoo.com/");
request.Headers.Add("X-Requested-With", "XMLHttpRequest");
var response = await _httpClient.SendAsync(request, cancellationToken);
response.EnsureSuccessStatusCode();
var html = await response.Content.ReadAsStringAsync(cancellationToken);
// Parse aste dall'HTML (fragment AJAX)
auctions = ParseAuctionsFromHtml(html);
Console.WriteLine($"[BidooBrowser] Trovate {auctions.Count} aste nella categoria {category.DisplayName}");
// ?? DEBUG: Verifica quante aste hanno IsCreditAuction = true
if (category.IsSpecialCategory && category.TabId == 1)
{
var creditCount = auctions.Count(a => a.IsCreditAuction);
Console.WriteLine($"[BidooBrowser] DEBUG Aste di Puntate: {creditCount}/{auctions.Count} hanno IsCreditAuction=true");
// Log primi 3 nomi per debug
foreach (var a in auctions.Take(3))
{
Console.WriteLine($"[BidooBrowser] - {a.Name} (ID: {a.AuctionId}, IsCreditAuction: {a.IsCreditAuction})");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"[BidooBrowser] Errore caricamento aste: {ex.Message}");
}
return auctions;
}
private static string GetTabName(int tabId)
{
return tabId switch
{
1 => "BIDS",
2 => "FAV",
3 => "ALL",
5 => "MANUAL",
_ => "ALL"
};
}
/// <summary>
/// Parsa le aste dall'HTML della pagina
/// </summary>
private List<BidooBrowserAuction> ParseAuctionsFromHtml(string html)
{
var auctions = new List<BidooBrowserAuction>();
try
{
// Pattern per estrarre i div delle aste
// <div id="divAsta85584421" class="..." data-id="85584421" data-url="27_Puntate_85584421" data-freq="8" ...>
var auctionDivPattern = new Regex(
@"<div\s+id=""divAsta(\d+)""[^>]*" +
@"data-id=""(\d+)""[^>]*" +
@"data-url=""([^""]+)""[^>]*" +
@"data-freq=""(\d+)""[^>]*" +
@"(?:data-credit=""(\d+)"")?[^>]*" +
@"(?:data-credit-value=""(\d+)"")?[^>]*" +
@"(?:data-id-product=""(\d+)"")?",
RegexOptions.IgnoreCase | RegexOptions.Singleline);
// Pattern alternativo più semplice per catturare attributi
var simplePattern = new Regex(
@"<div[^>]+id=""divAsta(\d+)""[^>]*>",
RegexOptions.IgnoreCase);
var divMatches = simplePattern.Matches(html);
foreach (Match divMatch in divMatches)
{
if (!divMatch.Success) continue;
var auctionId = divMatch.Groups[1].Value;
// Trova il blocco completo dell'asta
var startIndex = divMatch.Index;
var endPattern = @"<!--/ \.bid -->";
var endIndex = html.IndexOf(endPattern, startIndex);
if (endIndex < 0) endIndex = html.IndexOf("</div><!--", startIndex + 1000);
if (endIndex < 0) continue;
var auctionHtml = html.Substring(startIndex, Math.Min(endIndex - startIndex + 100, html.Length - startIndex));
var auction = ParseSingleAuction(auctionId, auctionHtml);
if (auction != null)
{
auctions.Add(auction);
}
}
}
catch (Exception ex)
{
Console.WriteLine($"[BidooBrowser] Errore parsing HTML: {ex.Message}");
}
return auctions;
}
/// <summary>
/// Parsa una singola asta dal suo blocco HTML
/// </summary>
private BidooBrowserAuction? ParseSingleAuction(string auctionId, string html)
{
try
{
var auction = new BidooBrowserAuction { AuctionId = auctionId };
// Estrai data-url
var urlMatch = Regex.Match(html, @"data-url=""([^""]+)""");
if (urlMatch.Success)
{
auction.Url = $"https://it.bidoo.com/auction.php?a={urlMatch.Groups[1].Value}";
}
// Estrai data-freq
var freqMatch = Regex.Match(html, @"data-freq=""(\d+)""");
if (freqMatch.Success && int.TryParse(freqMatch.Groups[1].Value, out int freq))
{
auction.TimerFrequency = freq;
}
// Estrai data-credit e data-credit-value
var creditMatch = Regex.Match(html, @"data-credit=""(\d+)""");
if (creditMatch.Success && creditMatch.Groups[1].Value == "1")
{
auction.IsCreditAuction = true;
}
var creditValueMatch = Regex.Match(html, @"data-credit-value=""(\d+)""");
if (creditValueMatch.Success && int.TryParse(creditValueMatch.Groups[1].Value, out int creditVal))
{
auction.CreditValue = creditVal;
}
// Estrai data-id-product
var productMatch = Regex.Match(html, @"data-id-product=""(\d+)""");
if (productMatch.Success && int.TryParse(productMatch.Groups[1].Value, out int productId))
{
auction.ProductId = productId;
}
// Estrai immagine
var imgMatch = Regex.Match(html, @"<img[^>]+class=""img_small[^""]*""[^>]+src=""([^""]+)""");
if (imgMatch.Success)
{
auction.ImageUrl = imgMatch.Groups[1].Value;
}
else
{
// Pattern alternativo
imgMatch = Regex.Match(html, @"src=""(https://[^""]+/products/[^""]+)""");
if (imgMatch.Success)
{
auction.ImageUrl = imgMatch.Groups[1].Value;
}
}
// Estrai nome prodotto
var nameMatch = Regex.Match(html, @"<a[^>]+class=""name[^""]*""[^>]*>([^<]+)</a>", RegexOptions.IgnoreCase);
if (nameMatch.Success)
{
auction.Name = System.Net.WebUtility.HtmlDecode(nameMatch.Groups[1].Value.Trim());
}
// Estrai prezzo compralo subito
var buyNowMatch = Regex.Match(html, @"buy-rapid-now[^>]*>[^<]*<i[^>]*></i>\s*([0-9,\.]+)\s*€", RegexOptions.IgnoreCase);
if (buyNowMatch.Success)
{
var priceStr = buyNowMatch.Groups[1].Value.Replace(",", ".").Trim();
if (decimal.TryParse(priceStr, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out decimal buyNow))
{
auction.BuyNowPrice = buyNow;
}
}
// Controlla se è manuale (bi-noauto)
auction.IsManualOnly = html.Contains("bi-noauto", StringComparison.OrdinalIgnoreCase);
// Prezzo e bidder verranno aggiornati dalla chiamata a data.php
auction.CurrentPrice = 0.01m;
auction.LastBidder = "";
auction.RemainingSeconds = auction.TimerFrequency;
return auction;
}
catch (Exception ex)
{
Console.WriteLine($"[BidooBrowser] Errore parsing asta {auctionId}: {ex.Message}");
return null;
}
}
/// <summary>
/// Aggiorna lo stato delle aste usando data.php con LISTID (polling multiplo)
/// Formato chiamata: data.php?LISTID=id1,id2,id3&chk=timestamp
/// Formato risposta: timestamp*(id;status;expiry;price;bidder;timer;countdown#id2;...)
/// </summary>
public async Task UpdateAuctionStatesAsync(List<BidooBrowserAuction> auctions, CancellationToken cancellationToken = default)
{
if (auctions.Count == 0) return;
try
{
// Costruisci la lista di ID per il polling (formato LISTID)
var auctionIds = string.Join(",", auctions.Select(a => a.AuctionId));
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
var url = $"https://it.bidoo.com/data.php?LISTID={auctionIds}&chk={timestamp}";
Console.WriteLine($"[BidooBrowser] Polling {auctions.Count} aste...");
var request = new HttpRequestMessage(HttpMethod.Get, url);
AddBrowserHeaders(request, "https://it.bidoo.com/");
request.Headers.Add("X-Requested-With", "XMLHttpRequest");
var response = await _httpClient.SendAsync(request, cancellationToken);
if (!response.IsSuccessStatusCode)
{
Console.WriteLine($"[BidooBrowser] Polling fallito: {response.StatusCode}");
return;
}
var responseText = await response.Content.ReadAsStringAsync(cancellationToken);
// Parse risposta formato LISTID
ParseListIdResponse(responseText, auctions);
foreach (var auction in auctions)
{
auction.LastUpdated = DateTime.UtcNow;
}
}
catch (Exception ex)
{
Console.WriteLine($"[BidooBrowser] Errore aggiornamento stati: {ex.Message}");
}
}
/// <summary>
/// Parsa la risposta di data.php formato LISTID
/// Formato: serverTimestamp*(id;status;expiry;price;;#id2;status2;...)
/// Esempio: 1769073106*(85559629;ON;1769082240;1;;#85559630;ON;1769082240;1;;)
/// Il timestamp del server viene usato come riferimento per calcolare il tempo rimanente
/// </summary>
private void ParseListIdResponse(string response, List<BidooBrowserAuction> auctions)
{
try
{
// Trova inizio dati dopo timestamp*
var starIndex = response.IndexOf('*');
if (starIndex == -1)
{
Console.WriteLine("[BidooBrowser] Risposta non valida: manca '*'");
return;
}
// Estrai il timestamp del server (prima di *)
var serverTimestampStr = response.Substring(0, starIndex);
long serverTimestamp = 0;
long.TryParse(serverTimestampStr, out serverTimestamp);
var mainData = response.Substring(starIndex + 1);
// Rimuovi parentesi se presenti
if (mainData.StartsWith("(") && mainData.EndsWith(")"))
{
mainData = mainData.Substring(1, mainData.Length - 2);
}
// Split per ogni asta (separatore #)
var auctionEntries = mainData.Split('#', StringSplitOptions.RemoveEmptyEntries);
int updatedCount = 0;
foreach (var entry in auctionEntries)
{
// Formato: id;status;expiry;price;; (bidder e timer possono essere vuoti)
var fields = entry.Split(';');
if (fields.Length < 4) continue;
var id = fields[0].Trim();
var status = fields[1].Trim(); // ON/OFF
var expiryStr = fields[2].Trim(); // timestamp scadenza (stesso formato del server)
var priceStr = fields[3].Trim(); // prezzo (centesimi)
var bidder = fields.Length > 4 ? fields[4].Trim() : ""; // ultimo bidder (può essere vuoto)
var auction = auctions.FirstOrDefault(a => a.AuctionId == id);
if (auction == null) continue;
// Aggiorna prezzo (è in centesimi, convertire in euro)
if (int.TryParse(priceStr, out int priceCents))
{
auction.CurrentPrice = priceCents / 100m;
}
// Aggiorna bidder solo se non vuoto
if (!string.IsNullOrEmpty(bidder))
{
auction.LastBidder = bidder;
}
// Calcola tempo rimanente usando il timestamp del server come riferimento
if (long.TryParse(expiryStr, out long expiryTimestamp) && serverTimestamp > 0)
{
// Il tempo rimanente è: expiry - serverTime (entrambi nello stesso formato)
var remainingSeconds = expiryTimestamp - serverTimestamp;
auction.RemainingSeconds = remainingSeconds > 0 ? (int)remainingSeconds : 0;
}
else if (status == "ON")
{
// Se non riusciamo a calcolare, usa il timer frequency come fallback
if (auction.RemainingSeconds <= 0)
{
auction.RemainingSeconds = auction.TimerFrequency;
}
}
// Status: ON = attiva in countdown, OFF = terminata/in pausa
auction.IsActive = status == "ON";
auction.IsSold = status != "ON" && auction.RemainingSeconds <= 0;
updatedCount++;
}
Console.WriteLine($"[BidooBrowser] Aggiornate {updatedCount} aste su {auctionEntries.Length}");
}
catch (Exception ex)
{
Console.WriteLine($"[BidooBrowser] Errore parsing LISTID response: {ex.Message}");
}
}
/// <summary>
/// Converte countdown string in secondi
/// Formati: "7m", "1h 16m", "00:08", vuoto (usa timer frequency)
/// </summary>
private int ParseCountdown(string countdown, int defaultSeconds)
{
if (string.IsNullOrWhiteSpace(countdown))
{
return defaultSeconds;
}
try
{
// Formato ore e minuti: "1h 16m"
var hourMatch = Regex.Match(countdown, @"(\d+)h");
var minMatch = Regex.Match(countdown, @"(\d+)m");
int totalSeconds = 0;
if (hourMatch.Success && int.TryParse(hourMatch.Groups[1].Value, out int hours))
{
totalSeconds += hours * 3600;
}
if (minMatch.Success && int.TryParse(minMatch.Groups[1].Value, out int mins))
{
totalSeconds += mins * 60;
}
if (totalSeconds > 0)
{
return totalSeconds;
}
// Formato "00:08" (mm:ss o ss)
if (countdown.Contains(":"))
{
var parts = countdown.Split(':');
if (parts.Length == 2 &&
int.TryParse(parts[0], out int p1) &&
int.TryParse(parts[1], out int p2))
{
return p1 * 60 + p2;
}
}
// Solo numero = secondi
if (int.TryParse(countdown, out int secs))
{
return secs;
}
}
catch { }
return defaultSeconds;
}
/// <summary>
/// Carica nuove aste usando get_auction_updates.php (simula scrolling infinito)
/// Questa API restituisce aste che non sono ancora state caricate
/// </summary>
public async Task<List<BidooBrowserAuction>> GetMoreAuctionsAsync(
BidooCategoryInfo category,
List<string> existingAuctionIds,
CancellationToken cancellationToken = default)
{
var newAuctions = new List<BidooBrowserAuction>();
try
{
var existingIdsSet = existingAuctionIds.ToHashSet();
// Prepara la chiamata POST a get_auction_updates.php
var url = "https://it.bidoo.com/get_auction_updates.php";
// Costruisci il body della richiesta
var viewIds = string.Join(",", existingAuctionIds);
var tabValue = category.IsSpecialCategory ? category.TabId : 4;
var tagValue = category.IsSpecialCategory ? 0 : category.TagId;
var formContent = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("prefetch", "true"),
new KeyValuePair<string, string>("view", viewIds),
new KeyValuePair<string, string>("tab", tabValue.ToString()),
new KeyValuePair<string, string>("tag", tagValue.ToString())
});
var request = new HttpRequestMessage(HttpMethod.Post, url)
{
Content = formContent
};
AddBrowserHeaders(request, "https://it.bidoo.com/");
request.Headers.Add("X-Requested-With", "XMLHttpRequest");
Console.WriteLine($"[BidooBrowser] Fetching more auctions with {existingAuctionIds.Count} existing IDs...");
var response = await _httpClient.SendAsync(request, cancellationToken);
response.EnsureSuccessStatusCode();
var responseText = await response.Content.ReadAsStringAsync(cancellationToken);
// Parse la risposta JSON
// Formato: {"gc":[],"int":[],"list":[id1,id2,...],"items":["<html>","<html>",...]}
newAuctions = ParseGetAuctionUpdatesResponse(responseText, existingIdsSet);
Console.WriteLine($"[BidooBrowser] Trovate {newAuctions.Count} nuove aste");
}
catch (Exception ex)
{
Console.WriteLine($"[BidooBrowser] Errore caricamento nuove aste: {ex.Message}");
}
return newAuctions;
}
/// <summary>
/// Parsa la risposta di get_auction_updates.php
/// </summary>
private List<BidooBrowserAuction> ParseGetAuctionUpdatesResponse(string json, HashSet<string> existingIds)
{
var auctions = new List<BidooBrowserAuction>();
try
{
// Parse JSON manuale per estrarre items[]
// Cerchiamo "items":["...","..."]
var itemsMatch = Regex.Match(json, @"""items"":\s*\[(.*?)\](?=,""|\})", RegexOptions.Singleline);
if (!itemsMatch.Success)
{
Console.WriteLine("[BidooBrowser] Nessun items trovato nella risposta");
return auctions;
}
var itemsContent = itemsMatch.Groups[1].Value;
// Gli items sono stringhe HTML escaped, dobbiamo parsarle
// Ogni item è una stringa JSON che contiene HTML
var htmlPattern = new Regex(@"""((?:[^""\\]|\\.)*?)""", RegexOptions.Singleline);
var htmlMatches = htmlPattern.Matches(itemsContent);
foreach (Match htmlMatch in htmlMatches)
{
if (!htmlMatch.Success) continue;
// Unescape la stringa JSON
var escapedHtml = htmlMatch.Groups[1].Value;
var html = UnescapeJsonString(escapedHtml);
// Estrai l'ID dell'asta
var idMatch = Regex.Match(html, @"id=""divAsta(\d+)""");
if (!idMatch.Success) continue;
var auctionId = idMatch.Groups[1].Value;
// Salta se già esiste
if (existingIds.Contains(auctionId)) continue;
// Parsa l'asta dall'HTML
var auction = ParseSingleAuction(auctionId, html);
if (auction != null)
{
auctions.Add(auction);
}
}
}
catch (Exception ex)
{
Console.WriteLine($"[BidooBrowser] Errore parsing get_auction_updates response: {ex.Message}");
}
return auctions;
}
/// <summary>
/// Unescape di una stringa JSON
/// </summary>
private static string UnescapeJsonString(string escaped)
{
return escaped
.Replace("\\/", "/")
.Replace("\\n", "\n")
.Replace("\\r", "\r")
.Replace("\\t", "\t")
.Replace("\\\"", "\"")
.Replace("\\\\", "\\");
}
}
}