Aggiunta infrastruttura avanzata per gestione aste

- Introdotta la classe `BidooApiClient` per interagire con le API Bidoo.
- Aggiunto `SessionManager` per la gestione sicura delle sessioni.
- Creato `TestBidooApi` per test manuali delle API.
- Implementato `CsvExporter` per esportare dati e statistiche in CSV.
- Aggiunto `PersistenceManager` per salvare e caricare aste in JSON.
- Introdotto `AuctionViewModel` per supportare il pattern MVVM.
- Migliorata l'interfaccia utente con layout moderno e stili dinamici.
- Aggiornata la documentazione in `README.md` per riflettere le nuove funzionalità.
- Aggiunte classi per rappresentare informazioni, stato e storico delle aste.
- Ottimizzate le richieste HTTP per simulare un browser reale.
This commit is contained in:
Alberto Balbo
2025-10-23 23:10:46 +02:00
parent db1d99d424
commit 4e16f50aeb
26 changed files with 4522 additions and 2576 deletions

View File

@@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
namespace AutoBidder.Models
{
/// <summary>
/// Informazioni base di un'asta monitorata
/// </summary>
public class AuctionInfo
{
public string AuctionId { get; set; } = "";
public string Name { get; set; } = ""; // Opzionale, può essere lasciato vuoto
public string OriginalUrl { get; set; } = ""; // URL completo dell'asta (per referer)
// Configurazione asta
public int TimerClick { get; set; } = 0; // Secondo del timer per click (default 0)
public int DelayMs { get; set; } = 50; // Ritardo aggiuntivo in ms (per compensare latenza)
public double MinPrice { get; set; } = 0;
public double MaxPrice { get; set; } = 0;
public int MinResets { get; set; } = 0; // Numero minimo reset prima di puntare
public int MaxResets { get; set; } = 0; // Numero massimo reset (0 = illimitati)
// Stato asta
public bool IsActive { get; set; } = true;
public bool IsPaused { get; set; } = false;
// Contatori
public int MyClicks { get; set; } = 0;
public int ResetCount { get; set; } = 0;
// Timestamp
public DateTime AddedAt { get; set; } = DateTime.UtcNow;
public DateTime? LastClickAt { get; set; }
// Storico
public List<BidHistory> BidHistory { get; set; } = new();
public Dictionary<string, BidderInfo> BidderStats { get; set; } = new(StringComparer.OrdinalIgnoreCase);
// Legacy (deprecato, usa BidderStats)
[System.Text.Json.Serialization.JsonIgnore]
public Dictionary<string, int> Bidders { get; set; } = new(StringComparer.OrdinalIgnoreCase);
// Log per-asta (non serializzato)
[System.Text.Json.Serialization.JsonIgnore]
public List<string> AuctionLog { get; set; } = new();
/// <summary>
/// Aggiunge una voce al log dell'asta
/// </summary>
public void AddLog(string message)
{
var entry = $"{DateTime.Now:HH:mm:ss} - {message}";
AuctionLog.Add(entry);
// Mantieni solo ultimi 100 log
if (AuctionLog.Count > 100)
{
AuctionLog.RemoveAt(0);
}
}
}
}

View File

