Compare commits
2 Commits
fef7b909e7
...
cb30e1fb08
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cb30e1fb08 | ||
|
|
717dc44b3b |
@@ -5,9 +5,25 @@
|
|||||||
StartupUri="MainWindow.xaml">
|
StartupUri="MainWindow.xaml">
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
<ResourceDictionary.MergedDictionaries>
|
<!-- Stile pulsanti globale -->
|
||||||
<ResourceDictionary Source="Converters/Converters.xaml" />
|
<Style x:Key="SmallButtonStyle" TargetType="Button">
|
||||||
</ResourceDictionary.MergedDictionaries>
|
<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>
|
</ResourceDictionary>
|
||||||
</Application.Resources>
|
</Application.Resources>
|
||||||
</Application>
|
</Application>
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
[Binary ICO placeholder removed in this environment]
|
|
||||||
@@ -11,13 +11,22 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<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" />
|
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6584" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<!-- Ensure the application icon is included as a WPF Resource so Icon="Assets/app.ico" resolves -->
|
<Resource Include="Icon\favicon.ico" />
|
||||||
<Resource Include="Assets\app.ico" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</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"
|
<Window x:Class="AutoBidder.MainWindow"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:local="clr-namespace:AutoBidder.Converters"
|
|
||||||
Title="AutoBidder v3.0" Height="800" Width="1400"
|
Title="AutoBidder v3.0" Height="800" Width="1400"
|
||||||
Background="#0a0a0a" Foreground="#FFFFFF"
|
Background="#0a0a0a" Foreground="#FFFFFF"
|
||||||
WindowStartupLocation="CenterScreen">
|
WindowStartupLocation="CenterScreen"
|
||||||
|
Icon="pack://application:,,,/Icon/favicon.ico">
|
||||||
<Window.Resources>
|
<Window.Resources>
|
||||||
<!-- Stile pulsanti stile vecchia versione -->
|
<!-- Stile pulsanti stile vecchia versione -->
|
||||||
<Style x:Key="MainButtonStyle" TargetType="Button">
|
<Style x:Key="MainButtonStyle" TargetType="Button">
|
||||||
@@ -27,24 +26,127 @@
|
|||||||
</Setter>
|
</Setter>
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
<Style x:Key="SmallButtonStyle" TargetType="Button">
|
<!-- Grid action button styles (enable/opacity based on auction state) -->
|
||||||
<Setter Property="Foreground" Value="White" />
|
<Style x:Key="GridStartButtonStyle" TargetType="Button" BasedOn="{StaticResource SmallButtonStyle}">
|
||||||
<Setter Property="FontWeight" Value="SemiBold" />
|
<Setter Property="IsEnabled" Value="True" />
|
||||||
<Setter Property="FontSize" Value="12" />
|
<Setter Property="Opacity" Value="1" />
|
||||||
<Setter Property="BorderThickness" Value="0" />
|
<Style.Triggers>
|
||||||
<Setter Property="Cursor" Value="Hand" />
|
<MultiDataTrigger>
|
||||||
<Setter Property="Template">
|
<MultiDataTrigger.Conditions>
|
||||||
<Setter.Value>
|
<Condition Binding="{Binding IsActive}" Value="True" />
|
||||||
<ControlTemplate TargetType="Button">
|
<Condition Binding="{Binding IsPaused}" Value="False" />
|
||||||
<Border Background="{TemplateBinding Background}"
|
</MultiDataTrigger.Conditions>
|
||||||
CornerRadius="6"
|
<Setter Property="IsEnabled" Value="False" />
|
||||||
Padding="{TemplateBinding Padding}">
|
<Setter Property="Opacity" Value="0.5" />
|
||||||
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" />
|
</MultiDataTrigger>
|
||||||
</Border>
|
<Trigger Property="IsEnabled" Value="False">
|
||||||
</ControlTemplate>
|
<Trigger.EnterActions>
|
||||||
</Setter.Value>
|
<BeginStoryboard>
|
||||||
</Setter>
|
<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>
|
||||||
|
|
||||||
|
<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>
|
</Window.Resources>
|
||||||
|
|
||||||
<Grid Margin="12" Background="#0a0a0a">
|
<Grid Margin="12" Background="#0a0a0a">
|
||||||
@@ -60,42 +162,39 @@
|
|||||||
<Border Grid.Row="0" Background="#1a1a1a" Padding="12" CornerRadius="8" Margin="0,0,0,12">
|
<Border Grid.Row="0" Background="#1a1a1a" Padding="12" CornerRadius="8" Margin="0,0,0,12">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
<!-- Sinistra: Utente, Puntate, Aste da confermare -->
|
||||||
<!-- Info Utente -->
|
<StackPanel Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left">
|
||||||
<StackPanel Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Center" Margin="0,0,20,0">
|
<StackPanel Orientation="Horizontal" Margin="0,0,0,2">
|
||||||
<TextBlock Text="User" FontSize="16" Margin="0,0,8,0" Foreground="#00CC66" FontWeight="Bold" />
|
<TextBlock Text="Utente: " FontSize="14" Foreground="#00CC66" FontWeight="Bold" />
|
||||||
<TextBlock x:Name="UsernameText" Text="Non configurato" FontSize="13" FontWeight="Bold" Foreground="#00CC66" VerticalAlignment="Center" />
|
<TextBlock x:Name="UsernameText" Text="Non configurato" FontSize="14" FontWeight="Bold" Foreground="#00CC66" Width="160" TextTrimming="CharacterEllipsis" />
|
||||||
<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" />
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
<StackPanel Orientation="Horizontal" Margin="0,0,0,2">
|
||||||
<!-- Pulsanti Centrali -->
|
<TextBlock Text="Puntate: " FontSize="14" Foreground="#666" FontWeight="Bold" />
|
||||||
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
|
<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>
|
||||||
|
<!-- Destra: Pulsanti globali -->
|
||||||
|
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
|
||||||
<Button x:Name="ConfigSessionButton" Content="Configura" Click="ConfigSessionButton_Click"
|
<Button x:Name="ConfigSessionButton" Content="Configura" Click="ConfigSessionButton_Click"
|
||||||
Style="{StaticResource SmallButtonStyle}"
|
Style="{StaticResource SmallButtonStyle}"
|
||||||
Background="#8B5CF6" Padding="20,10" Margin="0,0,6,0" Height="40" MinWidth="110" />
|
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}"
|
Style="{StaticResource SmallButtonStyle}"
|
||||||
Background="#00CC66" Padding="20,10" Margin="0,0,6,0" Height="40" MinWidth="110" />
|
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}"
|
Style="{StaticResource SmallButtonStyle}"
|
||||||
Background="#FF9933" Padding="20,10" Margin="0,0,6,0" Height="40" MinWidth="110" />
|
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}"
|
Style="{StaticResource SmallButtonStyle}"
|
||||||
Background="#CC0000" Padding="20,10" Opacity="0.5" Height="40" MinWidth="110" />
|
Background="#CC0000" Padding="20,10" Opacity="0.5" Height="40" MinWidth="110" />
|
||||||
</StackPanel>
|
</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>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
@@ -120,7 +219,7 @@
|
|||||||
|
|
||||||
<!-- Header con titolo e pulsanti -->
|
<!-- Header con titolo e pulsanti -->
|
||||||
<Grid Grid.Row="0" Margin="0,0,0,12">
|
<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">
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||||
<Button x:Name="AddUrlButton" Content="+ Aggiungi" Click="AddUrlButton_Click"
|
<Button x:Name="AddUrlButton" Content="+ Aggiungi" Click="AddUrlButton_Click"
|
||||||
Style="{StaticResource SmallButtonStyle}"
|
Style="{StaticResource SmallButtonStyle}"
|
||||||
@@ -182,6 +281,7 @@
|
|||||||
</DataGrid.Resources>
|
</DataGrid.Resources>
|
||||||
<DataGrid.Columns>
|
<DataGrid.Columns>
|
||||||
<DataGridTextColumn Header="Asta" Binding="{Binding Name, Mode=OneWay}" Width="2*" IsReadOnly="True" />
|
<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="Stato" Binding="{Binding StatusDisplay, Mode=OneWay}" Width="90" IsReadOnly="True" />
|
||||||
<DataGridTextColumn Header="Timer" Binding="{Binding TimerDisplay, Mode=OneWay}" Width="70" 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" />
|
<DataGridTextColumn Header="Prezzo" Binding="{Binding PriceDisplay, Mode=OneWay}" Width="85" IsReadOnly="True" />
|
||||||
@@ -192,38 +292,10 @@
|
|||||||
<DataGridTemplateColumn.CellTemplate>
|
<DataGridTemplateColumn.CellTemplate>
|
||||||
<DataTemplate>
|
<DataTemplate>
|
||||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
<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 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.Opacity>
|
<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" />
|
||||||
<MultiBinding Converter="{StaticResource StartButtonOpacityConverter}">
|
<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" />
|
||||||
<Binding Path="IsActive" />
|
<Button Content="Punta" Command="{Binding DataContext.GridBidCommand, RelativeSource={RelativeSource AncestorType=Window}}" CommandParameter="{Binding}" Style="{StaticResource GridManualBidButtonStyle}" Background="#8B5CF6" Padding="6,2" MinWidth="40" />
|
||||||
<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" />
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</DataGridTemplateColumn.CellTemplate>
|
</DataGridTemplateColumn.CellTemplate>
|
||||||
@@ -295,7 +367,7 @@
|
|||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
</Grid.ColumnDefinitions>
|
</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" />
|
<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" />
|
<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>
|
</Grid>
|
||||||
|
|
||||||
@@ -341,13 +413,9 @@
|
|||||||
<UniformGrid Columns="2" Margin="0,0,0,12">
|
<UniformGrid Columns="2" Margin="0,0,0,12">
|
||||||
<StackPanel Margin="0,0,4,0">
|
<StackPanel Margin="0,0,4,0">
|
||||||
<TextBlock Text="Max Clicks (0=inf)" FontSize="10" Margin="0,0,0,4" Foreground="#999" />
|
<TextBlock Text="Max Clicks (0=inf)" FontSize="10" Margin="0,0,0,4" Foreground="#999" />
|
||||||
<TextBox x:Name="SelectedMaxClicks" Text="0" TextChanged="SelectedMaxClicks_TextChanged"
|
<TextBox x:Name="SelectedMaxClicks" Text="0" TextChanged="SelectedMaxClicks_TextChanged" Background="#1a1a1a" Foreground="#FFF" Padding="8" FontSize="12" BorderBrush="#444" BorderThickness="1" />
|
||||||
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" />
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
<StackPanel />
|
||||||
</UniformGrid>
|
</UniformGrid>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
using System;
|
using System.Collections.ObjectModel;
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.Web.WebView2.Core;
|
|
||||||
using AutoBidder.Models;
|
using AutoBidder.Models;
|
||||||
using AutoBidder.Services;
|
using AutoBidder.Services;
|
||||||
using AutoBidder.ViewModels;
|
using AutoBidder.ViewModels;
|
||||||
using AutoBidder.Utilities;
|
using AutoBidder.Utilities;
|
||||||
using Microsoft.Win32;
|
using AutoBidder.Dialogs;
|
||||||
|
|
||||||
namespace AutoBidder
|
namespace AutoBidder
|
||||||
{
|
{
|
||||||
@@ -24,11 +20,23 @@ namespace AutoBidder
|
|||||||
// SERVIZI CORE
|
// SERVIZI CORE
|
||||||
private readonly AuctionMonitor _auctionMonitor;
|
private readonly AuctionMonitor _auctionMonitor;
|
||||||
private readonly ObservableCollection<AuctionViewModel> _auctionViewModels = new();
|
private readonly ObservableCollection<AuctionViewModel> _auctionViewModels = new();
|
||||||
|
|
||||||
// UI State
|
// UI State
|
||||||
private AuctionViewModel? _selectedAuction;
|
private AuctionViewModel? _selectedAuction;
|
||||||
private bool _isAutomationActive = false;
|
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()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
@@ -37,6 +45,18 @@ namespace AutoBidder
|
|||||||
// Inizializza servizi
|
// Inizializza servizi
|
||||||
_auctionMonitor = new AuctionMonitor();
|
_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
|
// Event handlers
|
||||||
_auctionMonitor.OnAuctionUpdated += AuctionMonitor_OnAuctionUpdated;
|
_auctionMonitor.OnAuctionUpdated += AuctionMonitor_OnAuctionUpdated;
|
||||||
_auctionMonitor.OnBidExecuted += AuctionMonitor_OnBidExecuted;
|
_auctionMonitor.OnBidExecuted += AuctionMonitor_OnBidExecuted;
|
||||||
@@ -53,7 +73,75 @@ namespace AutoBidder
|
|||||||
UpdateGlobalControlButtons();
|
UpdateGlobalControlButtons();
|
||||||
|
|
||||||
Log("[OK] AutoBidder v4.0 avviato (API-based)");
|
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)
|
private void AuctionMonitor_OnAuctionUpdated(AuctionState state)
|
||||||
@@ -116,8 +204,7 @@ namespace AutoBidder
|
|||||||
{
|
{
|
||||||
// Carica sessione esistente (se presente)
|
// Carica sessione esistente (se presente)
|
||||||
var currentSession = _auctionMonitor.GetSession();
|
var currentSession = _auctionMonitor.GetSession();
|
||||||
string existingToken = "";
|
string existingToken = string.Empty;
|
||||||
string existingUsername = "";
|
|
||||||
|
|
||||||
if (currentSession != null)
|
if (currentSession != null)
|
||||||
{
|
{
|
||||||
@@ -130,11 +217,9 @@ namespace AutoBidder
|
|||||||
{
|
{
|
||||||
existingToken = currentSession.AuthToken;
|
existingToken = currentSession.AuthToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
existingUsername = currentSession.Username ?? "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var dialog = new SessionDialog(existingToken, existingUsername);
|
var dialog = new SessionDialog(existingToken);
|
||||||
if (dialog.ShowDialog() == true)
|
if (dialog.ShowDialog() == true)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -142,32 +227,43 @@ namespace AutoBidder
|
|||||||
// Usa InitializeSessionWithCookie invece di InitializeSession
|
// Usa InitializeSessionWithCookie invece di InitializeSession
|
||||||
// perché il token è __stattrb (non __stattr)
|
// perché il token è __stattrb (non __stattr)
|
||||||
var cookieString = $"__stattrb={dialog.AuthToken}";
|
var cookieString = $"__stattrb={dialog.AuthToken}";
|
||||||
_auctionMonitor.InitializeSessionWithCookie(cookieString, dialog.Username);
|
_auctionMonitor.InitializeSessionWithCookie(cookieString, ""); // Username non più passato
|
||||||
|
|
||||||
UsernameText.Text = dialog.Username ?? string.Empty;
|
|
||||||
StartButton.IsEnabled = true;
|
StartButton.IsEnabled = true;
|
||||||
|
|
||||||
// Salva sessione in modo sicuro (crittografata)
|
// Salva sessione in modo sicuro (crittografata)
|
||||||
var session = _auctionMonitor.GetSession();
|
var session = _auctionMonitor.GetSession();
|
||||||
SessionManager.SaveSession(session);
|
SessionManager.SaveSession(session);
|
||||||
|
|
||||||
Log($"Sessione configurata per: {dialog.Username}");
|
Log($"Sessione configurata");
|
||||||
Log($"Cookie salvato in modo sicuro");
|
Log($"Cookie salvato in modo sicuro");
|
||||||
|
|
||||||
// Aggiorna info utente
|
// Aggiorna info utente (nome e puntate) tramite chiamata automatica
|
||||||
Task.Run(() =>
|
Task.Run(async () =>
|
||||||
|
{
|
||||||
|
var userData = await _auctionMonitor.GetUserDataAsync();
|
||||||
|
if (userData != null)
|
||||||
{
|
{
|
||||||
_auctionMonitor.UpdateUserInfoAsync().GetAwaiter().GetResult();
|
|
||||||
var updatedSession = _auctionMonitor.GetSession();
|
|
||||||
Dispatcher.Invoke(() =>
|
Dispatcher.Invoke(() =>
|
||||||
{
|
{
|
||||||
RemainingBidsText.Text = updatedSession.RemainingBids.ToString();
|
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)
|
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);
|
MessageBox.Show($"Errore: {ex.Message}", "Errore", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -248,24 +344,6 @@ namespace AutoBidder
|
|||||||
MessageBox.Show("Usa i pulsanti Pausa/Riprendi per asta", "Info", MessageBoxButton.OK, MessageBoxImage.Information);
|
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)
|
private async void AddUrlButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var dialog = new AddAuctionSimpleDialog();
|
var dialog = new AddAuctionSimpleDialog();
|
||||||
@@ -402,7 +480,8 @@ namespace AutoBidder
|
|||||||
{
|
{
|
||||||
if (int.TryParse(tb.Text, out var value) && value >= 0)
|
if (int.TryParse(tb.Text, out var value) && value >= 0)
|
||||||
{
|
{
|
||||||
_selectedAuction.AuctionInfo.MaxClicks = value;
|
_selectedAuction.MaxClicks = value;
|
||||||
|
SaveAuctions(); // Persist change immediately
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -511,13 +590,13 @@ namespace AutoBidder
|
|||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
productName = ExtractProductName(originalUrl);
|
productName = ExtractProductName(originalUrl) ?? string.Empty;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// È solo un ID numerico - costruisci URL generico
|
// È solo un ID numerico - costruisci URL generico
|
||||||
auctionId = input.Trim();
|
auctionId = input.Trim();
|
||||||
productName = null;
|
productName = string.Empty;
|
||||||
originalUrl = $"https://it.bidoo.com/auction.php?a=asta_{auctionId}";
|
originalUrl = $"https://it.bidoo.com/auction.php?a=asta_{auctionId}";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -554,7 +633,7 @@ namespace AutoBidder
|
|||||||
SaveAuctions();
|
SaveAuctions();
|
||||||
UpdateTotalCount();
|
UpdateTotalCount();
|
||||||
|
|
||||||
Log($"[+] Asta aggiunta: {displayName}");
|
// AuctionMonitor already emits a global log entry for added auctions
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -593,7 +672,8 @@ namespace AutoBidder
|
|||||||
var name = $"Asta {auctionId}";
|
var name = $"Asta {auctionId}";
|
||||||
try
|
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>");
|
var match = System.Text.RegularExpressions.Regex.Match(html, @"<title>([^<]+)</title>");
|
||||||
if (match.Success)
|
if (match.Success)
|
||||||
{
|
{
|
||||||
@@ -623,7 +703,7 @@ namespace AutoBidder
|
|||||||
SaveAuctions();
|
SaveAuctions();
|
||||||
UpdateTotalCount();
|
UpdateTotalCount();
|
||||||
|
|
||||||
Log($"[+] Asta aggiunta: {name}");
|
// AuctionMonitor already emits a global log entry for added auctions
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -753,18 +833,16 @@ namespace AutoBidder
|
|||||||
SelectedMaxPrice.Text = auction.MaxPrice.ToString();
|
SelectedMaxPrice.Text = auction.MaxPrice.ToString();
|
||||||
SelectedMinResets.Text = auction.AuctionInfo.MinResets.ToString();
|
SelectedMinResets.Text = auction.AuctionInfo.MinResets.ToString();
|
||||||
SelectedMaxResets.Text = auction.AuctionInfo.MaxResets.ToString();
|
SelectedMaxResets.Text = auction.AuctionInfo.MaxResets.ToString();
|
||||||
|
SelectedMaxClicks.Text = auction.MaxClicks.ToString();
|
||||||
// Mostra il link dell'asta selezionata
|
// Mostra il link dell'asta selezionata
|
||||||
var url = auction.AuctionInfo.OriginalUrl;
|
var url = auction.AuctionInfo.OriginalUrl;
|
||||||
if (string.IsNullOrEmpty(url))
|
if (string.IsNullOrEmpty(url))
|
||||||
url = $"https://it.bidoo.com/auction.php?a=asta_{auction.AuctionId}";
|
url = $"https://it.bidoo.com/auction.php?a=asta_{auction.AuctionId}";
|
||||||
SelectedAuctionUrl.Text = url;
|
SelectedAuctionUrl.Text = url;
|
||||||
|
|
||||||
// Abilita solo i pulsanti rimasti
|
// Abilita solo i pulsanti rimasti
|
||||||
ResetSettingsButton.IsEnabled = true;
|
ResetSettingsButton.IsEnabled = true;
|
||||||
ClearBiddersButton.IsEnabled = true;
|
ClearBiddersButton.IsEnabled = true;
|
||||||
ClearLogButton.IsEnabled = true;
|
ClearLogButton.IsEnabled = true;
|
||||||
|
|
||||||
// Aggiorna log asta
|
// Aggiorna log asta
|
||||||
UpdateAuctionLog(auction);
|
UpdateAuctionLog(auction);
|
||||||
// Aggiorna bidders con metodo dedicato
|
// Aggiorna bidders con metodo dedicato
|
||||||
@@ -773,43 +851,14 @@ namespace AutoBidder
|
|||||||
catch { }
|
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)
|
private void UpdateAuctionLog(AuctionViewModel auction)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var auctionInfo = auction.AuctionInfo;
|
var auctionInfo = auction.AuctionInfo;
|
||||||
|
var logBox = SelectedAuctionLog;
|
||||||
SelectedAuctionLog.Document.Blocks.Clear();
|
var doc = logBox.Document;
|
||||||
|
doc.Blocks.Clear();
|
||||||
foreach (var entry in auctionInfo.AuctionLog)
|
foreach (var entry in auctionInfo.AuctionLog)
|
||||||
{
|
{
|
||||||
var upper = entry.ToUpperInvariant();
|
var upper = entry.ToUpperInvariant();
|
||||||
@@ -818,14 +867,21 @@ namespace AutoBidder
|
|||||||
color = System.Windows.Media.Brushes.IndianRed;
|
color = System.Windows.Media.Brushes.IndianRed;
|
||||||
else if (upper.Contains("[WARN]") || upper.Contains("WARN"))
|
else if (upper.Contains("[WARN]") || upper.Contains("WARN"))
|
||||||
color = System.Windows.Media.Brushes.Orange;
|
color = System.Windows.Media.Brushes.Orange;
|
||||||
|
|
||||||
var p = new System.Windows.Documents.Paragraph { Margin = new Thickness(0) };
|
var p = new System.Windows.Documents.Paragraph { Margin = new Thickness(0) };
|
||||||
var r = new System.Windows.Documents.Run(entry) { Foreground = color };
|
var r = new System.Windows.Documents.Run(entry) { Foreground = color };
|
||||||
p.Inlines.Add(r);
|
p.Inlines.Add(r);
|
||||||
SelectedAuctionLog.Document.Blocks.Add(p);
|
doc.Blocks.Add(p);
|
||||||
}
|
}
|
||||||
|
// Scroll only if user is already near the bottom
|
||||||
SelectedAuctionLog.ScrollToEnd();
|
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 { }
|
catch { }
|
||||||
}
|
}
|
||||||
@@ -840,7 +896,7 @@ namespace AutoBidder
|
|||||||
|
|
||||||
SelectedAuctionBiddersGrid.ItemsSource = null; // Force refresh
|
SelectedAuctionBiddersGrid.ItemsSource = null; // Force refresh
|
||||||
SelectedAuctionBiddersGrid.ItemsSource = bidders;
|
SelectedAuctionBiddersGrid.ItemsSource = bidders;
|
||||||
SelectedAuctionBiddersCount.Text = bidders.Count.ToString();
|
SelectedAuctionBiddersCount.Text = bidders?.Count.ToString() ?? "0";
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
@@ -853,7 +909,7 @@ namespace AutoBidder
|
|||||||
|
|
||||||
private void UpdateTotalCount()
|
private void UpdateTotalCount()
|
||||||
{
|
{
|
||||||
TotalAuctionsText.Text = _auctionViewModels.Count.ToString();
|
MonitorateTitle.Text = $"Aste monitorate: {_auctionViewModels.Count}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void LoadSavedAuctions()
|
private void LoadSavedAuctions()
|
||||||
@@ -861,28 +917,25 @@ namespace AutoBidder
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var auctions = PersistenceManager.LoadAuctions();
|
var auctions = PersistenceManager.LoadAuctions();
|
||||||
|
|
||||||
foreach (var auction in auctions)
|
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);
|
_auctionMonitor.AddAuction(auction);
|
||||||
var vm = new AuctionViewModel(auction);
|
var vm = new AuctionViewModel(auction);
|
||||||
_auctionViewModels.Add(vm);
|
_auctionViewModels.Add(vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
// On startup treat persisted auctions as stopped (user expectation)
|
// On startup treat persisted auctions as stopped (user expectation)
|
||||||
foreach (var vm in _auctionViewModels)
|
foreach (var vm in _auctionViewModels)
|
||||||
{
|
{
|
||||||
vm.IsActive = false;
|
vm.IsActive = false;
|
||||||
vm.IsPaused = false;
|
vm.IsPaused = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateTotalCount();
|
UpdateTotalCount();
|
||||||
|
|
||||||
if (auctions.Count > 0)
|
if (auctions.Count > 0)
|
||||||
{
|
{
|
||||||
Log($"[OK] Caricate {auctions.Count} aste salvate");
|
Log($"[OK] Caricate {auctions.Count} aste salvate");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Carica sessione salvata (se esiste)
|
// Carica sessione salvata (se esiste)
|
||||||
LoadSavedSession();
|
LoadSavedSession();
|
||||||
}
|
}
|
||||||
@@ -933,12 +986,24 @@ namespace AutoBidder
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Log($"[WARN] Cookie potrebbe essere scaduto - Riconfigura sessione");
|
// Secondary fallback: try scraping user data from HTML to ensure cookie is truly invalid
|
||||||
MessageBox.Show(
|
var htmlUser = _auctionMonitor.GetUserDataFromHtmlAsync().GetAwaiter().GetResult();
|
||||||
"Il cookie salvato potrebbe essere scaduto.\nRiconfigura la sessione con 'Configura Sessione'.",
|
if (htmlUser != null)
|
||||||
"Sessione Scaduta",
|
{
|
||||||
MessageBoxButton.OK,
|
Dispatcher.Invoke(() =>
|
||||||
MessageBoxImage.Warning);
|
{
|
||||||
|
SetUserBanner(htmlUser.Username, htmlUser.RemainingBids);
|
||||||
|
Log($"[OK] Dati utente rilevati via HTML - Utente: {htmlUser.Username}, Puntate residue: {htmlUser.RemainingBids}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Both checks failed: log a warning but do NOT show intrusive MessageBox on startup
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
Log($"[WARN] Impossibile verificare sessione: cookie non valido o rete assente. Riconfigura la sessione se persistono i problemi.");
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -994,7 +1059,6 @@ namespace AutoBidder
|
|||||||
else
|
else
|
||||||
level = LogLevel.Info;
|
level = LogLevel.Info;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dispatcher.BeginInvoke(() =>
|
Dispatcher.BeginInvoke(() =>
|
||||||
{
|
{
|
||||||
var para = new System.Windows.Documents.Paragraph();
|
var para = new System.Windows.Documents.Paragraph();
|
||||||
@@ -1015,6 +1079,11 @@ namespace AutoBidder
|
|||||||
}
|
}
|
||||||
para.Inlines.Add(run);
|
para.Inlines.Add(run);
|
||||||
LogBox.Document.Blocks.Add(para);
|
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();
|
LogBox.ScrollToEnd();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -1169,67 +1238,59 @@ namespace AutoBidder
|
|||||||
StopButton.IsEnabled = true; StopButton.Opacity = 1.0;
|
StopButton.IsEnabled = true; StopButton.Opacity = 1.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async void UserBannerTimer_Tick(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
await UpdateUserBannerInfoAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== DIALOG HELPER =====
|
private async Task UpdateUserBannerInfoAsync()
|
||||||
|
|
||||||
public class AddAuctionDialog : Window
|
|
||||||
{
|
{
|
||||||
private readonly TextBox _urlTextBox;
|
var info = await _auctionMonitor.GetUserBannerInfoAsync();
|
||||||
public string AuctionUrl => _urlTextBox.Text.Trim();
|
if (info != null)
|
||||||
|
|
||||||
public AddAuctionDialog()
|
|
||||||
{
|
{
|
||||||
Title = "Aggiungi Asta";
|
BannerAsteDaRiscattare.Text = $"{info.nAsteVinte - info.nAsteConfermate}";
|
||||||
Width = 500;
|
// BannerPuntateBonus.Text = $"Bonus: {info.nPuntateBonus}"; // RIMOSSO
|
||||||
Height = 150;
|
}
|
||||||
WindowStartupLocation = WindowStartupLocation.CenterOwner;
|
else
|
||||||
ResizeMode = ResizeMode.NoResize;
|
{
|
||||||
|
BannerAsteDaRiscattare.Text = "--";
|
||||||
var grid = new Grid { Margin = new Thickness(20) };
|
// BannerPuntateBonus.Text = "Bonus: --"; // RIMOSSO
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== HttpClient Helper =====
|
private async void UserHtmlTimer_Tick(object? sender, EventArgs e)
|
||||||
internal static class HttpClientProvider
|
|
||||||
{
|
{
|
||||||
private static readonly System.Net.Http.HttpClient _httpClient = new();
|
await UpdateUserHtmlInfoAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task<string> GetStringAsync(string url)
|
private async Task UpdateUserHtmlInfoAsync()
|
||||||
{
|
{
|
||||||
return await _httpClient.GetStringAsync(url);
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace AutoBidder.Models
|
namespace AutoBidder.Models
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Informazioni base di un'asta monitorata
|
/// Informazioni base di un'asta monitorata
|
||||||
|
/// Solo HTTP, nessuna modalità, browser o multi-click
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AuctionInfo
|
public class AuctionInfo
|
||||||
{
|
{
|
||||||
@@ -19,15 +22,14 @@ namespace AutoBidder.Models
|
|||||||
public double MaxPrice { get; set; } = 0;
|
public double MaxPrice { get; set; } = 0;
|
||||||
public int MinResets { get; set; } = 0; // Numero minimo reset prima di puntare
|
public int MinResets { get; set; } = 0; // Numero minimo reset prima di puntare
|
||||||
public int MaxResets { get; set; } = 0; // Numero massimo reset (0 = illimitati)
|
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)
|
[JsonPropertyName("MaxClicks")]
|
||||||
public int MaxClicks { get; set; } = 0;
|
public int MaxClicks { get; set; } = 0; // Numero massimo di puntate consentite (0 = illimitato)
|
||||||
|
|
||||||
// Stato asta
|
// Stato asta
|
||||||
public bool IsActive { get; set; } = true;
|
public bool IsActive { get; set; } = true;
|
||||||
public bool IsPaused { get; set; } = false;
|
public bool IsPaused { get; set; } = false;
|
||||||
|
|
||||||
// Contatori
|
// Contatori
|
||||||
public int MyClicks { get; set; } = 0;
|
|
||||||
public int ResetCount { get; set; } = 0;
|
public int ResetCount { get; set; } = 0;
|
||||||
|
|
||||||
// Timestamp
|
// Timestamp
|
||||||
@@ -35,17 +37,19 @@ namespace AutoBidder.Models
|
|||||||
public DateTime? LastClickAt { get; set; }
|
public DateTime? LastClickAt { get; set; }
|
||||||
|
|
||||||
// Storico
|
// 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);
|
public Dictionary<string, BidderInfo> BidderStats { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
// Legacy (deprecato, usa BidderStats)
|
// Legacy (deprecato) - removed `Bidders` dictionary; use `BidderStats` instead
|
||||||
[System.Text.Json.Serialization.JsonIgnore]
|
|
||||||
public Dictionary<string, int> Bidders { get; set; } = new(StringComparer.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
// Log per-asta (non serializzato)
|
// Log per-asta (non serializzato)
|
||||||
[System.Text.Json.Serialization.JsonIgnore]
|
[System.Text.Json.Serialization.JsonIgnore]
|
||||||
public List<string> AuctionLog { get; set; } = new();
|
public List<string> AuctionLog { get; set; } = new();
|
||||||
|
|
||||||
|
// Flag runtime: indica che è in corso un'operazione di final attack per questa asta
|
||||||
|
[System.Text.Json.Serialization.JsonIgnore]
|
||||||
|
public bool IsAttackInProgress { get; set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Aggiunge una voce al log dell'asta
|
/// Aggiunge una voce al log dell'asta
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -54,11 +58,13 @@ namespace AutoBidder.Models
|
|||||||
var entry = $"{DateTime.Now:HH:mm:ss} - {message}";
|
var entry = $"{DateTime.Now:HH:mm:ss} - {message}";
|
||||||
AuctionLog.Add(entry);
|
AuctionLog.Add(entry);
|
||||||
|
|
||||||
// Mantieni solo ultimi 100 log
|
// Mantieni solo ultimi 500 log
|
||||||
if (AuctionLog.Count > 100)
|
if (AuctionLog.Count > 500)
|
||||||
{
|
{
|
||||||
AuctionLog.RemoveAt(0);
|
AuctionLog.RemoveAt(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int PollingLatencyMs { get; set; } = 0; // Ultima latenza polling ms
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,10 +61,12 @@ namespace AutoBidder.Models
|
|||||||
Name = auction.Name,
|
Name = auction.Name,
|
||||||
MonitoringStarted = auction.AddedAt,
|
MonitoringStarted = auction.AddedAt,
|
||||||
MonitoringDuration = DateTime.UtcNow - 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,
|
Resets = auction.ResetCount,
|
||||||
UniqueBidders = auction.Bidders.Count,
|
UniqueBidders = auction.BidderStats?.Count ?? 0,
|
||||||
BidderRanking = auction.Bidders
|
BidderRanking = (auction.BidderStats ?? new Dictionary<string, BidderInfo>())
|
||||||
|
.ToDictionary(k => k.Key, v => v.Value.BidCount)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (auction.BidHistory.Any())
|
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();
|
var top = auction.BidderStats.OrderByDescending(b => b.Value.BidCount).First();
|
||||||
stats.MostActiveBidder = topBidder.Key;
|
stats.MostActiveBidder = top.Key;
|
||||||
stats.MostActiveBidderCount = topBidder.Value;
|
stats.MostActiveBidderCount = top.Value.BidCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
return stats;
|
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
|
namespace AutoBidder.Services
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Servizio centrale per monitoraggio multi-asta
|
/// Servizio centrale per monitoraggio aste
|
||||||
/// Gestisce polling API Bidoo e trigger dei click
|
/// Solo HTTP, nessuna modalità, browser o multi-click
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AuctionMonitor
|
public class AuctionMonitor
|
||||||
{
|
{
|
||||||
@@ -79,6 +79,30 @@ namespace AutoBidder.Services
|
|||||||
return _apiClient.GetSession();
|
return _apiClient.GetSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene dati utente (nome, puntate residue, saldo, id) tramite chiamata AJAX leggera
|
||||||
|
/// </summary>
|
||||||
|
public async Task<UserData?> GetUserDataAsync()
|
||||||
|
{
|
||||||
|
return await _apiClient.GetUserDataAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ottiene info banner utente (aste vinte, bonus, ecc.) tramite chiamata AJAX
|
||||||
|
/// </summary>
|
||||||
|
public async Task<UserBannerInfo?> GetUserBannerInfoAsync()
|
||||||
|
{
|
||||||
|
return await _apiClient.GetUserBannerInfoAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Estrae nome utente e puntate residue dall'HTML di bids_history.php
|
||||||
|
/// </summary>
|
||||||
|
public async Task<UserData?> GetUserDataFromHtmlAsync()
|
||||||
|
{
|
||||||
|
return await _apiClient.GetUserDataFromHtmlAsync();
|
||||||
|
}
|
||||||
|
|
||||||
public void AddAuction(AuctionInfo auction)
|
public void AddAuction(AuctionInfo auction)
|
||||||
{
|
{
|
||||||
lock (_auctions)
|
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()
|
public void Start()
|
||||||
{
|
{
|
||||||
if (_monitoringTask != null && !_monitoringTask.IsCompleted)
|
if (_monitoringTask != null && !_monitoringTask.IsCompleted)
|
||||||
@@ -259,6 +277,9 @@ namespace AutoBidder.Services
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Aggiorna la latenza per la DataGrid
|
||||||
|
auction.PollingLatencyMs = state.PollingLatencyMs;
|
||||||
|
|
||||||
// Se l'asta è terminata, segnala e disattiva polling
|
// Se l'asta è terminata, segnala e disattiva polling
|
||||||
if (state.Status == AuctionStatus.EndedWon ||
|
if (state.Status == AuctionStatus.EndedWon ||
|
||||||
state.Status == AuctionStatus.EndedLost ||
|
state.Status == AuctionStatus.EndedLost ||
|
||||||
@@ -289,9 +310,10 @@ namespace AutoBidder.Services
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log stato solo per aste attive (riduci spam)
|
// Log stato solo per aste attive (riduci spam) - keep detailed per-auction log
|
||||||
if (state.Status == AuctionStatus.Running)
|
if (state.Status == AuctionStatus.Running)
|
||||||
{
|
{
|
||||||
|
// Detailed info stays in auction log only
|
||||||
auction.AddLog($"API OK - Timer: {state.Timer:F2}s, EUR{state.Price:F2}, {state.LastBidder}, {state.PollingLatencyMs}ms");
|
auction.AddLog($"API OK - Timer: {state.Timer:F2}s, EUR{state.Price:F2}, {state.LastBidder}, {state.PollingLatencyMs}ms");
|
||||||
}
|
}
|
||||||
else if (state.Status == AuctionStatus.Paused)
|
else if (state.Status == AuctionStatus.Paused)
|
||||||
@@ -305,45 +327,92 @@ namespace AutoBidder.Services
|
|||||||
// Aggiorna storico e bidders
|
// Aggiorna storico e bidders
|
||||||
UpdateAuctionHistory(auction, state);
|
UpdateAuctionHistory(auction, state);
|
||||||
|
|
||||||
// Verifica se puntare (solo se asta Running, NON se in pausa)
|
// FINAL-ATTACK PROTOCOL: when the remaining timer is below our latency threshold (<= 0.5s)
|
||||||
if (state.Status == AuctionStatus.Running && ShouldBid(auction, state) && !auction.IsPaused)
|
// we stop the normal polling loop for this auction and send a single minimal bid request.
|
||||||
|
if (state.Status == AuctionStatus.Running && !auction.IsPaused && ShouldBid(auction, state))
|
||||||
{
|
{
|
||||||
auction.AddLog($"[TRIGGER] CONDIZIONI OK - Timer {state.Timer:F2}s <= {auction.TimerClick}s");
|
// Use latency threshold (0.5s default) - treat as critical window
|
||||||
auction.AddLog($"[BID] Invio puntata...");
|
var latencyThreshold = 0.5; // seconds
|
||||||
OnLog?.Invoke($"[BID] [{auction.AuctionId}] PUNTATA a {state.Timer:F2}s!");
|
if (!auction.IsAttackInProgress && state.Timer <= latencyThreshold)
|
||||||
|
|
||||||
// Attendi ritardo configurato
|
|
||||||
if (auction.DelayMs > 0)
|
|
||||||
{
|
{
|
||||||
await Task.Delay(auction.DelayMs, token);
|
// Enter attack state for this auction to avoid concurrent triggers
|
||||||
}
|
auction.IsAttackInProgress = true;
|
||||||
|
|
||||||
// Esegui puntata API
|
try
|
||||||
var result = await _apiClient.PlaceBidAsync(auction.AuctionId);
|
|
||||||
|
|
||||||
// Aggiorna contatori
|
|
||||||
if (result.Success)
|
|
||||||
{
|
{
|
||||||
auction.MyClicks++;
|
// Log detailed info into auction log
|
||||||
|
auction.AddLog($"[ATTACK] Final attack: Timer {state.Timer:F3}s <= {latencyThreshold}s -> executing final bid...");
|
||||||
|
|
||||||
|
// Place final minimal bid (one GET with auctionID & submit=1)
|
||||||
|
var finalResult = await _apiClient.PlaceBidFinalAsync(auction.AuctionId, auction.OriginalUrl);
|
||||||
|
|
||||||
auction.LastClickAt = DateTime.UtcNow;
|
auction.LastClickAt = DateTime.UtcNow;
|
||||||
|
OnBidExecuted?.Invoke(auction, finalResult);
|
||||||
|
|
||||||
|
if (finalResult.Success)
|
||||||
|
{
|
||||||
|
// Success: log concise global, detailed per-auction
|
||||||
|
auction.AddLog($"[OK] Final bid OK: {finalResult.LatencyMs}ms -> EUR{finalResult.NewPrice:F2}");
|
||||||
|
OnLog?.Invoke($"[OK] Puntata riuscita su {auction.Name} ({auction.AuctionId}): {finalResult.LatencyMs}ms");
|
||||||
|
|
||||||
|
// After success, an expiration may be extended by server (T_exp = now + 8s)
|
||||||
|
// We simply resume normal monitoring to observe new timer
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Failure cases: log and do not attempt immediate retries
|
||||||
|
auction.AddLog($"[FAIL] Final bid failed: {finalResult.Error}");
|
||||||
|
OnLog?.Invoke($"[FAIL] Puntata fallita su {auction.Name} ({auction.AuctionId}): {finalResult.Error}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notifica risultato
|
// Always add history entry
|
||||||
|
auction.BidHistory.Add(new BidHistory
|
||||||
|
{
|
||||||
|
Timestamp = finalResult.Timestamp,
|
||||||
|
EventType = finalResult.Success ? BidEventType.MyBid : BidEventType.OpponentBid,
|
||||||
|
Bidder = "Tu",
|
||||||
|
Price = state.Price,
|
||||||
|
Timer = state.Timer,
|
||||||
|
LatencyMs = finalResult.LatencyMs,
|
||||||
|
Success = finalResult.Success,
|
||||||
|
Notes = finalResult.Success ? $"EUR{finalResult.NewPrice:F2}" : finalResult.Error
|
||||||
|
});
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
auction.IsAttackInProgress = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Done with final attack; skip the older branch below
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise fallback to normal early-bid behavior
|
||||||
|
if (Math.Abs(state.Timer) < 0.001)
|
||||||
|
{
|
||||||
|
// Put detailed info into auction log but avoid noisy global log lines
|
||||||
|
auction.AddLog($"[TRIGGER] Timer 0, attendo delay {auction.DelayMs}ms e invio puntata direttamente...");
|
||||||
|
|
||||||
|
if (auction.DelayMs > 0)
|
||||||
|
await Task.Delay(auction.DelayMs, token);
|
||||||
|
|
||||||
|
// Direct bid - API client already writes detailed request/response into auction.AddLog via subscription
|
||||||
|
var result = await _apiClient.PlaceBidAsync(auction.AuctionId);
|
||||||
|
auction.LastClickAt = DateTime.UtcNow;
|
||||||
OnBidExecuted?.Invoke(auction, result);
|
OnBidExecuted?.Invoke(auction, result);
|
||||||
|
|
||||||
// Log
|
// Add concise global log (single line) and keep extended details inside auction log
|
||||||
if (result.Success)
|
if (result.Success)
|
||||||
{
|
{
|
||||||
auction.AddLog($"[OK] PUNTATA OK: {result.LatencyMs}ms -> EUR{result.NewPrice:F2}");
|
auction.AddLog($"[OK] PUNTATA OK: {result.LatencyMs}ms -> EUR{result.NewPrice:F2}");
|
||||||
OnLog?.Invoke($"[OK] [{auction.AuctionId}] Puntata riuscita {result.LatencyMs}ms");
|
OnLog?.Invoke($"[OK] Puntata riuscita su {auction.Name} ({auction.AuctionId}): {result.LatencyMs}ms");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auction.AddLog($"[FAIL] PUNTATA FALLITA: {result.Error}");
|
auction.AddLog($"[FAIL] PUNTATA FALLITA: {result.Error}");
|
||||||
OnLog?.Invoke($"[FAIL] [{auction.AuctionId}] ERRORE: {result.Error}");
|
OnLog?.Invoke($"[FAIL] Puntata fallita su {auction.Name} ({auction.AuctionId}): {result.Error}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Storico
|
|
||||||
auction.BidHistory.Add(new BidHistory
|
auction.BidHistory.Add(new BidHistory
|
||||||
{
|
{
|
||||||
Timestamp = result.Timestamp,
|
Timestamp = result.Timestamp,
|
||||||
@@ -355,13 +424,43 @@ namespace AutoBidder.Services
|
|||||||
Success = result.Success,
|
Success = result.Success,
|
||||||
Notes = result.Success ? $"EUR{result.NewPrice:F2}" : result.Error
|
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)
|
else
|
||||||
if (auction.MaxClicks > 0 && auction.MyClicks >= auction.MaxClicks)
|
|
||||||
{
|
{
|
||||||
auction.IsPaused = true;
|
// Normal early-bid path: schedule immediate delay then place bid
|
||||||
auction.AddLog($"[PAUSA] Massimo click ({auction.MaxClicks}) raggiunto - Puntate disabilitate");
|
auction.AddLog($"[TRIGGER] CONDIZIONI OK - Timer {state.Timer:F2}s <= {auction.TimerClick}s");
|
||||||
OnLog?.Invoke($"[PAUSA] [{auction.AuctionId}] MaxClicks raggiunti");
|
|
||||||
|
if (auction.DelayMs > 0)
|
||||||
|
{
|
||||||
|
await Task.Delay(auction.DelayMs, token);
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = await _apiClient.PlaceBidAsync(auction.AuctionId);
|
||||||
|
auction.LastClickAt = DateTime.UtcNow;
|
||||||
|
OnBidExecuted?.Invoke(auction, result);
|
||||||
|
|
||||||
|
if (result.Success)
|
||||||
|
{
|
||||||
|
auction.AddLog($"[OK] PUNTATA OK: {result.LatencyMs}ms -> EUR{result.NewPrice:F2}");
|
||||||
|
OnLog?.Invoke($"[OK] Puntata riuscita su {auction.Name} ({auction.AuctionId}): {result.LatencyMs}ms");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auction.AddLog($"[FAIL] PUNTATA FALLITA: {result.Error}");
|
||||||
|
OnLog?.Invoke($"[FAIL] Puntata fallita su {auction.Name} ({auction.AuctionId}): {result.Error}");
|
||||||
|
}
|
||||||
|
|
||||||
|
auction.BidHistory.Add(new BidHistory
|
||||||
|
{
|
||||||
|
Timestamp = result.Timestamp,
|
||||||
|
EventType = result.Success ? BidEventType.MyBid : BidEventType.OpponentBid,
|
||||||
|
Bidder = "Tu",
|
||||||
|
Price = state.Price,
|
||||||
|
Timer = state.Timer,
|
||||||
|
LatencyMs = result.LatencyMs,
|
||||||
|
Success = result.Success,
|
||||||
|
Notes = result.Success ? $"EUR{result.NewPrice:F2}" : result.Error
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -393,10 +492,6 @@ namespace AutoBidder.Services
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Max clicks per auction
|
|
||||||
if (auction.MaxClicks > 0 && auction.MyClicks >= auction.MaxClicks)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,28 +12,7 @@ namespace AutoBidder.Services
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Servizio completo API Bidoo (polling, puntate, info utente)
|
/// Servizio completo API Bidoo (polling, puntate, info utente)
|
||||||
/// 100% API-based con simulazione completa del comportamento browser
|
/// Solo HTTP, nessuna modalità, browser o multi-click
|
||||||
///
|
|
||||||
/// 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)
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class BidooApiClient
|
public class BidooApiClient
|
||||||
{
|
{
|
||||||
@@ -91,14 +70,13 @@ namespace AutoBidder.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Inizializza sessione con cookie string (fallback)
|
/// Inizializza sessione con cookie string (manuale)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void InitializeSessionWithCookie(string cookieString, string username)
|
public void InitializeSessionWithCookie(string cookieString, string username)
|
||||||
{
|
{
|
||||||
_session.CookieString = cookieString;
|
_session.CookieString = cookieString;
|
||||||
_session.Username = username;
|
_session.Username = username;
|
||||||
|
Log($"[SESSION] Cookie impostato manualmente ({cookieString.Length} chars)");
|
||||||
Log($"[SESSION] Cookie impostato ({cookieString.Length} chars)");
|
|
||||||
Log($"[SESSION] Username: {username}");
|
Log($"[SESSION] Username: {username}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,20 +86,12 @@ namespace AutoBidder.Services
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void AddAuthHeaders(HttpRequestMessage request, string? referer = null, string? auctionId = null)
|
private void AddAuthHeaders(HttpRequestMessage request, string? referer = null, string? auctionId = null)
|
||||||
{
|
{
|
||||||
// 1. AUTENTICAZIONE (priorità: CookieString completa, poi Token singolo)
|
// 1. AUTENTICAZIONE (solo cookie manuale)
|
||||||
// Il cookie principale per Bidoo è __stattr (non PHPSESSID)
|
|
||||||
if (!string.IsNullOrWhiteSpace(_session.CookieString))
|
if (!string.IsNullOrWhiteSpace(_session.CookieString))
|
||||||
{
|
{
|
||||||
// Usa la stringa cookie completa (es: "__stattr=eyJyZWZ...")
|
|
||||||
request.Headers.Add("Cookie", _session.CookieString);
|
request.Headers.Add("Cookie", _session.CookieString);
|
||||||
Log("[AUTH] Using full cookie string", auctionId);
|
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
|
else
|
||||||
{
|
{
|
||||||
Log("[AUTH WARN] No authentication method available!", auctionId);
|
Log("[AUTH WARN] No authentication method available!", auctionId);
|
||||||
@@ -244,40 +214,28 @@ namespace AutoBidder.Services
|
|||||||
{
|
{
|
||||||
var startTime = DateTime.UtcNow;
|
var startTime = DateTime.UtcNow;
|
||||||
var url = $"https://it.bidoo.com/data.php?ALL={auctionId}&LISTID=0";
|
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 request = new HttpRequestMessage(HttpMethod.Get, url);
|
||||||
|
|
||||||
var referer = !string.IsNullOrEmpty(auctionUrl)
|
var referer = !string.IsNullOrEmpty(auctionUrl)
|
||||||
? auctionUrl
|
? auctionUrl
|
||||||
: $"https://it.bidoo.com/auction.php?a=asta_{auctionId}";
|
: $"https://it.bidoo.com/auction.php?a=asta_{auctionId}";
|
||||||
|
|
||||||
Log($"[API REQUEST] Using referer: {referer}", auctionId);
|
|
||||||
AddAuthHeaders(request, referer, auctionId);
|
AddAuthHeaders(request, referer, auctionId);
|
||||||
|
|
||||||
var response = await _httpClient.SendAsync(request, token);
|
var response = await _httpClient.SendAsync(request, token);
|
||||||
var latency = (int)(DateTime.UtcNow - startTime).TotalMilliseconds;
|
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();
|
var responseText = await response.Content.ReadAsStringAsync();
|
||||||
Log($"[API RESPONSE] Body ({responseText.Length} bytes): {responseText}", auctionId);
|
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
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 null;
|
||||||
}
|
}
|
||||||
|
var state = ParsePollingResponse(auctionId, responseText, latency);
|
||||||
return ParsePollingResponse(auctionId, responseText, latency);
|
return state;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log($"[API EXCEPTION] Polling {auctionId}: {ex.GetType().Name} - {ex.Message}", auctionId);
|
Log($"[ERRORE] [{auctionId}] API non ha risposto (motivo: eccezione)", null); // globale
|
||||||
Log($"[API EXCEPTION] StackTrace: {ex.StackTrace}", auctionId);
|
Log($"API non ha risposto: {ex.GetType().Name} - {ex.Message}", auctionId); // asta
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -286,156 +244,71 @@ namespace AutoBidder.Services
|
|||||||
{
|
{
|
||||||
try
|
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 serverTimestamp;
|
||||||
string mainData;
|
string mainData;
|
||||||
|
|
||||||
// Cerca il separatore '*' che divide timestamp da dati
|
|
||||||
var starIndex = response.IndexOf('*');
|
var starIndex = response.IndexOf('*');
|
||||||
if (starIndex == -1)
|
if (starIndex == -1)
|
||||||
{
|
{
|
||||||
Log("[PARSE ERROR] No '*' separator found in response", auctionId);
|
Log("[PARSE ERROR] No '*' separator found in response", auctionId);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var timestampPart = response.Substring(0, starIndex);
|
var timestampPart = response.Substring(0, starIndex);
|
||||||
mainData = response.Substring(starIndex + 1);
|
mainData = response.Substring(starIndex + 1);
|
||||||
|
|
||||||
// Il timestamp può contenere '|' (es: "1761120002|1")
|
|
||||||
// Prendiamo solo la prima parte numerica
|
|
||||||
if (timestampPart.Contains('|'))
|
if (timestampPart.Contains('|'))
|
||||||
{
|
{
|
||||||
serverTimestamp = timestampPart.Split('|')[0];
|
serverTimestamp = timestampPart.Split('|')[0];
|
||||||
Log($"[PARSE] Extended format detected: {timestampPart}", auctionId);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
serverTimestamp = timestampPart;
|
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 bracketStart = mainData.IndexOf('[');
|
||||||
var bracketEnd = mainData.IndexOf(']');
|
var bracketEnd = mainData.IndexOf(']');
|
||||||
|
|
||||||
if (bracketStart == -1 || bracketEnd == -1)
|
if (bracketStart == -1 || bracketEnd == -1)
|
||||||
{
|
{
|
||||||
Log("[PARSE ERROR] Missing brackets in auction data", auctionId);
|
Log("[PARSE ERROR] Missing brackets in auction data", auctionId);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var auctionData = mainData.Substring(bracketStart + 1, bracketEnd - bracketStart - 1);
|
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 firstSeparator = auctionData.IndexOfAny(new[] { '|', ',' });
|
||||||
var coreData = firstSeparator > 0 ? auctionData.Substring(0, firstSeparator) : auctionData;
|
var coreData = firstSeparator > 0 ? auctionData.Substring(0, firstSeparator) : auctionData;
|
||||||
|
|
||||||
var fields = coreData.Split(';');
|
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)
|
if (fields.Length < 5)
|
||||||
{
|
{
|
||||||
Log($"[PARSE ERROR] Expected at least 5 core fields, got {fields.Length}", auctionId);
|
Log($"[PARSE ERROR] Expected at least 5 core fields, got {fields.Length}", auctionId);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var state = new AuctionState
|
var state = new AuctionState
|
||||||
{
|
{
|
||||||
AuctionId = auctionId,
|
AuctionId = auctionId,
|
||||||
SnapshotTime = DateTime.UtcNow,
|
SnapshotTime = DateTime.UtcNow,
|
||||||
PollingLatencyMs = latency
|
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();
|
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();
|
string lastBidder = fields[4].Trim();
|
||||||
bool hasWinner = !string.IsNullOrEmpty(lastBidder);
|
bool hasWinner = !string.IsNullOrEmpty(lastBidder);
|
||||||
bool iAmWinner = hasWinner && lastBidder.Equals(_session.Username, StringComparison.OrdinalIgnoreCase);
|
bool iAmWinner = hasWinner && lastBidder.Equals(_session.Username, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
state.Status = DetermineAuctionStatus(status, hasWinner, iAmWinner, ref state);
|
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))
|
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);
|
var timerSeconds = (double)(expiryTs - serverTs);
|
||||||
state.Timer = Math.Max(0, timerSeconds);
|
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))
|
if (int.TryParse(fields[3], out var priceIndex))
|
||||||
{
|
{
|
||||||
state.Price = priceIndex * 0.01;
|
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.LastBidder = fields[4].Trim();
|
||||||
state.IsMyBid = !string.IsNullOrEmpty(_session.Username) &&
|
state.IsMyBid = !string.IsNullOrEmpty(_session.Username) &&
|
||||||
state.LastBidder.Equals(_session.Username, StringComparison.OrdinalIgnoreCase);
|
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;
|
state.ParsingSuccess = true;
|
||||||
|
// Log only summary on success
|
||||||
Log($"[PARSE SUCCESS] ✓ Timer: {state.Timer:F2}s, Price: €{state.Price:F2}, Bidder: {state.LastBidder}, Status: {state.Status}", auctionId);
|
Log($"[PARSE SUCCESS] Timer: {state.Timer:F2}s, Price: €{state.Price:F2}, Bidder: {state.LastBidder}, Status: {state.Status}", auctionId);
|
||||||
|
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Log($"[PARSE EXCEPTION] {ex.GetType().Name}: {ex.Message}", auctionId);
|
Log($"[PARSE EXCEPTION] {ex.GetType().Name}: {ex.Message}", auctionId);
|
||||||
Log($"[PARSE EXCEPTION] StackTrace: {ex.StackTrace}", auctionId);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -459,7 +332,7 @@ namespace AutoBidder.Services
|
|||||||
Log($"[USER INFO RESPONSE] Latency: {latency}ms");
|
Log($"[USER INFO RESPONSE] Latency: {latency}ms");
|
||||||
|
|
||||||
var responseText = await response.Content.ReadAsStringAsync();
|
var responseText = await response.Content.ReadAsStringAsync();
|
||||||
Log($"[USER INFO RESPONSE] Body: {responseText}");
|
Log($"[USER INFO RESPONSE] Body length: {responseText.Length}");
|
||||||
|
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
@@ -484,39 +357,33 @@ namespace AutoBidder.Services
|
|||||||
AuctionId = auctionId,
|
AuctionId = auctionId,
|
||||||
Timestamp = DateTime.UtcNow
|
Timestamp = DateTime.UtcNow
|
||||||
};
|
};
|
||||||
|
|
||||||
try
|
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 url = "https://it.bidoo.com/bid.php";
|
||||||
var payload = $"AID={WebUtility.UrlEncode(auctionId)}&sup=0&shock=0";
|
var payload = $"AID={WebUtility.UrlEncode(auctionId)}&sup=0&shock=0";
|
||||||
|
|
||||||
Log($"[BID REQUEST] GET {url}?{payload}", auctionId);
|
Log($"[BID REQUEST] GET {url}?{payload}", auctionId);
|
||||||
|
|
||||||
var getUrl = url + "?" + payload;
|
var getUrl = url + "?" + payload;
|
||||||
|
|
||||||
var request = new HttpRequestMessage(HttpMethod.Get, getUrl);
|
var request = new HttpRequestMessage(HttpMethod.Get, getUrl);
|
||||||
|
|
||||||
var referer = !string.IsNullOrEmpty(auctionUrl) ? auctionUrl : $"https://it.bidoo.com/asta/nome-prodotto-{auctionId}";
|
var referer = !string.IsNullOrEmpty(auctionUrl) ? auctionUrl : $"https://it.bidoo.com/asta/nome-prodotto-{auctionId}";
|
||||||
AddAuthHeaders(request, referer, auctionId);
|
AddAuthHeaders(request, referer, auctionId);
|
||||||
if (!request.Headers.Contains("Origin"))
|
if (!request.Headers.Contains("Origin"))
|
||||||
{
|
{
|
||||||
request.Headers.Add("Origin", "https://it.bidoo.com");
|
request.Headers.Add("Origin", "https://it.bidoo.com");
|
||||||
}
|
}
|
||||||
|
|
||||||
var startTime = DateTime.UtcNow;
|
var startTime = DateTime.UtcNow;
|
||||||
var response = await _httpClient.SendAsync(request);
|
var response = await _httpClient.SendAsync(request);
|
||||||
result.LatencyMs = (int)(DateTime.UtcNow - startTime).TotalMilliseconds;
|
result.LatencyMs = (int)(DateTime.UtcNow - startTime).TotalMilliseconds;
|
||||||
|
|
||||||
Log($"[BID RESPONSE] Status: {(int)response.StatusCode} {response.StatusCode}", auctionId);
|
Log($"[BID RESPONSE] Status: {(int)response.StatusCode} {response.StatusCode}", auctionId);
|
||||||
Log($"[BID RESPONSE] Latency: {result.LatencyMs}ms", auctionId);
|
Log($"[BID RESPONSE] Latency: {result.LatencyMs}ms", auctionId);
|
||||||
|
|
||||||
var responseText = await response.Content.ReadAsStringAsync();
|
var responseText = await response.Content.ReadAsStringAsync();
|
||||||
result.Response = responseText;
|
result.Response = responseText;
|
||||||
|
Log($"[BID RESPONSE] Body length: {responseText.Length} bytes", auctionId);
|
||||||
Log($"[BID RESPONSE] Body ({responseText.Length} bytes): {responseText}", 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))
|
if (responseText.StartsWith("ok", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
result.Success = true;
|
result.Success = true;
|
||||||
@@ -525,8 +392,7 @@ namespace AutoBidder.Services
|
|||||||
{
|
{
|
||||||
result.NewPrice = priceIndex * 0.01;
|
result.NewPrice = priceIndex * 0.01;
|
||||||
}
|
}
|
||||||
|
Log("[BID SUCCESS] ✓ Bid placed successfully", auctionId);
|
||||||
Log("[BID SUCCESS] ✓ Bid placed successfully via GET", auctionId);
|
|
||||||
}
|
}
|
||||||
else if (responseText.StartsWith("error", StringComparison.OrdinalIgnoreCase) || responseText.StartsWith("no|", StringComparison.OrdinalIgnoreCase))
|
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;
|
result.Error = string.IsNullOrEmpty(responseText) ? $"HTTP {(int)response.StatusCode}" : responseText;
|
||||||
Log($"[BID ERROR] Unexpected response format: {result.Error}", auctionId);
|
Log($"[BID ERROR] Unexpected response format: {result.Error}", auctionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -555,7 +420,79 @@ namespace AutoBidder.Services
|
|||||||
result.Success = false;
|
result.Success = false;
|
||||||
result.Error = ex.Message;
|
result.Error = ex.Message;
|
||||||
Log($"[BID EXCEPTION] {ex.GetType().Name}: {ex.Message}", auctionId);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Place a minimal final bid using the simpler payload required by the final-attack protocol.
|
||||||
|
/// Uses: ?auctionID=[ID]&submit=1
|
||||||
|
/// </summary>
|
||||||
|
public async Task<BidResult> PlaceBidFinalAsync(string auctionId, string? auctionUrl = null)
|
||||||
|
{
|
||||||
|
var result = new BidResult
|
||||||
|
{
|
||||||
|
AuctionId = auctionId,
|
||||||
|
Timestamp = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Log($"[BID FINAL] Placing final bid minimal payload", auctionId);
|
||||||
|
var url = "https://it.bidoo.com/bid.php";
|
||||||
|
var payload = $"auctionID={WebUtility.UrlEncode(auctionId)}&submit=1";
|
||||||
|
Log($"[BID REQUEST] GET {url}?{payload}", auctionId);
|
||||||
|
var getUrl = url + "?" + payload;
|
||||||
|
var request = new HttpRequestMessage(HttpMethod.Get, getUrl);
|
||||||
|
var referer = !string.IsNullOrEmpty(auctionUrl) ? auctionUrl : $"https://it.bidoo.com/asta/nome-prodotto-{auctionId}";
|
||||||
|
AddAuthHeaders(request, referer, auctionId);
|
||||||
|
if (!request.Headers.Contains("Origin"))
|
||||||
|
{
|
||||||
|
request.Headers.Add("Origin", "https://it.bidoo.com");
|
||||||
|
}
|
||||||
|
var startTime = DateTime.UtcNow;
|
||||||
|
var response = await _httpClient.SendAsync(request);
|
||||||
|
result.LatencyMs = (int)(DateTime.UtcNow - startTime).TotalMilliseconds;
|
||||||
|
Log($"[BID RESPONSE] Status: {(int)response.StatusCode} {response.StatusCode}", auctionId);
|
||||||
|
Log($"[BID RESPONSE] Latency: {result.LatencyMs}ms", auctionId);
|
||||||
|
var responseText = await response.Content.ReadAsStringAsync();
|
||||||
|
result.Response = responseText;
|
||||||
|
Log($"[BID RESPONSE] Body length: {responseText.Length} bytes", auctionId);
|
||||||
|
if (!string.IsNullOrEmpty(responseText))
|
||||||
|
{
|
||||||
|
var preview = responseText.Length > 80 ? responseText.Substring(0, 80) + "..." : responseText;
|
||||||
|
Log($"[BID RESPONSE] Preview: {preview}", auctionId);
|
||||||
|
}
|
||||||
|
if (responseText.StartsWith("ok", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
result.Success = true;
|
||||||
|
var parts = responseText.Split('|');
|
||||||
|
if (parts.Length > 1 && double.TryParse(parts[1], out var priceIndex))
|
||||||
|
{
|
||||||
|
result.NewPrice = priceIndex * 0.01;
|
||||||
|
}
|
||||||
|
Log("[BID SUCCESS] ✓ Final bid placed successfully", auctionId);
|
||||||
|
}
|
||||||
|
else if (responseText.StartsWith("error", StringComparison.OrdinalIgnoreCase) || responseText.StartsWith("no|", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
result.Success = false;
|
||||||
|
var parts = responseText.Split('|');
|
||||||
|
result.Error = parts.Length > 1 ? parts[1] : responseText;
|
||||||
|
Log($"[BID ERROR] Server returned error: {result.Error}", auctionId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.Success = false;
|
||||||
|
result.Error = string.IsNullOrEmpty(responseText) ? $"HTTP {(int)response.StatusCode}" : responseText;
|
||||||
|
Log($"[BID ERROR] Unexpected response format: {result.Error}", auctionId);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
result.Success = false;
|
||||||
|
result.Error = ex.Message;
|
||||||
|
Log($"[BID EXCEPTION] {ex.GetType().Name}: {ex.Message}", auctionId);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -623,5 +560,136 @@ namespace AutoBidder.Services
|
|||||||
{
|
{
|
||||||
_httpClient?.Dispose();
|
_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>
|
/// <summary>
|
||||||
/// Gestore persistenza sessione Bidoo
|
/// Gestore persistenza sessione Bidoo
|
||||||
/// Salva in modo sicuro il cookie di autenticazione per riutilizzo
|
/// Salva in modo sicuro il cookie di autenticazione per riutilizzo
|
||||||
|
/// Il cookie deve essere inserito manualmente dall'utente (copiato dal browser)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class SessionManager
|
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}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -101,22 +101,23 @@ namespace AutoBidder.Utilities
|
|||||||
var csv = new StringBuilder();
|
var csv = new StringBuilder();
|
||||||
|
|
||||||
// Header
|
// Header
|
||||||
csv.AppendLine("Auction ID,Name,Timer Click,Delay (ms),Min Price,Max Price," +
|
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");
|
||||||
"My Clicks,Resets,Total Bidders,Active,Paused," +
|
|
||||||
"Added At,Last Click At");
|
|
||||||
|
|
||||||
// Data
|
// Data
|
||||||
foreach (var auction in auctions)
|
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}," +
|
csv.AppendLine($"{auction.AuctionId}," +
|
||||||
$"\"{auction.Name}\"," +
|
$"\"{auction.Name}\"," +
|
||||||
$"{auction.TimerClick}," +
|
$"{auction.TimerClick}," +
|
||||||
$"{auction.DelayMs}," +
|
$"{auction.DelayMs}," +
|
||||||
$"{auction.MinPrice:F2}," +
|
$"{auction.MinPrice:F2}," +
|
||||||
$"{auction.MaxPrice:F2}," +
|
$"{auction.MaxPrice:F2}," +
|
||||||
$"{auction.MyClicks}," +
|
$"{myBids}," +
|
||||||
$"{auction.ResetCount}," +
|
$"{auction.ResetCount}," +
|
||||||
$"{auction.Bidders.Count}," +
|
$"{totalBidders}," +
|
||||||
$"{auction.IsActive}," +
|
$"{auction.IsActive}," +
|
||||||
$"{auction.IsPaused}," +
|
$"{auction.IsPaused}," +
|
||||||
$"{auction.AddedAt:yyyy-MM-dd HH:mm:ss}," +
|
$"{auction.AddedAt:yyyy-MM-dd HH:mm:ss}," +
|
||||||
|
|||||||
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;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
using AutoBidder.Models;
|
using AutoBidder.Models;
|
||||||
|
|
||||||
namespace AutoBidder.ViewModels
|
namespace AutoBidder.ViewModels
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ViewModel per una riga della griglia aste (DataBinding)
|
/// ViewModel per una riga della griglia aste (DataBinding)
|
||||||
|
/// Solo HTTP, nessuna modalità, browser o multi-click
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AuctionViewModel : INotifyPropertyChanged
|
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
|
// Stato
|
||||||
public bool IsActive
|
public bool IsActive
|
||||||
{
|
{
|
||||||
@@ -74,7 +86,6 @@ namespace AutoBidder.ViewModels
|
|||||||
OnPropertyChanged(nameof(IsActive));
|
OnPropertyChanged(nameof(IsActive));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsPaused
|
public bool IsPaused
|
||||||
{
|
{
|
||||||
get => _auctionInfo.IsPaused;
|
get => _auctionInfo.IsPaused;
|
||||||
@@ -84,11 +95,24 @@ namespace AutoBidder.ViewModels
|
|||||||
OnPropertyChanged(nameof(IsPaused));
|
OnPropertyChanged(nameof(IsPaused));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contatori
|
|
||||||
public int MyClicks => _auctionInfo.MyClicks;
|
|
||||||
public int ResetCount => _auctionInfo.ResetCount;
|
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)
|
// Dati live (da ultimo polling)
|
||||||
public string TimerDisplay
|
public string TimerDisplay
|
||||||
{
|
{
|
||||||
@@ -192,7 +216,6 @@ namespace AutoBidder.ViewModels
|
|||||||
public void UpdateState(AuctionState state)
|
public void UpdateState(AuctionState state)
|
||||||
{
|
{
|
||||||
_lastState = state;
|
_lastState = state;
|
||||||
|
|
||||||
// Notifica tutte le proprietà dipendenti dallo stato
|
// Notifica tutte le proprietà dipendenti dallo stato
|
||||||
OnPropertyChanged(nameof(TimerDisplay));
|
OnPropertyChanged(nameof(TimerDisplay));
|
||||||
OnPropertyChanged(nameof(PriceDisplay));
|
OnPropertyChanged(nameof(PriceDisplay));
|
||||||
@@ -200,6 +223,8 @@ namespace AutoBidder.ViewModels
|
|||||||
OnPropertyChanged(nameof(IsMyBid));
|
OnPropertyChanged(nameof(IsMyBid));
|
||||||
OnPropertyChanged(nameof(StatusDisplay));
|
OnPropertyChanged(nameof(StatusDisplay));
|
||||||
OnPropertyChanged(nameof(Strategy));
|
OnPropertyChanged(nameof(Strategy));
|
||||||
|
OnPropertyChanged(nameof(MyClicks));
|
||||||
|
OnPropertyChanged(nameof(AuctionInfo)); // For PollingLatencyMs
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -207,8 +232,9 @@ namespace AutoBidder.ViewModels
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void RefreshCounters()
|
public void RefreshCounters()
|
||||||
{
|
{
|
||||||
OnPropertyChanged(nameof(MyClicks));
|
// RIMOSSO: OnPropertyChanged(nameof(MyClicks));
|
||||||
OnPropertyChanged(nameof(ResetCount));
|
OnPropertyChanged(nameof(ResetCount));
|
||||||
|
OnPropertyChanged(nameof(MyClicks));
|
||||||
}
|
}
|
||||||
|
|
||||||
public event PropertyChangedEventHandler? PropertyChanged;
|
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.
|
> 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
|
- Requisiti di sistema
|
||||||
- Installazione e avvio
|
- Installazione e avvio
|
||||||
- Guida rapida (primi passi)
|
- Guida rapida (primi passi)
|
||||||
- Modalità operative: Asta Singola e Multi-Asta
|
- Gestione aste tramite griglia
|
||||||
- Strategie e consigli pratici
|
- Strategie e consigli pratici
|
||||||
- Dettagli tecnici: Polling, Click HTTP e sincronizzazione cookie
|
- Dettagli tecnici: Polling e Click HTTP
|
||||||
- Impostazioni e persistenza
|
- Impostazioni e persistenza
|
||||||
- Esportazione e diagnostica
|
- Esportazione e diagnostica
|
||||||
- FAQ e risoluzione problemi
|
- FAQ e risoluzione problemi
|
||||||
@@ -23,10 +23,9 @@ Sommario
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Caratteristiche principali
|
## Caratteristiche principali
|
||||||
- Monitoraggio in tempo reale di singole aste o molte aste contemporaneamente
|
- Monitoraggio in tempo reale di tutte le aste tramite griglia unica
|
||||||
- Due modalità operative: `Asta Singola` (massima precisione) e `Multi-Asta` (monitoraggio e auto-switch)
|
- Polling adattivo solo HTTP per ridurre uso di CPU e RAM
|
||||||
- Polling adattivo (HTTP / WebView2 / Active) per ridurre uso di CPU e RAM
|
- Click HTTP diretto (reverse engineered) con cookie manuale
|
||||||
- Click HTTP diretto (reverse engineered) con sincronizzazione dei cookie dal WebView2
|
|
||||||
- Persistenza della lista aste (`auctions.json`) e esportazione CSV delle statistiche
|
- 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
|
- UI dark theme moderna con griglia in tempo reale, dettagli asta, log e contatori
|
||||||
|
|
||||||
@@ -44,59 +43,47 @@ Sommario
|
|||||||
- `dotnet restore`
|
- `dotnet restore`
|
||||||
- `dotnet build --configuration Release`
|
- `dotnet build --configuration Release`
|
||||||
4. Eseguire l'app:
|
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)
|
## Guida rapida (primi passi)
|
||||||
1. Avvia l'app: l'interfaccia principale mostra due pannelli (controlli a sinistra, browser WebView2 a destra).
|
1. Avvia l'app: l'interfaccia principale mostra la griglia delle aste monitorate.
|
||||||
2. Login: accedi a `bidoo.com` tramite il browser integrato (se vuoi usare il Click HTTP diretto devi essere loggato).
|
2. Configura la sessione: copia il cookie `__stattrb` dal browser (F12 > Application > Cookies) e inseriscilo tramite il dialog di configurazione.
|
||||||
3. Scegli modalità:
|
3. Aggiungi aste: usa il pulsante `+ Aggiungi` per inserire URL o ID delle aste da monitorare.
|
||||||
- `Asta Singola` per concentrarti su un solo oggetto
|
4. Configura impostazioni per ogni asta (Timer Click, Min/Max Price, Max Clicks, Max Resets, Ritardo).
|
||||||
- `Multi-Asta` per monitorare più aste e lasciare che l'app esegua auto-switch
|
5. Premi `Avvia Tutti` per attivare il monitoraggio e le puntate automatiche.
|
||||||
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.
|
|
||||||
|
|
||||||
## Modalità operative
|
## Gestione aste tramite griglia
|
||||||
|
- Tutte le aste sono gestite tramite una griglia unica.
|
||||||
### Asta Singola
|
- Puoi avviare, mettere in pausa, fermare o puntare manualmente su ogni asta direttamente dalla griglia.
|
||||||
- Ideale per oggetti di valore dove la precisione è critica.
|
- Non esistono più modalità "Asta Singola" o "Multi-Asta": tutto è gestito in modo uniforme.
|
||||||
- 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).
|
|
||||||
|
|
||||||
## Strategie consigliate
|
## Strategie consigliate
|
||||||
- Monitoraggio massivo (50+ aste): usa `URL Manuale`, `Timer Click` 0, limiti di prezzo bassi.
|
- Monitoraggio massivo: aggiungi molte aste tramite URL o ID, imposta Timer Click basso e limiti di prezzo.
|
||||||
- Asta singola ad alto valore: `Timer Click` 0-1, `Multi-Click` ON, `Ritardo` 0ms.
|
- Asta ad alto valore: Timer Click 0-1, Ritardo 0ms.
|
||||||
- Caccia all'affare: `Max Price` molto basso, monitora molte aste con Timer Click 0.
|
- Caccia all'affare: Max Price basso, monitora molte aste con Timer Click 0.
|
||||||
|
|
||||||
## Approfondimento tecnico
|
## Approfondimento tecnico
|
||||||
|
|
||||||
### Polling adattivo (Dual-Track)
|
### Polling adattivo
|
||||||
- Track 1 — Background Polling: esegue richieste HTTP ogni ~5s per aggiornare timer, prezzo e ultimo bidder. Mantiene uso CPU/RAM minimo.
|
- Il monitoraggio avviene solo tramite chiamate HTTP dirette alle API di Bidoo.
|
||||||
- Track 2 — Click Loop: attivo solo dopo `Avvia`. Polling dinamico 20-400ms per aste critiche e invio dei click (HTTP diretto o WebView2 fallback).
|
- Nessun WebView2 viene utilizzato, nemmeno come fallback.
|
||||||
|
- Il polling si adatta al timer dell'asta per ottimizzare la frequenza delle richieste.
|
||||||
|
|
||||||
### Click HTTP diretto
|
### Click HTTP diretto
|
||||||
- Al momento dell'azione, il client costruisce una GET verso l'endpoint reverse engineered di Bidoo tipo:
|
- 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`
|
`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.
|
- Richiede i cookie di sessione (`__stattrb`) che vengono copiati manualmente dal browser.
|
||||||
- Latenza tipica: 10-30ms (molto più veloce del click via WebView2 che può impiegare 50-100ms)
|
- Latenza tipica: 10-30ms.
|
||||||
- Se il Click HTTP fallisce, l'app esegue fallback automatico con `ExecuteScript` su WebView2.
|
- Non esiste più alcun fallback WebView2: tutte le puntate sono gestite solo via HTTP.
|
||||||
|
|
||||||
### Sincronizzazione cookie
|
### Sincronizzazione cookie
|
||||||
- Al primo `Avvia` viene letto il `CookieManager` di `CoreWebView2`, copiato in un `CookieContainer` per l'`HttpClient` utilizzato dai Click HTTP.
|
- I cookie vengono inseriti manualmente dall'utente tramite dialog.
|
||||||
- I cookie sono conservati solo in memoria, mai su disco. Devono essere rinfrescati ri-effettuando il login nel WebView2 se scadono.
|
- Non vengono mai salvati su disco in chiaro.
|
||||||
|
- Se il cookie scade, è necessario copiarlo nuovamente dal browser.
|
||||||
|
|
||||||
## Persistenza e file locali
|
## Persistenza e file locali
|
||||||
- Lista aste manuali salvata in:
|
- Lista aste manuali salvata in:
|
||||||
`%AppData%\\AutoBidder\\auctions.json`
|
`%AppData%\AutoBidder\auctions.json`
|
||||||
- La lista viene ricaricata automaticamente all'avvio dell'app.
|
- 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).
|
- 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)
|
- `Max Clicks` / `Max Resets`: limiti operativi (0 = illimitato)
|
||||||
- `Min/Max Price`: evita puntate fuori dal range desiderato
|
- `Min/Max Price`: evita puntate fuori dal range desiderato
|
||||||
- `Ritardo (ms)`: delay aggiuntivo prima di inviare il click
|
- `Ritardo (ms)`: delay aggiuntivo prima di inviare il click
|
||||||
- `Multi-Click`: invia più click paralleli (utile su connessioni con jitter)
|
|
||||||
|
|
||||||
## Interfaccia e controlli
|
## Interfaccia e controlli
|
||||||
- Pannello sinistro: controlli principali (modalità, Avvia, Pausa, Stop, aggiungi/rimuovi aste, esporta CSV)
|
- Griglia centrale: mostra tutte le aste monitorate con controlli per avviare, mettere in pausa, fermare e puntare manualmente.
|
||||||
- Pannello destro: browser integrato (WebView2) + dettagli asta quando selezionata
|
- Log in tempo reale per ogni asta con dettagli di latenza, risposta server e fallimenti.
|
||||||
- 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
|
|
||||||
|
|
||||||
## Diagnostica ed esportazione
|
## Diagnostica ed esportazione
|
||||||
- Abilita log dettagliato prima di aprire issue o per analisi locali
|
- 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
|
- Mantieni screenshot del pannello log per report più chiari
|
||||||
|
|
||||||
## FAQ e risoluzione dei problemi
|
## 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.
|
- "Non vedo Click HTTP riuscito": assicurati di aver inserito correttamente il cookie dal browser e che la sessione sia valida.
|
||||||
- "Aste non rilevate": assicurati di essere nella pagina Preferiti per il metodo automatico oppure aggiungi gli URL manualmente.
|
- "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.
|
- "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à
|
## Avvisi e responsabilità
|
||||||
- L'automazione potrebbe violare i Termini di Servizio di Bidoo. L'uso è a rischio e responsabilità dell'utente.
|
- 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.
|
- Cookie e dati di sessione rimangono in memoria e vengono rimossi alla chiusura dell'app.
|
||||||
|
|
||||||
## Changelog sintetico (ultime versioni)
|
## 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.10: Click HTTP diretto, sincronizzazione cookie automatica, miglioramenti prestazionali
|
||||||
- v2.9: Persistenza automatica, UI improvements, export CSV
|
- v2.9: Persistenza automatica, UI improvements, export CSV
|
||||||
- v2.8: Polling adattivo e strategie ibride
|
- v2.8: Polling adattivo e strategie ibride
|
||||||
@@ -141,13 +126,13 @@ Sommario
|
|||||||
---
|
---
|
||||||
|
|
||||||
Note tecniche per sviluppatori
|
Note tecniche per sviluppatori
|
||||||
- Progetto target: `.NET 8.0` (WPF + WebView2)
|
- Progetto target: `.NET 8.0` (WPF)
|
||||||
- Aree chiave del codice:
|
- Aree chiave del codice:
|
||||||
- `Services\\BidooApiClient.cs` — gestione Click HTTP e parsing risposte
|
- `Services\BidooApiClient.cs` — gestione Click HTTP e parsing risposte
|
||||||
- `Services\\AuctionMonitor.cs` — loop di polling e logica auto-switch
|
- `Services\AuctionMonitor.cs` — loop di polling
|
||||||
- `Services\\SessionManager.cs` — sincronizzazione cookie e HttpClient creation
|
- `Services\SessionManager.cs` — gestione cookie manuale
|
||||||
- `Utilities\\PersistenceManager.cs` — salvataggio/ricaricamento `auctions.json`
|
- `Utilities\PersistenceManager.cs` — salvataggio/ricaricamento `auctions.json`
|
||||||
- `ViewModels\\AuctionViewModel.cs` + XAML corrispondenti — visualizzazione e binding UI
|
- `ViewModels\AuctionViewModel.cs` + XAML corrispondenti — visualizzazione e binding UI
|
||||||
|
|
||||||
## Contributi
|
## Contributi
|
||||||
- Questo repository è privato. Per contribuire, aprire PR verso branch `main` e seguire le convenzioni del progetto.
|
- Questo repository è privato. Per contribuire, aprire PR verso branch `main` e seguire le convenzioni del progetto.
|
||||||
|
|||||||
Reference in New Issue
Block a user