Files
Mimante/Mimante/Core/MainWindow.ButtonHandlers.cs
T
Alby96andClaude Fable 5.1 9177b31bd5 Due database, interruttore dell'apprendimento, barre a icone; via kill-switch, esportazioni e voci deprecate
Tutto ciò che l'applicazione registra sta ora in due file SQLite in
%LocalAppData%\AutoBidder\Database: autobidder.sqlite per le osservazioni e
esercizio.sqlite per i documenti d'esercizio (aste nel monitor, prodotti, promozioni,
modelli appresi con prefisso ml/) e per i registri applicativo e del riscatto puntate.
Il motore comune è SqliteDatabase; i file JSON e la cartella Apprendimento delle
versioni precedenti vengono importati la prima volta e lasciati dove sono. La cartella
si cambia dalle Impostazioni: al salvataggio si chiede se spostare i file, e si riavvia.
Le cartelle Dati/Statistiche/Registri, i registri su file, le misure dell'anticipo e
le impostazioni morte (LogBids, AutoApplyProductDefaults, NewAuctionLimitsPriority…)
non esistono più.

Il kill-switch se ne va: al suo posto un solo interruttore in barra, accanto ad
Avvia/Osserva/Ferma, che spegne l'apprendimento. Spento, il motore punta solo entro i
limiti dell'utente (prezzo, puntate, budget, fascia oraria, rischio) con l'anticipo
fisso: niente valore atteso, regime, duello, bandit, anticipo adattivo. Le aste vengono
comunque registrate e studiate.

Tre difetti visti osservando il motore dal vivo per un'ora, in Osserva:
- la copertura «Compralo Ora» con V lasciato al listino azzerava la perdita coperta e
  il motore diceva BID a ogni scadenza con P dell'1%: ora vale solo per i prodotti con
  «Valore reale €» scritto, altrimenti la puntata si conta persa e P decide;
- la chiusura di un'asta arrivava due volte (fine, poi rimozione dal monitor) e di
  nuovo al riavvio: modello, profilo, bandit e statistiche per prodotto contavano ogni
  asta due volte. Il monitor la comunica una volta sola, l'apprendimento salta le aste
  già apprese, e le schede prodotto vengono ricostruite una volta dallo storico;
- la pagina delle ricompense veniva scambiata per la pagina di accesso perché porta un
  collegamento a /login.php: prima si guarda se è la pagina delle ricompense.

Interfaccia: la scheda Esporta e tutto il suo codice sono tolti; le esportazioni
chiedono sempre dove salvare. Prodotti, Storico e Apprendimento hanno barre a sole
icone con tooltip che dicono esattamente cosa succede, colorate come il monitor, con
i nuovi pulsanti di pulizia (spegni tutte le stelline, togli i non seguiti, pulizia
completa; seleziona tutte / elimina le selezionate / svuota lo storico). Impostazioni:
sezione Database, Manutenzione con «Elimina tutti i dati (tranne la login)» e
«Impostazioni di fabbrica», esportazione dei registri in testo. Modifiche.txt, il
prompt ormai realizzato, esce dal repository.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 13:58:08 +02:00

629 lines
25 KiB
C#

