Aggiunta calcolo valore prodotto e miglioramenti UI
Implementato il calcolo del valore reale dei prodotti in asta, includendo il prezzo "Compra Subito", spese di spedizione e risparmio stimato. Aggiunta una nuova sezione "Info Prodotto" nella UI per visualizzare i dettagli estratti e i calcoli. - **AuctionMonitorControl.xaml**: Aggiunta sezione fissa per mostrare informazioni prodotto e calcolo valore. - **AuctionMonitorControl.xaml.cs**: Gestiti eventi per il caricamento e aggiornamento delle informazioni prodotto. - **MainWindow**: Integrati handler per il calcolo e refresh delle informazioni prodotto. - **AuctionInfo.cs**: Aggiunte proprietà per gestire prezzo "Compra Subito", spese di spedizione e limiti di vincita. - **ProductValueCalculator.cs**: Nuova utility per calcolare il valore del prodotto e parsare informazioni dall'HTML. - **AuctionViewModel.cs**: Binding per visualizzare risparmio, costo totale e convenienza nella UI. - **Documentazione**: Aggiornata con dettagli sull'algoritmo di calcolo e layout UI. Fix: - Risolto problema di encoding UTF-8 per emoji nella UI. - Migliorato parsing HTML per prezzi e limiti di vincita. TODO: - Testare parsing su più aste e gestire edge cases. - Implementare caricamento automatico delle informazioni.
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using AutoBidder.Models;
|
||||
using AutoBidder.Utilities;
|
||||
|
||||
namespace AutoBidder.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Estensioni per AuctionMonitor per gestire il calcolo del valore del prodotto
|
||||
/// </summary>
|
||||
public static class AuctionMonitorProductValueExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Inizializza le informazioni del prodotto per un'asta
|
||||
/// Scarica la pagina HTML e estrae prezzo "Compra Subito", limite vincita, ecc.
|
||||
/// </summary>
|
||||
/// <param name="apiClient">Client API per scaricare la pagina</param>
|
||||
/// <param name="auctionInfo">Asta da inizializzare</param>
|
||||
/// <param name="token">Token di cancellazione</param>
|
||||
/// <returns>True se almeno alcune informazioni sono state estratte</returns>
|
||||
public static async Task<bool> InitializeProductInfoAsync(
|
||||
BidooApiClient apiClient,
|
||||
AuctionInfo auctionInfo,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Costruisci URL della pagina dell'asta
|
||||
string auctionUrl = auctionInfo.OriginalUrl;
|
||||
if (string.IsNullOrWhiteSpace(auctionUrl))
|
||||
{
|
||||
// Fallback: costruisci URL standard
|
||||
auctionUrl = $"https://it.bidoo.com/auction.php?a={auctionInfo.AuctionId}";
|
||||
}
|
||||
|
||||
auctionInfo.AddLog("[PRODUCT INFO] Scaricamento informazioni prodotto...");
|
||||
|
||||
// Scarica HTML della pagina (riutilizza il metodo di polling se necessario)
|
||||
var html = await DownloadAuctionPageAsync(apiClient, auctionUrl, token);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(html))
|
||||
{
|
||||
auctionInfo.AddLog("[PRODUCT INFO] WARN: Nessun HTML ricevuto");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Estrai informazioni dal HTML
|
||||
bool extracted = ProductValueCalculator.ExtractProductInfo(html, auctionInfo);
|
||||
|
||||
if (extracted)
|
||||
{
|
||||
var log = "[PRODUCT INFO] Estratte:";
|
||||
if (auctionInfo.BuyNowPrice.HasValue)
|
||||
log += $" Compra Subito={auctionInfo.BuyNowPrice.Value:F2}€";
|
||||
if (auctionInfo.HasWinLimit)
|
||||
log += $" Limite={auctionInfo.WinLimitDescription}";
|
||||
|
||||
auctionInfo.AddLog(log);
|
||||
}
|
||||
else
|
||||
{
|
||||
auctionInfo.AddLog("[PRODUCT INFO] WARN: Nessuna informazione trovata nell'HTML");
|
||||
}
|
||||
|
||||
return extracted;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
auctionInfo.AddLog($"[PRODUCT INFO ERROR] {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aggiorna il valore calcolato del prodotto basandosi sullo stato corrente
|
||||
/// </summary>
|
||||
/// <param name="auctionInfo">Asta da aggiornare</param>
|
||||
/// <param name="state">Stato corrente dell'asta</param>
|
||||
/// <param name="logValue">Se true, logga il valore nel log dell'asta</param>
|
||||
public static void UpdateProductValue(AuctionInfo auctionInfo, AuctionState state, bool logValue = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Calcola numero totale puntate basato sul prezzo (ogni puntata = +0.01€)
|
||||
int totalBids = (int)Math.Round(state.Price * 100);
|
||||
|
||||
// Calcola valore
|
||||
var value = ProductValueCalculator.Calculate(auctionInfo, state.Price, totalBids);
|
||||
|
||||
// Salva nel model
|
||||
auctionInfo.CalculatedValue = value;
|
||||
|
||||
// Log opzionale (utile solo per eventi significativi, non ad ogni polling)
|
||||
if (logValue)
|
||||
{
|
||||
var message = ProductValueCalculator.FormatValueMessage(value);
|
||||
auctionInfo.AddLog($"[VALUE] {message}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
auctionInfo.AddLog($"[VALUE ERROR] {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifica se conviene ancora puntare basandosi sul valore del prodotto
|
||||
/// </summary>
|
||||
/// <param name="auctionInfo">Asta da verificare</param>
|
||||
/// <param name="minSavingsPercentage">Percentuale minima di risparmio per considerare conveniente (default: 0)</param>
|
||||
/// <returns>True se conviene puntare, False se si sta spendendo troppo</returns>
|
||||
public static bool IsStillWorthBidding(AuctionInfo auctionInfo, double minSavingsPercentage = 0)
|
||||
{
|
||||
var value = auctionInfo.CalculatedValue;
|
||||
|
||||
// Se non abbiamo calcolato il valore, assumiamo sia conveniente
|
||||
if (value == null)
|
||||
return true;
|
||||
|
||||
// Se non c'è un prezzo "Compra Subito" di riferimento, non possiamo decidere
|
||||
if (!value.BuyNowPrice.HasValue)
|
||||
return true;
|
||||
|
||||
// Verifica risparmio
|
||||
if (!value.Savings.HasValue)
|
||||
return true;
|
||||
|
||||
// Controlla se il risparmio è abbastanza
|
||||
if (value.SavingsPercentage.HasValue)
|
||||
{
|
||||
return value.SavingsPercentage.Value >= minSavingsPercentage;
|
||||
}
|
||||
|
||||
return value.Savings.Value >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scarica la pagina HTML dell'asta
|
||||
/// </summary>
|
||||
private static async Task<string> DownloadAuctionPageAsync(
|
||||
BidooApiClient apiClient,
|
||||
string url,
|
||||
CancellationToken token)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Usa HttpClient del BidooApiClient se disponibile
|
||||
// Altrimenti crea un client temporaneo
|
||||
using var httpClient = new System.Net.Http.HttpClient();
|
||||
|
||||
// Copia headers dal client API se disponibile (per autenticazione)
|
||||
var session = apiClient.GetSession();
|
||||
if (!string.IsNullOrWhiteSpace(session.AuthToken))
|
||||
{
|
||||
httpClient.DefaultRequestHeaders.Add("Cookie", $"auth_token={session.AuthToken}");
|
||||
}
|
||||
|
||||
httpClient.DefaultRequestHeaders.Add("User-Agent",
|
||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
|
||||
|
||||
var response = await httpClient.GetAsync(url, token);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -495,20 +495,20 @@ namespace AutoBidder.Services
|
||||
result.Success = true;
|
||||
var parts = responseText.Split('|');
|
||||
|
||||
// Log della risposta completa per debugging
|
||||
Log($"[BID PARSE] Risposta completa: {responseText}", auctionId);
|
||||
Log($"[BID PARSE] Numero totale campi: {parts.Length}", auctionId);
|
||||
|
||||
// Formato risposta Bidoo: 9 campi separati da |
|
||||
// Campo 1 (indice 0): "ok"
|
||||
// Campo 2 (indice 1): Puntate residue totali
|
||||
// Campo 3 (indice 2): ?
|
||||
// Campo 4 (indice 3): ?
|
||||
if (parts.Length > 1 && int.TryParse(parts[1], out var remaining))
|
||||
{
|
||||
result.RemainingBids = remaining;
|
||||
_session.RemainingBids = remaining;
|
||||
}
|
||||
|
||||
// Campo 5 (indice 4): Puntate usate su questa asta
|
||||
// Campo 6-9 (indice 5-8): Altri dati
|
||||
if (parts.Length > 4 && int.TryParse(parts[4], out var usedOnAuction))
|
||||
{
|
||||
result.BidsUsedOnThisAuction = usedOnAuction;
|
||||
}
|
||||
|
||||
// Prezzo - presumibilmente nel campo 3 o nei campi successivi
|
||||
// Cerchiamo un campo numerico ragionevole per il prezzo
|
||||
for (int i = 1; i < Math.Min(parts.Length, 6); i++)
|
||||
{
|
||||
if (i == 1 || i == 4) continue; // Skip remaining bids e bids used
|
||||
@@ -516,62 +516,12 @@ namespace AutoBidder.Services
|
||||
if (double.TryParse(parts[i], out var priceIndex) && priceIndex > 0)
|
||||
{
|
||||
result.NewPrice = priceIndex * 0.01;
|
||||
Log($"[BID PARSE] Campo {i+1} - Prezzo parsato: €{result.NewPrice:F2}", auctionId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Campo 2 (indice 1): Puntate residue totali
|
||||
if (parts.Length > 1)
|
||||
{
|
||||
Log($"[BID PARSE] Campo 2 (indice 1) - Remaining bids: '{parts[1]}'", auctionId);
|
||||
if (int.TryParse(parts[1], out var remaining))
|
||||
{
|
||||
result.RemainingBids = remaining;
|
||||
_session.RemainingBids = remaining;
|
||||
Log($"[BID SUCCESS] ✅ Puntate residue totali: {remaining}", auctionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"[BID PARSE WARN] ⚠️ Impossibile parsare campo 2 come int: '{parts[1]}'", auctionId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"[BID PARSE ERROR] ❌ Risposta non ha campo 2 (remaining bids)", auctionId);
|
||||
}
|
||||
|
||||
// Campo 5 (indice 4): Puntate usate su questa asta
|
||||
if (parts.Length > 4)
|
||||
{
|
||||
Log($"[BID PARSE] Campo 5 (indice 4) - Bids used on auction: '{parts[4]}'", auctionId);
|
||||
if (int.TryParse(parts[4], out var usedOnAuction))
|
||||
{
|
||||
result.BidsUsedOnThisAuction = usedOnAuction;
|
||||
Log($"[BID SUCCESS] ✅ Puntate usate su questa asta: {usedOnAuction}", auctionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"[BID PARSE WARN] ⚠️ Impossibile parsare campo 5 come int: '{parts[4]}'", auctionId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"[BID PARSE ERROR] ❌ Risposta non ha campo 5 (bids used on auction) - Campi totali: {parts.Length}", auctionId);
|
||||
}
|
||||
|
||||
// Log tutti i campi per debugging completo
|
||||
Log($"[BID PARSE DEBUG] Tutti i campi della risposta:", auctionId);
|
||||
for (int i = 0; i < parts.Length; i++)
|
||||
{
|
||||
Log($" Campo {i+1} (indice {i}): '{parts[i]}'", auctionId);
|
||||
}
|
||||
|
||||
// Messaggio finale
|
||||
if (!result.RemainingBids.HasValue && !result.BidsUsedOnThisAuction.HasValue)
|
||||
{
|
||||
Log("[BID SUCCESS] ⚠️ Puntata piazzata ma dati contatori non disponibili nella risposta", auctionId);
|
||||
}
|
||||
// Log solo successo
|
||||
Log($"[BID SUCCESS] Puntata piazzata", auctionId);
|
||||
}
|
||||
else if (responseText.StartsWith("error", StringComparison.OrdinalIgnoreCase) || responseText.StartsWith("no|", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user