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:
62
Mimante/Models/AuctionInfo.cs
Normal file
62
Mimante/Models/AuctionInfo.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Mimante/Models/AuctionState.cs
Normal file
48
Mimante/Models/AuctionState.cs
Normal 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)
|
||||
}
|
||||
}
|
||||
126
Mimante/Models/AuctionStatistics.cs
Normal file
126
Mimante/Models/AuctionStatistics.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
29
Mimante/Models/BidHistory.cs
Normal file
29
Mimante/Models/BidHistory.cs
Normal 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
|
||||
}
|
||||
}
|
||||
18
Mimante/Models/BidResult.cs
Normal file
18
Mimante/Models/BidResult.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
18
Mimante/Models/BidderInfo.cs
Normal file
18
Mimante/Models/BidderInfo.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
48
Mimante/Models/BidooSession.cs
Normal file
48
Mimante/Models/BidooSession.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user