using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;
using System.Windows.Controls;
using AutoBidder.Dialogs;
using AutoBidder.Utilities;
using AutoBidder.ViewModels;
namespace AutoBidder
{
/// <summary>
/// Button click event handlers for auction operations
/// </summary>
public partial class MainWindow
{
// sender null = azione interna (non richiesta dall'utente): si evita di loggarla.
private void StartButton_Click(object? sender, RoutedEventArgs e)
{
try
{
if (!_isAutomationActive)
{
// Avvia il monitoraggio globale
_auctionMonitor.Start();
_isAutomationActive = true;
Log("[START] Monitoraggio avviato!", LogLevel.Success);
}
// Attiva e riprendi tutte le aste
foreach (var vm in _auctionViewModels)
{
if (!vm.IsActive)
{
vm.IsActive = true;
}
vm.IsPaused = false;
}
if (sender != null) // Solo se chiamato dall'utente, non internamente
{
Log("[START ALL] Tutte le aste avviate/riprese", LogLevel.Info);
}
// ? Salva gli stati aggiornati su disco
SaveAuctions();
UpdateGlobalControlButtons();
}
catch (Exception ex)
{
Log($"[ERRORE] Avvio: {ex.Message}", LogLevel.Error);
MessageBox.Show($"Errore: {ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void StopButton_Click(object? sender, RoutedEventArgs e)
{
try
{
// Ferma tutte le aste
foreach (var vm in _auctionViewModels)
{
vm.IsActive = false;
}
// Ferma il monitoraggio globale
if (_isAutomationActive)
{
_auctionMonitor.Stop();
_isAutomationActive = false;
}
// ? Salva gli stati aggiornati su disco
SaveAuctions();
UpdateGlobalControlButtons();
if (sender != null) // Solo se chiamato dall'utente
{
Log("[STOP ALL] Monitoraggio fermato e tutte le aste arrestate", LogLevel.Warning);
}
}
catch (Exception ex)
{
Log($"[ERRORE STOP] {ex.Message}", LogLevel.Error);
}
}
private async void PauseAllButton_Click(object? sender, RoutedEventArgs e)
{
try
{
// Osserva vale anche per le aste ferme: servono comunque i motori accesi
// per seguirle, semplicemente non punteranno.
foreach (var vm in _auctionViewModels)
{
vm.IsActive = true;
vm.IsPaused = true;
}
if (_auctionViewModels.Count > 0 && !_isAutomationActive)
{
_auctionMonitor.Start();
_isAutomationActive = true;
}
// ? Salva gli stati aggiornati su disco
SaveAuctions();
UpdateGlobalControlButtons();
if (sender != null) // Solo se chiamato dall'utente
{
Log("[OSSERVA] Tutte le aste seguite senza puntare", LogLevel.Warning);
}
}
catch (Exception ex)
{
Log($"[ERRORE] Pausa globale: {ex.Message}", LogLevel.Error);
}
}
private async void AddUrlButton_Click(object sender, RoutedEventArgs e)
{
var dialog = new AddAuctionSimpleDialog();
if (dialog.ShowDialog() == true)
{
var raw = dialog.AuctionId ?? string.Empty;
var parts = Regex.Split(raw, "[\r\n;]+|\\s+")
.Select(p => p.Trim())
.Where(p => !string.IsNullOrWhiteSpace(p))
.ToList();
if (parts.Count == 0)
{
MessageBox.Show("Nessun URL/ID valido trovato.", "Errore", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
var added = 0;
var skipped = new System.Collections.Generic.List<string>();
foreach (var part in parts)
{
try
{
var input = part;
string? aid = null;
if (input.Contains("bidoo.com") || input.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
aid = ExtractAuctionId(input);
}
else
{
aid = input;
}
if (!string.IsNullOrEmpty(aid) && _auctionViewModels.Any(a => a.AuctionId == aid))
{
skipped.Add(part + " (duplicato)");
continue;
}
await AddAuctionById(input);
added++;
}
catch (Exception ex)
{
skipped.Add(part + " (errore: " + ex.Message + ")");
}
}
UpdateGlobalControlButtons();
var summary = $"Aggiunte: {added}. Skipped: {skipped.Count}.";
if (skipped.Count > 0)
summary += "\nDettagli: " + string.Join("; ", skipped.Take(10));
MessageBox.Show(summary, "Aggiunta aste", MessageBoxButton.OK, MessageBoxImage.Information);
// ? RIMOSSO: Retry automatico ora avviene alla selezione on-demand
// Le aste con nome generico vengono aggiornate automaticamente quando l'utente le seleziona
}
}
private void RemoveUrlButton_Click(object sender, RoutedEventArgs e)
{
if (_selectedAuction == null)
{
MessageBox.Show("Seleziona un'asta dalla griglia", "Nessuna Selezione", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
var auctionName = _selectedAuction.Name;
var auctionId = _selectedAuction.AuctionId;
// Salva l'indice corrente prima di rimuovere
var currentIndex = _auctionViewModels.IndexOf(_selectedAuction);
// Conferma rimozione
var result = MessageBox.Show(
$"Rimuovere l'asta dal monitoraggio?\n\n{auctionName}\n(ID: {auctionId})\n\nL'asta verr eliminata dalla lista e non sar pi monitorata.",
"Conferma Rimozione",
MessageBoxButton.YesNo,
MessageBoxImage.Question);
if (result != MessageBoxResult.Yes)
{
Log($"[REMOVE] Rimozione annullata: {auctionName}", LogLevel.Info);
return;
}
try
{
// Rimuove dal monitor
_auctionMonitor.RemoveAuction(auctionId);
// Rimuove dal ViewModel
_auctionViewModels.Remove(_selectedAuction);
// Salva modifiche
SaveAuctions();
UpdateTotalCount();
UpdateGlobalControlButtons();
Log($"[REMOVE] Asta rimossa: {auctionName} (ID: {auctionId})", LogLevel.Success);
// ? NUOVO: Sposta il focus sulla riga successiva
if (_auctionViewModels.Count > 0)
{
// Se c' ancora almeno un'asta nella lista
int newIndex;
if (currentIndex >= _auctionViewModels.Count)
{
// L'asta rimossa era l'ultima, seleziona la nuova ultima
newIndex = _auctionViewModels.Count - 1;
}
else
{
// Seleziona l'asta che ora si trova nella stessa posizione
newIndex = currentIndex;
}
// Seleziona l'asta
MultiAuctionsGrid.SelectedIndex = newIndex;
_selectedAuction = _auctionViewModels[newIndex];
// ? FIX: Salva il nome della NUOVA asta selezionata per il log
var newAuctionName = _selectedAuction?.Name ?? "Sconosciuta";
// Forza il focus sulla griglia dopo un breve delay per permettere alla UI di aggiornarsi
Dispatcher.BeginInvoke(new Action(() =>
{
MultiAuctionsGrid.Focus();
// Scroll fino alla riga selezionata per assicurarsi che sia visibile
if (MultiAuctionsGrid.SelectedItem != null)
{
MultiAuctionsGrid.ScrollIntoView(MultiAuctionsGrid.SelectedItem);
}
// ? FIX: Usa la variabile locale invece di _selectedAuction.Name
Log($"[FOCUS] Focus spostato su: {newAuctionName}", LogLevel.Info);
}), System.Windows.Threading.DispatcherPriority.Background);
}
else
{
// Nessuna asta rimasta, reset selezione
_selectedAuction = null;
Log($"[REMOVE] Nessuna asta rimasta nella lista", LogLevel.Info);
}
}
catch (Exception ex)
{
Log($"[ERROR] Errore rimozione asta: {ex.Message}", LogLevel.Error);
MessageBox.Show($"Errore durante la rimozione:\n{ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void RemoveAllButton_Click(object sender, RoutedEventArgs e)
{
if (_auctionViewModels.Count == 0)
{
MessageBox.Show("Non ci sono aste da rimuovere", "Lista Vuota", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
var count = _auctionViewModels.Count;
// Conferma rimozione
var result = MessageBox.Show(
$"Rimuovere TUTTE le aste dal monitoraggio?\n\nSono presenti {count} aste monitorate.\n\nTutte le aste verranno eliminate dalla lista e non saranno pi monitorate.",
"Conferma Rimozione Totale",
MessageBoxButton.YesNo,
MessageBoxImage.Warning);
if (result != MessageBoxResult.Yes)
{
Log($"[REMOVE ALL] Rimozione annullata", LogLevel.Info);
return;
}
try
{
// Ferma il monitoraggio se attivo
if (_isAutomationActive)
{
_auctionMonitor.Stop();
_isAutomationActive = false;
Log("[STOP] Monitoraggio fermato prima della rimozione totale", LogLevel.Info);
}
// Rimuove tutte le aste dal monitor e dal ViewModel
var auctionsToRemove = _auctionViewModels.ToList(); // Copia per evitare modifiche durante iterazione
foreach (var auction in auctionsToRemove)
{
_auctionMonitor.RemoveAuction(auction.AuctionId);
}
// Pulisci la lista ViewModel
_auctionViewModels.Clear();
// Resetta selezione
_selectedAuction = null;
// Salva modifiche
SaveAuctions();
UpdateTotalCount();
UpdateGlobalControlButtons();
Log($"[REMOVE ALL] Tutte le aste rimosse: {count} aste eliminate", LogLevel.Success);
MessageBox.Show($"Tutte le {count} aste sono state rimosse dal monitoraggio.", "Rimozione Completata", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
Log($"[ERROR] Errore rimozione totale: {ex.Message}", LogLevel.Error);
MessageBox.Show($"Errore durante la rimozione delle aste: {ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private async void CopyAuctionUrlButton_Click(object sender, RoutedEventArgs e)
{
if (_selectedAuction == null)
{
MessageBox.Show(
"Seleziona un'asta dalla griglia prima di copiare l'URL.",
"Nessuna Asta Selezionata",
MessageBoxButton.OK,
MessageBoxImage.Information);
Log("[INFO] Tentativo di copia URL senza asta selezionata", LogLevel.Info);
return;
}
var url = _selectedAuction.AuctionInfo.OriginalUrl;
if (string.IsNullOrEmpty(url))
url = $"https://it.bidoo.com/auction.php?a=asta_{_selectedAuction.AuctionId}";
// Tenta di copiare con retry mechanism
const int maxAttempts = 3;
const int delayMs = 50;
for (int attempt = 1; attempt <= maxAttempts; attempt++)
{
try
{
Clipboard.SetText(url);
Log("URL copiato negli appunti", LogLevel.Success);
return; // Successo, esci
}
catch (System.Runtime.InteropServices.COMException ex) when (ex.ErrorCode == unchecked((int)0x800401D0)) // CLIPBRD_E_CANT_OPEN
{
if (attempt < maxAttempts)
{
// Clipboard occupato, riprova dopo un breve delay
System.Threading.Thread.Sleep(delayMs);
continue;
}
// Ultimo tentativo fallito
Log($"[WARN] Clipboard temporaneamente occupato. Il testo potrebbe essere stato copiato.", LogLevel.Warning);
return;
}
catch (Exception ex)
{
// Altri errori
Log($"[ERRORE] Impossibile copiare URL: {ex.Message}", LogLevel.Error);
return;
}
}
}
private void OpenAuctionInternalButton_Click(object sender, RoutedEventArgs e)
{
if (_selectedAuction == null)
{
MessageBox.Show("Seleziona un'asta dalla griglia", "Nessuna Selezione", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
try
{
var url = _selectedAuction.AuctionInfo.OriginalUrl;
if (string.IsNullOrEmpty(url))
url = $"https://it.bidoo.com/auction.php?a=asta_{_selectedAuction.AuctionId}";
// Naviga alla scheda Browser, in modalita' browser (non catalogo)
TabBrowser.IsChecked = true;
Browser.ShowBrowser();
// Naviga all'URL
if (EmbeddedWebView?.CoreWebView2 != null)
{
EmbeddedWebView.CoreWebView2.Navigate(url);
Log($"[BROWSER] Apertura asta nel browser interno: {_selectedAuction.Name}", LogLevel.Info);
}
else
{
Log($"[WARN] Browser interno non ancora inizializzato", LogLevel.Warning);
MessageBox.Show("Il browser interno non ancora pronto.\nRiprova tra qualche secondo.", "Browser", MessageBoxButton.OK, MessageBoxImage.Warning);
}
}
catch (Exception ex)
{
Log($"[ERRORE] Apertura nel browser interno: {ex.Message}", LogLevel.Error);
MessageBox.Show($"Errore durante l'apertura: {ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void OpenAuctionExternalButton_Click(object sender, RoutedEventArgs e)
{
if (_selectedAuction == null)
{
MessageBox.Show("Seleziona un'asta dalla griglia", "Nessuna Selezione", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
try
{
var url = _selectedAuction.AuctionInfo.OriginalUrl;
if (string.IsNullOrEmpty(url))
url = $"https://it.bidoo.com/auction.php?a=asta_{_selectedAuction.AuctionId}";
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
{
FileName = url,
UseShellExecute = true
});
Log($"[BROWSER] Apertura asta nel browser esterno: {_selectedAuction.Name}", LogLevel.Info);
}
catch (Exception ex)
{
Log($"[ERRORE] Apertura nel browser esterno: {ex.Message}", LogLevel.Error);
MessageBox.Show($"Errore durante l'apertura: {ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private async void RefreshProductInfoButton_Click(object sender, RoutedEventArgs e)
{
try
{
if (_selectedAuction == null)
{
MessageBox.Show("Seleziona un'asta dalla griglia", "Nessuna Selezione", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
}
var auction = _selectedAuction.AuctionInfo;
// Verifica che ci siano le info prodotto caricate
if (!auction.BuyNowPrice.HasValue)
{
MessageBox.Show(
"Informazioni prodotto non disponibili.\n\n" +
"Il sistema le sta caricando automaticamente.\n" +
"Riprova tra qualche secondo.",
"Info Prodotto Mancanti",
MessageBoxButton.OK,
MessageBoxImage.Information);
return;
}
// Feedback visivo
AuctionMonitor.RefreshProductInfoButton.IsEnabled = false;
// CALCOLA LIMITI SUGGERITI (CONSERVATIVI)
double buyNowPrice = auction.BuyNowPrice.Value;
double shippingCost = auction.ShippingCost ?? 0;
double totalValue = buyNowPrice + shippingCost;
// Max EUR = 40% del valore TOTALE (pi conservativo del 50%)
double suggestedMaxPrice = totalValue * 0.40;
suggestedMaxPrice = Math.Round(suggestedMaxPrice, 2);
// CALCOLA MAX CLICKS (numero massimo puntate conservativo)
// Formula: (Valore Totale - Max EUR) / 0.20 per puntata
// Poi riduciamo del 20% per maggiore margine di sicurezza
int maxClicksTheoretical = (int)Math.Floor((totalValue - suggestedMaxPrice) / 0.20);
int suggestedMaxClicks = (int)Math.Floor(maxClicksTheoretical * 0.80); // 80% del teorico
// Minimo 10 puntate per dare comunque una chance
if (suggestedMaxClicks < 10) suggestedMaxClicks = 10;
Log($"[LIMITI] Valore={buyNowPrice:F2} + Extra={shippingCost:F2} = Tot={totalValue:F2} ? MaxEUR={suggestedMaxPrice:F2} (40%), MaxClicks={suggestedMaxClicks}", LogLevel.Info);
// CHIEDI CONFERMA
var result = MessageBox.Show(
$"Limiti suggeriti (conservativi):\n\n" +
$"Max EUR: {suggestedMaxPrice:F2}\n" +
$"Max Clicks: {suggestedMaxClicks}\n\n" +
$"Applicare questi valori?",
"Conferma Limiti",
MessageBoxButton.YesNo,
MessageBoxImage.Question);
if (result != MessageBoxResult.Yes)
{
Log($"[LIMITI] Annullato dall'utente", LogLevel.Info);
return;
}
// APPLICA I LIMITI
_selectedAuction.MaxPrice = suggestedMaxPrice;
_selectedAuction.MaxClicks = suggestedMaxClicks;
// AGGIORNA UI
UpdateAuctionSettingsDisplay(_selectedAuction);
// SALVA
SaveAuctions();
Log($"[LIMITI] Applicati: MaxEUR={suggestedMaxPrice:F2}, MaxClicks={suggestedMaxClicks}", LogLevel.Success);
}
catch (Exception ex)
{
Log($"[ERRORE] Calcolo limiti: {ex.Message}", LogLevel.Error);
MessageBox.Show($"Errore durante il calcolo dei limiti:\n\n{ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
AuctionMonitor.RefreshProductInfoButton.IsEnabled = true;
}
}
/// <summary>
/// Sposta l'asta selezionata verso l'alto nell'elenco
/// </summary>
private void MoveUpButton_Click(object sender, RoutedEventArgs e)
{
if (_selectedAuction == null)
{
MessageBox.Show("Seleziona un'asta dalla griglia", "Nessuna Selezione", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
try
{
var currentIndex = _auctionViewModels.IndexOf(_selectedAuction);
if (currentIndex <= 0)
{
// Gi in cima o non trovata
Log($"[MOVE] L'asta gi in cima alla lista", LogLevel.Info);
return;
}
// Sposta l'elemento verso l'alto
_auctionViewModels.Move(currentIndex, currentIndex - 1);
// Mantieni la selezione
MultiAuctionsGrid.SelectedItem = _selectedAuction;
MultiAuctionsGrid.ScrollIntoView(_selectedAuction);
// Salva il nuovo ordine
SaveAuctions();
Log($"[MOVE UP] Asta spostata verso l'alto: {_selectedAuction.Name}", LogLevel.Success);
}
catch (Exception ex)
{
Log($"[ERRORE] Spostamento asta verso l'alto: {ex.Message}", LogLevel.Error);
MessageBox.Show($"Errore durante lo spostamento: {ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
/// <summary>
/// Sposta l'asta selezionata verso il basso nell'elenco
/// </summary>
private void MoveDownButton_Click(object sender, RoutedEventArgs e)
{
if (_selectedAuction == null)
{
MessageBox.Show("Seleziona un'asta dalla griglia", "Nessuna Selezione", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
try
{
var currentIndex = _auctionViewModels.IndexOf(_selectedAuction);
if (currentIndex < 0 || currentIndex >= _auctionViewModels.Count - 1)
{
// Gi in fondo o non trovata
Log($"[MOVE] L'asta gi in fondo alla lista", LogLevel.Info);
return;
}
// Sposta l'elemento verso il basso
_auctionViewModels.Move(currentIndex, currentIndex + 1);
// Mantieni la selezione
MultiAuctionsGrid.SelectedItem = _selectedAuction;
MultiAuctionsGrid.ScrollIntoView(_selectedAuction);
// Salva il nuovo ordine
SaveAuctions();
Log($"[MOVE DOWN] Asta spostata verso il basso: {_selectedAuction.Name}", LogLevel.Success);
}
catch (Exception ex)
{
Log($"[ERRORE] Spostamento asta verso il basso: {ex.Message}", LogLevel.Error);
MessageBox.Show($"Errore durante lo spostamento: {ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
}