mirror of
https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies.git
synced 2026-07-25 06:14:31 +00:00
Merge 1.7.1 to master
This commit is contained in:
+19
-22
@@ -3,15 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00
|
|||||||
# Visual Studio Version 17
|
# Visual Studio Version 17
|
||||||
VisualStudioVersion = 17.4.33205.214
|
VisualStudioVersion = 17.4.33205.214
|
||||||
MinimumVisualStudioVersion = 10.0.40219.1
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NebulaAuth", "NebulaAuth\NebulaAuth.csproj", "{0FD01700-6D5C-451B-93BA-0860647E8F13}"
|
|
||||||
ProjectSection(ProjectDependencies) = postProject
|
|
||||||
{09F02072-F91D-4DAA-87BC-A34D3E150570} = {09F02072-F91D-4DAA-87BC-A34D3E150570}
|
|
||||||
EndProjectSection
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NebulaAuth.LegacyConverter", "NebulaAuth.LegacyConverter\NebulaAuth.LegacyConverter.csproj", "{2D78A7D9-986A-4890-8A91-7ABD57A91830}"
|
|
||||||
EndProject
|
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SteamLibForked", "SteamLibForked\SteamLibForked.csproj", "{09F02072-F91D-4DAA-87BC-A34D3E150570}"
|
|
||||||
EndProject
|
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "changelog", "changelog", "{161BFADE-FCF3-45D1-82EA-8A1B187529F7}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "changelog", "changelog", "{161BFADE-FCF3-45D1-82EA-8A1B187529F7}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
changelog\1.3.4.html = changelog\1.3.4.html
|
changelog\1.3.4.html = changelog\1.3.4.html
|
||||||
@@ -28,27 +19,33 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "changelog", "changelog", "{
|
|||||||
changelog\1.5.4.html = changelog\1.5.4.html
|
changelog\1.5.4.html = changelog\1.5.4.html
|
||||||
changelog\1.5.5.html = changelog\1.5.5.html
|
changelog\1.5.5.html = changelog\1.5.5.html
|
||||||
changelog\1.5.6.html = changelog\1.5.6.html
|
changelog\1.5.6.html = changelog\1.5.6.html
|
||||||
changelog\1.5.7.html = changelog\1.5.7.html
|
changelog\1.7.1.html = changelog\1.7.1.html
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SteamLibForked", "src\SteamLibForked\SteamLibForked.csproj", "{224F9DB0-3D20-A614-BA2A-12F22B13A2C6}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NebulaAuth.LegacyConverter", "src\NebulaAuth.LegacyConverter\NebulaAuth.LegacyConverter.csproj", "{C8235305-E5C4-155B-C718-C0F239CA3AB7}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NebulaAuth", "src\NebulaAuth\NebulaAuth.csproj", "{C42F63B6-32F7-ED08-5B86-CD51989761AD}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
Release|Any CPU = Release|Any CPU
|
Release|Any CPU = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
{0FD01700-6D5C-451B-93BA-0860647E8F13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{224F9DB0-3D20-A614-BA2A-12F22B13A2C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{0FD01700-6D5C-451B-93BA-0860647E8F13}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{224F9DB0-3D20-A614-BA2A-12F22B13A2C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{0FD01700-6D5C-451B-93BA-0860647E8F13}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{224F9DB0-3D20-A614-BA2A-12F22B13A2C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{0FD01700-6D5C-451B-93BA-0860647E8F13}.Release|Any CPU.Build.0 = Release|Any CPU
|
{224F9DB0-3D20-A614-BA2A-12F22B13A2C6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{2D78A7D9-986A-4890-8A91-7ABD57A91830}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{C8235305-E5C4-155B-C718-C0F239CA3AB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{2D78A7D9-986A-4890-8A91-7ABD57A91830}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{C8235305-E5C4-155B-C718-C0F239CA3AB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{2D78A7D9-986A-4890-8A91-7ABD57A91830}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{C8235305-E5C4-155B-C718-C0F239CA3AB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{2D78A7D9-986A-4890-8A91-7ABD57A91830}.Release|Any CPU.Build.0 = Release|Any CPU
|
{C8235305-E5C4-155B-C718-C0F239CA3AB7}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{09F02072-F91D-4DAA-87BC-A34D3E150570}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{C42F63B6-32F7-ED08-5B86-CD51989761AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{09F02072-F91D-4DAA-87BC-A34D3E150570}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{C42F63B6-32F7-ED08-5B86-CD51989761AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{09F02072-F91D-4DAA-87BC-A34D3E150570}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{C42F63B6-32F7-ED08-5B86-CD51989761AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{09F02072-F91D-4DAA-87BC-A34D3E150570}.Release|Any CPU.Build.0 = Release|Any CPU
|
{C42F63B6-32F7-ED08-5B86-CD51989761AD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Windows;
|
|
||||||
using NebulaAuth.Core;
|
|
||||||
using NebulaAuth.Model;
|
|
||||||
using NebulaAuth.Model.Exceptions;
|
|
||||||
|
|
||||||
namespace NebulaAuth;
|
|
||||||
|
|
||||||
public partial class App
|
|
||||||
{
|
|
||||||
protected override void OnStartup(StartupEventArgs e)
|
|
||||||
{
|
|
||||||
base.OnStartup(e);
|
|
||||||
|
|
||||||
LocManager.Init();
|
|
||||||
LocManager.SetApplicationLocalization(Settings.Instance.Language);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Shell.Initialize();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
var msg = ex.ToString();
|
|
||||||
if (ex is CantAlignTimeException)
|
|
||||||
{
|
|
||||||
msg = LocManager.Get("CantAlignTimeError");
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageBox.Show(msg, "Error", MessageBoxButton.OK, MessageBoxImage.Stop, MessageBoxResult.OK,
|
|
||||||
MessageBoxOptions.DefaultDesktopOnly);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<OutputType>WinExe</OutputType>
|
|
||||||
<TargetFramework>net8.0-windows7.0</TargetFramework>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
<UseWPF>true</UseWPF>
|
|
||||||
<UseWindowsForms>true</UseWindowsForms>
|
|
||||||
<LangVersion>latest</LangVersion>
|
|
||||||
<SatelliteResourceLanguages>en;ru;ua</SatelliteResourceLanguages>
|
|
||||||
<ApplicationIcon>Theme\lock.ico</ApplicationIcon>
|
|
||||||
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
|
|
||||||
<AssemblyVersion>1.5.7</AssemblyVersion>
|
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Remove="Theme\1140x641.jpg" />
|
|
||||||
<None Remove="Theme\Background.jpg" />
|
|
||||||
<None Remove="Theme\lock.ico" />
|
|
||||||
<None Remove="Theme\SplashScreen.png" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="Autoupdater.NET.Official" Version="1.9.2" />
|
|
||||||
<PackageReference Include="CodingSeb.Localization.JsonFileLoader" Version="1.3.1" />
|
|
||||||
<PackageReference Include="CodingSeb.Localization.WPF" Version="1.3.3" />
|
|
||||||
<PackageReference Include="CodingSebLocalization.Fody" Version="1.3.1" />
|
|
||||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.2" />
|
|
||||||
<PackageReference Include="MaterialDesignColors" Version="2.1.4" />
|
|
||||||
<PackageReference Include="MaterialDesignThemes" Version="4.9.0" />
|
|
||||||
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.122" />
|
|
||||||
<PackageReference Include="NLog" Version="5.3.4" />
|
|
||||||
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.14" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Resource Include="Theme\Background.jpg">
|
|
||||||
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
|
||||||
</Resource>
|
|
||||||
<Resource Include="Theme\lock.ico" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\SteamLibForked\SteamLibForked.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<SplashScreen Include="Theme\SplashScreen.png" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Update="View\Dialogs\LoginAgainOnImportDialog.xaml.cs">
|
|
||||||
<SubType>Code</SubType>
|
|
||||||
</Compile>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Update="NLog.config">
|
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
<None Update="localization.loc.json">
|
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
|
||||||
|
|
||||||
<!--Card-->
|
|
||||||
<SolidColorBrush x:Key="MaterialDesignCardBackground" Color="#26292E" />
|
|
||||||
<SolidColorBrush x:Key="PrimaryHueMidBrush" Color="#FF9056B1" />
|
|
||||||
<SolidColorBrush x:Key="PrimaryHueLightBrush" Color="#FF9C70B5" />
|
|
||||||
<SolidColorBrush x:Key="PrimaryHueDarkForegroundBrush" Color="#FF851BC1" />
|
|
||||||
</ResourceDictionary>
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 365 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 490 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 906 KiB |
@@ -1,48 +0,0 @@
|
|||||||
<UserControl x:Class="NebulaAuth.View.Dialogs.LoginAgainDialog"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:theme="clr-namespace:NebulaAuth.Theme"
|
|
||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
|
||||||
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
|
|
||||||
xmlns:model="clr-namespace:NebulaAuth.Model"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
theme:FontScaleWindow.Scale="0.9" theme:FontScaleWindow.ResizeFont="True"
|
|
||||||
Foreground="WhiteSmoke"
|
|
||||||
d:DataContext="{d:DesignInstance other:LoginAgainVM}"
|
|
||||||
Background="{DynamicResource WindowBackground}">
|
|
||||||
|
|
||||||
<Grid MinHeight="100" MinWidth="300" Margin="20">
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<TextBlock theme:FontScaleWindow.Scale="0.9" theme:FontScaleWindow.ResizeFont="True" Margin="10,0,0,0"
|
|
||||||
HorizontalAlignment="Left">
|
|
||||||
<Run Text="{Tr LoginAgainDialog.LoginFor, IsDynamic=False}" />
|
|
||||||
<Run FontWeight="Bold" Text="{Binding UserName}" />
|
|
||||||
</TextBlock>
|
|
||||||
<TextBox Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Margin="10,10,10,0" Grid.Row="1"
|
|
||||||
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
|
|
||||||
materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}" />
|
|
||||||
<CheckBox Grid.Row="2" Margin="10,10,10,0" IsEnabled="{Binding Source={x:Static model:PHandler.IsPasswordSet}}"
|
|
||||||
IsChecked="{Binding SavePassword}" Content="{Tr LoginAgainDialog.SaveEncryptedPassword}" />
|
|
||||||
<Grid Grid.Row="3" Margin="10,10,10,0">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition />
|
|
||||||
<ColumnDefinition />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<Button IsDefault="True" IsEnabled="{Binding IsFormValid}" Margin="0,0,5,5"
|
|
||||||
Style="{StaticResource MaterialDesignOutlinedButton}"
|
|
||||||
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
|
|
||||||
CommandParameter="{StaticResource True}" Content="{Tr LoginAgainDialog.LoginButton}" />
|
|
||||||
<Button IsCancel="True" Grid.Column="1" Margin="5,0,0,5"
|
|
||||||
Style="{StaticResource MaterialDesignOutlinedButton}"
|
|
||||||
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
|
|
||||||
CommandParameter="{StaticResource False}" Content="{Tr LoginAgainDialog.CancelButton}" />
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</UserControl>
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
<UserControl x:Class="NebulaAuth.View.Dialogs.LoginAgainOnImportDialog"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:theme="clr-namespace:NebulaAuth.Theme"
|
|
||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
|
||||||
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
|
|
||||||
xmlns:model="clr-namespace:NebulaAuth.Model"
|
|
||||||
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
theme:FontScaleWindow.Scale="0.9" theme:FontScaleWindow.ResizeFont="True"
|
|
||||||
Foreground="WhiteSmoke"
|
|
||||||
d:DataContext="{d:DesignInstance other:LoginAgainOnImportVM}"
|
|
||||||
Background="{DynamicResource WindowBackground}">
|
|
||||||
|
|
||||||
<Grid MinHeight="100" MinWidth="300" Margin="20">
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<TextBlock theme:FontScaleWindow.Scale="0.9" theme:FontScaleWindow.ResizeFont="True" Margin="10,0,0,0"
|
|
||||||
HorizontalAlignment="Left">
|
|
||||||
<Run Text="{Tr LoginAgainDialog.LoginFor, IsDynamic=False}" />
|
|
||||||
<Run FontWeight="Bold" Text="{Binding UserName}" />
|
|
||||||
</TextBlock>
|
|
||||||
<TextBox Text="{Binding Password, UpdateSourceTrigger=PropertyChanged}" Margin="10,10,10,0" Grid.Row="1"
|
|
||||||
Style="{StaticResource MaterialDesignFloatingHintTextBox}"
|
|
||||||
materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}" />
|
|
||||||
<ComboBox ToolTip="{Tr LoginAgainDialog.ProxyToolTip}" Grid.Row="2" Margin="10,18,10,0"
|
|
||||||
materialDesign:HintAssist.Hint="{Tr Common.Proxy}" ItemsSource="{Binding Proxies}"
|
|
||||||
SelectedItem="{Binding SelectedProxy}">
|
|
||||||
<ItemsControl.ItemTemplate>
|
|
||||||
<DataTemplate DataType="{x:Type entities:MaProxy}">
|
|
||||||
<TextBlock Text="{Binding Converter={StaticResource ProxyTextConverter}, Mode=OneWay}" />
|
|
||||||
</DataTemplate>
|
|
||||||
</ItemsControl.ItemTemplate>
|
|
||||||
<UIElement.InputBindings>
|
|
||||||
<KeyBinding Key="Delete" Command="{Binding RemoveProxyCommand}" />
|
|
||||||
</UIElement.InputBindings>
|
|
||||||
</ComboBox>
|
|
||||||
<CheckBox Grid.Row="3" Margin="10,10,10,0" IsEnabled="{Binding Source={x:Static model:PHandler.IsPasswordSet}}"
|
|
||||||
IsChecked="{Binding SavePassword}" Content="{Tr LoginAgainDialog.SaveEncryptedPassword}" />
|
|
||||||
<CheckBox Grid.Row="4" Margin="10,10,10,0" IsEnabled="{Binding MafileHasProxy}"
|
|
||||||
Content="{Tr LoginAgainDialog.UseMafileProxy}" IsChecked="{Binding UseMafileProxy}" />
|
|
||||||
<Grid Grid.Row="5" Margin="10,10,10,0">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition />
|
|
||||||
<ColumnDefinition />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<Button IsEnabled="{Binding IsFormValid}" IsDefault="True" Margin="0,5,5,5"
|
|
||||||
Style="{StaticResource MaterialDesignOutlinedButton}"
|
|
||||||
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
|
|
||||||
CommandParameter="{StaticResource True}" Content="{Tr LoginAgainDialog.LoginButton}" />
|
|
||||||
<Button IsCancel="True" Grid.Column="1" Margin="5,5,0,5"
|
|
||||||
Style="{StaticResource MaterialDesignOutlinedButton}"
|
|
||||||
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
|
|
||||||
CommandParameter="{StaticResource False}" Content="{Tr LoginAgainDialog.CancelButton}" />
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</UserControl>
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows;
|
|
||||||
using System.Windows.Media.Imaging;
|
|
||||||
using SteamLib.Core.Interfaces;
|
|
||||||
using SteamLib.Exceptions;
|
|
||||||
|
|
||||||
namespace NebulaAuth.View.Dialogs;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Логика взаимодействия для WaitLoginDialog.xaml
|
|
||||||
/// </summary>
|
|
||||||
public partial class WaitLoginDialog : ICaptchaResolver
|
|
||||||
{
|
|
||||||
private TaskCompletionSource<string> _tcs = new();
|
|
||||||
|
|
||||||
public WaitLoginDialog()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string> Resolve(Uri imageUrl, HttpClient client)
|
|
||||||
{
|
|
||||||
CaptchaGrid.Visibility = Visibility.Visible;
|
|
||||||
var stream = await client.GetStreamAsync(imageUrl);
|
|
||||||
return await Application.Current.Dispatcher.Invoke(async () =>
|
|
||||||
{
|
|
||||||
var image = await LoadImage(stream);
|
|
||||||
CaptchaImage.Source = image;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await _tcs.Task;
|
|
||||||
}
|
|
||||||
catch (TaskCanceledException)
|
|
||||||
{
|
|
||||||
throw new LoginException(LoginError.CaptchaRequired);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<BitmapImage> LoadImage(Stream stream)
|
|
||||||
{
|
|
||||||
using var ms = new MemoryStream();
|
|
||||||
await stream.CopyToAsync(ms);
|
|
||||||
ms.Position = 0;
|
|
||||||
|
|
||||||
var image = new BitmapImage();
|
|
||||||
|
|
||||||
image.BeginInit();
|
|
||||||
image.CacheOption = BitmapCacheOption.OnLoad;
|
|
||||||
image.StreamSource = ms;
|
|
||||||
image.EndInit();
|
|
||||||
await stream.DisposeAsync();
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CancelButton_OnClick(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
_tcs.SetCanceled();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SendCaptchaBtn_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(CaptchaTB.Text)) return;
|
|
||||||
var oldTcs = _tcs;
|
|
||||||
_tcs = new TaskCompletionSource<string>();
|
|
||||||
oldTcs.SetResult(CaptchaTB.Text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,166 +0,0 @@
|
|||||||
<UserControl x:Class="NebulaAuth.View.LinkerView"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
|
||||||
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
Foreground="WhiteSmoke"
|
|
||||||
FontFamily="{materialDesign:MaterialDesignFont}"
|
|
||||||
MinHeight="640"
|
|
||||||
MinWidth="400"
|
|
||||||
Background="{DynamicResource WindowBackground}"
|
|
||||||
d:DataContext="{d:DesignInstance other:LinkAccountVM}"
|
|
||||||
FontSize="18">
|
|
||||||
<d:DesignerProperties.DesignStyle>
|
|
||||||
<Style TargetType="UserControl">
|
|
||||||
<Setter Property="Background" Value="{DynamicResource WindowBackground}" />
|
|
||||||
</Style>
|
|
||||||
</d:DesignerProperties.DesignStyle>
|
|
||||||
<Grid>
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="*" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
|
|
||||||
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<Grid.Resources>
|
|
||||||
<Style TargetType="materialDesign:PackIcon">
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=StackPanel}, Path=Tag}"
|
|
||||||
Value="False">
|
|
||||||
<Setter Property="Foreground" Value="IndianRed" />
|
|
||||||
<Setter Property="Kind" Value="CheckBoxOutlineBlank" />
|
|
||||||
</DataTrigger>
|
|
||||||
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=StackPanel}, Path=Tag}"
|
|
||||||
Value="True">
|
|
||||||
<Setter Property="Foreground" Value="Green" />
|
|
||||||
<Setter Property="Kind" Value="CheckBoxOutline" />
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
|
|
||||||
</Style>
|
|
||||||
<Style TargetType="TextBlock">
|
|
||||||
<Style.Triggers>
|
|
||||||
<DataTrigger Binding="{Binding RelativeSource={RelativeSource AncestorType=StackPanel}, Path=Tag}"
|
|
||||||
Value="True">
|
|
||||||
<Setter Property="Foreground" Value="Gray" />
|
|
||||||
</DataTrigger>
|
|
||||||
</Style.Triggers>
|
|
||||||
</Style>
|
|
||||||
</Grid.Resources>
|
|
||||||
<Grid Margin="10">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition />
|
|
||||||
<ColumnDefinition />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<StackPanel Orientation="Horizontal">
|
|
||||||
<TextBlock FontStyle="Normal" Foreground="DarkGray" HorizontalAlignment="Left"
|
|
||||||
VerticalAlignment="Center" FontSize="18" Text="{Tr LinkerDialog.Title}" />
|
|
||||||
<TextBlock Margin="5,0,0,0" VerticalAlignment="Center" FontSize="12">
|
|
||||||
<Hyperlink Command="{Binding OpenTroubleshootingCommand}">
|
|
||||||
<Run Text="{Tr LinkerDialog.GotErrorHyperlinkText, IsDynamic=False}" />
|
|
||||||
</Hyperlink>
|
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
<Button Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}"
|
|
||||||
HorizontalAlignment="Right" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}">
|
|
||||||
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
|
|
||||||
</Button>
|
|
||||||
</Grid>
|
|
||||||
<Separator Background="DarkGray" Grid.Row="1" />
|
|
||||||
<Grid Grid.Row="2" Margin="10,20,10,10">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<ComboBox Padding="10" FontSize="16" Style="{StaticResource MaterialDesignOutlinedComboBox}"
|
|
||||||
SelectedValue="{Binding SelectedProxy}" ItemsSource="{Binding Proxies}"
|
|
||||||
materialDesign:HintAssist.Hint="{Tr LinkerDialog.Proxy}"
|
|
||||||
IsEnabled="{Binding IsPasswordFieldVisible}">
|
|
||||||
<ComboBox.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<TextBlock>
|
|
||||||
<Run Text="{Binding Key, Mode=OneWay}" /><Run Text=":" />
|
|
||||||
<Run Text="{Binding Value.Address, Mode=OneWay}" />
|
|
||||||
</TextBlock>
|
|
||||||
</DataTemplate>
|
|
||||||
</ComboBox.ItemTemplate>
|
|
||||||
</ComboBox>
|
|
||||||
<Button Grid.Column="1" IsEnabled="{Binding IsPasswordFieldVisible}" Margin="5,0,0,0"
|
|
||||||
Command="{Binding ResetProxyCommand}" Content="{materialDesign:PackIcon Trash}" />
|
|
||||||
</Grid>
|
|
||||||
<StackPanel Grid.Row="3" Orientation="Horizontal" Margin="7" Tag="{Binding IsLogin}">
|
|
||||||
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0" />
|
|
||||||
<TextBlock FontSize="16" Text="{Tr LinkerDialog.Authorization}" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Grid.Row="4" Orientation="Horizontal" Margin="7" Tag="{Binding IsEmailCode}">
|
|
||||||
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0" />
|
|
||||||
<TextBlock FontSize="16" Text="{Tr LinkerDialog.EmailCode}" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Grid.Row="5" Orientation="Horizontal" Margin="7" Tag="{Binding IsPhoneNumber}">
|
|
||||||
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0" />
|
|
||||||
<TextBlock FontSize="16" Text="{Tr LinkerDialog.PhoneNumber}" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Grid.Row="6" Orientation="Horizontal" Margin="7" Tag="{Binding IsEmailConfirmation}">
|
|
||||||
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0" />
|
|
||||||
<TextBlock FontSize="16" Text="{Tr LinkerDialog.EmailLink}" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Grid.Row="7" Orientation="Horizontal" Margin="7" Tag="{Binding IsLinkCode}">
|
|
||||||
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0" />
|
|
||||||
<TextBlock FontSize="16" Text="{Tr LinkerDialog.SmsOrCode}" />
|
|
||||||
</StackPanel>
|
|
||||||
<StackPanel Grid.Row="8" Orientation="Horizontal" Margin="7" Tag="{Binding IsCompleted}">
|
|
||||||
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0" />
|
|
||||||
<TextBlock FontSize="16" Text="{Tr LinkerDialog.Completed}" />
|
|
||||||
</StackPanel>
|
|
||||||
<Grid Grid.Row="9" Margin="10,20,10,10">
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="*" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<TextBox Padding="7" Text="{Binding FieldText}"
|
|
||||||
Visibility="{Binding IsFieldVisible, Converter={StaticResource BooleanToVisibilityConverter}}"
|
|
||||||
Style="{StaticResource MaterialDesignOutlinedTextBox}" FontSize="16" Margin="0,0,0,10" />
|
|
||||||
<TextBox Grid.Row="1" Padding="7" Text="{Binding PassFieldText}"
|
|
||||||
Visibility="{Binding IsPasswordFieldVisible, Converter={StaticResource BooleanToVisibilityConverter}}"
|
|
||||||
Style="{StaticResource MaterialDesignOutlinedTextBox}" FontSize="16" />
|
|
||||||
<GroupBox Grid.Row="2" MaxWidth="400" BorderBrush="{StaticResource PrimaryHueMidBrush}"
|
|
||||||
VerticalAlignment="Bottom" BorderThickness="1" Margin="0,10,0,0" Padding="5">
|
|
||||||
<GroupBox.HeaderTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<TextBlock Text="{Tr LinkerDialog.Message}" FontSize="16" Foreground="GhostWhite" />
|
|
||||||
</DataTemplate>
|
|
||||||
</GroupBox.HeaderTemplate>
|
|
||||||
<Grid>
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<TextBlock Foreground="GhostWhite" FontSize="16" Text="{Binding HintText}"
|
|
||||||
HorizontalAlignment="Stretch" TextWrapping="Wrap" />
|
|
||||||
<Button Command="{Binding CopyCodeCommand}"
|
|
||||||
Visibility="{Binding IsCompleted, Converter={StaticResource BooleanToVisibilityConverter}}"
|
|
||||||
Grid.Row="1">
|
|
||||||
<materialDesign:PackIcon Kind="ContentCopy" />
|
|
||||||
</Button>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
</GroupBox>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Button Grid.Row="10" Height="35" FontSize="17" Command="{Binding ProceedCommand}"
|
|
||||||
Content="{Tr LinkerDialog.ProceedButton}" />
|
|
||||||
</Grid>
|
|
||||||
</UserControl>
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
<UserControl x:Class="NebulaAuth.View.SettingsView"
|
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
|
||||||
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
|
|
||||||
mc:Ignorable="d"
|
|
||||||
d:DesignHeight="650"
|
|
||||||
Foreground="WhiteSmoke"
|
|
||||||
FontFamily="{materialDesign:MaterialDesignFont}"
|
|
||||||
MinHeight="600"
|
|
||||||
MinWidth="400"
|
|
||||||
|
|
||||||
MaxWidth="200"
|
|
||||||
d:DataContext="{d:DesignInstance other:SettingsVM}"
|
|
||||||
Background="{DynamicResource WindowBackground}">
|
|
||||||
<Grid>
|
|
||||||
<Grid Margin="15,10,15,15">
|
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<Grid>
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition />
|
|
||||||
<ColumnDefinition />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<TextBlock FontStyle="Normal" Foreground="DarkGray" HorizontalAlignment="Left"
|
|
||||||
VerticalAlignment="Center" FontSize="18" Text="{Tr SettingsDialog.Title}" />
|
|
||||||
<Button IsCancel="True" Grid.Column="1" Width="30" Height="30"
|
|
||||||
Style="{StaticResource MaterialDesignIconForegroundButton}" HorizontalAlignment="Right"
|
|
||||||
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}">
|
|
||||||
<materialDesign:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed" />
|
|
||||||
</Button>
|
|
||||||
</Grid>
|
|
||||||
<Separator Grid.Row="1" Margin="0,10,0,0" />
|
|
||||||
|
|
||||||
<StackPanel Grid.Row="2" Margin="10,30,10,10" Orientation="Vertical" HorizontalAlignment="Center">
|
|
||||||
<ComboBox Style="{StaticResource MaterialDesignOutlinedComboBox}" FontSize="16"
|
|
||||||
ItemsSource="{Binding BackgroundModes}" DisplayMemberPath="Value" SelectedValuePath="Key"
|
|
||||||
SelectedValue="{Binding BackgroundMode}"
|
|
||||||
materialDesign:HintAssist.Hint="{Tr SettingsDialog.BackgroundHint}" />
|
|
||||||
<ComboBox Style="{StaticResource MaterialDesignFloatingHintComboBox}" Margin="0,20,0,0" FontSize="16"
|
|
||||||
ItemsSource="{Binding Languages}" DisplayMemberPath="Value" SelectedValuePath="Key"
|
|
||||||
SelectedValue="{Binding Language}" materialDesign:HintAssist.Hint="{Tr LanguageWord}" />
|
|
||||||
<CheckBox Margin="0,10,0,0" FontSize="16" IsChecked="{Binding HideToTray}"
|
|
||||||
Content="{Tr SettingsDialog.MinimizeToTray}" />
|
|
||||||
<CheckBox Margin="0,10,0,0" FontSize="16" IsChecked="{Binding UseIcon}"
|
|
||||||
Content="{Tr SettingsDialog.UseIndicator}" ToolTip="{Tr SettingsDialog.UseIndicatorHint}" />
|
|
||||||
<materialDesign:ColorPicker IsEnabled="{Binding UseIcon}" Color="{Binding IconColor, Delay=50}" />
|
|
||||||
<CheckBox Margin="0,10,0,5" FontSize="16" IsChecked="{Binding UseBackground}"
|
|
||||||
Content="{Tr SettingsDialog.UseCustomColor}" />
|
|
||||||
|
|
||||||
<materialDesign:ColorPicker Height="100" IsEnabled="{Binding UseBackground}"
|
|
||||||
Color="{Binding BackgroundColor, Delay=50}" />
|
|
||||||
<Grid>
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<PasswordBox MinWidth="250" materialDesign:PasswordBoxAssist.Password="{Binding Password}"
|
|
||||||
Height="Auto" VerticalAlignment="Center" FontSize="16" Margin="0,10,0,0"
|
|
||||||
Style="{StaticResource MaterialDesignFloatingHintRevealPasswordBox}"
|
|
||||||
materialDesign:HintAssist.Hint="{Tr SettingsDialog.PasswordBox.CurrentCryptPassword}"
|
|
||||||
materialDesign:HintAssist.HelperText="{Tr SettingsDialog.PasswordBox.Hint}" />
|
|
||||||
<Button Command="{Binding SetPasswordCommand}" Margin="5,0,0,0" VerticalAlignment="Bottom"
|
|
||||||
Grid.Column="1">
|
|
||||||
<materialDesign:PackIcon Kind="ContentSave" />
|
|
||||||
</Button>
|
|
||||||
</Grid>
|
|
||||||
<CheckBox Margin="0,20,0,0" FontSize="16" IsChecked="{Binding LegacyMode}"
|
|
||||||
Content="{Tr SettingsDialog.LegacyMafileMode}"
|
|
||||||
ToolTip="{Tr SettingsDialog.LegacyMafileModeHint}" />
|
|
||||||
<!--<CheckBox IsEnabled="False" Margin="0,10,0,0" FontSize="16" IsChecked="{Binding AllowAutoUpdate}" Content="{Tr SettingsDialog.AllowAutoUpdate}"/>-->
|
|
||||||
<CheckBox Margin="0,10,0,0" FontSize="16" IsChecked="{Binding UseAccountNameAsMafileName}">
|
|
||||||
<TextBlock TextWrapping="WrapWithOverflow" Text="{Tr SettingsDialog.UseAccountName}" />
|
|
||||||
</CheckBox>
|
|
||||||
<CheckBox IsChecked="{Binding IgnorePatchTuesdayErrors}" Margin="0,10,0,0" FontSize="16"
|
|
||||||
ToolTip="{Tr SettingsDialog.IgnorePatchTuesdayErrorsHint}">
|
|
||||||
<TextBlock TextWrapping="WrapWithOverflow" Text="{Tr SettingsDialog.IgnorePatchTuesdayErrors}" />
|
|
||||||
</CheckBox>
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</UserControl>
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
namespace NebulaAuth.View;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Логика взаимодействия для SettingsView.xaml
|
|
||||||
/// </summary>
|
|
||||||
public partial class SettingsView
|
|
||||||
{
|
|
||||||
public SettingsView()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,457 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Net.Http;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows;
|
|
||||||
using AchiesUtilities.Collections;
|
|
||||||
using AchiesUtilities.Web.Proxy;
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
|
||||||
using CommunityToolkit.Mvvm.Input;
|
|
||||||
using MaterialDesignThemes.Wpf;
|
|
||||||
using NebulaAuth.Core;
|
|
||||||
using NebulaAuth.Model;
|
|
||||||
using NebulaAuth.Model.Entities;
|
|
||||||
using NebulaAuth.Utility;
|
|
||||||
using NLog;
|
|
||||||
using SteamLib;
|
|
||||||
using SteamLib.Account;
|
|
||||||
using SteamLib.Authentication;
|
|
||||||
using SteamLib.Authentication.LoginV2;
|
|
||||||
using SteamLib.Core.Interfaces;
|
|
||||||
using SteamLib.Exceptions;
|
|
||||||
using SteamLib.Exceptions.Mobile;
|
|
||||||
using SteamLib.ProtoCore.Exceptions;
|
|
||||||
using SteamLib.SteamMobile.AuthenticatorLinker;
|
|
||||||
using SteamLib.Web;
|
|
||||||
using ILogger = Microsoft.Extensions.Logging.ILogger;
|
|
||||||
|
|
||||||
namespace NebulaAuth.ViewModel.Other;
|
|
||||||
|
|
||||||
public partial class LinkAccountVM : ObservableObject, IEmailProvider, IPhoneNumberProvider, ISmsCodeProvider
|
|
||||||
{
|
|
||||||
private const string LOCALIZATION_KEY = "LinkVM";
|
|
||||||
private static Logger Logger => Shell.Logger;
|
|
||||||
private static ILogger Logger2 => Shell.ExtensionsLogger;
|
|
||||||
|
|
||||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
|
||||||
public LinkAccountVM()
|
|
||||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
|
|
||||||
{
|
|
||||||
if (MaClient.DefaultProxy != null)
|
|
||||||
{
|
|
||||||
var def = Proxies.FirstOrDefault(p => p.Value.Equals(MaClient.DefaultProxy));
|
|
||||||
if (def.Value != null!)
|
|
||||||
{
|
|
||||||
SelectedProxy = def;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand(AllowConcurrentExecutions = true, CanExecute = nameof(CanProceed))]
|
|
||||||
public async Task Proceed()
|
|
||||||
{
|
|
||||||
if (IsCompleted)
|
|
||||||
DialogHost.Close(null);
|
|
||||||
|
|
||||||
CanProceed = false;
|
|
||||||
|
|
||||||
#region Login
|
|
||||||
|
|
||||||
if (IsLogin == false)
|
|
||||||
{
|
|
||||||
SetProxy();
|
|
||||||
ClearCookies();
|
|
||||||
_loginV2ExecutorOptions = new LoginV2ExecutorOptions(LoginV2Executor.NullConsumer, Client)
|
|
||||||
{
|
|
||||||
DeviceDetails = LoginV2ExecutorOptions.GetMobileDefaultDevice(),
|
|
||||||
WebsiteId = "Mobile",
|
|
||||||
EmailAuthProvider = this,
|
|
||||||
Logger = Logger2
|
|
||||||
};
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
IsLogin = true;
|
|
||||||
var userName = FieldText;
|
|
||||||
var pass = PassFieldText;
|
|
||||||
_password = pass;
|
|
||||||
FieldText = string.Empty;
|
|
||||||
IsFieldVisible = false;
|
|
||||||
HintText = string.Empty;
|
|
||||||
_sessionData =
|
|
||||||
(MobileSessionData) await LoginV2Executor.DoLogin(_loginV2ExecutorOptions, userName, pass);
|
|
||||||
Handler.CookieContainer.SetSteamMobileCookiesWithMobileToken(_sessionData);
|
|
||||||
IsEmailCode = true;
|
|
||||||
}
|
|
||||||
catch (EResultException ex)
|
|
||||||
{
|
|
||||||
Logger.Error(ex, "Link exception on login");
|
|
||||||
HintText = GetLocalizationOrDefault("CantLogin") + ErrorTranslatorHelper.TranslateEResult(ex.Result);
|
|
||||||
InvokeOnDispatcher(ResetState);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (LoginException ex)
|
|
||||||
{
|
|
||||||
Logger.Error(ex, "Link exception on login");
|
|
||||||
HintText = GetLocalizationOrDefault("CantLogin") + ErrorTranslatorHelper.TranslateLoginError(ex.Error);
|
|
||||||
InvokeOnDispatcher(ResetState);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.Error(ex, "Link exception on login");
|
|
||||||
HintText = GetLocalizationOrDefault("CantLogin") + ex.Message;
|
|
||||||
InvokeOnDispatcher(ResetState);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsEmailCode == false)
|
|
||||||
{
|
|
||||||
_emailCodeTcs.SetResult(FieldText);
|
|
||||||
HintText = string.Empty;
|
|
||||||
FieldText = string.Empty;
|
|
||||||
_emailCodeTcs = new TaskCompletionSource<string>();
|
|
||||||
IsFieldVisible = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
if (_isLinkStarted)
|
|
||||||
goto linkStarted;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
_isLinkStarted = true;
|
|
||||||
var linkOptions = new LinkOptions(Client, LoginV2Executor.NullConsumer, this,
|
|
||||||
null, this, this, Backup, Logger2);
|
|
||||||
_linker = new SteamAuthenticatorLinker(linkOptions);
|
|
||||||
var result = await _linker.LinkAccount(_sessionData);
|
|
||||||
IsLinkCode = true;
|
|
||||||
IsCompleted = true;
|
|
||||||
var mafile = Mafile.FromMobileDataExtended(result);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (SelectedProxy.HasValue)
|
|
||||||
mafile.Proxy = new MaProxy(SelectedProxy.Value.Key, SelectedProxy.Value.Value);
|
|
||||||
if (Settings.Instance.IsPasswordSet)
|
|
||||||
mafile.Password = PHandler.Encrypt(_password);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.Error(ex, "Error during saving Nebula data to mafile");
|
|
||||||
}
|
|
||||||
|
|
||||||
Storage.SaveMafile(mafile);
|
|
||||||
File.Delete(Path.Combine("mafiles_backup", mafile.AccountName + ".mafile"));
|
|
||||||
HintText =
|
|
||||||
string.Format(GetLocalizationOrDefault("MafileLinked"),
|
|
||||||
mafile.RevocationCode,
|
|
||||||
mafile.SessionData?.SteamId.Steam64);
|
|
||||||
|
|
||||||
_rCode = mafile.RevocationCode ?? string.Empty;
|
|
||||||
CanProceed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (AuthenticatorLinkerException ex)
|
|
||||||
{
|
|
||||||
Logger.Error(ex, "Link exception");
|
|
||||||
HintText = $"{GetLocalizationCommon("Error")}: {ErrorTranslatorHelper.TranslateLinkerError(ex.Error)}";
|
|
||||||
InvokeOnDispatcher(ResetState);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (HttpRequestException ex)
|
|
||||||
{
|
|
||||||
var msg = ex.StatusCode?.ToString() ?? ex.Message;
|
|
||||||
HintText = $"{GetLocalizationCommon("RequestError")}: {msg}";
|
|
||||||
InvokeOnDispatcher(ResetState);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (EResultException ex)
|
|
||||||
{
|
|
||||||
Logger.Error(ex, "Link exception");
|
|
||||||
HintText = GetLocalizationOrDefault("ErrorWithCode") + ErrorTranslatorHelper.TranslateEResult(ex.Result);
|
|
||||||
InvokeOnDispatcher(ResetState);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Logger.Error(ex, "Link exception");
|
|
||||||
HintText = GetLocalizationOrDefault("UnknownError") + ex.Message;
|
|
||||||
InvokeOnDispatcher(ResetState);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
linkStarted:
|
|
||||||
if (IsPhoneNumber == false)
|
|
||||||
{
|
|
||||||
var phoneText = FieldText;
|
|
||||||
FieldText = string.Empty;
|
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(phoneText))
|
|
||||||
{
|
|
||||||
HintText = string.Empty;
|
|
||||||
IsFieldVisible = false;
|
|
||||||
_phoneNumberTcs.SetResult(null);
|
|
||||||
_phoneNumberTcs = new TaskCompletionSource<long?>();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(phoneText) && phoneText.Length >= 4 &&
|
|
||||||
long.TryParse(phoneText, out var phone))
|
|
||||||
{
|
|
||||||
HintText = string.Empty;
|
|
||||||
IsFieldVisible = false;
|
|
||||||
_phoneNumberTcs.SetResult(phone);
|
|
||||||
_phoneNumberTcs = new TaskCompletionSource<long?>();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
HintText = GetLocalizationOrDefault("PleaseEnterCorrectPhone");
|
|
||||||
CanProceed = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsEmailConfirmation == false)
|
|
||||||
{
|
|
||||||
HintText = string.Empty;
|
|
||||||
_emailConfTcs.SetResult();
|
|
||||||
_emailConfTcs = new TaskCompletionSource();
|
|
||||||
CanProceed = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (IsLinkCode == false)
|
|
||||||
{
|
|
||||||
var linkCode = FieldText;
|
|
||||||
FieldText = string.Empty;
|
|
||||||
if (!string.IsNullOrWhiteSpace(linkCode) && linkCode.Length >= 4)
|
|
||||||
{
|
|
||||||
HintText = string.Empty;
|
|
||||||
IsFieldVisible = false;
|
|
||||||
_linkCodeTcs.SetResult(linkCode);
|
|
||||||
_linkCodeTcs = new TaskCompletionSource<string>();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
HintText = GetLocalizationOrDefault("PleaseEnterCorrectCode");
|
|
||||||
CanProceed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
public void ResetProxy()
|
|
||||||
{
|
|
||||||
if (IsPasswordFieldVisible == false) return;
|
|
||||||
SelectedProxy = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InvokeOnDispatcher(Action action)
|
|
||||||
{
|
|
||||||
Application.Current.Dispatcher.BeginInvoke(action, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ResetState()
|
|
||||||
{
|
|
||||||
PassFieldText = string.Empty;
|
|
||||||
IsLogin = false;
|
|
||||||
IsFieldVisible = true;
|
|
||||||
IsEmailCode = false;
|
|
||||||
_isLinkStarted = false;
|
|
||||||
IsPhoneNumber = false;
|
|
||||||
IsEmailConfirmation = false;
|
|
||||||
CanProceed = true;
|
|
||||||
_emailCodeTcs = new TaskCompletionSource<string>();
|
|
||||||
_rCode = string.Empty;
|
|
||||||
_password = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Backup(MobileDataExtended data)
|
|
||||||
{
|
|
||||||
if (Directory.Exists("mafiles_backup") == false)
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory("mafiles_backup");
|
|
||||||
}
|
|
||||||
|
|
||||||
var json = NebulaSerializer.SerializeMafile(data, null);
|
|
||||||
File.WriteAllText(Path.Combine("mafiles_backup", data.AccountName + ".mafile"),
|
|
||||||
json); //TODO: Move logic to Storage
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private void OpenTroubleshooting()
|
|
||||||
{
|
|
||||||
const string troubleshootingURI =
|
|
||||||
"https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/docs/{0}/LinkingTroubleshooting";
|
|
||||||
|
|
||||||
var localized = string.Format(troubleshootingURI, LocManager.GetCurrentLanguageCode());
|
|
||||||
Process.Start(new ProcessStartInfo(new Uri(localized).ToString())
|
|
||||||
{
|
|
||||||
UseShellExecute = true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private void CopyCode()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Clipboard.SetText(_rCode);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Shell.Logger.Error(ex, "Error whily copying RCode");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static string GetLocalizationOrDefault(string key)
|
|
||||||
{
|
|
||||||
return LocManager.GetCodeBehindOrDefault(key, LOCALIZATION_KEY, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetLocalizationCommon(string key)
|
|
||||||
{
|
|
||||||
return LocManager.GetCommonOrDefault(key, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Properties
|
|
||||||
|
|
||||||
[ObservableProperty] [NotifyPropertyChangedFor(nameof(IsPasswordFieldVisible))]
|
|
||||||
private bool _isLogin;
|
|
||||||
|
|
||||||
|
|
||||||
[ObservableProperty] private bool _isEmailCode;
|
|
||||||
[ObservableProperty] private bool _isPhoneNumber;
|
|
||||||
[ObservableProperty] private bool _isEmailConfirmation;
|
|
||||||
[ObservableProperty] private bool _isLinkCode;
|
|
||||||
[ObservableProperty] private bool _isCompleted;
|
|
||||||
|
|
||||||
[ObservableProperty] private bool _isFieldVisible = true;
|
|
||||||
|
|
||||||
|
|
||||||
[ObservableProperty] private string _fieldText;
|
|
||||||
[ObservableProperty] private string _passFieldText;
|
|
||||||
[ObservableProperty] private string _hintText = GetLocalizationOrDefault("EnterLoginAndPassword");
|
|
||||||
|
|
||||||
private TaskCompletionSource<string> _emailCodeTcs = new();
|
|
||||||
private TaskCompletionSource<long?> _phoneNumberTcs = new();
|
|
||||||
private TaskCompletionSource _emailConfTcs = new();
|
|
||||||
private TaskCompletionSource<string> _linkCodeTcs = new();
|
|
||||||
|
|
||||||
private bool _isLinkStarted;
|
|
||||||
private string _rCode = string.Empty;
|
|
||||||
private string _password = string.Empty;
|
|
||||||
|
|
||||||
[ObservableProperty] [NotifyCanExecuteChangedFor(nameof(ProceedCommand))]
|
|
||||||
private bool _canProceed = true;
|
|
||||||
|
|
||||||
public bool IsPasswordFieldVisible => !IsLogin;
|
|
||||||
|
|
||||||
private LoginV2ExecutorOptions _loginV2ExecutorOptions;
|
|
||||||
private SteamAuthenticatorLinker _linker;
|
|
||||||
private MobileSessionData _sessionData;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region HttpClient
|
|
||||||
|
|
||||||
private static HttpClient Client { get; }
|
|
||||||
private static HttpClientHandler Handler { get; }
|
|
||||||
private static DynamicProxy Proxy { get; }
|
|
||||||
public ObservableDictionary<int, ProxyData> Proxies => ProxyStorage.Proxies;
|
|
||||||
|
|
||||||
public KeyValuePair<int, ProxyData>? SelectedProxy
|
|
||||||
{
|
|
||||||
get => _selectedProxy;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
SetProperty(ref _selectedProxy, value);
|
|
||||||
SetProxy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private KeyValuePair<int, ProxyData>? _selectedProxy;
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Providers
|
|
||||||
|
|
||||||
public int MaxRetryCount { get; }
|
|
||||||
|
|
||||||
public Task<string> GetEmailAuthCode(ILoginConsumer caller)
|
|
||||||
{
|
|
||||||
CanProceed = true;
|
|
||||||
HintText = GetLocalizationOrDefault("EnterEmailCode");
|
|
||||||
IsFieldVisible = true;
|
|
||||||
return _emailCodeTcs.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<string> GetAddAuthenticatorCode(ILoginConsumer caller)
|
|
||||||
{
|
|
||||||
IsPhoneNumber = true;
|
|
||||||
IsEmailConfirmation = true;
|
|
||||||
CanProceed = true;
|
|
||||||
HintText = GetLocalizationOrDefault("EnterEmailCode");
|
|
||||||
IsFieldVisible = true;
|
|
||||||
return _linkCodeTcs.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task ConfirmEmailLink(ILoginConsumer caller, EmailConfirmationType confirmationType)
|
|
||||||
{
|
|
||||||
IsPhoneNumber = true;
|
|
||||||
CanProceed = true;
|
|
||||||
HintText = GetLocalizationOrDefault("ClickOnEmailLink");
|
|
||||||
return _emailConfTcs.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<long?> GetPhoneNumber(ILoginConsumer caller)
|
|
||||||
{
|
|
||||||
CanProceed = true;
|
|
||||||
HintText = GetLocalizationOrDefault("EnterPhoneNumber");
|
|
||||||
IsFieldVisible = true;
|
|
||||||
return _phoneNumberTcs.Task;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<int> GetSmsCode(ILoginConsumer caller, long? phoneNumber, string? hint)
|
|
||||||
{
|
|
||||||
IsPhoneNumber = true;
|
|
||||||
IsEmailConfirmation = true;
|
|
||||||
CanProceed = true;
|
|
||||||
HintText = string.Format(GetLocalizationOrDefault("PhoneHint"), hint);
|
|
||||||
IsFieldVisible = true;
|
|
||||||
var code = await _linkCodeTcs.Task;
|
|
||||||
return int.Parse(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
#region Client
|
|
||||||
|
|
||||||
static LinkAccountVM()
|
|
||||||
{
|
|
||||||
Proxy = new DynamicProxy();
|
|
||||||
var clientPair = ClientBuilder.BuildMobileClient(Proxy, null);
|
|
||||||
Client = clientPair.Client;
|
|
||||||
Handler = clientPair.Handler;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ClearCookies()
|
|
||||||
{
|
|
||||||
Handler.CookieContainer.ClearMobileSessionCookies();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void SetProxy()
|
|
||||||
{
|
|
||||||
Proxy.SetData(SelectedProxy?.Value);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<item>
|
<item>
|
||||||
<version>1.5.7.0</version>
|
<version>1.7.1.0</version>
|
||||||
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.5.7/NebulaAuth.1.5.7.zip</url>
|
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.7.1/NebulaAuth.1.7.1.zip</url>
|
||||||
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.5.7.html</changelog>
|
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.7.1.html</changelog>
|
||||||
<mandatory>false</mandatory>
|
<mandatory>false</mandatory>
|
||||||
</item>
|
</item>
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
using System.Net;
|
|
||||||
using SteamLib.Core;
|
|
||||||
using SteamLib.Exceptions;
|
|
||||||
using SteamLib.ProtoCore;
|
|
||||||
using SteamLib.ProtoCore.Services;
|
|
||||||
|
|
||||||
namespace SteamLib.Api.Mobile;
|
|
||||||
|
|
||||||
public static class SteamMobileAuthenticatorApi
|
|
||||||
{
|
|
||||||
public static async Task<GetAuthSessionsForAccount_Response> GetAuthSessionsForAccount(HttpClient client,
|
|
||||||
string accessToken, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
var req = SteamConstants.STEAM_API + "IAuthenticationService/GetAuthSessionsForAccount/v1?access_token=" +
|
|
||||||
accessToken;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await client.GetProto<GetAuthSessionsForAccount_Response>(req, new EmptyMessage(),
|
|
||||||
cancellationToken);
|
|
||||||
}
|
|
||||||
catch (HttpRequestException ex)
|
|
||||||
when (ex.StatusCode == HttpStatusCode.Unauthorized)
|
|
||||||
{
|
|
||||||
throw new SessionInvalidException(SessionInvalidException.GOT_401_MSG, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<GetAuthSessionInfo_Response> GetAuthSessionInfo(HttpClient client, string accessToken,
|
|
||||||
ulong clientId, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
var req = SteamConstants.STEAM_API + "IAuthenticationService/GetAuthSessionInfo/v1?access_token=" + accessToken;
|
|
||||||
var reqData = new GetAuthSessionInfo_Request
|
|
||||||
{
|
|
||||||
ClientId = clientId
|
|
||||||
};
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return await client.PostProto<GetAuthSessionInfo_Response>(req, reqData, cancellationToken);
|
|
||||||
}
|
|
||||||
catch (HttpRequestException ex)
|
|
||||||
when (ex.StatusCode == HttpStatusCode.Unauthorized)
|
|
||||||
{
|
|
||||||
throw new SessionInvalidException(SessionInvalidException.GOT_401_MSG, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static async Task<bool> UpdateAuthSessionStatus(HttpClient client, string accessToken, string sharedSecret,
|
|
||||||
UpdateAuthSessionWithMobileConfirmation_Request request)
|
|
||||||
{
|
|
||||||
var req = SteamConstants.STEAM_API +
|
|
||||||
"IAuthenticationService/UpdateAuthSessionWithMobileConfirmation/v1?access_token=" + accessToken;
|
|
||||||
|
|
||||||
request.ComputeSignature(sharedSecret);
|
|
||||||
await client.PostProtoEnsureSuccess(req, request);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
using AchiesUtilities.Models;
|
|
||||||
using AchiesUtilities.Newtonsoft.JSON.Converters.Special;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using SteamLib.Authentication;
|
|
||||||
using SteamLib.Core;
|
|
||||||
using SteamLib.Core.Enums;
|
|
||||||
using SteamLib.Core.StatusCodes;
|
|
||||||
using SteamLib.Exceptions;
|
|
||||||
using SteamLib.Exceptions.General;
|
|
||||||
|
|
||||||
namespace SteamLib.Api;
|
|
||||||
|
|
||||||
public static class SteamGlobalApi
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Raw AccessToken value
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="client"></param>
|
|
||||||
/// <param name="domain"></param>
|
|
||||||
/// <param name="cancellationToken"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
/// <exception cref="SessionInvalidException"></exception>
|
|
||||||
public static async Task<string> RefreshJwt(HttpClient client, SteamDomain domain,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
var domainUri = SteamDomains.GetDomain(domain);
|
|
||||||
var data = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
{"redir", domainUri}
|
|
||||||
};
|
|
||||||
var cont = new FormUrlEncodedContent(data);
|
|
||||||
var resp = await client.PostAsync("https://login.steampowered.com/jwt/ajaxrefresh", cont, cancellationToken);
|
|
||||||
var respStr = await resp.EnsureSuccessStatusCode().Content.ReadAsStringAsync(cancellationToken);
|
|
||||||
|
|
||||||
JwtRefreshJson? jwtRefresh;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
jwtRefresh = JsonConvert.DeserializeObject<JwtRefreshJson>(respStr);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
throw new UnsupportedResponseException(respStr, ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jwtRefresh?.Success != true)
|
|
||||||
{
|
|
||||||
Exception? inner = null;
|
|
||||||
if (jwtRefresh?.Error != null)
|
|
||||||
{
|
|
||||||
var errorCode = jwtRefresh.Error.Value;
|
|
||||||
var translated = SteamStatusCode.Translate<SteamStatusCode>(errorCode, out _);
|
|
||||||
inner = new SteamStatusCodeException(translated, respStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new SessionInvalidException(
|
|
||||||
"AjaxRefresh was not successful. 'steamRefresh_steam' cookie is missing or refresh token is invalid",
|
|
||||||
inner);
|
|
||||||
}
|
|
||||||
|
|
||||||
data = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
{"steamID", jwtRefresh.SteamId.ToString()},
|
|
||||||
{"nonce", jwtRefresh.Nonce},
|
|
||||||
{"redir", jwtRefresh.Redir},
|
|
||||||
{"auth", jwtRefresh.Auth}
|
|
||||||
};
|
|
||||||
|
|
||||||
cont = new FormUrlEncodedContent(data);
|
|
||||||
var update = await client.PostAsync(jwtRefresh.LoginUrl, cont, cancellationToken);
|
|
||||||
var updateResp = await update.Content.ReadAsStringAsync(cancellationToken);
|
|
||||||
JwtUpdateJson result;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
result = JsonConvert.DeserializeObject<JwtUpdateJson>(updateResp) ?? throw new NullReferenceException();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
throw new UnsupportedResponseException(updateResp, ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
var resultStatus = SteamStatusCode.Translate<SteamStatusCode>(result.Result, out _);
|
|
||||||
if (resultStatus.Equals(SteamStatusCode.Ok) == false)
|
|
||||||
{
|
|
||||||
throw new SessionInvalidException(
|
|
||||||
"AjaxRefresh (set-token) response was not successful. Response string stored in Exception.Data")
|
|
||||||
{
|
|
||||||
Data = {{"Response", updateResp}}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return SteamTokenHelper.ExtractJwtFromSetCookiesHeader(update.Headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private class JwtRefreshJson
|
|
||||||
{
|
|
||||||
[JsonProperty("success")] public bool Success { get; set; }
|
|
||||||
[JsonProperty("login_url")] public string LoginUrl { get; set; } = string.Empty;
|
|
||||||
[JsonProperty("steamID")] public long SteamId { get; set; }
|
|
||||||
[JsonProperty("nonce")] public string Nonce { get; set; } = string.Empty;
|
|
||||||
[JsonProperty("redir")] public string Redir { get; set; } = string.Empty;
|
|
||||||
[JsonProperty("auth")] public string Auth { get; set; } = string.Empty;
|
|
||||||
[JsonProperty("error")] public int? Error { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
private class JwtUpdateJson
|
|
||||||
{
|
|
||||||
[JsonProperty("result")] public int Result { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("rtExpiry")]
|
|
||||||
[JsonConverter(typeof(UnixTimeStampConverter))]
|
|
||||||
public UnixTimeStamp RtExpiry { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using SteamLib.Authentication;
|
|
||||||
using SteamLib.Core.Interfaces;
|
|
||||||
using SteamLib.Exceptions;
|
|
||||||
|
|
||||||
namespace SteamLib.Login.Default;
|
|
||||||
|
|
||||||
[Obsolete("Method removed from Steam")]
|
|
||||||
public class LoginExecutor
|
|
||||||
{
|
|
||||||
public static ILoginConsumer NullConsumer { get; } = new NullLoginConsumer();
|
|
||||||
public ILoginConsumer Caller { get; }
|
|
||||||
public HttpClient HttpClient { get; }
|
|
||||||
public ILogger? Logger { get; init; }
|
|
||||||
public IEmailProvider? EmailAuthProvider { get; init; }
|
|
||||||
public ICaptchaResolver? CaptchaResolver { get; init; }
|
|
||||||
public ISteamGuardProvider? SteamGuardProvider { get; init; }
|
|
||||||
|
|
||||||
private LoginExecutor(LoginExecutorOptions options)
|
|
||||||
{
|
|
||||||
Caller = options.Consumer;
|
|
||||||
HttpClient = options.HttpClient;
|
|
||||||
Logger = options.Logger;
|
|
||||||
EmailAuthProvider = options.EmailAuthProvider;
|
|
||||||
SteamGuardProvider = options.SteamGuardProvider;
|
|
||||||
CaptchaResolver = options.CaptchaResolver;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static async Task<TransferParameters> DoLogin(LoginExecutorOptions options, string username, string password,
|
|
||||||
CancellationToken cancellationToken = default) //TODO: logs
|
|
||||||
{
|
|
||||||
var executor = new LoginExecutor(options);
|
|
||||||
var client = executor.HttpClient;
|
|
||||||
|
|
||||||
LoginStage loginStage = new GetRsaStage(username);
|
|
||||||
loginStage = await ((GetRsaStage) loginStage).Proceed(client, cancellationToken);
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
|
|
||||||
if (loginStage is ReadyToLoginStage rdyToLoginStage)
|
|
||||||
{
|
|
||||||
loginStage = await rdyToLoginStage.Proceed(client, password, string.Empty, cancellationToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
var twoFactorTry = 0;
|
|
||||||
var emailTry = 0;
|
|
||||||
while (loginStage.GetType() != typeof(LoginErrorStage) && loginStage is not LoginSuccessStage)
|
|
||||||
{
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
|
||||||
switch (loginStage)
|
|
||||||
{
|
|
||||||
case CaptchaNeededStage captchaNeededStage:
|
|
||||||
{
|
|
||||||
if (executor.CaptchaResolver == null) throw new LoginException(LoginError.CaptchaRequired);
|
|
||||||
var captchaText = await executor.CaptchaResolver.Resolve(captchaNeededStage.CaptchaImage, client);
|
|
||||||
loginStage = await captchaNeededStage.Proceed(client, captchaText, cancellationToken);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case EmailAuthStage emailAuthStage:
|
|
||||||
{
|
|
||||||
if (executor.EmailAuthProvider == null) throw new LoginException(LoginError.EmailAuthRequired);
|
|
||||||
if (emailTry > executor.EmailAuthProvider.MaxRetryCount)
|
|
||||||
throw new LoginException(LoginError.InvalidEmailAuthCode);
|
|
||||||
var code = await executor.EmailAuthProvider.GetEmailAuthCode(executor.Caller);
|
|
||||||
loginStage = await emailAuthStage.Proceed(client, code, cancellationToken);
|
|
||||||
emailTry++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case TwoFactorStage twoFactorStage:
|
|
||||||
{
|
|
||||||
if (executor.SteamGuardProvider == null) throw new LoginException(LoginError.SteamGuardRequired);
|
|
||||||
if (twoFactorTry > executor.SteamGuardProvider.MaxRetryCount)
|
|
||||||
throw new LoginException(LoginError.InvalidTwoFactorCode);
|
|
||||||
var twoFactor = await executor.SteamGuardProvider.GetSteamGuardCode(executor.Caller);
|
|
||||||
loginStage = await twoFactorStage.Proceed(client, twoFactor, cancellationToken);
|
|
||||||
twoFactorTry++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case ReadyToLoginStage readyToLoginStage: //When captcha proceeded, stage goes there
|
|
||||||
{
|
|
||||||
loginStage = await readyToLoginStage.Proceed(client, password, string.Empty, cancellationToken);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(loginStage));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loginStage is LoginErrorStage error)
|
|
||||||
{
|
|
||||||
var content = await error.Response.Content.ReadAsStringAsync(cancellationToken);
|
|
||||||
if (content.Contains("The account name or password that you have entered is incorrect"))
|
|
||||||
{
|
|
||||||
throw new LoginException(LoginError.InvalidCredentials);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new LoginException(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (loginStage is not LoginSuccessStage successStage)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Unexpected login stage at this point. " + loginStage.GetType())
|
|
||||||
{
|
|
||||||
Data = {{"stage", loginStage}}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return successStage.TransferParameters;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using SteamLib.Core.Interfaces;
|
|
||||||
|
|
||||||
namespace SteamLib.Login.Default;
|
|
||||||
|
|
||||||
public class LoginExecutorOptions
|
|
||||||
{
|
|
||||||
public ILoginConsumer Consumer { get; }
|
|
||||||
public HttpClient HttpClient { get; }
|
|
||||||
public ILogger? Logger { get; init; }
|
|
||||||
public IEmailProvider? EmailAuthProvider { get; init; }
|
|
||||||
public ICaptchaResolver? CaptchaResolver { get; init; }
|
|
||||||
public ISteamGuardProvider? SteamGuardProvider { get; init; }
|
|
||||||
|
|
||||||
public LoginExecutorOptions(ILoginConsumer consumer, HttpClient httpClient)
|
|
||||||
{
|
|
||||||
Consumer = consumer;
|
|
||||||
HttpClient = httpClient;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace SteamLib.Login.Default;
|
|
||||||
|
|
||||||
#pragma warning disable CS8618
|
|
||||||
/// <summary>
|
|
||||||
/// Class to Deserialize the json response strings after the login/>
|
|
||||||
/// </summary>
|
|
||||||
internal class LoginResultJson
|
|
||||||
{
|
|
||||||
[JsonProperty("success")] public bool Success { get; set; }
|
|
||||||
[JsonProperty("message")] public string Message { get; set; }
|
|
||||||
[JsonProperty("captcha_needed")] public bool CaptchaNeeded { get; set; }
|
|
||||||
[JsonProperty("captcha_gid")] public string CaptchaGid { get; set; }
|
|
||||||
[JsonProperty("emailauth_needed")] public bool EmailAuthNeeded { get; set; }
|
|
||||||
[JsonProperty("emailsteamid")] public string EmailSteamId { get; set; }
|
|
||||||
[JsonProperty("requires_twofactor")] public bool RequiresTwoFactor { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,252 +0,0 @@
|
|||||||
using AchiesUtilities.Web.Extensions;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using SteamLib.Authentication;
|
|
||||||
using SteamLib.Utility;
|
|
||||||
|
|
||||||
namespace SteamLib.Login.Default;
|
|
||||||
|
|
||||||
internal abstract class LoginStage
|
|
||||||
{
|
|
||||||
protected Dictionary<string, string> Data { get; }
|
|
||||||
|
|
||||||
protected LoginStage()
|
|
||||||
{
|
|
||||||
Data = new Dictionary<string, string>();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected LoginStage(Dictionary<string, string> data)
|
|
||||||
{
|
|
||||||
Data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected bool CheckIfLoginCompleted(LoginResultJson json)
|
|
||||||
{
|
|
||||||
return !json.CaptchaNeeded && !json.EmailAuthNeeded && !json.RequiresTwoFactor && json.Success;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task<LoginResultJson> ConvertJson(HttpResponseMessage response)
|
|
||||||
{
|
|
||||||
response.EnsureSuccessStatusCode();
|
|
||||||
var webResponseString = await response.Content.ReadAsStringAsync();
|
|
||||||
return JsonConvert.DeserializeObject<LoginResultJson>(webResponseString)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task<LoginSuccessStage> CompleteLogin(HttpResponseMessage response)
|
|
||||||
{
|
|
||||||
var jsonStr = await response.Content.ReadAsStringAsync();
|
|
||||||
var json = JObject.Parse(jsonStr);
|
|
||||||
var transferParameters = json["transfer_parameters"]!.ToObject<TransferParameters>()!;
|
|
||||||
return new LoginSuccessStage(transferParameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async Task<HttpResponseMessage> LoginRequest(HttpClient client,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
return await client.PostAsync("https://steamcommunity.com/login/dologin", new FormUrlEncodedContent(Data),
|
|
||||||
cancellationToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class LoginErrorStage : LoginStage
|
|
||||||
{
|
|
||||||
public readonly int ErrorCode;
|
|
||||||
public readonly string ErrorString;
|
|
||||||
public readonly HttpResponseMessage Response;
|
|
||||||
public readonly LoginStage Stage;
|
|
||||||
|
|
||||||
public LoginErrorStage(string errorString, HttpResponseMessage responseMessage, LoginStage stage)
|
|
||||||
{
|
|
||||||
ErrorString = errorString;
|
|
||||||
Stage = stage;
|
|
||||||
ErrorCode = Utilities.GetSuccessCode(responseMessage.Content.ReadAsStringSync());
|
|
||||||
Response = responseMessage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class GetRsaStage : LoginStage
|
|
||||||
{
|
|
||||||
public GetRsaStage(Dictionary<string, string> data) : base(data)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public GetRsaStage(string userName)
|
|
||||||
{
|
|
||||||
Data["username"] = userName;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="client"></param>
|
|
||||||
/// <param name="cancellationToken"></param>
|
|
||||||
/// <returns> <see cref="ReadyToLoginStage" /> if success</returns>
|
|
||||||
public async Task<LoginStage> Proceed(HttpClient client, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
var content = new FormUrlEncodedContent(Data);
|
|
||||||
var response = await client.PostAsync("https://steamcommunity.com/login/getrsakey", content, cancellationToken);
|
|
||||||
var responseString = await response.Content.ReadAsStringAsync(cancellationToken);
|
|
||||||
var rsaJson = JsonConvert.DeserializeObject<RsaKeyJson>(responseString)!;
|
|
||||||
if (!rsaJson.success)
|
|
||||||
return new LoginErrorStage("Can't get RSA", response, this);
|
|
||||||
|
|
||||||
Data["rsatimestamp"] = rsaJson.timestamp;
|
|
||||||
return new ReadyToLoginStage(Data, rsaJson.publickey_exp, rsaJson.publickey_mod);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class ReadyToLoginStage : LoginStage
|
|
||||||
{
|
|
||||||
private readonly string _publicKeyExp;
|
|
||||||
private readonly string _publicKeyMod;
|
|
||||||
|
|
||||||
internal ReadyToLoginStage(Dictionary<string, string> data, string publicKeyExp, string publicKeyMod) : base(data)
|
|
||||||
{
|
|
||||||
_publicKeyExp = publicKeyExp;
|
|
||||||
_publicKeyMod = publicKeyMod;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<LoginStage> Proceed(HttpClient client, string password, string loginFriendlyName,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
var encryptedBase64Password =
|
|
||||||
EncryptionHelper.ToBase64EncryptedPassword(_publicKeyExp, _publicKeyMod, password);
|
|
||||||
var unixTimestamp = (int) DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds;
|
|
||||||
Data["password"] = encryptedBase64Password;
|
|
||||||
Data["remember_login"] = "true";
|
|
||||||
Data["loginfriendlyname"] = loginFriendlyName;
|
|
||||||
Data["donotcache"] =
|
|
||||||
unixTimestamp + "000"; // Added three "0"'s because Steam has a weird unix timestamp interpretation.
|
|
||||||
|
|
||||||
_ = await client.GetAsync("https://steamcommunity.com/", cancellationToken);
|
|
||||||
var webResponse = await LoginRequest(client, cancellationToken);
|
|
||||||
var result = await ConvertJson(webResponse);
|
|
||||||
|
|
||||||
if (CheckIfLoginCompleted(result))
|
|
||||||
{
|
|
||||||
return await CompleteLogin(webResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.CaptchaNeeded)
|
|
||||||
{
|
|
||||||
return new CaptchaNeededStage(Data, result.CaptchaGid);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.EmailAuthNeeded)
|
|
||||||
{
|
|
||||||
return new EmailAuthStage(Data);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.RequiresTwoFactor)
|
|
||||||
{
|
|
||||||
return new TwoFactorStage(Data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new LoginErrorStage("Can't proceed login.", webResponse, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class TwoFactorStage : LoginStage
|
|
||||||
{
|
|
||||||
internal TwoFactorStage(Dictionary<string, string> data) : base(data)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<LoginStage> Proceed(HttpClient client, string twoFactorCode,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
Data["twofactorcode"] = twoFactorCode;
|
|
||||||
var webResponse = await LoginRequest(client, cancellationToken);
|
|
||||||
var result = await ConvertJson(webResponse);
|
|
||||||
|
|
||||||
if (CheckIfLoginCompleted(result))
|
|
||||||
{
|
|
||||||
return await CompleteLogin(webResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.CaptchaNeeded)
|
|
||||||
{
|
|
||||||
return new CaptchaNeededStage(Data, result.CaptchaGid);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.RequiresTwoFactor)
|
|
||||||
{
|
|
||||||
return new TwoFactorStage(Data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new LoginErrorStage("Can't proceed login. Bad TwoFactor code", webResponse, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class EmailAuthStage : LoginStage
|
|
||||||
{
|
|
||||||
internal EmailAuthStage(Dictionary<string, string> data) : base(data)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<LoginStage> Proceed(HttpClient client, string emailCode,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
Data["emailauth"] = emailCode;
|
|
||||||
var webResponse = await LoginRequest(client, cancellationToken);
|
|
||||||
var result = await ConvertJson(webResponse);
|
|
||||||
|
|
||||||
if (CheckIfLoginCompleted(result))
|
|
||||||
{
|
|
||||||
return await CompleteLogin(webResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.CaptchaNeeded)
|
|
||||||
{
|
|
||||||
return new CaptchaNeededStage(Data, result.CaptchaGid);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.EmailAuthNeeded)
|
|
||||||
{
|
|
||||||
return new EmailAuthStage(Data);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return new LoginErrorStage("Can't proceed login. Bad Email code", webResponse, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class CaptchaNeededStage : LoginStage
|
|
||||||
{
|
|
||||||
internal Uri CaptchaImage { get; }
|
|
||||||
|
|
||||||
public CaptchaNeededStage(Dictionary<string, string> data, string captchaGid) : base(data)
|
|
||||||
{
|
|
||||||
captchaGid = Uri.EscapeDataString(captchaGid);
|
|
||||||
Data["captchagid"] = captchaGid;
|
|
||||||
CaptchaImage = new Uri("https://steamcommunity.com/login/rendercaptcha?gid=" + captchaGid);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="client"></param>
|
|
||||||
/// <param name="captchaText"></param>
|
|
||||||
/// <param name="cancellationToken"></param>
|
|
||||||
/// <returns><see cref="ReadyToLoginStage" /> or <see cref="LoginErrorStage" /></returns>
|
|
||||||
public async Task<LoginStage> Proceed(HttpClient client, string captchaText,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
Data["captcha_text"] = captchaText;
|
|
||||||
//When captcha required we need to do login from start
|
|
||||||
|
|
||||||
var rsaStage = new GetRsaStage(Data);
|
|
||||||
var res = await rsaStage.Proceed(client, cancellationToken);
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class LoginSuccessStage : LoginStage
|
|
||||||
{
|
|
||||||
public TransferParameters TransferParameters { get; }
|
|
||||||
|
|
||||||
public LoginSuccessStage(TransferParameters transferParameters)
|
|
||||||
{
|
|
||||||
TransferParameters = transferParameters;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
using SteamLib.Core.Interfaces;
|
|
||||||
|
|
||||||
namespace SteamLib.Login.Default;
|
|
||||||
|
|
||||||
internal class NullLoginConsumer : ILoginConsumer
|
|
||||||
{
|
|
||||||
public string FriendlyName { get; } = "null";
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
// ReSharper disable InconsistentNaming
|
|
||||||
// ReSharper disable IdentifierTypo
|
|
||||||
|
|
||||||
#pragma warning disable CS8618
|
|
||||||
|
|
||||||
namespace SteamLib.Login.Default;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Class to Deserialize the json response strings of the getResKey request/>
|
|
||||||
/// </summary>
|
|
||||||
internal class RsaKeyJson
|
|
||||||
{
|
|
||||||
public bool success { get; set; }
|
|
||||||
public string publickey_mod { get; set; }
|
|
||||||
public string publickey_exp { get; set; }
|
|
||||||
public string timestamp { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,254 +0,0 @@
|
|||||||
using AchiesUtilities.Web.Extensions;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using SteamLib.Account;
|
|
||||||
using SteamLib.Core;
|
|
||||||
using SteamLib.Core.Enums;
|
|
||||||
using SteamLib.Core.Interfaces;
|
|
||||||
using SteamLib.Core.Models;
|
|
||||||
using SteamLib.Core.StatusCodes;
|
|
||||||
using SteamLib.Exceptions;
|
|
||||||
using SteamLib.Login.Default;
|
|
||||||
using SteamLib.ProtoCore;
|
|
||||||
using SteamLib.ProtoCore.Enums;
|
|
||||||
using SteamLib.ProtoCore.Exceptions;
|
|
||||||
using SteamLib.ProtoCore.Services;
|
|
||||||
using SteamLib.Utility;
|
|
||||||
using SteamLib.Web;
|
|
||||||
|
|
||||||
namespace SteamLib.Authentication.LoginV2;
|
|
||||||
|
|
||||||
public class LoginV2Executor
|
|
||||||
{
|
|
||||||
public static ILoginConsumer NullConsumer { get; } = new NullLoginConsumer();
|
|
||||||
public ILoginConsumer Caller { get; }
|
|
||||||
public HttpClient HttpClient { get; }
|
|
||||||
public ILogger? Logger { get; init; }
|
|
||||||
public IEmailProvider? EmailAuthProvider { get; init; }
|
|
||||||
public ICaptchaResolver? CaptchaResolver { get; init; }
|
|
||||||
public ISteamGuardProvider? SteamGuardProvider { get; init; }
|
|
||||||
public string WebsiteId { get; init; }
|
|
||||||
public DeviceDetails DeviceDetails { get; init; }
|
|
||||||
|
|
||||||
private LoginV2Executor(LoginV2ExecutorOptions options)
|
|
||||||
{
|
|
||||||
Caller = options.Consumer;
|
|
||||||
HttpClient = options.HttpClient;
|
|
||||||
Logger = options.Logger;
|
|
||||||
EmailAuthProvider = options.EmailAuthProvider;
|
|
||||||
SteamGuardProvider = options.SteamGuardProvider;
|
|
||||||
WebsiteId = options.GetWebsiteIdOrDefault();
|
|
||||||
DeviceDetails = options.DeviceDetails ?? DeviceDetails.CreateDefault();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Do log in on <see href="https://steamcommunity.com/">SteamCommunity</see>.<br />
|
|
||||||
/// Some functions require proper SessionId. But <see cref="SessionData" /> contains only SteamCommunity related
|
|
||||||
/// SessionId and some functions on other services may not work
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="options"></param>
|
|
||||||
/// <param name="username"></param>
|
|
||||||
/// <param name="password"></param>
|
|
||||||
/// <param name="cancellationToken"></param>
|
|
||||||
/// <returns><see cref="SessionData" /> or <see cref="MobileSessionData" /> depending on which token type is returned</returns>
|
|
||||||
/// <exception cref="LoginException"></exception>
|
|
||||||
/// <exception cref="EResultException"></exception>
|
|
||||||
/// <exception cref="NotSupportedException"></exception>
|
|
||||||
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
|
||||||
public static async Task<ISessionData> DoLogin(LoginV2ExecutorOptions options, string username, string password,
|
|
||||||
CancellationToken cancellationToken = default) //TODO: logs
|
|
||||||
{
|
|
||||||
var executor = new LoginV2Executor(options);
|
|
||||||
var client = executor.HttpClient;
|
|
||||||
|
|
||||||
var sessionId = await SteamWebApi.GetLoginSessionId(client, cancellationToken);
|
|
||||||
var rsgMsg = new GetPasswordRSAPublicKey_Request
|
|
||||||
{
|
|
||||||
AccountName = username
|
|
||||||
};
|
|
||||||
var rsaResp = await client.GetProto<GetPasswordRSAPublicKey_Response>(
|
|
||||||
"https://api.steampowered.com/IAuthenticationService/GetPasswordRSAPublicKey/v1", rsgMsg,
|
|
||||||
cancellationToken);
|
|
||||||
|
|
||||||
|
|
||||||
var encodedPassword =
|
|
||||||
EncryptionHelper.ToBase64EncryptedPassword(rsaResp.PublickKeyExp, rsaResp.PublickKeyMod, password);
|
|
||||||
|
|
||||||
|
|
||||||
var beginAuthMsg = new BeginAuthSessionViaCredentials_Request
|
|
||||||
{
|
|
||||||
DeviceFriendlyName = string.Empty,
|
|
||||||
AccountName = username,
|
|
||||||
EncryptedPassword = encodedPassword,
|
|
||||||
EncryptionTimestamp = rsaResp.Timestamp,
|
|
||||||
RememberLogin = true,
|
|
||||||
PlatformType = executor.DeviceDetails.PlatformType,
|
|
||||||
Persistence = 1,
|
|
||||||
WebsiteId = executor.WebsiteId,
|
|
||||||
DeviceDetails = executor.DeviceDetails
|
|
||||||
};
|
|
||||||
|
|
||||||
BeginAuthSessionViaCredentials_Response beginAuthResp;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
beginAuthResp = await client.PostProto<BeginAuthSessionViaCredentials_Response>(
|
|
||||||
"https://api.steampowered.com/IAuthenticationService/BeginAuthSessionViaCredentials/v1", beginAuthMsg,
|
|
||||||
cancellationToken);
|
|
||||||
}
|
|
||||||
catch (EResultException ex)
|
|
||||||
when (ex.Result == EResult.InvalidPassword)
|
|
||||||
{
|
|
||||||
throw new LoginException(LoginError.InvalidCredentials);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
var clientId = beginAuthResp.ClientId;
|
|
||||||
var steamId = (long) beginAuthResp.Steamid;
|
|
||||||
|
|
||||||
var conf = beginAuthResp.AllowedConfirmations.FirstOrDefault(c =>
|
|
||||||
c.ConfirmationType is EAuthSessionGuardType.EmailCode or EAuthSessionGuardType.DeviceCode);
|
|
||||||
|
|
||||||
conf ??= beginAuthResp.AllowedConfirmations.FirstOrDefault();
|
|
||||||
|
|
||||||
switch (conf?.ConfirmationType)
|
|
||||||
{
|
|
||||||
case EAuthSessionGuardType.None:
|
|
||||||
break;
|
|
||||||
case EAuthSessionGuardType.DeviceCode:
|
|
||||||
case EAuthSessionGuardType.EmailCode:
|
|
||||||
await UpdateWithCode(executor, clientId, (ulong) steamId, conf.ConfirmationType);
|
|
||||||
break;
|
|
||||||
case EAuthSessionGuardType.Unknown:
|
|
||||||
case EAuthSessionGuardType.DeviceConfirmation:
|
|
||||||
case EAuthSessionGuardType.EmailConfirmation:
|
|
||||||
case EAuthSessionGuardType.MachineToken:
|
|
||||||
case EAuthSessionGuardType.LegacyMachineAuth:
|
|
||||||
throw new NotSupportedException(
|
|
||||||
$"Auth confirmation type of {conf.ConfirmationType} is not implemented yet");
|
|
||||||
default:
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(conf.ConfirmationType), conf?.ConfirmationType,
|
|
||||||
"Unknown confirmation type or null");
|
|
||||||
}
|
|
||||||
|
|
||||||
var pollSessionMsg = new PollAuthSessionStatus_Request
|
|
||||||
{
|
|
||||||
ClientId = clientId,
|
|
||||||
RequestId = beginAuthResp.RequestId
|
|
||||||
};
|
|
||||||
|
|
||||||
var pollResp =
|
|
||||||
await client.PostProto<PollAuthSessionStatus_Response>(
|
|
||||||
"https://api.steampowered.com/IAuthenticationService/PollAuthSessionStatus/v1", pollSessionMsg,
|
|
||||||
cancellationToken);
|
|
||||||
|
|
||||||
SteamAuthToken refreshToken;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
refreshToken = SteamTokenHelper.Parse(pollResp.RefreshToken);
|
|
||||||
if (refreshToken.Type is not (SteamAccessTokenType.Refresh or SteamAccessTokenType.MobileRefresh))
|
|
||||||
throw new ArgumentException(
|
|
||||||
"Refresh token must be of type Refresh or MobileRefresh. No 'renew' audience found in JWT.",
|
|
||||||
nameof(pollResp.RefreshToken)); //Argument exception for less code
|
|
||||||
}
|
|
||||||
catch (ArgumentException ex)
|
|
||||||
{
|
|
||||||
throw new LoginException(null,
|
|
||||||
"Steam returned invalid refresh token or it's schema was unsupported. See inner exception for more details",
|
|
||||||
LoginError.UndefinedError, ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
var data = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
{"nonce", pollResp.RefreshToken},
|
|
||||||
{"sessionid", sessionId}
|
|
||||||
};
|
|
||||||
|
|
||||||
var finalize = await client.PostAsync("https://login.steampowered.com/jwt/finalizelogin",
|
|
||||||
new FormUrlEncodedContent(data), cancellationToken);
|
|
||||||
var finalizeContent = await finalize.EnsureSuccessStatusCode().Content.ReadAsStringAsync(cancellationToken);
|
|
||||||
|
|
||||||
var finalizeResp =
|
|
||||||
SteamLibErrorMonitor.HandleResponse(finalizeContent,
|
|
||||||
() => JsonConvert.DeserializeObject<FinalizeLoginJson>(finalizeContent)!);
|
|
||||||
|
|
||||||
|
|
||||||
List<SteamAuthToken> tokens = new();
|
|
||||||
foreach (var transferInfo in finalizeResp.TransferInfo)
|
|
||||||
{
|
|
||||||
var transferData = new Dictionary<string, string>
|
|
||||||
{
|
|
||||||
{"nonce", transferInfo.TransferInfoParams.Nonce},
|
|
||||||
{"auth", transferInfo.TransferInfoParams.Auth},
|
|
||||||
{"steamID", steamId.ToString()}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var req = new HttpRequestMessage(HttpMethod.Post, transferInfo.Url);
|
|
||||||
req.Content = new FormUrlEncodedContent(transferData);
|
|
||||||
req.Headers.Referrer = SteamDomains.GetDomainUri(SteamDomain.Store);
|
|
||||||
var transferResp = await client.SendAsync(req, cancellationToken);
|
|
||||||
var transferContent = await transferResp.ReadAsStringEnsureSuccessAsync(cancellationToken);
|
|
||||||
var status = JObject.Parse(transferContent);
|
|
||||||
var result = status["result"]?.Value<int>();
|
|
||||||
if (result != null)
|
|
||||||
SteamStatusCode
|
|
||||||
.ValidateSuccessOrThrow(result
|
|
||||||
.Value); //TODO: Fix steam.tv token transfer (result always 8). But who really cares..
|
|
||||||
|
|
||||||
var tokenStr = SteamTokenHelper.ExtractJwtFromSetCookiesHeader(transferResp.Headers);
|
|
||||||
var token = SteamTokenHelper.Parse(tokenStr);
|
|
||||||
tokens.Add(token);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
executor.Logger?.Log(LogLevel.Warning, ex, "Can't transfer tokens for URI: {uri}", transferInfo.Url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var accessToken = SteamTokenHelper.Parse(pollResp.AccessToken);
|
|
||||||
|
|
||||||
if (accessToken.Type == SteamAccessTokenType.Mobile)
|
|
||||||
{
|
|
||||||
return new MobileSessionData(sessionId, SteamId.FromSteam64(steamId), refreshToken, accessToken, tokens);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new SessionData(sessionId, SteamId.FromSteam64(steamId), refreshToken, tokens);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task UpdateWithCode(LoginV2Executor executor, ulong clientId, ulong steamId,
|
|
||||||
EAuthSessionGuardType guardType)
|
|
||||||
{
|
|
||||||
string? code;
|
|
||||||
if (guardType == EAuthSessionGuardType.DeviceCode)
|
|
||||||
{
|
|
||||||
if (executor.SteamGuardProvider != null)
|
|
||||||
code = await executor.SteamGuardProvider.GetSteamGuardCode(executor.Caller);
|
|
||||||
else
|
|
||||||
{
|
|
||||||
throw new LoginException(LoginError.SteamGuardRequired);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var t = executor.EmailAuthProvider?.GetEmailAuthCode(executor.Caller) ??
|
|
||||||
throw new LoginException(LoginError.EmailAuthRequired);
|
|
||||||
code = await t;
|
|
||||||
}
|
|
||||||
|
|
||||||
var updateCodeMsg = new UpdateAuthSessionWithSteamGuardCode_Request
|
|
||||||
{
|
|
||||||
ClientId = clientId,
|
|
||||||
Code = code,
|
|
||||||
CodeType = guardType,
|
|
||||||
Steamid = steamId
|
|
||||||
};
|
|
||||||
|
|
||||||
await executor.HttpClient.PostProtoEnsureSuccess(
|
|
||||||
new Uri("https://api.steampowered.com/IAuthenticationService/UpdateAuthSessionWithSteamGuardCode/v1"),
|
|
||||||
updateCodeMsg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,757 +0,0 @@
|
|||||||
// ReSharper disable All
|
|
||||||
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Collections.ObjectModel;
|
|
||||||
|
|
||||||
namespace SteamLib.Core.Enums;
|
|
||||||
|
|
||||||
public enum Country
|
|
||||||
{
|
|
||||||
Afghanistan,
|
|
||||||
Aland_Islands,
|
|
||||||
Albania,
|
|
||||||
Algeria,
|
|
||||||
American_Samoa,
|
|
||||||
Andorra,
|
|
||||||
Angola,
|
|
||||||
Anguilla,
|
|
||||||
Antigua_and_Barbuda,
|
|
||||||
Argentina,
|
|
||||||
Armenia,
|
|
||||||
Aruba,
|
|
||||||
Australia,
|
|
||||||
Austria,
|
|
||||||
Azerbaijan,
|
|
||||||
Bahamas,
|
|
||||||
Bahrain,
|
|
||||||
Bangladesh,
|
|
||||||
Barbados,
|
|
||||||
Belarus,
|
|
||||||
Belgium,
|
|
||||||
Belize,
|
|
||||||
Bhutan,
|
|
||||||
Bolivia,
|
|
||||||
Bosnia_and_Herzegovina,
|
|
||||||
Botswana,
|
|
||||||
Brazil,
|
|
||||||
Brunei_Darussalam,
|
|
||||||
Bulgaria,
|
|
||||||
Burundi,
|
|
||||||
Cambodia,
|
|
||||||
Cameroon,
|
|
||||||
Canada,
|
|
||||||
Cape_Verde,
|
|
||||||
Cayman_Islands,
|
|
||||||
Chad,
|
|
||||||
Chile,
|
|
||||||
China,
|
|
||||||
Colombia,
|
|
||||||
Congo,
|
|
||||||
Costa_Rica,
|
|
||||||
Cote_d_Ivoire,
|
|
||||||
Croatia,
|
|
||||||
Cyprus,
|
|
||||||
Czech_Republic,
|
|
||||||
Denmark,
|
|
||||||
Dominica,
|
|
||||||
Dominican_Republic,
|
|
||||||
Ecuador,
|
|
||||||
Egypt,
|
|
||||||
El_Salvador,
|
|
||||||
Estonia,
|
|
||||||
Eswatini,
|
|
||||||
Ethiopia,
|
|
||||||
Faroe_Islands,
|
|
||||||
Finland,
|
|
||||||
France,
|
|
||||||
French_Guiana,
|
|
||||||
French_Polynesia,
|
|
||||||
Gabon,
|
|
||||||
Georgia,
|
|
||||||
Germany,
|
|
||||||
Ghana,
|
|
||||||
Gibraltar,
|
|
||||||
Greece,
|
|
||||||
Greenland,
|
|
||||||
Grenada,
|
|
||||||
Guadeloupe,
|
|
||||||
Guam,
|
|
||||||
Guatemala,
|
|
||||||
Guernsey,
|
|
||||||
Guyana,
|
|
||||||
Haiti,
|
|
||||||
Honduras,
|
|
||||||
Hong_Kong,
|
|
||||||
Hungary,
|
|
||||||
Iceland,
|
|
||||||
India,
|
|
||||||
Indonesia,
|
|
||||||
Iran,
|
|
||||||
Iraq,
|
|
||||||
Ireland,
|
|
||||||
Isle_of_Man,
|
|
||||||
Israel,
|
|
||||||
Italy,
|
|
||||||
Jamaica,
|
|
||||||
Japan,
|
|
||||||
Jersey,
|
|
||||||
Jordan,
|
|
||||||
Kazakhstan,
|
|
||||||
Kenya,
|
|
||||||
Korea,
|
|
||||||
Kuwait,
|
|
||||||
Kyrgyzstan,
|
|
||||||
Laos,
|
|
||||||
Latvia,
|
|
||||||
Lebanon,
|
|
||||||
Libya,
|
|
||||||
Liechtenstein,
|
|
||||||
Lithuania,
|
|
||||||
Luxembourg,
|
|
||||||
Macau,
|
|
||||||
Macedonia,
|
|
||||||
Madagascar,
|
|
||||||
Malaysia,
|
|
||||||
Maldives,
|
|
||||||
Mali,
|
|
||||||
Malta,
|
|
||||||
Martinique,
|
|
||||||
Mauritius,
|
|
||||||
Mayotte,
|
|
||||||
Mexico,
|
|
||||||
Moldova,
|
|
||||||
Monaco,
|
|
||||||
Mongolia,
|
|
||||||
Montenegro,
|
|
||||||
Morocco,
|
|
||||||
Mozambique,
|
|
||||||
Myanmar,
|
|
||||||
Namibia,
|
|
||||||
Nepal,
|
|
||||||
Netherlands,
|
|
||||||
New_Zealand,
|
|
||||||
Nicaragua,
|
|
||||||
Nigeria,
|
|
||||||
Northern_Mariana_Islands,
|
|
||||||
Norway,
|
|
||||||
Oman,
|
|
||||||
Pakistan,
|
|
||||||
Palestinian_Territory,
|
|
||||||
Panama,
|
|
||||||
Papua_New_Guinea,
|
|
||||||
Paraguay,
|
|
||||||
Peru,
|
|
||||||
Philippines,
|
|
||||||
Poland,
|
|
||||||
Portugal,
|
|
||||||
Puerto_Rico,
|
|
||||||
Qatar,
|
|
||||||
Reunion,
|
|
||||||
Romania,
|
|
||||||
Russian_Federation,
|
|
||||||
Rwanda,
|
|
||||||
Saint_Kitts_and_Nevis,
|
|
||||||
Saint_Lucia,
|
|
||||||
Saint_Vincent_and_the_Grenadines,
|
|
||||||
Saudi_Arabia,
|
|
||||||
Senegal,
|
|
||||||
Serbia,
|
|
||||||
Seychelles,
|
|
||||||
Singapore,
|
|
||||||
Slovakia,
|
|
||||||
Slovenia,
|
|
||||||
South_Africa,
|
|
||||||
Spain,
|
|
||||||
Sri_Lanka,
|
|
||||||
Sudan,
|
|
||||||
Suriname,
|
|
||||||
Sweden,
|
|
||||||
Switzerland,
|
|
||||||
Taiwan,
|
|
||||||
Tajikistan,
|
|
||||||
Tanzania,
|
|
||||||
Thailand,
|
|
||||||
Togo,
|
|
||||||
Trinidad_and_Tobago,
|
|
||||||
Tunisia,
|
|
||||||
Turkey,
|
|
||||||
Turkmenistan,
|
|
||||||
Turks_and_Caicos_Islands,
|
|
||||||
Uganda,
|
|
||||||
Ukraine,
|
|
||||||
United_Arab_Emirates,
|
|
||||||
United_Kingdom,
|
|
||||||
United_States,
|
|
||||||
Uruguay,
|
|
||||||
Uzbekistan,
|
|
||||||
Venezuela,
|
|
||||||
Viet_Nam,
|
|
||||||
Virgin_Islands,
|
|
||||||
Yemen,
|
|
||||||
Zambia,
|
|
||||||
Zimbabwe
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class CountryResolver
|
|
||||||
{
|
|
||||||
public static IReadOnlyDictionary<string, Country> ResolveByCountryCode = new ReadOnlyDictionary<string, Country>(
|
|
||||||
new Dictionary<string, Country>()
|
|
||||||
{
|
|
||||||
{"US", Country.United_States},
|
|
||||||
{"CA", Country.Canada},
|
|
||||||
{"AF", Country.Afghanistan},
|
|
||||||
{"AX", Country.Aland_Islands},
|
|
||||||
{"AL", Country.Albania},
|
|
||||||
{"DZ", Country.Algeria},
|
|
||||||
{"AS", Country.American_Samoa},
|
|
||||||
{"AD", Country.Andorra},
|
|
||||||
{"AO", Country.Angola},
|
|
||||||
{"AI", Country.Anguilla},
|
|
||||||
{"AG", Country.Antigua_and_Barbuda},
|
|
||||||
{"AR", Country.Argentina},
|
|
||||||
{"AM", Country.Armenia},
|
|
||||||
{"AW", Country.Aruba},
|
|
||||||
{"AU", Country.Australia},
|
|
||||||
{"AT", Country.Austria},
|
|
||||||
{"AZ", Country.Azerbaijan},
|
|
||||||
{"BS", Country.Bahamas},
|
|
||||||
{"BH", Country.Bahrain},
|
|
||||||
{"BD", Country.Bangladesh},
|
|
||||||
{"BB", Country.Barbados},
|
|
||||||
{"BY", Country.Belarus},
|
|
||||||
{"BE", Country.Belgium},
|
|
||||||
{"BZ", Country.Belize},
|
|
||||||
{"BT", Country.Bhutan},
|
|
||||||
{"BO", Country.Bolivia},
|
|
||||||
{"BA", Country.Bosnia_and_Herzegovina},
|
|
||||||
{"BW", Country.Botswana},
|
|
||||||
{"BR", Country.Brazil},
|
|
||||||
{"BN", Country.Brunei_Darussalam},
|
|
||||||
{"BG", Country.Bulgaria},
|
|
||||||
{"BI", Country.Burundi},
|
|
||||||
{"KH", Country.Cambodia},
|
|
||||||
{"CM", Country.Cameroon},
|
|
||||||
{"CV", Country.Cape_Verde},
|
|
||||||
{"KY", Country.Cayman_Islands},
|
|
||||||
{"TD", Country.Chad},
|
|
||||||
{"CL", Country.Chile},
|
|
||||||
{"CN", Country.China},
|
|
||||||
{"CO", Country.Colombia},
|
|
||||||
{"CD", Country.Congo},
|
|
||||||
{"CR", Country.Costa_Rica},
|
|
||||||
{"CI", Country.Cote_d_Ivoire},
|
|
||||||
{"HR", Country.Croatia},
|
|
||||||
{"CY", Country.Cyprus},
|
|
||||||
{"CZ", Country.Czech_Republic},
|
|
||||||
{"DK", Country.Denmark},
|
|
||||||
{"DM", Country.Dominica},
|
|
||||||
{"DO", Country.Dominican_Republic},
|
|
||||||
{"EC", Country.Ecuador},
|
|
||||||
{"EG", Country.Egypt},
|
|
||||||
{"SV", Country.El_Salvador},
|
|
||||||
{"EE", Country.Estonia},
|
|
||||||
{"ET", Country.Ethiopia},
|
|
||||||
{"FO", Country.Faroe_Islands},
|
|
||||||
{"FI", Country.Finland},
|
|
||||||
{"FR", Country.France},
|
|
||||||
{"GF", Country.French_Guiana},
|
|
||||||
{"PF", Country.French_Polynesia},
|
|
||||||
{"GA", Country.Gabon},
|
|
||||||
{"GE", Country.Georgia},
|
|
||||||
{"DE", Country.Germany},
|
|
||||||
{"GH", Country.Ghana},
|
|
||||||
{"GI", Country.Gibraltar},
|
|
||||||
{"GR", Country.Greece},
|
|
||||||
{"GL", Country.Greenland},
|
|
||||||
{"GD", Country.Grenada},
|
|
||||||
{"GP", Country.Guadeloupe},
|
|
||||||
{"GU", Country.Guam},
|
|
||||||
{"GT", Country.Guatemala},
|
|
||||||
{"GG", Country.Guernsey},
|
|
||||||
{"GY", Country.Guyana},
|
|
||||||
{"HT", Country.Haiti},
|
|
||||||
{"HN", Country.Honduras},
|
|
||||||
{"HK", Country.Hong_Kong},
|
|
||||||
{"HU", Country.Hungary},
|
|
||||||
{"IS", Country.Iceland},
|
|
||||||
{"IN", Country.India},
|
|
||||||
{"ID", Country.Indonesia},
|
|
||||||
{"IQ", Country.Iraq},
|
|
||||||
{"IE", Country.Ireland},
|
|
||||||
{"IR", Country.Iran},
|
|
||||||
{"IM", Country.Isle_of_Man},
|
|
||||||
{"IL", Country.Israel},
|
|
||||||
{"IT", Country.Italy},
|
|
||||||
{"JM", Country.Jamaica},
|
|
||||||
{"JP", Country.Japan},
|
|
||||||
{"JE", Country.Jersey},
|
|
||||||
{"JO", Country.Jordan},
|
|
||||||
{"KZ", Country.Kazakhstan},
|
|
||||||
{"KE", Country.Kenya},
|
|
||||||
{"KR", Country.Korea},
|
|
||||||
{"KW", Country.Kuwait},
|
|
||||||
{"KG", Country.Kyrgyzstan},
|
|
||||||
{"LA", Country.Laos},
|
|
||||||
{"LV", Country.Latvia},
|
|
||||||
{"LB", Country.Lebanon},
|
|
||||||
{"LY", Country.Libya},
|
|
||||||
{"LI", Country.Liechtenstein},
|
|
||||||
{"LT", Country.Lithuania},
|
|
||||||
{"LU", Country.Luxembourg},
|
|
||||||
{"MO", Country.Macau},
|
|
||||||
{"MK", Country.Macedonia},
|
|
||||||
{"MG", Country.Madagascar},
|
|
||||||
{"MY", Country.Malaysia},
|
|
||||||
{"MV", Country.Maldives},
|
|
||||||
{"ML", Country.Mali},
|
|
||||||
{"MT", Country.Malta},
|
|
||||||
{"MQ", Country.Martinique},
|
|
||||||
{"MU", Country.Mauritius},
|
|
||||||
{"YT", Country.Mayotte},
|
|
||||||
{"MX", Country.Mexico},
|
|
||||||
{"MD", Country.Moldova},
|
|
||||||
{"MC", Country.Monaco},
|
|
||||||
{"MN", Country.Mongolia},
|
|
||||||
{"ME", Country.Montenegro},
|
|
||||||
{"MA", Country.Morocco},
|
|
||||||
{"MZ", Country.Mozambique},
|
|
||||||
{"MM", Country.Myanmar},
|
|
||||||
{"NA", Country.Namibia},
|
|
||||||
{"NP", Country.Nepal},
|
|
||||||
{"NL", Country.Netherlands},
|
|
||||||
{"NZ", Country.New_Zealand},
|
|
||||||
{"NI", Country.Nicaragua},
|
|
||||||
{"NG", Country.Nigeria},
|
|
||||||
{"MP", Country.Northern_Mariana_Islands},
|
|
||||||
{"NO", Country.Norway},
|
|
||||||
{"OM", Country.Oman},
|
|
||||||
{"PK", Country.Pakistan},
|
|
||||||
{"PS", Country.Palestinian_Territory},
|
|
||||||
{"PA", Country.Panama},
|
|
||||||
{"PG", Country.Papua_New_Guinea},
|
|
||||||
{"PY", Country.Paraguay},
|
|
||||||
{"PE", Country.Peru},
|
|
||||||
{"PH", Country.Philippines},
|
|
||||||
{"PL", Country.Poland},
|
|
||||||
{"PT", Country.Portugal},
|
|
||||||
{"PR", Country.Puerto_Rico},
|
|
||||||
{"QA", Country.Qatar},
|
|
||||||
{"RE", Country.Reunion},
|
|
||||||
{"RO", Country.Romania},
|
|
||||||
{"RU", Country.Russian_Federation},
|
|
||||||
{"RW", Country.Rwanda},
|
|
||||||
{"LC", Country.Saint_Lucia},
|
|
||||||
{"SA", Country.Saudi_Arabia},
|
|
||||||
{"SN", Country.Senegal},
|
|
||||||
{"RS", Country.Serbia},
|
|
||||||
{"SC", Country.Seychelles},
|
|
||||||
{"SG", Country.Singapore},
|
|
||||||
{"SK", Country.Slovakia},
|
|
||||||
{"SI", Country.Slovenia},
|
|
||||||
{"ZA", Country.South_Africa},
|
|
||||||
{"ES", Country.Spain},
|
|
||||||
{"LK", Country.Sri_Lanka},
|
|
||||||
{"KN", Country.Saint_Kitts_and_Nevis},
|
|
||||||
{"PM", Country.Saint_Vincent_and_the_Grenadines},
|
|
||||||
{"SD", Country.Sudan},
|
|
||||||
{"SR", Country.Suriname},
|
|
||||||
{"SE", Country.Sweden},
|
|
||||||
{"CH", Country.Switzerland},
|
|
||||||
{"TW", Country.Taiwan},
|
|
||||||
{"TJ", Country.Tajikistan},
|
|
||||||
{"TZ", Country.Tanzania},
|
|
||||||
{"TH", Country.Thailand},
|
|
||||||
{"TG", Country.Togo},
|
|
||||||
{"TT", Country.Trinidad_and_Tobago},
|
|
||||||
{"TN", Country.Tunisia},
|
|
||||||
{"TR", Country.Turkey},
|
|
||||||
{"TM", Country.Turkmenistan},
|
|
||||||
{"TC", Country.Turks_and_Caicos_Islands},
|
|
||||||
{"UG", Country.Uganda},
|
|
||||||
{"UA", Country.Ukraine},
|
|
||||||
{"GB", Country.United_Kingdom},
|
|
||||||
{"VI", Country.Virgin_Islands},
|
|
||||||
{"AE", Country.United_Arab_Emirates},
|
|
||||||
{"UY", Country.Uruguay},
|
|
||||||
{"UZ", Country.Uzbekistan},
|
|
||||||
{"VE", Country.Venezuela},
|
|
||||||
{"VN", Country.Viet_Nam},
|
|
||||||
{"YE", Country.Yemen},
|
|
||||||
{"ZM", Country.Zambia},
|
|
||||||
{"ZW", Country.Zimbabwe}
|
|
||||||
});
|
|
||||||
|
|
||||||
public static IReadOnlyDictionary<Country, string> ResolveString { get; } = new Dictionary<Country, string>
|
|
||||||
{
|
|
||||||
{Country.Afghanistan, "Afghanistan"},
|
|
||||||
{Country.Aland_Islands, "Aland Islands"},
|
|
||||||
{Country.Albania, "Albania"},
|
|
||||||
{Country.Algeria, "Algeria"},
|
|
||||||
{Country.American_Samoa, "American Samoa"},
|
|
||||||
{Country.Andorra, "Andorra"},
|
|
||||||
{Country.Angola, "Angola"},
|
|
||||||
{Country.Anguilla, "Anguilla"},
|
|
||||||
{Country.Antigua_and_Barbuda, "Antigua and Barbuda"},
|
|
||||||
{Country.Argentina, "Argentina"},
|
|
||||||
{Country.Armenia, "Armenia"},
|
|
||||||
{Country.Aruba, "Aruba"},
|
|
||||||
{Country.Australia, "Australia"},
|
|
||||||
{Country.Austria, "Austria"},
|
|
||||||
{Country.Azerbaijan, "Azerbaijan"},
|
|
||||||
{Country.Bahamas, "Bahamas"},
|
|
||||||
{Country.Bahrain, "Bahrain"},
|
|
||||||
{Country.Bangladesh, "Bangladesh"},
|
|
||||||
{Country.Barbados, "Barbados"},
|
|
||||||
{Country.Belarus, "Belarus"},
|
|
||||||
{Country.Belgium, "Belgium"},
|
|
||||||
{Country.Belize, "Belize"},
|
|
||||||
{Country.Bhutan, "Bhutan"},
|
|
||||||
{Country.Bolivia, "Bolivia"},
|
|
||||||
{Country.Bosnia_and_Herzegovina, "Bosnia and Herzegovina"},
|
|
||||||
{Country.Botswana, "Botswana"},
|
|
||||||
{Country.Brazil, "Brazil"},
|
|
||||||
{Country.Brunei_Darussalam, "Brunei Darussalam"},
|
|
||||||
{Country.Bulgaria, "Bulgaria"},
|
|
||||||
{Country.Burundi, "Burundi"},
|
|
||||||
{Country.Cambodia, "Cambodia"},
|
|
||||||
{Country.Cameroon, "Cameroon"},
|
|
||||||
{Country.Canada, "Canada"},
|
|
||||||
{Country.Cape_Verde, "Cape Verde"},
|
|
||||||
{Country.Cayman_Islands, "Cayman Islands"},
|
|
||||||
{Country.Chad, "Chad"},
|
|
||||||
{Country.Chile, "Chile"},
|
|
||||||
{Country.China, "China"},
|
|
||||||
{Country.Colombia, "Colombia"},
|
|
||||||
{Country.Congo, "Congo, the Democratic Republic of the"},
|
|
||||||
{Country.Costa_Rica, "Costa Rica"},
|
|
||||||
{Country.Cote_d_Ivoire, "Cote d'Ivoire"},
|
|
||||||
{Country.Croatia, "Croatia"},
|
|
||||||
{Country.Cyprus, "Cyprus"},
|
|
||||||
{Country.Czech_Republic, "Czech Republic"},
|
|
||||||
{Country.Denmark, "Denmark"},
|
|
||||||
{Country.Dominica, "Dominica"},
|
|
||||||
{Country.Dominican_Republic, "Dominican Republic"},
|
|
||||||
{Country.Ecuador, "Ecuador"},
|
|
||||||
{Country.Egypt, "Egypt"},
|
|
||||||
{Country.El_Salvador, "El Salvador"},
|
|
||||||
{Country.Estonia, "Estonia"},
|
|
||||||
{Country.Eswatini, "Eswatini"},
|
|
||||||
{Country.Ethiopia, "Ethiopia"},
|
|
||||||
{Country.Faroe_Islands, "Faroe Islands"},
|
|
||||||
{Country.Finland, "Finland"},
|
|
||||||
{Country.France, "France"},
|
|
||||||
{Country.French_Guiana, "French Guiana"},
|
|
||||||
{Country.French_Polynesia, "French Polynesia"},
|
|
||||||
{Country.Gabon, "Gabon"},
|
|
||||||
{Country.Georgia, "Georgia"},
|
|
||||||
{Country.Germany, "Germany"},
|
|
||||||
{Country.Ghana, "Ghana"},
|
|
||||||
{Country.Gibraltar, "Gibraltar"},
|
|
||||||
{Country.Greece, "Greece"},
|
|
||||||
{Country.Greenland, "Greenland"},
|
|
||||||
{Country.Grenada, "Grenada"},
|
|
||||||
{Country.Guadeloupe, "Guadeloupe"},
|
|
||||||
{Country.Guam, "Guam"},
|
|
||||||
{Country.Guatemala, "Guatemala"},
|
|
||||||
{Country.Guernsey, "Guernsey"},
|
|
||||||
{Country.Guyana, "Guyana"},
|
|
||||||
{Country.Haiti, "Haiti"},
|
|
||||||
{Country.Honduras, "Honduras"},
|
|
||||||
{Country.Hong_Kong, "Hong Kong"},
|
|
||||||
{Country.Hungary, "Hungary"},
|
|
||||||
{Country.Iceland, "Iceland"},
|
|
||||||
{Country.India, "India"},
|
|
||||||
{Country.Indonesia, "Indonesia"},
|
|
||||||
{Country.Iran, "Iran"},
|
|
||||||
{Country.Iraq, "Iraq"},
|
|
||||||
{Country.Ireland, "Ireland"},
|
|
||||||
{Country.Isle_of_Man, "Isle of Man"},
|
|
||||||
{Country.Israel, "Israel"},
|
|
||||||
{Country.Italy, "Italy"},
|
|
||||||
{Country.Jamaica, "Jamaica"},
|
|
||||||
{Country.Japan, "Japan"},
|
|
||||||
{Country.Jersey, "Jersey"},
|
|
||||||
{Country.Jordan, "Jordan"},
|
|
||||||
{Country.Kazakhstan, "Kazakhstan"},
|
|
||||||
{Country.Kenya, "Kenya"},
|
|
||||||
{Country.Korea, "Korea, Republic of"},
|
|
||||||
{Country.Kuwait, "Kuwait"},
|
|
||||||
{Country.Kyrgyzstan, "Kyrgyzstan"},
|
|
||||||
{Country.Laos, "Lao People's Democratic Republic"},
|
|
||||||
{Country.Latvia, "Latvia"},
|
|
||||||
{Country.Lebanon, "Lebanon"},
|
|
||||||
{Country.Libya, "Libya"},
|
|
||||||
{Country.Liechtenstein, "Liechtenstein"},
|
|
||||||
{Country.Lithuania, "Lithuania"},
|
|
||||||
{Country.Luxembourg, "Luxembourg"},
|
|
||||||
{Country.Macau, "Macau"},
|
|
||||||
{Country.Macedonia, "Macedonia, the former Yugoslav Republic of"},
|
|
||||||
{Country.Madagascar, "Madagascar"},
|
|
||||||
{Country.Malaysia, "Malaysia"},
|
|
||||||
{Country.Maldives, "Maldives"},
|
|
||||||
{Country.Mali, "Mali"},
|
|
||||||
{Country.Malta, "Malta"},
|
|
||||||
{Country.Martinique, "Martinique"},
|
|
||||||
{Country.Mauritius, "Mauritius"},
|
|
||||||
{Country.Mayotte, "Mayotte"},
|
|
||||||
{Country.Mexico, "Mexico"},
|
|
||||||
{Country.Moldova, "Moldova, Republic of"},
|
|
||||||
{Country.Monaco, "Monaco"},
|
|
||||||
{Country.Mongolia, "Mongolia"},
|
|
||||||
{Country.Montenegro, "Montenegro"},
|
|
||||||
{Country.Morocco, "Morocco"},
|
|
||||||
{Country.Mozambique, "Mozambique"},
|
|
||||||
{Country.Myanmar, "Myanmar"},
|
|
||||||
{Country.Namibia, "Namibia"},
|
|
||||||
{Country.Nepal, "Nepal"},
|
|
||||||
{Country.Netherlands, "Netherlands"},
|
|
||||||
{Country.New_Zealand, "New Zealand"},
|
|
||||||
{Country.Nicaragua, "Nicaragua"},
|
|
||||||
{Country.Nigeria, "Nigeria"},
|
|
||||||
{Country.Northern_Mariana_Islands, "Northern Mariana Islands"},
|
|
||||||
{Country.Norway, "Norway"},
|
|
||||||
{Country.Oman, "Oman"},
|
|
||||||
{Country.Pakistan, "Pakistan"},
|
|
||||||
{Country.Palestinian_Territory, "Palestinian Territory, Occupied"},
|
|
||||||
{Country.Panama, "Panama"},
|
|
||||||
{Country.Papua_New_Guinea, "Papua New Guinea"},
|
|
||||||
{Country.Paraguay, "Paraguay"},
|
|
||||||
{Country.Peru, "Peru"},
|
|
||||||
{Country.Philippines, "Philippines"},
|
|
||||||
{Country.Poland, "Poland"},
|
|
||||||
{Country.Portugal, "Portugal"},
|
|
||||||
{Country.Puerto_Rico, "Puerto Rico"},
|
|
||||||
{Country.Qatar, "Qatar"},
|
|
||||||
{Country.Reunion, "Reunion"},
|
|
||||||
{Country.Romania, "Romania"},
|
|
||||||
{Country.Russian_Federation, "Russian Federation"},
|
|
||||||
{Country.Rwanda, "Rwanda"},
|
|
||||||
{Country.Saint_Kitts_and_Nevis, "Saint Kitts and Nevis"},
|
|
||||||
{Country.Saint_Lucia, "Saint Lucia"},
|
|
||||||
{Country.Saint_Vincent_and_the_Grenadines, "Saint Vincent and the Grenadines"},
|
|
||||||
{Country.Saudi_Arabia, "Saudi Arabia"},
|
|
||||||
{Country.Senegal, "Senegal"},
|
|
||||||
{Country.Serbia, "Serbia"},
|
|
||||||
{Country.Seychelles, "Seychelles"},
|
|
||||||
{Country.Singapore, "Singapore"},
|
|
||||||
{Country.Slovakia, "Slovakia"},
|
|
||||||
{Country.Slovenia, "Slovenia"},
|
|
||||||
{Country.South_Africa, "South Africa"},
|
|
||||||
{Country.Spain, "Spain"},
|
|
||||||
{Country.Sri_Lanka, "Sri Lanka"},
|
|
||||||
{Country.Sudan, "Sudan"},
|
|
||||||
{Country.Suriname, "Suriname"},
|
|
||||||
{Country.Sweden, "Sweden"},
|
|
||||||
{Country.Switzerland, "Switzerland"},
|
|
||||||
{Country.Taiwan, "Taiwan"},
|
|
||||||
{Country.Tajikistan, "Tajikistan"},
|
|
||||||
{Country.Tanzania, "Tanzania, United Republic of"},
|
|
||||||
{Country.Thailand, "Thailand"},
|
|
||||||
{Country.Togo, "Togo"},
|
|
||||||
{Country.Trinidad_and_Tobago, "Trinidad and Tobago"},
|
|
||||||
{Country.Tunisia, "Tunisia"},
|
|
||||||
{Country.Turkey, "Turkey"},
|
|
||||||
{Country.Turkmenistan, "Turkmenistan"},
|
|
||||||
{Country.Turks_and_Caicos_Islands, "Turks and Caicos Islands"},
|
|
||||||
{Country.Uganda, "Uganda"},
|
|
||||||
{Country.Ukraine, "Ukraine"},
|
|
||||||
{Country.United_Arab_Emirates, "United Arab Emirates"},
|
|
||||||
{Country.United_Kingdom, "United Kingdom"},
|
|
||||||
{Country.United_States, "United States"},
|
|
||||||
{Country.Uruguay, "Uruguay"},
|
|
||||||
{Country.Uzbekistan, "Uzbekistan"},
|
|
||||||
{Country.Venezuela, "Venezuela"},
|
|
||||||
{Country.Viet_Nam, "Viet Nam"},
|
|
||||||
{Country.Virgin_Islands, "Virgin Islands, U.S."},
|
|
||||||
{Country.Yemen, "Yemen"},
|
|
||||||
{Country.Zambia, "Zambia"},
|
|
||||||
{Country.Zimbabwe, "Zimbabwe"}
|
|
||||||
};
|
|
||||||
|
|
||||||
public static IReadOnlyDictionary<string, Country> ResolveCountry { get; } = new Dictionary<string, Country>()
|
|
||||||
{
|
|
||||||
{"Afghanistan", Country.Afghanistan},
|
|
||||||
{"Aland Islands", Country.Aland_Islands},
|
|
||||||
{"Albania", Country.Albania},
|
|
||||||
{"Algeria", Country.Algeria},
|
|
||||||
{"American Samoa", Country.American_Samoa},
|
|
||||||
{"Andorra", Country.Andorra},
|
|
||||||
{"Angola", Country.Angola},
|
|
||||||
{"Anguilla", Country.Anguilla},
|
|
||||||
{"Antigua and Barbuda", Country.Antigua_and_Barbuda},
|
|
||||||
{"Argentina", Country.Argentina},
|
|
||||||
{"Armenia", Country.Armenia},
|
|
||||||
{"Aruba", Country.Aruba},
|
|
||||||
{"Australia", Country.Australia},
|
|
||||||
{"Austria", Country.Austria},
|
|
||||||
{"Azerbaijan", Country.Azerbaijan},
|
|
||||||
{"Bahamas", Country.Bahamas},
|
|
||||||
{"Bahrain", Country.Bahrain},
|
|
||||||
{"Bangladesh", Country.Bangladesh},
|
|
||||||
{"Barbados", Country.Barbados},
|
|
||||||
{"Belarus", Country.Belarus},
|
|
||||||
{"Belgium", Country.Belgium},
|
|
||||||
{"Belize", Country.Belize},
|
|
||||||
{"Bhutan", Country.Bhutan},
|
|
||||||
{"Bolivia", Country.Bolivia},
|
|
||||||
{"Bosnia and Herzegovina", Country.Bosnia_and_Herzegovina},
|
|
||||||
{"Botswana", Country.Botswana},
|
|
||||||
{"Brazil", Country.Brazil},
|
|
||||||
{"Brunei Darussalam", Country.Brunei_Darussalam},
|
|
||||||
{"Bulgaria", Country.Bulgaria},
|
|
||||||
{"Burundi", Country.Burundi},
|
|
||||||
{"Cambodia", Country.Cambodia},
|
|
||||||
{"Cameroon", Country.Cameroon},
|
|
||||||
{"Canada", Country.Canada},
|
|
||||||
{"Cape Verde", Country.Cape_Verde},
|
|
||||||
{"Cayman Islands", Country.Cayman_Islands},
|
|
||||||
{"Chad", Country.Chad},
|
|
||||||
{"Chile", Country.Chile},
|
|
||||||
{"China", Country.China},
|
|
||||||
{"Colombia", Country.Colombia},
|
|
||||||
{"Congo, the Democratic Republic of the", Country.Congo},
|
|
||||||
{"Costa Rica", Country.Costa_Rica},
|
|
||||||
{"Cote d'Ivoire", Country.Cote_d_Ivoire},
|
|
||||||
{"Croatia", Country.Croatia},
|
|
||||||
{"Cyprus", Country.Cyprus},
|
|
||||||
{"Czech Republic", Country.Czech_Republic},
|
|
||||||
{"Denmark", Country.Denmark},
|
|
||||||
{"Dominica", Country.Dominica},
|
|
||||||
{"Dominican Republic", Country.Dominican_Republic},
|
|
||||||
{"Ecuador", Country.Ecuador},
|
|
||||||
{"Egypt", Country.Egypt},
|
|
||||||
{"El Salvador", Country.El_Salvador},
|
|
||||||
{"Estonia", Country.Estonia},
|
|
||||||
{"Eswatini", Country.Eswatini},
|
|
||||||
{"Ethiopia", Country.Ethiopia},
|
|
||||||
{"Faroe Islands", Country.Faroe_Islands},
|
|
||||||
{"Finland", Country.Finland},
|
|
||||||
{"France", Country.France},
|
|
||||||
{"French Guiana", Country.French_Guiana},
|
|
||||||
{"French Polynesia", Country.French_Polynesia},
|
|
||||||
{"Gabon", Country.Gabon},
|
|
||||||
{"Georgia", Country.Georgia},
|
|
||||||
{"Germany", Country.Germany},
|
|
||||||
{"Ghana", Country.Ghana},
|
|
||||||
{"Gibraltar", Country.Gibraltar},
|
|
||||||
{"Greece", Country.Greece},
|
|
||||||
{"Greenland", Country.Greenland},
|
|
||||||
{"Grenada", Country.Grenada},
|
|
||||||
{"Guadeloupe", Country.Guadeloupe},
|
|
||||||
{"Guam", Country.Guam},
|
|
||||||
{"Guatemala", Country.Guatemala},
|
|
||||||
{"Guernsey", Country.Guernsey},
|
|
||||||
{"Guyana", Country.Guyana},
|
|
||||||
{"Haiti", Country.Haiti},
|
|
||||||
{"Honduras", Country.Honduras},
|
|
||||||
{"Hong Kong", Country.Hong_Kong},
|
|
||||||
{"Hungary", Country.Hungary},
|
|
||||||
{"Iceland", Country.Iceland},
|
|
||||||
{"India", Country.India},
|
|
||||||
{"Indonesia", Country.Indonesia},
|
|
||||||
{"Iran", Country.Iran},
|
|
||||||
{"Iraq", Country.Iraq},
|
|
||||||
{"Ireland", Country.Ireland},
|
|
||||||
{"Isle of Man", Country.Isle_of_Man},
|
|
||||||
{"Israel", Country.Israel},
|
|
||||||
{"Italy", Country.Italy},
|
|
||||||
{"Jamaica", Country.Jamaica},
|
|
||||||
{"Japan", Country.Japan},
|
|
||||||
{"Jersey", Country.Jersey},
|
|
||||||
{"Jordan", Country.Jordan},
|
|
||||||
{"Kazakhstan", Country.Kazakhstan},
|
|
||||||
{"Kenya", Country.Kenya},
|
|
||||||
{"Korea, Republic of", Country.Korea},
|
|
||||||
{"Kuwait", Country.Kuwait},
|
|
||||||
{"Kyrgyzstan", Country.Kyrgyzstan},
|
|
||||||
{"Lao People's Democratic Republic", Country.Laos},
|
|
||||||
{"Latvia", Country.Latvia},
|
|
||||||
{"Lebanon", Country.Lebanon},
|
|
||||||
{"Libya", Country.Libya},
|
|
||||||
{"Liechtenstein", Country.Liechtenstein},
|
|
||||||
{"Lithuania", Country.Lithuania},
|
|
||||||
{"Luxembourg", Country.Luxembourg},
|
|
||||||
{"Macau", Country.Macau},
|
|
||||||
{"Macedonia, the former Yugoslav Republic of", Country.Macedonia},
|
|
||||||
{"Madagascar", Country.Madagascar},
|
|
||||||
{"Malaysia", Country.Malaysia},
|
|
||||||
{"Maldives", Country.Maldives},
|
|
||||||
{"Mali", Country.Mali},
|
|
||||||
{"Malta", Country.Malta},
|
|
||||||
{"Martinique", Country.Martinique},
|
|
||||||
{"Mauritius", Country.Mauritius},
|
|
||||||
{"Mayotte", Country.Mayotte},
|
|
||||||
{"Mexico", Country.Mexico},
|
|
||||||
{"Moldova, Republic of", Country.Moldova},
|
|
||||||
{"Monaco", Country.Monaco},
|
|
||||||
{"Mongolia", Country.Mongolia},
|
|
||||||
{"Montenegro", Country.Montenegro},
|
|
||||||
{"Morocco", Country.Morocco},
|
|
||||||
{"Mozambique", Country.Mozambique},
|
|
||||||
{"Myanmar", Country.Myanmar},
|
|
||||||
{"Namibia", Country.Namibia},
|
|
||||||
{"Nepal", Country.Nepal},
|
|
||||||
{"Netherlands", Country.Netherlands},
|
|
||||||
{"New Zealand", Country.New_Zealand},
|
|
||||||
{"Nicaragua", Country.Nicaragua},
|
|
||||||
{"Nigeria", Country.Nigeria},
|
|
||||||
{"Northern Mariana Islands", Country.Northern_Mariana_Islands},
|
|
||||||
{"Norway", Country.Norway},
|
|
||||||
{"Oman", Country.Oman},
|
|
||||||
{"Pakistan", Country.Pakistan},
|
|
||||||
{"Palestinian Territory, Occupied", Country.Palestinian_Territory},
|
|
||||||
{"Panama", Country.Panama},
|
|
||||||
{"Papua New Guinea", Country.Papua_New_Guinea},
|
|
||||||
{"Paraguay", Country.Paraguay},
|
|
||||||
{"Peru", Country.Peru},
|
|
||||||
{"Philippines", Country.Philippines},
|
|
||||||
{"Poland", Country.Poland},
|
|
||||||
{"Portugal", Country.Portugal},
|
|
||||||
{"Puerto Rico", Country.Puerto_Rico},
|
|
||||||
{"Qatar", Country.Qatar},
|
|
||||||
{"Reunion", Country.Reunion},
|
|
||||||
{"Romania", Country.Romania},
|
|
||||||
{"Russian Federation", Country.Russian_Federation},
|
|
||||||
{"Rwanda", Country.Rwanda},
|
|
||||||
{"Saint Kitts and Nevis", Country.Saint_Kitts_and_Nevis},
|
|
||||||
{"Saint Lucia", Country.Saint_Lucia},
|
|
||||||
{"Saint Vincent and the Grenadines", Country.Saint_Vincent_and_the_Grenadines},
|
|
||||||
{"Saudi Arabia", Country.Saudi_Arabia},
|
|
||||||
{"Senegal", Country.Senegal},
|
|
||||||
{"Serbia", Country.Serbia},
|
|
||||||
{"Seychelles", Country.Seychelles},
|
|
||||||
{"Singapore", Country.Singapore},
|
|
||||||
{"Slovakia", Country.Slovakia},
|
|
||||||
{"Slovenia", Country.Slovenia},
|
|
||||||
{"South Africa", Country.South_Africa},
|
|
||||||
{"Spain", Country.Spain},
|
|
||||||
{"Sri Lanka", Country.Sri_Lanka},
|
|
||||||
{"Sudan", Country.Sudan},
|
|
||||||
{"Suriname", Country.Suriname},
|
|
||||||
{"Sweden", Country.Sweden},
|
|
||||||
{"Switzerland", Country.Switzerland},
|
|
||||||
{"Taiwan", Country.Taiwan},
|
|
||||||
{"Tajikistan", Country.Tajikistan},
|
|
||||||
{"Tanzania, United Republic of", Country.Tanzania},
|
|
||||||
{"Thailand", Country.Thailand},
|
|
||||||
{"Togo", Country.Togo},
|
|
||||||
{"Trinidad and Tobago", Country.Trinidad_and_Tobago},
|
|
||||||
{"Tunisia", Country.Tunisia},
|
|
||||||
{"Turkey", Country.Turkey},
|
|
||||||
{"Turkmenistan", Country.Turkmenistan},
|
|
||||||
{"Turks and Caicos Islands", Country.Turks_and_Caicos_Islands},
|
|
||||||
{"Uganda", Country.Uganda},
|
|
||||||
{"Ukraine", Country.Ukraine},
|
|
||||||
{"United Arab Emirates", Country.United_Arab_Emirates},
|
|
||||||
{"United Kingdom", Country.United_Kingdom},
|
|
||||||
{"United States", Country.United_States},
|
|
||||||
{"Uruguay", Country.Uruguay},
|
|
||||||
{"Uzbekistan", Country.Uzbekistan},
|
|
||||||
{"Venezuela", Country.Venezuela},
|
|
||||||
{"Viet Nam", Country.Viet_Nam},
|
|
||||||
{"Virgin Islands, U.S.", Country.Virgin_Islands},
|
|
||||||
{"Yemen", Country.Yemen},
|
|
||||||
{"Zambia", Country.Zambia},
|
|
||||||
{"Zimbabwe", Country.Zimbabwe}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
// ReSharper disable InconsistentNaming
|
|
||||||
|
|
||||||
namespace SteamLib.Core.Enums;
|
|
||||||
|
|
||||||
public enum Currency
|
|
||||||
{
|
|
||||||
None = 0,
|
|
||||||
USD = 1,
|
|
||||||
GBP = 2,
|
|
||||||
EUR = 3,
|
|
||||||
CHF = 4,
|
|
||||||
RUB = 5,
|
|
||||||
PLN = 6,
|
|
||||||
BRL = 7,
|
|
||||||
JPY = 8,
|
|
||||||
NOK = 9,
|
|
||||||
IDR = 10,
|
|
||||||
MYR = 11,
|
|
||||||
PHP = 12,
|
|
||||||
SGD = 13,
|
|
||||||
THB = 14,
|
|
||||||
VND = 15,
|
|
||||||
KRW = 16,
|
|
||||||
TRY = 17,
|
|
||||||
UAH = 18,
|
|
||||||
MXN = 19,
|
|
||||||
CAD = 20,
|
|
||||||
AUD = 21,
|
|
||||||
NZD = 22,
|
|
||||||
CNY = 23,
|
|
||||||
INR = 24,
|
|
||||||
CLP = 25,
|
|
||||||
PEN = 26,
|
|
||||||
COP = 27,
|
|
||||||
ZAR = 28,
|
|
||||||
HKD = 29,
|
|
||||||
TWD = 30,
|
|
||||||
SAR = 31,
|
|
||||||
AED = 32,
|
|
||||||
ARS = 34,
|
|
||||||
ILS = 35,
|
|
||||||
KZT = 37,
|
|
||||||
KWD = 38,
|
|
||||||
QAR = 39,
|
|
||||||
CRC = 40,
|
|
||||||
UYU = 41
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class CurrencyInfo
|
|
||||||
{
|
|
||||||
public static IReadOnlyDictionary<Currency, string> CurrencySymbols { get; } = new Dictionary<Currency, string>
|
|
||||||
{
|
|
||||||
{Currency.USD, "$"},
|
|
||||||
{Currency.GBP, "£"},
|
|
||||||
{Currency.EUR, "€"},
|
|
||||||
{Currency.CHF, "CHF"},
|
|
||||||
{Currency.RUB, "pуб."},
|
|
||||||
{Currency.PLN, "zł"},
|
|
||||||
{Currency.BRL, "R$"},
|
|
||||||
{Currency.JPY, "¥"},
|
|
||||||
{Currency.NOK, "kr"},
|
|
||||||
{Currency.IDR, "Rp"},
|
|
||||||
{Currency.MYR, "RM"},
|
|
||||||
{Currency.PHP, "P"},
|
|
||||||
{Currency.SGD, "S$"},
|
|
||||||
{Currency.THB, "฿"},
|
|
||||||
{Currency.VND, "₫"},
|
|
||||||
{Currency.KRW, "₩"},
|
|
||||||
{Currency.TRY, "TL"},
|
|
||||||
{Currency.UAH, "₴"},
|
|
||||||
{Currency.MXN, "Mex$"},
|
|
||||||
{Currency.CAD, "CDN$"},
|
|
||||||
{Currency.AUD, "A$"},
|
|
||||||
{Currency.NZD, "NZ$"},
|
|
||||||
{Currency.CNY, "¥"},
|
|
||||||
{Currency.INR, "₹"},
|
|
||||||
{Currency.CLP, "CLP$"},
|
|
||||||
{Currency.PEN, "S/."},
|
|
||||||
{Currency.COP, "COL$"},
|
|
||||||
{Currency.ZAR, "R"},
|
|
||||||
{Currency.HKD, "HK$"},
|
|
||||||
{Currency.TWD, "NT$"},
|
|
||||||
{Currency.SAR, "SR"},
|
|
||||||
{Currency.AED, "AED"},
|
|
||||||
{Currency.ARS, "ARS$"},
|
|
||||||
{Currency.ILS, "₪"},
|
|
||||||
{Currency.KZT, "₸"},
|
|
||||||
{Currency.KWD, "KD"},
|
|
||||||
{Currency.QAR, "QR"},
|
|
||||||
{Currency.CRC, "₡"},
|
|
||||||
{Currency.UYU, "$U"}
|
|
||||||
};
|
|
||||||
|
|
||||||
public static int ToInt(this Currency currency)
|
|
||||||
{
|
|
||||||
return (int) currency;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string ToIntString(this Currency currency)
|
|
||||||
{
|
|
||||||
return currency.ToInt().ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
namespace SteamLib.Core.Enums;
|
|
||||||
|
|
||||||
public enum SteamDomain
|
|
||||||
{
|
|
||||||
Undefined = 0,
|
|
||||||
Community = 1,
|
|
||||||
Store = 2,
|
|
||||||
Help = 3,
|
|
||||||
TV = 4,
|
|
||||||
Checkout = 5
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace SteamLib.Core.Interfaces;
|
|
||||||
|
|
||||||
public interface ICaptchaResolver
|
|
||||||
{
|
|
||||||
public Task<string> Resolve(Uri imageUrl, HttpClient client);
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
namespace SteamLib.Core.Interfaces;
|
|
||||||
|
|
||||||
public enum EmailConfirmationType
|
|
||||||
{
|
|
||||||
AttachPhoneAuthenticator
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IEmailProvider
|
|
||||||
{
|
|
||||||
public int MaxRetryCount { get; }
|
|
||||||
public Task<string> GetEmailAuthCode(ILoginConsumer caller);
|
|
||||||
public Task<string> GetAddAuthenticatorCode(ILoginConsumer caller);
|
|
||||||
|
|
||||||
public Task ConfirmEmailLink(ILoginConsumer caller, EmailConfirmationType confirmationType);
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
namespace SteamLib.Core.Interfaces;
|
|
||||||
|
|
||||||
public interface ILoginConsumer
|
|
||||||
{
|
|
||||||
public string FriendlyName { get; }
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace SteamLib.Core.Interfaces;
|
|
||||||
|
|
||||||
public interface ISteamGuardProvider
|
|
||||||
{
|
|
||||||
public int MaxRetryCount { get; }
|
|
||||||
public ValueTask<string> GetSteamGuardCode(ILoginConsumer caller);
|
|
||||||
}
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
using System.Collections.ObjectModel;
|
|
||||||
using AchiesUtilities.Models;
|
|
||||||
using SteamLib.Exceptions;
|
|
||||||
using SteamLib.Utility;
|
|
||||||
|
|
||||||
namespace SteamLib.Core.StatusCodes;
|
|
||||||
|
|
||||||
public partial class SteamStatusCode : Enumeration
|
|
||||||
{
|
|
||||||
public static IReadOnlyDictionary<int, SteamStatusCode> StatusCodes { get; }
|
|
||||||
|
|
||||||
static SteamStatusCode()
|
|
||||||
{
|
|
||||||
StatusCodes =
|
|
||||||
new ReadOnlyDictionary<int, SteamStatusCode>(GetAll<SteamStatusCode>()
|
|
||||||
.ToDictionary(ssc => ssc.Id, ssc => ssc));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected SteamStatusCode(int id, string name) : base(id, name)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Tries to translate status code. If no status code was found <see cref="SteamStatusCodeException" /> with
|
|
||||||
/// <see cref="Undefined" /> will be thrown
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"></typeparam>
|
|
||||||
/// <param name="response"></param>
|
|
||||||
/// <param name="isUniversal"></param>
|
|
||||||
/// <returns>
|
|
||||||
/// <see cref="SteamStatusCode" /> if value is universal. <typeparamref name="T" /> if value is
|
|
||||||
/// <typeparamref name="T" /> specific
|
|
||||||
/// </returns>
|
|
||||||
/// <exception cref="SteamStatusCodeException"></exception>
|
|
||||||
public static SteamStatusCode Translate<T>(string response, out bool isUniversal) where T : SteamStatusCode
|
|
||||||
{
|
|
||||||
int statusCode;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
statusCode = Utilities.GetSuccessCode(response);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
throw new SteamStatusCodeException(Undefined, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Translate<T>(statusCode, out isUniversal);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Translate <paramref name="statusCode" /> to specific <see cref="T" /> or <see cref="SteamStatusCode" />
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"></typeparam>
|
|
||||||
/// <param name="statusCode"></param>
|
|
||||||
/// <param name="isUniversal"></param>
|
|
||||||
/// <returns>
|
|
||||||
/// <see cref="SteamStatusCode" /> if value is universal. <typeparamref name="T" /> if value is
|
|
||||||
/// <typeparamref name="T" /> specific
|
|
||||||
/// </returns>
|
|
||||||
/// <exception cref="SteamStatusCodeException"></exception>
|
|
||||||
public static SteamStatusCode Translate<T>(int statusCode, out bool isUniversal) where T : SteamStatusCode
|
|
||||||
{
|
|
||||||
if (statusCode < 2)
|
|
||||||
{
|
|
||||||
var status = StatusCodes[statusCode];
|
|
||||||
isUniversal = true;
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
var allSpecific = GetAll<T>();
|
|
||||||
var translated = allSpecific.FirstOrDefault(s => s.Id == statusCode);
|
|
||||||
|
|
||||||
isUniversal = translated == null;
|
|
||||||
|
|
||||||
if (translated != null)
|
|
||||||
{
|
|
||||||
return translated;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (translated == null && StatusCodes.TryGetValue(statusCode, out var universal))
|
|
||||||
{
|
|
||||||
return universal;
|
|
||||||
}
|
|
||||||
|
|
||||||
return translated ?? new SteamStatusCode(statusCode, nameof(Unknown));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
{
|
|
||||||
if (obj is not SteamStatusCode translated)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (Id < 2)
|
|
||||||
{
|
|
||||||
return translated.Id == Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
return base.Equals(translated);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
{
|
|
||||||
return base.GetHashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="response"></param>
|
|
||||||
/// <exception cref="SteamStatusCodeException"></exception>
|
|
||||||
public static void ValidateSuccessOrThrow<T>(string response) where T : SteamStatusCode
|
|
||||||
{
|
|
||||||
var translated = Translate<T>(response, out _);
|
|
||||||
if (translated.Id != 1)
|
|
||||||
throw new SteamStatusCodeException(translated, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Same as <see cref="ValidateSuccessOrThrow{T}(string)" /> with <see cref="SteamStatusCode" /> generic parameter
|
|
||||||
/// <br />
|
|
||||||
/// <b>(Used in case when steam status code is not defined for this operation)</b>
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="response"></param>
|
|
||||||
/// <exception cref="SteamStatusCodeException"></exception>
|
|
||||||
public static void ValidateSuccessOrThrow(string response)
|
|
||||||
{
|
|
||||||
ValidateSuccessOrThrow<SteamStatusCode>(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="statusCode"></param>
|
|
||||||
/// <exception cref="SteamStatusCodeException"></exception>
|
|
||||||
public static void ValidateSuccessOrThrow<T>(int statusCode) where T : SteamStatusCode
|
|
||||||
{
|
|
||||||
if (statusCode == 1) return;
|
|
||||||
var translated = Translate<T>(statusCode, out _);
|
|
||||||
throw new SteamStatusCodeException(translated, statusCode.ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Same as <see cref="ValidateSuccessOrThrow{T}(int)" /> with <see cref="SteamStatusCode" /> generic parameter <br />
|
|
||||||
/// <b>(Used in case when steam status code is not defined for this operation)</b>
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="statusCode"></param>
|
|
||||||
/// <exception cref="SteamStatusCodeException"></exception>
|
|
||||||
public static void ValidateSuccessOrThrow(int statusCode)
|
|
||||||
{
|
|
||||||
ValidateSuccessOrThrow<SteamStatusCode>(statusCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Validates that status code is <see cref="Ok" /> or <typeparamref name="T" /> specific
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T"></typeparam>
|
|
||||||
/// <param name="response"></param>
|
|
||||||
/// <param name="isUniversal"></param>
|
|
||||||
/// <exception cref="SteamStatusCodeException"></exception>
|
|
||||||
public static SteamStatusCode TranslateOrThrow<T>(string response, out bool isUniversal) where T : SteamStatusCode
|
|
||||||
{
|
|
||||||
var translated = Translate<T>(response, out isUniversal);
|
|
||||||
if (translated.Id < 1)
|
|
||||||
throw new SteamStatusCodeException(translated, response);
|
|
||||||
return translated;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return $"{Name} ({Id})";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
using SteamLib.Core.Interfaces;
|
|
||||||
|
|
||||||
namespace SteamLib.Exceptions;
|
|
||||||
|
|
||||||
public class LoginException : Exception
|
|
||||||
{
|
|
||||||
public LoginError Error { get; }
|
|
||||||
public string? Response { get; }
|
|
||||||
|
|
||||||
public LoginException(LoginError error)
|
|
||||||
: base($"Login was unsuccessful. Error {error}")
|
|
||||||
{
|
|
||||||
Error = error;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LoginException(string response)
|
|
||||||
{
|
|
||||||
Error = LoginError.UndefinedError;
|
|
||||||
Response = response;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LoginException(string? response, string? message, LoginError? error, Exception? innerException) : base(
|
|
||||||
message, innerException)
|
|
||||||
{
|
|
||||||
Response = response;
|
|
||||||
Error = error ?? LoginError.UndefinedError;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum LoginError
|
|
||||||
{
|
|
||||||
CaptchaRequired,
|
|
||||||
InvalidCredentials,
|
|
||||||
InvalidEmailAuthCode,
|
|
||||||
InvalidTwoFactorCode,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// SteamEmail authentication is required to login but no <see cref="IEmailProvider" /> was provided
|
|
||||||
/// </summary>
|
|
||||||
EmailAuthRequired,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// SteamGuard is required to login but no <see cref="ISteamGuardProvider" /> was provided
|
|
||||||
/// </summary>
|
|
||||||
SteamGuardRequired,
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Some error occurred while trying to login.
|
|
||||||
/// </summary>
|
|
||||||
UndefinedError
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
using SteamLib.Core.StatusCodes;
|
|
||||||
|
|
||||||
namespace SteamLib.Exceptions;
|
|
||||||
|
|
||||||
public class SteamStatusCodeException : Exception
|
|
||||||
{
|
|
||||||
public SteamStatusCode StatusCode { get; }
|
|
||||||
public string? Response { get; }
|
|
||||||
|
|
||||||
public SteamStatusCodeException(SteamStatusCode statusCode, string? response, Exception? innerException = null)
|
|
||||||
: base($"Steam return not successful status code {statusCode}", innerException)
|
|
||||||
{
|
|
||||||
StatusCode = statusCode;
|
|
||||||
Response = response;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SteamStatusCodeException(string message, SteamStatusCode statusCode, string? response = null,
|
|
||||||
Exception? innerException = null)
|
|
||||||
: base(message, innerException)
|
|
||||||
{
|
|
||||||
StatusCode = statusCode;
|
|
||||||
Response = response;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
using SteamLib.ProtoCore.Enums;
|
|
||||||
|
|
||||||
namespace SteamLib.ProtoCore.Exceptions;
|
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
public class EResultException : Exception
|
|
||||||
{
|
|
||||||
public EResult Result { get; }
|
|
||||||
|
|
||||||
public EResultException()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public EResultException(EResult result) : base("EResult error: " + result)
|
|
||||||
{
|
|
||||||
Result = result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EResultException(string message, EResult result, Exception inner) : base(message, inner)
|
|
||||||
{
|
|
||||||
Result = result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EResultException(EResult result, Exception inner) : base("EResult error: " + result, inner)
|
|
||||||
{
|
|
||||||
Result = result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
namespace SteamLib.ProtoCore.Exceptions;
|
|
||||||
|
|
||||||
[Serializable]
|
|
||||||
public class UnknownEResultException : Exception
|
|
||||||
{
|
|
||||||
public int EResult { get; }
|
|
||||||
|
|
||||||
public UnknownEResultException()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public UnknownEResultException(int eResult) : base("Got unknown EResult: " + eResult)
|
|
||||||
{
|
|
||||||
EResult = eResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
|
||||||
|
|
||||||
<PropertyGroup>
|
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
|
||||||
<Nullable>enable</Nullable>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<PackageReference Include="AchiesUtilities.Newtonsoft.JSON" Version="1.2.11" />
|
|
||||||
<PackageReference Include="AchiesUtilities.Web" Version="1.0.14" />
|
|
||||||
<PackageReference Include="HtmlAgilityPack" Version="1.11.67" />
|
|
||||||
<PackageReference Include="JetBrains.Annotations" Version="2024.2.0" />
|
|
||||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.1" />
|
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
|
||||||
<PackageReference Include="protobuf-net" Version="3.2.30" />
|
|
||||||
<PackageReference Include="protobuf-net.Core" Version="3.2.30" />
|
|
||||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.1.2" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
|
||||||
@@ -1,110 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
|
|
||||||
namespace SteamLib.Utility.MafileSerialization;
|
|
||||||
|
|
||||||
public partial class MafileSerializer //Write
|
|
||||||
{
|
|
||||||
private const string CREDITS_PROPERTY_NAME = "Credits";
|
|
||||||
|
|
||||||
public static string Serialize(MobileDataExtended mobileData, Formatting formatting = Formatting.Indented,
|
|
||||||
bool sign = true, MafileCredits? credits = null)
|
|
||||||
{
|
|
||||||
using var w = new StringWriter();
|
|
||||||
using var write = new JsonTextWriter(w);
|
|
||||||
write.Formatting = formatting;
|
|
||||||
var j = JObject.FromObject(mobileData);
|
|
||||||
j.Add(SIGNATURE_PROPERTY_NAME, MAFILE_VERSION);
|
|
||||||
if (sign)
|
|
||||||
{
|
|
||||||
credits ??= MafileCredits.Instance;
|
|
||||||
var obj = JObject.FromObject(credits);
|
|
||||||
j.Add(CREDITS_PROPERTY_NAME, obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
j.WriteTo(write);
|
|
||||||
return w.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static async Task<string> SerializeAsync(MobileDataExtended mobileData,
|
|
||||||
Formatting formatting = Formatting.Indented, bool sign = true, MafileCredits? credits = null)
|
|
||||||
{
|
|
||||||
await using var w = new StringWriter();
|
|
||||||
await using var write = new JsonTextWriter(w);
|
|
||||||
write.Formatting = formatting;
|
|
||||||
var j = JObject.FromObject(mobileData);
|
|
||||||
j.Add(SIGNATURE_PROPERTY_NAME, j);
|
|
||||||
if (sign)
|
|
||||||
{
|
|
||||||
credits ??= MafileCredits.Instance;
|
|
||||||
var obj = JObject.FromObject(credits);
|
|
||||||
j.Add(CREDITS_PROPERTY_NAME, obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
await j.WriteToAsync(write);
|
|
||||||
return w.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string SerializeLegacy(MobileData mobileData, Formatting formatting,
|
|
||||||
Dictionary<string, object?>? additionalProperties = null, bool sign = true, MafileCredits? credits = null)
|
|
||||||
{
|
|
||||||
var result = new LegacyMafile
|
|
||||||
{
|
|
||||||
SharedSecret = mobileData.SharedSecret,
|
|
||||||
IdentitySecret = mobileData.IdentitySecret,
|
|
||||||
DeviceId = mobileData.DeviceId
|
|
||||||
};
|
|
||||||
|
|
||||||
if (mobileData is MobileDataExtended ext)
|
|
||||||
{
|
|
||||||
result.RevocationCode = ext.RevocationCode ?? string.Empty;
|
|
||||||
result.AccountName = ext.AccountName;
|
|
||||||
result.SessionData = ext.SessionData == null
|
|
||||||
? null
|
|
||||||
: new
|
|
||||||
{
|
|
||||||
AccessToken = ext.SessionData?.MobileToken?.Token,
|
|
||||||
steamLoginSecure = ext.SessionData?.MobileToken?.SignedToken,
|
|
||||||
RefreshToken = ext.SessionData?.RefreshToken.Token,
|
|
||||||
SteamID = ext.SessionData?.SteamId.Steam64.Id,
|
|
||||||
SessionID = ext.SessionData?.SessionId
|
|
||||||
};
|
|
||||||
result.ServerTime = ext.ServerTime;
|
|
||||||
result.SerialNumber = ext.SerialNumber.ToString();
|
|
||||||
result.Uri = ext.Uri;
|
|
||||||
result.TokenGid = ext.TokenGid;
|
|
||||||
result.Secret1 = ext.Secret1;
|
|
||||||
result.SteamId = ext.SteamId.Steam64.Id;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
using var w = new StringWriter();
|
|
||||||
using var write = new JsonTextWriter(w);
|
|
||||||
write.Formatting = formatting;
|
|
||||||
var j = JObject.FromObject(result);
|
|
||||||
if (additionalProperties != null)
|
|
||||||
{
|
|
||||||
foreach (var (name, value) in additionalProperties)
|
|
||||||
{
|
|
||||||
JToken? jToken = null;
|
|
||||||
if (value != null)
|
|
||||||
{
|
|
||||||
jToken = JToken.FromObject(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
j.Add(name, jToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sign)
|
|
||||||
{
|
|
||||||
credits ??= MafileCredits.Instance;
|
|
||||||
var obj = JObject.FromObject(credits);
|
|
||||||
j.Add(CREDITS_PROPERTY_NAME, obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
j.WriteTo(write);
|
|
||||||
return w.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace SteamLib.Web.Converters;
|
|
||||||
|
|
||||||
public class StringToLongIfNeededConverter : JsonConverter<string>
|
|
||||||
{
|
|
||||||
public override void WriteJson(JsonWriter writer, string? value, JsonSerializer serializer)
|
|
||||||
{
|
|
||||||
writer.WriteValue(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ReadJson(JsonReader reader, Type objectType, string? existingValue, bool hasExistingValue,
|
|
||||||
JsonSerializer serializer)
|
|
||||||
{
|
|
||||||
return reader.Value?.ToString() ?? "null";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
|
|
||||||
#pragma warning disable CS8618
|
|
||||||
|
|
||||||
namespace SteamLib.Web.Models.GlobalMarketInfo;
|
|
||||||
|
|
||||||
[PublicAPI]
|
|
||||||
public class GlobalInfoModel
|
|
||||||
{
|
|
||||||
[MemberNotNullWhen(true, nameof(SteamId))]
|
|
||||||
public bool IsLoggedIn { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// <see langword="null" /> if <see cref="IsLoggedIn" /> is <see langword="null" /> or wallet doesn't exist
|
|
||||||
/// </summary>
|
|
||||||
public MarketWalletSchema? WalletInfo { get; set; }
|
|
||||||
|
|
||||||
public bool RequiresBillingInfo { get; set; }
|
|
||||||
public bool HasBillingStates { get; set; }
|
|
||||||
public string CountryCode { get; set; }
|
|
||||||
public string Language { get; set; }
|
|
||||||
public long? SteamId { get; set; }
|
|
||||||
public string SessionId { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
using AchiesUtilities.Newtonsoft.JSON.Converters.Common;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using SteamLib.Core.Enums;
|
|
||||||
|
|
||||||
#pragma warning disable CS8618
|
|
||||||
|
|
||||||
namespace SteamLib.Web.Models.GlobalMarketInfo;
|
|
||||||
|
|
||||||
public class MarketWalletSchema
|
|
||||||
{
|
|
||||||
[JsonProperty("wallet_currency")] public Currency WalletCurrency { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("wallet_country")] public string WalletCountry { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("wallet_state")] public string WalletState { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("wallet_fee")]
|
|
||||||
[JsonConverter(typeof(IntToStringConverter))]
|
|
||||||
public int WalletFee { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("wallet_fee_minimum")]
|
|
||||||
[JsonConverter(typeof(IntToStringConverter))]
|
|
||||||
public int WalletFeeMinimum { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("wallet_fee_percent")]
|
|
||||||
[JsonConverter(typeof(DecimalToStringConverter))]
|
|
||||||
public decimal WalletFeePercent { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("wallet_publisher_fee_percent_default")]
|
|
||||||
[JsonConverter(typeof(DecimalToStringConverter))]
|
|
||||||
public decimal WalletPublisherFeePercentDefault { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("wallet_fee_base")]
|
|
||||||
[JsonConverter(typeof(IntToStringConverter))]
|
|
||||||
public int WalletFeeBase { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("wallet_balance")]
|
|
||||||
[JsonConverter(typeof(LongToStringConverter))]
|
|
||||||
public long WalletBalance { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("wallet_delayed_balance")]
|
|
||||||
[JsonConverter(typeof(LongToStringConverter))]
|
|
||||||
public long WalletDelayedBalance { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("wallet_max_balance")]
|
|
||||||
[JsonConverter(typeof(LongToStringConverter))]
|
|
||||||
public long WalletMaxBalance { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("wallet_trade_max_balance")]
|
|
||||||
[JsonConverter(typeof(LongToStringConverter))]
|
|
||||||
public long WalletTradeMaxBalance { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("success")] public int Success { get; set; }
|
|
||||||
|
|
||||||
[JsonProperty("rwgrsn")] public int Rwgrsn { get; set; }
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
using System.Text.RegularExpressions;
|
|
||||||
using HtmlAgilityPack;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using SteamLib.Web.Models.GlobalMarketInfo;
|
|
||||||
|
|
||||||
namespace SteamLib.Web.Scrappers.HTML;
|
|
||||||
|
|
||||||
public static class MarketGlobalInfoScrapper
|
|
||||||
{
|
|
||||||
private static readonly Regex LoggedInRegex = new("var g_bLoggedIn = (.+);", RegexOptions.Compiled);
|
|
||||||
|
|
||||||
private static readonly Regex ReqBillingInfoRegex =
|
|
||||||
new("var g_bRequiresBillingInfo = (.+);", RegexOptions.Compiled);
|
|
||||||
|
|
||||||
private static readonly Regex CountryCodeRegex = new("var g_strCountryCode = \"(.+)\";", RegexOptions.Compiled);
|
|
||||||
private static readonly Regex HasBillingStates = new("var g_bHasBillingStates = (.+);", RegexOptions.Compiled);
|
|
||||||
private static readonly Regex LanguageRegex = new("var g_strLanguage = \"(.+)\";", RegexOptions.Compiled);
|
|
||||||
private static readonly Regex WalletInfoRegex = new("var g_rgWalletInfo = (.+);", RegexOptions.Compiled);
|
|
||||||
|
|
||||||
private static readonly Regex SessionIdRegex = new("g_sessionID = \"(.+)\";", RegexOptions.Compiled);
|
|
||||||
private static readonly Regex SteamIdRegex = new("g_steamID = \"(.+)\";", RegexOptions.Compiled);
|
|
||||||
|
|
||||||
public static GlobalInfoModel Scrap(string html)
|
|
||||||
{
|
|
||||||
var document = new HtmlDocument();
|
|
||||||
document.LoadHtml(html);
|
|
||||||
|
|
||||||
var scriptNodes = document.DocumentNode.SelectNodes("//*[@id=\"responsive_page_template_content\"]/script");
|
|
||||||
var index = scriptNodes.Count > 2
|
|
||||||
? 1
|
|
||||||
: 0; //If account is limited or market unavailable elements will be displaced
|
|
||||||
|
|
||||||
var scriptNode = scriptNodes[index];
|
|
||||||
var script = scriptNode.InnerText!;
|
|
||||||
|
|
||||||
var logged = GetBool(script, LoggedInRegex);
|
|
||||||
var hasBillingStates = GetBool(script, HasBillingStates);
|
|
||||||
var reqBillingInfo = GetBool(script, ReqBillingInfoRegex);
|
|
||||||
|
|
||||||
var country = CountryCodeRegex.Match(html).Groups[1].Value;
|
|
||||||
var language = LanguageRegex.Match(html).Groups[1].Value;
|
|
||||||
|
|
||||||
|
|
||||||
var walletInfoStr = WalletInfoRegex.Match(html).Groups[1].Value;
|
|
||||||
MarketWalletSchema? wallet = null;
|
|
||||||
|
|
||||||
var sessionScriptNode =
|
|
||||||
document.DocumentNode.SelectSingleNode("//div[@class='responsive_page_content']/script");
|
|
||||||
var sessionScript = sessionScriptNode.InnerText!;
|
|
||||||
|
|
||||||
var sessionId = SessionIdRegex.Match(sessionScript).Groups[1].Value;
|
|
||||||
long? steamId = null;
|
|
||||||
|
|
||||||
|
|
||||||
if (logged)
|
|
||||||
{
|
|
||||||
wallet = JsonConvert.DeserializeObject<MarketWalletSchema>(walletInfoStr);
|
|
||||||
var steamIdStr = SteamIdRegex.Match(sessionScript).Groups[1].Value;
|
|
||||||
steamId = long.Parse(new string(steamIdStr.Where(char.IsDigit).ToArray()));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return new GlobalInfoModel
|
|
||||||
{
|
|
||||||
CountryCode = country,
|
|
||||||
HasBillingStates = hasBillingStates,
|
|
||||||
IsLoggedIn = logged,
|
|
||||||
Language = language,
|
|
||||||
RequiresBillingInfo = reqBillingInfo,
|
|
||||||
WalletInfo = wallet,
|
|
||||||
SessionId = sessionId,
|
|
||||||
SteamId = steamId
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool GetBool(string script, Regex regex)
|
|
||||||
{
|
|
||||||
var value = regex.Match(script).Groups[1].Value;
|
|
||||||
return bool.Parse(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
using SteamLib.Core;
|
|
||||||
using SteamLib.Exceptions.General;
|
|
||||||
using SteamLib.Web.Models.GlobalMarketInfo;
|
|
||||||
using SteamLib.Web.Scrappers.HTML;
|
|
||||||
using System.Threading;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using HtmlAgilityPack;
|
|
||||||
using SteamLib.Core.Models;
|
|
||||||
|
|
||||||
namespace SteamLib.Web;
|
|
||||||
|
|
||||||
public static class SteamWebApi
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Login is not required
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="client"></param>
|
|
||||||
/// <param name="cancellationToken"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
public static async Task<GlobalInfoModel> GetMarketGlobalInfo(HttpClient client,
|
|
||||||
CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
var resp = await client.GetStringAsync(SteamConstants.STEAM_MARKET, cancellationToken);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
return MarketGlobalInfoScrapper.Scrap(resp);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
throw new UnsupportedResponseException(resp, ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[RegexPattern]
|
|
||||||
[SuppressMessage("ReSharper", "UseRawString")]
|
|
||||||
[SuppressMessage("ReSharper", "StringLiteralTypo")]
|
|
||||||
private static readonly string _regexTip =
|
|
||||||
@"g_sessionID = ""(?<g_sessionID>.*)"";"
|
|
||||||
+ @"\s*g_steamID = (?<g_steamID>.*);";
|
|
||||||
|
|
||||||
private static readonly Regex Regex = new(_regexTip, RegexOptions.Compiled);
|
|
||||||
private const string XPATH = "//div[@class='responsive_page_content']/script";
|
|
||||||
|
|
||||||
public static async Task<string> GetLoginSessionId(HttpClient client, CancellationToken cancellationToken = default)
|
|
||||||
{
|
|
||||||
var resp = await client.GetStringAsync("https://steamcommunity.com/login/home", cancellationToken);
|
|
||||||
|
|
||||||
var document = new HtmlDocument();
|
|
||||||
document.LoadHtml(resp);
|
|
||||||
var scriptNode = document.DocumentNode.SelectSingleNode(XPATH) ?? throw new NullReferenceException("Script Node was null");
|
|
||||||
var script = scriptNode.InnerText;
|
|
||||||
var match = Regex.Match(script);
|
|
||||||
if (!match.Success)
|
|
||||||
throw new UnsupportedResponseException(script, "Page contains script but regex was unsuccessful");
|
|
||||||
return match.Groups["g_sessionID"].Value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Changelog</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||||
|
background-color: #eeeeee;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.changelog-container {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 25px;
|
||||||
|
margin: 25px auto;
|
||||||
|
max-width: 800px;
|
||||||
|
box-shadow: 0 5px 15px rgba(0,0,0,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.change {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 0;
|
||||||
|
border-left: 4px solid #a50ec7;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.version {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 1.5em;
|
||||||
|
color: #a50ec7;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date {
|
||||||
|
font-style: italic;
|
||||||
|
color: #888;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
margin-left: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-size: 1em;
|
||||||
|
padding: 0 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description ul {
|
||||||
|
list-style: inside square;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 600px) {
|
||||||
|
.changelog-container {
|
||||||
|
width: 90%;
|
||||||
|
margin: 25px auto;
|
||||||
|
padding: 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<div class="changelog-container">
|
||||||
|
<!-- Changelog entry -->
|
||||||
|
<div class="change">
|
||||||
|
<div class="version">Version 1.7.1</div>
|
||||||
|
<div class="date">09.07.2025</div>
|
||||||
|
<div class="description">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b>NEWS:</b> Official Telegram group now available! Join us at
|
||||||
|
<b>
|
||||||
|
<a href="https://t.me/nebulaauth">t.me/nebulaauth</a>
|
||||||
|
</b>
|
||||||
|
</li>
|
||||||
|
<li><b>UPDATE:</b> Added support for a new confirmation type — Purchase.</li>
|
||||||
|
<li><b>UPDATE:</b> Merged Global 1.7.0 updates into the release version (<a href="https://t.me/nebulaauth/32">patch notes</a>).</li>
|
||||||
|
<li><b>FIX:</b> Fixed a crash occurring during confirmation loading.</li>
|
||||||
|
<li><b>ENHANCEMENT:</b> Improved localization and system messages.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<item>
|
||||||
|
<version>1.5.6.0</version>
|
||||||
|
<url>https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/releases/download/1.5.6/NebulaAuth.1.5.6.zip</url>
|
||||||
|
<changelog>https://achiez.github.io/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/changelog/1.5.6.html</changelog>
|
||||||
|
<mandatory>false</mandatory>
|
||||||
|
</item>
|
||||||
+1
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net9.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -4,13 +4,11 @@
|
|||||||
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
xmlns:converters="clr-namespace:NebulaAuth.Converters"
|
xmlns:converters="clr-namespace:NebulaAuth.Converters"
|
||||||
xmlns:system="clr-namespace:System;assembly=System.Runtime"
|
xmlns:system="clr-namespace:System;assembly=System.Runtime"
|
||||||
xmlns:background="clr-namespace:NebulaAuth.Converters.Background"
|
xmlns:background="clr-namespace:NebulaAuth.Converters.Background">
|
||||||
StartupUri="MainWindow.xaml">
|
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
<SolidColorBrush x:Key="MaterialDesignPaper">#1E2025</SolidColorBrush>
|
|
||||||
<FontFamily x:Key="Roboto">pack://application:,,,/Fonts/Roboto/#Roboto</FontFamily>
|
|
||||||
<FontFamily x:Key="RobotoSymbols">pack://application:,,,/Fonts/Roboto/#Roboto Symbols</FontFamily>
|
|
||||||
<converters:CoefficientConverter x:Key="CoefficientConverter" />
|
<converters:CoefficientConverter x:Key="CoefficientConverter" />
|
||||||
<converters:ReverseBooleanConverter x:Key="ReverseBooleanConverter" />
|
<converters:ReverseBooleanConverter x:Key="ReverseBooleanConverter" />
|
||||||
<converters:ProxyTextConverter x:Key="ProxyTextConverter" />
|
<converters:ProxyTextConverter x:Key="ProxyTextConverter" />
|
||||||
@@ -19,32 +17,50 @@
|
|||||||
<converters:ColorToBrushConverter x:Key="ColorToBrushConverter" />
|
<converters:ColorToBrushConverter x:Key="ColorToBrushConverter" />
|
||||||
<converters:AnyMafilesToVisibilityConverter x:Key="AnyMafilesToVisibilityConverter" />
|
<converters:AnyMafilesToVisibilityConverter x:Key="AnyMafilesToVisibilityConverter" />
|
||||||
<converters:PortableMaClientStatusToColorConverter x:Key="PortableMaClientStatusToColorConverter" />
|
<converters:PortableMaClientStatusToColorConverter x:Key="PortableMaClientStatusToColorConverter" />
|
||||||
|
|
||||||
|
<converters:NullableToBooleanConverter x:Key="NullableToBooleanConverter" />
|
||||||
<!-- Background converters-->
|
<!-- Background converters-->
|
||||||
<background:BackgroundImageVisibleConverter x:Key="BackgroundImageVisibleConverter" />
|
<background:BackgroundImageVisibleConverter x:Key="BackgroundImageVisibleConverter" />
|
||||||
<background:BackgroundSourceConverter x:Key="BackgroundSourceConverter" />
|
<background:BackgroundSourceConverter x:Key="BackgroundSourceConverter" />
|
||||||
|
|
||||||
|
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
|
||||||
|
<materialDesign:NullableToVisibilityConverter x:Key="NullableToVisibilityConverter" />
|
||||||
|
|
||||||
|
|
||||||
<system:Boolean x:Key="True">True</system:Boolean>
|
<system:Boolean x:Key="True">True</system:Boolean>
|
||||||
<system:Boolean x:Key="False">False</system:Boolean>
|
<system:Boolean x:Key="False">False</system:Boolean>
|
||||||
|
|
||||||
|
|
||||||
<ResourceDictionary.MergedDictionaries>
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
|
||||||
|
<ResourceDictionary Source="Theme/Themes/DefaultTheme.xaml" />
|
||||||
<materialDesign:BundledTheme BaseTheme="Dark" PrimaryColor="LightGreen" SecondaryColor="Purple" />
|
|
||||||
<ResourceDictionary
|
<ResourceDictionary
|
||||||
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
|
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesign3.Defaults.xaml" />
|
||||||
|
<ResourceDictionary Source="Theme/Brushes.xaml" />
|
||||||
|
<!--Controls-->
|
||||||
|
|
||||||
<ResourceDictionary
|
<ResourceDictionary
|
||||||
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/materialdesigntheme.snackbar.xaml" />
|
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/materialdesigntheme.snackbar.xaml" />
|
||||||
<ResourceDictionary
|
|
||||||
Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/materialdesigntheme.Dark.xaml" />
|
|
||||||
<!-- Theme-->
|
|
||||||
<ResourceDictionary Source="Theme/Brushes.xaml" />
|
|
||||||
<ResourceDictionary Source="Theme/WindowStyle/WindowStyle.xaml" />
|
|
||||||
|
|
||||||
<!--Controls-->
|
|
||||||
<ResourceDictionary Source="View/ConfirmationTemplates.xaml" />
|
<ResourceDictionary Source="View/ConfirmationTemplates.xaml" />
|
||||||
|
<ResourceDictionary Source="View/LinkerStepTemplates.xaml" />
|
||||||
|
<ResourceDictionary Source="View/MafileMoverStepTemplates.xaml" />
|
||||||
|
|
||||||
|
<!--Theme-->
|
||||||
|
|
||||||
|
<ResourceDictionary Source="Theme/WindowStyle/WindowStyle.xaml" />
|
||||||
|
<ResourceDictionary Source="Theme/MaterialDesignThemes.Overrides.xaml" />
|
||||||
|
|
||||||
|
|
||||||
|
<ResourceDictionary>
|
||||||
|
<Style TargetType="ToolTip" BasedOn="{StaticResource {x:Type ToolTip}}">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource Base100Brush}" />
|
||||||
|
<Setter Property="Background" Value="{DynamicResource BaseContentBrush}" />
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
||||||
|
|
||||||
</ResourceDictionary.MergedDictionaries>
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
|
||||||
|
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|
||||||
</Application.Resources>
|
</Application.Resources>
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
|
using AchiesUtilities.Extensions;
|
||||||
|
using NebulaAuth.Core;
|
||||||
|
using NebulaAuth.Model;
|
||||||
|
using NebulaAuth.Model.Exceptions;
|
||||||
|
|
||||||
|
namespace NebulaAuth;
|
||||||
|
|
||||||
|
public partial class App
|
||||||
|
{
|
||||||
|
protected override async void OnStartup(StartupEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var splashScreen = new SplashScreen("Theme\\SplashScreen.png");
|
||||||
|
splashScreen.Show(false, true);
|
||||||
|
base.OnStartup(e);
|
||||||
|
LocManager.Init();
|
||||||
|
LocManager.SetApplicationLocalization(Settings.Instance.Language);
|
||||||
|
Shell.Initialize();
|
||||||
|
|
||||||
|
var files = 0;
|
||||||
|
if (Directory.Exists(Storage.MAFILE_F))
|
||||||
|
files = Directory.GetFiles(Storage.MafileFolder)
|
||||||
|
.Count(f => Path.GetExtension(f).EqualsIgnoreCase(".mafile"));
|
||||||
|
|
||||||
|
var threads = files > 0 ? files / 100 + 1 : 1;
|
||||||
|
await Storage.Initialize(threads);
|
||||||
|
var mainWindow = new MainWindow();
|
||||||
|
Current.MainWindow = mainWindow;
|
||||||
|
mainWindow.Show();
|
||||||
|
splashScreen.Close(TimeSpan.Zero);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var msg = ex.ToString();
|
||||||
|
if (ex is CantAlignTimeException)
|
||||||
|
{
|
||||||
|
msg = LocManager.Get("CantAlignTimeError");
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageBox.Show(msg, "Error", MessageBoxButton.OK, MessageBoxImage.Stop, MessageBoxResult.OK,
|
||||||
|
MessageBoxOptions.DefaultDesktopOnly);
|
||||||
|
|
||||||
|
Shell.Logger.Fatal(ex, "Application startup failed");
|
||||||
|
Current.Shutdown(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+4
-4
@@ -11,10 +11,10 @@ public class AnyMafilesToVisibilityConverter : IValueConverter
|
|||||||
|
|
||||||
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
{
|
{
|
||||||
if (_everAnyMafiles)
|
//if (_everAnyMafiles)
|
||||||
{
|
//{
|
||||||
return Visibility.Collapsed;
|
// return Visibility.Collapsed;
|
||||||
}
|
//}
|
||||||
|
|
||||||
if (value is 0)
|
if (value is 0)
|
||||||
{
|
{
|
||||||
+1
-1
@@ -18,7 +18,7 @@ public class BackgroundSourceConverter : IValueConverter
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return new BitmapImage(new Uri("pack://application:,,,/Theme/Background.jpg"));
|
return new BitmapImage(new Uri("pack://application:,,,/Theme/Background.png"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:system="clr-namespace:System;assembly=System.Runtime"
|
||||||
|
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
|
||||||
|
xmlns:converters="clr-namespace:NebulaAuth.Converters"
|
||||||
|
xmlns:background="clr-namespace:NebulaAuth.Converters.Background">
|
||||||
|
<converters:CoefficientConverter x:Key="CoefficientConverter" />
|
||||||
|
<converters:ReverseBooleanConverter x:Key="ReverseBooleanConverter" />
|
||||||
|
<converters:ProxyTextConverter x:Key="ProxyTextConverter" />
|
||||||
|
<converters:ProxyDataTextConverter x:Key="ProxyDataTextConverter" />
|
||||||
|
<converters:MultiCommandParameterConverter x:Key="MultiCommandParameterConverter" />
|
||||||
|
<converters:ColorToBrushConverter x:Key="ColorToBrushConverter" />
|
||||||
|
<converters:AnyMafilesToVisibilityConverter x:Key="AnyMafilesToVisibilityConverter" />
|
||||||
|
<converters:PortableMaClientStatusToColorConverter x:Key="PortableMaClientStatusToColorConverter" />
|
||||||
|
|
||||||
|
<converters:NullableToBooleanConverter x:Key="NullableToBooleanConverter" />
|
||||||
|
<!-- Background converters-->
|
||||||
|
<background:BackgroundImageVisibleConverter x:Key="BackgroundImageVisibleConverter" />
|
||||||
|
<background:BackgroundSourceConverter x:Key="BackgroundSourceConverter" />
|
||||||
|
|
||||||
|
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
|
||||||
|
<materialDesign:NullableToVisibilityConverter x:Key="NullableToVisibilityConverter" />
|
||||||
|
|
||||||
|
|
||||||
|
<system:Boolean x:Key="True">True</system:Boolean>
|
||||||
|
<system:Boolean x:Key="False">False</system:Boolean>
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace NebulaAuth.Converters;
|
||||||
|
|
||||||
|
public class NullableToBooleanConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
return value != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -14,6 +14,7 @@ public class PortableMaClientStatusToColorConverter : IValueConverter
|
|||||||
return new SolidColorBrush(Color.FromRgb(187, 224, 139));
|
return new SolidColorBrush(Color.FromRgb(187, 224, 139));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return new SolidColorBrush(Color.FromRgb(224, 139, 139));
|
return new SolidColorBrush(Color.FromRgb(224, 139, 139));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5,6 +5,8 @@ using NebulaAuth.Model;
|
|||||||
using NebulaAuth.Model.Entities;
|
using NebulaAuth.Model.Entities;
|
||||||
using NebulaAuth.View;
|
using NebulaAuth.View;
|
||||||
using NebulaAuth.View.Dialogs;
|
using NebulaAuth.View.Dialogs;
|
||||||
|
using NebulaAuth.ViewModel.Linker;
|
||||||
|
using NebulaAuth.ViewModel.MafileMover;
|
||||||
using NebulaAuth.ViewModel.Other;
|
using NebulaAuth.ViewModel.Other;
|
||||||
|
|
||||||
namespace NebulaAuth.Core;
|
namespace NebulaAuth.Core;
|
||||||
@@ -86,11 +88,37 @@ public static class DialogsController
|
|||||||
|
|
||||||
public static async Task ShowLinkerDialog()
|
public static async Task ShowLinkerDialog()
|
||||||
{
|
{
|
||||||
var vm = new LinkAccountVM();
|
LinkAccountVM? vm = null;
|
||||||
var view = new LinkerView
|
try
|
||||||
{
|
{
|
||||||
DataContext = vm
|
vm = new LinkAccountVM();
|
||||||
};
|
var view = new LinkerView
|
||||||
await DialogHost.Show(view);
|
{
|
||||||
|
DataContext = vm
|
||||||
|
};
|
||||||
|
await DialogHost.Show(view);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
vm?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task ShowMafileMoverDialog()
|
||||||
|
{
|
||||||
|
MafileMoverVM? vm = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
vm = new MafileMoverVM();
|
||||||
|
var view = new MafileMoverView
|
||||||
|
{
|
||||||
|
DataContext = vm
|
||||||
|
};
|
||||||
|
await DialogHost.Show(view);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
vm?.Dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@ public static class LocManager
|
|||||||
|
|
||||||
public static void Init()
|
public static void Init()
|
||||||
{
|
{
|
||||||
Loc.LogOutMissingTranslations = true;
|
Loc.Instance.LogOutMissingTranslations = true;
|
||||||
|
|
||||||
LocalizationLoader.Instance.FileLanguageLoaders.Add(new JsonFileLoader());
|
LocalizationLoader.Instance.FileLanguageLoaders.Add(new JsonFileLoader());
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ public class SnackbarController
|
|||||||
|
|
||||||
private static TimeSpan GetSnackbarTime(string str)
|
private static TimeSpan GetSnackbarTime(string str)
|
||||||
{
|
{
|
||||||
var duration = str.Length / 0.03;
|
var duration = str.Length / 0.022;
|
||||||
if (duration < MIN_SNACKBAR_TIME)
|
if (duration < MIN_SNACKBAR_TIME)
|
||||||
{
|
{
|
||||||
duration = MIN_SNACKBAR_TIME;
|
duration = MIN_SNACKBAR_TIME;
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Drawing.Drawing2D;
|
using System.Drawing.Drawing2D;
|
||||||
|
using System.Linq;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Interop;
|
using System.Windows.Interop;
|
||||||
using System.Windows.Media;
|
|
||||||
using System.Windows.Media.Imaging;
|
using System.Windows.Media.Imaging;
|
||||||
using System.Windows.Shell;
|
using System.Windows.Shell;
|
||||||
using NebulaAuth.Model;
|
using NebulaAuth.Model;
|
||||||
@@ -14,7 +14,6 @@ namespace NebulaAuth.Core;
|
|||||||
|
|
||||||
public static class ThemeManager
|
public static class ThemeManager
|
||||||
{
|
{
|
||||||
public static System.Windows.Media.Color DefaultBackgroundColor = System.Windows.Media.Color.FromRgb(30, 32, 37);
|
|
||||||
private static readonly Window MainWindow = Application.Current.MainWindow!;
|
private static readonly Window MainWindow = Application.Current.MainWindow!;
|
||||||
|
|
||||||
static ThemeManager()
|
static ThemeManager()
|
||||||
@@ -24,11 +23,7 @@ public static class ThemeManager
|
|||||||
|
|
||||||
private static void SettingsOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
private static void SettingsOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
if (e.PropertyName == nameof(Settings.BackgroundColor))
|
if (e.PropertyName == nameof(Settings.IconColor))
|
||||||
{
|
|
||||||
UpdateBackground();
|
|
||||||
}
|
|
||||||
else if (e.PropertyName == nameof(Settings.IconColor))
|
|
||||||
{
|
{
|
||||||
UpdateIcon();
|
UpdateIcon();
|
||||||
}
|
}
|
||||||
@@ -58,15 +53,40 @@ public static class ThemeManager
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void UpdateBackground()
|
|
||||||
|
public static void ApplyTheme(string themeName)
|
||||||
{
|
{
|
||||||
var color = Settings.Instance.BackgroundColor ?? DefaultBackgroundColor;
|
var colorDict = new ResourceDictionary
|
||||||
Application.Current.Resources["WindowBackground"] = new SolidColorBrush(color);
|
{
|
||||||
|
Source = new Uri($"Theme/Themes/{themeName}.xaml", UriKind.Relative)
|
||||||
|
};
|
||||||
|
|
||||||
|
var brushDict = new ResourceDictionary
|
||||||
|
{
|
||||||
|
Source = new Uri("Theme/Brushes.xaml", UriKind.Relative)
|
||||||
|
};
|
||||||
|
|
||||||
|
var mergedDictionaries = Application.Current.Resources.MergedDictionaries;
|
||||||
|
|
||||||
|
|
||||||
|
var toRemove = mergedDictionaries
|
||||||
|
.Where(d => d.Source?.OriginalString.Contains("Theme/Themes/") == true ||
|
||||||
|
d.Source?.OriginalString.EndsWith("Brushes.xaml") == true)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var dict in toRemove)
|
||||||
|
{
|
||||||
|
mergedDictionaries.Remove(dict);
|
||||||
|
}
|
||||||
|
|
||||||
|
mergedDictionaries.Insert(0, colorDict);
|
||||||
|
mergedDictionaries.Insert(0, brushDict);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void InitializeTheme()
|
public static void InitializeTheme()
|
||||||
{
|
{
|
||||||
UpdateIcon();
|
UpdateIcon();
|
||||||
UpdateBackground();
|
ApplyTheme(Settings.Instance.GetTheme());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@ namespace NebulaAuth.Core;
|
|||||||
public static class UpdateManager
|
public static class UpdateManager
|
||||||
{
|
{
|
||||||
private const string UPDATE_URL =
|
private const string UPDATE_URL =
|
||||||
"https://raw.githubusercontent.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/master/NebulaAuth/update.xml";
|
"https://raw.githubusercontent.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies/master/misc/update.xml";
|
||||||
|
|
||||||
public static void CheckForUpdates()
|
public static void CheckForUpdates()
|
||||||
{
|
{
|
||||||
@@ -9,12 +9,14 @@
|
|||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
|
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
|
||||||
xmlns:viewModel="clr-namespace:NebulaAuth.ViewModel"
|
xmlns:viewModel="clr-namespace:NebulaAuth.ViewModel"
|
||||||
|
xmlns:controls="clr-namespace:NebulaAuth.Theme.Controls"
|
||||||
WindowStartupLocation="CenterScreen"
|
WindowStartupLocation="CenterScreen"
|
||||||
MinHeight="500" MinWidth="500" DefaultFontSize="18" ScaleCoefficient="0.4"
|
MinHeight="500" MinWidth="500" DefaultFontSize="18" ScaleCoefficient="0.4"
|
||||||
Title="NebulaAuth" Height="800" Width="730" Style="{StaticResource MainWindow}"
|
Title="NebulaAuth" Height="800" Width="730"
|
||||||
RenderOptions.BitmapScalingMode="HighQuality" Foreground="#FFF5F5F5"
|
Style="{StaticResource MainWindow}"
|
||||||
|
RenderOptions.BitmapScalingMode="HighQuality"
|
||||||
FontFamily="{md:MaterialDesignFont}"
|
FontFamily="{md:MaterialDesignFont}"
|
||||||
TextElement.Foreground="{DynamicResource MaterialDesignBody}"
|
TextElement.Foreground="{DynamicResource BaseContentBrush}"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
d:DataContext="{d:DesignInstance viewModel:MainVM}">
|
d:DataContext="{d:DesignInstance viewModel:MainVM}">
|
||||||
<b:Interaction.Behaviors>
|
<b:Interaction.Behaviors>
|
||||||
@@ -28,20 +30,31 @@
|
|||||||
</Window.InputBindings>
|
</Window.InputBindings>
|
||||||
<Border Name="DragNDropBorder" Panel.ZIndex="3" Opacity="1" AllowDrop="True"
|
<Border Name="DragNDropBorder" Panel.ZIndex="3" Opacity="1" AllowDrop="True"
|
||||||
DragDrop.DragEnter="Rectangle_DragEnter" DragLeave="Rectangle_DragLeave" Drop="Rectangle_Drop">
|
DragDrop.DragEnter="Rectangle_DragEnter" DragLeave="Rectangle_DragLeave" Drop="Rectangle_Drop">
|
||||||
<md:DialogHost DialogOpened="DialogHost_DialogOpened" DialogClosed="DialogHost_DialogClosed">
|
<md:DialogHost DialogOpened="DialogHost_DialogOpened"
|
||||||
|
DialogClosed="DialogHost_DialogClosed"
|
||||||
|
ApplyBlurBackground="{Binding Settings.ApplyBlurBackground}"
|
||||||
|
BlurRadius="12"
|
||||||
|
x:Name="DialogHostInstance">
|
||||||
|
|
||||||
<Grid ZIndex="0">
|
<Grid ZIndex="0">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition />
|
<RowDefinition />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<Image Grid.RowSpan="2" Opacity="0.6" Stretch="UniformToFill"
|
<Image Grid.RowSpan="2" Opacity="{Binding Settings.BackgroundOpacity}" Stretch="UniformToFill"
|
||||||
RenderOptions.BitmapScalingMode="LowQuality" HorizontalAlignment="Center"
|
RenderOptions.BitmapScalingMode="LowQuality" HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Visibility="{Binding Settings.BackgroundMode, Converter={StaticResource BackgroundImageVisibleConverter}}"
|
Visibility="{Binding Settings.BackgroundMode, Converter={StaticResource BackgroundImageVisibleConverter}}"
|
||||||
Source="{Binding Settings.BackgroundMode, Mode=OneWay, Converter={StaticResource BackgroundSourceConverter}}" />
|
Source="{Binding Settings.BackgroundMode, Mode=OneWay, Converter={StaticResource BackgroundSourceConverter}}">
|
||||||
<Rectangle Grid.Row="0" Grid.RowSpan="2" Opacity="0.5" Fill="#FF1F1D1D"
|
<Image.Effect>
|
||||||
Visibility="{Binding Settings.BackgroundMode, Converter={StaticResource BackgroundImageVisibleConverter}}" />
|
<BlurEffect Radius="{Binding Settings.BackgroundBlur}" />
|
||||||
|
</Image.Effect>
|
||||||
|
</Image>
|
||||||
|
<Rectangle Grid.Row="0" Grid.RowSpan="2" Fill="Black" Stretch="UniformToFill"
|
||||||
|
Opacity="{Binding Settings.BackgroundGamma}"
|
||||||
|
IsHitTestVisible="False" />
|
||||||
|
|
||||||
<Rectangle Name="DragNDropOverlay" Grid.Row="0" Grid.RowSpan="3" Panel.ZIndex="1" Fill="#242123"
|
<Rectangle Name="DragNDropOverlay" Grid.Row="0" Grid.RowSpan="3" Panel.ZIndex="1" Fill="#242123"
|
||||||
Opacity="0.9" Visibility="Hidden" />
|
Opacity="0.9" Visibility="Hidden" />
|
||||||
<StackPanel Name="DragNDropPanel" Grid.Row="0" ZIndex="2" w:FontScaleWindow.Scale="1"
|
<StackPanel Name="DragNDropPanel" Grid.Row="0" ZIndex="2" w:FontScaleWindow.Scale="1"
|
||||||
@@ -51,13 +64,10 @@
|
|||||||
<md:PackIcon Foreground="#9a65b8" HorizontalAlignment="Center" Width="36" Height="36"
|
<md:PackIcon Foreground="#9a65b8" HorizontalAlignment="Center" Width="36" Height="36"
|
||||||
Kind="FileReplaceOutline" />
|
Kind="FileReplaceOutline" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<ToolBarTray IsLocked="True" Grid.Row="0">
|
<ToolBarTray>
|
||||||
<ToolBarTray.Background>
|
<ToolBar ClipToBounds="True" HorizontalContentAlignment="Stretch"
|
||||||
<SolidColorBrush Opacity="0.6" Color="#FF1A1C25" />
|
Style="{StaticResource MaterialDesignToolBar}" FontSize="16">
|
||||||
</ToolBarTray.Background>
|
<Menu FontSize="16">
|
||||||
<ToolBar Background="#00FFFFFF" ClipToBounds="False" Style="{StaticResource MaterialDesignToolBar}"
|
|
||||||
w:FontScaleWindow.Scale="0.68" w:FontScaleWindow.ResizeFont="True">
|
|
||||||
<Menu w:FontScaleWindow.Scale="0.68" w:FontScaleWindow.ResizeFont="True">
|
|
||||||
<MenuItem Header="{Tr MainWindow.Menu.File.Caption}">
|
<MenuItem Header="{Tr MainWindow.Menu.File.Caption}">
|
||||||
<MenuItem Header="{Tr MainWindow.Menu.File.Import}"
|
<MenuItem Header="{Tr MainWindow.Menu.File.Import}"
|
||||||
Command="{Binding AddMafileCommand}" />
|
Command="{Binding AddMafileCommand}" />
|
||||||
@@ -75,8 +85,11 @@
|
|||||||
<MenuItem Header="{Tr MainWindow.Menu.Account.Caption}">
|
<MenuItem Header="{Tr MainWindow.Menu.Account.Caption}">
|
||||||
<MenuItem Header="{Tr MainWindow.Menu.Account.Link}"
|
<MenuItem Header="{Tr MainWindow.Menu.Account.Link}"
|
||||||
Command="{Binding LinkAccountCommand}" />
|
Command="{Binding LinkAccountCommand}" />
|
||||||
|
<MenuItem Header="{Tr MainWindow.Menu.Account.MoveSteamGuard}"
|
||||||
|
Command="{Binding MoveAccountCommand}" />
|
||||||
<MenuItem Header="{Tr MainWindow.Menu.Account.Unlink}"
|
<MenuItem Header="{Tr MainWindow.Menu.Account.Unlink}"
|
||||||
Command="{Binding RemoveAuthenticatorCommand}" />
|
Command="{Binding RemoveAuthenticatorCommand}" />
|
||||||
|
|
||||||
<MenuItem IsEnabled="False" Header="⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" Height="15" />
|
<MenuItem IsEnabled="False" Header="⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" Height="15" />
|
||||||
<MenuItem IsEnabled="{Binding IsMafileSelected}"
|
<MenuItem IsEnabled="{Binding IsMafileSelected}"
|
||||||
Header="{Tr MainWindow.Menu.Account.RefreshSession}"
|
Header="{Tr MainWindow.Menu.Account.RefreshSession}"
|
||||||
@@ -91,7 +104,8 @@
|
|||||||
md:HintAssist.Hint="{Tr MainWindow.AppBar.GroupsHint}"
|
md:HintAssist.Hint="{Tr MainWindow.AppBar.GroupsHint}"
|
||||||
MinWidth="100" Margin="8,0,8,0" VerticalAlignment="Center" IsEditable="True"
|
MinWidth="100" Margin="8,0,8,0" VerticalAlignment="Center" IsEditable="True"
|
||||||
ItemsSource="{Binding Groups}"
|
ItemsSource="{Binding Groups}"
|
||||||
SelectedValue="{Binding SelectedGroup}">
|
SelectedValue="{Binding SelectedGroup}"
|
||||||
|
md:TextFieldAssist.HasClearButton="True">
|
||||||
|
|
||||||
<FrameworkElement.Resources>
|
<FrameworkElement.Resources>
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
@@ -114,6 +128,7 @@
|
|||||||
md:HintAssist.Hint="{Tr MainWindow.AppBar.Proxy.ProxyHint}"
|
md:HintAssist.Hint="{Tr MainWindow.AppBar.Proxy.ProxyHint}"
|
||||||
md:ComboBoxAssist.ShowSelectedItem="False"
|
md:ComboBoxAssist.ShowSelectedItem="False"
|
||||||
SelectedValue="{Binding SelectedProxy}"
|
SelectedValue="{Binding SelectedProxy}"
|
||||||
|
|
||||||
ItemsSource="{Binding Proxies}">
|
ItemsSource="{Binding Proxies}">
|
||||||
<ItemsControl.ItemTemplate>
|
<ItemsControl.ItemTemplate>
|
||||||
<DataTemplate DataType="{x:Type entities:MaProxy}">
|
<DataTemplate DataType="{x:Type entities:MaProxy}">
|
||||||
@@ -132,7 +147,8 @@
|
|||||||
</UIElement.InputBindings>
|
</UIElement.InputBindings>
|
||||||
</ComboBox>
|
</ComboBox>
|
||||||
<md:PackIcon Kind="StopAlert" VerticalAlignment="Center" HorizontalAlignment="Center"
|
<md:PackIcon Kind="StopAlert" VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||||
Foreground="#FFFF0000" Margin="3" ToolTipService.InitialShowDelay="300">
|
Foreground="{DynamicResource ErrorBrush}" Margin="3"
|
||||||
|
ToolTipService.InitialShowDelay="300">
|
||||||
<md:PackIcon.ToolTip>
|
<md:PackIcon.ToolTip>
|
||||||
<TextBlock>
|
<TextBlock>
|
||||||
<Run
|
<Run
|
||||||
@@ -153,12 +169,11 @@
|
|||||||
</UIElement.Visibility>
|
</UIElement.Visibility>
|
||||||
</md:PackIcon>
|
</md:PackIcon>
|
||||||
<md:PackIcon Kind="StopAlert" VerticalAlignment="Center" HorizontalAlignment="Center"
|
<md:PackIcon Kind="StopAlert" VerticalAlignment="Center" HorizontalAlignment="Center"
|
||||||
Foreground="#FFFFA500" Margin="3"
|
Foreground="{DynamicResource WarningBrush}" Margin="3"
|
||||||
ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyAlert.DefaultInUse}"
|
ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyAlert.DefaultInUse}"
|
||||||
ToolTipService.InitialShowDelay="300"
|
ToolTipService.InitialShowDelay="300"
|
||||||
Visibility="{Binding IsDefaultProxy, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
Visibility="{Binding IsDefaultProxy, Converter={StaticResource BooleanToVisibilityConverter}}" />
|
||||||
<ToggleButton ToolTip="{Tr MainWindow.AppBar.MarketTimerHint}"
|
<ToggleButton ToolTip="{Tr MainWindow.AppBar.MarketTimerHint}"
|
||||||
Foreground="{DynamicResource PrimaryHueDarkForegroundBrush}"
|
|
||||||
IsEnabled="{Binding IsMafileSelected}" Margin="2"
|
IsEnabled="{Binding IsMafileSelected}" Margin="2"
|
||||||
Style="{StaticResource MaterialDesignFlatToggleButton}"
|
Style="{StaticResource MaterialDesignFlatToggleButton}"
|
||||||
IsChecked="{Binding MarketTimerEnabled}">
|
IsChecked="{Binding MarketTimerEnabled}">
|
||||||
@@ -175,7 +190,6 @@
|
|||||||
</FrameworkElement.ContextMenu>
|
</FrameworkElement.ContextMenu>
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
<ToggleButton ToolTip="{Tr MainWindow.AppBar.TradeTimerHint}"
|
<ToggleButton ToolTip="{Tr MainWindow.AppBar.TradeTimerHint}"
|
||||||
Foreground="{DynamicResource PrimaryHueDarkForegroundBrush}"
|
|
||||||
IsEnabled="{Binding IsMafileSelected}" Margin="2"
|
IsEnabled="{Binding IsMafileSelected}" Margin="2"
|
||||||
Style="{StaticResource MaterialDesignFlatToggleButton}"
|
Style="{StaticResource MaterialDesignFlatToggleButton}"
|
||||||
IsChecked="{Binding TradeTimerEnabled}">
|
IsChecked="{Binding TradeTimerEnabled}">
|
||||||
@@ -197,7 +211,7 @@
|
|||||||
PreviewTextInput="UIElement_OnPreviewTextInput" />
|
PreviewTextInput="UIElement_OnPreviewTextInput" />
|
||||||
<ToggleButton ToolTip="{Tr MainWindow.AppBar.ShowAutoConfirmAccountsHint}"
|
<ToggleButton ToolTip="{Tr MainWindow.AppBar.ShowAutoConfirmAccountsHint}"
|
||||||
HorizontalAlignment="Right" IsChecked="{Binding MaacDisplay}"
|
HorizontalAlignment="Right" IsChecked="{Binding MaacDisplay}"
|
||||||
Foreground="WhiteSmoke" VerticalAlignment="Center"
|
Foreground="{DynamicResource BaseContentBrush}" VerticalAlignment="Center"
|
||||||
Style="{StaticResource MaterialDesignFlatPrimaryToggleButton}" Margin="4">
|
Style="{StaticResource MaterialDesignFlatPrimaryToggleButton}" Margin="4">
|
||||||
<md:PackIcon Kind="Accounts" />
|
<md:PackIcon Kind="Accounts" />
|
||||||
</ToggleButton>
|
</ToggleButton>
|
||||||
@@ -214,117 +228,138 @@
|
|||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<ListBox w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True" Grid.Row="0"
|
|
||||||
Margin="10,15,0,0" ItemsSource="{Binding MaFiles}"
|
|
||||||
SelectedValue="{Binding SelectedMafile}">
|
|
||||||
<ListBox.ItemContainerStyle>
|
|
||||||
<Style TargetType="ListBoxItem" BasedOn="{StaticResource MaterialDesignListBoxItem}">
|
|
||||||
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
|
||||||
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
|
||||||
</Style>
|
|
||||||
</ListBox.ItemContainerStyle>
|
|
||||||
<ListBox.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<Grid>
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<TextBlock HorizontalAlignment="Stretch" Text="{Binding AccountName}">
|
|
||||||
<TextBlock.Foreground>
|
|
||||||
<Binding Mode="OneWay" Path="LinkedClient.IsError"
|
|
||||||
Converter="{StaticResource PortableMaClientStatusToColorConverter}">
|
|
||||||
<Binding.FallbackValue>
|
|
||||||
<SolidColorBrush Color="WhiteSmoke" />
|
|
||||||
</Binding.FallbackValue>
|
|
||||||
</Binding>
|
|
||||||
</TextBlock.Foreground>
|
|
||||||
</TextBlock>
|
|
||||||
<StackPanel
|
|
||||||
Visibility="{Binding LinkedClient, Converter={StaticResource NullableToVisibilityConverter}}"
|
|
||||||
Grid.Column="1" Orientation="Horizontal">
|
|
||||||
<md:PackIcon Kind="ShoppingCart"
|
|
||||||
Visibility="{Binding LinkedClient.AutoConfirmMarket, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Collapsed}" />
|
|
||||||
<md:PackIcon Kind="AccountArrowRight"
|
|
||||||
Visibility="{Binding LinkedClient.AutoConfirmTrades, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Collapsed}" />
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
|
||||||
</DataTemplate>
|
|
||||||
</ListBox.ItemTemplate>
|
|
||||||
<ListBox.InputBindings>
|
|
||||||
<KeyBinding Key="C" Modifiers="Control"
|
|
||||||
Command="{Binding DataContext.CopyLoginCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
|
||||||
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}" />
|
|
||||||
<KeyBinding Key="X" Modifiers="Control"
|
|
||||||
Command="{Binding DataContext.CopyMafileCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
|
||||||
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}" />
|
|
||||||
</ListBox.InputBindings>
|
|
||||||
<FrameworkElement.ContextMenu>
|
|
||||||
<ContextMenu>
|
|
||||||
<MenuItem InputGestureText="Ctrl+C"
|
|
||||||
Header="{Tr MainWindow.ContextMenus.Mafile.CopyLogin}"
|
|
||||||
Command="{Binding CopyLoginCommand}"
|
|
||||||
CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
|
||||||
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.CopySteamId}"
|
|
||||||
Command="{Binding CopySteamIdCommand}"
|
|
||||||
CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
|
||||||
<MenuItem InputGestureText="Ctrl+X"
|
|
||||||
Header="{Tr MainWindow.ContextMenus.Mafile.CopyMafile}"
|
|
||||||
Command="{Binding CopyMafileCommand}"
|
|
||||||
CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
|
||||||
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.AddToGroup}"
|
|
||||||
ItemsSource="{Binding Groups}">
|
|
||||||
<ItemsControl.ItemContainerStyle>
|
|
||||||
<Style BasedOn="{StaticResource MaterialDesignMenuItem}"
|
|
||||||
TargetType="{x:Type MenuItem}">
|
|
||||||
<Setter Property="Command"
|
|
||||||
Value="{Binding DataContext.AddToGroupCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
|
||||||
<Setter Property="CommandParameter">
|
|
||||||
<Setter.Value>
|
|
||||||
<MultiBinding
|
|
||||||
Converter="{StaticResource MultiCommandParameterConverter}">
|
|
||||||
<Binding />
|
|
||||||
<Binding Path="PlacementTarget.SelectedValue"
|
|
||||||
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
|
||||||
</MultiBinding>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</Style>
|
|
||||||
</ItemsControl.ItemContainerStyle>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.RemoveFromGroup}"
|
|
||||||
Command="{Binding Path=RemoveGroupCommand}"
|
|
||||||
CommandParameter="{Binding Path=PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />
|
|
||||||
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.CopyPassword}"
|
|
||||||
Command="{Binding Path=CopyPasswordCommand}"
|
|
||||||
CommandParameter="{Binding Path=PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />
|
|
||||||
</ContextMenu>
|
|
||||||
</FrameworkElement.ContextMenu>
|
|
||||||
|
|
||||||
</ListBox>
|
|
||||||
<Border Grid.Row="0" Margin="10" Padding="5"
|
<Border Grid.Row="0" Margin="10" Padding="5"
|
||||||
Visibility="{Binding MaFiles.Count, Converter={StaticResource AnyMafilesToVisibilityConverter}, Mode=OneWay}">
|
BorderThickness="1"
|
||||||
|
CornerRadius="12">
|
||||||
|
<Border.BorderBrush>
|
||||||
|
<SolidColorBrush Color="{DynamicResource Base300Color}" Opacity="0.3" />
|
||||||
|
</Border.BorderBrush>
|
||||||
<Border.Background>
|
<Border.Background>
|
||||||
<SolidColorBrush Color="DarkGray" Opacity="0.5" />
|
<SolidColorBrush Color="{DynamicResource Base100Color}"
|
||||||
|
Opacity="{Binding Settings.LeftOpacity}" />
|
||||||
</Border.Background>
|
</Border.Background>
|
||||||
<TextBlock TextWrapping="WrapWithOverflow" FontSize="16"
|
<Grid>
|
||||||
Text="{Tr MainWindow.Global.StartTip}" />
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<TextBlock Margin="4"
|
||||||
|
Visibility="{Binding MaFiles.Count, Converter={StaticResource AnyMafilesToVisibilityConverter}}"
|
||||||
|
TextWrapping="WrapWithOverflow" FontSize="16"
|
||||||
|
Text="{Tr MainWindow.LeftPart.NoMafiles}" />
|
||||||
|
|
||||||
|
<ListBox w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True" Margin="2"
|
||||||
|
ItemsSource="{Binding MaFiles}"
|
||||||
|
SelectedValue="{Binding SelectedMafile}">
|
||||||
|
<ListBox.ItemContainerStyle>
|
||||||
|
<Style TargetType="ListBoxItem"
|
||||||
|
BasedOn="{StaticResource MaterialDesignListBoxItem}">
|
||||||
|
<Setter Property="HorizontalAlignment" Value="Stretch" />
|
||||||
|
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
|
||||||
|
</Style>
|
||||||
|
</ListBox.ItemContainerStyle>
|
||||||
|
<ListBox.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock HorizontalAlignment="Stretch" Text="{Binding AccountName}">
|
||||||
|
<TextBlock.Resources>
|
||||||
|
<SolidColorBrush x:Key="BaseContentBrushProxy"
|
||||||
|
Color="{DynamicResource BaseContentColor}" />
|
||||||
|
</TextBlock.Resources>
|
||||||
|
|
||||||
|
<TextBlock.Foreground>
|
||||||
|
<Binding Path="LinkedClient.IsError"
|
||||||
|
Converter="{StaticResource PortableMaClientStatusToColorConverter}"
|
||||||
|
FallbackValue="{StaticResource BaseContentBrushProxy}" />
|
||||||
|
</TextBlock.Foreground>
|
||||||
|
</TextBlock>
|
||||||
|
<StackPanel
|
||||||
|
Visibility="{Binding LinkedClient, Converter={StaticResource NullableToVisibilityConverter}}"
|
||||||
|
Grid.Column="1" Orientation="Horizontal">
|
||||||
|
<md:PackIcon Kind="ShoppingCart"
|
||||||
|
Visibility="{Binding LinkedClient.AutoConfirmMarket, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Collapsed}" />
|
||||||
|
<md:PackIcon Kind="AccountArrowRight"
|
||||||
|
Visibility="{Binding LinkedClient.AutoConfirmTrades, Converter={StaticResource BooleanToVisibilityConverter}, FallbackValue=Collapsed}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListBox.ItemTemplate>
|
||||||
|
<ListBox.InputBindings>
|
||||||
|
<KeyBinding Key="C" Modifiers="Control"
|
||||||
|
Command="{Binding DataContext.CopyLoginCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||||
|
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}" />
|
||||||
|
<KeyBinding Key="X" Modifiers="Control"
|
||||||
|
Command="{Binding DataContext.CopyMafileCommand, RelativeSource={RelativeSource AncestorType=Window}}"
|
||||||
|
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType=ListBox}}" />
|
||||||
|
</ListBox.InputBindings>
|
||||||
|
<FrameworkElement.ContextMenu>
|
||||||
|
<ContextMenu>
|
||||||
|
<MenuItem InputGestureText="Ctrl+C"
|
||||||
|
Header="{Tr MainWindow.ContextMenus.Mafile.CopyLogin}"
|
||||||
|
Command="{Binding CopyLoginCommand}"
|
||||||
|
CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||||||
|
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.CopySteamId}"
|
||||||
|
Command="{Binding CopySteamIdCommand}"
|
||||||
|
CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||||||
|
<MenuItem InputGestureText="Ctrl+X"
|
||||||
|
Header="{Tr MainWindow.ContextMenus.Mafile.CopyMafile}"
|
||||||
|
Command="{Binding CopyMafileCommand}"
|
||||||
|
CommandParameter="{Binding PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||||||
|
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.AddToGroup}"
|
||||||
|
ItemsSource="{Binding Groups}">
|
||||||
|
<ItemsControl.ItemContainerStyle>
|
||||||
|
<Style BasedOn="{StaticResource MaterialDesignMenuItem}"
|
||||||
|
TargetType="{x:Type MenuItem}">
|
||||||
|
<Setter Property="Command"
|
||||||
|
Value="{Binding DataContext.AddToGroupCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
|
||||||
|
<Setter Property="CommandParameter">
|
||||||
|
<Setter.Value>
|
||||||
|
<MultiBinding
|
||||||
|
Converter="{StaticResource MultiCommandParameterConverter}">
|
||||||
|
<Binding />
|
||||||
|
<Binding Path="PlacementTarget.SelectedValue"
|
||||||
|
RelativeSource="{RelativeSource AncestorType=ContextMenu}" />
|
||||||
|
</MultiBinding>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</ItemsControl.ItemContainerStyle>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.RemoveFromGroup}"
|
||||||
|
Command="{Binding Path=RemoveGroupCommand}"
|
||||||
|
CommandParameter="{Binding Path=PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />
|
||||||
|
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.CopyPassword}"
|
||||||
|
Command="{Binding Path=CopyPasswordCommand}"
|
||||||
|
CommandParameter="{Binding Path=PlacementTarget.SelectedValue, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" />
|
||||||
|
|
||||||
|
</ContextMenu>
|
||||||
|
</FrameworkElement.ContextMenu>
|
||||||
|
|
||||||
|
</ListBox>
|
||||||
|
|
||||||
|
<TextBox Style="{StaticResource MaterialDesignFloatingHintTextBox}"
|
||||||
|
md:TextFieldAssist.HasClearButton="True" w:FontScaleWindow.Scale="0.7"
|
||||||
|
w:FontScaleWindow.ResizeFont="True" Grid.Row="1" Margin="10,5,10,10"
|
||||||
|
md:HintAssist.Hint="{Tr MainWindow.LeftPart.SearchBoxHint}"
|
||||||
|
Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
</Border>
|
</Border>
|
||||||
<TextBox Style="{StaticResource MaterialDesignFloatingHintTextBox}"
|
|
||||||
md:TextFieldAssist.HasClearButton="True" w:FontScaleWindow.Scale="0.7"
|
|
||||||
w:FontScaleWindow.ResizeFont="True" Grid.Row="1" Margin="10,5,10,10"
|
|
||||||
md:HintAssist.Hint="{Tr MainWindow.LeftPart.SearchBoxHint}"
|
|
||||||
Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
|
|
||||||
</Grid>
|
</Grid>
|
||||||
<md:Card Grid.Column="1" Margin="10,10,15,10" UniformCornerRadius="15">
|
<md:Card BorderThickness="1" Style="{StaticResource MaterialDesignOutlinedCard}" Grid.Column="1"
|
||||||
<Control.Background>
|
Margin="10,10,15,10" UniformCornerRadius="12" Opacity="{Binding Settings.RightOpacity}">
|
||||||
<SolidColorBrush Color="#FF1A1D25" Opacity="0.6" />
|
|
||||||
</Control.Background>
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition />
|
<RowDefinition Height="115*" />
|
||||||
|
<RowDefinition Height="436*" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<Grid Row="0">
|
<Grid Row="0">
|
||||||
@@ -334,47 +369,50 @@
|
|||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<TextBox w:FontScaleWindow.Scale="1.1" w:FontScaleWindow.ResizeFont="True"
|
<TextBox w:FontScaleWindow.Scale="1.1" w:FontScaleWindow.ResizeFont="True"
|
||||||
IsReadOnly="True" HorizontalContentAlignment="Center" Background="#00FFFFFF"
|
IsReadOnly="True" HorizontalContentAlignment="Center" Background="#00FFFFFF"
|
||||||
Style="{StaticResource MaterialDesignFilledTextBox}"
|
|
||||||
|
Style="{StaticResource MaterialDesignTextBox}"
|
||||||
|
|
||||||
Text="{Binding Code, FallbackValue=Code}">
|
Text="{Binding Code, FallbackValue=Code}">
|
||||||
<TextBox.InputBindings>
|
<TextBox.InputBindings>
|
||||||
<MouseBinding Gesture="LeftClick" Command="{Binding CopyCodeCommand}" />
|
<MouseBinding Gesture="LeftClick" Command="{Binding CopyCodeCommand}" />
|
||||||
</TextBox.InputBindings>
|
</TextBox.InputBindings>
|
||||||
</TextBox>
|
</TextBox>
|
||||||
<Grid Row="1">
|
<controls:CodeProgressBar
|
||||||
<Grid.ColumnDefinitions>
|
Visibility="{Binding SelectedMafile, Converter={StaticResource NullableToVisibilityConverter}}"
|
||||||
<ColumnDefinition />
|
Grid.Row="1" Height="4" Style="{StaticResource MaterialDesignLinearProgressBar}"
|
||||||
<ColumnDefinition Width="Auto" />
|
MaxTime="30" TimeRemaining="{Binding CodeProgress, Mode=OneWay}" />
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
<ProgressBar Margin="5" Foreground="#FF9932CC" Height="15"
|
|
||||||
md:TransitionAssist.DisableTransitions="True"
|
|
||||||
Value="{Binding CodeProgress}" />
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Button IsEnabled="{Binding IsMafileSelected}" w:FontScaleWindow.Scale="0.7"
|
<Button IsEnabled="{Binding IsMafileSelected}" w:FontScaleWindow.Scale="0.7"
|
||||||
w:FontScaleWindow.ResizeFont="True" Grid.Row="1"
|
w:FontScaleWindow.ResizeFont="True" Grid.Row="1"
|
||||||
Style="{StaticResource MaterialDesignOutlinedButton}" Margin="10"
|
Style="{StaticResource MaterialDesignOutlinedButton}" Margin="0,10,0,10"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
Content="{Tr MainWindow.RightPart.LoadConfirmations}"
|
||||||
Command="{Binding GetConfirmationsCommand}"
|
Command="{Binding GetConfirmationsCommand}"
|
||||||
Width="{Binding Path=ActualWidth, Converter={StaticResource CoefficientConverter}, ConverterParameter=0.6, RelativeSource={RelativeSource AncestorType=md:Card}}">
|
Width="{Binding Path=ActualWidth, Converter={StaticResource CoefficientConverter}, ConverterParameter=0.6, RelativeSource={RelativeSource AncestorType=md:Card}}" />
|
||||||
<TextBlock TextWrapping="Wrap" TextTrimming="WordEllipsis"
|
|
||||||
Text="{Tr MainWindow.RightPart.LoadConfirmations}" />
|
|
||||||
</Button>
|
|
||||||
<ItemsControl md:RippleAssist.IsDisabled="True" Focusable="False" Grid.Row="2"
|
<ItemsControl md:RippleAssist.IsDisabled="True" Focusable="False" Grid.Row="2"
|
||||||
w:FontScaleWindow.Scale="0.72" w:FontScaleWindow.ResizeFont="True"
|
w:FontScaleWindow.Scale="0.72" w:FontScaleWindow.ResizeFont="True"
|
||||||
Margin="10"
|
|
||||||
|
Margin="10,10,10,10"
|
||||||
Visibility="{Binding ConfirmationsVisible, Converter={StaticResource BooleanToVisibilityConverter}}"
|
Visibility="{Binding ConfirmationsVisible, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||||
ItemsSource="{Binding Confirmations}" />
|
ItemsSource="{Binding Confirmations}" Grid.RowSpan="2" />
|
||||||
<Button Grid.Row="3" Margin="1,0,1,3" w:FontScaleWindow.Scale="0.8"
|
<Button Style="{StaticResource MaterialDesignFlatButton}" Grid.Row="4"
|
||||||
w:FontScaleWindow.ResizeFont="True" Command="{Binding ConfirmLoginCommand}"
|
w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True"
|
||||||
|
Command="{Binding ConfirmLoginCommand}"
|
||||||
|
|
||||||
Content="{Tr MainWindow.RightPart.ConfirmLogin}" />
|
Content="{Tr MainWindow.RightPart.ConfirmLogin}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</md:Card>
|
</md:Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Border Grid.Row="2" BorderBrush="#FF696969">
|
<Border Grid.Row="2" BorderBrush="#1e1e24">
|
||||||
<Border.Background>
|
<Border.Background>
|
||||||
<SolidColorBrush Opacity="0.5" Color="DimGray" />
|
<SolidColorBrush Opacity="1" Color="{DynamicResource Base300Color}" />
|
||||||
</Border.Background>
|
</Border.Background>
|
||||||
<Grid>
|
<Grid>
|
||||||
<ToolBarPanel Orientation="Horizontal" Margin="5" w:FontScaleWindow.Scale="0.73"
|
|
||||||
|
<ToolBarPanel TextElement.Foreground="{DynamicResource SecondaryContentBrush}"
|
||||||
|
Orientation="Horizontal" Margin="5" w:FontScaleWindow.Scale="0.73"
|
||||||
|
|
||||||
w:FontScaleWindow.ResizeFont="True">
|
w:FontScaleWindow.ResizeFont="True">
|
||||||
<TextBlock FontWeight="Bold" Text="{Tr MainWindow.Footer.Account}" />
|
<TextBlock FontWeight="Bold" Text="{Tr MainWindow.Footer.Account}" />
|
||||||
<TextBlock Text="{Binding SelectedMafile.AccountName}" />
|
<TextBlock Text="{Binding SelectedMafile.AccountName}" />
|
||||||
@@ -382,19 +420,21 @@
|
|||||||
<TextBlock FontWeight="Bold" Text="{Tr MainWindow.Footer.Group}" />
|
<TextBlock FontWeight="Bold" Text="{Tr MainWindow.Footer.Group}" />
|
||||||
<TextBlock FontWeight="Normal"
|
<TextBlock FontWeight="Normal"
|
||||||
Text="{Binding SelectedMafile.Group, TargetNullValue=' ', FallbackValue=' ', Mode=OneWay}" />
|
Text="{Binding SelectedMafile.Group, TargetNullValue=' ', FallbackValue=' ', Mode=OneWay}" />
|
||||||
|
<!--<Button Command="{Binding DebugCommand}">Debug</Button>-->
|
||||||
</ToolBarPanel>
|
</ToolBarPanel>
|
||||||
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Right" w:FontScaleWindow.Scale="0.7"
|
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Right" w:FontScaleWindow.Scale="0.7"
|
||||||
w:FontScaleWindow.ResizeFont="True" Margin="0,0,10,0">
|
w:FontScaleWindow.ResizeFont="True" Margin="0,0,10,0">
|
||||||
<Hyperlink
|
<Hyperlink
|
||||||
NavigateUri="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies"
|
NavigateUri="https://github.com/achiez/NebulaAuth-Steam-Desktop-Authenticator-by-Achies"
|
||||||
Foreground="{DynamicResource PrimaryHueMidBrush}"
|
|
||||||
RequestNavigate="Hyperlink_OnRequestNavigate">
|
Foreground="{DynamicResource AccentBrush}"
|
||||||
|
Command="{Binding OpenLinksViewCommand}">
|
||||||
by achies
|
by achies
|
||||||
</Hyperlink>
|
</Hyperlink>
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
<md:Snackbar Grid.Row="1" FontSize="20" MessageQueue="{Binding MessageQueue}" />
|
<md:Snackbar Grid.Row="1" MessageQueue="{Binding MessageQueue}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</md:DialogHost>
|
</md:DialogHost>
|
||||||
</Border>
|
</Border>
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Navigation;
|
|
||||||
using System.Windows.Threading;
|
using System.Windows.Threading;
|
||||||
using MaterialDesignThemes.Wpf;
|
using MaterialDesignThemes.Wpf;
|
||||||
using NebulaAuth.Core;
|
using NebulaAuth.Core;
|
||||||
@@ -30,10 +28,13 @@ public partial class MainWindow
|
|||||||
|
|
||||||
private async void OnApplicationStarted(object? sender, EventArgs e)
|
private async void OnApplicationStarted(object? sender, EventArgs e)
|
||||||
{
|
{
|
||||||
|
((MainVM) DataContext).CurrentDialogHost = DialogHostInstance;
|
||||||
if (Settings.Instance.IsPasswordSet == false) return;
|
if (Settings.Instance.IsPasswordSet == false) return;
|
||||||
await Dispatcher.BeginInvoke(ShowSetPasswordDialog, DispatcherPriority.ContextIdle);
|
Topmost = false;
|
||||||
|
await Dispatcher.InvokeAsync(ShowSetPasswordDialog, DispatcherPriority.ContextIdle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private async Task ShowSetPasswordDialog()
|
private async Task ShowSetPasswordDialog()
|
||||||
{
|
{
|
||||||
var vm = new SetEncryptPasswordVM();
|
var vm = new SetEncryptPasswordVM();
|
||||||
@@ -41,6 +42,7 @@ public partial class MainWindow
|
|||||||
{
|
{
|
||||||
DataContext = vm
|
DataContext = vm
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = await DialogHost.Show(dialog);
|
var result = await DialogHost.Show(dialog);
|
||||||
var pass = vm.Password;
|
var pass = vm.Password;
|
||||||
if (result is true && string.IsNullOrWhiteSpace(pass) == false)
|
if (result is true && string.IsNullOrWhiteSpace(pass) == false)
|
||||||
@@ -49,15 +51,6 @@ public partial class MainWindow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e)
|
|
||||||
{
|
|
||||||
Process.Start(new ProcessStartInfo(e.Uri.ToString())
|
|
||||||
{
|
|
||||||
UseShellExecute = true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#region Dran'n'Drop
|
#region Dran'n'Drop
|
||||||
|
|
||||||
private void UIElement_OnPreviewTextInput(object sender, TextCompositionEventArgs e)
|
private void UIElement_OnPreviewTextInput(object sender, TextCompositionEventArgs e)
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
using NebulaAuth.Model.MAAC;
|
using NebulaAuth.Model.MAAC;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using SteamLib;
|
using SteamLib;
|
||||||
using SteamLib.Account;
|
using SteamLibForked.Models.Session;
|
||||||
|
|
||||||
namespace NebulaAuth.Model.Entities;
|
namespace NebulaAuth.Model.Entities;
|
||||||
|
|
||||||
+5
-6
@@ -8,7 +8,6 @@ using System.Threading.Tasks;
|
|||||||
using AchiesUtilities.Extensions;
|
using AchiesUtilities.Extensions;
|
||||||
using NebulaAuth.Core;
|
using NebulaAuth.Core;
|
||||||
using NebulaAuth.Model.Entities;
|
using NebulaAuth.Model.Entities;
|
||||||
using NLog;
|
|
||||||
|
|
||||||
namespace NebulaAuth.Model.MAAC;
|
namespace NebulaAuth.Model.MAAC;
|
||||||
|
|
||||||
@@ -16,6 +15,8 @@ public static class MultiAccountAutoConfirmer
|
|||||||
{
|
{
|
||||||
private const string LOC_PATH = "MAAC";
|
private const string LOC_PATH = "MAAC";
|
||||||
private static readonly ReaderWriterLockSlim Lock = new();
|
private static readonly ReaderWriterLockSlim Lock = new();
|
||||||
|
|
||||||
|
private static readonly SemaphoreSlim ExecutionLock = new(1, 1);
|
||||||
public static ObservableCollection<Mafile> Clients { get; }
|
public static ObservableCollection<Mafile> Clients { get; }
|
||||||
private static Timer Timer { get; }
|
private static Timer Timer { get; }
|
||||||
|
|
||||||
@@ -27,12 +28,10 @@ public static class MultiAccountAutoConfirmer
|
|||||||
UpdateTimer();
|
UpdateTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static readonly SemaphoreSlim ExecutionLock = new(1, 1);
|
|
||||||
|
|
||||||
// ReSharper disable once AsyncVoidMethod //Already safe
|
// ReSharper disable once AsyncVoidMethod //Already safe
|
||||||
private static async void TimerConfirm(object? state)
|
private static async void TimerConfirm(object? state)
|
||||||
{
|
{
|
||||||
bool isHeld = false;
|
var isHeld = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
isHeld = await ExecutionLock.WaitAsync(0);
|
isHeld = await ExecutionLock.WaitAsync(0);
|
||||||
@@ -41,8 +40,8 @@ public static class MultiAccountAutoConfirmer
|
|||||||
SnackbarController.SendSnackbar(GetLocalization("TimerPreventedOverlap"));
|
SnackbarController.SendSnackbar(GetLocalization("TimerPreventedOverlap"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await TimerConfirmInternal();
|
|
||||||
|
|
||||||
|
await TimerConfirmInternal();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -60,7 +59,7 @@ public static class MultiAccountAutoConfirmer
|
|||||||
private static async Task TimerConfirmInternal()
|
private static async Task TimerConfirmInternal()
|
||||||
{
|
{
|
||||||
var clients = Lock.ReadLock(() => Clients.ToArray());
|
var clients = Lock.ReadLock(() => Clients.ToArray());
|
||||||
var enabledClients = clients.Where(x => x.LinkedClient is { IsError: false }).ToArray();
|
var enabledClients = clients.Where(x => x.LinkedClient is {IsError: false}).ToArray();
|
||||||
enabledClients = DistributeEvenly(enabledClients).ToArray();
|
enabledClients = DistributeEvenly(enabledClients).ToArray();
|
||||||
var confirmed = 0;
|
var confirmed = 0;
|
||||||
await Task.Run(async () =>
|
await Task.Run(async () =>
|
||||||
+4
-3
@@ -14,10 +14,10 @@ using NebulaAuth.Model.Entities;
|
|||||||
using NebulaAuth.Utility;
|
using NebulaAuth.Utility;
|
||||||
using SteamLib.Api.Mobile;
|
using SteamLib.Api.Mobile;
|
||||||
using SteamLib.Authentication;
|
using SteamLib.Authentication;
|
||||||
using SteamLib.Core.Interfaces;
|
using SteamLib.Exceptions.Authorization;
|
||||||
using SteamLib.Exceptions;
|
|
||||||
using SteamLib.SteamMobile.Confirmations;
|
using SteamLib.SteamMobile.Confirmations;
|
||||||
using SteamLib.Web;
|
using SteamLib.Web;
|
||||||
|
using SteamLibForked.Abstractions;
|
||||||
|
|
||||||
namespace NebulaAuth.Model.MAAC;
|
namespace NebulaAuth.Model.MAAC;
|
||||||
|
|
||||||
@@ -90,7 +90,8 @@ public partial class PortableMaClient : ObservableObject, IDisposable
|
|||||||
var toConfirm = new List<Confirmation>();
|
var toConfirm = new List<Confirmation>();
|
||||||
if (AutoConfirmMarket)
|
if (AutoConfirmMarket)
|
||||||
{
|
{
|
||||||
var market = conf.Where(c => c.ConfType == ConfirmationType.MarketSellTransaction);
|
var market = conf.Where(c =>
|
||||||
|
c.ConfType is ConfirmationType.MarketSellTransaction or ConfirmationType.Purchase);
|
||||||
toConfirm.AddRange(market);
|
toConfirm.AddRange(market);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -7,9 +7,9 @@ using AchiesUtilities.Web.Models;
|
|||||||
using AchiesUtilities.Web.Proxy;
|
using AchiesUtilities.Web.Proxy;
|
||||||
using NebulaAuth.Model.Entities;
|
using NebulaAuth.Model.Entities;
|
||||||
using SteamLib.Api.Mobile;
|
using SteamLib.Api.Mobile;
|
||||||
|
using SteamLib.Api.Services;
|
||||||
using SteamLib.Authentication;
|
using SteamLib.Authentication;
|
||||||
using SteamLib.Core.Interfaces;
|
using SteamLib.Exceptions.Authorization;
|
||||||
using SteamLib.Exceptions;
|
|
||||||
using SteamLib.ProtoCore.Services;
|
using SteamLib.ProtoCore.Services;
|
||||||
using SteamLib.SteamMobile.Confirmations;
|
using SteamLib.SteamMobile.Confirmations;
|
||||||
using SteamLib.Web;
|
using SteamLib.Web;
|
||||||
@@ -26,6 +26,7 @@ public static class MaClient
|
|||||||
|
|
||||||
public static ProxyData? DefaultProxy { get; set; }
|
public static ProxyData? DefaultProxy { get; set; }
|
||||||
|
|
||||||
|
|
||||||
static MaClient()
|
static MaClient()
|
||||||
{
|
{
|
||||||
Proxy = new DynamicProxy();
|
Proxy = new DynamicProxy();
|
||||||
@@ -62,7 +63,7 @@ public static class MaClient
|
|||||||
return SteamMobileConfirmationsApi.GetConfirmations(Client, mafile, mafile.SessionData!.SteamId);
|
return SteamMobileConfirmationsApi.GetConfirmations(Client, mafile, mafile.SessionData!.SteamId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Task LoginAgain(Mafile mafile, string password, bool savePassword, ICaptchaResolver? resolver)
|
public static Task LoginAgain(Mafile mafile, string password, bool savePassword)
|
||||||
{
|
{
|
||||||
SetProxy(mafile);
|
SetProxy(mafile);
|
||||||
return SessionHandler.LoginAgain(new HttpClientHandlerPair(Client, ClientHandler), mafile, password,
|
return SessionHandler.LoginAgain(new HttpClientHandlerPair(Client, ClientHandler), mafile, password,
|
||||||
@@ -137,7 +138,7 @@ public static class MaClient
|
|||||||
ValidateMafile(mafile);
|
ValidateMafile(mafile);
|
||||||
SetProxy(mafile);
|
SetProxy(mafile);
|
||||||
var token = mafile.SessionData!.GetMobileToken()!.Value;
|
var token = mafile.SessionData!.GetMobileToken()!.Value;
|
||||||
var sessions = await SteamMobileAuthenticatorApi.GetAuthSessionsForAccount(Client, token.Token);
|
var sessions = await AuthenticationServiceApi.GetAuthSessionsForAccount(Client, token.Token);
|
||||||
|
|
||||||
if (sessions.ClientIds.Count == 0)
|
if (sessions.ClientIds.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -156,16 +157,11 @@ public static class MaClient
|
|||||||
}
|
}
|
||||||
|
|
||||||
var clientId = sessions.ClientIds.Single();
|
var clientId = sessions.ClientIds.Single();
|
||||||
var clientInfo = await SteamMobileAuthenticatorApi.GetAuthSessionInfo(Client, token.Token, clientId);
|
var clientInfo = await AuthenticationServiceApi.GetAuthSessionInfo(Client, token.Token, clientId);
|
||||||
var updateReq = new UpdateAuthSessionWithMobileConfirmation_Request
|
var updateReq =
|
||||||
{
|
AuthRequestHelper.CreateMobileConfirmationRequest(1, clientId, mafile.SessionData.SteamId.Steam64,
|
||||||
ClientId = clientId,
|
mafile.SharedSecret);
|
||||||
Confirm = true,
|
await AuthenticationServiceApi.UpdateAuthSessionWithMobileConfirmation(Client, token.Token, updateReq);
|
||||||
Persistence = 1,
|
|
||||||
Steamid = mafile.SessionData.SteamId.Steam64.ToUlong(),
|
|
||||||
Version = 1
|
|
||||||
};
|
|
||||||
await SteamMobileAuthenticatorApi.UpdateAuthSessionStatus(Client, token.Token, mafile.SharedSecret, updateReq);
|
|
||||||
return new LoginConfirmationResult
|
return new LoginConfirmationResult
|
||||||
{
|
{
|
||||||
Country = clientInfo.Country,
|
Country = clientInfo.Country,
|
||||||
@@ -22,6 +22,11 @@ public static class NebulaSerializer
|
|||||||
AllowDeviceIdGeneration = true,
|
AllowDeviceIdGeneration = true,
|
||||||
AllowSessionIdGeneration = true,
|
AllowSessionIdGeneration = true,
|
||||||
ThrowIfInvalidSteamId = false
|
ThrowIfInvalidSteamId = false
|
||||||
|
},
|
||||||
|
SerializationOptions =
|
||||||
|
{
|
||||||
|
Sign = true,
|
||||||
|
Formatting = Formatting.Indented
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -80,9 +85,9 @@ public static class NebulaSerializer
|
|||||||
{
|
{
|
||||||
if (Settings.Instance.LegacyMode)
|
if (Settings.Instance.LegacyMode)
|
||||||
{
|
{
|
||||||
return MafileSerializer.SerializeLegacy(data, Formatting.Indented, properties);
|
return MafileSerializer.SerializeLegacy(data, Serializer.Settings.SerializationOptions, properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
return MafileSerializer.Serialize(data);
|
return Serializer.Serialize(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@ using MaterialDesignThemes.Wpf;
|
|||||||
using NebulaAuth.Core;
|
using NebulaAuth.Core;
|
||||||
using NebulaAuth.Model.Entities;
|
using NebulaAuth.Model.Entities;
|
||||||
using NebulaAuth.View.Dialogs;
|
using NebulaAuth.View.Dialogs;
|
||||||
using SteamLib.Exceptions;
|
using SteamLib.Exceptions.Authorization;
|
||||||
|
|
||||||
namespace NebulaAuth.Model;
|
namespace NebulaAuth.Model;
|
||||||
|
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using AchiesUtilities.Web.Models;
|
using AchiesUtilities.Web.Models;
|
||||||
using NebulaAuth.Model.Entities;
|
using NebulaAuth.Model.Entities;
|
||||||
using SteamLib.Account;
|
|
||||||
using SteamLib.Api.Mobile;
|
using SteamLib.Api.Mobile;
|
||||||
using SteamLib.Authentication;
|
using SteamLib.Authentication;
|
||||||
using SteamLib.Authentication.LoginV2;
|
using SteamLib.Authentication.LoginV2;
|
||||||
using SteamLib.Exceptions;
|
using SteamLib.Exceptions.Authorization;
|
||||||
|
using SteamLib.Factory.Helpers;
|
||||||
using SteamLib.SteamMobile;
|
using SteamLib.SteamMobile;
|
||||||
|
using SteamLibForked.Models.Session;
|
||||||
|
|
||||||
namespace NebulaAuth.Model;
|
namespace NebulaAuth.Model;
|
||||||
|
|
||||||
@@ -35,11 +36,11 @@ public partial class SessionHandler //API
|
|||||||
public static async Task LoginAgain(HttpClientHandlerPair chp, Mafile mafile, string password, bool savePassword)
|
public static async Task LoginAgain(HttpClientHandlerPair chp, Mafile mafile, string password, bool savePassword)
|
||||||
{
|
{
|
||||||
var sgGenerator = new SteamGuardCodeGenerator(mafile.SharedSecret);
|
var sgGenerator = new SteamGuardCodeGenerator(mafile.SharedSecret);
|
||||||
var options = new LoginV2ExecutorOptions(LoginV2Executor.NullConsumer, chp.Client)
|
var options = new LoginV2ExecutorOptions(StaticLoginConsumer.Instance, chp.Client)
|
||||||
{
|
{
|
||||||
Logger = Shell.ExtensionsLogger,
|
Logger = Shell.ExtensionsLogger,
|
||||||
SteamGuardProvider = sgGenerator,
|
AuthProviders = [sgGenerator],
|
||||||
DeviceDetails = LoginV2ExecutorOptions.GetMobileDefaultDevice(),
|
DeviceDetails = DeviceDetailsDefaultBuilder.GetMobileDefaultDevice(),
|
||||||
WebsiteId = "Mobile"
|
WebsiteId = "Mobile"
|
||||||
};
|
};
|
||||||
chp.Handler.CookieContainer.ClearMobileSessionCookies();
|
chp.Handler.CookieContainer.ClearMobileSessionCookies();
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
@@ -12,6 +13,15 @@ public partial class Settings : ObservableObject
|
|||||||
{
|
{
|
||||||
public static Settings Instance { get; }
|
public static Settings Instance { get; }
|
||||||
|
|
||||||
|
private static IReadOnlyDictionary<ThemeType, string> ThemeNames { get; } = new Dictionary<ThemeType, string>
|
||||||
|
{
|
||||||
|
{ThemeType.Default, "DefaultTheme"},
|
||||||
|
{ThemeType.Light, "LightTheme"},
|
||||||
|
{ThemeType.Black, "BlackTheme"},
|
||||||
|
{ThemeType.Luxury, "LuxuryTheme"},
|
||||||
|
{ThemeType.Shadcn, "ShadcnTheme"}
|
||||||
|
};
|
||||||
|
|
||||||
static Settings()
|
static Settings()
|
||||||
{
|
{
|
||||||
if (File.Exists("settings.json") == false)
|
if (File.Exists("settings.json") == false)
|
||||||
@@ -40,6 +50,10 @@ public partial class Settings : ObservableObject
|
|||||||
private static void SettingsOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
private static void SettingsOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
|
||||||
{
|
{
|
||||||
Save();
|
Save();
|
||||||
|
if (e.PropertyName == nameof(ThemeType))
|
||||||
|
{
|
||||||
|
ThemeManager.ApplyTheme(ThemeNames[Instance.ThemeType]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Save()
|
public static void Save()
|
||||||
@@ -48,12 +62,27 @@ public partial class Settings : ObservableObject
|
|||||||
File.WriteAllText("settings.json", json);
|
File.WriteAllText("settings.json", json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void ResetThemeDefaults()
|
||||||
|
{
|
||||||
|
Instance.BackgroundBlur = 0.0;
|
||||||
|
Instance.BackgroundOpacity = 1.0;
|
||||||
|
Instance.BackgroundGamma = 0.0;
|
||||||
|
Instance.LeftOpacity = 0.4;
|
||||||
|
Instance.RightOpacity = 0.8;
|
||||||
|
Instance.ApplyBlurBackground = true;
|
||||||
|
Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetTheme()
|
||||||
|
{
|
||||||
|
return ThemeNames.TryGetValue(ThemeType, out var themeName) ? themeName : ThemeNames[ThemeType.Default];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#region Properties
|
#region Properties
|
||||||
|
|
||||||
[ObservableProperty] private BackgroundMode _backgroundMode = BackgroundMode.Default;
|
|
||||||
[ObservableProperty] private bool _hideToTray;
|
[ObservableProperty] private bool _hideToTray;
|
||||||
[ObservableProperty] private int _timerSeconds = 60;
|
[ObservableProperty] private int _timerSeconds = 60;
|
||||||
[ObservableProperty] private Color? _backgroundColor;
|
|
||||||
[ObservableProperty] private Color? _iconColor;
|
[ObservableProperty] private Color? _iconColor;
|
||||||
[ObservableProperty] private bool _isPasswordSet;
|
[ObservableProperty] private bool _isPasswordSet;
|
||||||
[ObservableProperty] private LocalizationLanguage _language = LocalizationLanguage.English;
|
[ObservableProperty] private LocalizationLanguage _language = LocalizationLanguage.English;
|
||||||
@@ -62,6 +91,15 @@ public partial class Settings : ObservableObject
|
|||||||
[ObservableProperty] private bool _useAccountNameAsMafileName;
|
[ObservableProperty] private bool _useAccountNameAsMafileName;
|
||||||
[ObservableProperty] private bool _ignorePatchTuesdayErrors;
|
[ObservableProperty] private bool _ignorePatchTuesdayErrors;
|
||||||
|
|
||||||
|
[ObservableProperty] private BackgroundMode _backgroundMode = BackgroundMode.Default;
|
||||||
|
[ObservableProperty] private double _leftOpacity = 0.4;
|
||||||
|
[ObservableProperty] private double _rightOpacity = 1.0;
|
||||||
|
[ObservableProperty] private double _backgroundBlur;
|
||||||
|
[ObservableProperty] private double _backgroundOpacity = 1;
|
||||||
|
[ObservableProperty] private double _backgroundGamma;
|
||||||
|
[ObservableProperty] private bool _applyBlurBackground = true;
|
||||||
|
[ObservableProperty] private ThemeType _themeType = ThemeType.Default;
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,4 +108,13 @@ public enum BackgroundMode
|
|||||||
Default,
|
Default,
|
||||||
Custom,
|
Custom,
|
||||||
Color
|
Color
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ThemeType
|
||||||
|
{
|
||||||
|
Default = 0,
|
||||||
|
Black = 1,
|
||||||
|
Light = 2,
|
||||||
|
Luxury = 3,
|
||||||
|
Shadcn = 4
|
||||||
}
|
}
|
||||||
@@ -11,11 +11,12 @@ namespace NebulaAuth.Model;
|
|||||||
|
|
||||||
public static class Shell
|
public static class Shell
|
||||||
{
|
{
|
||||||
public static Logger Logger { get; } = LogManager.GetLogger("Logger");
|
public static Logger Logger { get; private set; } = null!;
|
||||||
public static ILogger ExtensionsLogger { get; private set; } = null!;
|
public static ILogger ExtensionsLogger { get; private set; } = null!;
|
||||||
|
|
||||||
public static void Initialize()
|
public static void Initialize()
|
||||||
{
|
{
|
||||||
|
Logger = LogManager.GetLogger("Logger");
|
||||||
var lp = new NLogLoggerProvider();
|
var lp = new NLogLoggerProvider();
|
||||||
var logger = lp.CreateLogger("SteamLib");
|
var logger = lp.CreateLogger("SteamLib");
|
||||||
SteamLibErrorMonitor.MonitorLogger = logger;
|
SteamLibErrorMonitor.MonitorLogger = logger;
|
||||||
@@ -39,5 +40,6 @@ public static class Shell
|
|||||||
private static void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs e)
|
private static void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||||
{
|
{
|
||||||
Logger.Fatal((Exception) e.ExceptionObject);
|
Logger.Fatal((Exception) e.ExceptionObject);
|
||||||
|
LogManager.Shutdown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,71 +1,84 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using AchiesUtilities.Extensions;
|
using AchiesUtilities.Extensions;
|
||||||
using NebulaAuth.Model.Entities;
|
using NebulaAuth.Model.Entities;
|
||||||
using NebulaAuth.Model.Exceptions;
|
using NebulaAuth.Model.Exceptions;
|
||||||
using SteamLib.Core.Models;
|
using SteamLib;
|
||||||
using SteamLib.Exceptions;
|
using SteamLib.Exceptions.Authorization;
|
||||||
using SteamLib.SteamMobile;
|
using SteamLib.SteamMobile;
|
||||||
|
|
||||||
namespace NebulaAuth.Model;
|
namespace NebulaAuth.Model;
|
||||||
|
|
||||||
//RETHINK
|
|
||||||
public static class Storage
|
public static class Storage
|
||||||
{
|
{
|
||||||
public const string MAFILE_F = "maFiles";
|
public const string MAFILE_F = "maFiles";
|
||||||
public const string REMOVED_F = "maFiles_removed";
|
public const string REMOVED_F = "maFiles_removed";
|
||||||
|
private static int _duplicateFound;
|
||||||
|
|
||||||
public static readonly int DuplicateFound;
|
public static int DuplicateFound => _duplicateFound;
|
||||||
|
|
||||||
public static string MafileFolder { get; } = Path.GetFullPath(MAFILE_F);
|
public static string MafileFolder { get; } = Path.GetFullPath(MAFILE_F);
|
||||||
public static string RemovedMafileFolder { get; } = Path.GetFullPath(REMOVED_F);
|
public static string RemovedMafileFolder { get; } = Path.GetFullPath(REMOVED_F);
|
||||||
|
|
||||||
public static ObservableCollection<Mafile> MaFiles { get; } = new();
|
public static ObservableCollection<Mafile> MaFiles { get; private set; } = new();
|
||||||
|
|
||||||
static Storage()
|
static Storage()
|
||||||
{
|
{
|
||||||
if (Directory.Exists(MafileFolder) == false)
|
}
|
||||||
{
|
|
||||||
|
public static async Task Initialize(int threadCount, CancellationToken token = default)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(MafileFolder))
|
||||||
Directory.CreateDirectory(MafileFolder);
|
Directory.CreateDirectory(MafileFolder);
|
||||||
}
|
|
||||||
|
|
||||||
if (Directory.Exists(RemovedMafileFolder) == false)
|
if (!Directory.Exists(RemovedMafileFolder))
|
||||||
{
|
|
||||||
Directory.CreateDirectory(RemovedMafileFolder);
|
Directory.CreateDirectory(RemovedMafileFolder);
|
||||||
}
|
|
||||||
|
|
||||||
var files = Directory.GetFiles(MafileFolder);
|
|
||||||
var hashNames = new HashSet<string>();
|
|
||||||
var hashIds = new HashSet<SteamId>();
|
|
||||||
var comparer = new MafileNameComparer(Settings.Instance.UseAccountNameAsMafileName);
|
var comparer = new MafileNameComparer(Settings.Instance.UseAccountNameAsMafileName);
|
||||||
var ordered = files.Order(comparer).ToList();
|
var files = Directory
|
||||||
foreach (var file in ordered.Where(file => Path.GetExtension(file).EqualsIgnoreCase(".mafile")))
|
.GetFiles(MafileFolder)
|
||||||
|
.Where(file => Path.GetExtension(file).EqualsIgnoreCase(".mafile"))
|
||||||
|
.Order(comparer)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
|
||||||
|
var hashNames = new ConcurrentDictionary<string, byte>();
|
||||||
|
var hashIds = new ConcurrentDictionary<SteamId, byte>();
|
||||||
|
var localList = new ConcurrentBag<Mafile>();
|
||||||
|
|
||||||
|
var processed = 0;
|
||||||
|
|
||||||
|
await Task.Run(() =>
|
||||||
{
|
{
|
||||||
try
|
return Parallel.ForEachAsync(files,
|
||||||
{
|
new ParallelOptions {CancellationToken = token, MaxDegreeOfParallelism = threadCount},
|
||||||
var data = ReadMafile(file);
|
async (file, ct) =>
|
||||||
|
|
||||||
if (hashNames.Contains(data.AccountName) || hashIds.Contains(data.SteamId))
|
|
||||||
{
|
{
|
||||||
DuplicateFound++;
|
try
|
||||||
Shell.Logger.Error("Duplicate mafile {file}", Path.GetFileName(file));
|
{
|
||||||
continue;
|
var data = await ReadMafileAsync(file);
|
||||||
}
|
|
||||||
|
|
||||||
hashNames.Add(data.AccountName);
|
if (!hashNames.TryAdd(data.AccountName, 0) ||
|
||||||
if (data.SessionData != null) hashIds.Add(data.SteamId);
|
(data.SessionData != null && !hashIds.TryAdd(data.SteamId, 0)))
|
||||||
MaFiles.Add(data);
|
{
|
||||||
}
|
Interlocked.Increment(ref _duplicateFound);
|
||||||
catch (Exception ex)
|
Shell.Logger.Error("Duplicate mafile {file}", Path.GetFileName(file));
|
||||||
{
|
}
|
||||||
Shell.Logger.Error(ex, "Can't load mafile {file}", Path.GetFileName(file));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MaFiles = new ObservableCollection<Mafile>(MaFiles.OrderBy(m => m.AccountName));
|
localList.Add(data);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Shell.Logger.Error(ex, "Can't load mafile {file}", Path.GetFileName(file));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, token);
|
||||||
|
|
||||||
|
MaFiles = new ObservableCollection<Mafile>(localList.OrderBy(m => m.AccountName));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -117,6 +130,12 @@ public static class Storage
|
|||||||
return NebulaSerializer.Deserialize(str);
|
return NebulaSerializer.Deserialize(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static async Task<Mafile> ReadMafileAsync(string path)
|
||||||
|
{
|
||||||
|
var str = await File.ReadAllTextAsync(path);
|
||||||
|
return NebulaSerializer.Deserialize(str);
|
||||||
|
}
|
||||||
|
|
||||||
public static void SaveMafile(Mafile data)
|
public static void SaveMafile(Mafile data)
|
||||||
{
|
{
|
||||||
var path = CreatePathForMafile(data);
|
var path = CreatePathForMafile(data);
|
||||||
@@ -202,9 +221,35 @@ public static class Storage
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void BackupHandler(MobileDataExtended data)
|
||||||
|
{
|
||||||
|
if (Directory.Exists("mafiles_backup") == false)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory("mafiles_backup");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var json = NebulaSerializer.SerializeMafile(data, null);
|
||||||
|
File.WriteAllText(Path.Combine("mafiles_backup", data.AccountName + ".mafile"),
|
||||||
|
json);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void BackupHandlerStr(string accountName, string data)
|
||||||
|
{
|
||||||
|
if (Directory.Exists("mafiles_backup") == false)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory("mafiles_backup");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
File.WriteAllText(Path.Combine("mafiles_backup", accountName + ".mafile"),
|
||||||
|
data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Refactor
|
//TODO: Refactor
|
||||||
|
//TODO: use numeric orderer when .net 10 released
|
||||||
internal class MafileNameComparer : IComparer<string>
|
internal class MafileNameComparer : IComparer<string>
|
||||||
{
|
{
|
||||||
private const string MAF_64_START = "765";
|
private const string MAF_64_START = "765";
|
||||||
@@ -16,7 +16,6 @@
|
|||||||
</layout>
|
</layout>
|
||||||
<layout type='SimpleLayout' text="," />
|
<layout type='SimpleLayout' text="," />
|
||||||
</layout>
|
</layout>
|
||||||
|
|
||||||
</target>
|
</target>
|
||||||
</targets>
|
</targets>
|
||||||
|
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net9.0-windows7.0</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<SatelliteResourceLanguages>en;ru;ua</SatelliteResourceLanguages>
|
||||||
|
<ApplicationIcon>Theme\lock.ico</ApplicationIcon>
|
||||||
|
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
|
||||||
|
<AssemblyVersion>1.7.1</AssemblyVersion>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="Theme\1140x641.jpg" />
|
||||||
|
<None Remove="Theme\Background.png" />
|
||||||
|
<None Remove="Theme\lock.ico" />
|
||||||
|
<None Remove="Theme\SplashScreen.png" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Autoupdater.NET.Official" Version="1.9.2" />
|
||||||
|
<PackageReference Include="CodingSeb.Localization.JsonFileLoader" Version="1.4.0" />
|
||||||
|
<PackageReference Include="CodingSeb.Localization.WPF" Version="1.4.1" />
|
||||||
|
<PackageReference Include="CodingSebLocalization.Fody" Version="1.4.0" />
|
||||||
|
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||||
|
<PackageReference Include="MaterialDesignThemes" Version="5.2.1" />
|
||||||
|
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.135" />
|
||||||
|
<PackageReference Include="NLog" Version="5.5.0" />
|
||||||
|
<PackageReference Include="NLog.Extensions.Logging" Version="5.5.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Resource Include="Theme\Background.png">
|
||||||
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
|
</Resource>
|
||||||
|
<Resource Include="Theme\Background_Old.jpg">
|
||||||
|
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
|
||||||
|
</Resource>
|
||||||
|
<Resource Include="Theme\lock.ico" />
|
||||||
|
<Resource Include="Theme\SplashScreen.png" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\SteamLibForked\SteamLibForked.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Update="View\Dialogs\LoginAgainOnImportDialog.xaml.cs">
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="NLog.config">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
<None Update="localization.loc.json">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:s="clr-namespace:System;assembly=mscorlib"
|
||||||
|
xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml"
|
||||||
|
xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||||
|
<s:Boolean
|
||||||
|
x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=viewmodel_005Clinker_005Csteps/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||||
@@ -6,4 +6,9 @@
|
|||||||
• Добавить запоминание пароля при привязке мафайла
|
• Добавить запоминание пароля при привязке мафайла
|
||||||
• Функция переноса гуарда с мобильного устройства на ПК с созданием мафайла
|
• Функция переноса гуарда с мобильного устройства на ПК с созданием мафайла
|
||||||
• Добавить полное шифрование мафайлов по аналогии с SDA
|
• Добавить полное шифрование мафайлов по аналогии с SDA
|
||||||
• Сделать автоматический билд и загрузку обновления на Github при изменении в мастер ветке
|
• Сделать автоматический билд и загрузку обновления на Github при изменении в мастер ветке
|
||||||
|
• Антик-окно как в MarketApp, возможность сразу открыть браузер напр. на CefSharp с залогиненым аккаунтом
|
||||||
|
• Стабильность авто-подтверждений, возможность задать пользователю порог времени когда льются ошибки и только тогда выключать авто-подтверждение
|
||||||
|
• Безопасное сохранение мафайлов через .tmp / .bak
|
||||||
|
• Создание механизма накопления ошибок, чтобы исправить Patch Tuesday, условно аккаунт может игнорировать ошибки 1-2 часа (настриавемо)
|
||||||
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.4 MiB |
|
Before Width: | Height: | Size: 713 KiB After Width: | Height: | Size: 713 KiB |
@@ -0,0 +1,92 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="PrimaryBrush" Color="{StaticResource PrimaryColor}" />
|
||||||
|
<SolidColorBrush x:Key="PrimaryContentBrush" Color="{StaticResource PrimaryContentColor}" />
|
||||||
|
<SolidColorBrush x:Key="SecondaryBrush" Color="{StaticResource SecondaryColor}" />
|
||||||
|
<SolidColorBrush x:Key="SecondaryContentBrush" Color="{StaticResource SecondaryContentColor}" />
|
||||||
|
<SolidColorBrush x:Key="AccentBrush" Color="{StaticResource AccentColor}" />
|
||||||
|
<SolidColorBrush x:Key="AccentContentBrush" Color="{StaticResource AccentContentColor}" />
|
||||||
|
<SolidColorBrush x:Key="NeutralBrush" Color="{StaticResource NeutralColor}" />
|
||||||
|
<SolidColorBrush x:Key="NeutralContentBrush" Color="{StaticResource NeutralContentColor}" />
|
||||||
|
<SolidColorBrush x:Key="Base100Brush" Color="{StaticResource Base100Color}" />
|
||||||
|
<SolidColorBrush x:Key="Base200Brush" Color="{StaticResource Base200Color}" />
|
||||||
|
<SolidColorBrush x:Key="Base300Brush" Color="{StaticResource Base300Color}" />
|
||||||
|
<!--BackgroundBrush conflict-->
|
||||||
|
<SolidColorBrush x:Key="BaseContentBrush" Color="{StaticResource BaseContentColor}" />
|
||||||
|
<SolidColorBrush x:Key="BaseShadowBrush" Color="{StaticResource BaseShadowColor}" />
|
||||||
|
<SolidColorBrush x:Key="InfoBrush" Color="{StaticResource InfoColor}" />
|
||||||
|
<SolidColorBrush x:Key="InfoContentBrush" Color="{StaticResource InfoContentColor}" />
|
||||||
|
<SolidColorBrush x:Key="SuccessBrush" Color="{StaticResource SuccessColor}" />
|
||||||
|
<SolidColorBrush x:Key="SuccessContentBrush" Color="{StaticResource SuccessContentColor}" />
|
||||||
|
<SolidColorBrush x:Key="WarningBrush" Color="{StaticResource WarningColor}" />
|
||||||
|
<SolidColorBrush x:Key="WarningContentBrush" Color="{StaticResource WarningContentColor}" />
|
||||||
|
<SolidColorBrush x:Key="ErrorBrush" Color="{StaticResource ErrorColor}" />
|
||||||
|
<SolidColorBrush x:Key="ErrorContentBrush" Color="{StaticResource ErrorContentColor}" />
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="MainBackgroundBrush" Color="{StaticResource NeutralColor}" />
|
||||||
|
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.Primary.Light" Color="{StaticResource PrimaryColor}" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.Primary.Light.Foreground" Color="{StaticResource BaseContentColor}" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.Primary" Color="{StaticResource PrimaryColor}" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.Primary.Foreground" Color="{StaticResource BaseContentColor}" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.Primary.Dark" Color="{StaticResource PrimaryColor}" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.Primary.Dark.Foreground" Color="{StaticResource PrimaryContentColor}" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.Background" Color="{StaticResource Base200Color}" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.Foreground" Color="{StaticResource BaseContentColor}" />
|
||||||
|
|
||||||
|
<!--Unchecked checkbox uses it-->
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.ForegroundLight" Color="{StaticResource PrimaryColor}" />
|
||||||
|
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.ToolBar.Item.Background" Color="{StaticResource PrimaryColor}" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.ToolBar.Item.Foreground" Color="{StaticResource BaseContentColor}" />
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.Card.Background" Color="{StaticResource Base100Color}" />
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.TextBox.HoverBorder" Color="{StaticResource BaseContentColor}"
|
||||||
|
Opacity="0.4" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.TextBox.Border" Color="{StaticResource BaseContentColor}"
|
||||||
|
Opacity="0.4" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.TextBox.OutlineBorder" Color="{StaticResource BaseContentColor}"
|
||||||
|
Opacity="0.4" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.TextBox.DisabledBackground" Color="{StaticResource Base100Color}" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.TextBox.FilledBackground" Color="{StaticResource Base100Color}" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.TextBox.HoverBackground" Color="{StaticResource Base100Color}" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.TextBox.OutlineInactiveBorder" Color="{StaticResource Base100Color}" />
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.SnackBar.Background" Color="{StaticResource BaseContentColor}" />
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.ComboBox.Disabled" Color="{StaticResource Base100Color}" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.ComboBox.FilledBackground" Color="{StaticResource Base100Color}" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.ComboBox.HoverBackground" Color="{StaticResource Base100Color}" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.ComboBox.OutlineInactiveBorder"
|
||||||
|
Color="{StaticResource BaseContentColor}" Opacity="0.4" />
|
||||||
|
<!--<SolidColorBrush x:Key="MaterialDesign.Brush.ComboBox.Popup.DarkBackground" Color="Red" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.ComboBox.Popup.DarkForeground" Color="Red" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.ComboBox.Popup.LightBackground" Color="Red" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.ComboBox.Popup.LightForeground" Color="Red" />-->
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.ComboBox.HoverBorder" Color="{StaticResource BaseContentColor}"
|
||||||
|
Opacity="0.4" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.ComboBox.Border" Color="{StaticResource BaseContentColor}"
|
||||||
|
Opacity="0.4" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.ComboBox.OutlineBorder" Color="{StaticResource BaseContentColor}"
|
||||||
|
Opacity="0.4" />
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.ToolBar.Background" Color="{StaticResource Base100Color}" />
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.Secondary.Light"
|
||||||
|
Color="{StaticResource SecondaryColor}" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.Secondary.Light.Foreground"
|
||||||
|
Color="{StaticResource BaseContentColor}" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.Secondary" Color="{StaticResource SecondaryColor}" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.Secondary.Foreground"
|
||||||
|
Color="{StaticResource BaseContentColor}" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.Secondary.Dark"
|
||||||
|
Color="{StaticResource SecondaryColor}" />
|
||||||
|
<SolidColorBrush x:Key="MaterialDesign.Brush.Secondary.Dark.Foreground"
|
||||||
|
Color="{StaticResource BaseContentColor}" />
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Media.Animation;
|
||||||
|
|
||||||
|
namespace NebulaAuth.Theme.Controls;
|
||||||
|
|
||||||
|
public class CodeProgressBar : ProgressBar
|
||||||
|
{
|
||||||
|
public static readonly DependencyProperty TimeRemainingProperty =
|
||||||
|
DependencyProperty.Register(nameof(TimeRemaining), typeof(double), typeof(CodeProgressBar),
|
||||||
|
new PropertyMetadata(-1.0, OnTimeRemainingChanged));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty MaxTimeProperty =
|
||||||
|
DependencyProperty.Register(nameof(MaxTime), typeof(double), typeof(CodeProgressBar),
|
||||||
|
new PropertyMetadata(30.0)); // По умолчанию 30 сек
|
||||||
|
|
||||||
|
public double TimeRemaining
|
||||||
|
{
|
||||||
|
get => (double) GetValue(TimeRemainingProperty);
|
||||||
|
set => SetValue(TimeRemainingProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double MaxTime
|
||||||
|
{
|
||||||
|
get => (double) GetValue(MaxTimeProperty);
|
||||||
|
set => SetValue(MaxTimeProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnTimeRemainingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (d is CodeProgressBar progressBar)
|
||||||
|
{
|
||||||
|
var newValue = (double) e.NewValue;
|
||||||
|
progressBar.StartProgressAnimation(newValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartProgressAnimation(double timeRemaining)
|
||||||
|
{
|
||||||
|
if (timeRemaining <= 0 || MaxTime <= 0) return;
|
||||||
|
|
||||||
|
var progress = (1 - timeRemaining / MaxTime) * 100;
|
||||||
|
Value = 0;
|
||||||
|
Value = 100;
|
||||||
|
var animation = new DoubleAnimation
|
||||||
|
{
|
||||||
|
From = progress,
|
||||||
|
To = 100,
|
||||||
|
Duration = TimeSpan.FromSeconds(timeRemaining),
|
||||||
|
AccelerationRatio = 0,
|
||||||
|
DecelerationRatio = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
BeginAnimation(ValueProperty, animation);
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user