@@ -0,0 +1,48 @@
using System;
namespace AutoBidder.Models
{
/// <summary>
/// Stato real-time di un'asta (snapshot dal polling HTTP)
/// </summary>
public class AuctionState
{
public string AuctionId { get; set; } = "";
// Dati correnti
public double Timer { get; set; } = 999;
public double Price { get; set; } = 0;
public string LastBidder { get; set; } = "";
public bool IsMyBid { get; set; } = false;
// Stato asta
public AuctionStatus Status { get; set; } = AuctionStatus.Unknown;
public string StartTime { get; set; } = ""; // Es: "Oggi alle 17:00" o "23 Ottobre 10:10"
// Timestamp snapshot
public DateTime SnapshotTime { get; set; } = DateTime.UtcNow;
// Latenza polling
public int PollingLatencyMs { get; set; } = 0;
// Dati estratti HTML
public string RawHtml { get; set; } = "";
public bool ParsingSuccess { get; set; } = true;
}
/// <summary>
/// Stato corrente dell'asta
/// </summary>
public enum AuctionStatus
{
Unknown, // Non determinato
Running, // Asta in corso (ON + timer attivo + utenti presenti)
Paused, // Asta in pausa (STOP nelle API - tipicamente 00:00-10:00)
EndedWon, // Asta terminata - HAI VINTO! (OFF + io sono last bidder)
EndedLost, // Asta terminata - Persa (OFF + altro è last bidder)
Pending, // In attesa di inizio (ON + no bidder + expiry < 30min)
Scheduled, // Programmata per più tardi (ON + no bidder + expiry > 30min)
Closed, // Asta chiusa/terminata (generico)
NotStarted // Non ancora iniziata (legacy)
}
}

View File

@@ -0,0 +1,126 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace AutoBidder.Models
{
/// <summary>
/// Statistiche aggregate di un'asta per dashboard e export
/// </summary>
public class AuctionStatistics
{
public string AuctionId { get; set; } = "";
public string Name { get; set; } = "";
// Tempo monitoraggio
public DateTime MonitoringStarted { get; set; }
public TimeSpan MonitoringDuration { get; set; }
// Contatori
public int TotalBids { get; set; }
public int MyBids { get; set; }
public int OpponentBids { get; set; }
public int Resets { get; set; }
public int UniqueBidders { get; set; }
// Prezzi
public double StartPrice { get; set; }
public double CurrentPrice { get; set; }
public double MinPrice { get; set; }
public double MaxPrice { get; set; }
public double AvgPrice { get; set; }
// Timer
public double AvgTimerAtBid { get; set; }
public double MinTimerReached { get; set; }
// Latenza
public int AvgPollingLatencyMs { get; set; }
public int AvgClickLatencyMs { get; set; }
public int MinClickLatencyMs { get; set; }
public int MaxClickLatencyMs { get; set; }
// Rate
public double BidsPerMinute { get; set; }
public double ResetsPerHour { get; set; }
// Competitor analysis
public string MostActiveBidder { get; set; } = "";
public int MostActiveBidderCount { get; set; }
public Dictionary<string, int> BidderRanking { get; set; } = new();
// Success rate
public double MyBidSuccessRate { get; set; } // % mie puntate sul totale
// Calcola statistiche da BidHistory
public static AuctionStatistics Calculate(AuctionInfo auction)
{
var stats = new AuctionStatistics
{
AuctionId = auction.AuctionId,
Name = auction.Name,
MonitoringStarted = auction.AddedAt,
MonitoringDuration = DateTime.UtcNow - auction.AddedAt,
MyBids = auction.MyClicks,
Resets = auction.ResetCount,
UniqueBidders = auction.Bidders.Count,
BidderRanking = auction.Bidders
};
if (auction.BidHistory.Any())
{
var prices = auction.BidHistory.Select(h => h.Price).Where(p => p > 0).ToList();
if (prices.Any())
{
stats.StartPrice = prices.First();
stats.CurrentPrice = prices.Last();
stats.MinPrice = prices.Min();
stats.MaxPrice = prices.Max();
stats.AvgPrice = prices.Average();
}
stats.TotalBids = auction.BidHistory.Count(h => h.EventType == BidEventType.MyBid || h.EventType == BidEventType.OpponentBid);
stats.OpponentBids = stats.TotalBids - stats.MyBids;
var timers = auction.BidHistory.Select(h => h.Timer).ToList();
if (timers.Any())
{
stats.AvgTimerAtBid = timers.Average();
stats.MinTimerReached = timers.Min();
}
var latencies = auction.BidHistory.Where(h => h.EventType == BidEventType.MyBid).Select(h => h.LatencyMs).ToList();
if (latencies.Any())
{
stats.AvgClickLatencyMs = (int)latencies.Average();
stats.MinClickLatencyMs = latencies.Min();
stats.MaxClickLatencyMs = latencies.Max();
}
if (stats.MonitoringDuration.TotalMinutes > 0)
{
stats.BidsPerMinute = stats.TotalBids / stats.MonitoringDuration.TotalMinutes;
}
if (stats.MonitoringDuration.TotalHours > 0)
{
stats.ResetsPerHour = stats.Resets / stats.MonitoringDuration.TotalHours;
}
if (stats.TotalBids > 0)
{
stats.MyBidSuccessRate = (double)stats.MyBids / stats.TotalBids * 100;
}
}
if (auction.Bidders.Any())
{
var topBidder = auction.Bidders.OrderByDescending(b => b.Value).First();
stats.MostActiveBidder = topBidder.Key;
stats.MostActiveBidderCount = topBidder.Value;
}
return stats;
}
}
}

