Aggiunta applicazione WPF "AutoBidder"
* Aggiunti `App.xaml` e `App.xaml.cs` per configurare l'app. * Creati `MainWindow.xaml` e `MainWindow.xaml.cs` per UI e logica. * Implementata automazione con `WebView2` per interazioni web. * Aggiunti stili personalizzati per pulsanti e controlli. * Configurato progetto WPF in `.NET 8.0` con supporto WebView2. * Aggiunto file soluzione `Mimante.sln` con configurazioni di build. * Migliorata gestione di timer, prezzo e clic con script JS. * Aggiunta gestione visiva per pulsanti di avvio/arresto. * Creata icona dinamica per la finestra principale.
This commit is contained in:
9
Mimante/App.xaml
Normal file
9
Mimante/App.xaml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<Application x:Class="Mimante.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:local="clr-namespace:Mimante"
|
||||||
|
StartupUri="MainWindow.xaml">
|
||||||
|
<Application.Resources>
|
||||||
|
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
||||||
14
Mimante/App.xaml.cs
Normal file
14
Mimante/App.xaml.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System.Configuration;
|
||||||
|
using System.Data;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace Mimante
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for App.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
10
Mimante/AssemblyInfo.cs
Normal file
10
Mimante/AssemblyInfo.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
[assembly: ThemeInfo(
|
||||||
|
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// or application resource dictionaries)
|
||||||
|
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
|
||||||
|
//(used if a resource is not found in the page,
|
||||||
|
// app, or any theme specific resource dictionaries)
|
||||||
|
)]
|
||||||
155
Mimante/MainWindow.xaml
Normal file
155
Mimante/MainWindow.xaml
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
<Window x:Class="AutoBidder.MainWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:local="clr-namespace:AutoBidder"
|
||||||
|
xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="AutoBidder - Bidoo Auto Bid" Height="700" Width="1100" Background="#101219" Foreground="#E6EDF3">
|
||||||
|
<Window.Resources>
|
||||||
|
<Style x:Key="PrimaryButtonStyle" TargetType="Button">
|
||||||
|
<Setter Property="Foreground" Value="White" />
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold" />
|
||||||
|
<Setter Property="Padding" Value="10,6" />
|
||||||
|
<Setter Property="Margin" Value="0,6,0,0" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
|
<Setter Property="Height" Value="40" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Button">
|
||||||
|
<Border Background="{TemplateBinding Background}" CornerRadius="8" SnapsToDevicePixels="True">
|
||||||
|
<ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" RecognizesAccessKey="True" />
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="StartButtonStyle" TargetType="Button" BasedOn="{StaticResource PrimaryButtonStyle}">
|
||||||
|
<Setter Property="Background" Value="#16A34A" /> <!-- green -->
|
||||||
|
</Style>
|
||||||
|
<Style x:Key="StopButtonStyle" TargetType="Button" BasedOn="{StaticResource PrimaryButtonStyle}">
|
||||||
|
<Setter Property="Background" Value="#DC2626" /> <!-- red -->
|
||||||
|
<Setter Property="Opacity" Value="0.6" />
|
||||||
|
</Style>
|
||||||
|
<Style x:Key="RefreshButtonStyle" TargetType="Button" BasedOn="{StaticResource PrimaryButtonStyle}">
|
||||||
|
<Setter Property="Background" Value="#0EA5E9" /> <!-- blue -->
|
||||||
|
<Setter Property="Height" Value="30" />
|
||||||
|
<Setter Property="Padding" Value="8,4" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="TextBlock">
|
||||||
|
<Setter Property="Foreground" Value="#E6EDF3" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="LogBoxStyle" TargetType="TextBox">
|
||||||
|
<Setter Property="Background" Value="#0F1720" />
|
||||||
|
<Setter Property="Foreground" Value="#E6EDF3" />
|
||||||
|
<Setter Property="BorderBrush" Value="#263143" />
|
||||||
|
<Setter Property="BorderThickness" Value="1" />
|
||||||
|
<Setter Property="Padding" Value="8" />
|
||||||
|
<Setter Property="FontFamily" Value="Consolas" />
|
||||||
|
<Setter Property="FontSize" Value="12" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="NumericBoxStyle" 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="6" />
|
||||||
|
<Setter Property="Width" Value="80" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Center" />
|
||||||
|
<Setter Property="FontWeight" Value="SemiBold" />
|
||||||
|
</Style>
|
||||||
|
</Window.Resources>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="360" MinWidth="220" />
|
||||||
|
<ColumnDefinition Width="6" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- Left panel: fixed height, resizable width -->
|
||||||
|
<Border Grid.Column="0" Margin="12" Background="#0B1015" CornerRadius="6">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" /> <!-- Start -->
|
||||||
|
<RowDefinition Height="Auto" /> <!-- Stop -->
|
||||||
|
<RowDefinition Height="Auto" /> <!-- Back/Refresh -->
|
||||||
|
<RowDefinition Height="Auto" /> <!-- Site -->
|
||||||
|
<RowDefinition Height="Auto" /> <!-- Stats -->
|
||||||
|
<RowDefinition Height="Auto" /> <!-- Settings -->
|
||||||
|
<RowDefinition Height="Auto" /> <!-- Price -->
|
||||||
|
<RowDefinition Height="Auto" /> <!-- Log label -->
|
||||||
|
<RowDefinition Height="*" /> <!-- Log box expands -->
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Button x:Name="StartButton" Grid.Row="0" Style="{StaticResource StartButtonStyle}" Click="StartButton_Click" Margin="12,12,12,0">Avvia</Button>
|
||||||
|
<Button x:Name="StopButton" Grid.Row="1" Style="{StaticResource StopButtonStyle}" Click="StopButton_Click" IsEnabled="False" Margin="12,8,12,0">Stop</Button>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="2" Orientation="Horizontal" Margin="12,8,12,0">
|
||||||
|
<Button x:Name="BackButton" Style="{StaticResource RefreshButtonStyle}" Click="BackButton_Click" Width="80" Height="30">Indietro</Button>
|
||||||
|
<Button x:Name="RefreshButton" Style="{StaticResource RefreshButtonStyle}" Click="RefreshButton_Click" Width="80" Height="30" Margin="8,0,0,0">Aggiorna</Button>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="3" Orientation="Vertical" Margin="12,12,12,0">
|
||||||
|
<TextBlock Text="Sito:" />
|
||||||
|
<TextBlock x:Name="SiteLinkText" Text="https://it.bidoo.com" TextWrapping="Wrap" FontWeight="Bold" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="4" Orientation="Horizontal" Margin="12,8,12,0">
|
||||||
|
<TextBlock Text="Auto-click:" Margin="0,0,8,0" />
|
||||||
|
<TextBlock x:Name="ClickCountText" Text="0" FontWeight="Bold" Margin="0,0,12,0" />
|
||||||
|
<TextBlock Text="Resets:" Margin="0,0,8,0" />
|
||||||
|
<TextBlock x:Name="ResetCountText" Text="0" FontWeight="Bold" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="5" Orientation="Horizontal" Margin="12,8,12,0">
|
||||||
|
<StackPanel Orientation="Vertical">
|
||||||
|
<TextBlock>Max Clicks</TextBlock>
|
||||||
|
<TextBox x:Name="MaxClicksBox" Style="{StaticResource NumericBoxStyle}" Text="0" ToolTip="0 = nessun limite" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Vertical" Margin="12,0,0,0">
|
||||||
|
<TextBlock>Max Resets</TextBlock>
|
||||||
|
<TextBox x:Name="MaxResetsBox" Style="{StaticResource NumericBoxStyle}" Text="0" ToolTip="0 = nessun limite" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Grid.Row="6" Orientation="Horizontal" Margin="12,8,12,0">
|
||||||
|
<StackPanel Orientation="Vertical">
|
||||||
|
<TextBlock>Min Price</TextBlock>
|
||||||
|
<TextBox x:Name="MinPriceBox" Style="{StaticResource NumericBoxStyle}" Text="0" ToolTip="0 = nessun limite" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Vertical" Margin="12,0,0,0">
|
||||||
|
<TextBlock>Max Price</TextBlock>
|
||||||
|
<TextBox x:Name="MaxPriceBox" Style="{StaticResource NumericBoxStyle}" Text="0" ToolTip="0 = nessun limite" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Vertical" Margin="12,0,0,0">
|
||||||
|
<TextBlock>Prezzo corrente</TextBlock>
|
||||||
|
<TextBlock x:Name="CurrentPriceText" Text="-" FontWeight="Bold" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="7" Margin="12,12,12,6" Text="Log operazioni:" />
|
||||||
|
|
||||||
|
<TextBox x:Name="LogBox" Grid.Row="8" Margin="12" IsReadOnly="True" VerticalScrollBarVisibility="Auto" TextWrapping="Wrap" AcceptsReturn="True" Style="{StaticResource LogBoxStyle}" />
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- GridSplitter to allow resizing the left panel (only width) -->
|
||||||
|
<GridSplitter Grid.Column="1" Width="6" HorizontalAlignment="Center" VerticalAlignment="Stretch" Background="#1f2937" ShowsPreview="True" ResizeDirection="Columns" ResizeBehavior="PreviousAndNext" Cursor="SizeWE" />
|
||||||
|
|
||||||
|
<!-- Right: webview -->
|
||||||
|
<Grid Grid.Column="2">
|
||||||
|
<Border Margin="12" CornerRadius="8" Background="#0B1015">
|
||||||
|
<wv2:WebView2 x:Name="webView" Source="https://it.bidoo.com" Margin="2" />
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
636
Mimante/MainWindow.xaml.cs
Normal file
636
Mimante/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,636 @@
|
|||||||
|
using System;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using Microsoft.Web.WebView2.Core;
|
||||||
|
|
||||||
|
namespace AutoBidder
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for MainWindow.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
private CancellationTokenSource? _cts;
|
||||||
|
private Task? _automationTask;
|
||||||
|
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
// create a simple programmatic icon (circle with AB) and assign to window
|
||||||
|
try
|
||||||
|
{
|
||||||
|
const int size = 64;
|
||||||
|
var dv = new DrawingVisual();
|
||||||
|
using (var dc = dv.RenderOpen())
|
||||||
|
{
|
||||||
|
dc.DrawRoundedRectangle(new SolidColorBrush(Color.FromRgb(16,163,74)), null, new Rect(0, 0, size, size), 8, 8);
|
||||||
|
var ft = new FormattedText("AB", System.Globalization.CultureInfo.InvariantCulture, FlowDirection.LeftToRight,
|
||||||
|
new Typeface("Segoe UI"), 28, Brushes.White, 1.0);
|
||||||
|
var pt = new Point((size - ft.Width) / 2, (size - ft.Height) / 2 - 2);
|
||||||
|
dc.DrawText(ft, pt);
|
||||||
|
}
|
||||||
|
var rtb = new RenderTargetBitmap(size, size, 96, 96, PixelFormats.Pbgra32);
|
||||||
|
rtb.Render(dv);
|
||||||
|
this.Icon = rtb; // set window and taskbar icon
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore icon errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// initial visual states: start is primary action
|
||||||
|
StartButton.IsEnabled = true;
|
||||||
|
StartButton.Opacity = 1.0;
|
||||||
|
StopButton.IsEnabled = false;
|
||||||
|
StopButton.Opacity = 0.5;
|
||||||
|
BackButton.IsEnabled = false;
|
||||||
|
|
||||||
|
// navigation completed -> update back button state
|
||||||
|
webView.NavigationCompleted += WebView_NavigationCompleted;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WebView_NavigationCompleted(object? sender, CoreWebView2NavigationCompletedEventArgs e)
|
||||||
|
{
|
||||||
|
UpdateBackButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateBackButton()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
BackButton.IsEnabled = webView.CoreWebView2 != null && webView.CoreWebView2.CanGoBack;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string? url = null;
|
||||||
|
if (webView.CoreWebView2 != null)
|
||||||
|
{
|
||||||
|
url = webView.CoreWebView2.Source;
|
||||||
|
}
|
||||||
|
else if (webView.Source != null)
|
||||||
|
{
|
||||||
|
url = webView.Source.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(url)) SiteLinkText.Text = url;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void StartButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
StartButton.IsEnabled = false;
|
||||||
|
StopButton.IsEnabled = true;
|
||||||
|
// visual feedback: running -> stop is primary
|
||||||
|
StartButton.Opacity = 0.5;
|
||||||
|
StopButton.Opacity = 1.0;
|
||||||
|
|
||||||
|
Log("Inizializzazione web...");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Ensure WebView2 is initialized
|
||||||
|
if (webView.CoreWebView2 == null)
|
||||||
|
{
|
||||||
|
await webView.EnsureCoreWebView2Async();
|
||||||
|
}
|
||||||
|
UpdateBackButton();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var msg = "Errore inizializzazione WebView2: " + ex.Message;
|
||||||
|
Log(msg);
|
||||||
|
StartButton.IsEnabled = true;
|
||||||
|
StopButton.IsEnabled = false;
|
||||||
|
StartButton.Opacity = 1.0;
|
||||||
|
StopButton.Opacity = 0.5;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log("Avviato: WebView inizializzato. Avvio monitoraggio pagina.");
|
||||||
|
|
||||||
|
_cts = new CancellationTokenSource();
|
||||||
|
var token = _cts.Token;
|
||||||
|
_automationTask = Task.Run(async () => await AutomationLoop(token), token);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StopButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
// visual feedback: stopped -> start is primary
|
||||||
|
StartButton.Opacity = 1.0;
|
||||||
|
StopButton.Opacity = 0.5;
|
||||||
|
StopAutomation("Arrestato dall'utente");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BackButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (webView.CoreWebView2 == null)
|
||||||
|
{
|
||||||
|
// if not initialized, try ensuring
|
||||||
|
_ = webView.EnsureCoreWebView2Async();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (webView.CoreWebView2 != null && webView.CoreWebView2.CanGoBack)
|
||||||
|
{
|
||||||
|
webView.CoreWebView2.GoBack();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log("Errore navigazione indietro: " + ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void RefreshButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (webView.CoreWebView2 == null)
|
||||||
|
{
|
||||||
|
await webView.EnsureCoreWebView2Async();
|
||||||
|
}
|
||||||
|
webView.CoreWebView2?.Reload();
|
||||||
|
Log("Pagina aggiornata");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log("Errore aggiornamento pagina: " + ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StopAutomation(string reason)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_cts?.Cancel();
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
StartButton.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
StartButton.IsEnabled = true;
|
||||||
|
StopButton.IsEnabled = false;
|
||||||
|
// reset visual states: start primary
|
||||||
|
StartButton.Opacity = 1.0;
|
||||||
|
StopButton.Opacity = 0.5;
|
||||||
|
});
|
||||||
|
|
||||||
|
Log("STOP: " + reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Log(string message)
|
||||||
|
{
|
||||||
|
var entry = $"{DateTime.Now:HH:mm:ss} - {message}{Environment.NewLine}";
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
if (LogBox != null)
|
||||||
|
{
|
||||||
|
LogBox.AppendText(entry);
|
||||||
|
LogBox.ScrollToEnd();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// ignore logging errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AutomationLoop(CancellationToken token)
|
||||||
|
{
|
||||||
|
int clickCount = 0;
|
||||||
|
int resetCount = 0;
|
||||||
|
string? previousTimer = null;
|
||||||
|
|
||||||
|
// read limits from UI
|
||||||
|
int maxClicks = 0;
|
||||||
|
int maxResets = 0;
|
||||||
|
double minPrice = 0.0;
|
||||||
|
double maxPrice = double.MaxValue;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
int.TryParse(MaxClicksBox.Text, out maxClicks);
|
||||||
|
int.TryParse(MaxResetsBox.Text, out maxResets);
|
||||||
|
if (maxClicks <= 0) maxClicks = int.MaxValue;
|
||||||
|
if (maxResets <= 0) maxResets = int.MaxValue;
|
||||||
|
|
||||||
|
// price bounds: 0 = no limit
|
||||||
|
if (!double.TryParse(MinPriceBox.Text.Replace(',', '.'), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out minPrice)) minPrice = 0.0;
|
||||||
|
if (!double.TryParse(MaxPriceBox.Text.Replace(',', '.'), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var tmpMax)) tmpMax = 0.0;
|
||||||
|
if (tmpMax <= 0) maxPrice = double.MaxValue; else maxPrice = tmpMax;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch { maxClicks = int.MaxValue; maxResets = int.MaxValue; minPrice = 0.0; maxPrice = double.MaxValue; }
|
||||||
|
|
||||||
|
// post-click delay to avoid duplicate actions
|
||||||
|
// increased delay to click as late as possible (milliseconds)
|
||||||
|
const int postClickDelayMs = 1200;
|
||||||
|
|
||||||
|
// Improved JS snippet: find PUNTA anchor/button by class or text, find nearby numeric timer elements (including svg text) and price
|
||||||
|
const string findScript = @"(function(){
|
||||||
|
function isVisible(el){ if(!el) return false; try{ var r=el.getBoundingClientRect(); var s=window.getComputedStyle(el); return r.width>0 && r.height>0 && s.visibility!=='hidden' && s.display!=='none'; }catch(e){ return false; } }
|
||||||
|
|
||||||
|
// find price
|
||||||
|
var priceText = '';
|
||||||
|
try{ var pEl = document.querySelector('.auction-action-price strong, .auction-action-price'); if(pEl) priceText = (pEl.textContent||pEl.innerText||'').trim(); }catch(e){}
|
||||||
|
var priceVal = null;
|
||||||
|
try{ if(priceText){ var p = priceText.replace('€','').replace(/\./g,'').replace(',','.').match(/\d+(?:\.\d+)?/); if(p) priceVal = p[0]; } }catch(e){}
|
||||||
|
|
||||||
|
// Try to find a bid button that might say PUNTA or INIZIA
|
||||||
|
var btn = document.querySelector('a.auction-btn-bid, a.bid-button, .auction-btn-bid');
|
||||||
|
if(btn && isVisible(btn)){
|
||||||
|
var txt = (btn.textContent||btn.innerText||'').trim();
|
||||||
|
if(/\bINIZIA\b/i.test(txt)){
|
||||||
|
return JSON.stringify({status:'soon', debug: txt, price: priceVal});
|
||||||
|
}
|
||||||
|
if(/\bPUNTA\b/i.test(txt)){
|
||||||
|
// continue to find timer normally
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefer direct countdown element by known class
|
||||||
|
try{
|
||||||
|
var direct = document.querySelector('.text-countdown-progressbar');
|
||||||
|
if(direct && isVisible(direct)){
|
||||||
|
var t = (direct.textContent||'').trim();
|
||||||
|
var m = t.match(/\d+/);
|
||||||
|
if(m) return JSON.stringify({status:'found', timer:m[0], price: priceVal, debug: direct.outerHTML});
|
||||||
|
// if no digits, continue to fallback
|
||||||
|
}
|
||||||
|
}catch(err){ /* ignore */ }
|
||||||
|
|
||||||
|
if(!btn || !isVisible(btn)){
|
||||||
|
var candidates = Array.from(document.querySelectorAll('a, button, div, span')).filter(e=> e && (e.innerText||e.textContent) && /\bPUNTA\b/i.test((e.innerText||e.textContent)) && isVisible(e));
|
||||||
|
if(candidates.length>0) btn = candidates[0];
|
||||||
|
}
|
||||||
|
if(!btn) return JSON.stringify({status:'no-button', price: priceVal});
|
||||||
|
|
||||||
|
var btnRect; try{ btnRect = btn.getBoundingClientRect(); }catch(e){ btnRect = {top:0,left:0,right:0,bottom:0,width:0,height:0}; }
|
||||||
|
|
||||||
|
// collect possible numeric-containing elements, including svg text
|
||||||
|
var nodeList = Array.from(document.querySelectorAll('div, span, p, strong, b, i, em, label, small, a, svg text'));
|
||||||
|
var nums = nodeList.map(e=>{ try{return {el:e, text:(e.textContent||'').trim(), rect:e.getBoundingClientRect(), html:(e.outerHTML||'')}; }catch(err){ return null; }}).filter(x=> x && /\d+/.test(x.text)).map(x=>({el:x.el, text:x.text, rect:x.rect, html:x.html}));
|
||||||
|
|
||||||
|
if(nums.length==0) return JSON.stringify({status:'no-timer', price: priceVal});
|
||||||
|
|
||||||
|
// prefer elements that are visually near and above the button
|
||||||
|
function distanceRect(a,b){ var ax=(a.left+a.right)/2, ay=(a.top+a.bottom)/2; var bx=(b.left+b.right)/2, by=(b.top+b.bottom)/2; return Math.hypot(ax-bx, ay-by); }
|
||||||
|
|
||||||
|
nums.sort(function(a,b){
|
||||||
|
var da = distanceRect(a.rect, btnRect);
|
||||||
|
var db = distanceRect(b.rect, btnRect);
|
||||||
|
var ya = btnRect.top - a.rect.bottom;
|
||||||
|
var yb = btnRect.top - b.rect.bottom;
|
||||||
|
var pref = (ya>=0?0:200) - (yb>=0?0:200);
|
||||||
|
return (da - db) + pref;
|
||||||
|
});
|
||||||
|
|
||||||
|
var best = nums[0];
|
||||||
|
var m = (best.text||'').match(/\d+/);
|
||||||
|
if(!m) return JSON.stringify({status:'no-timer-extracted', debug: best.html, price: priceVal});
|
||||||
|
return JSON.stringify({status:'found', timer:m[0], price: priceVal, debug: best.html});
|
||||||
|
})();";
|
||||||
|
|
||||||
|
const string clickScript = @"(function(){
|
||||||
|
var btn = document.querySelector('a.auction-btn-bid, a.bid-button, .auction-btn-bid');
|
||||||
|
if(!btn){ var candidates = Array.from(document.querySelectorAll('a, button, div, span')).filter(e=> e && (e.innerText||e.textContent) && /\bPUNTA\b/i.test((e.innerText||e.textContent))); if(candidates.length>0) btn=candidates[0]; }
|
||||||
|
if(!btn) return 'no-button';
|
||||||
|
try{ btn.click(); return 'clicked'; }catch(e){ try{ var evt = document.createEvent('MouseEvents'); evt.initEvent('click', true, true); btn.dispatchEvent(evt); return 'dispatched'; }catch(ex){ return 'error:'+ (ex && ex.message?ex.message:ex); } }
|
||||||
|
})();";
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (!token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
string? result = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var op = Dispatcher.InvokeAsync(() => webView.ExecuteScriptAsync(findScript));
|
||||||
|
var innerTask = await op.Task.ConfigureAwait(false);
|
||||||
|
result = await innerTask.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log("Errore JS/interop: " + ex.Message);
|
||||||
|
StopAutomation("Errore JS/interop: " + ex.Message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string json = result ?? "";
|
||||||
|
if (json.Length >= 2 && json[0] == '"' && json[^1] == '"')
|
||||||
|
{
|
||||||
|
json = System.Text.Json.JsonSerializer.Deserialize<string>(result) ?? json;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var doc = JsonDocument.Parse(json);
|
||||||
|
var root = doc.RootElement;
|
||||||
|
var status = root.GetProperty("status").GetString();
|
||||||
|
|
||||||
|
// price may be present
|
||||||
|
string? priceStr = null;
|
||||||
|
double? priceVal = null;
|
||||||
|
if (root.TryGetProperty("price", out var priceEl) && priceEl.ValueKind != JsonValueKind.Null)
|
||||||
|
{
|
||||||
|
priceStr = priceEl.GetString();
|
||||||
|
if (!string.IsNullOrEmpty(priceStr))
|
||||||
|
{
|
||||||
|
if (double.TryParse(priceStr.Replace(',', '.').Trim(), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var pval))
|
||||||
|
{
|
||||||
|
priceVal = pval;
|
||||||
|
Dispatcher.Invoke(() => CurrentPriceText.Text = pval.ToString("0.##") + " €");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status == "soon")
|
||||||
|
{
|
||||||
|
Log("Stato: INIZIA TRA POCO. In attesa che appaia il pulsante PUNTA...");
|
||||||
|
// poll until PUNTA button appears (or cancellation)
|
||||||
|
while (!token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
string? chk = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var op2 = Dispatcher.InvokeAsync(() => webView.ExecuteScriptAsync("(function(){ var b=document.querySelector('a.auction-btn-bid, a.bid-button'); if(!b) return 'no'; var t=(b.textContent||b.innerText||'').trim(); return /PUNTA/i.test(t)?'yes':'no'; })();"));
|
||||||
|
var inner2 = await op2.Task.ConfigureAwait(false);
|
||||||
|
chk = await inner2.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch { chk = null; }
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(chk))
|
||||||
|
{
|
||||||
|
if (chk.Length >= 2 && chk[0] == '"' && chk[^1] == '"') chk = JsonSerializer.Deserialize<string>(chk) ?? chk;
|
||||||
|
if (chk == "yes")
|
||||||
|
{
|
||||||
|
Log("Pulsante PUNTA trovato. Riprendo esecuzione.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(700, token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue; // next main loop iteration
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status == "no-button")
|
||||||
|
{
|
||||||
|
Log("Nessun pulsante PUNTA trovato; arresto");
|
||||||
|
StopAutomation("Nessun pulsante PUNTA trovato; arresto");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status == "no-timer" || status == "no-timer-extracted")
|
||||||
|
{
|
||||||
|
// Pause: wait until timer reappears
|
||||||
|
Log("Timer non trovato: pausa in corso, verrà ripreso quando il timer ricompare");
|
||||||
|
bool resumed = false;
|
||||||
|
while (!token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
string? pollResult = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var op3 = Dispatcher.InvokeAsync(() => webView.ExecuteScriptAsync(findScript));
|
||||||
|
var inner3 = await op3.Task.ConfigureAwait(false);
|
||||||
|
pollResult = await inner3.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch { pollResult = null; }
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(pollResult))
|
||||||
|
{
|
||||||
|
if (pollResult.Length >= 2 && pollResult[0] == '"' && pollResult[^1] == '"') pollResult = JsonSerializer.Deserialize<string>(pollResult) ?? pollResult;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var doc2 = JsonDocument.Parse(pollResult);
|
||||||
|
var st2 = doc2.RootElement.GetProperty("status").GetString();
|
||||||
|
if (st2 == "found" || st2 == "soon")
|
||||||
|
{
|
||||||
|
Log("Timer ricomparso, ripresa.");
|
||||||
|
resumed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (st2 == "no-button")
|
||||||
|
{
|
||||||
|
Log("Nessun pulsante durante pausa; arresto");
|
||||||
|
StopAutomation("Nessun pulsante durante pausa");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(800, token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resumed) break;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status == "found")
|
||||||
|
{
|
||||||
|
var timerValue = root.GetProperty("timer").GetString();
|
||||||
|
|
||||||
|
// only log when timer value actually changes
|
||||||
|
if (timerValue != previousTimer)
|
||||||
|
{
|
||||||
|
Log("Timer rilevato: " + timerValue);
|
||||||
|
|
||||||
|
// detect resets: if price available, enforce min/max bounds
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (previousTimer != null && int.TryParse(previousTimer, out var prev) && int.TryParse(timerValue, out var curr))
|
||||||
|
{
|
||||||
|
if (curr > prev)
|
||||||
|
{
|
||||||
|
resetCount++;
|
||||||
|
Dispatcher.Invoke(() => ResetCountText.Text = resetCount.ToString());
|
||||||
|
Log("Timer resettato (contatore): " + resetCount);
|
||||||
|
// stop if reached limit
|
||||||
|
if (resetCount >= maxResets)
|
||||||
|
{
|
||||||
|
StopAutomation($"Limite reset raggiunto: {resetCount}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
previousTimer = timerValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// price check: if price available, enforce min/max bounds
|
||||||
|
if (priceVal.HasValue)
|
||||||
|
{
|
||||||
|
if (priceVal.Value < minPrice)
|
||||||
|
{
|
||||||
|
Log($"Prezzo {priceVal.Value:0.##}€ sotto limite minimo ({minPrice:0.##}). Pausa.");
|
||||||
|
// wait until price >= minPrice
|
||||||
|
while (!token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
// poll price only
|
||||||
|
string? pricePoll = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var opP = Dispatcher.InvokeAsync(() => webView.ExecuteScriptAsync(@"(function(){ var p=document.querySelector('.auction-action-price strong, .auction-action-price'); if(!p) return null; var t=(p.textContent||p.innerText||'').trim(); var num = t.replace('€','').replace(/\./g,'').replace(',','.').match(/\d+(?:\.\d+)?/); return num?num[0]:null; })();"));
|
||||||
|
var innerP = await opP.Task.ConfigureAwait(false);
|
||||||
|
pricePoll = await innerP.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch { pricePoll = null; }
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(pricePoll))
|
||||||
|
{
|
||||||
|
if (pricePoll.Length >= 2 && pricePoll[0] == '"' && pricePoll[^1] == '"') pricePoll = JsonSerializer.Deserialize<string>(pricePoll) ?? pricePoll;
|
||||||
|
if (double.TryParse(pricePoll.Replace(',', '.'), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var pp))
|
||||||
|
{
|
||||||
|
Dispatcher.Invoke(() => CurrentPriceText.Text = pp.ToString("0.##") + " €");
|
||||||
|
if (pp >= minPrice)
|
||||||
|
{
|
||||||
|
Log("Prezzo salito sopra il minimo; ripresa esecuzione.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(700, token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priceVal.Value > maxPrice)
|
||||||
|
{
|
||||||
|
Log($"Prezzo {priceVal.Value:0.##}€ sopra limite massimo ({maxPrice:0.##}). Pausa.");
|
||||||
|
while (!token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
string? pricePoll = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var opP = Dispatcher.InvokeAsync(() => webView.ExecuteScriptAsync(@"(function(){ var p=document.querySelector('.auction-action-price strong, .auction-action-price'); if(!p) return null; var t=(p.textContent||p.innerText||'').trim(); var num = t.replace('€','').replace(/\./g,'').replace(',','.').match(/\d+(?:\.\d+)?/); return num?num[0]:null; })();"));
|
||||||
|
var innerP = await opP.Task.ConfigureAwait(false);
|
||||||
|
pricePoll = await innerP.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch { pricePoll = null; }
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(pricePoll))
|
||||||
|
{
|
||||||
|
if (pricePoll.Length >= 2 && pricePoll[0] == '"' && pricePoll[^1] == '"') pricePoll = JsonSerializer.Deserialize<string>(pricePoll) ?? pricePoll;
|
||||||
|
if (double.TryParse(pricePoll.Replace(',', '.'), System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.InvariantCulture, out var pp))
|
||||||
|
{
|
||||||
|
Dispatcher.Invoke(() => CurrentPriceText.Text = pp.ToString("0.##") + " €");
|
||||||
|
if (pp <= maxPrice)
|
||||||
|
{
|
||||||
|
Log("Prezzo sceso sotto il massimo; ripresa esecuzione.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(700, token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timerValue == "0")
|
||||||
|
{
|
||||||
|
// immediate click when timer reaches 0 (extreme test)
|
||||||
|
string clickResult;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var op2 = Dispatcher.InvokeAsync(() => webView.ExecuteScriptAsync(clickScript));
|
||||||
|
var inner2 = await op2.Task.ConfigureAwait(false);
|
||||||
|
clickResult = await inner2.ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log("Errore durante click JS: " + ex.Message);
|
||||||
|
StopAutomation("Errore durante click JS: " + ex.Message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clickResult.Length >= 2 && clickResult[0] == '"' && clickResult[^1] == '"')
|
||||||
|
{
|
||||||
|
clickResult = JsonSerializer.Deserialize<string>(clickResult) ?? clickResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
// increment click counter and update UI
|
||||||
|
clickCount++;
|
||||||
|
Dispatcher.Invoke(() => ClickCountText.Text = clickCount.ToString());
|
||||||
|
|
||||||
|
Log("Click eseguito: " + clickResult + " (totale: " + clickCount + ")");
|
||||||
|
|
||||||
|
// stop if reached max clicks
|
||||||
|
if (clickCount >= maxClicks)
|
||||||
|
{
|
||||||
|
StopAutomation($"Limite click raggiunto: {clickCount}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(postClickDelayMs, token).ConfigureAwait(false);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(200, token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(200, token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (JsonException ex)
|
||||||
|
{
|
||||||
|
Log("Errore parsing JSON: " + ex.Message);
|
||||||
|
await Task.Delay(300, token).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Log("Errore loop: " + ex.Message);
|
||||||
|
StopAutomation("Errore loop: " + ex.Message);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
StartButton.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
StartButton.IsEnabled = true;
|
||||||
|
StopButton.IsEnabled = false;
|
||||||
|
StartButton.Opacity = 1.0;
|
||||||
|
StopButton.Opacity = 0.5;
|
||||||
|
});
|
||||||
|
|
||||||
|
Log("Automazione terminata");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnClosed(EventArgs e)
|
||||||
|
{
|
||||||
|
try { _cts?.Cancel(); } catch { }
|
||||||
|
base.OnClosed(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
Mimante/Mimante.csproj
Normal file
17
Mimante/Mimante.csproj
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
<AssemblyName>AutoBidder</AssemblyName>
|
||||||
|
<RootNamespace>AutoBidder</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.3530-prerelease" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
25
Mimante/Mimante.sln
Normal file
25
Mimante/Mimante.sln
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.14.36511.14 d17.14
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mimante", "Mimante.csproj", "{9BBAEF93-DF66-432C-9349-459E272D6538}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{9BBAEF93-DF66-432C-9349-459E272D6538}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{9BBAEF93-DF66-432C-9349-459E272D6538}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{9BBAEF93-DF66-432C-9349-459E272D6538}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{9BBAEF93-DF66-432C-9349-459E272D6538}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {1C55CA56-D270-4D9A-91DA-410BF131E905}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
Reference in New Issue
Block a user