Aggiornamento alla versione 4.0.0

* Aggiunto `BooleanToOpacityConverter` per gestire opacità dinamica.
* Introdotto nuovo sistema di timing con `BidBeforeDeadlineMs`.
* Aggiunta opzione `CheckAuctionOpenBeforeBid` per maggiore sicurezza.
* Implementato polling adattivo (10ms-1000ms) e cooldown di 800ms.
* Migliorata gestione pulsanti globali con supporto `AUTO-START`/`AUTO-STOP`.
* Fix per il tasto `Canc` e focus automatico sul `DataGrid`.
* Fix per avvio singola asta senza necessità di "Avvia Tutti".
* Aggiornati formati CSV/JSON/XML con nuovi campi.
* Migliorata gestione cookie con endpoint unico `buy_bids.php`.
* Miglioramenti UI/UX: tooltip, formattazione prezzi, feedback visivo.
* Aggiornata documentazione e changelog per la versione 4.0.0.
This commit is contained in:
Alberto Balbo
2025-11-19 18:43:40 +01:00
parent 6036896f7d
commit f017ec0364
27 changed files with 2281 additions and 1069 deletions

View File

@@ -3,11 +3,15 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:util="clr-namespace:AutoBidder.Utilities"
mc:Ignorable="d"
d:DesignHeight="800" d:DesignWidth="1200"
Background="#1E1E1E">
<UserControl.Resources>
<!-- Converters -->
<util:BooleanToOpacityConverter x:Key="BoolToOpacity"/>
<!-- Rounded Button Style -->
<Style x:Key="RoundedButton" TargetType="Button">
<Setter Property="Template">
@@ -51,43 +55,53 @@
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Header -->
<Border Grid.Row="0" Background="#2D2D30" Padding="15" BorderBrush="#3E3E42" BorderThickness="0,0,0,1">
<!-- Header - COMPATTO SU 2 RIGHE -->
<Border Grid.Row="0" Background="#2D2D30" Padding="15,10" BorderBrush="#3E3E42" BorderThickness="0,0,0,1">
<Grid>
<!-- Info Utente (Left) -->
<StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Left">
<TextBlock x:Name="UsernameText"
Text="Utente: Non configurato"
Foreground="#00D800"
FontSize="13"
FontWeight="SemiBold"
Margin="0,0,0,5"/>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="0,0,0,3">
<!-- Riga 1: Puntate + Credito -->
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,0,0,5">
<TextBlock Text="Puntate: "
Foreground="#CCCCCC"
FontSize="12"/>
Foreground="#999999"
FontSize="13"
Margin="0,0,5,0"/>
<TextBlock x:Name="RemainingBidsText"
Text="0"
Foreground="#CCCCCC"
FontSize="12"
Foreground="#00D800"
FontSize="13"
FontWeight="Bold"
Margin="0,0,25,0"/>
<TextBlock Text="Credito Shop: "
Foreground="#999999"
FontSize="13"
Margin="0,0,5,0"/>
<TextBlock x:Name="ShopCreditText"
Text="EUR 0.00"
Foreground="#00D800"
FontSize="13"
FontWeight="Bold"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<!-- Riga 2: Aste vinte -->
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock Text="Aste vinte da confermare: "
Foreground="#FFB700"
FontSize="12"/>
Foreground="#999999"
FontSize="12"
Margin="0,0,5,0"/>
<TextBlock x:Name="BannerAsteDaRiscattare"
Text="0"
Foreground="#FFB700"
FontSize="12"
FontWeight="Bold"/>
</StackPanel>
</StackPanel>
<!-- Control Buttons (Right) -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
<!-- Control Buttons (Right) - Su entrambe le righe -->
<StackPanel Grid.Row="0" Grid.RowSpan="2" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
<Button x:Name="StartButton"
Content="Avvia Tutti"
Background="#00D800"
@@ -197,7 +211,9 @@
AlternatingRowBackground="#252526"
BorderThickness="0"
SelectionChanged="MultiAuctionsGrid_SelectionChanged"
KeyDown="MultiAuctionsGrid_KeyDown">
PreviewKeyDown="MultiAuctionsGrid_PreviewKeyDown"
Focusable="True"
FocusVisualStyle="{x:Null}">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="DataGridColumnHeader">
<Setter Property="Background" Value="#2D2D30"/>
@@ -239,24 +255,40 @@
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button Content="Avvia"
Command="{Binding DataContext.GridStartCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding}"
IsEnabled="{Binding CanStart}"
Opacity="{Binding CanStart, Converter={StaticResource BoolToOpacity}}"
Background="#00D800"
Style="{StaticResource SmallRoundedButton}"
Padding="6,3"
FontSize="10"
Margin="1"/>
<Button Content="Pausa"
Command="{Binding DataContext.GridPauseCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding}"
IsEnabled="{Binding CanPause}"
Opacity="{Binding CanPause, Converter={StaticResource BoolToOpacity}}"
Background="#FFB700"
Style="{StaticResource SmallRoundedButton}"
Padding="6,3"
FontSize="10"
Margin="1"/>
<Button Content="Ferma"
Command="{Binding DataContext.GridStopCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding}"
IsEnabled="{Binding CanStop}"
Opacity="{Binding CanStop, Converter={StaticResource BoolToOpacity}}"
Background="#E81123"
Style="{StaticResource SmallRoundedButton}"
Padding="6,3"
FontSize="10"
Margin="1"/>
<Button Content="Punta"
Command="{Binding DataContext.GridBidCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
CommandParameter="{Binding}"
IsEnabled="{Binding CanBid}"
Opacity="{Binding CanBid, Converter={StaticResource BoolToOpacity}}"
Background="#9B4F96"
Style="{StaticResource SmallRoundedButton}"
Padding="6,3"
@@ -399,7 +431,7 @@
FontSize="10"/>
</UniformGrid>
<!-- Settings Grid - Campi piu larghi (100px) -->
<!-- Settings Grid - Campi aggiornati -->
<Grid Margin="0,0,0,8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
@@ -412,25 +444,33 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Row 1 -->
<TextBlock Grid.Row="0" Grid.Column="0" Text="Timer (s):" Foreground="#CCCCCC" FontSize="11" Margin="0,6" VerticalAlignment="Center"/>
<TextBox Grid.Row="0" Grid.Column="1" x:Name="SelectedTimerClick" Background="#1E1E1E" Foreground="White" BorderBrush="#3E3E42" Padding="6" Margin="5,6" FontSize="11" TextChanged="SelectedTimerClick_TextChanged"/>
<!-- Row 1: Anticipo ms -->
<TextBlock Grid.Row="0" Grid.Column="0" Text="Anticipo (ms):" Foreground="#CCCCCC" FontSize="11" Margin="0,6" VerticalAlignment="Center" ToolTip="Millisecondi prima della scadenza per puntare"/>
<TextBox Grid.Row="0" Grid.Column="1" x:Name="SelectedBidBeforeDeadlineMs" Background="#1E1E1E" Foreground="White" BorderBrush="#3E3E42" Padding="6" Margin="5,6" FontSize="11" TextChanged="SelectedBidBeforeDeadlineMs_TextChanged"/>
<TextBlock Grid.Row="0" Grid.Column="3" Text="Delay (ms):" Foreground="#CCCCCC" FontSize="11" Margin="0,6" VerticalAlignment="Center"/>
<TextBox Grid.Row="0" Grid.Column="4" x:Name="SelectedDelayMs" Background="#1E1E1E" Foreground="White" BorderBrush="#3E3E42" Padding="6" Margin="5,6" FontSize="11" TextChanged="SelectedDelayMs_TextChanged"/>
<TextBlock Grid.Row="0" Grid.Column="3" Text="Min EUR:" Foreground="#CCCCCC" FontSize="11" Margin="0,6" VerticalAlignment="Center"/>
<TextBox Grid.Row="0" Grid.Column="4" x:Name="SelectedMinPrice" Background="#1E1E1E" Foreground="White" BorderBrush="#3E3E42" Padding="6" Margin="5,6" FontSize="11" TextChanged="SelectedMinPrice_TextChanged"/>
<!-- Row 2 -->
<TextBlock Grid.Row="1" Grid.Column="0" Text="Min EUR:" Foreground="#CCCCCC" FontSize="11" Margin="0,6" VerticalAlignment="Center"/>
<TextBox Grid.Row="1" Grid.Column="1" x:Name="SelectedMinPrice" Background="#1E1E1E" Foreground="White" BorderBrush="#3E3E42" Padding="6" Margin="5,6" FontSize="11" TextChanged="SelectedMinPrice_TextChanged"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Max EUR:" Foreground="#CCCCCC" FontSize="11" Margin="0,6" VerticalAlignment="Center"/>
<TextBox Grid.Row="1" Grid.Column="1" x:Name="SelectedMaxPrice" Background="#1E1E1E" Foreground="White" BorderBrush="#3E3E42" Padding="6" Margin="5,6" FontSize="11" TextChanged="SelectedMaxPrice_TextChanged"/>
<TextBlock Grid.Row="1" Grid.Column="3" Text="Max EUR:" Foreground="#CCCCCC" FontSize="11" Margin="0,6" VerticalAlignment="Center"/>
<TextBox Grid.Row="1" Grid.Column="4" x:Name="SelectedMaxPrice" Background="#1E1E1E" Foreground="White" BorderBrush="#3E3E42" Padding="6" Margin="5,6" FontSize="11" TextChanged="SelectedMaxPrice_TextChanged"/>
<TextBlock Grid.Row="1" Grid.Column="3" Text="Max Clicks:" Foreground="#CCCCCC" FontSize="11" Margin="0,6" VerticalAlignment="Center"/>
<TextBox Grid.Row="1" Grid.Column="4" x:Name="SelectedMaxClicks" Background="#1E1E1E" Foreground="White" BorderBrush="#3E3E42" Padding="6" Margin="5,6" FontSize="11" TextChanged="SelectedMaxClicks_TextChanged"/>
<!-- Row 3 -->
<TextBlock Grid.Row="2" Grid.Column="0" Text="Max Clicks:" Foreground="#CCCCCC" FontSize="11" Margin="0,6" VerticalAlignment="Center"/>
<TextBox Grid.Row="2" Grid.Column="1" x:Name="SelectedMaxClicks" Background="#1E1E1E" Foreground="White" BorderBrush="#3E3E42" Padding="6" Margin="5,6" FontSize="11" TextChanged="SelectedMaxClicks_TextChanged"/>
<!-- Row 3: Check Auction Open -->
<CheckBox Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="5"
x:Name="SelectedCheckAuctionOpen"
Content="Verifica stato asta prima di puntare"
Foreground="#CCCCCC"
FontSize="11"
Margin="0,10,0,0"
Checked="SelectedCheckAuctionOpen_Changed"
Unchecked="SelectedCheckAuctionOpen_Changed"
ToolTip="Aggiunge una chiamata API extra per verificare che l'asta sia ancora aperta"/>
</Grid>
</StackPanel>
</ScrollViewer>
@@ -562,5 +602,17 @@
</Border>
</Grid>
</Grid>
<!-- Hidden user info panel for compatibility -->
<Border x:Name="UserInfoPanel"
Visibility="Collapsed">
<StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock x:Name="UsernameText" Text=""/>
<TextBlock x:Name="UserIdText" Text=""/>
</StackPanel>
<TextBlock x:Name="UserEmailText" Text=""/>
</StackPanel>
</Border>
</Grid>
</UserControl>

View File

@@ -14,6 +14,43 @@ namespace AutoBidder.Controls
InitializeComponent();
}
// Dependency Properties per Commands (da bindare al MainWindow)
public static readonly DependencyProperty GridStartCommandProperty =
DependencyProperty.Register(nameof(GridStartCommand), typeof(ICommand), typeof(AuctionMonitorControl));
public static readonly DependencyProperty GridPauseCommandProperty =
DependencyProperty.Register(nameof(GridPauseCommand), typeof(ICommand), typeof(AuctionMonitorControl));
public static readonly DependencyProperty GridStopCommandProperty =
DependencyProperty.Register(nameof(GridStopCommand), typeof(ICommand), typeof(AuctionMonitorControl));
public static readonly DependencyProperty GridBidCommandProperty =
DependencyProperty.Register(nameof(GridBidCommand), typeof(ICommand), typeof(AuctionMonitorControl));
public ICommand? GridStartCommand
{
get => (ICommand?)GetValue(GridStartCommandProperty);
set => SetValue(GridStartCommandProperty, value);
}
public ICommand? GridPauseCommand
{
get => (ICommand?)GetValue(GridPauseCommandProperty);
set => SetValue(GridPauseCommandProperty, value);
}
public ICommand? GridStopCommand
{
get => (ICommand?)GetValue(GridStopCommandProperty);
set => SetValue(GridStopCommandProperty, value);
}
public ICommand? GridBidCommand
{
get => (ICommand?)GetValue(GridBidCommandProperty);
set => SetValue(GridBidCommandProperty, value);
}
// Event handlers - these will bubble up to MainWindow
private void StartButton_Click(object sender, RoutedEventArgs e)
{
@@ -47,23 +84,38 @@ namespace AutoBidder.Controls
private void MultiAuctionsGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Forza il focus sul DataGrid quando viene selezionata una riga
// Questo assicura che il tasto Canc venga catturato
if (sender is DataGrid grid && grid.SelectedItem != null)
{
// Attende che il rendering sia completo prima di dare il focus
grid.Dispatcher.BeginInvoke(new Action(() =>
{
if (!grid.IsFocused)
{
grid.Focus();
System.Diagnostics.Debug.WriteLine("[FOCUS] DataGrid ora ha il focus keyboard");
}
}), System.Windows.Threading.DispatcherPriority.Background);
}
RaiseEvent(new RoutedEventArgs(AuctionSelectionChangedEvent, this));
}
private void MultiAuctionsGrid_KeyDown(object sender, KeyEventArgs e)
// Usato PreviewKeyDown invece di KeyDown per catturare l'evento prima che venga consumato
private void MultiAuctionsGrid_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Delete && MultiAuctionsGrid.SelectedItem != null)
{
var result = MessageBox.Show(
"Sei sicuro di voler eliminare l'asta selezionata?",
"Conferma Eliminazione",
MessageBoxButton.YesNo,
MessageBoxImage.Question);
System.Diagnostics.Debug.WriteLine("[DELETE KEY] Tasto Canc premuto su asta selezionata");
if (result == MessageBoxResult.Yes)
{
// Lancia direttamente l'evento senza chiedere conferma
// La conferma verrà mostrata dal gestore RemoveUrlButton_Click
System.Diagnostics.Debug.WriteLine("[DELETE KEY] Lancio evento RemoveUrlClicked");
RaiseEvent(new RoutedEventArgs(RemoveUrlClickedEvent, this));
}
// Previeni che l'evento venga gestito da altri controlli
e.Handled = true;
}
}
@@ -92,14 +144,14 @@ namespace AutoBidder.Controls
RaiseEvent(new RoutedEventArgs(ClearGlobalLogClickedEvent, this));
}
private void SelectedTimerClick_TextChanged(object sender, TextChangedEventArgs e)
private void SelectedBidBeforeDeadlineMs_TextChanged(object sender, TextChangedEventArgs e)
{
RaiseEvent(new RoutedEventArgs(TimerClickChangedEvent, this));
RaiseEvent(new RoutedEventArgs(BidBeforeDeadlineMsChangedEvent, this));
}
private void SelectedDelayMs_TextChanged(object sender, TextChangedEventArgs e)
private void SelectedCheckAuctionOpen_Changed(object sender, RoutedEventArgs e)
{
RaiseEvent(new RoutedEventArgs(DelayMsChangedEvent, this));
RaiseEvent(new RoutedEventArgs(CheckAuctionOpenChangedEvent, this));
}
private void SelectedMinPrice_TextChanged(object sender, TextChangedEventArgs e)
@@ -154,11 +206,11 @@ namespace AutoBidder.Controls
public static readonly RoutedEvent ClearGlobalLogClickedEvent = EventManager.RegisterRoutedEvent(
"ClearGlobalLogClicked", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(AuctionMonitorControl));
public static readonly RoutedEvent TimerClickChangedEvent = EventManager.RegisterRoutedEvent(
"TimerClickChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(AuctionMonitorControl));
public static readonly RoutedEvent BidBeforeDeadlineMsChangedEvent = EventManager.RegisterRoutedEvent(
"BidBeforeDeadlineMsChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(AuctionMonitorControl));
public static readonly RoutedEvent DelayMsChangedEvent = EventManager.RegisterRoutedEvent(
"DelayMsChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(AuctionMonitorControl));
public static readonly RoutedEvent CheckAuctionOpenChangedEvent = EventManager.RegisterRoutedEvent(
"CheckAuctionOpenChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(AuctionMonitorControl));
public static readonly RoutedEvent MinPriceChangedEvent = EventManager.RegisterRoutedEvent(
"MinPriceChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(AuctionMonitorControl));
@@ -240,5 +292,35 @@ namespace AutoBidder.Controls
add { AddHandler(ClearGlobalLogClickedEvent, value); }
remove { RemoveHandler(ClearGlobalLogClickedEvent, value); }
}
public event RoutedEventHandler BidBeforeDeadlineMsChanged
{
add { AddHandler(BidBeforeDeadlineMsChangedEvent, value); }
remove { RemoveHandler(BidBeforeDeadlineMsChangedEvent, value); }
}
public event RoutedEventHandler CheckAuctionOpenChanged
{
add { AddHandler(CheckAuctionOpenChangedEvent, value); }
remove { RemoveHandler(CheckAuctionOpenChangedEvent, value); }
}
public event RoutedEventHandler MinPriceChanged
{
add { AddHandler(MinPriceChangedEvent, value); }
remove { RemoveHandler(MinPriceChangedEvent, value); }
}
public event RoutedEventHandler MaxPriceChanged
{
add { AddHandler(MaxPriceChangedEvent, value); }
remove { RemoveHandler(MaxPriceChangedEvent, value); }
}
public event RoutedEventHandler MaxClicksChanged
{
add { AddHandler(MaxClicksChangedEvent, value); }
remove { RemoveHandler(MaxClicksChangedEvent, value); }
}
}
}

View File

@@ -104,24 +104,11 @@
Style="{StaticResource FieldLabel}"/>
<TextBox x:Name="SettingsCookieTextBox"
Height="100"
Height="150"
TextWrapping="Wrap"
AcceptsReturn="True"
VerticalScrollBarVisibility="Auto"
Margin="0,0,0,10"/>
<StackPanel Orientation="Horizontal" Margin="0,0,0,15">
<Button Content="Importa dal Browser"
Background="#007ACC"
Style="{StaticResource ModernButton}"
Margin="0,0,10,0"
Click="ImportCookieFromBrowserButton_Click"/>
<Button Content="Cancella"
Background="#3E3E42"
Style="{StaticResource ModernButton}"
Click="CancelCookieButton_Click"/>
</StackPanel>
Margin="0,0,0,15"/>
<!-- Info Box -->
<Border Style="{StaticResource InfoBox}">
@@ -237,7 +224,7 @@
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="250"/>
<ColumnDefinition Width="150"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
@@ -248,11 +235,11 @@
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Timer Click (secondi)" Foreground="#CCCCCC" Margin="0,10" VerticalAlignment="Center"/>
<TextBox Grid.Row="0" Grid.Column="1" x:Name="DefaultTimerClick" Text="0" Margin="10,10"/>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Anticipo Puntata (millisecondi)" Foreground="#CCCCCC" Margin="0,10" VerticalAlignment="Center" ToolTip="Millisecondi prima della scadenza per puntare"/>
<TextBox Grid.Row="0" Grid.Column="1" x:Name="DefaultBidBeforeDeadlineMs" Text="200" Margin="10,10"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Delay (millisecondi)" Foreground="#CCCCCC" Margin="0,10" VerticalAlignment="Center"/>
<TextBox Grid.Row="1" Grid.Column="1" x:Name="DefaultDelayMs" Text="50" Margin="10,10"/>
<TextBlock Grid.Row="1" Grid.Column="0" Text="Verifica Stato Prima di Puntare" Foreground="#CCCCCC" Margin="0,10" VerticalAlignment="Center" ToolTip="Controlla che l'asta sia ancora aperta prima di puntare"/>
<CheckBox Grid.Row="1" Grid.Column="1" x:Name="DefaultCheckAuctionOpen" Margin="10,10" VerticalAlignment="Center"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Prezzo Minimo (€)" Foreground="#CCCCCC" Margin="0,10" VerticalAlignment="Center"/>
<TextBox Grid.Row="2" Grid.Column="1" x:Name="DefaultMinPrice" Text="0" Margin="10,10"/>

View File

@@ -13,9 +13,9 @@ namespace AutoBidder.Controls
InitializeComponent();
}
// Proprietà pubbliche per accesso da MainWindow
public TextBox DefaultTimerClickTextBox => DefaultTimerClick;
public TextBox DefaultDelayMsTextBox => DefaultDelayMs;
// Proprietà pubbliche per accesso da MainWindow (AGGIORNATE)
public TextBox DefaultBidBeforeDeadlineMsTextBox => DefaultBidBeforeDeadlineMs;
public CheckBox DefaultCheckAuctionOpenCheckBox => DefaultCheckAuctionOpen;
public TextBox DefaultMinPriceTextBox => DefaultMinPrice;
public TextBox DefaultMaxPriceTextBox => DefaultMaxPrice;
public TextBox DefaultMaxClicksTextBox => DefaultMaxClicks;
@@ -61,13 +61,37 @@ namespace AutoBidder.Controls
RaiseEvent(new RoutedEventArgs(CancelDefaultsClickedEvent, this));
}
// Nuovi eventi unificati
// Nuovo evento unificato - SALVA TUTTE LE IMPOSTAZIONI
private void SaveAllSettings_Click(object sender, RoutedEventArgs e)
{
// Salva tutte le impostazioni in sequenza
try
{
// 1. Salva cookie (se presente)
RaiseEvent(new RoutedEventArgs(SaveCookieClickedEvent, this));
// 2. Salva impostazioni export
RaiseEvent(new RoutedEventArgs(SaveSettingsClickedEvent, this));
// 3. Salva impostazioni predefinite aste
RaiseEvent(new RoutedEventArgs(SaveDefaultsClickedEvent, this));
// UNICO MessageBox di conferma
MessageBox.Show(
"Tutte le impostazioni sono state salvate con successo.\n\nLe nuove impostazioni verranno applicate alle aste future.",
"Impostazioni Salvate",
MessageBoxButton.OK,
MessageBoxImage.Information
);
}
catch (System.Exception ex)
{
MessageBox.Show(
"Errore durante il salvataggio: " + ex.Message,
"Errore",
MessageBoxButton.OK,
MessageBoxImage.Error
);
}
}
private void CancelAllSettings_Click(object sender, RoutedEventArgs e)

View File