View File

@@ -0,0 +1,29 @@
using System;
namespace AutoBidder.Models
{
/// <summary>
/// Entry storico per ogni puntata/evento dell'asta
/// </summary>
public class BidHistory
{
public DateTime Timestamp { get; set; }
public BidEventType EventType { get; set; }
public string Bidder { get; set; } = "";
public double Price { get; set; }
public double Timer { get; set; }
public int LatencyMs { get; set; }
public bool Success { get; set; }
public string Notes { get; set; } = "";
}
public enum BidEventType
{
MyBid, // Mia puntata
OpponentBid, // Puntata avversario
Reset, // Reset timer
PriceChange, // Cambio prezzo
AuctionStarted, // Asta iniziata
AuctionEnded // Asta terminata
}
}

View File

@@ -0,0 +1,18 @@
using System;
namespace AutoBidder.Models
{
/// <summary>
/// Risultato di un tentativo di puntata API
/// </summary>
public class BidResult
{
public string AuctionId { get; set; } = "";
public DateTime Timestamp { get; set; }
public bool Success { get; set; }
public int LatencyMs { get; set; }
public string Response { get; set; } = "";
public string Error { get; set; } = "";
public double NewPrice { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
using System;
namespace AutoBidder.Models
{
/// <summary>
/// Informazioni su un utente che ha piazzato puntate
/// </summary>
public class BidderInfo
{
public string Username { get; set; } = "";
public int BidCount { get; set; } = 0;
public DateTime LastBidTime { get; set; } = DateTime.MinValue;
public string LastBidTimeDisplay => LastBidTime == DateTime.MinValue
? "-"
: LastBidTime.ToString("HH:mm:ss");
}
}

View File

@@ -0,0 +1,48 @@
using System;
namespace AutoBidder.Models
{
/// <summary>
/// Sessione Bidoo con token di autenticazione
/// </summary>
public class BidooSession
{
/// <summary>
/// Token di autenticazione (estratto da cookie o header)
/// Usato per autenticare tutte le chiamate API
/// </summary>
public string AuthToken { get; set; } = "";
/// <summary>
/// Cookie string completa (opzionale, backup)
/// Formato: "cookie1=value1; cookie2=value2; ..."
/// </summary>
public string CookieString { get; set; } = "";
/// <summary>
/// Username estratto dalla sessione
/// </summary>
public string Username { get; set; } = "";
/// <summary>
/// Puntate rimanenti sull'account
/// </summary>
public int RemainingBids { get; set; } = 0;
/// <summary>
/// Timestamp ultimo aggiornamento info account
/// </summary>
public DateTime LastAccountUpdate { get; set; } = DateTime.MinValue;
/// <summary>
/// Flag sessione valida
/// </summary>
public bool IsValid => !string.IsNullOrWhiteSpace(AuthToken) || !string.IsNullOrWhiteSpace(CookieString);
/// <summary>
/// CSRF Token per puntate (estratto da pagina, opzionale)
/// </summary>
public string? CsrfToken { get; set; }
}
}