Aggiornamento alla versione 3.0.0
- Rimosse funzionalità legacy legate a WebView2. - Introdotto uno stile globale per i pulsanti. - Semplificata l'interfaccia con gestione tramite griglia unica. - Aggiunti comandi per avviare, mettere in pausa e fermare aste. - Introdotta gestione manuale dei cookie tramite dialog. - Aggiunti dialog per configurare sessione e aggiungere aste. - Migliorata la persistenza con salvataggio sicuro (DPAPI). - Rifattorizzate statistiche per utilizzare `BidHistory` e `BidderStats`. - Ottimizzato il polling per ridurre il carico di sistema. - Aggiornata esportazione CSV con dati più dettagliati. - Introdotti nuovi modelli dati per utente e banner aste. - Rimossi file di test manuale e codice obsoleto. - Aggiornata documentazione per riflettere le modifiche. - Aggiunta nuova icona dell'applicazione. - Migliorata la sicurezza eliminando il salvataggio in chiaro dei cookie.
This commit is contained in:
@@ -5,9 +5,25 @@
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Converters/Converters.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!-- Stile pulsanti globale -->
|
||||
<Style x:Key="SmallButtonStyle" TargetType="Button">
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="12"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
|
||||
[Binary ICO placeholder removed in this environment]
|
||||
@@ -11,13 +11,22 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3530-prerelease" />
|
||||
<Compile Remove=".github\**" />
|
||||
<EmbeddedResource Remove=".github\**" />
|
||||
<None Remove=".github\**" />
|
||||
<Page Remove=".github\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="Icon\favicon.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6584" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Ensure the application icon is included as a WPF Resource so Icon="Assets/app.ico" resolves -->
|
||||
<Resource Include="Assets\app.ico" />
|
||||
<Resource Include="Icon\favicon.ico" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
<Window x:Class="AutoBidder.BrowserWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
|
||||
Title="Browser - Bidoo"
|
||||
Height="800" Width="1200"
|
||||
Background="#101219" Foreground="#E6EDF3">
|
||||
|
||||
<Window.Resources>
|
||||
<Style x:Key="AddressBarStyle" TargetType="TextBox">
|
||||
<Setter Property="Background" Value="#0B1220" />
|
||||
<Setter Property="Foreground" Value="#E6EDF3" />
|
||||
<Setter Property="BorderBrush" Value="#263143" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="Padding" Value="8,6" />
|
||||
<Setter Property="FontSize" Value="13" />
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Barra navigazione -->
|
||||
<Grid Grid.Row="0" Margin="12,12,12,6" Background="#0B1015">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Button x:Name="BackButton" Grid.Column="0" Content="?" Click="BackButton_Click"
|
||||
Background="#374151" Foreground="White" Padding="12,8" Margin="8,8,6,8"
|
||||
BorderThickness="0" FontWeight="Bold" FontSize="14" MinWidth="40" Height="38" IsEnabled="False">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="6">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
|
||||
<Button x:Name="RefreshButton" Grid.Column="1" Content="?" Click="RefreshButton_Click"
|
||||
Background="#0EA5E9" Foreground="White" Padding="12,8" Margin="0,8,8,8"
|
||||
BorderThickness="0" FontWeight="Bold" FontSize="16" MinWidth="40" Height="38">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="6">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
|
||||
<TextBox x:Name="AddressBar" Grid.Column="2" Style="{StaticResource AddressBarStyle}"
|
||||
Text="https://it.bidoo.com" KeyDown="AddressBar_KeyDown" Margin="0,8,8,8" />
|
||||
|
||||
<Button x:Name="NavigateButton" Grid.Column="3" Content="Vai" Click="NavigateButton_Click"
|
||||
Background="#16A34A" Foreground="White" Padding="20,8" Margin="0,8,8,8"
|
||||
BorderThickness="0" FontWeight="Bold" FontSize="14" Height="38">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="6">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
|
||||
<Button x:Name="AddCurrentPageButton" Grid.Column="4" Content="? Aggiungi Asta" Click="AddCurrentPageButton_Click"
|
||||
Background="#8B5CF6" Foreground="White" Padding="16,8" Margin="0,8,8,8"
|
||||
BorderThickness="0" FontWeight="SemiBold" FontSize="13" Height="38">
|
||||
<Button.Template>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}" CornerRadius="6">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Button.Template>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<!-- WebView -->
|
||||
<Border Grid.Row="1" Margin="12,0,12,12" CornerRadius="8" Background="#0B1015">
|
||||
<wv2:WebView2 x:Name="webView" Source="https://it.bidoo.com" Margin="2" />
|
||||
</Border>
|
||||
</Grid>
|
||||
</Window>
|
||||
@@ -1,204 +0,0 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
|
||||
namespace AutoBidder
|
||||
{
|
||||
/// <summary>
|
||||
/// Finestra browser separata per navigazione Bidoo
|
||||
/// </summary>
|
||||
public partial class BrowserWindow : Window
|
||||
{
|
||||
public event Action<string>? OnAddAuction;
|
||||
|
||||
public BrowserWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
Loaded += BrowserWindow_Loaded;
|
||||
}
|
||||
|
||||
private async void BrowserWindow_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (webView.CoreWebView2 == null)
|
||||
{
|
||||
await webView.EnsureCoreWebView2Async();
|
||||
}
|
||||
|
||||
webView.NavigationCompleted += WebView_NavigationCompleted;
|
||||
webView.NavigationStarting += WebView_NavigationStarting;
|
||||
|
||||
// Context menu per aggiungere asta
|
||||
if (webView.CoreWebView2 != null)
|
||||
{
|
||||
webView.CoreWebView2.ContextMenuRequested += CoreWebView2_ContextMenuRequested;
|
||||
webView.CoreWebView2.Navigate("https://it.bidoo.com");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Errore inizializzazione: {ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void CoreWebView2_ContextMenuRequested(object? sender, CoreWebView2ContextMenuRequestedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentUrl = webView.CoreWebView2?.Source ?? "";
|
||||
|
||||
if (IsValidAuctionUrl(currentUrl) && webView.CoreWebView2 != null)
|
||||
{
|
||||
// Aggiungi voce menu contestuale
|
||||
var menuItem = webView.CoreWebView2.Environment.CreateContextMenuItem(
|
||||
"Aggiungi asta al monitoraggio",
|
||||
null,
|
||||
CoreWebView2ContextMenuItemKind.Command);
|
||||
|
||||
menuItem.CustomItemSelected += (s, args) =>
|
||||
{
|
||||
OnAddAuction?.Invoke(currentUrl);
|
||||
};
|
||||
|
||||
e.MenuItems.Insert(0, menuItem);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void WebView_NavigationStarting(object? sender, CoreWebView2NavigationStartingEventArgs e)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(e.Uri) && !IsBidooUrl(e.Uri))
|
||||
{
|
||||
e.Cancel = true;
|
||||
MessageBox.Show("Solo domini Bidoo consentiti!", "Navigazione Bloccata", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
private void WebView_NavigationCompleted(object? sender, CoreWebView2NavigationCompletedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
Dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
BackButton.IsEnabled = webView.CoreWebView2?.CanGoBack ?? false;
|
||||
|
||||
var url = webView.CoreWebView2?.Source ?? "";
|
||||
if (!string.IsNullOrEmpty(url))
|
||||
{
|
||||
AddressBar.Text = url;
|
||||
}
|
||||
});
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
private bool IsBidooUrl(string url)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(url)) return false;
|
||||
|
||||
try
|
||||
{
|
||||
var uri = new Uri(url);
|
||||
var host = uri.Host.ToLowerInvariant();
|
||||
|
||||
return host.Contains("bidoo.com") || host.Contains("bidoo.it") ||
|
||||
host.Contains("bidoo.fr") || host.Contains("bidoo.es") ||
|
||||
host.Contains("bidoo.de");
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool IsValidAuctionUrl(string url)
|
||||
{
|
||||
if (!IsBidooUrl(url)) return false;
|
||||
|
||||
try
|
||||
{
|
||||
var uri = new Uri(url);
|
||||
return uri.AbsolutePath.Contains("/asta/") || uri.Query.Contains("?a=");
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void BackButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
webView.CoreWebView2?.GoBack();
|
||||
}
|
||||
|
||||
private void RefreshButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
webView.CoreWebView2?.Reload();
|
||||
}
|
||||
|
||||
private void NavigateButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
NavigateToAddress();
|
||||
}
|
||||
|
||||
private void AddressBar_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Enter)
|
||||
{
|
||||
NavigateToAddress();
|
||||
}
|
||||
}
|
||||
|
||||
private void NavigateToAddress()
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = AddressBar.Text.Trim();
|
||||
|
||||
if (string.IsNullOrEmpty(url)) return;
|
||||
|
||||
if (!url.StartsWith("http"))
|
||||
{
|
||||
url = "https://" + url;
|
||||
}
|
||||
|
||||
if (!IsBidooUrl(url))
|
||||
{
|
||||
MessageBox.Show("Solo URL Bidoo consentiti!", "URL Non Valido", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
webView.CoreWebView2?.Navigate(url);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Errore navigazione: {ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void AddCurrentPageButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentUrl = webView.CoreWebView2?.Source ?? "";
|
||||
|
||||
if (string.IsNullOrEmpty(currentUrl) || !IsValidAuctionUrl(currentUrl))
|
||||
{
|
||||
MessageBox.Show("La pagina corrente non <20> un'asta valida!", "URL Non Valido", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
|
||||
OnAddAuction?.Invoke(currentUrl);
|
||||
MessageBox.Show("Asta aggiunta al monitoraggio!", "Successo", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
MessageBox.Show($"Errore: {ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace AutoBidder.Converters
|
||||
{
|
||||
public class AndNotPausedConverter : IMultiValueConverter
|
||||
{
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (values.Length == 2 && values[0] is bool isActive && values[1] is bool isPaused)
|
||||
{
|
||||
return isActive && !isPaused;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace AutoBidder.Converters
|
||||
{
|
||||
// Converte bool in Opacity: true -> 0.5 (disabilitato), false -> 1.0 (abilitato)
|
||||
public class BoolToOpacityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
bool inverse = parameter?.ToString() == "Inverse";
|
||||
if (value is bool b)
|
||||
{
|
||||
if (inverse)
|
||||
return b ? 0.5 : 1.0;
|
||||
else
|
||||
return b ? 1.0 : 0.5;
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
// Converte (IsActive, IsPaused) in Opacity per il pulsante Pausa
|
||||
public class PauseButtonOpacityConverter : IMultiValueConverter
|
||||
{
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (values.Length == 2 && values[0] is bool isActive && values[1] is bool isPaused)
|
||||
{
|
||||
return (isActive && !isPaused) ? 1.0 : 0.5;
|
||||
}
|
||||
return 0.5;
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:AutoBidder.Converters">
|
||||
<local:InverseBoolConverter x:Key="InverseBoolConverter"/>
|
||||
<local:AndNotPausedConverter x:Key="AndNotPausedConverter"/>
|
||||
<local:StartResumeConverter x:Key="StartResumeConverter"/>
|
||||
<local:BoolToOpacityConverter x:Key="BoolToOpacityConverter"/>
|
||||
<local:PauseButtonOpacityConverter x:Key="PauseButtonOpacityConverter"/>
|
||||
<local:StartButtonOpacityConverter x:Key="StartButtonOpacityConverter"/>
|
||||
</ResourceDictionary>
|
||||
@@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace AutoBidder.Converters
|
||||
{
|
||||
public class InverseBoolConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool b)
|
||||
return !b;
|
||||
return true;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (value is bool b)
|
||||
return !b;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace AutoBidder.Converters
|
||||
{
|
||||
public class StartButtonOpacityConverter : IMultiValueConverter
|
||||
{
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (values.Length == 2 && values[0] is bool isActive && values[1] is bool isPaused)
|
||||
{
|
||||
// Bright (1.0) when not active (can start) or when paused (can resume)
|
||||
return (!isActive || isPaused) ? 1.0 : 0.5;
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace AutoBidder.Converters
|
||||
{
|
||||
// Returns true when the Start/Resume button for a row should be enabled:
|
||||
// Enabled when NOT active (stopped) OR when paused (to resume).
|
||||
public class StartResumeConverter : IMultiValueConverter
|
||||
{
|
||||
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
if (values.Length >= 2 && values[0] is bool isActive && values[1] is bool isPaused)
|
||||
{
|
||||
return (!isActive) || isPaused;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Mimante/Dialogs/AddAuctionSimpleDialog.xaml
Normal file
28
Mimante/Dialogs/AddAuctionSimpleDialog.xaml
Normal file
@@ -0,0 +1,28 @@
|
||||
<Window x:Class="AutoBidder.Dialogs.AddAuctionSimpleDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Aggiungi Asta" Height="220" Width="600"
|
||||
Background="#0a0a0a" Foreground="#FFFFFF"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Icon="pack://application:,,,/Icon/favicon.ico"
|
||||
ResizeMode="NoResize">
|
||||
<Border Background="#1a1a1a" CornerRadius="8" Padding="16" Margin="8">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Text="Inserire URL dell'asta" Foreground="#CCCCCC" FontSize="14" Margin="0,0,0,10" />
|
||||
<TextBox x:Name="AuctionUrlBox" Grid.Row="1" MinWidth="320" Margin="0,0,0,8"
|
||||
Background="#181818" Foreground="#00CCFF" BorderBrush="#444" BorderThickness="1"
|
||||
Padding="8" FontSize="13" ToolTip="Inserisci l'URL completo dell'asta" />
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,8,0,0">
|
||||
<Button x:Name="OkButton" Content="OK" Width="110" Margin="6" Padding="10,8"
|
||||
Style="{StaticResource SmallButtonStyle}" Background="#00CC66" Foreground="White" Click="OkButton_Click" />
|
||||
<Button x:Name="CancelButton" Content="Annulla" Width="110" Margin="6" Padding="10,8"
|
||||
Style="{StaticResource SmallButtonStyle}" Background="#666" Foreground="White" Click="CancelButton_Click" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
33
Mimante/Dialogs/AddAuctionSimpleDialog.xaml.cs
Normal file
33
Mimante/Dialogs/AddAuctionSimpleDialog.xaml.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace AutoBidder.Dialogs
|
||||
{
|
||||
public partial class AddAuctionSimpleDialog : Window
|
||||
{
|
||||
public string AuctionId { get; private set; } = string.Empty;
|
||||
|
||||
public AddAuctionSimpleDialog()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void OkButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var text = AuctionUrlBox.Text?.Trim() ?? string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(text) || !text.StartsWith("http"))
|
||||
{
|
||||
MessageBox.Show("Inserisci un URL valido dell'asta.", "Errore", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
AuctionId = text;
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void CancelButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
41
Mimante/Dialogs/SessionDialog.xaml
Normal file
41
Mimante/Dialogs/SessionDialog.xaml
Normal file
@@ -0,0 +1,41 @@
|
||||
<Window x:Class="AutoBidder.Dialogs.SessionDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Title="Configura Sessione" Height="440" Width="700"
|
||||
Background="#0a0a0a" Foreground="#FFFFFF"
|
||||
WindowStartupLocation="CenterOwner"
|
||||
Icon="pack://application:,,,/Icon/favicon.ico"
|
||||
ResizeMode="NoResize">
|
||||
<Border Background="#1a1a1a" CornerRadius="8" Padding="16" Margin="8">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock Text="Inserisci Cookie di sessione" FontSize="16" FontWeight="SemiBold" Foreground="#00CC66" Margin="0,0,0,12" />
|
||||
<TextBlock Grid.Row="1" Text="Cookie di sessione (copia dal menu delle Impostazioni/Applicazioni di Chrome)" Foreground="#CCCCCC" FontSize="13" />
|
||||
<TextBlock Grid.Row="2" Foreground="#888" FontSize="12" TextWrapping="Wrap" Margin="0,4,0,10">
|
||||
<Run Text="Come trovare i cookie di sessione in Chrome:" />
|
||||
<LineBreak/>
|
||||
<Run Text="1. Apri Chrome e premi F12 (Windows) o Cmd+Option+I (Mac) per aprire gli Strumenti per sviluppatori." />
|
||||
<LineBreak/>
|
||||
<Run Text="2. Vai alla scheda 'Application'." />
|
||||
<LineBreak/>
|
||||
<Run Text="3. Nel pannello a sinistra, espandi 'Storage' e seleziona 'Cookies'." />
|
||||
<LineBreak/>
|
||||
<Run Text="4. Scegli il sito desiderato e copia il valore del cookie di sessione." />
|
||||
</TextBlock>
|
||||
<TextBox x:Name="CookieBox" Grid.Row="3" MinWidth="320" MinHeight="120" MaxHeight="220" VerticalScrollBarVisibility="Auto"
|
||||
Background="#181818" Foreground="#00CCFF" BorderBrush="#444" BorderThickness="1" Padding="8" FontSize="14" AcceptsReturn="True" TextWrapping="Wrap" />
|
||||
<StackPanel Grid.Row="4" Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,12,0,0">
|
||||
<Button x:Name="OkButton" Content="OK" Width="110" Margin="6" Padding="10,8"
|
||||
Style="{StaticResource SmallButtonStyle}" Background="#00CC66" Foreground="White" Click="OkButton_Click" />
|
||||
<Button x:Name="CancelButton" Content="Annulla" Width="110" Margin="6" Padding="10,8"
|
||||
Style="{StaticResource SmallButtonStyle}" Background="#666" Foreground="White" Click="CancelButton_Click" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</Window>
|
||||
33
Mimante/Dialogs/SessionDialog.xaml.cs
Normal file
33
Mimante/Dialogs/SessionDialog.xaml.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.Windows;
|
||||
|
||||
namespace AutoBidder.Dialogs
|
||||
{
|
||||
public partial class SessionDialog : Window
|
||||
{
|
||||
public string AuthToken { get; private set; } = string.Empty;
|
||||
|
||||
public SessionDialog(string existingToken = "", string existingUsername = "")
|
||||
{
|
||||
InitializeComponent();
|
||||
CookieBox.Text = existingToken ?? string.Empty;
|
||||
}
|
||||
|
||||
private void OkButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
AuthToken = CookieBox.Text?.Trim() ?? string.Empty;
|
||||
if (string.IsNullOrEmpty(AuthToken))
|
||||
{
|
||||
MessageBox.Show("Cookie obbligatorio", "Errore", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
|
||||
private void CancelButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
DialogResult = false;
|
||||
Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,247 +0,0 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace AutoBidder
|
||||
{
|
||||
/// <summary>
|
||||
/// Dialog per inizializzare sessione Bidoo
|
||||
/// Richiede: Auth Token + Username
|
||||
/// </summary>
|
||||
public class SessionDialog : Window
|
||||
{
|
||||
private readonly TextBox _tokenTextBox;
|
||||
private readonly TextBox _usernameTextBox;
|
||||
|
||||
public string AuthToken => _tokenTextBox.Text.Trim();
|
||||
public string Username => _usernameTextBox.Text.Trim();
|
||||
|
||||
public SessionDialog(string existingToken = "", string existingUsername = "")
|
||||
{
|
||||
Title = "Configura Sessione Bidoo";
|
||||
Width = 680;
|
||||
Height = 420;
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
ResizeMode = ResizeMode.NoResize;
|
||||
Background = System.Windows.Media.Brushes.Black;
|
||||
Foreground = System.Windows.Media.Brushes.White;
|
||||
|
||||
var grid = new Grid { Margin = new Thickness(20) };
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(10) });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(140) });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(15) });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(10) });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(40) });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(20) });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
|
||||
var label1 = new TextBlock
|
||||
{
|
||||
Text = "Cookie __stattrb (F12 > Application > Cookies > __stattrb):",
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = System.Windows.Media.Brushes.White,
|
||||
TextWrapping = TextWrapping.Wrap
|
||||
};
|
||||
Grid.SetRow(label1, 0);
|
||||
|
||||
_tokenTextBox = new TextBox
|
||||
{
|
||||
Text = existingToken,
|
||||
Padding = new Thickness(10),
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
AcceptsReturn = false,
|
||||
VerticalScrollBarVisibility = ScrollBarVisibility.Auto,
|
||||
VerticalContentAlignment = VerticalAlignment.Top,
|
||||
Background = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(15, 15, 15)),
|
||||
Foreground = System.Windows.Media.Brushes.LightGray,
|
||||
FontFamily = new System.Windows.Media.FontFamily("Consolas"),
|
||||
FontSize = 11,
|
||||
ToolTip = "Esempio: eyJVU0VSSUQiOiI2NzA3NjY0Ii..."
|
||||
};
|
||||
Grid.SetRow(_tokenTextBox, 2);
|
||||
|
||||
var label2 = new TextBlock
|
||||
{
|
||||
Text = "Username Bidoo:",
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = System.Windows.Media.Brushes.White
|
||||
};
|
||||
Grid.SetRow(label2, 4);
|
||||
|
||||
_usernameTextBox = new TextBox
|
||||
{
|
||||
Text = existingUsername,
|
||||
Padding = new Thickness(10),
|
||||
FontSize = 14,
|
||||
VerticalContentAlignment = VerticalAlignment.Center,
|
||||
Background = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(15, 15, 15)),
|
||||
Foreground = System.Windows.Media.Brushes.LightGray
|
||||
};
|
||||
Grid.SetRow(_usernameTextBox, 6);
|
||||
|
||||
var buttonPanel = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
HorizontalAlignment = HorizontalAlignment.Right
|
||||
};
|
||||
Grid.SetRow(buttonPanel, 8);
|
||||
|
||||
var okButton = new Button
|
||||
{
|
||||
Content = "Conferma",
|
||||
Width = 120,
|
||||
Height = 40,
|
||||
Margin = new Thickness(0, 0, 10, 0),
|
||||
Background = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0, 204, 102)),
|
||||
Foreground = System.Windows.Media.Brushes.White,
|
||||
FontWeight = FontWeights.Bold,
|
||||
FontSize = 14
|
||||
};
|
||||
var cancelButton = new Button
|
||||
{
|
||||
Content = "Annulla",
|
||||
Width = 120,
|
||||
Height = 40,
|
||||
Background = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(204, 0, 0)),
|
||||
Foreground = System.Windows.Media.Brushes.White,
|
||||
FontWeight = FontWeights.Bold,
|
||||
FontSize = 14
|
||||
};
|
||||
|
||||
okButton.Click += (s, e) =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(AuthToken) || string.IsNullOrWhiteSpace(Username))
|
||||
{
|
||||
MessageBox.Show("Inserisci Token e Username!", "Errore", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return;
|
||||
}
|
||||
DialogResult = true;
|
||||
Close();
|
||||
};
|
||||
cancelButton.Click += (s, e) => { DialogResult = false; Close(); };
|
||||
|
||||
buttonPanel.Children.Add(okButton);
|
||||
buttonPanel.Children.Add(cancelButton);
|
||||
|
||||
grid.Children.Add(label1);
|
||||
grid.Children.Add(_tokenTextBox);
|
||||
grid.Children.Add(label2);
|
||||
grid.Children.Add(_usernameTextBox);
|
||||
grid.Children.Add(buttonPanel);
|
||||
|
||||
Content = grid;
|
||||
|
||||
Loaded += (s, e) => _tokenTextBox.Focus();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dialog semplificato per aggiungere asta (ID o URL completo)
|
||||
/// </summary>
|
||||
public class AddAuctionSimpleDialog : Window
|
||||
{
|
||||
private readonly TextBox _auctionIdTextBox;
|
||||
public string AuctionId => _auctionIdTextBox.Text.Trim();
|
||||
|
||||
public AddAuctionSimpleDialog()
|
||||
{
|
||||
Title = "Aggiungi Asta";
|
||||
Width = 680;
|
||||
Height = 280;
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
ResizeMode = ResizeMode.NoResize;
|
||||
Background = System.Windows.Media.Brushes.Black;
|
||||
Foreground = System.Windows.Media.Brushes.White;
|
||||
|
||||
var grid = new Grid { Margin = new Thickness(20) };
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(10) });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(50) });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(10) });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(20) });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(50) });
|
||||
|
||||
var label = new TextBlock
|
||||
{
|
||||
Text = "URL Asta o ID:",
|
||||
FontWeight = FontWeights.SemiBold,
|
||||
Foreground = System.Windows.Media.Brushes.White
|
||||
};
|
||||
Grid.SetRow(label, 0);
|
||||
|
||||
_auctionIdTextBox = new TextBox
|
||||
{
|
||||
Text = "",
|
||||
Padding = new Thickness(10),
|
||||
FontSize = 13,
|
||||
Background = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(15, 15, 15)),
|
||||
Foreground = System.Windows.Media.Brushes.LightGray,
|
||||
VerticalContentAlignment = VerticalAlignment.Center
|
||||
};
|
||||
Grid.SetRow(_auctionIdTextBox, 2);
|
||||
|
||||
var hintLabel = new TextBlock
|
||||
{
|
||||
Text = "Esempio: https://it.bidoo.com/auction.php?a=Galaxy_S25_Ultra_256GB_81204324\nOppure: 81204324",
|
||||
FontSize = 11,
|
||||
Foreground = System.Windows.Media.Brushes.Gray,
|
||||
TextWrapping = TextWrapping.Wrap
|
||||
};
|
||||
Grid.SetRow(hintLabel, 4);
|
||||
|
||||
var buttonPanel = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Horizontal,
|
||||
HorizontalAlignment = HorizontalAlignment.Right
|
||||
};
|
||||
Grid.SetRow(buttonPanel, 6);
|
||||
|
||||
var okButton = new Button
|
||||
{
|
||||
Content = "Aggiungi",
|
||||
Width = 120,
|
||||
Height = 40,
|
||||
Margin = new Thickness(0, 0, 10, 0),
|
||||
Background = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(0, 204, 102)),
|
||||
Foreground = System.Windows.Media.Brushes.White,
|
||||
FontWeight = FontWeights.Bold,
|
||||
FontSize = 14
|
||||
};
|
||||
var cancelButton = new Button
|
||||
{
|
||||
Content = "Annulla",
|
||||
Width = 120,
|
||||
Height = 40,
|
||||
Background = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromRgb(204, 0, 0)),
|
||||
Foreground = System.Windows.Media.Brushes.White,
|
||||
FontWeight = FontWeights.Bold,
|
||||
FontSize = 14
|
||||
};
|
||||
|
||||
okButton.Click += (s, e) => { DialogResult = true; Close(); };
|
||||
cancelButton.Click += (s, e) => { DialogResult = false; Close(); };
|
||||
_auctionIdTextBox.KeyDown += (s, e) =>
|
||||
{
|
||||
if (e.Key == System.Windows.Input.Key.Enter)
|
||||
{
|
||||
DialogResult = true;
|
||||
Close();
|
||||
}
|
||||
};
|
||||
|
||||
buttonPanel.Children.Add(okButton);
|
||||
buttonPanel.Children.Add(cancelButton);
|
||||
|
||||
grid.Children.Add(label);
|
||||
grid.Children.Add(_auctionIdTextBox);
|
||||
grid.Children.Add(hintLabel);
|
||||
grid.Children.Add(buttonPanel);
|
||||
|
||||
Content = grid;
|
||||
|
||||
Loaded += (s, e) => _auctionIdTextBox.Focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
Mimante/Icon/favicon.ico
Normal file
BIN
Mimante/Icon/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
@@ -1,11 +1,10 @@
|
||||
<Window x:Class="AutoBidder.MainWindow"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:AutoBidder.Converters"
|
||||
Title="AutoBidder v3.0" Height="800" Width="1400"
|
||||
Background="#0a0a0a" Foreground="#FFFFFF"
|
||||
WindowStartupLocation="CenterScreen">
|
||||
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Icon="pack://application:,,,/Icon/favicon.ico">
|
||||
<Window.Resources>
|
||||
<!-- Stile pulsanti stile vecchia versione -->
|
||||
<Style x:Key="MainButtonStyle" TargetType="Button">
|
||||
@@ -26,25 +25,128 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="SmallButtonStyle" TargetType="Button">
|
||||
<Setter Property="Foreground" Value="White" />
|
||||
<Setter Property="FontWeight" Value="SemiBold" />
|
||||
<Setter Property="FontSize" Value="12" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Button">
|
||||
<Border Background="{TemplateBinding Background}"
|
||||
CornerRadius="6"
|
||||
Padding="{TemplateBinding Padding}">
|
||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
|
||||
<!-- Grid action button styles (enable/opacity based on auction state) -->
|
||||
<Style x:Key="GridStartButtonStyle" TargetType="Button" BasedOn="{StaticResource SmallButtonStyle}">
|
||||
<Setter Property="IsEnabled" Value="True" />
|
||||
<Setter Property="Opacity" Value="1" />
|
||||
<Style.Triggers>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsActive}" Value="True" />
|
||||
<Condition Binding="{Binding IsPaused}" Value="False" />
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="IsEnabled" Value="False" />
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
</MultiDataTrigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Trigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="0.5" Duration="0:0:0.15" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.EnterActions>
|
||||
<Trigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.15" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.ExitActions>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="GridPauseButtonStyle" TargetType="Button" BasedOn="{StaticResource SmallButtonStyle}">
|
||||
<Setter Property="IsEnabled" Value="False" />
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
<Style.Triggers>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsActive}" Value="True" />
|
||||
<Condition Binding="{Binding IsPaused}" Value="False" />
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="IsEnabled" Value="True" />
|
||||
<Setter Property="Opacity" Value="1" />
|
||||
</MultiDataTrigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Trigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="0.5" Duration="0:0:0.15" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.EnterActions>
|
||||
<Trigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.15" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.ExitActions>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="GridStopButtonStyle" TargetType="Button" BasedOn="{StaticResource SmallButtonStyle}">
|
||||
<Setter Property="IsEnabled" Value="False" />
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsActive}" Value="True">
|
||||
<Setter Property="IsEnabled" Value="True" />
|
||||
<Setter Property="Opacity" Value="1" />
|
||||
</DataTrigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Trigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="0.5" Duration="0:0:0.15" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.EnterActions>
|
||||
<Trigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.15" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.ExitActions>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="GridManualBidButtonStyle" TargetType="Button" BasedOn="{StaticResource SmallButtonStyle}">
|
||||
<Setter Property="IsEnabled" Value="False" />
|
||||
<Setter Property="Opacity" Value="0.5" />
|
||||
<Style.Triggers>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsActive}" Value="True" />
|
||||
<Condition Binding="{Binding IsPaused}" Value="False" />
|
||||
</MultiDataTrigger.Conditions>
|
||||
<Setter Property="IsEnabled" Value="True" />
|
||||
<Setter Property="Opacity" Value="1" />
|
||||
</MultiDataTrigger>
|
||||
<Trigger Property="IsEnabled" Value="False">
|
||||
<Trigger.EnterActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="0.5" Duration="0:0:0.15" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.EnterActions>
|
||||
<Trigger.ExitActions>
|
||||
<BeginStoryboard>
|
||||
<Storyboard>
|
||||
<DoubleAnimation Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.15" />
|
||||
</Storyboard>
|
||||
</BeginStoryboard>
|
||||
</Trigger.ExitActions>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
|
||||
</Window.Resources>
|
||||
|
||||
<Grid Margin="12" Background="#0a0a0a">
|
||||
@@ -60,42 +162,39 @@
|
||||
<Border Grid.Row="0" Background="#1a1a1a" Padding="12" CornerRadius="8" Margin="0,0,0,12">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Info Utente -->
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,20,0">
|
||||
<TextBlock Text="User" FontSize="16" Margin="0,0,8,0" Foreground="#00CC66" FontWeight="Bold" />
|
||||
<TextBlock x:Name="UsernameText" Text="Non configurato" FontSize="13" FontWeight="Bold" Foreground="#00CC66" VerticalAlignment="Center" />
|
||||
<TextBlock Text=" | Bids " FontSize="13" Foreground="#666" Margin="12,0,8,0" />
|
||||
<TextBlock x:Name="RemainingBidsText" Text="--" FontSize="13" FontWeight="Bold" Foreground="#0099FF" VerticalAlignment="Center" />
|
||||
<!-- Sinistra: Utente, Puntate, Aste da confermare -->
|
||||
<StackPanel Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left">
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,2">
|
||||
<TextBlock Text="Utente: " FontSize="14" Foreground="#00CC66" FontWeight="Bold" />
|
||||
<TextBlock x:Name="UsernameText" Text="Non configurato" FontSize="14" FontWeight="Bold" Foreground="#00CC66" Width="160" TextTrimming="CharacterEllipsis" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Margin="0,0,0,2">
|
||||
<TextBlock Text="Puntate: " FontSize="14" Foreground="#666" FontWeight="Bold" />
|
||||
<TextBlock x:Name="RemainingBidsText" Text="--" FontSize="14" FontWeight="Bold" Foreground="#0099FF" Width="80" TextTrimming="CharacterEllipsis" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Aste vinte da confermare: " FontSize="14" Foreground="#FF9933" FontWeight="Bold" />
|
||||
<TextBlock x:Name="BannerAsteDaRiscattare" Text="--" FontSize="14" Foreground="#FF9933" FontWeight="Bold" Width="40" TextTrimming="CharacterEllipsis" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Pulsanti Centrali -->
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
|
||||
<!-- Destra: Pulsanti globali -->
|
||||
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||
<Button x:Name="ConfigSessionButton" Content="Configura" Click="ConfigSessionButton_Click"
|
||||
Style="{StaticResource SmallButtonStyle}"
|
||||
Background="#8B5CF6" Padding="20,10" Margin="0,0,6,0" Height="40" MinWidth="110" />
|
||||
<Button x:Name="StartButton" Content="Avvia Tutti" Click="StartButton_Click" IsEnabled="False"
|
||||
<Button x:Name="StartButton" Content="Avvia Tutti" Command="{Binding StartAllCommand}" IsEnabled="False"
|
||||
Style="{StaticResource SmallButtonStyle}"
|
||||
Background="#00CC66" Padding="20,10" Margin="0,0,6,0" Height="40" MinWidth="110" />
|
||||
<Button x:Name="PauseAllButton" Content="Pausa Tutti" Click="PauseAllButton_Click" IsEnabled="True"
|
||||
<Button x:Name="PauseAllButton" Content="Pausa Tutti" Command="{Binding PauseAllCommand}" IsEnabled="True"
|
||||
Style="{StaticResource SmallButtonStyle}"
|
||||
Background="#FF9933" Padding="20,10" Margin="0,0,6,0" Height="40" MinWidth="110" />
|
||||
<Button x:Name="StopButton" Content="Ferma Tutti" Click="StopButton_Click" IsEnabled="False"
|
||||
<Button x:Name="StopButton" Content="Ferma Tutti" Command="{Binding StopAllCommand}" IsEnabled="False"
|
||||
Style="{StaticResource SmallButtonStyle}"
|
||||
Background="#CC0000" Padding="20,10" Opacity="0.5" Height="40" MinWidth="110" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Contatore Aste -->
|
||||
<Border Grid.Column="2" Background="#0f0f0f" CornerRadius="6" Padding="16,8" VerticalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Text="Aste:" FontSize="12" Foreground="#999" Margin="0,0,8,0" VerticalAlignment="Center" />
|
||||
<TextBlock x:Name="TotalAuctionsText" Text="0" FontWeight="Bold" FontSize="18" Foreground="#00CC66" VerticalAlignment="Center" />
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
@@ -120,7 +219,7 @@
|
||||
|
||||
<!-- Header con titolo e pulsanti -->
|
||||
<Grid Grid.Row="0" Margin="0,0,0,12">
|
||||
<TextBlock Text="Aste Monitorate" FontSize="14" FontWeight="Bold" Foreground="#00CC66" VerticalAlignment="Center" />
|
||||
<TextBlock x:Name="MonitorateTitle" Text="Aste monitorate: 0" FontSize="14" FontWeight="Bold" Foreground="#00CC66" VerticalAlignment="Center" />
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button x:Name="AddUrlButton" Content="+ Aggiungi" Click="AddUrlButton_Click"
|
||||
Style="{StaticResource SmallButtonStyle}"
|
||||
@@ -182,6 +281,7 @@
|
||||
</DataGrid.Resources>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn Header="Asta" Binding="{Binding Name, Mode=OneWay}" Width="2*" IsReadOnly="True" />
|
||||
<DataGridTextColumn Header="Latenza (ms)" Binding="{Binding AuctionInfo.PollingLatencyMs, Mode=OneWay}" Width="80" IsReadOnly="True" />
|
||||
<DataGridTextColumn Header="Stato" Binding="{Binding StatusDisplay, Mode=OneWay}" Width="90" IsReadOnly="True" />
|
||||
<DataGridTextColumn Header="Timer" Binding="{Binding TimerDisplay, Mode=OneWay}" Width="70" IsReadOnly="True" />
|
||||
<DataGridTextColumn Header="Prezzo" Binding="{Binding PriceDisplay, Mode=OneWay}" Width="85" IsReadOnly="True" />
|
||||
@@ -192,38 +292,10 @@
|
||||
<DataGridTemplateColumn.CellTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<Button Content="Avvia" CommandParameter="{Binding}" Click="GridStartAuction_Click" Style="{StaticResource SmallButtonStyle}" Background="#00CC66" Padding="6,2" MinWidth="40" Margin="0,0,4,0">
|
||||
<Button.Opacity>
|
||||
<MultiBinding Converter="{StaticResource StartButtonOpacityConverter}">
|
||||
<Binding Path="IsActive" />
|
||||
<Binding Path="IsPaused" />
|
||||
</MultiBinding>
|
||||
</Button.Opacity>
|
||||
<Button.IsEnabled>
|
||||
<MultiBinding Converter="{StaticResource StartResumeConverter}">
|
||||
<Binding Path="IsActive" />
|
||||
<Binding Path="IsPaused" />
|
||||
</MultiBinding>
|
||||
</Button.IsEnabled>
|
||||
</Button>
|
||||
<Button Content="Pausa" CommandParameter="{Binding}" Click="GridPauseAuction_Click" Style="{StaticResource SmallButtonStyle}" Background="#FF9933" Padding="6,2" MinWidth="40" Margin="0,0,4,0">
|
||||
<Button.IsEnabled>
|
||||
<MultiBinding Converter="{StaticResource AndNotPausedConverter}">
|
||||
<Binding Path="IsActive" />
|
||||
<Binding Path="IsPaused" />
|
||||
</MultiBinding>
|
||||
</Button.IsEnabled>
|
||||
<Button.Opacity>
|
||||
<MultiBinding Converter="{StaticResource PauseButtonOpacityConverter}">
|
||||
<Binding Path="IsActive" />
|
||||
<Binding Path="IsPaused" />
|
||||
</MultiBinding>
|
||||
</Button.Opacity>
|
||||
</Button>
|
||||
<Button Content="Ferma" CommandParameter="{Binding}" Click="GridStopAuction_Click" Style="{StaticResource SmallButtonStyle}" Background="#CC0000" Padding="6,2" MinWidth="40" Margin="0,0,4,0"
|
||||
IsEnabled="{Binding IsActive}"
|
||||
Opacity="{Binding IsActive, Converter={StaticResource BoolToOpacityConverter}}" />
|
||||
<Button Content="Punta" CommandParameter="{Binding}" Click="GridManualBid_Click" Style="{StaticResource SmallButtonStyle}" Background="#8B5CF6" Padding="6,2" MinWidth="40" />
|
||||
<Button Content="Avvia" Command="{Binding DataContext.GridStartCommand, RelativeSource={RelativeSource AncestorType=Window}}" CommandParameter="{Binding}" Style="{StaticResource GridStartButtonStyle}" Background="#00CC66" Padding="6,2" MinWidth="40" Margin="0,0,4,0" />
|
||||
<Button Content="Pausa" Command="{Binding DataContext.GridPauseCommand, RelativeSource={RelativeSource AncestorType=Window}}" CommandParameter="{Binding}" Style="{StaticResource GridPauseButtonStyle}" Background="#FF9933" Padding="6,2" MinWidth="40" Margin="0,0,4,0" />
|
||||
<Button Content="Ferma" Command="{Binding DataContext.GridStopCommand, RelativeSource={RelativeSource AncestorType=Window}}" CommandParameter="{Binding}" Style="{StaticResource GridStopButtonStyle}" Background="#CC0000" Padding="6,2" MinWidth="40" Margin="0,0,4,0" />
|
||||
<Button Content="Punta" Command="{Binding DataContext.GridBidCommand, RelativeSource={RelativeSource AncestorType=Window}}" CommandParameter="{Binding}" Style="{StaticResource GridManualBidButtonStyle}" Background="#8B5CF6" Padding="6,2" MinWidth="40" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</DataGridTemplateColumn.CellTemplate>
|
||||
@@ -295,7 +367,7 @@
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBox x:Name="SelectedAuctionUrl" Text="" IsReadOnly="True" MinWidth="220" Margin="0,0,8,0" VerticalAlignment="Center" Background="#181818" Foreground="#00CCFF" BorderBrush="#333" BorderThickness="1" FontSize="11" Grid.Column="0" HorizontalAlignment="Stretch" />
|
||||
<Button Content="Apri" x:Name="OpenAuctionButton" Click="OpenAuctionButton_Click" Style="{StaticResource SmallButtonStyle}" Background="#0099FF" Padding="10,6" Margin="0,0,4,0" Height="28" MinWidth="60" FontSize="11" Grid.Column="1" />
|
||||
<Button Content="Apri" x:Name="OpenAuctionButton" Click="GridOpenAuction_Click" Style="{StaticResource SmallButtonStyle}" Background="#0099FF" Padding="10,6" Margin="0,0,4,0" Height="28" MinWidth="60" FontSize="11" Grid.Column="1" />
|
||||
<Button Content="Copia" x:Name="CopyAuctionUrlButton" Click="CopyAuctionUrlButton_Click" Style="{StaticResource SmallButtonStyle}" Background="#666" Padding="10,6" Height="28" MinWidth="60" FontSize="11" Grid.Column="2" />
|
||||
</Grid>
|
||||
|
||||
@@ -341,13 +413,9 @@
|
||||
<UniformGrid Columns="2" Margin="0,0,0,12">
|
||||
<StackPanel Margin="0,0,4,0">
|
||||
<TextBlock Text="Max Clicks (0=inf)" FontSize="10" Margin="0,0,0,4" Foreground="#999" />
|
||||
<TextBox x:Name="SelectedMaxClicks" Text="0" TextChanged="SelectedMaxClicks_TextChanged"
|
||||
Background="#1a1a1a" Foreground="#FFF" Padding="8" FontSize="12" BorderBrush="#444" BorderThickness="1" />
|
||||
</StackPanel>
|
||||
<StackPanel Margin="4,0,0,0">
|
||||
<TextBlock Text="(Opzionale)" FontSize="10" Margin="0,0,0,4" Foreground="#999" />
|
||||
<TextBlock Text="Limita il numero di puntate del bot per questa asta" FontSize="10" Foreground="#AAA" TextWrapping="Wrap" />
|
||||
<TextBox x:Name="SelectedMaxClicks" Text="{Binding MaxClicks, Mode=TwoWay}" Background="#1a1a1a" Foreground="#FFF" Padding="8" FontSize="12" BorderBrush="#444" BorderThickness="1" />
|
||||
</StackPanel>
|
||||
<StackPanel />
|
||||
</UniformGrid>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Net;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using AutoBidder.Models;
|
||||
using AutoBidder.Services;
|
||||
using AutoBidder.ViewModels;
|
||||
using AutoBidder.Utilities;
|
||||
using Microsoft.Win32;
|
||||
using AutoBidder.Dialogs;
|
||||
|
||||
namespace AutoBidder
|
||||
{
|
||||
@@ -24,57 +20,142 @@ namespace AutoBidder
|
||||
// SERVIZI CORE
|
||||
private readonly AuctionMonitor _auctionMonitor;
|
||||
private readonly ObservableCollection<AuctionViewModel> _auctionViewModels = new();
|
||||
|
||||
// UI State
|
||||
private AuctionViewModel? _selectedAuction;
|
||||
private bool _isAutomationActive = false;
|
||||
private BrowserWindow? _browserWindow;
|
||||
|
||||
|
||||
// Commands
|
||||
public RelayCommand? StartAllCommand { get; private set; }
|
||||
public RelayCommand? StopAllCommand { get; private set; }
|
||||
public RelayCommand? PauseAllCommand { get; private set; }
|
||||
|
||||
// Grid item commands (parameter = AuctionViewModel)
|
||||
public RelayCommand? GridStartCommand { get; private set; }
|
||||
public RelayCommand? GridPauseCommand { get; private set; }
|
||||
public RelayCommand? GridStopCommand { get; private set; }
|
||||
public RelayCommand? GridBidCommand { get; private set; }
|
||||
|
||||
private System.Windows.Threading.DispatcherTimer _userBannerTimer;
|
||||
private System.Windows.Threading.DispatcherTimer _userHtmlTimer;
|
||||
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
|
||||
// Inizializza servizi
|
||||
_auctionMonitor = new AuctionMonitor();
|
||||
|
||||
|
||||
// Commands
|
||||
StartAllCommand = new RelayCommand(_ => ExecuteStartAll());
|
||||
StopAllCommand = new RelayCommand(_ => ExecuteStopAll());
|
||||
PauseAllCommand = new RelayCommand(_ => ExecutePauseAll());
|
||||
|
||||
GridStartCommand = new RelayCommand(param => ExecuteGridStart(param as ViewModels.AuctionViewModel));
|
||||
GridPauseCommand = new RelayCommand(param => ExecuteGridPause(param as ViewModels.AuctionViewModel));
|
||||
GridStopCommand = new RelayCommand(param => ExecuteGridStop(param as ViewModels.AuctionViewModel));
|
||||
GridBidCommand = new RelayCommand(async param => await ExecuteGridBidAsync(param as ViewModels.AuctionViewModel));
|
||||
|
||||
this.DataContext = this;
|
||||
|
||||
// Event handlers
|
||||
_auctionMonitor.OnAuctionUpdated += AuctionMonitor_OnAuctionUpdated;
|
||||
_auctionMonitor.OnBidExecuted += AuctionMonitor_OnBidExecuted;
|
||||
_auctionMonitor.OnLog += AuctionMonitor_OnLog;
|
||||
_auctionMonitor.OnResetCountChanged += AuctionMonitor_OnResetCountChanged;
|
||||
|
||||
|
||||
// Bind griglia
|
||||
MultiAuctionsGrid.ItemsSource = _auctionViewModels;
|
||||
|
||||
|
||||
// Carica aste salvate
|
||||
LoadSavedAuctions();
|
||||
|
||||
|
||||
// Ensure initial global button states (pause/stop disabled until starting)
|
||||
UpdateGlobalControlButtons();
|
||||
|
||||
|
||||
Log("[OK] AutoBidder v4.0 avviato (API-based)");
|
||||
Log("[INFO] Usa 'Configura Sessione' per inserire PHPSESSID");
|
||||
Log("[INFO] Usa 'Configura Sessione' per inserire cookie dal menu Applicazioni di Chrome");
|
||||
|
||||
// Timer per aggiornamento banner utente ogni minuto
|
||||
_userBannerTimer = new System.Windows.Threading.DispatcherTimer();
|
||||
_userBannerTimer.Interval = TimeSpan.FromMinutes(1);
|
||||
_userBannerTimer.Tick += UserBannerTimer_Tick;
|
||||
_userBannerTimer.Start();
|
||||
_ = UpdateUserBannerInfoAsync();
|
||||
|
||||
// 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();
|
||||
_ = UpdateUserHtmlInfoAsync();
|
||||
}
|
||||
|
||||
|
||||
// Command implementations
|
||||
private void ExecuteStartAll()
|
||||
{
|
||||
StartButton_Click(null, null);
|
||||
}
|
||||
private void ExecuteStopAll()
|
||||
{
|
||||
StopButton_Click(null, null);
|
||||
}
|
||||
private void ExecutePauseAll()
|
||||
{
|
||||
PauseAllButton_Click(null, null);
|
||||
}
|
||||
|
||||
private void ExecuteGridStart(ViewModels.AuctionViewModel? vm)
|
||||
{
|
||||
if (vm == null) return;
|
||||
vm.IsActive = true; vm.IsPaused = false; Log($"[START] Asta avviata: {vm.Name}"); UpdateGlobalControlButtons();
|
||||
}
|
||||
private void ExecuteGridPause(ViewModels.AuctionViewModel? vm)
|
||||
{
|
||||
if (vm == null) return;
|
||||
vm.IsPaused = true; Log($"[PAUSA] Asta in pausa: {vm.Name}"); UpdateGlobalControlButtons();
|
||||
}
|
||||
private void ExecuteGridStop(ViewModels.AuctionViewModel? vm)
|
||||
{
|
||||
if (vm == null) return;
|
||||
vm.IsActive = false; Log($"[STOP] Asta fermata: {vm.Name}"); UpdateGlobalControlButtons();
|
||||
}
|
||||
private async Task ExecuteGridBidAsync(ViewModels.AuctionViewModel? vm)
|
||||
{
|
||||
if (vm == null) return;
|
||||
try
|
||||
{
|
||||
Log($"[BID] Puntata manuale richiesta su: {vm.Name}");
|
||||
var result = await _auctionMonitor.PlaceManualBidAsync(vm.AuctionInfo);
|
||||
if (result.Success)
|
||||
Log($"[OK] Puntata manuale su {vm.Name}: {result.LatencyMs}ms");
|
||||
else
|
||||
Log($"[FAIL] Puntata manuale su {vm.Name}: {result.Error}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"[ERRORE] Puntata manuale: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void AuctionMonitor_OnAuctionUpdated(AuctionState state)
|
||||
{
|
||||
Dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
var vm = _auctionViewModels.FirstOrDefault(a => a.AuctionId == state.AuctionId);
|
||||
vm?.UpdateState(state);
|
||||
|
||||
|
||||
// Se è l'asta selezionata, aggiorna tutto
|
||||
if (_selectedAuction != null && _selectedAuction.AuctionId == state.AuctionId)
|
||||
{
|
||||
UpdateAuctionLog(_selectedAuction);
|
||||
RefreshBiddersGrid(_selectedAuction);
|
||||
}
|
||||
|
||||
|
||||
// Aggiorna lo stato dei bottoni globali quando cambia stato di un'asta
|
||||
UpdateGlobalControlButtons();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void AuctionMonitor_OnBidExecuted(AuctionInfo auction, BidResult result)
|
||||
{
|
||||
Dispatcher.BeginInvoke(() =>
|
||||
@@ -83,7 +164,7 @@ namespace AutoBidder
|
||||
if (vm != null)
|
||||
{
|
||||
vm.RefreshCounters();
|
||||
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
Log($"[OK] Click su {auction.Name}: {result.LatencyMs}ms {result.Response}");
|
||||
@@ -95,12 +176,12 @@ namespace AutoBidder
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private void AuctionMonitor_OnLog(string message)
|
||||
{
|
||||
Log(message);
|
||||
}
|
||||
|
||||
|
||||
private void AuctionMonitor_OnResetCountChanged(string auctionId)
|
||||
{
|
||||
Dispatcher.BeginInvoke(() =>
|
||||
@@ -109,16 +190,15 @@ namespace AutoBidder
|
||||
vm?.RefreshCounters();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ===== UI BUTTON HANDLERS =====
|
||||
|
||||
|
||||
private void ConfigSessionButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Carica sessione esistente (se presente)
|
||||
var currentSession = _auctionMonitor.GetSession();
|
||||
string existingToken = "";
|
||||
string existingUsername = "";
|
||||
|
||||
string existingToken = string.Empty;
|
||||
|
||||
if (currentSession != null)
|
||||
{
|
||||
// Estrai token da CookieString (formato: "__stattrb=TOKENVALUE")
|
||||
@@ -130,11 +210,9 @@ namespace AutoBidder
|
||||
{
|
||||
existingToken = currentSession.AuthToken;
|
||||
}
|
||||
|
||||
existingUsername = currentSession.Username ?? "";
|
||||
}
|
||||
|
||||
var dialog = new SessionDialog(existingToken, existingUsername);
|
||||
|
||||
var dialog = new SessionDialog(existingToken);
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
try
|
||||
@@ -142,37 +220,48 @@ namespace AutoBidder
|
||||
// Usa InitializeSessionWithCookie invece di InitializeSession
|
||||
// perché il token è __stattrb (non __stattr)
|
||||
var cookieString = $"__stattrb={dialog.AuthToken}";
|
||||
_auctionMonitor.InitializeSessionWithCookie(cookieString, dialog.Username);
|
||||
|
||||
UsernameText.Text = dialog.Username ?? string.Empty;
|
||||
_auctionMonitor.InitializeSessionWithCookie(cookieString, ""); // Username non più passato
|
||||
|
||||
StartButton.IsEnabled = true;
|
||||
|
||||
|
||||
// Salva sessione in modo sicuro (crittografata)
|
||||
var session = _auctionMonitor.GetSession();
|
||||
SessionManager.SaveSession(session);
|
||||
|
||||
Log($"Sessione configurata per: {dialog.Username}");
|
||||
|
||||
Log($"Sessione configurata");
|
||||
Log($"Cookie salvato in modo sicuro");
|
||||
|
||||
// Aggiorna info utente
|
||||
Task.Run(() =>
|
||||
|
||||
// Aggiorna info utente (nome e puntate) tramite chiamata automatica
|
||||
Task.Run(async () =>
|
||||
{
|
||||
_auctionMonitor.UpdateUserInfoAsync().GetAwaiter().GetResult();
|
||||
var updatedSession = _auctionMonitor.GetSession();
|
||||
Dispatcher.Invoke(() =>
|
||||
var userData = await _auctionMonitor.GetUserDataAsync();
|
||||
if (userData != null)
|
||||
{
|
||||
RemainingBidsText.Text = updatedSession.RemainingBids.ToString();
|
||||
});
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
SetUserBanner(userData.Username, userData.RemainingBids);
|
||||
Log($"[OK] Utente: {userData.Username}, Puntate residue: {userData.RemainingBids}");
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
UsernameText.Text = "";
|
||||
RemainingBidsText.Text = "0";
|
||||
Log($"[WARN] Impossibile ricavare dati utente");
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"Errore configurazione sessione: {ex.Message}");
|
||||
Log($"[ERRORE] Configurazione sessione: {ex.Message}");
|
||||
MessageBox.Show($"Errore: {ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void StartButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
try
|
||||
@@ -214,7 +303,7 @@ namespace AutoBidder
|
||||
MessageBox.Show($"Errore: {ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void StopButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// Stop monitoring and stop all auctions
|
||||
@@ -241,31 +330,13 @@ namespace AutoBidder
|
||||
Log($"[STOP ERROR] {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void PauseBidButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
// TODO: Implementa pausa globale
|
||||
MessageBox.Show("Usa i pulsanti Pausa/Riprendi per asta", "Info", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
}
|
||||
|
||||
private void OpenBrowserButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_browserWindow == null || !_browserWindow.IsVisible)
|
||||
{
|
||||
_browserWindow = new BrowserWindow();
|
||||
_browserWindow.OnAddAuction += async (url) =>
|
||||
{
|
||||
await AddAuctionFromUrl(url);
|
||||
};
|
||||
_browserWindow.Show();
|
||||
Log("[BROWSER] Browser aperto");
|
||||
}
|
||||
else
|
||||
{
|
||||
_browserWindow.Activate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async void AddUrlButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var dialog = new AddAuctionSimpleDialog();
|
||||
@@ -274,7 +345,7 @@ namespace AutoBidder
|
||||
await AddAuctionById(dialog.AuctionId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void RemoveUrlButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_selectedAuction == null)
|
||||
@@ -282,24 +353,24 @@ namespace AutoBidder
|
||||
MessageBox.Show("Seleziona un'asta dalla griglia", "Nessuna Selezione", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var result = MessageBox.Show(
|
||||
$"Rimuovere '{_selectedAuction.Name}'",
|
||||
"Conferma Rimozione",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question);
|
||||
|
||||
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
_auctionMonitor.RemoveAuction(_selectedAuction.AuctionId);
|
||||
_auctionViewModels.Remove(_selectedAuction);
|
||||
_selectedAuction = null;
|
||||
|
||||
|
||||
SaveAuctions();
|
||||
UpdateTotalCount();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void MultiAuctionsGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (MultiAuctionsGrid.SelectedItem is AuctionViewModel selected)
|
||||
@@ -308,26 +379,26 @@ namespace AutoBidder
|
||||
UpdateSelectedAuctionDetails(selected);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void PauseAuctionButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_selectedAuction == null) return;
|
||||
|
||||
|
||||
|
||||
|
||||
_selectedAuction.IsPaused = true;
|
||||
UpdateAuctionButtonStates();
|
||||
Log($"[PAUSA] Asta in pausa: {_selectedAuction.Name}");
|
||||
}
|
||||
|
||||
|
||||
private void ResumeAuctionButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_selectedAuction == null) return;
|
||||
|
||||
|
||||
_selectedAuction.IsPaused = false;
|
||||
UpdateAuctionButtonStates();
|
||||
Log($"[RIPRENDI] Asta riattivata: {_selectedAuction.Name}");
|
||||
}
|
||||
|
||||
|
||||
private void SelectedTimerClick_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (_selectedAuction != null && sender is TextBox tb)
|
||||
@@ -338,7 +409,7 @@ namespace AutoBidder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void SelectedDelayMs_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (_selectedAuction != null && sender is TextBox tb)
|
||||
@@ -349,7 +420,7 @@ namespace AutoBidder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void SelectedMinPrice_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (_selectedAuction != null && sender is TextBox tb)
|
||||
@@ -361,7 +432,7 @@ namespace AutoBidder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void SelectedMaxPrice_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (_selectedAuction != null && sender is TextBox tb)
|
||||
@@ -373,7 +444,7 @@ namespace AutoBidder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void SelectedMinResets_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (_selectedAuction != null && sender is TextBox tb)
|
||||
@@ -384,7 +455,7 @@ namespace AutoBidder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void SelectedMaxResets_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (_selectedAuction != null && sender is TextBox tb)
|
||||
@@ -395,28 +466,29 @@ namespace AutoBidder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void SelectedMaxClicks_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
if (_selectedAuction != null && sender is TextBox tb)
|
||||
{
|
||||
if (int.TryParse(tb.Text, out var value) && value >= 0)
|
||||
{
|
||||
_selectedAuction.AuctionInfo.MaxClicks = value;
|
||||
_selectedAuction.MaxClicks = value;
|
||||
SaveAuctions(); // Persist change immediately
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ResetSettingsButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_selectedAuction == null) return;
|
||||
|
||||
|
||||
var result = MessageBox.Show(
|
||||
"Ripristinare le impostazioni ai valori predefiniti?",
|
||||
"Conferma Reset",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question);
|
||||
|
||||
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
_selectedAuction.TimerClick = 0;
|
||||
@@ -425,22 +497,22 @@ namespace AutoBidder
|
||||
_selectedAuction.MaxPrice = 0;
|
||||
_selectedAuction.AuctionInfo.MinResets = 0;
|
||||
_selectedAuction.AuctionInfo.MaxResets = 0;
|
||||
|
||||
|
||||
UpdateSelectedAuctionDetails(_selectedAuction);
|
||||
Log($"Reset impostazioni: {_selectedAuction.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ClearBiddersButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_selectedAuction == null) return;
|
||||
|
||||
|
||||
var result = MessageBox.Show(
|
||||
"Cancellare la lista degli utenti?",
|
||||
"Conferma Pulizia",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question);
|
||||
|
||||
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
_selectedAuction.AuctionInfo.BidderStats.Clear();
|
||||
@@ -449,17 +521,17 @@ namespace AutoBidder
|
||||
Log($"[CLEAR] Lista utenti pulita: {_selectedAuction.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ClearLogButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_selectedAuction == null) return;
|
||||
|
||||
|
||||
var result = MessageBox.Show(
|
||||
"Cancellare il log dell'asta?",
|
||||
"Conferma Pulizia",
|
||||
MessageBoxButton.YesNo,
|
||||
MessageBoxImage.Question);
|
||||
|
||||
|
||||
if (result == MessageBoxResult.Yes)
|
||||
{
|
||||
_selectedAuction.AuctionInfo.AuctionLog.Clear();
|
||||
@@ -467,7 +539,7 @@ namespace AutoBidder
|
||||
Log($"Log pulito: {_selectedAuction.Name}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void CopyAuctionUrlButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_selectedAuction == null) return;
|
||||
@@ -484,7 +556,7 @@ namespace AutoBidder
|
||||
}
|
||||
}
|
||||
// ===== HELPER METHODS =====
|
||||
|
||||
|
||||
private Task AddAuctionById(string input)
|
||||
{
|
||||
try
|
||||
@@ -494,11 +566,11 @@ namespace AutoBidder
|
||||
MessageBox.Show("Input non valido!", "Errore", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
string auctionId;
|
||||
string? productName;
|
||||
string originalUrl;
|
||||
|
||||
|
||||
// Verifica se è un URL o solo un ID
|
||||
if (input.Contains("bidoo.com") || input.Contains("http"))
|
||||
{
|
||||
@@ -510,27 +582,27 @@ namespace AutoBidder
|
||||
MessageBox.Show("Impossibile estrarre ID dall'URL!", "Errore", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
productName = ExtractProductName(originalUrl);
|
||||
|
||||
productName = ExtractProductName(originalUrl) ?? string.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
// È solo un ID numerico - costruisci URL generico
|
||||
auctionId = input.Trim();
|
||||
productName = null;
|
||||
productName = string.Empty;
|
||||
originalUrl = $"https://it.bidoo.com/auction.php?a=asta_{auctionId}";
|
||||
}
|
||||
|
||||
|
||||
// Verifica duplicati
|
||||
if (_auctionViewModels.Any(a => a.AuctionId == auctionId))
|
||||
{
|
||||
MessageBox.Show("Asta già monitorata!", "Duplicato", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
// Crea nome visualizzazione
|
||||
var displayName = string.IsNullOrEmpty(productName)
|
||||
? $"Asta {auctionId}"
|
||||
var displayName = string.IsNullOrEmpty(productName)
|
||||
? $"Asta {auctionId}"
|
||||
: $"{productName} ({auctionId})";
|
||||
|
||||
// Crea model
|
||||
@@ -593,7 +665,8 @@ namespace AutoBidder
|
||||
var name = $"Asta {auctionId}";
|
||||
try
|
||||
{
|
||||
var html = await HttpClientProvider.GetStringAsync(url);
|
||||
using var httpClient = new System.Net.Http.HttpClient();
|
||||
var html = await httpClient.GetStringAsync(url);
|
||||
var match = System.Text.RegularExpressions.Regex.Match(html, @"<title>([^<]+)</title>");
|
||||
if (match.Success)
|
||||
{
|
||||
@@ -753,18 +826,16 @@ namespace AutoBidder
|
||||
SelectedMaxPrice.Text = auction.MaxPrice.ToString();
|
||||
SelectedMinResets.Text = auction.AuctionInfo.MinResets.ToString();
|
||||
SelectedMaxResets.Text = auction.AuctionInfo.MaxResets.ToString();
|
||||
|
||||
SelectedMaxClicks.Text = auction.MaxClicks.ToString();
|
||||
// Mostra il link dell'asta selezionata
|
||||
var url = auction.AuctionInfo.OriginalUrl;
|
||||
if (string.IsNullOrEmpty(url))
|
||||
url = $"https://it.bidoo.com/auction.php?a=asta_{auction.AuctionId}";
|
||||
SelectedAuctionUrl.Text = url;
|
||||
|
||||
// Abilita solo i pulsanti rimasti
|
||||
ResetSettingsButton.IsEnabled = true;
|
||||
ClearBiddersButton.IsEnabled = true;
|
||||
ClearLogButton.IsEnabled = true;
|
||||
|
||||
// Aggiorna log asta
|
||||
UpdateAuctionLog(auction);
|
||||
// Aggiorna bidders con metodo dedicato
|
||||
@@ -773,43 +844,14 @@ namespace AutoBidder
|
||||
catch { }
|
||||
}
|
||||
|
||||
private void OpenAuctionButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (_selectedAuction == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
// Usa l'URL originale salvato nell'asta
|
||||
var url = _selectedAuction.AuctionInfo.OriginalUrl;
|
||||
|
||||
if (string.IsNullOrEmpty(url))
|
||||
{
|
||||
// Fallback: costruisci URL generico
|
||||
url = $"https://it.bidoo.com/auction.php?a=asta_{_selectedAuction.AuctionId}";
|
||||
}
|
||||
|
||||
System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo
|
||||
{
|
||||
FileName = url,
|
||||
UseShellExecute = true
|
||||
});
|
||||
|
||||
Log($"[LINK] Apertura asta: {_selectedAuction.Name}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"[ERRORE] Errore apertura asta: {ex.Message}");
|
||||
MessageBox.Show($"Errore: {ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateAuctionLog(AuctionViewModel auction)
|
||||
{
|
||||
try
|
||||
{
|
||||
var auctionInfo = auction.AuctionInfo;
|
||||
|
||||
SelectedAuctionLog.Document.Blocks.Clear();
|
||||
var logBox = SelectedAuctionLog;
|
||||
var doc = logBox.Document;
|
||||
doc.Blocks.Clear();
|
||||
foreach (var entry in auctionInfo.AuctionLog)
|
||||
{
|
||||
var upper = entry.ToUpperInvariant();
|
||||
@@ -818,14 +860,21 @@ namespace AutoBidder
|
||||
color = System.Windows.Media.Brushes.IndianRed;
|
||||
else if (upper.Contains("[WARN]") || upper.Contains("WARN"))
|
||||
color = System.Windows.Media.Brushes.Orange;
|
||||
|
||||
var p = new System.Windows.Documents.Paragraph { Margin = new Thickness(0) };
|
||||
var r = new System.Windows.Documents.Run(entry) { Foreground = color };
|
||||
p.Inlines.Add(r);
|
||||
SelectedAuctionLog.Document.Blocks.Add(p);
|
||||
doc.Blocks.Add(p);
|
||||
}
|
||||
|
||||
SelectedAuctionLog.ScrollToEnd();
|
||||
// Scroll only if user is already near the bottom
|
||||
var viewer = logBox;
|
||||
var scroll = viewer.VerticalScrollBarVisibility;
|
||||
var vpos = viewer.VerticalOffset;
|
||||
var vmax = viewer.ExtentHeight - viewer.ViewportHeight;
|
||||
if (vmax - vpos < 40) // threshold: 40px from bottom
|
||||
{
|
||||
viewer.ScrollToEnd();
|
||||
}
|
||||
// else: preserve scroll position
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
@@ -840,7 +889,7 @@ namespace AutoBidder
|
||||
|
||||
SelectedAuctionBiddersGrid.ItemsSource = null; // Force refresh
|
||||
SelectedAuctionBiddersGrid.ItemsSource = bidders;
|
||||
SelectedAuctionBiddersCount.Text = bidders.Count.ToString();
|
||||
SelectedAuctionBiddersCount.Text = bidders?.Count.ToString() ?? "0";
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
@@ -853,7 +902,7 @@ namespace AutoBidder
|
||||
|
||||
private void UpdateTotalCount()
|
||||
{
|
||||
TotalAuctionsText.Text = _auctionViewModels.Count.ToString();
|
||||
MonitorateTitle.Text = $"Aste monitorate: {_auctionViewModels.Count}";
|
||||
}
|
||||
|
||||
private void LoadSavedAuctions()
|
||||
@@ -861,28 +910,25 @@ namespace AutoBidder
|
||||
try
|
||||
{
|
||||
var auctions = PersistenceManager.LoadAuctions();
|
||||
|
||||
foreach (var auction in auctions)
|
||||
{
|
||||
// Protezione: rimuovi eventuali BidHistory null
|
||||
auction.BidHistory = auction.BidHistory?.Where(b => b != null).ToList() ?? new List<BidHistory>();
|
||||
_auctionMonitor.AddAuction(auction);
|
||||
var vm = new AuctionViewModel(auction);
|
||||
_auctionViewModels.Add(vm);
|
||||
}
|
||||
|
||||
// On startup treat persisted auctions as stopped (user expectation)
|
||||
foreach (var vm in _auctionViewModels)
|
||||
{
|
||||
vm.IsActive = false;
|
||||
vm.IsPaused = false;
|
||||
}
|
||||
|
||||
UpdateTotalCount();
|
||||
|
||||
if (auctions.Count > 0)
|
||||
{
|
||||
Log($"[OK] Caricate {auctions.Count} aste salvate");
|
||||
}
|
||||
|
||||
// Carica sessione salvata (se esiste)
|
||||
LoadSavedSession();
|
||||
}
|
||||
@@ -994,7 +1040,6 @@ namespace AutoBidder
|
||||
else
|
||||
level = LogLevel.Info;
|
||||
}
|
||||
|
||||
Dispatcher.BeginInvoke(() =>
|
||||
{
|
||||
var para = new System.Windows.Documents.Paragraph();
|
||||
@@ -1015,6 +1060,11 @@ namespace AutoBidder
|
||||
}
|
||||
para.Inlines.Add(run);
|
||||
LogBox.Document.Blocks.Add(para);
|
||||
// Mantieni solo ultimi 500 log
|
||||
while (LogBox.Document.Blocks.Count > 500)
|
||||
{
|
||||
LogBox.Document.Blocks.Remove(LogBox.Document.Blocks.FirstBlock);
|
||||
}
|
||||
LogBox.ScrollToEnd();
|
||||
});
|
||||
}
|
||||
@@ -1057,7 +1107,7 @@ namespace AutoBidder
|
||||
private void GridStartAuction_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is Button btn && btn.CommandParameter is AuctionViewModel vm)
|
||||
{
|
||||
{
|
||||
vm.IsActive = true;
|
||||
vm.IsPaused = false;
|
||||
Log($"[START] Asta avviata: {vm.Name}");
|
||||
@@ -1104,7 +1154,7 @@ namespace AutoBidder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void PauseAllButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
int paused = 0;
|
||||
@@ -1121,7 +1171,7 @@ namespace AutoBidder
|
||||
// When paused, Start and Stop should be enabled, Pause disabled
|
||||
UpdateGlobalControlButtons();
|
||||
}
|
||||
|
||||
|
||||
private void ClearGlobalLogButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
var result = MessageBox.Show("Pulire il log globale?", "Conferma", MessageBoxButton.YesNo, MessageBoxImage.Question);
|
||||
@@ -1169,67 +1219,59 @@ namespace AutoBidder
|
||||
StopButton.IsEnabled = true; StopButton.Opacity = 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== DIALOG HELPER =====
|
||||
|
||||
public class AddAuctionDialog : Window
|
||||
{
|
||||
private readonly TextBox _urlTextBox;
|
||||
public string AuctionUrl => _urlTextBox.Text.Trim();
|
||||
|
||||
public AddAuctionDialog()
|
||||
|
||||
private async void UserBannerTimer_Tick(object? sender, EventArgs e)
|
||||
{
|
||||
Title = "Aggiungi Asta";
|
||||
Width = 500;
|
||||
Height = 150;
|
||||
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
||||
ResizeMode = ResizeMode.NoResize;
|
||||
|
||||
var grid = new Grid { Margin = new Thickness(20) };
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(10) });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(15) });
|
||||
grid.RowDefinitions.Add(new RowDefinition { Height = GridLength.Auto });
|
||||
|
||||
var label = new TextBlock { Text = "URL Asta:", FontWeight = FontWeights.SemiBold };
|
||||
Grid.SetRow(label, 0);
|
||||
|
||||
_urlTextBox = new TextBox { Text = "https://it.bidoo.com/asta/", Height = 30, Padding = new Thickness(5) };
|
||||
Grid.SetRow(_urlTextBox, 2);
|
||||
|
||||
var buttonPanel = new StackPanel { Orientation = Orientation.Horizontal, HorizontalAlignment = HorizontalAlignment.Right };
|
||||
Grid.SetRow(buttonPanel, 4);
|
||||
|
||||
var okButton = new Button { Content = "Aggiungi", Width = 100, Height = 32, Margin = new Thickness(0, 0, 10, 0) };
|
||||
var cancelButton = new Button { Content = "Annulla", Width = 100, Height = 32 };
|
||||
|
||||
okButton.Click += (s, e) => { DialogResult = true; Close(); };
|
||||
cancelButton.Click += (s, e) => { DialogResult = false; Close(); };
|
||||
_urlTextBox.KeyDown += (s, e) => { if (e.Key == Key.Enter) { DialogResult = true; Close(); } };
|
||||
|
||||
buttonPanel.Children.Add(okButton);
|
||||
buttonPanel.Children.Add(cancelButton);
|
||||
|
||||
grid.Children.Add(label);
|
||||
grid.Children.Add(_urlTextBox);
|
||||
grid.Children.Add(buttonPanel);
|
||||
|
||||
Content = grid;
|
||||
|
||||
Loaded += (s, e) => _urlTextBox.Focus();
|
||||
await UpdateUserBannerInfoAsync();
|
||||
}
|
||||
}
|
||||
|
||||
// ===== HttpClient Helper =====
|
||||
internal static class HttpClientProvider
|
||||
{
|
||||
private static readonly System.Net.Http.HttpClient _httpClient = new();
|
||||
|
||||
public static async Task<string> GetStringAsync(string url)
|
||||
|
||||
private async Task UpdateUserBannerInfoAsync()
|
||||
{
|
||||
return await _httpClient.GetStringAsync(url);
|
||||
var info = await _auctionMonitor.GetUserBannerInfoAsync();
|
||||
if (info != null)
|
||||
{
|
||||
BannerAsteDaRiscattare.Text = $"{info.nAsteVinte - info.nAsteConfermate}";
|
||||
// BannerPuntateBonus.Text = $"Bonus: {info.nPuntateBonus}"; // RIMOSSO
|
||||
}
|
||||
else
|
||||
{
|
||||
BannerAsteDaRiscattare.Text = "--";
|
||||
// BannerPuntateBonus.Text = "Bonus: --"; // RIMOSSO
|
||||
}
|
||||
}
|
||||
|
||||
private async void UserHtmlTimer_Tick(object? sender, EventArgs e)
|
||||
{
|
||||
await UpdateUserHtmlInfoAsync();
|
||||
}
|
||||
|
||||
private async Task UpdateUserHtmlInfoAsync()
|
||||
{
|
||||
var userData = await _auctionMonitor.GetUserDataFromHtmlAsync();
|
||||
if (userData != null)
|
||||
{
|
||||
Dispatcher.Invoke(() =>
|
||||
{
|
||||
SetUserBanner(userData.Username, userData.RemainingBids);
|
||||
Log($"[HTML] Utente: {userData.Username}, Puntate residue: {userData.RemainingBids}");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void SetUserBanner(string username, int remainingBids)
|
||||
{
|
||||
// Filtra valori non validi
|
||||
if (string.IsNullOrWhiteSpace(username) || username.Contains("<!DOCTYPE") || username.Contains("<html") || username.Contains("<head") || username.Contains("<title"))
|
||||
{
|
||||
UsernameText.Text = "Non configurato";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Rimuovi newline e taglia a max 20 caratteri
|
||||
var clean = username.Replace("\r", "").Replace("\n", "");
|
||||
UsernameText.Text = clean.Length > 20 ? clean.Substring(0, 20) + "..." : clean;
|
||||
}
|
||||
RemainingBidsText.Text = remainingBids.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AutoBidder.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Informazioni base di un'asta monitorata
|
||||
/// Solo HTTP, nessuna modalità, browser o multi-click
|
||||
/// </summary>
|
||||
public class AuctionInfo
|
||||
{
|
||||
@@ -19,15 +22,14 @@ namespace AutoBidder.Models
|
||||
public double MaxPrice { get; set; } = 0;
|
||||
public int MinResets { get; set; } = 0; // Numero minimo reset prima di puntare
|
||||
public int MaxResets { get; set; } = 0; // Numero massimo reset (0 = illimitati)
|
||||
// Numero massimo di click/puntate che il bot può eseguire per questa asta (0 = illimitato)
|
||||
public int MaxClicks { get; set; } = 0;
|
||||
[JsonPropertyName("MaxClicks")]
|
||||
public int MaxClicks { get; set; } = 0; // Numero massimo di puntate consentite (0 = illimitato)
|
||||
|
||||
// Stato asta
|
||||
public bool IsActive { get; set; } = true;
|
||||
public bool IsPaused { get; set; } = false;
|
||||
|
||||
// Contatori
|
||||
public int MyClicks { get; set; } = 0;
|
||||
public int ResetCount { get; set; } = 0;
|
||||
|
||||
// Timestamp
|
||||
@@ -35,12 +37,10 @@ namespace AutoBidder.Models
|
||||
public DateTime? LastClickAt { get; set; }
|
||||
|
||||
// Storico
|
||||
public List<BidHistory> BidHistory { get; set; } = new();
|
||||
public List<BidHistory> BidHistory { get; set; } = new List<BidHistory>();
|
||||
public Dictionary<string, BidderInfo> BidderStats { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Legacy (deprecato, usa BidderStats)
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
public Dictionary<string, int> Bidders { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
||||
// Legacy (deprecato) - removed `Bidders` dictionary; use `BidderStats` instead
|
||||
|
||||
// Log per-asta (non serializzato)
|
||||
[System.Text.Json.Serialization.JsonIgnore]
|
||||
@@ -54,11 +54,13 @@ namespace AutoBidder.Models
|
||||
var entry = $"{DateTime.Now:HH:mm:ss} - {message}";
|
||||
AuctionLog.Add(entry);
|
||||
|
||||
// Mantieni solo ultimi 100 log
|
||||
if (AuctionLog.Count > 100)
|
||||
// Mantieni solo ultimi 500 log
|
||||
if (AuctionLog.Count > 500)
|
||||
{
|
||||
AuctionLog.RemoveAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
public int PollingLatencyMs { get; set; } = 0; // Ultima latenza polling ms
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,10 +61,12 @@ namespace AutoBidder.Models
|
||||
Name = auction.Name,
|
||||
MonitoringStarted = auction.AddedAt,
|
||||
MonitoringDuration = DateTime.UtcNow - auction.AddedAt,
|
||||
MyBids = auction.MyClicks,
|
||||
// MyBids calcolato dalle entry in BidHistory
|
||||
MyBids = auction.BidHistory.Count(h => h.EventType == BidEventType.MyBid),
|
||||
Resets = auction.ResetCount,
|
||||
UniqueBidders = auction.Bidders.Count,
|
||||
BidderRanking = auction.Bidders
|
||||
UniqueBidders = auction.BidderStats?.Count ?? 0,
|
||||
BidderRanking = (auction.BidderStats ?? new Dictionary<string, BidderInfo>())
|
||||
.ToDictionary(k => k.Key, v => v.Value.BidCount)
|
||||
};
|
||||
|
||||
if (auction.BidHistory.Any())
|
||||
@@ -113,11 +115,11 @@ namespace AutoBidder.Models
|
||||
}
|
||||
}
|
||||
|
||||
if (auction.Bidders.Any())
|
||||
if (auction.BidderStats != null && auction.BidderStats.Any())
|
||||
{
|
||||
var topBidder = auction.Bidders.OrderByDescending(b => b.Value).First();
|
||||
stats.MostActiveBidder = topBidder.Key;
|
||||
stats.MostActiveBidderCount = topBidder.Value;
|
||||
var top = auction.BidderStats.OrderByDescending(b => b.Value.BidCount).First();
|
||||
stats.MostActiveBidder = top.Key;
|
||||
stats.MostActiveBidderCount = top.Value.BidCount;
|
||||
}
|
||||
|
||||
return stats;
|
||||
|
||||
19
Mimante/Models/UserBannerInfo.cs
Normal file
19
Mimante/Models/UserBannerInfo.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AutoBidder.Models
|
||||
{
|
||||
public class UserBannerInfo
|
||||
{
|
||||
public int nAsteConfermate { get; set; }
|
||||
public int limiteAsteVinte { get; set; }
|
||||
public int nAsteVinte { get; set; }
|
||||
public int nPuntateRiscattate { get; set; }
|
||||
public int nPuntateBonus { get; set; }
|
||||
public int nPuntateDaRiscattare { get; set; }
|
||||
public int nAstePerBonus { get; set; }
|
||||
public long validUntil { get; set; }
|
||||
public int percentualeBonus { get; set; }
|
||||
public int viewSlot { get; set; }
|
||||
public List<object> extraSlots { get; set; } = new();
|
||||
}
|
||||
}
|
||||
10
Mimante/Models/UserData.cs
Normal file
10
Mimante/Models/UserData.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace AutoBidder.Models
|
||||
{
|
||||
public class UserData
|
||||
{
|
||||
public string? Username { get; set; }
|
||||
public int RemainingBids { get; set; }
|
||||
public double? CashBalance { get; set; }
|
||||
public int? UserId { get; set; }
|
||||
}
|
||||
}
|
||||
33
Mimante/README.md
Normal file
33
Mimante/README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# AutoBidder
|
||||
|
||||
AutoBidder è una semplice applicazione WPF per monitorare aste Bidoo via API (HTTP-only).
|
||||
|
||||
Questa versione è stata ripulita da funzionalità legacy (WebView2/browser integrato, multi-click, modalità legacy) e si basa esclusivamente su chiamate HTTP verso le API del sito.
|
||||
|
||||
Features principali:
|
||||
- Monitoraggio parallelo di più aste
|
||||
- Polling adattivo basato su timer aste
|
||||
- Invio puntate via HTTP
|
||||
- Visualizzazione log globale e log per asta
|
||||
- Esportazione CSV di cronologia e statistiche
|
||||
- Salvataggio sessione (cookie) in modo sicuro (DPAPI)
|
||||
|
||||
Pulizia effettuata:
|
||||
- Rimosse classi XAML/Converters non più utilizzate
|
||||
- Rimosso file di test manuale
|
||||
- Rifattorizzate statistiche per usare `BidHistory` e `BidderStats`
|
||||
|
||||
Come usare:
|
||||
1. Avvia l'app
|
||||
2. Configura la sessione con il cookie `__stattrb=...` tramite `Configura`
|
||||
3. Aggiungi aste (ID o URL) con `+ Aggiungi`
|
||||
4. Avvia il monitoraggio con `Avvia Tutti`
|
||||
|
||||
Sicurezza:
|
||||
- La sessione viene salvata criptata in `%APPDATA%/AutoBidder/session.dat` usando DPAPI per l'utente corrente.
|
||||
|
||||
Note per sviluppatori:
|
||||
- Progetto .NET 8 (WPF)
|
||||
- File principali: `Services/BidooApiClient.cs`, `Services/AuctionMonitor.cs`, `MainWindow.xaml.cs`.
|
||||
- Per ulteriori pulizie o refactor, eseguire una ricerca per `legacy` o `RIMOSSO`.
|
||||
|
||||
@@ -8,8 +8,8 @@ using AutoBidder.Models;
|
||||
namespace AutoBidder.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Servizio centrale per monitoraggio multi-asta
|
||||
/// Gestisce polling API Bidoo e trigger dei click
|
||||
/// Servizio centrale per monitoraggio aste
|
||||
/// Solo HTTP, nessuna modalità, browser o multi-click
|
||||
/// </summary>
|
||||
public class AuctionMonitor
|
||||
{
|
||||
@@ -79,6 +79,30 @@ namespace AutoBidder.Services
|
||||
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();
|
||||
}
|
||||
|
||||
public void AddAuction(AuctionInfo auction)
|
||||
{
|
||||
lock (_auctions)
|
||||
@@ -112,12 +136,6 @@ namespace AutoBidder.Services
|
||||
}
|
||||
}
|
||||
|
||||
public Task<bool> InitializeCookies(Microsoft.Web.WebView2.Wpf.WebView2 webView)
|
||||
{
|
||||
// Non più utilizzato - usa InitializeSession invece
|
||||
return Task.FromResult(false);
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
if (_monitoringTask != null && !_monitoringTask.IsCompleted)
|
||||
@@ -251,14 +269,17 @@ namespace AutoBidder.Services
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
auction.AddLog("ERRORE: Nessun dato ricevuto da API");
|
||||
OnLog?.Invoke($"[ERRORE] [{auction.AuctionId}] API non ha risposto");
|
||||
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 ||
|
||||
@@ -305,63 +326,86 @@ namespace AutoBidder.Services
|
||||
// Aggiorna storico e bidders
|
||||
UpdateAuctionHistory(auction, state);
|
||||
|
||||
// Verifica se puntare (solo se asta Running, NON se in pausa)
|
||||
if (state.Status == AuctionStatus.Running && ShouldBid(auction, state) && !auction.IsPaused)
|
||||
{
|
||||
auction.AddLog($"[TRIGGER] CONDIZIONI OK - Timer {state.Timer:F2}s <= {auction.TimerClick}s");
|
||||
auction.AddLog($"[BID] Invio puntata...");
|
||||
OnLog?.Invoke($"[BID] [{auction.AuctionId}] PUNTATA a {state.Timer:F2}s!");
|
||||
|
||||
// Attendi ritardo configurato
|
||||
if (auction.DelayMs > 0)
|
||||
// OTTIMIZZAZIONE PUNTATA
|
||||
if (state.Status == AuctionStatus.Running && !auction.IsPaused && ShouldBid(auction, state))
|
||||
{
|
||||
// Se timer è esattamente 0
|
||||
if (Math.Abs(state.Timer) < 0.001)
|
||||
{
|
||||
await Task.Delay(auction.DelayMs, token);
|
||||
}
|
||||
|
||||
// Esegui puntata API
|
||||
var result = await _apiClient.PlaceBidAsync(auction.AuctionId);
|
||||
|
||||
// Aggiorna contatori
|
||||
if (result.Success)
|
||||
{
|
||||
auction.MyClicks++;
|
||||
auction.LastClickAt = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
// Notifica risultato
|
||||
OnBidExecuted?.Invoke(auction, result);
|
||||
|
||||
// Log
|
||||
if (result.Success)
|
||||
{
|
||||
auction.AddLog($"[OK] PUNTATA OK: {result.LatencyMs}ms -> EUR{result.NewPrice:F2}");
|
||||
OnLog?.Invoke($"[OK] [{auction.AuctionId}] Puntata riuscita {result.LatencyMs}ms");
|
||||
auction.AddLog($"[TRIGGER] Timer 0, attendo delay {auction.DelayMs}ms...");
|
||||
OnLog?.Invoke($"[BID] [{auction.AuctionId}] Timer 0, attendo delay...");
|
||||
if (auction.DelayMs > 0)
|
||||
await Task.Delay(auction.DelayMs, token);
|
||||
// Rileggi stato asta
|
||||
var stateAfterDelay = await _apiClient.PollAuctionStateAsync(auction.AuctionId, auction.OriginalUrl, token);
|
||||
if (stateAfterDelay != null && Math.Abs(stateAfterDelay.Timer) < 0.001 && stateAfterDelay.LastBidder == state.LastBidder)
|
||||
{
|
||||
auction.AddLog($"[BID] Condizioni OK dopo delay, invio puntata...");
|
||||
OnLog?.Invoke($"[BID] [{auction.AuctionId}] PUNTATA dopo delay!");
|
||||
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] [{auction.AuctionId}] Puntata riuscita {result.LatencyMs}ms");
|
||||
}
|
||||
else
|
||||
{
|
||||
auction.AddLog($"[FAIL] PUNTATA FALLITA: {result.Error}");
|
||||
OnLog?.Invoke($"[FAIL] [{auction.AuctionId}] ERRORE: {result.Error}");
|
||||
}
|
||||
auction.BidHistory.Add(new BidHistory
|
||||
{
|
||||
Timestamp = result.Timestamp,
|
||||
EventType = result.Success ? BidEventType.MyBid : BidEventType.OpponentBid,
|
||||
Bidder = "Tu",
|
||||
Price = stateAfterDelay.Price,
|
||||
Timer = stateAfterDelay.Timer,
|
||||
LatencyMs = result.LatencyMs,
|
||||
Success = result.Success,
|
||||
Notes = result.Success ? $"EUR{result.NewPrice:F2}" : result.Error
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
auction.AddLog($"[SKIP] Puntata non inviata: timer/utente cambiato dopo delay");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auction.AddLog($"[FAIL] PUNTATA FALLITA: {result.Error}");
|
||||
OnLog?.Invoke($"[FAIL] [{auction.AuctionId}] ERRORE: {result.Error}");
|
||||
}
|
||||
|
||||
// Storico
|
||||
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
|
||||
});
|
||||
|
||||
// Se abbiamo raggiunto il numero massimo di click per l'asta, metti in pausa le puntate (ma continua il monitor)
|
||||
if (auction.MaxClicks > 0 && auction.MyClicks >= auction.MaxClicks)
|
||||
{
|
||||
auction.IsPaused = true;
|
||||
auction.AddLog($"[PAUSA] Massimo click ({auction.MaxClicks}) raggiunto - Puntate disabilitate");
|
||||
OnLog?.Invoke($"[PAUSA] [{auction.AuctionId}] MaxClicks raggiunti");
|
||||
// Logica normale per timer > 0
|
||||
auction.AddLog($"[TRIGGER] CONDIZIONI OK - Timer {state.Timer:F2}s <= {auction.TimerClick}s");
|
||||
auction.AddLog($"[BID] Invio puntata...");
|
||||
OnLog?.Invoke($"[BID] [{auction.AuctionId}] PUNTATA a {state.Timer:F2}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] [{auction.AuctionId}] Puntata riuscita {result.LatencyMs}ms");
|
||||
}
|
||||
else
|
||||
{
|
||||
auction.AddLog($"[FAIL] PUNTATA FALLITA: {result.Error}");
|
||||
OnLog?.Invoke($"[FAIL] [{auction.AuctionId}] ERRORE: {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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -393,10 +437,6 @@ namespace AutoBidder.Services
|
||||
return false;
|
||||
}
|
||||
|
||||
// Max clicks per auction
|
||||
if (auction.MaxClicks > 0 && auction.MyClicks >= auction.MaxClicks)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,28 +12,7 @@ namespace AutoBidder.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Servizio completo API Bidoo (polling, puntate, info utente)
|
||||
/// 100% API-based con simulazione completa del comportamento browser
|
||||
///
|
||||
/// FLUSSO DI AUTENTICAZIONE:
|
||||
/// - Cookie principale: __stattr (non PHPSESSID)
|
||||
/// - Il cookie deve essere estratto dal browser dopo login manuale
|
||||
/// - Tutti i request devono includere: Cookie + X-Requested-With: XMLHttpRequest
|
||||
///
|
||||
/// CHIAMATE GET (SOLO LETTURA - No CSRF Token):
|
||||
/// 1. Polling asta: GET data.php?ALL={id}&LISTID=0
|
||||
/// 2. Info utente: GET ajax/get_auction_bids_info_banner.php
|
||||
///
|
||||
/// CHIAMATE POST (AZIONI - Richiedono CSRF Token):
|
||||
/// 1. Piazza puntata: POST bid.php
|
||||
/// - Step 1: GET pagina asta HTML per estrarre bid_token
|
||||
/// - Step 2: POST con payload: a={id}&bid_type=manual&bid_token={token}&time_sent={ts}
|
||||
/// - Step 3: Analizza risposta: "ok|..." o "error|..."
|
||||
///
|
||||
/// SIMULAZIONE BROWSER:
|
||||
/// - User-Agent: Chrome su Windows
|
||||
/// - Headers CORS: Sec-Fetch-*
|
||||
/// - Referer: URL pagina asta
|
||||
/// - Content-Type: application/x-www-form-urlencoded (per POST)
|
||||
/// Solo HTTP, nessuna modalità, browser o multi-click
|
||||
/// </summary>
|
||||
public class BidooApiClient
|
||||
{
|
||||
@@ -91,14 +70,13 @@ namespace AutoBidder.Services
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inizializza sessione con cookie string (fallback)
|
||||
/// Inizializza sessione con cookie string (manuale)
|
||||
/// </summary>
|
||||
public void InitializeSessionWithCookie(string cookieString, string username)
|
||||
{
|
||||
_session.CookieString = cookieString;
|
||||
_session.Username = username;
|
||||
|
||||
Log($"[SESSION] Cookie impostato ({cookieString.Length} chars)");
|
||||
Log($"[SESSION] Cookie impostato manualmente ({cookieString.Length} chars)");
|
||||
Log($"[SESSION] Username: {username}");
|
||||
}
|
||||
|
||||
@@ -108,20 +86,12 @@ namespace AutoBidder.Services
|
||||
/// </summary>
|
||||
private void AddAuthHeaders(HttpRequestMessage request, string? referer = null, string? auctionId = null)
|
||||
{
|
||||
// 1. AUTENTICAZIONE (priorità: CookieString completa, poi Token singolo)
|
||||
// Il cookie principale per Bidoo è __stattr (non PHPSESSID)
|
||||
// 1. AUTENTICAZIONE (solo cookie manuale)
|
||||
if (!string.IsNullOrWhiteSpace(_session.CookieString))
|
||||
{
|
||||
// Usa la stringa cookie completa (es: "__stattr=eyJyZWZ...")
|
||||
request.Headers.Add("Cookie", _session.CookieString);
|
||||
Log("[AUTH] Using full cookie string", auctionId);
|
||||
}
|
||||
else if (!string.IsNullOrWhiteSpace(_session.AuthToken))
|
||||
{
|
||||
// Fallback: se abbiamo solo il token, assumiamo sia __stattr
|
||||
request.Headers.Add("Cookie", $"__stattr={_session.AuthToken}");
|
||||
Log("[AUTH] Using __stattr token", auctionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log("[AUTH WARN] No authentication method available!", auctionId);
|
||||
@@ -244,40 +214,28 @@ namespace AutoBidder.Services
|
||||
{
|
||||
var startTime = DateTime.UtcNow;
|
||||
var url = $"https://it.bidoo.com/data.php?ALL={auctionId}&LISTID=0";
|
||||
|
||||
Log($"[API REQUEST] GET {url}", auctionId);
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
|
||||
var referer = !string.IsNullOrEmpty(auctionUrl)
|
||||
? auctionUrl
|
||||
: $"https://it.bidoo.com/auction.php?a=asta_{auctionId}";
|
||||
|
||||
Log($"[API REQUEST] Using referer: {referer}", auctionId);
|
||||
AddAuthHeaders(request, referer, auctionId);
|
||||
|
||||
var response = await _httpClient.SendAsync(request, token);
|
||||
var latency = (int)(DateTime.UtcNow - startTime).TotalMilliseconds;
|
||||
|
||||
Log($"[API RESPONSE] Status: {(int)response.StatusCode} {response.StatusCode}", auctionId);
|
||||
Log($"[API RESPONSE] Latency: {latency}ms", auctionId);
|
||||
Log($"[API RESPONSE] Content-Type: {response.Content.Headers.ContentType}", auctionId);
|
||||
|
||||
var responseText = await response.Content.ReadAsStringAsync();
|
||||
Log($"[API RESPONSE] Body ({responseText.Length} bytes): {responseText}", auctionId);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
Log($"[API ERROR] Non-success status code: {response.StatusCode}", auctionId);
|
||||
string reason = response.StatusCode == System.Net.HttpStatusCode.RequestTimeout ? "timeout" : "errore HTTP";
|
||||
Log($"[ERRORE] [{auctionId}] API non ha risposto (motivo: {reason})", null); // globale
|
||||
Log($"API non ha risposto: {response.StatusCode} ({reason})", auctionId); // asta
|
||||
return null;
|
||||
}
|
||||
|
||||
return ParsePollingResponse(auctionId, responseText, latency);
|
||||
var state = ParsePollingResponse(auctionId, responseText, latency);
|
||||
return state;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"[API EXCEPTION] Polling {auctionId}: {ex.GetType().Name} - {ex.Message}", auctionId);
|
||||
Log($"[API EXCEPTION] StackTrace: {ex.StackTrace}", auctionId);
|
||||
Log($"[ERRORE] [{auctionId}] API non ha risposto (motivo: eccezione)", null); // globale
|
||||
Log($"API non ha risposto: {ex.GetType().Name} - {ex.Message}", auctionId); // asta
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -286,156 +244,71 @@ namespace AutoBidder.Services
|
||||
{
|
||||
try
|
||||
{
|
||||
Log($"[PARSE] Starting parse for auction {auctionId}", auctionId);
|
||||
Log($"[PARSE] Raw response: {response}", auctionId);
|
||||
|
||||
// Step 1: Estrai Server Timestamp (può avere formato "timestamp*..." o "timestamp|flag*")
|
||||
string serverTimestamp;
|
||||
string mainData;
|
||||
|
||||
// Cerca il separatore '*' che divide timestamp da dati
|
||||
var starIndex = response.IndexOf('*');
|
||||
if (starIndex == -1)
|
||||
{
|
||||
Log("[PARSE ERROR] No '*' separator found in response", auctionId);
|
||||
return null;
|
||||
}
|
||||
|
||||
var timestampPart = response.Substring(0, starIndex);
|
||||
mainData = response.Substring(starIndex + 1);
|
||||
|
||||
// Il timestamp può contenere '|' (es: "1761120002|1")
|
||||
// Prendiamo solo la prima parte numerica
|
||||
if (timestampPart.Contains('|'))
|
||||
{
|
||||
serverTimestamp = timestampPart.Split('|')[0];
|
||||
Log($"[PARSE] Extended format detected: {timestampPart}", auctionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
serverTimestamp = timestampPart;
|
||||
}
|
||||
|
||||
Log($"[PARSE] Server Timestamp: {serverTimestamp}", auctionId);
|
||||
Log($"[PARSE] Main Data: {mainData}", auctionId);
|
||||
|
||||
// Step 2: Estrai dati asta tra parentesi quadre [...]
|
||||
var bracketStart = mainData.IndexOf('[');
|
||||
var bracketEnd = mainData.IndexOf(']');
|
||||
|
||||
if (bracketStart == -1 || bracketEnd == -1)
|
||||
{
|
||||
Log("[PARSE ERROR] Missing brackets in auction data", auctionId);
|
||||
return null;
|
||||
}
|
||||
|
||||
var auctionData = mainData.Substring(bracketStart + 1, bracketEnd - bracketStart - 1);
|
||||
Log($"[PARSE] Auction Data extracted: {auctionData}", auctionId);
|
||||
|
||||
// Step 3: Split per ';' per ottenere i campi principali
|
||||
// Nota: la stringa può contenere '|' per separare bid history, quindi ci fermiamo al primo '|' o ','
|
||||
var firstSeparator = auctionData.IndexOfAny(new[] { '|', ',' });
|
||||
var coreData = firstSeparator > 0 ? auctionData.Substring(0, firstSeparator) : auctionData;
|
||||
|
||||
var fields = coreData.Split(';');
|
||||
Log($"[PARSE] Core fields count: {fields.Length}", auctionId);
|
||||
for (int i = 0; i < Math.Min(fields.Length, 10); i++)
|
||||
{
|
||||
Log($"[PARSE] Field[{i}] = '{fields[i]}'", auctionId);
|
||||
}
|
||||
|
||||
if (fields.Length < 5)
|
||||
{
|
||||
Log($"[PARSE ERROR] Expected at least 5 core fields, got {fields.Length}", auctionId);
|
||||
return null;
|
||||
}
|
||||
|
||||
var state = new AuctionState
|
||||
{
|
||||
AuctionId = auctionId,
|
||||
SnapshotTime = DateTime.UtcNow,
|
||||
PollingLatencyMs = latency
|
||||
};
|
||||
|
||||
// Step 4: Parse campi principali
|
||||
|
||||
// Field 0: AuctionID (verifica)
|
||||
Log($"[PARSE] Auction ID from response: {fields[0]} (expected: {auctionId})", auctionId);
|
||||
|
||||
// Field 1: Status (ON/OFF)
|
||||
var status = fields[1].Trim().ToUpperInvariant();
|
||||
|
||||
// Determiniamo lo stato in base a: Status API + LastBidder + Timer
|
||||
// Parsing di Field 4 (LastBidder) anticipato per logica stato
|
||||
string lastBidder = fields[4].Trim();
|
||||
bool hasWinner = !string.IsNullOrEmpty(lastBidder);
|
||||
bool iAmWinner = hasWinner && lastBidder.Equals(_session.Username, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
state.Status = DetermineAuctionStatus(status, hasWinner, iAmWinner, ref state);
|
||||
|
||||
Log($"[PARSE] Status: {status} -> {state.Status}", auctionId);
|
||||
|
||||
// Field 2: Expiry Timestamp (CRITICO per timing)
|
||||
// IMPORTANTE: Usa il ServerTimestamp dalla risposta, NON il tempo locale!
|
||||
// Formato: ServerTimestamp*[..;ExpiryTimestamp;..]
|
||||
// Timer = ExpiryTimestamp - ServerTimestamp
|
||||
if (long.TryParse(serverTimestamp, out var serverTs) && long.TryParse(fields[2], out var expiryTs))
|
||||
{
|
||||
// Calcolo corretto usando il tempo del server
|
||||
var timerSeconds = (double)(expiryTs - serverTs);
|
||||
state.Timer = Math.Max(0, timerSeconds);
|
||||
|
||||
Log($"[PARSE] Server Timestamp: {serverTs}", auctionId);
|
||||
Log($"[PARSE] Expiry Timestamp: {expiryTs}", auctionId);
|
||||
Log($"[PARSE] Timer (Expiry - Server): {expiryTs} - {serverTs} = {timerSeconds:F2}s", auctionId);
|
||||
Log($"[PARSE] ⏱️ Final Timer: {state.Timer:F2}s", auctionId);
|
||||
|
||||
// DEBUG: Verifica se il timer sembra sbagliato
|
||||
if (state.Timer > 60)
|
||||
{
|
||||
Log("[PARSE WARN] Timer unusually high! Check data", auctionId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"[PARSE WARN] Failed to parse timestamps - Server: {serverTimestamp}, Expiry: {fields[2]}", auctionId);
|
||||
}
|
||||
|
||||
// Field 3: Price Index (0.01 EUR per index)
|
||||
if (int.TryParse(fields[3], out var priceIndex))
|
||||
{
|
||||
state.Price = priceIndex * 0.01;
|
||||
Log($"[PARSE] Price Index: {priceIndex} -> €{state.Price:F2}", auctionId);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log($"[PARSE WARN] Failed to parse price index: {fields[3]}", auctionId);
|
||||
}
|
||||
|
||||
// Field 4: Last Bidder
|
||||
state.LastBidder = fields[4].Trim();
|
||||
state.IsMyBid = !string.IsNullOrEmpty(_session.Username) &&
|
||||
state.LastBidder.Equals(_session.Username, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
Log($"[PARSE] 👤 Last Bidder: {state.LastBidder}", auctionId);
|
||||
Log($"[PARSE] Is My Bid: {state.IsMyBid} (my username: {_session.Username})", auctionId);
|
||||
|
||||
// Field 5 (optional): Additional flags or data
|
||||
if (fields.Length > 5)
|
||||
{
|
||||
Log($"[PARSE] Additional data fields present: {fields.Length - 5}", auctionId);
|
||||
}
|
||||
|
||||
state.ParsingSuccess = true;
|
||||
|
||||
Log($"[PARSE SUCCESS] ✓ Timer: {state.Timer:F2}s, Price: €{state.Price:F2}, Bidder: {state.LastBidder}, Status: {state.Status}", auctionId);
|
||||
|
||||
// Log only summary on success
|
||||
Log($"[PARSE SUCCESS] Timer: {state.Timer:F2}s, Price: €{state.Price:F2}, Bidder: {state.LastBidder}, Status: {state.Status}", auctionId);
|
||||
return state;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"[PARSE EXCEPTION] {ex.GetType().Name}: {ex.Message}", auctionId);
|
||||
Log($"[PARSE EXCEPTION] StackTrace: {ex.StackTrace}", auctionId);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -457,9 +330,9 @@ namespace AutoBidder.Services
|
||||
|
||||
Log($"[USER INFO RESPONSE] Status: {(int)response.StatusCode} {response.StatusCode}");
|
||||
Log($"[USER INFO RESPONSE] Latency: {latency}ms");
|
||||
|
||||
|
||||
var responseText = await response.Content.ReadAsStringAsync();
|
||||
Log($"[USER INFO RESPONSE] Body: {responseText}");
|
||||
Log($"[USER INFO RESPONSE] Body length: {responseText.Length}");
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
@@ -484,39 +357,33 @@ namespace AutoBidder.Services
|
||||
AuctionId = auctionId,
|
||||
Timestamp = DateTime.UtcNow
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
Log($"[BID] Placing bid via direct GET to bid.php (no token)", auctionId);
|
||||
|
||||
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}";
|
||||
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 ({responseText.Length} bytes): {responseText}", auctionId);
|
||||
|
||||
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;
|
||||
@@ -525,8 +392,7 @@ namespace AutoBidder.Services
|
||||
{
|
||||
result.NewPrice = priceIndex * 0.01;
|
||||
}
|
||||
|
||||
Log("[BID SUCCESS] ✓ Bid placed successfully via GET", auctionId);
|
||||
Log("[BID SUCCESS] ✓ Bid placed successfully", auctionId);
|
||||
}
|
||||
else if (responseText.StartsWith("error", StringComparison.OrdinalIgnoreCase) || responseText.StartsWith("no|", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
@@ -547,7 +413,6 @@ namespace AutoBidder.Services
|
||||
result.Error = string.IsNullOrEmpty(responseText) ? $"HTTP {(int)response.StatusCode}" : responseText;
|
||||
Log($"[BID ERROR] Unexpected response format: {result.Error}", auctionId);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -555,7 +420,7 @@ namespace AutoBidder.Services
|
||||
result.Success = false;
|
||||
result.Error = ex.Message;
|
||||
Log($"[BID EXCEPTION] {ex.GetType().Name}: {ex.Message}", auctionId);
|
||||
Log($"[BID EXCEPTION] StackTrace: {ex.StackTrace}", auctionId);
|
||||
Log($"[BID EXCEPTION] StackTrace available in debug logs", auctionId);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -623,5 +488,136 @@ namespace AutoBidder.Services
|
||||
{
|
||||
_httpClient?.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene dati utente (nome, puntate residue, saldo, id) tramite chiamata AJAX leggera
|
||||
/// </summary>
|
||||
public async Task<UserData?> GetUserDataAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = "https://it.bidoo.com/update_credits_status.php?submit=1";
|
||||
Log($"[USER STATUS REQUEST] GET {url}");
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
AddAuthHeaders(request, "https://it.bidoo.com/");
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
var responseString = await response.Content.ReadAsStringAsync();
|
||||
Log($"[USER STATUS RESPONSE] Body: {responseString}");
|
||||
var userData = new UserData();
|
||||
var trimmed = responseString.Trim();
|
||||
// Caso: solo numero
|
||||
if (int.TryParse(trimmed, out int bids))
|
||||
{
|
||||
userData.RemainingBids = bids;
|
||||
return userData;
|
||||
}
|
||||
// Caso: HTML <span id="bids_count">125</span>
|
||||
if (trimmed.StartsWith("<span") && trimmed.Contains("id=\"bids_count\""))
|
||||
{
|
||||
var start = trimmed.IndexOf('>');
|
||||
var end = trimmed.IndexOf("</span>");
|
||||
if (start >= 0 && end > start)
|
||||
{
|
||||
var value = trimmed.Substring(start + 1, end - start - 1);
|
||||
if (int.TryParse(value, out bids))
|
||||
{
|
||||
userData.RemainingBids = bids;
|
||||
return userData;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Caso: stringa delimitata da pipe
|
||||
var parts = trimmed.Split('|');
|
||||
if (parts.Length >= 2)
|
||||
{
|
||||
userData.Username = parts[0];
|
||||
if (int.TryParse(parts[1], out bids))
|
||||
{
|
||||
userData.RemainingBids = bids;
|
||||
}
|
||||
if (parts.Length >= 3 && double.TryParse(parts[2], out double cash))
|
||||
{
|
||||
userData.CashBalance = cash;
|
||||
}
|
||||
if (parts.Length >= 4 && int.TryParse(parts[3], out int userId))
|
||||
{
|
||||
userData.UserId = userId;
|
||||
}
|
||||
return userData;
|
||||
}
|
||||
// Se non riconosciuto
|
||||
Log($"[USER STATUS PARSE ERROR] Formato non riconosciuto: {responseString}");
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"[USER STATUS EXCEPTION] {ex.GetType().Name}: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene info banner utente (aste vinte, bonus, ecc.) tramite chiamata AJAX
|
||||
/// </summary>
|
||||
public async Task<UserBannerInfo?> GetUserBannerInfoAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var url = "https://it.bidoo.com/ajax/get_auction_bids_info_banner.php";
|
||||
Log($"[USER BANNER REQUEST] GET {url}");
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
AddAuthHeaders(request, "https://it.bidoo.com/");
|
||||
var response = await _httpClient.SendAsync(request);
|
||||
var responseString = await response.Content.ReadAsStringAsync();
|
||||
Log($"[USER BANNER RESPONSE] Body: {responseString}");
|
||||
if (!response.IsSuccessStatusCode || string.IsNullOrWhiteSpace(responseString))
|
||||
return null;
|
||||
var info = System.Text.Json.JsonSerializer.Deserialize<UserBannerInfo>(responseString);
|
||||
return info;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"[USER BANNER EXCEPTION] {ex.GetType().Name}: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Estrae nome utente e puntate residue dall'HTML di bids_history.php
|
||||
/// </summary>
|
||||
public async Task<UserData?> GetUserDataFromHtmlAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
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);
|
||||
var html = await response.Content.ReadAsStringAsync();
|
||||
Log($"[USER HTML RESPONSE] Body length: {html.Length}");
|
||||
var userData = new UserData();
|
||||
// Estrai nome utente
|
||||
var userMatch = System.Text.RegularExpressions.Regex.Match(html, @"<a class=""pers_lnk""[^>]*>([^<]+)</a>");
|
||||
if (userMatch.Success)
|
||||
{
|
||||
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))
|
||||
{
|
||||
userData.RemainingBids = bids;
|
||||
}
|
||||
if (!string.IsNullOrEmpty(userData.Username) && userData.RemainingBids > 0)
|
||||
return userData;
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log($"[USER HTML EXCEPTION] {ex.GetType().Name}: {ex.Message}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace AutoBidder.Services
|
||||
/// <summary>
|
||||
/// Gestore persistenza sessione Bidoo
|
||||
/// Salva in modo sicuro il cookie di autenticazione per riutilizzo
|
||||
/// Il cookie deve essere inserito manualmente dall'utente (copiato dal browser)
|
||||
/// </summary>
|
||||
public class SessionManager
|
||||
{
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace AutoBidder.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// Test manuale delle API Bidoo
|
||||
/// Compilare e eseguire per testare le chiamate
|
||||
/// </summary>
|
||||
public class TestBidooApi
|
||||
{
|
||||
public static async Task RunAsync(string[] args)
|
||||
{
|
||||
Console.WriteLine("=== TEST MANUALE API BIDOO ===\n");
|
||||
|
||||
// CONFIGURAZIONE
|
||||
Console.Write("Inserisci il cookie __stattr: ");
|
||||
string? cookie = Console.ReadLine();
|
||||
|
||||
Console.Write("Inserisci l'ID asta (es: 81417915): ");
|
||||
string? auctionId = Console.ReadLine();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(cookie) || string.IsNullOrWhiteSpace(auctionId))
|
||||
{
|
||||
Console.WriteLine("? Cookie o Auction ID mancanti!");
|
||||
return;
|
||||
}
|
||||
|
||||
Console.WriteLine("\n--- Test 1: Polling Stato Asta ---");
|
||||
await TestPollingAsync(cookie, auctionId);
|
||||
|
||||
Console.WriteLine("\n--- Test 2: Info Utente ---");
|
||||
await TestUserInfoAsync(cookie);
|
||||
|
||||
Console.WriteLine("? Test completati! Premi un tasto per uscire...");
|
||||
Console.ReadKey();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test chiamata polling asta
|
||||
/// </summary>
|
||||
private static async Task TestPollingAsync(string cookie, string auctionId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var handler = new HttpClientHandler { UseCookies = false };
|
||||
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(5) };
|
||||
|
||||
var url = $"https://it.bidoo.com/data.php?ALL={auctionId}&LISTID=0";
|
||||
Console.WriteLine($"?? GET {url}");
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
|
||||
// Headers
|
||||
request.Headers.Add("Cookie", $"__stattr={cookie}");
|
||||
request.Headers.Add("X-Requested-With", "XMLHttpRequest");
|
||||
request.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
|
||||
request.Headers.Add("Referer", $"https://it.bidoo.com/asta/nome-prodotto-{auctionId}");
|
||||
|
||||
var startTime = DateTime.UtcNow;
|
||||
var response = await client.SendAsync(request);
|
||||
var latency = (int)(DateTime.UtcNow - startTime).TotalMilliseconds;
|
||||
|
||||
Console.WriteLine($"?? Status: {(int)response.StatusCode} {response.StatusCode}");
|
||||
Console.WriteLine($"?? Latency: {latency}ms");
|
||||
|
||||
var responseText = await response.Content.ReadAsStringAsync();
|
||||
Console.WriteLine($"?? Response ({responseText.Length} bytes):");
|
||||
Console.WriteLine(responseText);
|
||||
|
||||
// Parse semplice
|
||||
if (response.IsSuccessStatusCode && responseText.Contains("*"))
|
||||
{
|
||||
Console.WriteLine("\n?? Parsing:");
|
||||
var parts = responseText.Split('*');
|
||||
Console.WriteLine($" Server Time: {parts[0]}");
|
||||
|
||||
if (parts.Length > 1)
|
||||
{
|
||||
var data = parts[1].Replace("[", "").Replace("]", "").Split(';');
|
||||
if (data.Length >= 5)
|
||||
{
|
||||
Console.WriteLine($" Auction ID: {data[0]}");
|
||||
Console.WriteLine($" Status: {data[1]}");
|
||||
Console.WriteLine($" Expiry: {data[2]}");
|
||||
|
||||
if (int.TryParse(data[3], out var priceIndex))
|
||||
{
|
||||
Console.WriteLine($" Price: {priceIndex} ? €{(priceIndex * 0.01):F2}");
|
||||
}
|
||||
|
||||
Console.WriteLine($" Last Bidder: {data[4]}");
|
||||
|
||||
// Calcola timer
|
||||
if (long.TryParse(data[2], out var expiryTs))
|
||||
{
|
||||
var expiryTime = DateTimeOffset.FromUnixTimeSeconds(expiryTs);
|
||||
var timer = (expiryTime - DateTimeOffset.UtcNow).TotalSeconds;
|
||||
Console.WriteLine($" Timer: {Math.Max(0, timer):F2}s");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("?? Risposta non valida o errore");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"? Errore: {ex.GetType().Name} - {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test chiamata info utente
|
||||
/// </summary>
|
||||
private static async Task TestUserInfoAsync(string cookie)
|
||||
{
|
||||
try
|
||||
{
|
||||
var handler = new HttpClientHandler { UseCookies = false };
|
||||
using var client = new HttpClient(handler) { Timeout = TimeSpan.FromSeconds(5) };
|
||||
|
||||
var url = "https://it.bidoo.com/ajax/get_auction_bids_info_banner.php";
|
||||
Console.WriteLine($"?? GET {url}");
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
|
||||
// Headers
|
||||
request.Headers.Add("Cookie", $"__stattr={cookie}");
|
||||
request.Headers.Add("X-Requested-With", "XMLHttpRequest");
|
||||
request.Headers.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36");
|
||||
request.Headers.Add("Referer", "https://it.bidoo.com/");
|
||||
|
||||
var startTime = DateTime.UtcNow;
|
||||
var response = await client.SendAsync(request);
|
||||
var latency = (int)(DateTime.UtcNow - startTime).TotalMilliseconds;
|
||||
|
||||
Console.WriteLine($"?? Status: {(int)response.StatusCode} {response.StatusCode}");
|
||||
Console.WriteLine($"?? Latency: {latency}ms");
|
||||
|
||||
var responseText = await response.Content.ReadAsStringAsync();
|
||||
Console.WriteLine($"?? Response ({responseText.Length} bytes):");
|
||||
Console.WriteLine(responseText);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"? Errore: {ex.GetType().Name} - {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,30 +99,31 @@ namespace AutoBidder.Utilities
|
||||
public static void ExportAllAuctions(IEnumerable<AuctionInfo> auctions, string filePath)
|
||||
{
|
||||
var csv = new StringBuilder();
|
||||
|
||||
|
||||
// Header
|
||||
csv.AppendLine("Auction ID,Name,Timer Click,Delay (ms),Min Price,Max Price," +
|
||||
"My Clicks,Resets,Total Bidders,Active,Paused," +
|
||||
"Added At,Last Click At");
|
||||
|
||||
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");
|
||||
|
||||
// Data
|
||||
foreach (var auction in auctions)
|
||||
{
|
||||
var totalBidders = auction.BidderStats?.Count ?? 0;
|
||||
var myBids = auction.BidHistory.Count(h => h.EventType == BidEventType.MyBid);
|
||||
|
||||
csv.AppendLine($"{auction.AuctionId}," +
|
||||
$"\"{auction.Name}\"," +
|
||||
$"{auction.TimerClick}," +
|
||||
$"{auction.DelayMs}," +
|
||||
$"{auction.MinPrice:F2}," +
|
||||
$"{auction.MaxPrice:F2}," +
|
||||
$"{auction.MyClicks}," +
|
||||
$"{myBids}," +
|
||||
$"{auction.ResetCount}," +
|
||||
$"{auction.Bidders.Count}," +
|
||||
$"{totalBidders}," +
|
||||
$"{auction.IsActive}," +
|
||||
$"{auction.IsPaused}," +
|
||||
$"{auction.AddedAt:yyyy-MM-dd HH:mm:ss}," +
|
||||
$"{(auction.LastClickAt?.ToString("yyyy-MM-dd HH:mm:ss") ?? "")}");
|
||||
}
|
||||
|
||||
|
||||
File.WriteAllText(filePath, csv.ToString(), Encoding.UTF8);
|
||||
}
|
||||
}
|
||||
|
||||
34
Mimante/Utilities/RelayCommand.cs
Normal file
34
Mimante/Utilities/RelayCommand.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace AutoBidder.Utilities
|
||||
{
|
||||
public class RelayCommand : ICommand
|
||||
{
|
||||
private readonly Action<object?> _execute;
|
||||
private readonly Func<object?, bool>? _canExecute;
|
||||
|
||||
public RelayCommand(Action<object?> execute, Func<object?, bool>? canExecute = null)
|
||||
{
|
||||
_execute = execute ?? throw new ArgumentNullException(nameof(execute));
|
||||
_canExecute = canExecute;
|
||||
}
|
||||
|
||||
public bool CanExecute(object? parameter)
|
||||
{
|
||||
return _canExecute?.Invoke(parameter) ?? true;
|
||||
}
|
||||
|
||||
public void Execute(object? parameter)
|
||||
{
|
||||
_execute(parameter);
|
||||
}
|
||||
|
||||
public event EventHandler? CanExecuteChanged;
|
||||
|
||||
public void RaiseCanExecuteChanged()
|
||||
{
|
||||
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using AutoBidder.Models;
|
||||
|
||||
namespace AutoBidder.ViewModels
|
||||
{
|
||||
/// <summary>
|
||||
/// ViewModel per una riga della griglia aste (DataBinding)
|
||||
/// Solo HTTP, nessuna modalità, browser o multi-click
|
||||
/// </summary>
|
||||
public class AuctionViewModel : INotifyPropertyChanged
|
||||
{
|
||||
@@ -64,6 +66,16 @@ namespace AutoBidder.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public int MaxClicks
|
||||
{
|
||||
get => _auctionInfo.MaxClicks;
|
||||
set
|
||||
{
|
||||
_auctionInfo.MaxClicks = value;
|
||||
OnPropertyChanged(nameof(MaxClicks));
|
||||
}
|
||||
}
|
||||
|
||||
// Stato
|
||||
public bool IsActive
|
||||
{
|
||||
@@ -74,7 +86,6 @@ namespace AutoBidder.ViewModels
|
||||
OnPropertyChanged(nameof(IsActive));
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsPaused
|
||||
{
|
||||
get => _auctionInfo.IsPaused;
|
||||
@@ -84,10 +95,23 @@ namespace AutoBidder.ViewModels
|
||||
OnPropertyChanged(nameof(IsPaused));
|
||||
}
|
||||
}
|
||||
|
||||
// Contatori
|
||||
public int MyClicks => _auctionInfo.MyClicks;
|
||||
public int ResetCount => _auctionInfo.ResetCount;
|
||||
|
||||
// My clicks computed from history (thread-safe, null-safe)
|
||||
public int MyClicks
|
||||
{
|
||||
get
|
||||
{
|
||||
var history = _auctionInfo.BidHistory;
|
||||
if (history == null) return 0;
|
||||
BidHistory[] snapshot;
|
||||
lock (history)
|
||||
{
|
||||
snapshot = history.ToArray();
|
||||
}
|
||||
return snapshot.Count(h => h != null && h.EventType == BidEventType.MyBid);
|
||||
}
|
||||
}
|
||||
|
||||
// Dati live (da ultimo polling)
|
||||
public string TimerDisplay
|
||||
@@ -192,7 +216,6 @@ namespace AutoBidder.ViewModels
|
||||
public void UpdateState(AuctionState state)
|
||||
{
|
||||
_lastState = state;
|
||||
|
||||
// Notifica tutte le proprietà dipendenti dallo stato
|
||||
OnPropertyChanged(nameof(TimerDisplay));
|
||||
OnPropertyChanged(nameof(PriceDisplay));
|
||||
@@ -200,6 +223,8 @@ namespace AutoBidder.ViewModels
|
||||
OnPropertyChanged(nameof(IsMyBid));
|
||||
OnPropertyChanged(nameof(StatusDisplay));
|
||||
OnPropertyChanged(nameof(Strategy));
|
||||
OnPropertyChanged(nameof(MyClicks));
|
||||
OnPropertyChanged(nameof(AuctionInfo)); // For PollingLatencyMs
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -207,8 +232,9 @@ namespace AutoBidder.ViewModels
|
||||
/// </summary>
|
||||
public void RefreshCounters()
|
||||
{
|
||||
OnPropertyChanged(nameof(MyClicks));
|
||||
// RIMOSSO: OnPropertyChanged(nameof(MyClicks));
|
||||
OnPropertyChanged(nameof(ResetCount));
|
||||
OnPropertyChanged(nameof(MyClicks));
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler? PropertyChanged;
|
||||
|
||||
101
README.md
101
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
> AutoBidder è uno strumento desktop per Windows (WPF, .NET 8) pensato per automatizzare le offerte sul sito Bidoo.com. Questa guida descrive caratteristiche, installazione, configurazione, modalità operative, strategie avanzate, dettagli tecnici e risoluzione dei problemi.
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
---
|
||||
@@ -12,9 +12,9 @@ Sommario
|
||||
- Requisiti di sistema
|
||||
- Installazione e avvio
|
||||
- Guida rapida (primi passi)
|
||||
- Modalità operative: Asta Singola e Multi-Asta
|
||||
- Gestione aste tramite griglia
|
||||
- Strategie e consigli pratici
|
||||
- Dettagli tecnici: Polling, Click HTTP e sincronizzazione cookie
|
||||
- Dettagli tecnici: Polling e Click HTTP
|
||||
- Impostazioni e persistenza
|
||||
- Esportazione e diagnostica
|
||||
- FAQ e risoluzione problemi
|
||||
@@ -23,10 +23,9 @@ Sommario
|
||||
---
|
||||
|
||||
## Caratteristiche principali
|
||||
- Monitoraggio in tempo reale di singole aste o molte aste contemporaneamente
|
||||
- Due modalità operative: `Asta Singola` (massima precisione) e `Multi-Asta` (monitoraggio e auto-switch)
|
||||
- Polling adattivo (HTTP / WebView2 / Active) per ridurre uso di CPU e RAM
|
||||
- Click HTTP diretto (reverse engineered) con sincronizzazione dei cookie dal WebView2
|
||||
- Monitoraggio in tempo reale di tutte le aste tramite griglia unica
|
||||
- Polling adattivo solo HTTP per ridurre uso di CPU e RAM
|
||||
- Click HTTP diretto (reverse engineered) con cookie manuale
|
||||
- Persistenza della lista aste (`auctions.json`) e esportazione CSV delle statistiche
|
||||
- UI dark theme moderna con griglia in tempo reale, dettagli asta, log e contatori
|
||||
|
||||
@@ -44,59 +43,47 @@ Sommario
|
||||
- `dotnet restore`
|
||||
- `dotnet build --configuration Release`
|
||||
4. Eseguire l'app:
|
||||
- `dotnet run` (dalla cartella del progetto) oppure avviare `AutoBidder.exe` in `bin\\Release\\net8.0-windows`
|
||||
- `dotnet run` (dalla cartella del progetto) oppure avviare `AutoBidder.exe` in `bin\Release\net8.0-windows`
|
||||
|
||||
## Guida rapida (primi passi)
|
||||
1. Avvia l'app: l'interfaccia principale mostra due pannelli (controlli a sinistra, browser WebView2 a destra).
|
||||
2. Login: accedi a `bidoo.com` tramite il browser integrato (se vuoi usare il Click HTTP diretto devi essere loggato).
|
||||
3. Scegli modalità:
|
||||
- `Asta Singola` per concentrarti su un solo oggetto
|
||||
- `Multi-Asta` per monitorare più aste e lasciare che l'app esegua auto-switch
|
||||
4. Aggiungi aste (Multi-Asta):
|
||||
- Metodo Automatico: vai sulla pagina Preferiti e lascia che l'app rilevi le aste
|
||||
- Metodo Manuale: clicca `+ URL` o `Pagina` e incolla/aggiungi l'URL dell'asta
|
||||
5. Configura impostazioni globali o per-asta (Timer Click, Min/Max Price, Max Clicks, Max Resets, Ritardo, Multi-Click)
|
||||
6. Premi `Avvia` per attivare il Click Loop; il background polling rimane sempre attivo anche senza Avvia.
|
||||
1. Avvia l'app: l'interfaccia principale mostra la griglia delle aste monitorate.
|
||||
2. Configura la sessione: copia il cookie `__stattrb` dal browser (F12 > Application > Cookies) e inseriscilo tramite il dialog di configurazione.
|
||||
3. Aggiungi aste: usa il pulsante `+ Aggiungi` per inserire URL o ID delle aste da monitorare.
|
||||
4. Configura impostazioni per ogni asta (Timer Click, Min/Max Price, Max Clicks, Max Resets, Ritardo).
|
||||
5. Premi `Avvia Tutti` per attivare il monitoraggio e le puntate automatiche.
|
||||
|
||||
## Modalità operative
|
||||
|
||||
### Asta Singola
|
||||
- Ideale per oggetti di valore dove la precisione è critica.
|
||||
- Monitoraggio intensivo del DOM tramite WebView2, contatori dettagliati, lista utenti e log dedicato.
|
||||
- Supporta `Multi-Click` (click multipli paralleli) per aumentare probabilità in connessione instabile.
|
||||
- Impostazioni consigliate per asta singola: `Timer Click` 0-1, `Multi-Click` ON, `Ritardo` 0ms.
|
||||
|
||||
### Multi-Asta
|
||||
- Monitoraggio simultaneo di molte aste.
|
||||
- L'app punta solo sull'asta con `timer` più basso (auto-switch) per evitare concorrere contro se stessa.
|
||||
- Per ogni asta è possibile impostare opzioni indipendenti (Timer Click, Min/Max Price, Pausa/Riprendi).
|
||||
- Metodo manuale (URL diretto) è raccomandato per monitoraggi massivi (consumo risorse minimo).
|
||||
## Gestione aste tramite griglia
|
||||
- Tutte le aste sono gestite tramite una griglia unica.
|
||||
- Puoi avviare, mettere in pausa, fermare o puntare manualmente su ogni asta direttamente dalla griglia.
|
||||
- Non esistono più modalità "Asta Singola" o "Multi-Asta": tutto è gestito in modo uniforme.
|
||||
|
||||
## Strategie consigliate
|
||||
- Monitoraggio massivo (50+ aste): usa `URL Manuale`, `Timer Click` 0, limiti di prezzo bassi.
|
||||
- Asta singola ad alto valore: `Timer Click` 0-1, `Multi-Click` ON, `Ritardo` 0ms.
|
||||
- Caccia all'affare: `Max Price` molto basso, monitora molte aste con Timer Click 0.
|
||||
- Monitoraggio massivo: aggiungi molte aste tramite URL o ID, imposta Timer Click basso e limiti di prezzo.
|
||||
- Asta ad alto valore: Timer Click 0-1, Ritardo 0ms.
|
||||
- Caccia all'affare: Max Price basso, monitora molte aste con Timer Click 0.
|
||||
|
||||
## Approfondimento tecnico
|
||||
|
||||
### Polling adattivo (Dual-Track)
|
||||
- Track 1 — Background Polling: esegue richieste HTTP ogni ~5s per aggiornare timer, prezzo e ultimo bidder. Mantiene uso CPU/RAM minimo.
|
||||
- Track 2 — Click Loop: attivo solo dopo `Avvia`. Polling dinamico 20-400ms per aste critiche e invio dei click (HTTP diretto o WebView2 fallback).
|
||||
### Polling adattivo
|
||||
- Il monitoraggio avviene solo tramite chiamate HTTP dirette alle API di Bidoo.
|
||||
- Nessun WebView2 viene utilizzato, nemmeno come fallback.
|
||||
- Il polling si adatta al timer dell'asta per ottimizzare la frequenza delle richieste.
|
||||
|
||||
### Click HTTP diretto
|
||||
- Al momento dell'azione, il client costruisce una GET verso l'endpoint reverse engineered di Bidoo tipo:
|
||||
`GET https://it.bidoo.com/bid.php?AID=81204347&sup=0&shock=0`
|
||||
- Richiede i cookie di sessione (PHPSESSID, user_token, ecc.) che vengono sincronizzati dal WebView2.
|
||||
- Latenza tipica: 10-30ms (molto più veloce del click via WebView2 che può impiegare 50-100ms)
|
||||
- Se il Click HTTP fallisce, l'app esegue fallback automatico con `ExecuteScript` su WebView2.
|
||||
- Richiede i cookie di sessione (`__stattrb`) che vengono copiati manualmente dal browser.
|
||||
- Latenza tipica: 10-30ms.
|
||||
- Non esiste più alcun fallback WebView2: tutte le puntate sono gestite solo via HTTP.
|
||||
|
||||
### Sincronizzazione cookie
|
||||
- Al primo `Avvia` viene letto il `CookieManager` di `CoreWebView2`, copiato in un `CookieContainer` per l'`HttpClient` utilizzato dai Click HTTP.
|
||||
- I cookie sono conservati solo in memoria, mai su disco. Devono essere rinfrescati ri-effettuando il login nel WebView2 se scadono.
|
||||
- I cookie vengono inseriti manualmente dall'utente tramite dialog.
|
||||
- Non vengono mai salvati su disco in chiaro.
|
||||
- Se il cookie scade, è necessario copiarlo nuovamente dal browser.
|
||||
|
||||
## Persistenza e file locali
|
||||
- Lista aste manuali salvata in:
|
||||
`%AppData%\\AutoBidder\\auctions.json`
|
||||
`%AppData%\AutoBidder\auctions.json`
|
||||
- La lista viene ricaricata automaticamente all'avvio dell'app.
|
||||
- Esportazione CSV: la funzionalità `Export CSV` genera un file contenente statistiche per ogni asta (nome, ID, URL, timer, prezzo, strategy, click/resets, impostazioni per-asta).
|
||||
|
||||
@@ -105,13 +92,10 @@ Sommario
|
||||
- `Max Clicks` / `Max Resets`: limiti operativi (0 = illimitato)
|
||||
- `Min/Max Price`: evita puntate fuori dal range desiderato
|
||||
- `Ritardo (ms)`: delay aggiuntivo prima di inviare il click
|
||||
- `Multi-Click`: invia più click paralleli (utile su connessioni con jitter)
|
||||
|
||||
## Interfaccia e controlli
|
||||
- Pannello sinistro: controlli principali (modalità, Avvia, Pausa, Stop, aggiungi/rimuovi aste, esporta CSV)
|
||||
- Pannello destro: browser integrato (WebView2) + dettagli asta quando selezionata
|
||||
- Griglia aste (multi-asta): mostra Timer, Prezzo, Strategia, Clicks, Resets, Ultimo bidder
|
||||
- Log in tempo reale per ogni asta con dettagli di latenza, risposta server e fallimenti
|
||||
- Griglia centrale: mostra tutte le aste monitorate con controlli per avviare, mettere in pausa, fermare e puntare manualmente.
|
||||
- Log in tempo reale per ogni asta con dettagli di latenza, risposta server e fallimenti.
|
||||
|
||||
## Diagnostica ed esportazione
|
||||
- Abilita log dettagliato prima di aprire issue o per analisi locali
|
||||
@@ -119,17 +103,18 @@ Sommario
|
||||
- Mantieni screenshot del pannello log per report più chiari
|
||||
|
||||
## FAQ e risoluzione dei problemi
|
||||
- "Non vedo Click HTTP riuscito": assicurati di essere loggato nel WebView2 e che la sincronizzazione cookie sia avvenuta (vedi log "Cookie sincronizzati (X cookie)"). Premi `Avvia` dopo il login.
|
||||
- "Aste non rilevate": assicurati di essere nella pagina Preferiti per il metodo automatico oppure aggiungi gli URL manualmente.
|
||||
- "Non vedo Click HTTP riuscito": assicurati di aver inserito correttamente il cookie dal browser e che la sessione sia valida.
|
||||
- "Aste non rilevate": aggiungi manualmente gli URL o ID delle aste.
|
||||
- "Il programma non si avvia": verifica .NET 8.0 Runtime installato e che il build sia andato a buon fine.
|
||||
- "Click non funzionano": verifica Timer Click, limiti di prezzo e connessione internet. Controlla se il fallback WebView è attivo nei log.
|
||||
- "Click non funzionano": verifica Timer Click, limiti di prezzo, connessione internet e validità del cookie.
|
||||
|
||||
## Avvisi e responsabilità
|
||||
- L'automazione potrebbe violare i Termini di Servizio di Bidoo. L'uso è a rischio e responsabilità dell'utente.
|
||||
- Non salvare credenziali su disco: l'app non memorizza login, usa il WebView2 per la sessione.
|
||||
- Non salvare credenziali su disco: l'app non memorizza login, usa solo il cookie manuale.
|
||||
- Cookie e dati di sessione rimangono in memoria e vengono rimossi alla chiusura dell'app.
|
||||
|
||||
## Changelog sintetico (ultime versioni)
|
||||
- v3.0: Solo HTTP, nessun WebView2, cookie manuale, interfaccia unica su griglia
|
||||
- v2.10: Click HTTP diretto, sincronizzazione cookie automatica, miglioramenti prestazionali
|
||||
- v2.9: Persistenza automatica, UI improvements, export CSV
|
||||
- v2.8: Polling adattivo e strategie ibride
|
||||
@@ -141,13 +126,13 @@ Sommario
|
||||
---
|
||||
|
||||
Note tecniche per sviluppatori
|
||||
- Progetto target: `.NET 8.0` (WPF + WebView2)
|
||||
- Progetto target: `.NET 8.0` (WPF)
|
||||
- Aree chiave del codice:
|
||||
- `Services\\BidooApiClient.cs` — gestione Click HTTP e parsing risposte
|
||||
- `Services\\AuctionMonitor.cs` — loop di polling e logica auto-switch
|
||||
- `Services\\SessionManager.cs` — sincronizzazione cookie e HttpClient creation
|
||||
- `Utilities\\PersistenceManager.cs` — salvataggio/ricaricamento `auctions.json`
|
||||
- `ViewModels\\AuctionViewModel.cs` + XAML corrispondenti — visualizzazione e binding UI
|
||||
- `Services\BidooApiClient.cs` — gestione Click HTTP e parsing risposte
|
||||
- `Services\AuctionMonitor.cs` — loop di polling
|
||||
- `Services\SessionManager.cs` — gestione cookie manuale
|
||||
- `Utilities\PersistenceManager.cs` — salvataggio/ricaricamento `auctions.json`
|
||||
- `ViewModels\AuctionViewModel.cs` + XAML corrispondenti — visualizzazione e binding UI
|
||||
|
||||
## Contributi
|
||||
- Questo repository è privato. Per contribuire, aprire PR verso branch `main` e seguire le convenzioni del progetto.
|
||||
|
||||
Reference in New Issue
Block a user