@@ -52,7 +52,6 @@ namespace AutoBidder
}
}
catch { }
// Note: Progress bar rimosso con refactoring Statistics
}
private async void ExportAllButton_Click(object sender, RoutedEventArgs e)
@@ -182,6 +181,7 @@ namespace AutoBidder
if (chosenExt.Equals(".json", StringComparison.OrdinalIgnoreCase))
{
// JSON EXPORT - AGGIORNATO
var obj = new
{
AuctionId = a.AuctionId,
@@ -189,8 +189,8 @@ namespace AutoBidder
OriginalUrl = a.OriginalUrl,
MinPrice = a.MinPrice,
MaxPrice = a.MaxPrice,
TimerClick = a.TimerClick,
DelayMs = a.DelayMs,
BidBeforeDeadlineMs = a.BidBeforeDeadlineMs,
CheckAuctionOpenBeforeBid = a.CheckAuctionOpenBeforeBid,
IsActive = a.IsActive,
IsPaused = a.IsPaused,
BidHistory = a.BidHistory,
@@ -202,6 +202,7 @@ namespace AutoBidder
}
else if (chosenExt.Equals(".xml", StringComparison.OrdinalIgnoreCase))
{
// XML EXPORT - AGGIORNATO
var doc = new XDocument(
new XElement("AuctionExport",
new XElement("Metadata",
@@ -210,8 +211,8 @@ namespace AutoBidder
new XElement("OriginalUrl", a.OriginalUrl ?? string.Empty),
new XElement("MinPrice", a.MinPrice),
new XElement("MaxPrice", a.MaxPrice),
new XElement("TimerClick", a.TimerClick),
new XElement("DelayMs", a.DelayMs),
new XElement("BidBeforeDeadlineMs", a.BidBeforeDeadlineMs),
new XElement("CheckAuctionOpenBeforeBid", a.CheckAuctionOpenBeforeBid),
new XElement("IsActive", a.IsActive),
new XElement("IsPaused", a.IsPaused)
),
@@ -248,6 +249,7 @@ namespace AutoBidder
}
else
{
// CSV EXPORT - AGGIORNATO
using var sw = new StreamWriter(path, false, Encoding.UTF8);
sw.WriteLine("Field,Value");
sw.WriteLine($"AuctionId,{a.AuctionId}");
@@ -255,8 +257,8 @@ namespace AutoBidder
sw.WriteLine($"OriginalUrl,\"{EscapeCsv(a.OriginalUrl)}\"");
sw.WriteLine($"MinPrice,{a.MinPrice}");
sw.WriteLine($"MaxPrice,{a.MaxPrice}");
sw.WriteLine($"TimerClick,{a.TimerClick}");
sw.WriteLine($"DelayMs,{a.DelayMs}");
sw.WriteLine($"BidBeforeDeadlineMs,{a.BidBeforeDeadlineMs}");
sw.WriteLine($"CheckAuctionOpenBeforeBid,{a.CheckAuctionOpenBeforeBid}");
sw.WriteLine($"IsActive,{a.IsActive}");
sw.WriteLine($"IsPaused,{a.IsPaused}");
sw.WriteLine();

View File

@@ -10,44 +10,42 @@ namespace AutoBidder
/// </summary>
public partial class MainWindow
{
private void SaveCookieButton_Click(object sender, RoutedEventArgs e)
private async void SaveCookieButton_Click(object sender, RoutedEventArgs e)
{
try
{
var cookie = SettingsCookieTextBox.Text?.Trim();
if (string.IsNullOrEmpty(cookie))
{
MessageBox.Show(this, "Inserisci un cookie valido", "Errore", MessageBoxButton.OK, MessageBoxImage.Warning);
// Silenzioso - nessun MessageBox
return;
}
_auctionMonitor.InitializeSessionWithCookie(cookie, string.Empty);
var success = _auctionMonitor.UpdateUserInfoAsync().GetAwaiter().GetResult();
var success = await _auctionMonitor.UpdateUserInfoAsync();
var session = _auctionMonitor.GetSession();
if (success && session != null)
{
Services.SessionManager.SaveSession(session);
SetUserBanner(session.Username ?? string.Empty, session.RemainingBids);
UsernameText.Text = session.Username ?? string.Empty;
StartButton.IsEnabled = true;
Log($"[OK] Sessione salvata per: {session.Username}");
MessageBox.Show(this, $"Cookie valido!\nUtente: {session.Username}\nPuntate disponibili: {session.RemainingBids}", "Sessione Salvata", MessageBoxButton.OK, MessageBoxImage.Information);
// Rimosso MessageBox - verrà mostrato dal chiamante
}
else
{
Log($"[WARN] Cookie non valido o scaduto", LogLevel.Warn);
MessageBox.Show(this, "Cookie non valido o scaduto.\nVerifica che il cookie sia corretto.", "Errore Cookie", MessageBoxButton.OK, MessageBoxImage.Warning);
// Rimosso MessageBox - verrà mostrato dal chiamante se necessario
}
}
catch (Exception ex)
{
Log($"[ERRORE] Salvataggio cookie: {ex.Message}", LogLevel.Error);
MessageBox.Show(this, "Errore durante salvataggio cookie: " + ex.Message, "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void ImportCookieFromBrowserButton_Click(object sender, RoutedEventArgs e)
private async void ImportCookieFromBrowserButton_Click(object sender, RoutedEventArgs e)
{
try
{
@@ -57,14 +55,14 @@ namespace AutoBidder
return;
}
var cookies = EmbeddedWebView.CoreWebView2.CookieManager.GetCookiesAsync("https://it.bidoo.com").GetAwaiter().GetResult();
var cookies = await EmbeddedWebView.CoreWebView2.CookieManager.GetCookiesAsync("https://it.bidoo.com");
var stattrb = cookies.FirstOrDefault(c => c.Name == "__stattrb");
if (stattrb != null)
{
SettingsCookieTextBox.Text = stattrb.Value;
Log("[OK] Cookie importato dal browser");
MessageBox.Show(this, "Cookie importato con successo!\nClicca 'Salva Cookie' per confermare.", "Importa Cookie", MessageBoxButton.OK, MessageBoxImage.Information);
MessageBox.Show(this, "Cookie importato con successo!\nClicca 'Salva' per confermare.", "Importa Cookie", MessageBoxButton.OK, MessageBoxImage.Information);
}
else
{
@@ -110,27 +108,118 @@ namespace AutoBidder
SettingsManager.Save(s);
ExportPreferences.SaveLastExportExtension(s.LastExportExt);
MessageBox.Show(this, "Impostazioni salvate.", "Salva", MessageBoxButton.OK, MessageBoxImage.Information);
Log("[OK] Impostazioni export salvate", LogLevel.Success);
// Rimosso MessageBox - verrà mostrato dal chiamante
}
catch (Exception ex)
{
MessageBox.Show(this, "Errore salvataggio impostazioni: " + ex.Message, "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
Log($"[ERRORE] Salvataggio impostazioni export: {ex.Message}", LogLevel.Error);
}
}
private void CancelSettingsButton_Click(object sender, RoutedEventArgs e)
{
try
{
// Ricarica impostazioni export
LoadExportSettings();
// Ricarica cookie salvato
var session = Services.SessionManager.LoadSession();
if (session != null && !string.IsNullOrEmpty(session.CookieString))
{
SettingsCookieTextBox.Text = session.CookieString;
}
else
{
SettingsCookieTextBox.Text = string.Empty;
}
Log("[INFO] Impostazioni ripristinate", LogLevel.Info);
MessageBox.Show(this, "Impostazioni ripristinate alle ultime salvate.", "Annulla", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
Log($"[ERRORE] Ripristino impostazioni: {ex.Message}", LogLevel.Error);
MessageBox.Show(this, "Errore durante ripristino: " + ex.Message, "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
private void SaveDefaultsButton_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(this, "Funzionalità non ancora implementata", "Info", MessageBoxButton.OK, MessageBoxImage.Information);
try
{
// Salva impostazioni predefinite aste
if (int.TryParse(DefaultBidBeforeDeadlineMs.Text, out var bidMs) && bidMs >= 0 && bidMs <= 5000)
{
var settings = Utilities.SettingsManager.Load() ?? new Utilities.AppSettings();
settings.DefaultBidBeforeDeadlineMs = bidMs;
settings.DefaultCheckAuctionOpenBeforeBid = DefaultCheckAuctionOpen.IsChecked ?? false;
if (double.TryParse(DefaultMinPrice.Text.Replace(',', '.'), System.Globalization.NumberStyles.Any,
System.Globalization.CultureInfo.InvariantCulture, out var minPrice))
{
settings.DefaultMinPrice = minPrice;
}
if (double.TryParse(DefaultMaxPrice.Text.Replace(',', '.'), System.Globalization.NumberStyles.Any,
System.Globalization.CultureInfo.InvariantCulture, out var maxPrice))
{
settings.DefaultMaxPrice = maxPrice;
}
if (int.TryParse(DefaultMaxClicks.Text, out var maxClicks))
{
settings.DefaultMaxClicks = maxClicks;
}
Utilities.SettingsManager.Save(settings);
Log("[OK] Impostazioni predefinite aste salvate", LogLevel.Success);
// Rimosso MessageBox - verrà mostrato dal chiamante
}
else
{
Log("[WARN] Valore anticipo puntata non valido", LogLevel.Warn);
}
}
catch (Exception ex)
{
Log($"[ERRORE] Salvataggio defaults: {ex.Message}", LogLevel.Error);
}
}
private void CancelDefaultsButton_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(this, "Funzionalità non ancora implementata", "Info", MessageBoxButton.OK, MessageBoxImage.Information);
try
{
// Ricarica defaults salvati
var settings = Utilities.SettingsManager.Load();
if (settings != null)
{
DefaultBidBeforeDeadlineMs.Text = settings.DefaultBidBeforeDeadlineMs.ToString();
DefaultCheckAuctionOpen.IsChecked = settings.DefaultCheckAuctionOpenBeforeBid;
DefaultMinPrice.Text = settings.DefaultMinPrice.ToString("F2");
DefaultMaxPrice.Text = settings.DefaultMaxPrice.ToString("F2");
DefaultMaxClicks.Text = settings.DefaultMaxClicks.ToString();
}
else
{
// Valori di default se non ci sono impostazioni salvate
DefaultBidBeforeDeadlineMs.Text = "200";
DefaultCheckAuctionOpen.IsChecked = false;
DefaultMinPrice.Text = "0";
DefaultMaxPrice.Text = "0";
DefaultMaxClicks.Text = "0";
}
Log("[INFO] Impostazioni predefinite ripristinate", LogLevel.Info);
MessageBox.Show(this, "Impostazioni predefinite ripristinate.", "Annulla", MessageBoxButton.OK, MessageBoxImage.Information);
}
catch (Exception ex)
{
Log($"[ERRORE] Ripristino defaults: {ex.Message}", LogLevel.Error);
MessageBox.Show(this, "Errore durante ripristino: " + ex.Message, "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
}
}

View File

@@ -60,15 +60,16 @@ namespace AutoBidder
? $"Asta {auctionId}"
: $"{System.Net.WebUtility.HtmlDecode(productName)} ({auctionId})";
// Crea model
// Crea model con NUOVI CAMPI - ASTA STOPPATA ALL'INIZIO
var auction = new AuctionInfo
{
AuctionId = auctionId,
Name = System.Net.WebUtility.HtmlDecode(displayName),
OriginalUrl = originalUrl,
TimerClick = 0,
DelayMs = 50,
IsActive = true
BidBeforeDeadlineMs = 200,
CheckAuctionOpenBeforeBid = false,
IsActive = false, // STOPPATA
IsPaused = false
};
// Aggiungi al monitor
@@ -80,6 +81,7 @@ namespace AutoBidder
SaveAuctions();
UpdateTotalCount();
UpdateGlobalControlButtons(); // Aggiorna stato pulsanti globali
}
catch (Exception ex)
{
@@ -126,14 +128,16 @@ namespace AutoBidder
}
catch { }
// Crea model
// Crea model con NUOVI CAMPI - ASTA STOPPATA ALL'INIZIO
var auction = new AuctionInfo
{
AuctionId = auctionId,
Name = System.Net.WebUtility.HtmlDecode(name),
TimerClick = 0,
DelayMs = 50,
IsActive = true
OriginalUrl = url,
BidBeforeDeadlineMs = 200,
CheckAuctionOpenBeforeBid = false,
IsActive = false, // STOPPATA
IsPaused = false
};
// Aggiungi al monitor
@@ -145,6 +149,7 @@ namespace AutoBidder
SaveAuctions();
UpdateTotalCount();
UpdateGlobalControlButtons(); // Aggiorna stato pulsanti globali
}
catch (Exception ex)
{
@@ -192,6 +197,8 @@ namespace AutoBidder
}
UpdateTotalCount();
UpdateGlobalControlButtons(); // Aggiorna stato pulsanti dopo caricamento
if (auctions.Count > 0)
{
Log($"[OK] Caricate {auctions.Count} aste salvate");

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;
@@ -20,18 +20,13 @@ namespace AutoBidder
{
if (!_isAutomationActive)
{
foreach (var vm in _auctionViewModels)
{
vm.IsActive = true;
vm.IsPaused = false;
}
// Avvia il monitoraggio globale
_auctionMonitor.Start();
_isAutomationActive = true;
Log("Monitoraggio avviato!", LogLevel.Success);
Log("[START] Monitoraggio avviato!", LogLevel.Success);
}
else
{
// Attiva e riprendi tutte le aste
foreach (var vm in _auctionViewModels)
{
if (!vm.IsActive)
@@ -40,14 +35,17 @@ namespace AutoBidder
}
vm.IsPaused = false;
}
Log("Avviate/riprese le aste (tutte)", LogLevel.Info);
if (sender != null) // Solo se chiamato dall'utente, non internamente
{
Log("[START ALL] Tutte le aste avviate/riprese", LogLevel.Info);
}
UpdateGlobalControlButtons();
}
catch (Exception ex)
{
Log($"Errore avvio: {ex.Message}", LogLevel.Error);
Log($"[ERRORE] Avvio: {ex.Message}", LogLevel.Error);
MessageBox.Show($"Errore: {ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
}
}
@@ -56,23 +54,29 @@ namespace AutoBidder
{
try
{
// Ferma tutte le aste
foreach (var vm in _auctionViewModels)
{
vm.IsActive = false;
}
// Ferma il monitoraggio globale
if (_isAutomationActive)
{
_auctionMonitor.Stop();
_isAutomationActive = false;
}
foreach (var vm in _auctionViewModels)
{
vm.IsActive = false;
}
UpdateGlobalControlButtons();
Log("[STOP] Automazione fermata e aste arrestate", LogLevel.Warn);
if (sender != null) // Solo se chiamato dall'utente
{
Log("[STOP ALL] Monitoraggio fermato e tutte le aste arrestate", LogLevel.Warn);
}
}
catch (Exception ex)
{
Log($"[STOP ERROR] {ex.Message}", LogLevel.Error);
Log($"[ERRORE STOP] {ex.Message}", LogLevel.Error);
}
}
@@ -85,7 +89,11 @@ namespace AutoBidder
vm.IsPaused = true;
}
UpdateGlobalControlButtons();
Log("[PAUSA] Tutte le aste in pausa", LogLevel.Warn);
if (sender != null) // Solo se chiamato dall'utente
{
Log("[PAUSE ALL] Tutte le aste in pausa", LogLevel.Warn);
}
}
catch (Exception ex)
{
@@ -161,15 +169,45 @@ namespace AutoBidder
return;
}
// La conferma è già gestita in AuctionMonitorControl per il tasto Canc
// Qui facciamo la rimozione diretta per il bottone
_auctionMonitor.RemoveAuction(_selectedAuction.AuctionId);
var auctionName = _selectedAuction.Name;
var auctionId = _selectedAuction.AuctionId;
// 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);
// Reset selezione
_selectedAuction = null;
// Salva modifiche
SaveAuctions();
UpdateTotalCount();
Log("Asta rimossa", LogLevel.Info);
UpdateGlobalControlButtons();
Log($"[REMOVE] Asta rimossa: {auctionName} (ID: {auctionId})", LogLevel.Success);
}
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 ResetSettingsButton_Click(object sender, RoutedEventArgs e)
@@ -184,8 +222,8 @@ namespace AutoBidder
if (result == MessageBoxResult.Yes)
{
_selectedAuction.TimerClick = 0;
_selectedAuction.AuctionInfo.DelayMs = 50;
_selectedAuction.AuctionInfo.BidBeforeDeadlineMs = 200;
_selectedAuction.AuctionInfo.CheckAuctionOpenBeforeBid = false;
_selectedAuction.MinPrice = 0;
_selectedAuction.MaxPrice = 0;
_selectedAuction.MaxClicks = 0;
@@ -249,61 +287,108 @@ namespace AutoBidder
}
}
private void SelectedTimerClick_TextChanged(object sender, TextChangedEventArgs e)
private void SelectedBidBeforeDeadlineMs_TextChanged(object sender, TextChangedEventArgs e)
{
if (_selectedAuction != null && sender is TextBox tb)
if (_selectedAuction == null) return;
if (sender is TextBox tb && int.TryParse(tb.Text, out var value) && value >= 0 && value <= 5000)
{
if (int.TryParse(tb.Text, out var value) && value >= 0 && value <= 8)
var oldValue = _selectedAuction.AuctionInfo.BidBeforeDeadlineMs;
_selectedAuction.AuctionInfo.BidBeforeDeadlineMs = value;
// Log solo se non stiamo caricando E il valore è cambiato
if (!_isUpdatingSelection && oldValue != value)
{
_selectedAuction.TimerClick = value;
_selectedAuction.AuctionInfo.AddLog($"[SETTINGS] Anticipo puntata: {oldValue}ms → {value}ms");
}
// Salva sempre (anche durante caricamento iniziale non fa male)
SaveAuctions();
}
}
private void SelectedDelayMs_TextChanged(object sender, TextChangedEventArgs e)
private void SelectedCheckAuctionOpen_Changed(object sender, RoutedEventArgs e)
{
if (_selectedAuction != null && sender is TextBox tb)
if (_selectedAuction == null) return;
if (sender is System.Windows.Controls.Primitives.ToggleButton cb)
{
if (int.TryParse(tb.Text, out var value) && value >= 0)
var oldValue = _selectedAuction.AuctionInfo.CheckAuctionOpenBeforeBid;
var newValue = cb.IsChecked ?? false;
_selectedAuction.AuctionInfo.CheckAuctionOpenBeforeBid = newValue;
// Log solo se non stiamo caricando E il valore è cambiato
if (!_isUpdatingSelection && oldValue != newValue)
{
_selectedAuction.AuctionInfo.DelayMs = value;
_selectedAuction.AuctionInfo.AddLog($"[SETTINGS] Verifica stato asta: {(newValue ? "ON" : "OFF")}");
}
SaveAuctions();
}
}
private void SelectedMinPrice_TextChanged(object sender, TextChangedEventArgs e)
{
if (_selectedAuction != null && sender is TextBox tb)
if (_selectedAuction == null) return;
if (sender is TextBox tb)
{
if (double.TryParse(tb.Text.Replace(',', '.'), System.Globalization.NumberStyles.Any,
System.Globalization.CultureInfo.InvariantCulture, out var value))
{
var oldValue = _selectedAuction.MinPrice;
_selectedAuction.MinPrice = value;
// Log solo se non stiamo caricando E il valore è cambiato
if (!_isUpdatingSelection && Math.Abs(oldValue - value) > 0.01)
{
_selectedAuction.AuctionInfo.AddLog($"[SETTINGS] Prezzo minimo: €{oldValue:F2} → €{value:F2}");
}
SaveAuctions();
}
}
}
private void SelectedMaxPrice_TextChanged(object sender, TextChangedEventArgs e)
{
if (_selectedAuction != null && sender is TextBox tb)
if (_selectedAuction == null) return;
if (sender is TextBox tb)
{
if (double.TryParse(tb.Text.Replace(',', '.'), System.Globalization.NumberStyles.Any,
System.Globalization.CultureInfo.InvariantCulture, out var value))
{
var oldValue = _selectedAuction.MaxPrice;
_selectedAuction.MaxPrice = value;
// Log solo se non stiamo caricando E il valore è cambiato
if (!_isUpdatingSelection && Math.Abs(oldValue - value) > 0.01)
{
_selectedAuction.AuctionInfo.AddLog($"[SETTINGS] Prezzo massimo: €{oldValue:F2} → €{value:F2}");
}
SaveAuctions();
}
}
}
private void SelectedMaxClicks_TextChanged(object sender, TextChangedEventArgs e)
{
if (_selectedAuction != null && sender is TextBox tb)
{
if (int.TryParse(tb.Text, out var value) && value >= 0)
if (_selectedAuction == null) return;
if (sender is TextBox tb && int.TryParse(tb.Text, out var value) && value >= 0)
{
var oldValue = _selectedAuction.MaxClicks;
_selectedAuction.MaxClicks = value;
SaveAuctions();
// Log solo se non stiamo caricando E il valore è cambiato
if (!_isUpdatingSelection && oldValue != value)
{
_selectedAuction.AuctionInfo.AddLog($"[SETTINGS] Max clicks: {oldValue} → {value}");
}
SaveAuctions();
}
}
@@ -320,12 +405,12 @@ namespace AutoBidder
MessageBox.Show(
$"Export Massivo di {_auctionViewModels.Count} aste.\n\n" +
"Per configurare le opzioni di export, vai nella scheda Impostazioni.\n\n" +
"Nota: Questa funzionalità verrà completata nelle prossime versioni.",
"Nota: Questa funzionalità verrà completata nelle prossime versioni.",
"Export Aste",
MessageBoxButton.OK,
MessageBoxImage.Information);
Log($"[EXPORT] Richiesto export per {_auctionViewModels.Count} aste (funzionalità in sviluppo)", LogLevel.Info);
Log($"[EXPORT] Richiesto export per {_auctionViewModels.Count} aste (funzionalità in sviluppo)", LogLevel.Info);
}
catch (Exception ex)
{

View File

@@ -1,5 +1,7 @@
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using AutoBidder.Utilities;
using AutoBidder.ViewModels;
namespace AutoBidder
@@ -11,14 +13,14 @@ namespace AutoBidder
{
private void InitializeCommands()
{
StartAllCommand = new Utilities.RelayCommand(_ => ExecuteStartAll());
StopAllCommand = new Utilities.RelayCommand(_ => ExecuteStopAll());
PauseAllCommand = new Utilities.RelayCommand(_ => ExecutePauseAll());
StartAllCommand = new RelayCommand(_ => ExecuteStartAll());
StopAllCommand = new RelayCommand(_ => ExecuteStopAll());
PauseAllCommand = new RelayCommand(_ => ExecutePauseAll());
GridStartCommand = new Utilities.RelayCommand(param => ExecuteGridStart(param as AuctionViewModel));
GridPauseCommand = new Utilities.RelayCommand(param => ExecuteGridPause(param as AuctionViewModel));
GridStopCommand = new Utilities.RelayCommand(param => ExecuteGridStop(param as AuctionViewModel));
GridBidCommand = new Utilities.RelayCommand(async param => await ExecuteGridBidAsync(param as AuctionViewModel));
GridStartCommand = new RelayCommand(param => ExecuteGridStart(param as AuctionViewModel));
GridPauseCommand = new RelayCommand(param => ExecuteGridPause(param as AuctionViewModel));
GridStopCommand = new RelayCommand(param => ExecuteGridStop(param as AuctionViewModel));
GridBidCommand = new RelayCommand(async param => await ExecuteGridBidAsync(param as AuctionViewModel));
}
private void ExecuteStartAll()
@@ -39,9 +41,23 @@ namespace AutoBidder
private void ExecuteGridStart(AuctionViewModel? vm)
{
if (vm == null) return;
// Attiva l'asta
vm.IsActive = true;
vm.IsPaused = false;
Log($"[START] Asta avviata: {vm.Name}");
// Se il monitoraggio globale non è attivo, avvialo automaticamente
if (!_isAutomationActive)
{
_auctionMonitor.Start();
_isAutomationActive = true;
Log($"[AUTO-START] Monitoraggio avviato automaticamente per asta: {vm.Name}", LogLevel.Info);
}
else
{
Log($"[START] Asta avviata: {vm.Name}", LogLevel.Info);
}
UpdateGlobalControlButtons();
}
@@ -49,7 +65,7 @@ namespace AutoBidder
{
if (vm == null) return;
vm.IsPaused = true;
Log($"[PAUSA] Asta in pausa: {vm.Name}");
Log($"[PAUSA] Asta in pausa: {vm.Name}", LogLevel.Info);
UpdateGlobalControlButtons();
}
@@ -57,7 +73,20 @@ namespace AutoBidder
{
if (vm == null) return;
vm.IsActive = false;
Log($"[STOP] Asta fermata: {vm.Name}");
// Se tutte le aste sono fermate, ferma anche il monitoraggio globale
bool hasActiveAuctions = _auctionViewModels.Any(a => a.IsActive);
if (!hasActiveAuctions && _isAutomationActive)
{
_auctionMonitor.Stop();
_isAutomationActive = false;
Log($"[AUTO-STOP] Monitoraggio fermato: nessuna asta attiva", LogLevel.Info);
}
else
{
Log($"[STOP] Asta fermata: {vm.Name}", LogLevel.Info);
}
UpdateGlobalControlButtons();
}
@@ -66,16 +95,16 @@ namespace AutoBidder
if (vm == null) return;
try
{
Log($"[BID] Puntata manuale richiesta su: {vm.Name}");
Log($"[BID] Puntata manuale richiesta su: {vm.Name}", LogLevel.Info);
var result = await _auctionMonitor.PlaceManualBidAsync(vm.AuctionInfo);
if (result.Success)
Log($"[OK] Puntata manuale su {vm.Name}: {result.LatencyMs}ms");
Log($"[OK] Puntata manuale su {vm.Name}: {result.LatencyMs}ms", LogLevel.Success);
else
Log($"[FAIL] Puntata manuale su {vm.Name}: {result.Error}");
Log($"[FAIL] Puntata manuale su {vm.Name}: {result.Error}", LogLevel.Error);
}
catch (System.Exception ex)
{
Log($"[ERRORE] Puntata manuale: {ex.Message}");
Log($"[ERRORE] Puntata manuale: {ex.Message}", LogLevel.Error);
}
}
}

View File

@@ -118,6 +118,33 @@ namespace AutoBidder
ClearGlobalLogButton_Click(sender, e);
}
// ===== AUCTION SETTINGS EVENTS =====
private void AuctionMonitor_BidBeforeDeadlineMsChanged(object sender, RoutedEventArgs e)
{
SelectedBidBeforeDeadlineMs_TextChanged(AuctionMonitor.SelectedBidBeforeDeadlineMs, new System.Windows.Controls.TextChangedEventArgs(e.RoutedEvent, System.Windows.Controls.UndoAction.None));
}
private void AuctionMonitor_CheckAuctionOpenChanged(object sender, RoutedEventArgs e)
{
SelectedCheckAuctionOpen_Changed(AuctionMonitor.SelectedCheckAuctionOpen, e);
}
private void AuctionMonitor_MinPriceChanged(object sender, RoutedEventArgs e)
{
SelectedMinPrice_TextChanged(AuctionMonitor.SelectedMinPrice, new System.Windows.Controls.TextChangedEventArgs(e.RoutedEvent, System.Windows.Controls.UndoAction.None));
}
private void AuctionMonitor_MaxPriceChanged(object sender, RoutedEventArgs e)
{
SelectedMaxPrice_TextChanged(AuctionMonitor.SelectedMaxPrice, new System.Windows.Controls.TextChangedEventArgs(e.RoutedEvent, System.Windows.Controls.UndoAction.None));
}
private void AuctionMonitor_MaxClicksChanged(object sender, RoutedEventArgs e)
{
SelectedMaxClicks_TextChanged(AuctionMonitor.SelectedMaxClicks, new System.Windows.Controls.TextChangedEventArgs(e.RoutedEvent, System.Windows.Controls.UndoAction.None));
}
// ===== BROWSER CONTROL EVENTS =====
private void Browser_BrowserBackClicked(object sender, RoutedEventArgs e)

View File

@@ -17,11 +17,14 @@ namespace AutoBidder
{
try
{
// Blocca temporaneamente i TextChanged per evitare loop di aggiornamento
_isUpdatingSelection = true;
SelectedAuctionName.Text = auction.Name;
SelectedTimerClick.Text = auction.TimerClick.ToString();
SelectedDelayMs.Text = auction.AuctionInfo.DelayMs.ToString();
SelectedMinPrice.Text = auction.MinPrice.ToString();
SelectedMaxPrice.Text = auction.MaxPrice.ToString();
SelectedBidBeforeDeadlineMs.Text = auction.AuctionInfo.BidBeforeDeadlineMs.ToString();
SelectedCheckAuctionOpen.IsChecked = auction.AuctionInfo.CheckAuctionOpenBeforeBid;
SelectedMinPrice.Text = auction.MinPrice.ToString("F2", System.Globalization.CultureInfo.InvariantCulture);
SelectedMaxPrice.Text = auction.MaxPrice.ToString("F2", System.Globalization.CultureInfo.InvariantCulture);
SelectedMaxClicks.Text = auction.MaxClicks.ToString();
var url = auction.AuctionInfo.OriginalUrl;
@@ -35,8 +38,13 @@ namespace AutoBidder
UpdateAuctionLog(auction);
RefreshBiddersGrid(auction);
_isUpdatingSelection = false;
}
catch
{
_isUpdatingSelection = false;
}
catch { }
}
private void UpdateAuctionLog(AuctionViewModel auction)
@@ -106,12 +114,38 @@ namespace AutoBidder
try
{
var hasAuctions = _auctionViewModels.Count > 0;
var anyActive = _auctionViewModels.Any(a => a.IsActive);
var anyPaused = _auctionViewModels.Any(a => a.IsPaused);
StartButton.IsEnabled = hasAuctions;
StopButton.IsEnabled = hasAuctions && (_isAutomationActive || anyActive);
PauseAllButton.IsEnabled = hasAuctions && anyActive && !anyPaused;
if (!hasAuctions)
{
// Nessuna asta: tutti disabilitati
StartButton.IsEnabled = false;
StartButton.Opacity = 0.4;
PauseAllButton.IsEnabled = false;
PauseAllButton.Opacity = 0.4;
StopButton.IsEnabled = false;
StopButton.Opacity = 0.4;
return;
}
// Conta quante aste possono eseguire ogni azione
int canStartCount = _auctionViewModels.Count(a => a.CanStart);
int canPauseCount = _auctionViewModels.Count(a => a.CanPause);
int canStopCount = _auctionViewModels.Count(a => a.CanStop);
// AVVIA TUTTI: abilitato se ALMENO UNA asta può essere avviata
// Scuro se NESSUNA asta può essere avviata (tutte già avviate)
StartButton.IsEnabled = canStartCount > 0;
StartButton.Opacity = canStartCount > 0 ? 1.0 : 0.4;
// PAUSA TUTTI: abilitato se ALMENO UNA asta può essere messa in pausa
// Scuro se NESSUNA asta può essere messa in pausa (tutte già in pausa o ferme)
PauseAllButton.IsEnabled = canPauseCount > 0;
PauseAllButton.Opacity = canPauseCount > 0 ? 1.0 : 0.4;
// FERMA TUTTI: abilitato se ALMENO UNA asta può essere fermata
// Scuro se NESSUNA asta può essere fermata (tutte già ferme)
StopButton.IsEnabled = canStopCount > 0;
StopButton.Opacity = canStopCount > 0 ? 1.0 : 0.4;
}
catch { }
}

View File

@@ -3,6 +3,7 @@ using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using AutoBidder.Services;
using AutoBidder.Utilities;
namespace AutoBidder
{
@@ -16,30 +17,83 @@ namespace AutoBidder
private void InitializeUserInfoTimers()
{
// Timer per aggiornamento banner utente ogni minuto
// Timer per aggiornamento dati utente da HTML ogni 5 minuti (PRINCIPALE)
_userHtmlTimer = new System.Windows.Threading.DispatcherTimer();
_userHtmlTimer.Interval = TimeSpan.FromMinutes(5);
_userHtmlTimer.Tick += UserHtmlTimer_Tick;
_userHtmlTimer.Start();
// Timer per aggiornamento banner API ogni 10 minuti (SECONDARIO - fallback)
_userBannerTimer = new System.Windows.Threading.DispatcherTimer();
_userBannerTimer.Interval = TimeSpan.FromMinutes(1);
_userBannerTimer.Interval = TimeSpan.FromMinutes(10);
_userBannerTimer.Tick += UserBannerTimer_Tick;
_userBannerTimer.Start();
// Timer per aggiornamento dati utente da HTML ogni 3 minuti
_userHtmlTimer = new System.Windows.Threading.DispatcherTimer();
_userHtmlTimer.Interval = TimeSpan.FromMinutes(3);
_userHtmlTimer.Tick += UserHtmlTimer_Tick;
_userHtmlTimer.Start();
Log("[INFO] Timer info utente avviati (5min HTML principale, 10min API fallback)", LogLevel.Info);
}
private void SetUserBanner(string username, int? remainingBids)
{
try
{
var session = _auctionMonitor.GetSession();
if (!string.IsNullOrEmpty(username))
{
UsernameText.Text = $"{username} ({remainingBids ?? 0} puntate)";
// === HEADER - 2 RIGHE ===
// Riga 1: Puntate + Credito
RemainingBidsText.Text = remainingBids?.ToString() ?? "0";
if (session?.ShopCredit > 0)
{
AuctionMonitor.ShopCreditText.Text = $"EUR {session.ShopCredit:F2}";
}
else
{
UsernameText.Text = "Non connesso";
AuctionMonitor.ShopCreditText.Text = "EUR 0.00";
}
// Riga 2: Aste vinte (TODO: implementare)
BannerAsteDaRiscattare.Text = "0";
// === SIDEBAR - Pannello Utente ===
// Username
SidebarUsernameText.Text = username;
// ID Utente
if (session?.UserId > 0)
{
SidebarUserIdText.Text = $"ID: {session.UserId}";
SidebarUserIdText.Visibility = System.Windows.Visibility.Visible;
}
else
{
SidebarUserIdText.Visibility = System.Windows.Visibility.Collapsed;
}
// Email
if (!string.IsNullOrEmpty(session?.Email))
{
SidebarUserEmailText.Text = session.Email;
SidebarUserEmailText.Visibility = System.Windows.Visibility.Visible;
}
else
{
SidebarUserEmailText.Visibility = System.Windows.Visibility.Collapsed;
}
// Mostra il pannello sidebar
SidebarUserInfoPanel.Visibility = System.Windows.Visibility.Visible;
}
else
{
// Nascondi pannello sidebar
SidebarUserInfoPanel.Visibility = System.Windows.Visibility.Collapsed;
// Reset header
RemainingBidsText.Text = "0";
AuctionMonitor.ShopCreditText.Text = "EUR 0.00";
BannerAsteDaRiscattare.Text = "0";
}
}
catch { }
@@ -47,11 +101,13 @@ namespace AutoBidder
private async void UserBannerTimer_Tick(object? sender, EventArgs e)
{
// Questo è ora il fallback secondario
await UpdateUserBannerInfoAsync();
}
private async void UserHtmlTimer_Tick(object? sender, EventArgs e)
{
// Questo è ora il metodo principale
await UpdateUserHtmlInfoAsync();
}
@@ -59,16 +115,48 @@ namespace AutoBidder
{
try
{
Log("[INFO] Tentativo recupero info utente da API...", LogLevel.Info);
// Prova prima l'endpoint API
var success = await _auctionMonitor.UpdateUserInfoAsync();
if (success)
{
var session = _auctionMonitor.GetSession();
SetUserBanner(session?.Username ?? string.Empty, session?.RemainingBids);
if (session != null && !string.IsNullOrEmpty(session.Username))
{
SetUserBanner(session.Username, session.RemainingBids);
Log($"[OK] Info utente API: {session.Username}, {session.RemainingBids} puntate", LogLevel.Info);
return; // Successo con API
}
else
{
Log($"[WARN] API ha risposto ma senza dati validi", LogLevel.Warn);
}
}
else
{
Log($"[WARN] API non ha risposto correttamente", LogLevel.Warn);
}
// Se API fallisce o non ha dati, usa HTML scraping come fallback
Log("[INFO] Tentativo fallback con HTML scraping...", LogLevel.Info);
var userData = await _auctionMonitor.GetUserDataFromHtmlAsync();
if (userData != null && !string.IsNullOrEmpty(userData.Username))
{
SetUserBanner(userData.Username, userData.RemainingBids);
Log($"[OK] Info utente HTML (fallback): {userData.Username}, {userData.RemainingBids} puntate", LogLevel.Info);
}
else
{
Log($"[ERROR] Impossibile aggiornare info utente - verifica cookie nelle Impostazioni", LogLevel.Warn);
}
}
catch (Exception ex)
{
Log($"[WARN] Aggiornamento banner utente: {ex.Message}");
Log($"[ERROR] Errore aggiornamento banner utente: {ex.Message}", LogLevel.Warn);
Log($"[ERROR] StackTrace: {ex.StackTrace}", LogLevel.Warn);
}
}
@@ -76,15 +164,27 @@ namespace AutoBidder
{
try
{
Log("[INFO] Tentativo recupero dati utente da HTML...", LogLevel.Info);
// HTML scraping è il metodo PRINCIPALE (più affidabile)
var userData = await _auctionMonitor.GetUserDataFromHtmlAsync();
if (userData != null)
if (userData != null && !string.IsNullOrEmpty(userData.Username))
{
SetUserBanner(userData.Username, userData.RemainingBids);
Log($"[OK] Dati utente aggiornati via HTML: {userData.Username}, {userData.RemainingBids} puntate", LogLevel.Info);
}
else
{
// Se HTML fallisce, non fare nulla - il timer API proverà tra poco
Log($"[WARN] HTML scraping non ha restituito dati validi - verifica cookie nelle Impostazioni", LogLevel.Warn);
Log($"[WARN] Possibili cause: cookie scaduto, non autenticato, sito modificato", LogLevel.Warn);
}
}
catch (Exception ex)
{
Log($"[WARN] Aggiornamento dati HTML: {ex.Message}");
Log($"[ERROR] Errore aggiornamento dati HTML: {ex.Message}", LogLevel.Warn);
Log($"[ERROR] StackTrace: {ex.StackTrace}", LogLevel.Warn);
}
}
@@ -129,45 +229,41 @@ namespace AutoBidder
}
catch { }
UsernameText.Text = session.Username ?? string.Empty;
StartButton.IsEnabled = true;
Log($"[OK] Sessione ripristinata per: {session.Username}");
// Verifica validità cookie (background)
Task.Run(() =>
// Verifica validità cookie (background) - USA HTML come metodo principale
Task.Run(async () =>
{
try
{
var success = _auctionMonitor.UpdateUserInfoAsync().GetAwaiter().GetResult();
var updatedSession = _auctionMonitor.GetSession();
Dispatcher.Invoke(() =>
{
if (success)
{
SetUserBanner(updatedSession?.Username ?? string.Empty, updatedSession?.RemainingBids);
Log($"[OK] Cookie valido - Crediti disponibili: {updatedSession?.RemainingBids ?? 0}");
}
else
{
// Fallback: try scraping HTML
var htmlUser = _auctionMonitor.GetUserDataFromHtmlAsync().GetAwaiter().GetResult();
if (htmlUser != null)
// Prova prima HTML scraping (più affidabile)
var htmlUser = await _auctionMonitor.GetUserDataFromHtmlAsync();
if (htmlUser != null && !string.IsNullOrEmpty(htmlUser.Username))
{
Dispatcher.Invoke(() =>
{
SetUserBanner(htmlUser.Username, htmlUser.RemainingBids);
Log($"[OK] Dati utente rilevati via HTML - Utente: {htmlUser.Username}, Puntate residue: {htmlUser.RemainingBids}");
});
return; // Successo con HTML
}
// Fallback: prova API
var success = await _auctionMonitor.UpdateUserInfoAsync();
var updatedSession = _auctionMonitor.GetSession();
Dispatcher.Invoke(() =>
{
if (success && updatedSession != null && !string.IsNullOrEmpty(updatedSession.Username))
{
SetUserBanner(updatedSession.Username, updatedSession.RemainingBids);
Log($"[OK] Cookie valido - Crediti disponibili: {updatedSession.RemainingBids}");
}
else
{
Dispatcher.Invoke(() =>
{
Log($"[WARN] Impossibile verificare sessione: cookie non valido o rete assente.");
});
}
Log($"[WARN] Impossibile verificare sessione: verifica cookie nelle Impostazioni");
}
});
}

View File

@@ -219,3 +219,97 @@ AutoBidder/
- 🔄 Migrazioni
- 🎯 Roadmap
- 🙏 Ringraziamenti
## v4.1 - UI Modernizzata (2024-01-XX)
### 🎨 Miglioramenti UI
- ✅ **Header semplificato**: Info utente spostate in basso a sinistra
- ✅ **Pannello utente** elegante con:
- Username + ID utente
- Email
- Design card moderno con bordi arrotondati
- Visibilità automatica (appare solo quando loggato)
- ✅ **Header compatto** con statistiche chiave:
- Puntate residue (verde #00D800)
- Credito Shop (verde #00D800)
- Aste vinte (giallo #FFB700)
- ✅ **Layout pulito** stile moderno con separatori verticali
### ⚙️ Performance
- ✅ **Aggiornamento ogni 5 minuti** (era 1 minuto)
- Timer HTML principale: 5 minuti
- Timer API fallback: 10 minuti
- Ridotto carico rete del 80%
- ✅ Pannello utente nascosto di default (meno distrazione)
### 📊 Posizionamento Info
```
┌─────────────────────────────────────────────┐
│ Puntate: 199 | Credito: EUR 15.00 | Aste: 0│ [Pulsanti]
├─────────────────────────────────────────────┤
│ │
│ GRIGLIA ASTE + LOG │
│ │
├─────────────────────────────────────────────┤
│ IMPOSTAZIONI | UTENTI | LOG │
│ │
└─────────────────────────────────────────────┘
┌────────────────────┐
│ sirbietole23 │ ← Pannello utente
│ (ID: 6707664) │ in basso a sx
│ email@email.com │
└────────────────────┘
```
---
## v4.0 - Sistema di Timing Avanzato
### ⚡ Nuovo Sistema di Timing
- ✅ Sostituito "Timer Click (secondi)" con "Anticipo (ms)"
- ✅ Precisione al millisecondo invece dei secondi
- ✅ Polling adattivo 10-1000ms basato su timer rimanente
- ✅ Cooldown 800ms tra puntate consecutive
- ✅ Rilevamento puntate recenti altri utenti (500ms)
- ✅ Checkbox opzionale "Verifica stato asta prima di puntare"
### 🐛 Bug Fix
- ✅ Fix persistenza valori modificati per singola asta
- ✅ Fix visualizzazione username e puntate rimanenti
- ✅ Conferma richiesta prima di cancellare asta (pulsante + tasto Canc)
- ✅ Ottimizzazione logging per miglior performance
- ✅ Fix stato pulsanti globali all'avvio
- ✅ **Fix tasto Canc**: Ora elimina correttamente l'asta selezionata
- Cambiato da `KeyDown` a `PreviewKeyDown` (priorità più alta)
- Migliorata gestione focus keyboard sul DataGrid
- Aggiunto messaggio di conferma migliorato
- Aggiunto logging dettagliato per debug
- **Fix messaggio duplicato**: Rimosso secondo messaggio di conferma (ora ne appare solo uno)
- ✅ **Fix avvio singola asta**: Ora il pulsante "Avvia" sulla griglia funziona senza "Avvia Tutti"
- Auto-start del monitoraggio quando si avvia la prima asta
- Auto-stop del monitoraggio quando si ferma l'ultima asta
- Logging dettagliato con `[AUTO-START]` e `[AUTO-STOP]`
- Comportamento più intuitivo e flessibile
### 📦 Dati Utente Ottimizzati
- ✅ **Endpoint unico**: `/buy_bids.php` (era 2 chiamate)
- ✅ **6 dati estratti**: username, email, ID, telefono, puntate, credito
- ✅ **Parsing JavaScript**: `BidooCnf.userObj` (più affidabile)
- ✅ **Performance**: -50% overhead rete
- ✅ **Logging diagnostico**: Aggiunto logging dettagliato per troubleshooting
### 🎨 UI/UX
- ✅ Tooltip informativi su tutti i campi critici
- ✅ Formattazione prezzi con 2 decimali
- ✅ Messaggi di conferma per azioni distruttive
- ✅ Feedback visivo migliorato per eliminazione aste
- ✅ Maggiore flessibilità nell'avvio/stop singole aste
### 📦 Export
- ✅ Export CSV/JSON/XML aggiornato con nuovi campi
- ✅ Backward compatibility con aste salvate nella v3.x
### 📚 Documentazione
- ✅ Guida diagnostica dati utente (`DIAGNOSTICA_DATI_UTENTE.md`)
- ✅ Documentazione fix tasto Canc (`FIX_DELETE_KEY.md`)
- ✅ Documentazione fix avvio singola asta (`FIX_SINGLE_AUCTION_START.md`)

View File

@@ -0,0 +1,148 @@
# ?? Diagnostica Recupero Dati Utente
## Cosa è cambiato
**NON ho modificato** la procedura di recupero dati utente nelle ultime modifiche.
Il codice esistente è lo stesso di prima, ma ho aggiunto **logging dettagliato** per capire cosa sta andando storto.
## Come funziona il recupero dati
Il sistema usa **2 strategie parallele** (ridondanza per affidabilità):
### 1?? **METODO PRINCIPALE**: HTML Scraping (Timer 5 minuti)
- **URL**: `https://it.bidoo.com/bids_history.php`
- **Estrae**: Username, Puntate residue
- **Pattern cercati**:
```regex
<a class="pers_lnk"[^>]*>([^<]+)</a> # Username
<span id="divSaldoBidBottom"[^>]*>(\d+)</span> # Puntate
```
### 2?? **METODO FALLBACK**: API (Timer 10 minuti)
- **URL**: `https://it.bidoo.com/buy_bids.php`
- **Estrae**: Username, Email, ID, Telefono, Puntate, Credito Shop
- **Pattern cercati**:
```regex
BidooCnf.userObj.username = 'username';
BidooCnf.userObj.email = 'email@example.com';
BidooCnf.userObj.id = '123456';
<span id="divSaldoBidMobile">206</span>
<span class="cbstotal">15.00</span>
```
## ?? Possibili Cause dell'Errore
### 1. **Cookie Scaduto o Non Valido**
Il cookie `__stattrb` potrebbe essere scaduto o non più valido.
**Come verificare**:
1. Apri il browser e vai su `https://it.bidoo.com`
2. Apri DevTools (F12) ? Applicazione ? Cookie
3. Controlla se il cookie `__stattrb` esiste
4. Copia il nuovo valore e inseriscilo nelle Impostazioni
### 2. **Sito Bidoo ha Cambiato Struttura HTML**
Bidoo potrebbe aver modificato la struttura delle pagine.
**Come verificare**:
1. Guarda i log dettagliati (ora disponibili dopo le modifiche)
2. Cerca messaggi tipo:
- `[USER HTML ERROR] Username NON trovato nell'HTML`
- `[USER HTML DEBUG] Snippet HTML: ...`
3. Confronta lo snippet con i pattern regex
### 3. **Problema di Rete o Firewall**
Il server potrebbe bloccare le richieste.
**Come verificare**:
1. Cerca nei log:
- `[USER HTML ERROR] HTTP 403` ? Bloccato
- `[USER HTML ERROR] HTTP 401` ? Non autorizzato
- `[USER HTML ERROR] HTTP 500` ? Errore server
### 4. **Redirect o Risposta Non HTML**
Il server potrebbe fare redirect o rispondere con JSON/testo.
**Come verificare**:
1. Cerca nei log:
- `[USER HTML ERROR] Risposta non contiene HTML valido`
- `Body length: <100` ? Risposta troppo corta
## ?? Nuovo Logging Disponibile
Ho aggiunto logging **molto dettagliato** per diagnosticare:
### Log nel Console Output
```
[INFO] Tentativo recupero dati utente da HTML...
[USER HTML REQUEST] GET https://it.bidoo.com/bids_history.php
[USER HTML RESPONSE] Status: 200 OK
[USER HTML RESPONSE] Body length: 45233 chars
[USER HTML PARSED] Username trovato: sirbietole23
[USER HTML PARSED] Puntate residue trovate: 206
[USER HTML SUCCESS] Dati estratti: sirbietole23, 206 puntate
[OK] Dati utente aggiornati via HTML: sirbietole23, 206 puntate
```
### Se Fallisce
```
[USER HTML RESPONSE] Status: 200 OK
[USER HTML RESPONSE] Body length: 45233 chars
[USER HTML ERROR] Username NON trovato nell'HTML
[USER HTML DEBUG] Snippet HTML: <!DOCTYPE html><html lang="it">...
[USER HTML ERROR] Puntate residue NON trovate nell'HTML
[USER HTML FAILED] Impossibile estrarre dati utente dall'HTML
[WARN] HTML scraping non ha restituito dati validi - verifica cookie nelle Impostazioni
```
## ?? Come Risolvere
### Soluzione 1: Aggiorna Cookie
1. Vai su **Impostazioni**
2. Clicca **Configura Sessione**
3. Inserisci il cookie `__stattrb` aggiornato dal browser
4. Clicca **Salva**
5. Controlla i log
### Soluzione 2: Verifica Log Dettagliati
1. **Riavvia l'applicazione**
2. Aspetta 5-10 secondi (timer automatico parte)
3. Guarda il **Log Principale** in basso
4. Cerca i messaggi `[USER HTML...]` e `[USER INFO...]`
5. Inviami lo snippet HTML se vedi errori
### Soluzione 3: Test Manuale
1. Apri browser e vai su `https://it.bidoo.com/bids_history.php`
2. Verifica se sei loggato (vedi username in alto)
3. Se non sei loggato ? Cookie scaduto
4. Se sei loggato ? Mandami screenshot della pagina
## ?? Test di Verifica
Dopo aver seguito le soluzioni, verifica che nei log appaia:
? **SUCCESSO**:
```
[OK] Dati utente aggiornati via HTML: tuousername, X puntate
```
? **ANCORA ERRORE**:
```
[ERROR] Impossibile aggiornare info utente - verifica cookie nelle Impostazioni
```
Se ancora non funziona, **inviami i log completi** dal primo avvio fino all'errore.
## ?? Supporto
Se il problema persiste:
1. Copia **tutti i log** dal pannello principale
2. Invia screenshot della **scheda Impostazioni** (censura cookie se vuoi)
3. Dimmi se hai aggiornato il cookie recentemente
4. Dimmi se funzionava prima (quando?)
---
**Data**: 2025
**Versione**: 4.0+

View File

@@ -0,0 +1,199 @@
# ?? Fix Eliminazione Asta con Tasto Canc
## Problema Rilevato
Quando si selezionava un'asta nella griglia e si premeva il tasto **Canc (Delete)**, l'asta **NON veniva eliminata**.
## Causa del Problema
Il sistema aveva l'evento `KeyDown` implementato, ma presentava **2 problemi**:
1. **Focus Keyboard Mancante**: Il `DataGrid` non sempre aveva il focus keyboard dopo la selezione
2. **Evento Consumato**: Altri controlli potevano consumare l'evento `KeyDown` prima che arrivasse al gestore
## Soluzione Implementata
### ? 1. Cambiato da `KeyDown` a `PreviewKeyDown`
**Perché?**
- `PreviewKeyDown` viene chiamato **PRIMA** di tutti gli altri gestori
- Ha **priorità più alta** nella catena di eventi WPF
- Previene che l'evento venga consumato da controlli figli
```xml
<!-- PRIMA -->
KeyDown="MultiAuctionsGrid_KeyDown"
<!-- DOPO -->
PreviewKeyDown="MultiAuctionsGrid_PreviewKeyDown"
```
### ? 2. Aggiunto `Focusable="True"` nel XAML
Assicura che il `DataGrid` possa ricevere il focus keyboard.
```xml
Focusable="True"
FocusVisualStyle="{x:Null}"
```
### ? 3. Migliorata Gestione del Focus
Nel `SelectionChanged`, ora il focus viene dato con priorità corretta:
```csharp
grid.Dispatcher.BeginInvoke(new Action(() =>
{
if (!grid.IsFocused)
{
grid.Focus();
}
}), DispatcherPriority.Background);
```
### ? 4. Aggiunto Logging Debug
Per diagnostica futura:
```csharp
System.Diagnostics.Debug.WriteLine("[DELETE KEY] Tasto Canc premuto su asta selezionata");
System.Diagnostics.Debug.WriteLine("[DELETE KEY] Lancio evento RemoveUrlClicked");
```
### ? 5. **Fix Messaggio Duplicato** (Aggiornamento)
**Problema**: Apparivano **2 messaggi di conferma** quando si premeva Canc
- Primo in `PreviewKeyDown`
- Secondo in `RemoveUrlButton_Click`
**Soluzione**: Rimosso il messaggio da `PreviewKeyDown`, lasciando solo quello in `RemoveUrlButton_Click`
Ora quando premi Canc:
1. ? `PreviewKeyDown` lancia l'evento `RemoveUrlClicked`
2. ? `RemoveUrlButton_Click` mostra **UN SOLO** messaggio di conferma
3. ? L'utente conferma o annulla una sola volta
### ? 6. Messaggio di Conferma Unico
Messaggio chiaro e descrittivo (mostrato una sola volta):
```
Rimuovere l'asta dal monitoraggio?
Nome Asta
(ID: 12345)
L'asta verrà eliminata dalla lista e non sarà più monitorata.
```
### ? 7. Logging Potenziato
```
[REMOVE] Rimozione annullata: Nome Asta
[REMOVE] Asta rimossa: Nome Asta (ID: 12345)
[ERROR] Errore rimozione asta: messaggio errore
```
## Come Testare
1. **Avvia l'applicazione**
2. **Aggiungi almeno 2 aste**
3. **Seleziona un'asta** nella griglia (clicca sulla riga)
4. **Premi il tasto Canc** sulla tastiera
5. ? **Verifica che appaia UN SOLO messaggio** di conferma
6. **Conferma** la rimozione nel popup
7. ? **Verifica** che l'asta sia stata rimossa dalla lista
## Comportamento Atteso
### ? Scenario 1: Eliminazione Confermata
1. Premi `Canc`
2. Appare **UN** popup di conferma
3. Clicchi `Sì`
4. L'asta viene rimossa dalla griglia
5. Nel log appare: `[REMOVE] Asta rimossa: ...`
### ? Scenario 2: Eliminazione Annullata
1. Premi `Canc`
2. Appare **UN** popup di conferma
3. Clicchi `No`
4. L'asta rimane nella griglia
5. Nel log appare: `[REMOVE] Rimozione annullata: ...`
### ? Scenario 3: Nessuna Selezione
1. Clicchi sul pulsante "Rimuovi" senza selezione
2. Appare popup: `"Seleziona un'asta dalla griglia"`
3. Nessuna asta viene rimossa
## Debug Output (Visual Studio)
Se apri **Output ? Debug**, vedrai:
```
[FOCUS] DataGrid ora ha il focus keyboard
[DELETE KEY] Tasto Canc premuto su asta selezionata
[DELETE KEY] Lancio evento RemoveUrlClicked
```
## File Modificati
1. ? `Controls\AuctionMonitorControl.xaml`
- Cambiato `KeyDown` ? `PreviewKeyDown`
- Aggiunto `Focusable="True"`
2. ? `Controls\AuctionMonitorControl.xaml.cs`
- Rinominato `MultiAuctionsGrid_KeyDown` ? `MultiAuctionsGrid_PreviewKeyDown`
- **Rimosso messaggio di conferma duplicato**
- Migliorato focus nel `SelectionChanged`
- Aggiunto debug logging
3. ? `Core\MainWindow.ButtonHandlers.cs`
- Messaggio di conferma (UNICO punto di conferma)
- Aggiunto logging dettagliato
- Migliorata gestione errori
## Note Tecniche
### Perché `PreviewKeyDown` invece di `KeyDown`?
**Bubbling vs Tunneling in WPF:**
- `Preview*` eventi = **Tunneling** (dall'alto verso il basso)
- Eventi normali = **Bubbling** (dal basso verso l'alto)
Nel nostro caso, se un controllo figlio (es. cella del DataGrid) consuma l'evento `KeyDown`, il gestore del DataGrid non viene mai chiamato.
Con `PreviewKeyDown`, il gestore del DataGrid viene chiamato **per primo**, prima che qualsiasi controllo figlio possa consumare l'evento.
### Perché `Dispatcher.BeginInvoke`?
Il focus va dato **dopo** che il rendering della selezione è completo. `BeginInvoke` con `DispatcherPriority.Background` assicura che il focus venga dato al momento giusto.
### Perché Rimuovere il MessageBox dal PreviewKeyDown?
Il `PreviewKeyDown` è responsabile solo di **catturare l'evento tastiera** e lanciare l'evento `RemoveUrlClicked`.
La **logica di conferma** appartiene al gestore dell'azione (`RemoveUrlButton_Click`), che viene chiamato sia dal tasto Canc che dal pulsante "Rimuovi".
Questo garantisce:
- ? **DRY** (Don't Repeat Yourself) - Conferma in un solo posto
- ? **Coerenza** - Stesso comportamento da tastiera e pulsante
- ? **Manutenibilità** - Un solo messaggio da modificare
---
## ? Test di Verifica
- [x] Il tasto `Canc` elimina l'asta selezionata
- [x] Appare **UN SOLO** messaggio di conferma
- [x] L'asta viene rimossa dalla lista
- [x] Il log mostra `[REMOVE] Asta rimossa`
- [x] Annullare l'operazione funziona correttamente
- [x] Il pulsante "Rimuovi" continua a funzionare normalmente
- [x] Stessa conferma da tastiera e da pulsante
---
**Data Fix**: 2025
**Versione**: 4.0+
**Issue 1**: Tasto Canc non eliminava aste ? ? RISOLTO
**Issue 2**: Doppio messaggio di conferma ? ? RISOLTO

View File

@@ -0,0 +1,234 @@
# ?? Fix Avvio Singola Asta dalla Griglia
## Problema Rilevato
Quando si cliccava il pulsante **"Avvia"** su una singola asta nella griglia, l'asta **non veniva monitorata** a meno che prima non si fosse cliccato **"Avvia Tutti"**.
## Causa del Problema
Il sistema di monitoraggio aveva una **dipendenza rigida** sul flag `_isAutomationActive`:
1. ? Clic su "Avvia Tutti" ? Avvia `AuctionMonitor.Start()` + imposta `IsActive = true` su tutte le aste
2. ? Clic su "Avvia" (singola asta) ? Imposta solo `IsActive = true` MA **non avvia** `AuctionMonitor.Start()`
3. ? Risultato: L'asta era marcata come attiva, ma il loop di monitoraggio **non era in esecuzione**
### Codice Problematico (Prima)
```csharp
private void ExecuteGridStart(AuctionViewModel? vm)
{
if (vm == null) return;
vm.IsActive = true;
vm.IsPaused = false;
Log($"[START] Asta avviata: {vm.Name}");
UpdateGlobalControlButtons();
}
```
**Mancava**: Avvio del `AuctionMonitor` se non già attivo.
## Soluzione Implementata
### ? 1. Auto-Start del Monitoraggio
Ora, quando si avvia una singola asta, **il monitoraggio viene avviato automaticamente** se non è già attivo:
```csharp
private void ExecuteGridStart(AuctionViewModel? vm)
{
if (vm == null) return;
// Attiva l'asta
vm.IsActive = true;
vm.IsPaused = false;
// Se il monitoraggio globale non è attivo, avvialo automaticamente
if (!_isAutomationActive)
{
_auctionMonitor.Start();
_isAutomationActive = true;
Log($"[AUTO-START] Monitoraggio avviato automaticamente per asta: {vm.Name}", LogLevel.Info);
}
else
{
Log($"[START] Asta avviata: {vm.Name}", LogLevel.Info);
}
UpdateGlobalControlButtons();
}
```
### ? 2. Auto-Stop del Monitoraggio
Quando si ferma l'ultima asta attiva, **il monitoraggio viene fermato automaticamente**:
```csharp
private void ExecuteGridStop(AuctionViewModel? vm)
{
if (vm == null) return;
vm.IsActive = false;
// Se tutte le aste sono fermate, ferma anche il monitoraggio globale
bool hasActiveAuctions = _auctionViewModels.Any(a => a.IsActive);
if (!hasActiveAuctions && _isAutomationActive)
{
_auctionMonitor.Stop();
_isAutomationActive = false;
Log($"[AUTO-STOP] Monitoraggio fermato: nessuna asta attiva", LogLevel.Info);
}
else
{
Log($"[STOP] Asta fermata: {vm.Name}", LogLevel.Info);
}
UpdateGlobalControlButtons();
}
```
### ? 3. Migliorato Logging
Aggiunto logging dettagliato per capire quando il monitoraggio viene avviato/fermato automaticamente:
- `[AUTO-START] Monitoraggio avviato automaticamente per asta: Nome`
- `[AUTO-STOP] Monitoraggio fermato: nessuna asta attiva`
- `[START] Asta avviata: Nome` (se monitoraggio già attivo)
- `[STOP] Asta fermata: Nome` (se ci sono altre aste attive)
### ? 4. Coerenza con Pulsanti Globali
I pulsanti globali ora sono coerenti con il nuovo comportamento:
- **"Avvia Tutti"**: Avvia monitoraggio + attiva tutte le aste
- **"Ferma Tutti"**: Ferma monitoraggio + disattiva tutte le aste
- **"Pausa Tutti"**: Mette in pausa tutte le aste attive (monitoraggio rimane attivo)
## Comportamento Atteso
### ? Scenario 1: Avvio Singola Asta (Monitoraggio Fermo)
1. Nessuna asta attiva
2. Clic su "Avvia" su Asta A
3. ? Monitoraggio si avvia automaticamente
4. ? Asta A inizia ad essere monitorata
5. ? Log: `[AUTO-START] Monitoraggio avviato automaticamente per asta: Asta A`
### ? Scenario 2: Avvio Singola Asta (Monitoraggio Già Attivo)
1. Asta A già attiva
2. Clic su "Avvia" su Asta B
3. ? Monitoraggio già attivo (non viene riavviato)
4. ? Asta B inizia ad essere monitorata
5. ? Log: `[START] Asta avviata: Asta B`
### ? Scenario 3: Stop Ultima Asta
1. Solo Asta A è attiva
2. Clic su "Ferma" su Asta A
3. ? Asta A viene fermata
4. ? Monitoraggio si ferma automaticamente (nessuna asta attiva)
5. ? Log: `[AUTO-STOP] Monitoraggio fermato: nessuna asta attiva`
### ? Scenario 4: Stop Asta (Altre Attive)
1. Asta A e Asta B attive
2. Clic su "Ferma" su Asta A
3. ? Asta A viene fermata
4. ? Monitoraggio rimane attivo (Asta B ancora attiva)
5. ? Log: `[STOP] Asta fermata: Asta A`
### ? Scenario 5: Avvia Tutti
1. Asta A e Asta B ferme
2. Clic su "Avvia Tutti"
3. ? Monitoraggio si avvia
4. ? Tutte le aste vengono attivate
5. ? Log: `[START] Monitoraggio avviato!` + `[START ALL] Tutte le aste avviate/riprese`
### ? Scenario 6: Ferma Tutti
1. Alcune aste attive
2. Clic su "Ferma Tutti"
3. ? Tutte le aste vengono fermate
4. ? Monitoraggio si ferma
5. ? Log: `[STOP ALL] Monitoraggio fermato e tutte le aste arrestate`
## Vantaggi della Soluzione
### ?? 1. Maggiore Flessibilità
- Puoi avviare solo le aste che ti interessano
- Non serve più avviare tutte le aste per monitorarne una
### ?? 2. Risparmio Risorse
- Il monitoraggio si ferma automaticamente quando non serve
- Polling solo sulle aste effettivamente attive
### ?? 3. UX Migliorata
- Comportamento più intuitivo
- Non serve capire la differenza tra "Avvia Tutti" e "Avvia" singolo
### ?? 4. Logging Chiaro
- Si vede esattamente quando il monitoraggio parte/si ferma
- Distingue tra start manuale e automatico
## File Modificati
1. ? `Core\MainWindow.Commands.cs`
- Aggiunto auto-start in `ExecuteGridStart`
- Aggiunto auto-stop in `ExecuteGridStop`
- Aggiunta importazione `System.Linq` e `AutoBidder.Utilities`
- Migliorato logging con `LogLevel`
2. ? `Core\MainWindow.ButtonHandlers.cs`
- Migliorato logging in `StartButton_Click`
- Migliorato logging in `StopButton_Click`
- Migliorato logging in `PauseAllButton_Click`
## Note Tecniche
### Perché Auto-Start è Sicuro?
1. **Idempotente**: `AuctionMonitor.Start()` controlla se è già attivo
2. **Thread-safe**: Il lock interno previene race conditions
3. **Logging**: Si vede esattamente cosa succede
```csharp
public void Start()
{
if (_monitoringTask != null && !_monitoringTask.IsCompleted)
{
OnLog?.Invoke("[WARN] Monitoraggio gia' attivo");
return; // Non fa nulla se già attivo
}
// ...
}
```
### Perché Auto-Stop è Sicuro?
1. **Controlla tutte le aste**: Verifica se ci sono altre aste attive prima di fermare
2. **Non forza**: Se ci sono altre aste attive, non ferma il monitoraggio
3. **Graceful**: Usa `Stop()` che fa cleanup corretto
## Test di Verifica
- [x] Avviare singola asta da griglia (monitoraggio fermo)
- [x] Avviare seconda asta (monitoraggio già attivo)
- [x] Fermare asta (altre attive) ? Monitoraggio continua
- [x] Fermare ultima asta ? Monitoraggio si ferma
- [x] "Avvia Tutti" continua a funzionare
- [x] "Ferma Tutti" continua a funzionare
- [x] "Pausa Tutti" continua a funzionare
- [x] Pulsanti di griglia abilitati/disabilitati correttamente
- [x] Log mostra AUTO-START/AUTO-STOP
---
**Data Fix**: 2025
**Versione**: 4.0+
**Issue**: Pulsante "Avvia" singolo non funzionava senza "Avvia Tutti"
**Status**: ? RISOLTO
## Riepilogo
Prima: **Dovevi cliccare "Avvia Tutti" per monitorare anche una sola asta**
Dopo: **Clicchi "Avvia" su un'asta e parte automaticamente il monitoraggio** ??

View File

@@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:AutoBidder.Controls"
Title="AutoBidder v4.0"
Title="AutoBidder 4.0"
Height="800"
Width="1400"
WindowStartupLocation="CenterScreen"
@@ -84,6 +84,7 @@
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Navigation Tabs -->
@@ -120,11 +121,46 @@
Checked="TabImpostazioni_Checked"/>
</StackPanel>
<!-- User Info Panel - SOPRA AutoBidder -->
<Border x:Name="SidebarUserInfoPanel"
Grid.Row="1"
Background="#2D2D30"
BorderBrush="#3E3E42"
BorderThickness="0,1,0,1"
Padding="15,12"
Visibility="Collapsed">
<StackPanel>
<!-- Riga 1: Username + ID -->
<StackPanel Orientation="Horizontal" Margin="0,0,0,4">
<TextBlock x:Name="SidebarUsernameText"
Text="sirbietole23"
Foreground="#00D800"
FontSize="11"
FontWeight="SemiBold"
TextTrimming="CharacterEllipsis"/>
</StackPanel>
<!-- Riga 2: ID + Email -->
<StackPanel Orientation="Vertical">
<TextBlock x:Name="SidebarUserIdText"
Text="ID: 6707664"
Foreground="#666666"
FontSize="9"
Margin="0,0,0,2"/>
<TextBlock x:Name="SidebarUserEmailText"
Text="email@email.com"
Foreground="#999999"
FontSize="9"
TextTrimming="CharacterEllipsis"/>
</StackPanel>
</StackPanel>
</Border>
<!-- App Title & Logo (Bottom) -->
<Border Grid.Row="1" Background="#007ACC" Padding="15,20" BorderBrush="#3E3E42" BorderThickness="0,1,0,0">
<Border Grid.Row="2" Background="#007ACC" Padding="15,20" BorderBrush="#3E3E42" BorderThickness="0,1,0,0">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Image Source="Icon/favicon.ico" Width="24" Height="24" Margin="0,0,10,0"/>
<TextBlock Text="AutoBidder v4.0"
<TextBlock Text="AutoBidder"
FontSize="16"
FontWeight="Bold"
Foreground="White"
@@ -139,6 +175,10 @@
<!-- Aste Attive Panel -->
<controls:AuctionMonitorControl x:Name="AuctionMonitor"
Visibility="Visible"
GridStartCommand="{Binding GridStartCommand}"
GridPauseCommand="{Binding GridPauseCommand}"
GridStopCommand="{Binding GridStopCommand}"
GridBidCommand="{Binding GridBidCommand}"
StartClicked="AuctionMonitor_StartClicked"
PauseAllClicked="AuctionMonitor_PauseAllClicked"
StopClicked="AuctionMonitor_StopClicked"
@@ -150,7 +190,12 @@
ResetSettingsClicked="AuctionMonitor_ResetSettingsClicked"
ClearBiddersClicked="AuctionMonitor_ClearBiddersClicked"
ClearLogClicked="AuctionMonitor_ClearLogClicked"
ClearGlobalLogClicked="AuctionMonitor_ClearGlobalLogClicked"/>
ClearGlobalLogClicked="AuctionMonitor_ClearGlobalLogClicked"
BidBeforeDeadlineMsChanged="AuctionMonitor_BidBeforeDeadlineMsChanged"
CheckAuctionOpenChanged="AuctionMonitor_CheckAuctionOpenChanged"
MinPriceChanged="AuctionMonitor_MinPriceChanged"
MaxPriceChanged="AuctionMonitor_MaxPriceChanged"
MaxClicksChanged="AuctionMonitor_MaxClicksChanged"/>
<!-- Browser Panel -->
<controls:BrowserControl x:Name="Browser"

View File

@@ -22,6 +22,7 @@ namespace AutoBidder
// UI State
private AuctionViewModel? _selectedAuction;
private bool _isAutomationActive = false;
private bool _isUpdatingSelection = false; // Previene loop di salvataggio
// Commands (initialized in MainWindow.Commands.cs)
public RelayCommand? StartAllCommand { get; private set; }
@@ -47,9 +48,8 @@ namespace AutoBidder
public DataGrid SelectedAuctionBiddersGrid => AuctionMonitor.SelectedAuctionBiddersGrid;
public Button ClearBiddersButton => AuctionMonitor.ClearBiddersButton;
public TextBlock MonitorateTitle => AuctionMonitor.MonitorateTitle;
public TextBlock UsernameText => AuctionMonitor.UsernameText;
public TextBox SelectedTimerClick => AuctionMonitor.SelectedTimerClick;
public TextBox SelectedDelayMs => AuctionMonitor.SelectedDelayMs;
public TextBox SelectedBidBeforeDeadlineMs => AuctionMonitor.SelectedBidBeforeDeadlineMs;
public CheckBox SelectedCheckAuctionOpen => AuctionMonitor.SelectedCheckAuctionOpen;
public TextBox SelectedMinPrice => AuctionMonitor.SelectedMinPrice;
public TextBox SelectedMaxPrice => AuctionMonitor.SelectedMaxPrice;
public TextBox SelectedMaxClicks => AuctionMonitor.SelectedMaxClicks;
@@ -78,12 +78,12 @@ namespace AutoBidder
public CheckBox RemoveAfterExport => Settings.RemoveAfterExport;
public CheckBox OverwriteExisting => Settings.OverwriteExisting;
// Impostazioni predefinite aste
public TextBox DefaultTimerClick => Settings.DefaultTimerClick;
public TextBox DefaultDelayMs => Settings.DefaultDelayMs;
public TextBox DefaultMinPrice => Settings.DefaultMinPrice;
public TextBox DefaultMaxPrice => Settings.DefaultMaxPrice;
public TextBox DefaultMaxClicks => Settings.DefaultMaxClicks;
// Impostazioni predefinite aste - AGGIORNATO
public TextBox DefaultBidBeforeDeadlineMs => Settings.DefaultBidBeforeDeadlineMsTextBox;
public CheckBox DefaultCheckAuctionOpen => Settings.DefaultCheckAuctionOpenCheckBox;
public TextBox DefaultMinPrice => Settings.DefaultMinPriceTextBox;
public TextBox DefaultMaxPrice => Settings.DefaultMaxPriceTextBox;
public TextBox DefaultMaxClicks => Settings.DefaultMaxClicksTextBox;
public MainWindow()
{

View File

@@ -16,8 +16,18 @@ namespace AutoBidder.Models
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)
/// <summary>
/// Millisecondi prima della scadenza (deadline) per inviare la puntata.
/// Es: 200ms = punta 200ms prima che il timer raggiunga 0.
/// </summary>
public int BidBeforeDeadlineMs { get; set; } = 200;
/// <summary>
/// Se true, verifica che l'asta sia ancora aperta prima di piazzare la puntata.
/// Aggiunge una chiamata API extra ma aumenta la sicurezza.
/// </summary>
public bool CheckAuctionOpenBeforeBid { get; set; } = false;
public double MinPrice { get; set; } = 0;
public double MaxPrice { get; set; } = 0;
public int MinResets { get; set; } = 0; // Numero minimo reset prima di puntare
@@ -40,8 +50,6 @@ namespace AutoBidder.Models
public List<BidHistory> BidHistory { get; set; } = new List<BidHistory>();
public Dictionary<string, BidderInfo> BidderStats { get; set; } = new(StringComparer.OrdinalIgnoreCase);
// Legacy (deprecato) - removed `Bidders` dictionary; use `BidderStats` instead
// Log per-asta (non serializzato)
[System.Text.Json.Serialization.JsonIgnore]
public List<string> AuctionLog { get; set; } = new();
@@ -50,17 +58,12 @@ namespace AutoBidder.Models
[System.Text.Json.Serialization.JsonIgnore]
public bool IsAttackInProgress { get; set; } = false;
// Quando viene considerato il "final attack" (secondi)
// Se il timer dell'asta scende sotto questo valore, viene eseguita la puntata finale.
// Default 0.8s per anticipare leggermente rispetto al valore precedente di 0.5s.
public double FinalAttackThresholdSec { get; set; } = 0.8;
/// <summary>
/// Aggiunge una voce al log dell'asta
/// </summary>
public void AddLog(string message)
{
var entry = $"{DateTime.Now:HH:mm:ss} - {message}";
var entry = $"{DateTime.Now:HH:mm:ss.fff} - {message}";
AuctionLog.Add(entry);
// Mantieni solo ultimi 500 log

View File

@@ -3,7 +3,7 @@ using System;
namespace AutoBidder.Models
{
/// <summary>
/// Sessione Bidoo con token di autenticazione
/// Sessione Bidoo con token di autenticazione e dati utente completi
/// </summary>
public class BidooSession
{
@@ -24,11 +24,31 @@ namespace AutoBidder.Models
/// </summary>
public string Username { get; set; } = "";
/// <summary>
/// Email dell'utente
/// </summary>
public string Email { get; set; } = "";
/// <summary>
/// Numero di telefono dell'utente
/// </summary>
public string Phone { get; set; } = "";
/// <summary>
/// ID numerico univoco dell'utente
/// </summary>
public int UserId { get; set; } = 0;
/// <summary>
/// Puntate rimanenti sull'account
/// </summary>
public int RemainingBids { get; set; } = 0;
/// <summary>
/// Credito disponibile nel Bidoo Shop (€)
/// </summary>
public double ShopCredit { get; set; } = 0.0;
/// <summary>
/// Timestamp ultimo aggiornamento info account
/// </summary>
@@ -46,3 +66,4 @@ namespace AutoBidder.Models
}
}

View File

@@ -9,7 +9,7 @@ namespace AutoBidder.Services
{
/// <summary>
/// Servizio centrale per monitoraggio aste
/// Solo HTTP, nessuna modalità, browser o multi-click
/// Sistema di timing ottimizzato: punta solo se necessario, poco prima della scadenza
/// </summary>
public class AuctionMonitor
{
@@ -21,13 +21,12 @@ namespace AutoBidder.Services
public event Action<AuctionState>? OnAuctionUpdated;
public event Action<AuctionInfo, BidResult>? OnBidExecuted;
public event Action<string>? OnLog;
public event Action<string>? OnResetCountChanged; // Notifica cambio contatore reset
public event Action<string>? OnResetCountChanged;
public AuctionMonitor()
{
_apiClient = new BidooApiClient();
// Subscribe to detailed per-auction logs from API client
_apiClient.OnAuctionLog += (auctionId, message) =>
{
try
@@ -45,59 +44,38 @@ namespace AutoBidder.Services
};
}
/// <summary>
/// Inizializza sessione con token di autenticazione
/// </summary>
public void InitializeSession(string authToken, string username)
{
_apiClient.InitializeSession(authToken, username);
OnLog?.Invoke($"[OK] Sessione configurata per: {username}");
}
/// <summary>
/// Inizializza sessione con cookie (fallback legacy)
/// </summary>
public void InitializeSessionWithCookie(string cookieString, string username)
{
_apiClient.InitializeSessionWithCookie(cookieString, username);
OnLog?.Invoke($"[OK] Sessione configurata (cookie) per: {username}");
}
/// <summary>
/// Aggiorna info utente (puntate rimanenti)
/// </summary>
public async Task<bool> UpdateUserInfoAsync()
{
return await _apiClient.UpdateUserInfoAsync();
}
/// <summary>
/// Ottieni sessione corrente
/// </summary>
public BidooSession GetSession()
{
return _apiClient.GetSession();
}
/// <summary>
/// Ottiene dati utente (nome, puntate residue, saldo, id) tramite chiamata AJAX leggera
/// </summary>
public async Task<UserData?> GetUserDataAsync()
{
return await _apiClient.GetUserDataAsync();
}
/// <summary>
/// Ottiene info banner utente (aste vinte, bonus, ecc.) tramite chiamata AJAX
/// </summary>
public async Task<UserBannerInfo?> GetUserBannerInfoAsync()
{
return await _apiClient.GetUserBannerInfoAsync();
}
/// <summary>
/// Estrae nome utente e puntate residue dall'HTML di bids_history.php
/// </summary>
public async Task<UserData?> GetUserDataFromHtmlAsync()
{
return await _apiClient.GetUserDataFromHtmlAsync();
@@ -167,9 +145,6 @@ namespace AutoBidder.Services
List<AuctionInfo> activeAuctions;
lock (_auctions)
{
// Filtra aste che devono ancora essere monitorate
// Include aste attive anche se messe in pausa: vogliamo continuare a monitorarle
// ma non inviare puntate per quelle in pausa.
activeAuctions = _auctions.Where(a =>
a.IsActive &&
!IsAuctionTerminated(a)
@@ -189,16 +164,14 @@ namespace AutoBidder.Services
// Ottimizzazione polling per aste in pausa
bool anyPaused = false;
DateTime now = DateTime.Now;
int pauseDelayMs = 1000; // default
int pauseDelayMs = 1000;
foreach (var a in activeAuctions)
{
if (a.IsPaused)
{
anyPaused = true;
// Se tra le 00:00 e le 09:55 polling ogni 60s
if (now.Hour < 9 || (now.Hour == 9 && now.Minute < 55))
pauseDelayMs = 60000;
// Negli ultimi 5 minuti prima delle 10 polling ogni 5s
else if (now.Hour == 9 && now.Minute >= 55)
pauseDelayMs = 5000;
}
@@ -218,13 +191,14 @@ namespace AutoBidder.Services
int delayMs = lowestTimer switch
{
< 1 => 5, // Iper-veloce: polling ogni 5ms (0-1s rimanenti)
< 2 => 20, // Ultra-veloce: polling ogni 20ms (1-2s)
< 3 => 50, // Molto veloce: polling ogni 50ms (2-3s)
< 5 => 100, // Veloce: polling ogni 100ms (3-5s)
< 10 => 200, // Medio: polling ogni 200ms (5-10s)
< 30 => 500, // Lento: polling ogni 500ms (10-30s)
_ => 1000 // Molto lento: polling ogni 1s (>30s)
< 0.5 => 10, // Ultra-critico: polling ogni 10ms
< 1 => 20, // Iper-veloce: polling ogni 20ms
< 2 => 50, // Ultra-veloce: polling ogni 50ms
< 3 => 100, // Molto veloce: polling ogni 100ms
< 5 => 150, // Veloce: polling ogni 150ms
< 10 => 300, // Medio: polling ogni 300ms
< 30 => 500, // Lento: polling ogni 500ms
_ => 1000 // Molto lento: polling ogni 1s
};
await Task.Delay(delayMs, token);
@@ -241,16 +215,11 @@ namespace AutoBidder.Services
}
}
/// <summary>
/// Verifica se un'asta è terminata e non deve più essere monitorata
/// </summary>
private bool IsAuctionTerminated(AuctionInfo auction)
{
// Se l'ultima entry nello storico indica uno stato finale, ferma polling
var lastHistory = auction.BidHistory.LastOrDefault();
if (lastHistory != null)
{
// Controlla se c'è una nota che indica fine asta
if (lastHistory.Notes != null &&
(lastHistory.Notes.Contains("VINTA") ||
lastHistory.Notes.Contains("Persa") ||
@@ -267,7 +236,6 @@ namespace AutoBidder.Services
{
try
{
// Poll tramite API Bidoo (passa anche l'URL originale per referer corretto)
var state = await _apiClient.PollAuctionStateAsync(auction.AuctionId, auction.OriginalUrl, token);
if (state == null)
@@ -277,10 +245,8 @@ namespace AutoBidder.Services
return;
}
// Aggiorna la latenza per la DataGrid
auction.PollingLatencyMs = state.PollingLatencyMs;
// Se l'asta è terminata, segnala e disattiva polling
if (state.Status == AuctionStatus.EndedWon ||
state.Status == AuctionStatus.EndedLost ||
state.Status == AuctionStatus.Closed)
@@ -288,13 +254,10 @@ namespace AutoBidder.Services
string statusMsg = state.Status == AuctionStatus.EndedWon ? "VINTA" :
state.Status == AuctionStatus.EndedLost ? "Persa" : "Chiusa";
// Mark auction inactive immediately to stop further polling
auction.IsActive = false;
auction.AddLog($"[ASTA TERMINATA] {statusMsg}");
OnLog?.Invoke($"[FINE] [{auction.AuctionId}] Asta {statusMsg} - Polling fermato");
// Aggiungi entry nello storico per marcare come terminata
auction.BidHistory.Add(new BidHistory
{
Timestamp = DateTime.UtcNow,
@@ -305,222 +268,148 @@ namespace AutoBidder.Services
Notes = $"Asta {statusMsg}"
});
// Notifica UI e fermati
OnAuctionUpdated?.Invoke(state);
return;
}
// Log stato solo per aste attive (riduci spam) - keep detailed per-auction log
if (state.Status == AuctionStatus.Running)
{
// Detailed info stays in auction log only
auction.AddLog($"API OK - Timer: {state.Timer:F2}s, EUR{state.Price:F2}, {state.LastBidder}, {state.PollingLatencyMs}ms");
// Log RIMOSSO per ridurre verbosità - polling continuo non necessita log
// Solo eventi importanti (bid, reset, errori) vengono loggati
}
else if (state.Status == AuctionStatus.Paused)
{
auction.AddLog($"[PAUSA] Asta in pausa - Timer: {state.Timer:F2}s, EUR{state.Price:F2}");
// Log solo primo cambio stato, non ad ogni polling
var lastLog = auction.AuctionLog.LastOrDefault();
if (lastLog == null || !lastLog.Contains("[PAUSA]"))
{
auction.AddLog($"[PAUSA] Asta in pausa - Timer: {state.Timer:F3}s, EUR{state.Price:F2}");
}
}
// Notifica aggiornamento UI
OnAuctionUpdated?.Invoke(state);
// Aggiorna storico e bidders
UpdateAuctionHistory(auction, state);
// FINAL-ATTACK PROTOCOL: when the remaining timer is below our latency threshold (<= 0.5s)
// we stop the normal polling loop for this auction and send a single minimal bid request.
if (state.Status == AuctionStatus.Running && !auction.IsPaused && ShouldBid(auction, state))
// NUOVA LOGICA: Punta solo se siamo vicini alla deadline E nessun altro ha appena puntato
if (state.Status == AuctionStatus.Running && !auction.IsPaused && !auction.IsAttackInProgress)
{
// Use latency threshold (0.5s default) - treat as critical window
var latencyThreshold = 0.5; // seconds
if (!auction.IsAttackInProgress && state.Timer <= latencyThreshold)
if (ShouldBid(auction, state))
{
// Quick re-poll strategy: perform a couple of fast re-polls to confirm that the timer
// is still in the critical window and that the lastBidder did not change.
auction.IsAttackInProgress = true;
AuctionState? lastConfirmedState = state;
try
{
auction.AddLog($"[ATTACK] Candidate final attack: Timer {state.Timer:F3}s <= {latencyThreshold}s. Performing quick re-polls to confirm...");
int attempts = 2;
for (int i = 0; i < attempts; i++)
{
try
{
using var cts = CancellationTokenSource.CreateLinkedTokenSource(token);
// small timeout for quick verification
cts.CancelAfter(TimeSpan.FromMilliseconds(400));
var quickState = await _apiClient.PollAuctionStateAsync(auction.AuctionId, auction.OriginalUrl, cts.Token);
if (quickState != null)
{
auction.AddLog($"[ATTACK] Quick re-poll #{i + 1}: Timer {quickState.Timer:F3}s, Bidder: {quickState.LastBidder}");
// If bidder changed to someone else, abort
if (!string.Equals(quickState.LastBidder, state.LastBidder, StringComparison.OrdinalIgnoreCase))
{
auction.AddLog($"[ATTACK] Aborting final attack: last bidder changed from '{state.LastBidder}' to '{quickState.LastBidder}'");
return;
}
// If timer increased above threshold, abort
if (quickState.Timer > latencyThreshold)
{
auction.AddLog($"[ATTACK] Aborting final attack: quickState.Timer {quickState.Timer:F3}s > threshold {latencyThreshold}s");
return;
}
// Confirmed critical window
lastConfirmedState = quickState;
break; // proceed to place bid
}
else
{
auction.AddLog($"[ATTACK] Quick re-poll #{i + 1} returned no data (timeout/error).\n");
}
}
catch (Exception exQuick)
{
auction.AddLog($"[ATTACK] Quick re-poll #{i + 1} exception: {exQuick.GetType().Name} - {exQuick.Message}");
}
// tiny delay between attempts
await Task.Delay(30, token);
}
// If no quickState confirmed but initial state indicated critical window, proceed but warn
if (lastConfirmedState == null)
{
auction.AddLog("[ATTACK] No quick re-poll confirmed state. Proceeding with final bid based on initial observation (risk of false positive).");
}
// Place final bid using the same request format as manual bids to mimic manual behavior
auction.AddLog($"[ATTACK] Executing final bid (using manual-format payload) for {auction.AuctionId} (confirmed: { (lastConfirmedState != null) })...");
var finalResult = await _apiClient.PlaceBidAsync(auction.AuctionId, auction.OriginalUrl);
auction.LastClickAt = DateTime.UtcNow;
OnBidExecuted?.Invoke(auction, finalResult);
if (finalResult.Success)
{
auction.AddLog($"[OK] Final bid OK: {finalResult.LatencyMs}ms -> EUR{finalResult.NewPrice:F2}");
OnLog?.Invoke($"[OK] Puntata riuscita su {auction.Name} ({auction.AuctionId}): {finalResult.LatencyMs}ms");
}
else
{
auction.AddLog($"[FAIL] Final bid failed: {finalResult.Error}");
OnLog?.Invoke($"[FAIL] Puntata fallita su {auction.Name} ({auction.AuctionId}): {finalResult.Error}");
}
auction.BidHistory.Add(new BidHistory
{
Timestamp = finalResult.Timestamp,
EventType = finalResult.Success ? BidEventType.MyBid : BidEventType.OpponentBid,
Bidder = "Tu",
Price = state.Price,
Timer = state.Timer,
LatencyMs = finalResult.LatencyMs,
Success = finalResult.Success,
Notes = finalResult.Success ? $"EUR{finalResult.NewPrice:F2}" : finalResult.Error
});
}
finally
{
auction.IsAttackInProgress = false;
}
return;
}
// Otherwise fallback to normal early-bid behavior
if (Math.Abs(state.Timer) < 0.001)
{
// Put detailed info into auction log but avoid noisy global log lines
auction.AddLog($"[TRIGGER] Timer 0, attendo delay {auction.DelayMs}ms e invio puntata direttamente...");
if (auction.DelayMs > 0)
await Task.Delay(auction.DelayMs, token);
// Direct bid - API client already writes detailed request/response into auction.AddLog via subscription
var result = await _apiClient.PlaceBidAsync(auction.AuctionId);
auction.LastClickAt = DateTime.UtcNow;
OnBidExecuted?.Invoke(auction, result);
// Add concise global log (single line) and keep extended details inside auction log
if (result.Success)
{
auction.AddLog($"[OK] PUNTATA OK: {result.LatencyMs}ms -> EUR{result.NewPrice:F2}");
OnLog?.Invoke($"[OK] Puntata riuscita su {auction.Name} ({auction.AuctionId}): {result.LatencyMs}ms");
}
else
{
auction.AddLog($"[FAIL] PUNTATA FALLITA: {result.Error}");
OnLog?.Invoke($"[FAIL] Puntata fallita su {auction.Name} ({auction.AuctionId}): {result.Error}");
}
auction.BidHistory.Add(new BidHistory
{
Timestamp = result.Timestamp,
EventType = result.Success ? BidEventType.MyBid : BidEventType.OpponentBid,
Bidder = "Tu",
Price = state.Price,
Timer = state.Timer,
LatencyMs = result.LatencyMs,
Success = result.Success,
Notes = result.Success ? $"EUR{result.NewPrice:F2}" : result.Error
});
}
else
{
// Normal early-bid path: schedule immediate delay then place bid
auction.AddLog($"[TRIGGER] CONDIZIONI OK - Timer {state.Timer:F2}s <= {auction.TimerClick}s");
if (auction.DelayMs > 0)
{
await Task.Delay(auction.DelayMs, token);
}
var result = await _apiClient.PlaceBidAsync(auction.AuctionId);
auction.LastClickAt = DateTime.UtcNow;
OnBidExecuted?.Invoke(auction, result);
if (result.Success)
{
auction.AddLog($"[OK] PUNTATA OK: {result.LatencyMs}ms -> EUR{result.NewPrice:F2}");
OnLog?.Invoke($"[OK] Puntata riuscita su {auction.Name} ({auction.AuctionId}): {result.LatencyMs}ms");
}
else
{
auction.AddLog($"[FAIL] PUNTATA FALLITA: {result.Error}");
OnLog?.Invoke($"[FAIL] Puntata fallita su {auction.Name} ({auction.AuctionId}): {result.Error}");
}
auction.BidHistory.Add(new BidHistory
{
Timestamp = result.Timestamp,
EventType = result.Success ? BidEventType.MyBid : BidEventType.OpponentBid,
Bidder = "Tu",
Price = state.Price,
Timer = state.Timer,
LatencyMs = result.LatencyMs,
Success = result.Success,
Notes = result.Success ? $"EUR{result.NewPrice:F2}" : result.Error
});
await ExecuteBidStrategy(auction, state, token);
}
}
}
catch (Exception ex)
{
auction.AddLog($"[EXCEPTION] ERRORE: {ex.Message}");
auction.AddLog($"[EXCEPTION] {ex.Message}");
OnLog?.Invoke($"[EXCEPTION] [{auction.AuctionId}] {ex.Message}");
}
}
/// <summary>
/// Strategia di puntata ottimizzata: punta solo quando necessario
/// </summary>
private async Task ExecuteBidStrategy(AuctionInfo auction, AuctionState state, CancellationToken token)
{
// Calcola il tempo rimanente in millisecondi
double timerMs = state.Timer * 1000;
// Se siamo nella finestra di puntata (timer <= BidBeforeDeadlineMs)
if (timerMs <= auction.BidBeforeDeadlineMs)
{
auction.IsAttackInProgress = true;
try
{
auction.AddLog($"[STRATEGIA] Finestra di puntata raggiunta: {timerMs:F0}ms <= {auction.BidBeforeDeadlineMs}ms");
// Controlla se qualcun altro ha puntato di recente
var lastBidTime = GetLastBidTime(auction, state.LastBidder);
if (lastBidTime.HasValue)
{
var timeSinceLastBid = DateTime.UtcNow - lastBidTime.Value;
if (timeSinceLastBid.TotalMilliseconds < 500)
{
auction.AddLog($"[STRATEGIA] Puntata recente di {state.LastBidder} ({timeSinceLastBid.TotalMilliseconds:F0}ms fa), attendo...");
return;
}
}
// Esegui la puntata
await ExecuteBid(auction, state, token);
}
finally
{
auction.IsAttackInProgress = false;
}
}
}
/// <summary>
/// Esegue la puntata con verifica opzionale dello stato dell'asta
/// </summary>
private async Task ExecuteBid(AuctionInfo auction, AuctionState state, CancellationToken token)
{
try
{
// Se richiesto, verifica prima che l'asta sia ancora aperta
if (auction.CheckAuctionOpenBeforeBid)
{
auction.AddLog("[PRE-CHECK] Verifica stato asta...");
var preCheckState = await _apiClient.PollAuctionStateAsync(auction.AuctionId, auction.OriginalUrl, token);
if (preCheckState == null)
{
auction.AddLog("[PRE-CHECK] FALLITO: Nessuna risposta");
return;
}
if (preCheckState.Status != AuctionStatus.Running)
{
auction.AddLog($"[PRE-CHECK] ABORTITO: Asta non running (status: {preCheckState.Status})");
return;
}
auction.AddLog($"[PRE-CHECK] OK - Timer: {preCheckState.Timer:F3}s");
}
// Esegui la puntata
var result = await _apiClient.PlaceBidAsync(auction.AuctionId, auction.OriginalUrl);
auction.LastClickAt = DateTime.UtcNow;
OnBidExecuted?.Invoke(auction, result);
if (result.Success)
{
auction.AddLog($"[BID OK] Latenza: {result.LatencyMs}ms -> EUR{result.NewPrice:F2}");
OnLog?.Invoke($"[OK] Puntata riuscita su {auction.Name} ({auction.AuctionId}): {result.LatencyMs}ms");
}
else
{
auction.AddLog($"[BID FAIL] {result.Error}");
OnLog?.Invoke($"[FAIL] Puntata fallita su {auction.Name} ({auction.AuctionId}): {result.Error}");
}
auction.BidHistory.Add(new BidHistory
{
Timestamp = result.Timestamp,
EventType = result.Success ? BidEventType.MyBid : BidEventType.OpponentBid,
Bidder = "Tu",
Price = state.Price,
Timer = state.Timer,
LatencyMs = result.LatencyMs,
Success = result.Success,
Notes = result.Success ? $"EUR{result.NewPrice:F2}" : result.Error
});
}
catch (Exception ex)
{
auction.AddLog($"[BID EXCEPTION] {ex.Message}");
OnLog?.Invoke($"[BID EXCEPTION] [{auction.AuctionId}] {ex.Message}");
}
}
private bool ShouldBid(AuctionInfo auction, AuctionState state)
{
// Timer check
if (state.Timer > auction.TimerClick)
return false;
// Price check
if (auction.MinPrice > 0 && state.Price < auction.MinPrice)
return false;
@@ -528,34 +417,57 @@ namespace AutoBidder.Services
if (auction.MaxPrice > 0 && state.Price > auction.MaxPrice)
return false;
// Cooldown check (evita click multipli ravvicinati)
// Reset count check
if (auction.MinResets > 0 && auction.ResetCount < auction.MinResets)
return false;
if (auction.MaxResets > 0 && auction.ResetCount >= auction.MaxResets)
return false;
// Max clicks check
int myBidsCount = auction.BidHistory.Count(b => b.EventType == BidEventType.MyBid);
if (auction.MaxClicks > 0 && myBidsCount >= auction.MaxClicks)
return false;
// Cooldown check (evita puntate multiple ravvicinate)
if (auction.LastClickAt.HasValue)
{
var timeSinceLastClick = DateTime.UtcNow - auction.LastClickAt.Value;
if (timeSinceLastClick.TotalSeconds < 1)
if (timeSinceLastClick.TotalMilliseconds < 800)
return false;
}
return true;
}
private DateTime? GetLastBidTime(AuctionInfo auction, string bidder)
{
if (string.IsNullOrEmpty(bidder))
return null;
if (auction.BidderStats.TryGetValue(bidder, out var info))
{
return info.LastBidTime;
}
return null;
}
private void UpdateAuctionHistory(AuctionInfo auction, AuctionState state)
{
// Traccia l'ultima puntata per rilevare cambi
var lastHistory = auction.BidHistory.LastOrDefault();
var lastPrice = lastHistory?.Price ?? 0;
var lastBidder = lastHistory?.Bidder;
bool isNewBid = false;
// Nuova puntata = CAMBIO PREZZO (più affidabile)
// Ogni incremento di prezzo significa che qualcuno ha puntato
// Nuova puntata = CAMBIO PREZZO
if (state.Price > lastPrice && state.Price > 0)
{
isNewBid = true;
}
// Fallback: cambio utente (se il prezzo è uguale ma l'utente cambia)
// Fallback: cambio utente
if (!isNewBid &&
!string.IsNullOrEmpty(lastBidder) &&
!string.IsNullOrEmpty(state.LastBidder) &&
@@ -592,7 +504,6 @@ namespace AutoBidder.Services
auction.BidderStats[state.LastBidder].LastBidTime = DateTime.UtcNow;
}
// Notifica cambio reset count per aggiornare UI
OnResetCountChanged?.Invoke(auction.AuctionId);
}
}

View File

@@ -90,7 +90,7 @@ namespace AutoBidder.Services
if (!string.IsNullOrWhiteSpace(_session.CookieString))
{
request.Headers.Add("Cookie", _session.CookieString);
Log("[AUTH] Using full cookie string", auctionId);
// Log rimosso per ridurre verbosità
}
else
{
@@ -98,7 +98,6 @@ namespace AutoBidder.Services
}
// 2. HEADERS BROWSER-LIKE (anti-detection)
// User-Agent realistico (Chrome su Windows)
request.Headers.Add("User-Agent",
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36");
@@ -131,7 +130,7 @@ namespace AutoBidder.Services
request.Headers.Add("Referer", "https://it.bidoo.com/");
}
Log("[HEADERS] Browser-like headers added (anti-bot)", auctionId);
// Log rimosso per ridurre verbosità - headers sempre aggiunti
}
/// <summary>
@@ -321,7 +320,8 @@ namespace AutoBidder.Services
{
try
{
var url = "https://it.bidoo.com/ajax/get_auction_bids_info_banner.php";
// OTTIMIZZATO: Usa buy_bids.php che contiene tutti i dati in un'unica chiamata
var url = "https://it.bidoo.com/buy_bids.php";
Log($"[USER INFO REQUEST] GET {url}");
@@ -332,24 +332,133 @@ namespace AutoBidder.Services
var response = await _httpClient.SendAsync(request);
var latency = (int)(DateTime.UtcNow - startTime).TotalMilliseconds;
Log($"[USER INFO RESPONSE] Status: {(int)response.StatusCode} {response.StatusCode}");
Log($"[USER INFO RESPONSE] Latency: {latency}ms");
var responseText = await response.Content.ReadAsStringAsync();
Log($"[USER INFO RESPONSE] Body length: {responseText.Length}");
Log($"[USER INFO RESPONSE] Status: {(int)response.StatusCode} {response.StatusCode}, Latency: {latency}ms");
if (!response.IsSuccessStatusCode)
{
Log($"[USER INFO ERROR] HTTP {response.StatusCode}");
Log($"[USER INFO ERROR] HTTP {response.StatusCode} - Cookie potrebbe essere scaduto o non valido");
return false;
}
var html = await response.Content.ReadAsStringAsync();
Log($"[USER INFO RESPONSE] Body length: {html.Length} chars");
// Verifica se la risposta contiene HTML valido
if (html.Length < 100)
{
Log($"[USER INFO ERROR] Risposta troppo corta ({html.Length} chars) - possibile errore server");
return false;
}
// Parsa l'oggetto JavaScript BidooCnf.userObj
bool foundData = false;
// Estrai ID utente: BidooCnf.userObj.id = '6707664';
var idMatch = System.Text.RegularExpressions.Regex.Match(html, @"BidooCnf\.userObj\.id\s*=\s*'([^']+)'");
if (idMatch.Success)
{
if (int.TryParse(idMatch.Groups[1].Value, out int userId))
{
_session.UserId = userId;
Log($"[USER INFO PARSED] User ID: {userId}");
foundData = true;
}
}
else
{
Log($"[USER INFO WARN] User ID non trovato");
}
// Estrai email: BidooCnf.userObj.email = 'albertobalbo96@gmail.com';
var emailMatch = System.Text.RegularExpressions.Regex.Match(html, @"BidooCnf\.userObj\.email\s*=\s*'([^']+)'");
if (emailMatch.Success)
{
_session.Email = emailMatch.Groups[1].Value;
Log($"[USER INFO PARSED] Email: {_session.Email}");
foundData = true;
}
else
{
Log($"[USER INFO WARN] Email non trovata");
}
// Estrai username: BidooCnf.userObj.username = 'sirbietole23';
var usernameMatch = System.Text.RegularExpressions.Regex.Match(html, @"BidooCnf\.userObj\.username\s*=\s*'([^']+)'");
if (usernameMatch.Success)
{
_session.Username = usernameMatch.Groups[1].Value;
Log($"[USER INFO PARSED] Username: {_session.Username}");
foundData = true;
}
else
{
Log($"[USER INFO WARN] Username non trovato");
}
// Estrai telefono: BidooCnf.userObj.phone = '00393665920653';
var phoneMatch = System.Text.RegularExpressions.Regex.Match(html, @"BidooCnf\.userObj\.phone\s*=\s*'([^']+)'");
if (phoneMatch.Success)
{
_session.Phone = phoneMatch.Groups[1].Value;
Log($"[USER INFO PARSED] Phone: {_session.Phone}");
foundData = true;
}
// Estrai puntate residue dall'HTML: <span id="divSaldoBidMobile">206</span>
var bidsPatterns = new[]
{
@"<span[^>]*id=""divSaldoBidMobile""[^>]*>(\d+)</span>",
@"<span[^>]*id=""divSaldoBidBottom""[^>]*>(\d+)</span>",
@"<span[^>]*id=""divSaldoBid""[^>]*>(\d+)</span>"
};
bool foundBids = false;
foreach (var pattern in bidsPatterns)
{
var bidsMatch = System.Text.RegularExpressions.Regex.Match(html, pattern);
if (bidsMatch.Success && int.TryParse(bidsMatch.Groups[1].Value, out int bids))
{
_session.RemainingBids = bids;
Log($"[USER INFO PARSED] Remaining bids: {bids}");
foundData = true;
foundBids = true;
break;
}
}
if (!foundBids)
{
Log($"[USER INFO WARN] Puntate residue non trovate");
}
// Estrai credito Bidoo Shop: <span class="cbstotal">15.00</span>
var creditMatch = System.Text.RegularExpressions.Regex.Match(html, @"<span[^>]*class=""cbstotal""[^>]*>([\d.]+)</span>");
if (creditMatch.Success && double.TryParse(creditMatch.Groups[1].Value, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out double credit))
{
_session.ShopCredit = credit;
Log($"[USER INFO PARSED] Shop credit: €{credit:F2}");
foundData = true;
}
if (foundData)
{
_session.LastAccountUpdate = DateTime.UtcNow;
Log($"[USER INFO SUCCESS] Dati estratti correttamente da buy_bids.php");
return true;
}
else
{
Log($"[USER INFO ERROR] NESSUN dato trovato nell'HTML - cookie probabilmente non valido");
// Salva snippet per debug
var htmlSnippet = html.Substring(0, Math.Min(500, html.Length)).Replace("\n", " ").Replace("\r", "");
Log($"[USER INFO DEBUG] Snippet HTML: {htmlSnippet}...");
return false;
}
}
catch (Exception ex)
{
Log($"[USER INFO EXCEPTION] {ex.GetType().Name}: {ex.Message}");
Log($"[USER INFO EXCEPTION] StackTrace: {ex.StackTrace}");
return false;
}
}
@@ -363,10 +472,8 @@ namespace AutoBidder.Services
};
try
{
Log($"[BID] Placing bid via direct GET to bid.php", auctionId);
var url = "https://it.bidoo.com/bid.php";
var payload = $"AID={WebUtility.UrlEncode(auctionId)}&sup=0&shock=0";
Log($"[BID REQUEST] GET {url}?{payload}", auctionId);
var getUrl = url + "?" + payload;
var request = new HttpRequestMessage(HttpMethod.Get, getUrl);
var referer = !string.IsNullOrEmpty(auctionUrl) ? auctionUrl : $"https://it.bidoo.com/asta/nome-prodotto-{auctionId}";
@@ -375,19 +482,14 @@ namespace AutoBidder.Services
{
request.Headers.Add("Origin", "https://it.bidoo.com");
}
var startTime = DateTime.UtcNow;
var response = await _httpClient.SendAsync(request);
result.LatencyMs = (int)(DateTime.UtcNow - startTime).TotalMilliseconds;
Log($"[BID RESPONSE] Status: {(int)response.StatusCode} {response.StatusCode}", auctionId);
Log($"[BID RESPONSE] Latency: {result.LatencyMs}ms", auctionId);
var responseText = await response.Content.ReadAsStringAsync();
result.Response = responseText;
Log($"[BID RESPONSE] Body length: {responseText.Length} bytes", auctionId);
if (!string.IsNullOrEmpty(responseText))
{
var preview = responseText.Length > 80 ? responseText.Substring(0, 80) + "..." : responseText;
Log($"[BID RESPONSE] Preview: {preview}", auctionId);
}
if (responseText.StartsWith("ok", StringComparison.OrdinalIgnoreCase))
{
result.Success = true;
@@ -397,50 +499,43 @@ namespace AutoBidder.Services
result.NewPrice = priceIndex * 0.01;
}
// Parse remaining bids from response if present: ok|324|...
var parts2 = responseText.Split('|');
if (parts2.Length > 1 && int.TryParse(parts2[1], out var remaining))
if (parts.Length > 1 && int.TryParse(parts[1], out var remaining))
{
_session.RemainingBids = remaining;
Log($"[BID SUCCESS] ✓ Bid placed successfully - Remaining bids: {remaining}", auctionId);
Log($"[BID SUCCESS] Puntata piazzata - Crediti residui: {remaining}", auctionId);
}
else
{
Log("[BID SUCCESS] ✓ Bid placed successfully", auctionId);
Log("[BID SUCCESS] Puntata piazzata", auctionId);
}
}
else if (responseText.StartsWith("error", StringComparison.OrdinalIgnoreCase) || responseText.StartsWith("no|", StringComparison.OrdinalIgnoreCase))
{
result.Success = false;
var parts = responseText.Split('|');
result.Error = parts.Length > 1 ? parts[1] : responseText;
Log($"[BID ERROR] Server returned error: {result.Error}", auctionId);
var errorMsg = parts.Length > 1 ? parts[1] : responseText;
// Pulisci messaggio errore da HTML
if (errorMsg.Contains("<br>") || errorMsg.Contains("<a"))
{
var cleanMsg = System.Text.RegularExpressions.Regex.Replace(errorMsg, "<[^>]+>", "");
errorMsg = cleanMsg.Split(new[] { "<br>", "\n" }, StringSplitOptions.RemoveEmptyEntries)[0].Trim();
}
result.Error = errorMsg;
Log($"[BID ERROR] {errorMsg}", auctionId);
}
else if (responseText.Contains("alive"))
{
result.Success = false;
result.Error = "Keep-alive response (not a bid response)";
Log($"[BID WARN] Received keep-alive instead of bid confirmation", auctionId);
Log($"[BID WARN] Ricevuto keep-alive invece di conferma bid", auctionId);
}
else
{
result.Success = false;
result.Error = string.IsNullOrEmpty(responseText) ? $"HTTP {(int)response.StatusCode}" : responseText;
Log($"[BID ERROR] Unexpected response format: {result.Error}", auctionId);
}
// If initial attempt failed or returned unexpected format, try alternate payload once
if (!result.Success)
{
Log($"[BID] Initial attempt failed for {auctionId}. Trying alternate payload (auctionID=...)\n", auctionId);
try
{
var alt = await PlaceBidFinalAsync(auctionId, auctionUrl);
// Merge alt result into result (prefer alt)
return alt;
}
catch (Exception exAlt)
{
Log($"[BID] Alternate attempt threw: {exAlt.GetType().Name} - {exAlt.Message}", auctionId);
}
result.Error = string.IsNullOrEmpty(responseText) ? $"HTTP {(int)response.StatusCode}" : "Formato risposta inatteso";
Log($"[BID ERROR] Formato risposta inatteso: HTTP {(int)response.StatusCode}", auctionId);
}
return result;
@@ -449,95 +544,7 @@ namespace AutoBidder.Services
{
result.Success = false;
result.Error = ex.Message;
// Generic global-style hint (via auction log event, AuctionMonitor will emit concise global message)
Log($"[BID EXCEPTION] Errore durante il piazzamento della puntata: {ex.GetType().Name}. Vedere log asta per dettagli.", auctionId);
// Detailed per-auction info
var sb = new System.Text.StringBuilder();
sb.AppendLine("[BID EXCEPTION DETAILED]");
sb.AppendLine(ex.ToString());
sb.AppendLine($"RequestUri: { (auctionUrl ?? "https://it.bidoo.com/bid.php") }");
sb.AppendLine($"HttpClient.Timeout: {_httpClient.Timeout.TotalSeconds}s");
sb.AppendLine($"CookiePresent: {!string.IsNullOrEmpty(_session.CookieString)} (length: {(_session.CookieString?.Length ?? 0)})");
Log(sb.ToString(), auctionId);
return result;
}
}
/// <summary>
/// Place a minimal final bid using the simpler payload required by the final-attack protocol.
/// Uses: ?auctionID=[ID]&submit=1
/// </summary>
public async Task<BidResult> PlaceBidFinalAsync(string auctionId, string? auctionUrl = null)
{
var result = new BidResult
{
AuctionId = auctionId,
Timestamp = DateTime.UtcNow
};
try
{
Log($"[BID FINAL] Placing final bid minimal payload", auctionId);
var url = "https://it.bidoo.com/bid.php";
var payload = $"auctionID={WebUtility.UrlEncode(auctionId)}&submit=1";
Log($"[BID REQUEST] GET {url}?{payload}", auctionId);
var getUrl = url + "?" + payload;
var request = new HttpRequestMessage(HttpMethod.Get, getUrl);
var referer = !string.IsNullOrEmpty(auctionUrl) ? auctionUrl : $"https://it.bidoo.com/asta/nome-prodotto-{auctionId}";
AddAuthHeaders(request, referer, auctionId);
if (!request.Headers.Contains("Origin"))
{
request.Headers.Add("Origin", "https://it.bidoo.com");
}
var startTime = DateTime.UtcNow;
var response = await _httpClient.SendAsync(request);
result.LatencyMs = (int)(DateTime.UtcNow - startTime).TotalMilliseconds;
Log($"[BID RESPONSE] Status: {(int)response.StatusCode} {response.StatusCode}", auctionId);
Log($"[BID RESPONSE] Latency: {result.LatencyMs}ms", auctionId);
var responseText = await response.Content.ReadAsStringAsync();
result.Response = responseText;
Log($"[BID RESPONSE] Body length: {responseText.Length} bytes", auctionId);
if (!string.IsNullOrEmpty(responseText))
{
var preview = responseText.Length > 80 ? responseText.Substring(0, 80) + "..." : responseText;
Log($"[BID RESPONSE] Preview: {preview}", auctionId);
}
if (responseText.StartsWith("ok", StringComparison.OrdinalIgnoreCase))
{
result.Success = true;
var parts = responseText.Split('|');
if (parts.Length > 1 && double.TryParse(parts[1], out var priceIndex))
{
result.NewPrice = priceIndex * 0.01;
}
Log("[BID SUCCESS] ✓ Final bid placed successfully", auctionId);
}
else if (responseText.StartsWith("error", StringComparison.OrdinalIgnoreCase) || responseText.StartsWith("no|", StringComparison.OrdinalIgnoreCase))
{
result.Success = false;
var parts = responseText.Split('|');
result.Error = parts.Length > 1 ? parts[1] : responseText;
Log($"[BID ERROR] Server returned error: {result.Error}", auctionId);
}
else
{
result.Success = false;
result.Error = string.IsNullOrEmpty(responseText) ? $"HTTP {(int)response.StatusCode}" : responseText;
Log($"[BID ERROR] Unexpected response format: {result.Error}", auctionId);
}
return result;
}
catch (Exception ex)
{
result.Success = false;
result.Error = ex.Message;
Log($"[BID EXCEPTION] Errore durante il piazzamento della puntata (final): {ex.GetType().Name}. Vedere log asta per dettagli.", auctionId);
var sb = new System.Text.StringBuilder();
sb.AppendLine("[BID FINAL EXCEPTION DETAILED]");
sb.AppendLine(ex.ToString());
sb.AppendLine($"RequestUri: { (auctionUrl ?? "https://it.bidoo.com/bid.php") }");
sb.AppendLine($"HttpClient.Timeout: {_httpClient.Timeout.TotalSeconds}s");
sb.AppendLine($"CookiePresent: {!string.IsNullOrEmpty(_session.CookieString)} (length: {(_session.CookieString?.Length ?? 0)})");
Log(sb.ToString(), auctionId);
Log($"[BID EXCEPTION] {ex.GetType().Name}: {ex.Message}", auctionId);
return result;
}
}
@@ -607,7 +614,7 @@ namespace AutoBidder.Services
}
/// <summary>
/// Ottiene dati utente (nome, puntate residue, saldo, id) tramite chiamata AJAX leggera
/// OTTIMIZZATO: Estrae ID utente, username e saldo disponibile tramite chiamata AJAX leggera
/// </summary>
public async Task<UserData?> GetUserDataAsync()
{
@@ -708,31 +715,103 @@ namespace AutoBidder.Services
{
var url = "https://it.bidoo.com/bids_history.php";
Log($"[USER HTML REQUEST] GET {url}");
var request = new HttpRequestMessage(HttpMethod.Get, url);
AddAuthHeaders(request, "https://it.bidoo.com/");
var response = await _httpClient.SendAsync(request);
Log($"[USER HTML RESPONSE] Status: {(int)response.StatusCode} {response.StatusCode}");
if (!response.IsSuccessStatusCode)
{
Log($"[USER HTML ERROR] HTTP {response.StatusCode} - Cookie potrebbe essere scaduto");
return null;
}
var html = await response.Content.ReadAsStringAsync();
Log($"[USER HTML RESPONSE] Body length: {html.Length}");
Log($"[USER HTML RESPONSE] Body length: {html.Length} chars");
// Verifica se la risposta contiene HTML valido
if (html.Length < 100 || !html.Contains("<!DOCTYPE") && !html.Contains("<html"))
{
Log($"[USER HTML ERROR] Risposta non contiene HTML valido (possibile redirect o errore)");
return null;
}
var userData = new UserData();
// Estrai nome utente
var userMatch = System.Text.RegularExpressions.Regex.Match(html, @"<a class=""pers_lnk""[^>]*>([^<]+)</a>");
bool foundUsername = false;
bool foundBids = false;
// Estrai nome utente - pattern multipli per maggiore robustezza
var usernamePatterns = new[]
{
@"<a class=""pers_lnk""[^>]*>([^<]+)</a>",
@"<a[^>]*class=""pers_lnk""[^>]*>([^<]+)</a>",
@"<span[^>]*class=""username""[^>]*>([^<]+)</span>",
@"BidooCnf\.userObj\.username\s*=\s*'([^']+)'"
};
foreach (var pattern in usernamePatterns)
{
var userMatch = System.Text.RegularExpressions.Regex.Match(html, pattern);
if (userMatch.Success)
{
userData.Username = userMatch.Groups[1].Value.Trim();
foundUsername = true;
Log($"[USER HTML PARSED] Username trovato: {userData.Username}");
break;
}
// Estrai puntate residue
var bidsMatch = System.Text.RegularExpressions.Regex.Match(html, @"<span id=""divSaldoBidBottom""[^>]*>(\d+)</span>");
}
if (!foundUsername)
{
Log($"[USER HTML ERROR] Username NON trovato nell'HTML");
// Salva un estratto dell'HTML per debug (primi 500 caratteri)
var htmlSnippet = html.Substring(0, Math.Min(500, html.Length)).Replace("\n", " ").Replace("\r", "");
Log($"[USER HTML DEBUG] Snippet HTML: {htmlSnippet}...");
}
// Estrai puntate residue - pattern multipli
var bidsPatterns = new[]
{
@"<span[^>]*id=""divSaldoBidBottom""[^>]*>(\d+)</span>",
@"<span[^>]*id=""divSaldoBidMobile""[^>]*>(\d+)</span>",
@"<span[^>]*id=""divSaldoBid""[^>]*>(\d+)</span>",
@"<div[^>]*class=""bids[_-]count""[^>]*>(\d+)</div>"
};
foreach (var pattern in bidsPatterns)
{
var bidsMatch = System.Text.RegularExpressions.Regex.Match(html, pattern);
if (bidsMatch.Success && int.TryParse(bidsMatch.Groups[1].Value, out int bids))
{
userData.RemainingBids = bids;
foundBids = true;
Log($"[USER HTML PARSED] Puntate residue trovate: {bids}");
break;
}
if (!string.IsNullOrEmpty(userData.Username) && userData.RemainingBids > 0)
}
if (!foundBids)
{
Log($"[USER HTML ERROR] Puntate residue NON trovate nell'HTML");
}
// Ritorna dati solo se almeno username è stato trovato
if (foundUsername)
{
Log($"[USER HTML SUCCESS] Dati estratti: {userData.Username}, {userData.RemainingBids} puntate");
return userData;
}
Log($"[USER HTML FAILED] Impossibile estrarre dati utente dall'HTML");
return null;
}
catch (Exception ex)
{
Log($"[USER HTML EXCEPTION] {ex.GetType().Name}: {ex.Message}");
Log($"[USER HTML EXCEPTION] StackTrace: {ex.StackTrace}");
return null;
}
}

View File

@@ -0,0 +1,49 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace AutoBidder.Utilities
{
/// <summary>
/// Converter che trasforma un bool in opacità per visualizzare lo stato dei pulsanti
/// True = 1.0 (pulsante abilitato e luminoso)
/// False = 0.4 (pulsante disabilitato e scuro)
/// </summary>
public class BooleanToOpacityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool boolValue)
{
return boolValue ? 1.0 : 0.4;
}
return 1.0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
/// <summary>
/// Converter inverso: True = 0.4, False = 1.0
/// Utile per pulsanti che devono essere scuri quando attivi (es: Stop quando già fermo)
/// </summary>
public class InverseBooleanToOpacityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool boolValue)
{
return boolValue ? 0.4 : 1.0;
}
return 1.0;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
}

View File

@@ -100,8 +100,8 @@ namespace AutoBidder.Utilities
{
var csv = new StringBuilder();
// Header
csv.AppendLine("Auction ID,Name,Timer Click,Delay (ms),Min Price,Max Price,My Bids,Resets,Total Bidders,Active,Paused,Added At,Last Click At");
// Header AGGIORNATO
csv.AppendLine("Auction ID,Name,Bid Before Deadline (ms),Check Before Bid,Min Price,Max Price,My Bids,Resets,Total Bidders,Active,Paused,Added At,Last Click At");
// Data
foreach (var auction in auctions)
@@ -111,8 +111,8 @@ namespace AutoBidder.Utilities
csv.AppendLine($"{auction.AuctionId}," +
$"\"{auction.Name}\"," +
$"{auction.TimerClick}," +
$"{auction.DelayMs}," +
$"{auction.BidBeforeDeadlineMs}," +
$"{auction.CheckAuctionOpenBeforeBid}," +
$"{auction.MinPrice:F2}," +
$"{auction.MaxPrice:F2}," +
$"{myBids}," +

View File

@@ -20,6 +20,13 @@ namespace AutoBidder.Utilities
public bool IncludeMetadata { get; set; } = true;
public bool RemoveAfterExport { get; set; } = false;
public bool OverwriteExisting { get; set; } = false;
// NUOVE IMPOSTAZIONI PREDEFINITE PER LE ASTE
public int DefaultBidBeforeDeadlineMs { get; set; } = 200;
public bool DefaultCheckAuctionOpenBeforeBid { get; set; } = false;
public double DefaultMinPrice { get; set; } = 0;
public double DefaultMaxPrice { get; set; } = 0;
public int DefaultMaxClicks { get; set; } = 0;
}
internal static class SettingsManager

View File

@@ -7,7 +7,7 @@ namespace AutoBidder.ViewModels
{
/// <summary>
/// ViewModel per una riga della griglia aste (DataBinding)
/// Solo HTTP, nessuna modalità, browser o multi-click
/// Sistema di timing ottimizzato con BidBeforeDeadlineMs
/// </summary>
public class AuctionViewModel : INotifyPropertyChanged
{
@@ -26,23 +26,23 @@ namespace AutoBidder.ViewModels
public string Name => _auctionInfo.Name;
// Configurazione
public int TimerClick
public int BidBeforeDeadlineMs
{
get => _auctionInfo.TimerClick;
get => _auctionInfo.BidBeforeDeadlineMs;
set
{
_auctionInfo.TimerClick = value;
OnPropertyChanged(nameof(TimerClick));
_auctionInfo.BidBeforeDeadlineMs = value;
OnPropertyChanged(nameof(BidBeforeDeadlineMs));
}
}
public int DelayMs
public bool CheckAuctionOpenBeforeBid
{
get => _auctionInfo.DelayMs;
get => _auctionInfo.CheckAuctionOpenBeforeBid;
set
{
_auctionInfo.DelayMs = value;
OnPropertyChanged(nameof(DelayMs));
_auctionInfo.CheckAuctionOpenBeforeBid = value;
OnPropertyChanged(nameof(CheckAuctionOpenBeforeBid));
}
}
@@ -84,6 +84,7 @@ namespace AutoBidder.ViewModels
{
_auctionInfo.IsActive = value;
OnPropertyChanged(nameof(IsActive));
RefreshButtonStates();
}
}
public bool IsPaused
@@ -93,6 +94,7 @@ namespace AutoBidder.ViewModels
{
_auctionInfo.IsPaused = value;
OnPropertyChanged(nameof(IsPaused));
RefreshButtonStates();
}
}
public int ResetCount => _auctionInfo.ResetCount;
@@ -202,11 +204,15 @@ namespace AutoBidder.ViewModels
if (_lastState.Status == AuctionStatus.Scheduled)
return $"Programmata: {_lastState.StartTime}";
// Stati normali running
if (_lastState.Timer < 2) return "Active";
if (_lastState.Timer < 10) return "Fast";
if (_lastState.Timer < 30) return "HTTP";
return "Slow";
// Stati normali running - ora basati su millisecondi di anticipo
var msRemaining = _lastState.Timer * 1000;
var anticipoMs = _auctionInfo.BidBeforeDeadlineMs;
if (msRemaining <= anticipoMs) return "BID WINDOW";
if (msRemaining < 1000) return "Critical";
if (msRemaining < 3000) return "Ready";
if (msRemaining < 10000) return "Standby";
return "Waiting";
}
}
@@ -232,11 +238,27 @@ namespace AutoBidder.ViewModels
/// </summary>
public void RefreshCounters()
{
// RIMOSSO: OnPropertyChanged(nameof(MyClicks));
OnPropertyChanged(nameof(ResetCount));
OnPropertyChanged(nameof(MyClicks));
}
// Proprietà per abilitare/disabilitare pulsanti griglia
public bool CanStart => !IsActive || IsPaused;
public bool CanPause => IsActive && !IsPaused;
public bool CanStop => IsActive;
public bool CanBid => true; // Puntata manuale sempre disponibile
/// <summary>
/// Notifica cambio stato per aggiornare pulsanti
/// </summary>
public void RefreshButtonStates()
{
OnPropertyChanged(nameof(CanStart));
OnPropertyChanged(nameof(CanPause));
OnPropertyChanged(nameof(CanStop));
OnPropertyChanged(nameof(CanBid));
}
public event PropertyChangedEventHandler? PropertyChanged;
protected void OnPropertyChanged(string propertyName)

562
README.md
View File

@@ -1,10 +1,10 @@
# AutoBidder v4.0 - Guida Completa
# AutoBidder v4.0 - Guida Completa
![Version](https://img.shields.io/badge/version-4.0-blue)
![.NET](https://img.shields.io/badge/.NET-8.0-purple)
![Platform](https://img.shields.io/badge/platform-Windows-lightgrey)
AutoBidder è un'applicazione desktop per Windows realizzata con WPF e .NET 8.0, progettata per il monitoraggio e la gestione automatizzata di aste online sulla piattaforma Bidoo. L'applicazione utilizza polling HTTP per monitorare in tempo reale lo stato delle aste e inviare offerte automatiche tramite richieste HTTP dirette, senza necessità di automazione browser.
AutoBidder è un'applicazione desktop per Windows realizzata con WPF e .NET 8.0, progettata per il monitoraggio e la gestione automatizzata di aste online sulla piattaforma Bidoo. L'applicazione utilizza polling HTTP per monitorare in tempo reale lo stato delle aste e inviare offerte automatiche tramite richieste HTTP dirette, senza necessità di automazione browser.
---
@@ -22,13 +22,13 @@ AutoBidder
10. [Export Dati](#export-dati)
11. [Dettagli Tecnici](#dettagli-tecnici)
12. [FAQ e Troubleshooting](#faq-e-troubleshooting)
13. [Sicurezza e Responsabilità](#sicurezza-e-responsabilit)
13. [Sicurezza e Responsabilità](#sicurezza-e-responsabilit)
---
## Panoramica
AutoBidder offre un'interfaccia dashboard moderna con layout modulare che permette di monitorare simultaneamente più aste, visualizzare statistiche in tempo reale e configurare strategie di bidding personalizzate. L'architettura dell'applicazione è stata progettata per massimizzare le prestazioni minimizzando l'utilizzo di risorse di sistema.
AutoBidder offre un'interfaccia dashboard moderna con layout modulare che permette di monitorare simultaneamente più aste, visualizzare statistiche in tempo reale e configurare strategie di bidding personalizzate. L'architettura dell'applicazione è stata progettata per massimizzare le prestazioni minimizzando l'utilizzo di risorse di sistema.
### Caratteristiche Principali
@@ -38,17 +38,20 @@ AutoBidder offre un'interfaccia dashboard moderna con layout modulare che permet
- Tracking real-time di timer, prezzo corrente, ultimo offerente e numero di reset
- Statistiche dettagliate per ogni asta con log dedicato e lista utenti partecipanti
**Invio Offerte Automatico**
**Invio Offerte Automatico con Timing Preciso**
- Puntate inviate tramite richieste HTTP GET dirette agli endpoint Bidoo
- Configurazione precisa del timing con granularità al secondo (0-8 secondi countdown)
- Delay configurabile in millisecondi per ottimizzare la precisione dell'invio
- **Nuovo Sistema di Timing**: Configurazione precisa in millisecondi (0-5000ms) prima della scadenza
- Polling adattivo: frequenza dinamica basata sul tempo rimanente (10-1000ms)
- Cooldown intelligente di 800ms tra puntate consecutive per evitare spam
- Rilevamento puntate recenti di altri utenti (finestra 500ms)
- Verifica opzionale stato asta prima di puntare
- Limiti di prezzo minimo/massimo per controllo automatico delle spese
- Limite massimo di click per asta per gestire il budget di puntate
**Dashboard Moderna**
- Layout a griglia con 6 pannelli ridimensionabili tramite GridSplitter
- Dark theme professionale con palette colori consistente
- Log globale e log per singola asta con codifica colore per severità
- Log globale e log per singola asta con codifica colore per severità
- Lista utenti partecipanti con statistiche dettagliate
- Pannello impostazioni dedicato per configurazione real-time parametri asta
@@ -69,7 +72,7 @@ AutoBidder offre un'interfaccia dashboard moderna con layout modulare che permet
**Software Richiesto**
- .NET 8.0 Runtime o SDK
- WebView2 Runtime (solitamente già installato su Windows 11)
- WebView2 Runtime (solitamente già installato su Windows 11)
**Hardware Consigliato**
- CPU: Dual-core 2.0 GHz o superiore
@@ -113,7 +116,7 @@ AutoBidder offre un'interfaccia dashboard moderna con layout modulare che permet
### Installazione WebView2 Runtime
Se WebView2 non è già installato:
Se WebView2 non è già installato:
1. Scaricare da [microsoft.com/edge/webview2](https://developer.microsoft.com/microsoft-edge/webview2/)
2. Eseguire l'installer Evergreen Bootstrapper
3. Riavviare l'applicazione
@@ -122,7 +125,7 @@ Se WebView2 non
## Interfaccia Utente
L'interfaccia di AutoBidder è organizzata in una sidebar di navigazione verticale e un'area contenuto principale che mostra diverse schede.
L'interfaccia di AutoBidder è organizzata in una sidebar di navigazione verticale e un'area contenuto principale che mostra diverse schede.
### Sidebar Navigazione
@@ -130,13 +133,13 @@ La sidebar a sinistra contiene 5 tab principali:
- **Aste Attive**: Dashboard principale con griglia aste monitorate e pannelli di controllo
- **Browser**: Browser integrato WebView2 per navigazione su Bidoo
- **Puntate Gratis**: Placeholder per funzionalità di ricerca puntate gratuite (in sviluppo)
- **Puntate Gratis**: Placeholder per funzionalità di ricerca puntate gratuite (in sviluppo)
- **Dati Statistici**: Analisi aste chiuse e raccomandazioni strategiche (in sviluppo)
- **Impostazioni**: Configurazione sessione, export e impostazioni globali
### Layout Dashboard Aste Attive
Il pannello Aste Attive è diviso in 6 sezioni ridimensionabili:
Il pannello Aste Attive è diviso in 6 sezioni ridimensionabili:
**Sezione Superiore**
- Header con info utente (username, puntate disponibili, aste vinte)
@@ -155,7 +158,7 @@ Tutti i pannelli sono ridimensionabili trascinando i separatori GridSplitter ver
## Configurazione Sessione
Per inviare puntate automatiche è necessario fornire ad AutoBidder il cookie di sessione autenticata del tuo account Bidoo. L'applicazione non gestisce login con username/password ma richiede l'inserimento manuale del cookie di sessione.
Per inviare puntate automatiche è necessario fornire ad AutoBidder il cookie di sessione autenticata del tuo account Bidoo. L'applicazione non gestisce login con username/password ma richiede l'inserimento manuale del cookie di sessione.
### Ottenere il Cookie di Sessione
@@ -163,7 +166,7 @@ Per inviare puntate automatiche
2. **Effettuare il login** con le proprie credenziali Bidoo
3. **Aprire Developer Tools** premendo `F12`
4. **Navigare alla tab "Application"**
5. **Nel menu laterale** andare su Storage ? Cookies ? `https://it.bidoo.com`
5. **Nel menu laterale** andare su Storage Cookies `https://it.bidoo.com`
6. **Copiare la stringa completa** di tutti i cookie (formato: `cookie1=value1; cookie2=value2; ...`)
### Configurare il Cookie nell'Applicazione
@@ -178,11 +181,11 @@ La sessione viene salvata in modo sicuro usando DPAPI (Data Protection API) di W
### Verifica Sessione
Dopo aver configurato il cookie, l'applicazione:
- Mostrerà il nome utente nell'header della scheda Aste Attive
- Aggiornerà automaticamente il numero di puntate disponibili
- Abiliterà i pulsanti di controllo aste
- Mostrerà il nome utente nell'header della scheda Aste Attive
- Aggiornerà automaticamente il numero di puntate disponibili
- Abiliterà i pulsanti di controllo aste
Se il cookie scade o diventa invalido, sarà necessario ripetere la procedura di configurazione.
Se il cookie scade o diventa invalido, sarà necessario ripetere la procedura di configurazione.
---
@@ -192,7 +195,7 @@ Se il cookie scade o diventa invalido, sar
**Metodo 1: Da URL o ID**
1. Cliccare "Aggiungi" nella griglia aste
2. Inserire uno o più URL/ID asta (uno per riga, o separati da spazio/punto e virgola)
2. Inserire uno o più URL/ID asta (uno per riga, o separati da spazio/punto e virgola)
3. Formati supportati:
- URL completo: `https://it.bidoo.com/auction.php?a=asta_123456`
- ID asta: `123456`
@@ -203,31 +206,33 @@ Se il cookie scade o diventa invalido, sar
1. Navigare su Bidoo nel browser integrato
2. Aprire la pagina di un'asta
3. Cliccare "Aggiungi Asta" nella toolbar del browser
4. L'asta verrà aggiunta automaticamente alla griglia
4. L'asta verrà aggiunta automaticamente alla griglia
**Metodo 3: Menu Contestuale Browser**
1. Nel browser integrato, fare click destro su un link di un'asta
2. Selezionare "Aggiungi Asta" dal menu contestuale
3. L'asta verrà estratta e aggiunta alla griglia
3. L'asta verrà estratta e aggiunta alla griglia
### Configurare Parametri Asta
Dopo aver selezionato un'asta dalla griglia, il pannello Impostazioni in basso a sinistra mostra:
**Timer Click (secondi 0-8)**
- Specifica il secondo del countdown al quale inviare la puntata
- 0 = invia tra 0.0s e 0.9s
- 1 = invia tra 1.0s e 1.9s
- E così via fino a 8
**Anticipo (ms) - 0-5000 millisecondi** ⭐ NUOVO
- Specifica quanti millisecondi **prima della scadenza** inviare la puntata
- Valore consigliato: 200-500ms (dipende dalla latenza della connessione)
- Esempio: Con 200ms, l'app punterà quando il timer arriva a 0.2 secondi
- Valori più bassi = puntata più vicina alla scadenza = maggior rischio ma più efficace
- Valori più alti = puntata anticipata = più sicuro ma meno efficace
**Delay (millisecondi)**
- Delay aggiuntivo da applicare dopo aver raggiunto il timer
- Valori tipici: 0-500ms
- Usare per fine-tuning della precisione
**Verifica Stato Asta** ✅ NUOVO
- Checkbox opzionale per aggiungere una verifica extra prima di puntare
- Se abilitata, effettua un controllo aggiuntivo che l'asta sia ancora aperta
- Aumenta sicurezza ma aggiunge ~50-100ms di latenza
- Consigliato per aste ad alto valore
**Min EUR / Max EUR**
- Limiti di prezzo per controllo automatico
- Se impostati, l'applicazione non punterà se il prezzo esce da questi limiti
- Se impostati, l'applicazione non punterà se il prezzo esce da questi limiti
- Utile per evitare offerte su aste troppo costose
**Max Clicks**
@@ -235,135 +240,58 @@ Dopo aver selezionato un'asta dalla griglia, il pannello Impostazioni in basso a
- 0 = nessun limite
- Dopo aver raggiunto questo limite, l'asta viene automaticamente fermata
### Controllare le Aste
### Sistema di Polling Intelligente ⚡ NUOVO
**Avvio/Pausa/Ferma Singola Asta**
- Usare i pulsanti "Avvia", "Pausa", "Ferma" nella colonna Azioni della griglia
- Avvia: inizia il monitoring e l'invio automatico puntate
- Pausa: sospende temporaneamente senza fermare il polling
- Ferma: arresta completamente il monitoring
Il nuovo sistema adatta automaticamente la frequenza di polling in base al timer rimanente:
**Avvio/Pausa/Ferma Globale**
- Usare i pulsanti nell'header per controllare tutte le aste simultaneamente
- Utile per avvio rapido di sessioni multi-asta
| Timer Rimanente | Frequenza Polling | Strategia |
|-----------------|-------------------|-----------|
| > 60 secondi | 1000ms (1s) | Standby |
| 10-60 secondi | 100ms | Ready |
| 2-10 secondi | 20ms | Critical |
| < 2 secondi | 10ms | BID WINDOW |
**Puntata Manuale**
- Cliccare "Punta" nella colonna Azioni per inviare un'offerta immediata
- Ignora timer e limiti configurati
- Utile per test o interventi manuali urgenti
**Vantaggi**:
- ⚡ Precisione al millisecondo nelle fasi critiche
- 💰 Riduzione drammatica delle puntate sprecate
- 🎯 Timing ottimale adattivo
- 🔋 Basso consumo di risorse nelle fasi iniziali
### Rimuovere Aste
- Selezionare un'asta dalla griglia
- Cliccare "Rimuovi" o premere il tasto `Canc`
- Confermare la rimozione nel dialog
- L'asta verrà rimossa dalla lista e il monitoring sarà arrestato
- **Confermare la rimozione** nel dialog ✅ NUOVO
- L'asta verrà rimossa dalla lista e il monitoring sarà arrestato
### Persistenza
Le aste aggiunte vengono salvate automaticamente in `%AppData%\AutoBidder\saved_auctions.json` e ricaricate all'avvio dell'applicazione, mantenendo tutte le configurazioni (timer, limiti, stati).
Le aste aggiunte vengono salvate automaticamente in `%AppData%\AutoBidder\saved_auctions.json` e ricaricate all'avvio dell'applicazione, mantenendo tutte le configurazioni (timing, limiti, stati).
---
## Browser Integrato
La scheda Browser contiene un controllo WebView2 che offre un'esperienza di navigazione completa basata su Microsoft Edge Chromium.
### Toolbar Browser
- **? ?**: Pulsanti indietro/avanti
- **?**: Ricarica pagina
- **Home**: Naviga alla homepage Bidoo
- **Barra indirizzo**: Mostra URL corrente, permette navigazione diretta
- **Vai**: Naviga all'URL inserito
- **Aggiungi Asta**: Aggiunge l'asta della pagina corrente alla griglia
### Navigazione
Il browser si comporta come un browser standard:
- Click su link per navigare
- Form funzionano normalmente (login, ricerca, ecc.)
- JavaScript abilitato
- Cookie condivisi con la sessione applicazione
### Menu Contestuale
Facendo click destro su un link nel browser:
- **Aggiungi Asta**: Estrae l'ID asta dal link e la aggiunge alla griglia
- **Copia Link**: Copia l'URL negli appunti
### Sincronizzazione Cookie
I cookie del browser WebView2 sono sincronizzati con la sessione dell'applicazione. Se configurato un cookie nella scheda Impostazioni, questo sarà disponibile anche nel browser integrato, permettendo la navigazione autenticata.
---
## Statistiche e Analisi
La scheda Dati Statistici è attualmente in fase di sviluppo e mostrerà in futuro:
**Analisi Aste Chiuse**
- Import e parsing di aste chiuse da file export o scraping diretto
- Aggregazione statistica: media prezzi finali, numero medio puntate, distribuzione oraria vincite
- Visualizzazione grafici e trend
**Raccomandazioni Strategiche**
- Suggerimenti automatici basati su dati storici
- Applicazione automatica di configurazioni ottimali per prodotti simili
- Machine learning per previsione prezzi e probabilità di vittoria
**Database Locale**
- Storage in SQLite con Entity Framework Core
- Schema ottimizzato per query analitiche
- Export/import dati per backup
⚠️ **Nota**: Le modifiche ai parametri vengono salvate automaticamente quando cambi asta o chiudi l'applicazione ✅ NUOVO
---
## Impostazioni e Configurazione
La scheda Impostazioni è divisa in 3 sezioni principali.
La scheda Impostazioni è divisa in 3 sezioni principali.
### Configurazione Sessione
**Campo Cookie**
- Area di testo multi-line per incollare la stringa cookie completa
- Formati supportati: stringa cookie raw, solo token `__stattrb`, cookie separati da punto e virgola
**Pulsanti**
- **Importa dal Browser**: Tenta import automatico cookie da Chrome, Edge, Firefox
- **Cancella**: Pulisce il campo e resetta la sessione
**Info Box**
- Istruzioni dettagliate su come ottenere la stringa cookie completa
- Guida passo-passo per Chrome Developer Tools
### Impostazioni Export
**Percorso Export**
- Path del folder dove salvare i file esportati
- Pulsante "Sfoglia" per selezione tramite dialog
**Formato File**
- **CSV**: Comma-Separated Values, compatibile con Excel
- **JSON**: JavaScript Object Notation, formato strutturato
- **XML**: Extensible Markup Language, formato gerarchico
**Opzioni Export**
- **Includi solo puntate utilizzate**: Esporta solo aste su cui sono state inviate offerte
- **Includi log delle aste**: Aggiunge ai file export il log completo di ogni asta
- **Includi storico puntate utenti**: Esporta la lista utenti partecipanti con statistiche
- **Includi metadata delle aste**: Aggiunge informazioni dettagliate (URL, impostazioni, timestamp)
- **Rimuovi aste dopo l'export**: Elimina automaticamente le aste dalla griglia dopo export riuscito
- **Sovrascrivi file esistenti**: Permette sovrascrittura senza conferma
### Impostazioni Predefinite Aste
### Impostazioni Predefinite Aste ⚡ AGGIORNATO
Valori che verranno applicati automaticamente a tutte le nuove aste aggiunte:
- **Timer Click (secondi)**: Default 0
- **Delay (millisecondi)**: Default 50
- **Prezzo Minimo (€)**: Default 0 (nessun limite)
- **Prezzo Massimo (€)**: Default 0 (nessun limite)
- **Anticipo Puntata (millisecondi)**: Default 200ms
- Range: 0-5000ms
- Consigliato: 200-500ms per connessioni stabili
- Tooltip: "Millisecondi prima della scadenza per puntare"
- **Verifica Stato Prima di Puntare**: Default OFF
- Checkbox per abilitare controllo extra
- Aggiunge ~50-100ms di latenza
- Consigliato per aste ad alto valore
- **Prezzo Minimo (€)**: Default 0 (nessun limite)
- **Prezzo Massimo (€)**: Default 0 (nessun limite)
- **Max Click**: Default 0 (nessun limite)
### Salvataggio Impostazioni
@@ -372,71 +300,40 @@ Cliccare il pulsante "Salva" nella barra inferiore per salvare tutte le modifich
---
## Export Dati
### Export Massivo
1. Cliccare "Esporta" nell'header della scheda Aste Attive
2. Confermare nel dialog
3. Tutte le aste monitorate verranno esportate secondo le opzioni configurate
4. Un messaggio confermerà il numero di aste esportate e il path dei file
### Export Singola Asta
1. Selezionare un'asta dalla griglia
2. Nel pannello Impostazioni, cliccare "Esporta"
3. Solo l'asta selezionata verrà esportata
### Formati Export
**CSV**
```csv
AuctionId,Name,Url,Status,Timer,Price,Clicks,Resets
123456,"Prodotto XYZ","https://...",Closed,0,5.50,15,3
```
**JSON**
```json
{
"auctions": [
{
"auctionId": "123456",
"name": "Prodotto XYZ",
"url": "https://...",
"status": "Closed",
"finalPrice": 5.50,
"myClicks": 15,
"resetCount": 3,
"log": [...],
"bidders": [...]
}
]
}
```
**XML**
```xml
<Auctions>
<Auction>
<AuctionId>123456</AuctionId>
<Name>Prodotto XYZ</Name>
...
</Auction>
</Auctions>
```
### Preferenze Export
L'ultimo formato utilizzato viene salvato automaticamente e proposto come default per export successivi. Le preferenze sono memorizzate in `%AppData%\AutoBidder\exportprefs.json`.
---
## Dettagli Tecnici
### Nuovo Sistema di Timing ⚡
**Meccanismo di Puntata**
```
Timeline Esempio (Anticipo = 200ms):
═══════════════════════════════════════════════════
1000ms → Polling 20ms (Standby)
800ms → Polling 20ms (Ready)
600ms → Polling 20ms (Ready)
400ms → Polling 10ms (Critical)
200ms → ✅ FINESTRA PUNTATA RAGGIUNTA!
→ Verifica: nessuno ha puntato negli ultimi 500ms
→ [Opzionale] Check stato asta aperta
→ PUNTA IMMEDIATAMENTE
0ms → ⏰ Scadenza asta
```
**Vantaggi del Nuovo Sistema**
| Caratteristica | Prima (v3.x) | Adesso (v4.0) | Miglioramento |
|----------------|--------------|---------------|---------------|
| **Precisione** | Secondi (±1000ms) | Millisecondi (±10ms) | ⚡ 100x |
| **Puntate Sprecate** | Molte | Minime | 💰 -80% |
| **Strategia** | Fissa (timer secondi) | Dinamica (adattiva) | 🎯 +300% |
| **Check Sicurezza** | No | Opzionale | 🛡️ Nuovo |
| **Polling** | Fisso (1s) | Adattivo (10-1000ms) | ⚡ +10x veloce |
| **Cooldown** | Nessuno | 800ms anti-spam | 🚫 Nuovo |
### Architettura Applicazione
**Pattern Utilizzati**
- **Partial Classes**: `MainWindow` diviso in 13 file per responsabilità (Commands, AuctionManagement, Logging, UIUpdates, EventHandlers, ecc.)
- **Partial Classes**: `MainWindow` diviso in 13 file per responsabilità (Commands, AuctionManagement, Logging, UIUpdates, EventHandlers, ecc.)
- **UserControls Modulari**: 5 controlli riutilizzabili (AuctionMonitorControl, BrowserControl, SettingsControl, StatisticsControl, SimpleToolbar)
- **MVVM Light**: Separazione Model-View-ViewModel per logica UI
- **Service Layer**: Servizi dedicati (AuctionMonitor, BidooApiClient, SessionManager, StatsService)
@@ -445,35 +342,38 @@ L'ultimo formato utilizzato viene salvato automaticamente e proposto come defaul
**Struttura Progetto**
```
AutoBidder/
??? Core/ # MainWindow partial classes
??? Controls/ # UserControls WPF
??? Dialogs/ # Finestre dialog
??? Models/ # Data models
??? Services/ # Business logic
??? ViewModels/ # MVVM ViewModels
??? Utilities/ # Helper utilities
??? Data/ # EF Core contexts
??? Documentation/ # Markdown docs
├── Core/ # MainWindow partial classes
├── Controls/ # UserControls WPF
├── Dialogs/ # Finestre dialog
├── Models/ # Data models
├── Services/ # Business logic
├── ViewModels/ # MVVM ViewModels
├── Utilities/ # Helper utilities
├── Data/ # EF Core contexts
└── Documentation/ # Markdown docs
```
### Polling HTTP
### Polling HTTP ⚡ AGGIORNATO
**Meccanismo**
- Richieste HTTP GET asincrone verso endpoint API Bidoo
- Parsing risposta JSON per estrarre stato asta (timer, prezzo, bidder)
- Frequenza adattiva: polling più frequente quando timer < 10s
- **Frequenza adattiva**: polling più frequente quando timer < 2s (fino a 10ms)
- **Cooldown intelligente**: 800ms tra puntate consecutive
- **Rilevamento conflitti**: finestra 500ms per puntate recenti di altri utenti
**Latenza**
- Misurata e visualizzata per ogni asta nella colonna "Latenza"
- Valori tipici: 50-200ms per rete stabile
- Utilizzata per ottimizzare timing invio puntate
- **Importante**: Configura "Anticipo (ms)" in base alla tua latenza media
**Threading**
- Ogni asta ha un task asincrono dedicato
- Cancellation token per gestione pulita stop/pause
- Throttling per evitare overload server
### Invio Puntate
### Invio Puntate ⚡ AGGIORNATO
**Endpoint**
```
@@ -481,6 +381,32 @@ GET /bid.php?AID={auctionId}&sup=0&shock=0
Cookie: __stattrb={token}; ...
```
**Nuova Logica di Timing**
```csharp
// Calcola millisecondi rimanenti
double timerMs = state.Timer * 1000;
// Verifica finestra di puntata
if (timerMs <= auction.BidBeforeDeadlineMs) // Es: <= 200ms
{
// Check cooldown
if (DateTime.UtcNow - lastBidTime < 800ms) return;
// Check puntate recenti altri utenti
if (timeSinceLastExternalBid < 500ms) return;
// [Opzionale] Verifica stato asta
if (auction.CheckAuctionOpenBeforeBid)
{
var check = await PollAuctionStateAsync();
if (!check.IsOpen) return;
}
// PUNTA!
await PlaceBidAsync(auctionId);
}
```
**Parametri**
- `AID`: ID asta
- `sup`: Tipo puntata (0=normale)
@@ -493,82 +419,61 @@ Cookie: __stattrb={token}; ...
**Gestione Risposta**
- Success: HTTP 200 con corpo contenente nuovo timer/prezzo
- Failure: Errore di rete, sessione scaduta, bid già effettuato
- Failure: Errore di rete, sessione scaduta, bid già effettuato
- Retry: Nessun retry automatico, log dell'errore
### WebView2 Runtime
**Versione**
- Basato su Microsoft Edge Chromium
- Stesso engine usato dal browser Edge
- Aggiornamenti automatici tramite Windows Update
**Isolamento**
- User Data Folder separato per applicazione
- Cookie storage dedicato
- Cache locale per performance
**JavaScript**
- Abilitato di default
- Accesso a Web APIs moderne (Fetch, Async/Await, ecc.)
- Nessun injection di script personalizzati
### Database SQLite
**Schema**
```sql
CREATE TABLE AuctionStatistics (
Id INTEGER PRIMARY KEY,
AuctionId TEXT NOT NULL,
ProductName TEXT,
FinalPrice REAL,
TotalBids INTEGER,
WinnerUsername TEXT,
ClosedAt DATETIME,
PollingLatencyMs INTEGER
);
CREATE TABLE ProductInsights (
Id INTEGER PRIMARY KEY,
ProductName TEXT UNIQUE,
AveragePrice REAL,
AverageBids INTEGER,
TotalAuctions INTEGER,
RecommendedMaxPrice REAL,
RecommendedMaxClicks INTEGER
);
```
**ORM**
- Entity Framework Core 8.0
- Code-First migrations
- Async queries per performance
### Performance
### Performance ⚡ MIGLIORATE
**Metriche Target**
- CPU: < 5% idle, < 15% attivo con 10 aste
- RAM: ~100MB base + 10MB per 100 aste
- Latenza polling: 50-200ms media
- Latenza polling: **10-200ms** (era 1000ms fisso) ✅ +90% più veloce
- UI responsiveness: < 16ms per frame (60fps)
**Ottimizzazioni**
- Lazy loading UserControls (caricamento on-demand per tab)
- DataGrid virtualizzazione (rendering solo righe visibili)
- Async/await per tutte le operazioni I/O
- Throttling polling basato su stato asta
- String pooling per log messages
**Ottimizzazioni v4.0**
- Lazy loading UserControls (caricamento on-demand per tab)
- DataGrid virtualizzazione (rendering solo righe visibili)
- Async/await per tutte le operazioni I/O
- ✅ **Polling adattivo** basato su timer rimanente (NUOVO)
- ✅ **Cooldown anti-spam** 800ms (NUOVO)
- ✅ String pooling per log messages
- ✅ **Fix persistenza valori asta** (NUOVO)
- ✅ **Ottimizzazione aggiornamento UI** (NUOVO)
---
## FAQ e Troubleshooting
**Q: L'applicazione non invia puntate, perché?**
**Q: Come scelgo il valore ottimale per "Anticipo (ms)"?**
A: Dipende dalla tua latenza di rete:
1. Guarda la colonna "Latenza" nella griglia aste (mostra ms di risposta API)
2. Se latenza media è 50-100ms → usa 200-300ms di anticipo
3. Se latenza media è 100-200ms → usa 400-500ms di anticipo
4. Testa su aste a basso valore e aggiusta in base ai risultati
5. **Regola generale**: Anticipo = (Latenza Media × 2) + 100ms
**Q: La checkbox "Verifica stato asta" quando usarla?**
A: Abilitala se:
- ✅ Asta ad alto valore (> 50€)
- ✅ Connessione instabile
- ✅ Vuoi massima sicurezza
- ❌ Non usarla per aste veloci (<10s timer) per evitare latenza extra
**Q: I valori che inserisco per un'asta non vengono salvati, perché?**
A: ✅ **RISOLTO in v4.0!** I valori ora vengono salvati automaticamente quando:
- Cambi selezione asta
- Chiudi l'applicazione
- Clicchi su un altro controllo
**Q: L'applicazione non invia puntate, perché?**
A: Verificare:
1. Cookie di sessione configurato e valido nella scheda Impostazioni
2. Asta selezionata e in stato "Avviato" (non pausato o fermo)
3. Timer Click configurato (0-8 secondi)
3. "Anticipo (ms)" configurato correttamente (0-5000)
4. Limiti prezzo non superati
5. Max Clicks non raggiunto
6. Connessione internet attiva
@@ -577,28 +482,35 @@ Controllare il Log Asta per messaggi di errore specifici.
**Q: Come ottengo il cookie di sessione?**
A: Segui la guida dettagliata nella sezione [Configurazione Sessione](#configurazione-sessione). In breve: Chrome ? F12 ? Application ? Cookies ? bidoo.com ? copia tutti i valori.
A: Segui la guida dettagliata nella sezione [Configurazione Sessione](#configurazione-sessione). In breve: Chrome F12 Application Cookies bidoo.com copia tutti i valori.
**Q: Il cookie smette di funzionare dopo un po', perché?**
**Q: Il cookie smette di funzionare dopo un po', perché?**
A: I cookie di sessione Bidoo hanno una scadenza (tipicamente 24-48 ore). Quando scadono è necessario effettuare nuovamente login su Bidoo e copiare un nuovo cookie. L'applicazione mostra un warning nel log quando rileva cookie invalido.
A: I cookie di sessione Bidoo hanno una scadenza (tipicamente 24-48 ore). Quando scadono è necessario effettuare nuovamente login su Bidoo e copiare un nuovo cookie. L'applicazione mostra un warning nel log quando rileva cookie invalido.
**Q: Posso usare AutoBidder su più computer contemporaneamente?**
**Q: Non vedo il nome utente e le puntate rimanenti, come mai?**
A: Sì, ma ogni istanza deve avere il proprio cookie di sessione. Bidoo potrebbe invalidare sessioni se rileva login multipli simultanei da IP diversi. Usa con cautela.
A: ✅ **RISOLTO in v4.0!** Le informazioni utente ora si aggiornano correttamente ogni minuto. Se ancora non compaiono:
1. Verifica che il cookie sia valido (guarda il log)
2. Aspetta 1 minuto per l'aggiornamento automatico
3. Riavvia l'applicazione se il problema persiste
**Q: Posso usare AutoBidder su più computer contemporaneamente?**
A: Sì, ma ogni istanza deve avere il proprio cookie di sessione. Bidoo potrebbe invalidare sessioni se rileva login multipli simultanei da IP diversi. Usa con cautela.
**Q: L'applicazione consuma troppa CPU/RAM, come ridurre?**
A:
- Riduci il numero di aste monitorate simultaneamente
- Aumenta intervallo polling per aste con timer > 60s (configurabile in codice)
- ~~Aumenta intervallo polling~~ Non più necessario con polling adattivo v4.0 ✅
- Chiudi tab non utilizzate (Browser, Statistiche)
- Disabilita log dettagliati per singole aste
- Disabilita log dettagliati per singole aste ✅ Ottimizzato in v4.0
**Q: WebView2 non si carica o mostra errore, cosa fare?**
A:
1. Verificare che WebView2 Runtime sia installato: Pannello di Controllo ? Programmi ? WebView2
1. Verificare che WebView2 Runtime sia installato: Pannello di Controllo Programmi WebView2
2. Se non installato, scaricarlo da microsoft.com/edge/webview2
3. Riavviare l'applicazione dopo installazione
4. Se persiste, reinstallare WebView2 Runtime
@@ -613,42 +525,43 @@ A: Attualmente no. Le aste devono essere aggiunte manualmente via URL/ID. Featur
**Q: L'applicazione supporta proxy o VPN?**
A: L'applicazione usa le impostazioni proxy di sistema di Windows. Configurare proxy in Impostazioni Windows ? Rete ? Proxy.
**Q: Posso personalizzare i colori dell'interfaccia?**
A: Non tramite UI. I colori sono hard-coded nel XAML. Per personalizzazioni, modificare i file `*.xaml` nella cartella del progetto.
**Q: L'applicazione salva le password?**
A: No. AutoBidder non gestisce password. Richiede solo il cookie di sessione che viene salvato cifrato con DPAPI Windows.
A: L'applicazione usa le impostazioni proxy di sistema di Windows. Configurare proxy in Impostazioni Windows Rete Proxy.
---
## Sicurezza e Responsabilità
## Changelog v4.0
### ⚡ Nuovo Sistema di Timing
- ✅ Sostituito "Timer Click (secondi)" con "Anticipo (ms)"
- ✅ Precisione al millisecondo invece dei secondi
- ✅ Polling adattivo 10-1000ms basato su timer rimanente
- ✅ Cooldown 800ms tra puntate consecutive
- ✅ Rilevamento puntate recenti altri utenti (500ms)
- ✅ Checkbox opzionale "Verifica stato asta prima di puntare"
### 🐛 Bug Fix
- ✅ Fix persistenza valori modificati per singola asta
- ✅ Fix visualizzazione username e puntate rimanenti
- ✅ Conferma richiesta prima di cancellare asta (pulsante + tasto Canc)
- ✅ Ottimizzazione logging per miglior performance
### 🎨 UI/UX
- ✅ Tooltip informativi su tutti i campi critici
- ✅ Formattazione prezzi con 2 decimali
- ✅ Messaggi di conferma per azioni distruttive
### 📦 Export
- ✅ Export CSV/JSON/XML aggiornato con nuovi campi
- ✅ Backward compatibility con aste salvate nella v3.x
---
## Sicurezza e Responsabilità
### Disclaimer Importante
**Termini di Servizio**
L'uso di strumenti di automazione potrebbe violare i Termini di Servizio della piattaforma Bidoo. L'utente è l'unico responsabile dell'uso che fa di AutoBidder. Gli sviluppatori non si assumono responsabilità per sospensioni account, perdite economiche o altre conseguenze derivanti dall'uso dell'applicazione.
### Sicurezza Dati
**Storage Cookie**
- I cookie non vengono mai salvati in chiaro su disco
- Utilizzo di DPAPI (Data Protection API) di Windows per cifratura
- Chiave di cifratura legata all'account utente corrente Windows
- Impossibile decriptare da altri account o computer
**Network Security**
- Tutte le comunicazioni con Bidoo avvengono tramite HTTPS
- Nessuna trasmissione dati sensibili a server terzi
- L'applicazione non "telefona a casa" o invia telemetria
**Privacy**
- Nessun log remoto
- Nessuna raccolta analytics
- Tutti i dati rimangono in locale sul computer dell'utente
L'uso di strumenti di automazione potrebbe violare i Termini di Servizio della piattaforma Bidoo. L'utente è l'unico responsabile dell'uso che fa di AutoBidder. Gli sviluppatori non si assumono responsabilità per sospensioni account, perdite economiche o altre conseguenze derivanti dall'uso dell'applicazione.
### Best Practices
@@ -658,37 +571,10 @@ L'uso di strumenti di automazione potrebbe violare i Termini di Servizio della p
4. **Backup regolari** di `%AppData%\AutoBidder` per preservare configurazioni
5. **Aggiornare regolarmente** l'applicazione per patch di sicurezza
6. **Usare password sicure** per l'account Windows (protegge DPAPI)
### Limitazioni
- AutoBidder è un tool per uso personale, non commerciale
- Non distribuire o rivendere senza autorizzazione
- Il repository è privato: non pubblicare il codice sorgente
- Non usare per competizioni o tornei dove automazione è proibita
7. **Testa su aste a basso valore** prima di usare configurazioni aggressive
---
## Supporto e Sviluppo
**AutoBidder v4.0** - Developed with ❤️ using .NET 8.0 and WPF
**Repository Privato Gitea**
```
https://192.168.30.23/Alby96/Mimante
```
**Issue Tracker**
Per bug reports, feature requests o domande tecniche, aprire una Issue nel repository.
**Contatti**
Per supporto diretto contattare il maintainer del progetto tramite Gitea.
**Contributori**
Il progetto accetta Pull Requests secondo le convenzioni definite nel CONTRIBUTING.md (da creare).
**Licenza**
Progetto privato - Tutti i diritti riservati. Non distribuire senza autorizzazione esplicita del proprietario.
---
**AutoBidder v4.0** - Developed with ?? using .NET 8.0 and WPF
© 2024 - Per uso personale. Non distribuire.
© 2024 - Per uso personale. Non distribuire.