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 { /// /// Servizio per navigare le aste pubbliche di Bidoo senza autenticazione /// Permette di esplorare le categorie e visualizzare le aste disponibili /// public class BidooBrowserService { private readonly HttpClient _httpClient; private readonly List _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) }; } /// /// Aggiunge headers browser-like per evitare blocchi /// 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); } } /// /// Ottiene la lista delle categorie disponibili (con cache) /// public async Task> 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(); 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( @"]*>([^<]+)", 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; } /// /// Categorie predefinite come fallback /// private static List GetDefaultCategories() { return new List { 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" } }; } /// /// Ottiene le aste di una categoria specifica /// Bidoo usa un sistema AJAX per caricare le aste dinamicamente /// public async Task> GetAuctionsAsync( BidooCategoryInfo category, int page = 0, CancellationToken cancellationToken = default) { var auctions = new List(); 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" }; } /// /// Parsa le aste dall'HTML della pagina /// private List ParseAuctionsFromHtml(string html) { var auctions = new List(); try { // Pattern per estrarre i div delle aste //
var auctionDivPattern = new Regex( @"]*" + @"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( @"]+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 = @""; var endIndex = html.IndexOf(endPattern, startIndex); if (endIndex < 0) endIndex = html.IndexOf("