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
+98 -46
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"
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Riga 1: Puntate + Credito -->
<StackPanel Grid.Row="0" Orientation="Horizontal" HorizontalAlignment="Left" Margin="0,0,0,5">
<TextBlock Text="Puntate: "
Foreground="#999999"
FontSize="13"
Margin="0,0,5,0"/>
<TextBlock x:Name="RemainingBidsText"
Text="0"
Foreground="#00D800"
FontSize="13"
FontWeight="SemiBold"
Margin="0,0,0,5"/>
FontWeight="Bold"
Margin="0,0,25,0"/>
<StackPanel Orientation="Horizontal" Margin="0,0,0,3">
<TextBlock Text="Puntate: "
Foreground="#CCCCCC"
FontSize="12"/>
<TextBlock x:Name="RemainingBidsText"
Text="0"
Foreground="#CCCCCC"
FontSize="12"
FontWeight="Bold"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Aste vinte da confermare: "
Foreground="#FFB700"
FontSize="12"/>
<TextBlock x:Name="BannerAsteDaRiscattare"
Text="0"
Foreground="#FFB700"
FontSize="12"
FontWeight="Bold"/>
</StackPanel>
<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>
<!-- Riga 2: Aste vinte -->
<StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Left">
<TextBlock Text="Aste vinte da confermare: "
Foreground="#999999"
FontSize="12"
Margin="0,0,5,0"/>
<TextBlock x:Name="BannerAsteDaRiscattare"
Text="0"
Foreground="#FFB700"
FontSize="12"
FontWeight="Bold"/>
</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"/>
<!-- 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"/>
<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: 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>
+101 -19
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);
if (result == MessageBoxResult.Yes)
{
RaiseEvent(new RoutedEventArgs(RemoveUrlClickedEvent, this));
}
System.Diagnostics.Debug.WriteLine("[DELETE KEY] Tasto Canc premuto su asta selezionata");
// 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); }
}
}
}
+7 -20
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"/>
+32 -8
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
RaiseEvent(new RoutedEventArgs(SaveCookieClickedEvent, this));
RaiseEvent(new RoutedEventArgs(SaveSettingsClickedEvent, this));
RaiseEvent(new RoutedEventArgs(SaveDefaultsClickedEvent, this));
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)
@@ -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();
@@ -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)
{
LoadExportSettings();
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);
}
}
}
}
+15 -8
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");
+136 -51
View File
@@ -1,4 +1,4 @@
using System;
using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;
@@ -20,34 +20,32 @@ 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)
{
foreach (var vm in _auctionViewModels)
if (!vm.IsActive)
{
if (!vm.IsActive)
{
vm.IsActive = true;
}
vm.IsPaused = false;
vm.IsActive = true;
}
Log("Avviate/riprese le aste (tutte)", LogLevel.Info);
vm.IsPaused = false;
}
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);
_auctionViewModels.Remove(_selectedAuction);
_selectedAuction = null;
var auctionName = _selectedAuction.Name;
var auctionId = _selectedAuction.AuctionId;
SaveAuctions();
UpdateTotalCount();
Log("Asta rimossa", LogLevel.Info);
// 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();
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 (_selectedAuction == null) return;
if (sender is TextBox tb && int.TryParse(tb.Text, out var value) && value >= 0)
{
if (int.TryParse(tb.Text, out var value) && value >= 0)
var oldValue = _selectedAuction.MaxClicks;
_selectedAuction.MaxClicks = value;
// Log solo se non stiamo caricando E il valore è cambiato
if (!_isUpdatingSelection && oldValue != value)
{
_selectedAuction.MaxClicks = value;
SaveAuctions();
_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)
{
+43 -14
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);
}
}
}
+27
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)
+44 -10
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 { }
}
+133 -37
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
{
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
{
UsernameText.Text = "Non connesso";
// 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();
// 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)
if (success && updatedSession != null && !string.IsNullOrEmpty(updatedSession.Username))
{
SetUserBanner(updatedSession?.Username ?? string.Empty, updatedSession?.RemainingBids);
Log($"[OK] Cookie valido - Crediti disponibili: {updatedSession?.RemainingBids ?? 0}");
SetUserBanner(updatedSession.Username, updatedSession.RemainingBids);
Log($"[OK] Cookie valido - Crediti disponibili: {updatedSession.RemainingBids}");
}
else
{
// Fallback: try scraping HTML
var htmlUser = _auctionMonitor.GetUserDataFromHtmlAsync().GetAwaiter().GetResult();
if (htmlUser != null)
{
Dispatcher.Invoke(() =>
{
SetUserBanner(htmlUser.Username, htmlUser.RemainingBids);
Log($"[OK] Dati utente rilevati via HTML - Utente: {htmlUser.Username}, Puntate residue: {htmlUser.RemainingBids}");
});
}
else
{
Dispatcher.Invoke(() =>
{
Log($"[WARN] Impossibile verificare sessione: cookie non valido o rete assente.");
});
}
Log($"[WARN] Impossibile verificare sessione: verifica cookie nelle Impostazioni");
}
});
}
+94
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`)
@@ -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+
+199
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
@@ -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** ??
+49 -4
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"
+9 -9
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()
{
+13 -10
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
+22 -1
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
}
}
+174 -263
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;
await ExecuteBidStrategy(auction, state, token);
}
// 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
});
}
}
}
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,54 +417,77 @@ 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)
if (!isNewBid &&
!string.IsNullOrEmpty(lastBidder) &&
!string.IsNullOrEmpty(state.LastBidder) &&
!lastBidder.Equals(state.LastBidder, StringComparison.OrdinalIgnoreCase))
// Fallback: cambio utente
if (!isNewBid &&
!string.IsNullOrEmpty(lastBidder) &&
!string.IsNullOrEmpty(state.LastBidder) &&
!lastBidder.Equals(state.LastBidder, StringComparison.OrdinalIgnoreCase))
{
isNewBid = true;
}
if (isNewBid)
{
auction.ResetCount++;
auction.BidHistory.Add(new BidHistory
{
isNewBid = true;
}
if (isNewBid)
{
auction.ResetCount++;
auction.BidHistory.Add(new BidHistory
{
Timestamp = DateTime.UtcNow,
EventType = BidEventType.Reset,
Bidder = state.LastBidder,
Price = state.Price,
Timer = state.Timer,
Notes = $"Puntata: EUR{state.Price:F2}"
});
Timestamp = DateTime.UtcNow,
EventType = BidEventType.Reset,
Bidder = state.LastBidder,
Price = state.Price,
Timer = state.Timer,
Notes = $"Puntata: EUR{state.Price:F2}"
});
// Aggiorna statistiche bidder
if (!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);
}
}
+226 -147
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");
Log($"[USER INFO RESPONSE] Status: {(int)response.StatusCode} {response.StatusCode}, Latency: {latency}ms");
var responseText = await response.Content.ReadAsStringAsync();
Log($"[USER INFO RESPONSE] Body length: {responseText.Length}");
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;
}
_session.LastAccountUpdate = DateTime.UtcNow;
return true;
// 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>");
if (userMatch.Success)
bool foundUsername = false;
bool foundBids = false;
// Estrai nome utente - pattern multipli per maggiore robustezza
var usernamePatterns = new[]
{
userData.Username = userMatch.Groups[1].Value.Trim();
}
// Estrai puntate residue
var bidsMatch = System.Text.RegularExpressions.Regex.Match(html, @"<span id=""divSaldoBidBottom""[^>]*>(\d+)</span>");
if (bidsMatch.Success && int.TryParse(bidsMatch.Groups[1].Value, out int bids))
@"<a class=""pers_lnk""[^>]*>([^<]+)</a>",
@"<a[^>]*class=""pers_lnk""[^>]*>([^<]+)</a>",
@"<span[^>]*class=""username""[^>]*>([^<]+)</span>",
@"BidooCnf\.userObj\.username\s*=\s*'([^']+)'"
};
foreach (var pattern in usernamePatterns)
{
userData.RemainingBids = bids;
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;
}
}
if (!string.IsNullOrEmpty(userData.Username) && userData.RemainingBids > 0)
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 (!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;
}
}
@@ -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();
}
}
}
+4 -4
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}," +
+50 -43
View File
@@ -4,51 +4,58 @@ using System.Text.Json;
namespace AutoBidder.Utilities
{
internal class AppSettings
{
public string? ExportPath { get; set; }
public string? LastExportExt { get; set; }
public string ExportScope { get; set; } = "All"; // All, Closed, Unknown
public bool IncludeOnlyUsedBids { get; set; } = true;
public bool IncludeLogs { get; set; } = false;
public bool IncludeUserBids { get; set; } = false;
internal class AppSettings
{
public string? ExportPath { get; set; }
public string? LastExportExt { get; set; }
public string ExportScope { get; set; } = "All"; // All, Closed, Unknown
public bool IncludeOnlyUsedBids { get; set; } = true;
public bool IncludeLogs { get; set; } = false;
public bool IncludeUserBids { get; set; } = false;
// Added properties to match MainWindow expectations
public bool ExportOpen { get; set; } = true;
public bool ExportClosed { get; set; } = true;
public bool ExportUnknown { get; set; } = true;
public bool IncludeMetadata { get; set; } = true;
public bool RemoveAfterExport { get; set; } = false;
public bool OverwriteExisting { get; set; } = false;
}
// Added properties to match MainWindow expectations
public bool ExportOpen { get; set; } = true;
public bool ExportClosed { get; set; } = true;
public bool ExportUnknown { get; set; } = true;
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
{
private static readonly string _folder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "AutoBidder");
private static readonly string _file = Path.Combine(_folder, "settings.json");
internal static class SettingsManager
{
private static readonly string _folder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "AutoBidder");
private static readonly string _file = Path.Combine(_folder, "settings.json");
public static AppSettings Load()
{
try
{
if (!File.Exists(_file)) return new AppSettings();
var txt = File.ReadAllText(_file);
var s = JsonSerializer.Deserialize<AppSettings>(txt);
if (s == null) return new AppSettings();
return s;
}
catch { return new AppSettings(); }
}
public static AppSettings Load()
{
try
{
if (!File.Exists(_file)) return new AppSettings();
var txt = File.ReadAllText(_file);
var s = JsonSerializer.Deserialize<AppSettings>(txt);
if (s == null) return new AppSettings();
return s;
}
catch { return new AppSettings(); }
}
public static void Save(AppSettings settings)
{
try
{
if (!Directory.Exists(_folder)) Directory.CreateDirectory(_folder);
var txt = JsonSerializer.Serialize(settings, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(_file, txt);
}
catch { }
}
}
public static void Save(AppSettings settings)
{
try
{
if (!Directory.Exists(_folder)) Directory.CreateDirectory(_folder);
var txt = JsonSerializer.Serialize(settings, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText(_file, txt);
}
catch { }
}
}
}
+37 -15
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,10 +238,26 @@ 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;