Добавьте файлы проекта.

This commit is contained in:
Давид Чернопятов
2024-02-01 01:21:56 +02:00
parent ff5a5e1a58
commit e0ef2c6a46
189 changed files with 14229 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\SteamLibForked\SteamLibForked.csproj" />
</ItemGroup>
</Project>
+51
View File
@@ -0,0 +1,51 @@
using Newtonsoft.Json;
using SteamLib.Utility.MaFiles;
var currentPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
if (currentPath != null)
Environment.CurrentDirectory = currentPath;
Console.WriteLine(currentPath);
foreach (var path in args)
{
if (Path.Exists(path) == false)
{
Console.WriteLine($"NOT VALID PATH: '{path}'");
continue;
}
Console.WriteLine("Reading: " + path);
try
{
var text = File.ReadAllText(path);
var maf = MafileSerializer.Deserialize(text, out _);
var legacy = MafileSerializer.SerializeLegacy(maf, Formatting.Indented);
var name = Path.GetFileNameWithoutExtension(path);
Write(legacy, name);
Console.WriteLine("DONE: " + name);
}
catch (Exception ex)
{
Console.WriteLine($"ERROR: {ex.Message}");
Console.WriteLine(ex);
}
finally
{
Console.WriteLine("-----------------------------------------");
}
}
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
void Write(string maf, string name)
{
var path = Path.Combine(name + "_legacy.mafile");
File.WriteAllText(path, maf);
}
+40
View File
@@ -0,0 +1,40 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.4.33205.214
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
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0FD01700-6D5C-451B-93BA-0860647E8F13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0FD01700-6D5C-451B-93BA-0860647E8F13}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0FD01700-6D5C-451B-93BA-0860647E8F13}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0FD01700-6D5C-451B-93BA-0860647E8F13}.Release|Any CPU.Build.0 = Release|Any CPU
{2D78A7D9-986A-4890-8A91-7ABD57A91830}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2D78A7D9-986A-4890-8A91-7ABD57A91830}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2D78A7D9-986A-4890-8A91-7ABD57A91830}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2D78A7D9-986A-4890-8A91-7ABD57A91830}.Release|Any CPU.Build.0 = Release|Any CPU
{09F02072-F91D-4DAA-87BC-A34D3E150570}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{09F02072-F91D-4DAA-87BC-A34D3E150570}.Debug|Any CPU.Build.0 = Debug|Any CPU
{09F02072-F91D-4DAA-87BC-A34D3E150570}.Release|Any CPU.ActiveCfg = Release|Any CPU
{09F02072-F91D-4DAA-87BC-A34D3E150570}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FA34DDD5-BDC0-4C0A-B347-4ECDBF446900}
EndGlobalSection
EndGlobal
+2
View File
@@ -0,0 +1,2 @@
<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:String x:Key="/Default/CodeInspection/Highlighting/InspectionSeverities/=CheckNamespace/@EntryIndexedValue">DO_NOT_SHOW</s:String></wpf:ResourceDictionary>
+50
View File
@@ -0,0 +1,50 @@
<Application x:Class="NebulaAuth.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:NebulaAuth"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:theme="clr-namespace:NebulaAuth.Theme"
xmlns:converters="clr-namespace:NebulaAuth.Converters"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
xmlns:background="clr-namespace:NebulaAuth.Converters.Background"
StartupUri="MainWindow.xaml">
<Application.Resources>
<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:ReverseBooleanConverter x:Key="ReverseBooleanConverter"/>
<converters:OnOffBoolTextConverter x:Key="OnOffBoolTextConverter"/>
<converters:SelectedProxyTextConverter x:Key="SelectedProxyTextConverter"/>
<converters:ProxyTextConverter x:Key="ProxyTextConverter"/>
<converters:MultiCommandParameterConverter x:Key="MultiCommandParameterConverter"/>
<converters:ColorToBrushConverter x:Key="ColorToBrushConverter"/>
<!-- Background converters-->
<background:BackgroundImageVisibleConverter x:Key="BackgroundImageVisibleConverter"/>
<background:BackgroundSourceConverter x:Key="BackgroundSourceConverter"/>
<system:Boolean x:Key="True">True</system:Boolean>
<system:Boolean x:Key="False">False</system:Boolean>
<ResourceDictionary.MergedDictionaries>
<materialDesign:BundledTheme BaseTheme="Dark" PrimaryColor="LightGreen" SecondaryColor="Purple" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignExtensions;component/Themes/Generic.xaml" />
<ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
<ResourceDictionary 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.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
+35
View File
@@ -0,0 +1,35 @@
using NebulaAuth.Core;
using NebulaAuth.Model;
using NebulaAuth.Model.Exceptions;
using System;
using System.Windows;
using CodingSeb.Localization;
namespace NebulaAuth;
public partial class App : Application
{
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.Message;
if (ex is CantAlignTimeException)
{
msg = Loc.Tr(LocManager.GetCodeBehind("CantAlignTimeError"));
}
MessageBox.Show(msg);
throw;
}
}
}
+10
View File
@@ -0,0 +1,10 @@
using System.Windows;
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
@@ -0,0 +1,20 @@
using NebulaAuth.Model;
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace NebulaAuth.Converters.Background;
public class BackgroundImageVisibleConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return value is not BackgroundMode.Color ? Visibility.Visible : Visibility.Hidden;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
@@ -0,0 +1,28 @@
using System;
using System.Globalization;
using System.IO;
using System.Windows.Data;
using System.Windows.Media.Imaging;
using NebulaAuth.Model;
namespace NebulaAuth.Converters.Background;
public class BackgroundSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is BackgroundMode.Custom)
{
if(File.Exists("Background.png"))
return new BitmapImage(new Uri(Path.GetFullPath("Background.png")));
}
return new BitmapImage(new Uri("pack://application:,,,/Theme/Background.jpg"));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
@@ -0,0 +1,19 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace NebulaAuth.Converters;
[ValueConversion(typeof(double), typeof(double))]
public class CoefficientConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return System.Convert.ToDouble(value) * System.Convert.ToDouble(parameter, CultureInfo.InvariantCulture);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return (double)value / (double)parameter;
}
}
@@ -0,0 +1,30 @@
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
using System;
namespace NebulaAuth.Converters;
[ValueConversion(typeof(Color), typeof(Brush))]
public class ColorToBrushConverter : IValueConverter
{
public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is Color color)
{
SolidColorBrush rv = new(color);
rv.Freeze();
return rv;
}
return Binding.DoNothing;
}
public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is SolidColorBrush brush)
{
return brush.Color;
}
return default(Color);
}
}
@@ -0,0 +1,18 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace NebulaAuth.Converters;
public class MultiCommandParameterConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values.Clone();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
@@ -0,0 +1,23 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace NebulaAuth.Converters;
public class OnOffBoolTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is not bool b)
{
throw new InvalidCastException($"Can't cast value {value} to 'bool'");
}
return b ? "вкл" : "выкл";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
@@ -0,0 +1,24 @@
using System;
using System.Globalization;
using System.Windows.Data;
using NebulaAuth.Model.Entities;
namespace NebulaAuth.Converters;
public class ProxyTextConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is not MaProxy p)
{
return string.Empty;
}
return $"{p.Id}: {p.Data.Address}";
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
@@ -0,0 +1,18 @@
using System;
using System.Globalization;
using System.Windows.Data;
namespace NebulaAuth.Converters;
public class ReverseBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
return !(bool)value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
return !(bool)value;
}
}
@@ -0,0 +1,26 @@
using System;
using System.Globalization;
using System.Windows.Data;
using NebulaAuth.Model.Entities;
namespace NebulaAuth.Converters;
public class SelectedProxyTextConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values[0] is not MaProxy proxy)
{
return string.Empty;
}
var proxyExist = (bool)values[1];
return proxyExist ? $"{proxy.Id}: {proxy.Data.Address}" : "";
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Data;
namespace NebulaAuth.Converters;
public class ValueConverterGroup : List<IValueConverter>, IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return this.Aggregate(value, (current, converter) => converter.Convert(current, targetType, parameter, culture));
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
#endregion
}
+76
View File
@@ -0,0 +1,76 @@
using System.Threading.Tasks;
using MaterialDesignThemes.Wpf;
using NebulaAuth.Model.Entities;
using NebulaAuth.View;
using NebulaAuth.View.Dialogs;
using NebulaAuth.ViewModel.Other;
namespace NebulaAuth.Core;
public static class DialogsController
{
#region CommonDialogs
public static async Task<bool> ShowConfirmCancelDialog(string? msg = null)
{
var content = msg == null ? new ConfirmCancelDialog() : new ConfirmCancelDialog(msg);
var result = await DialogHost.Show(content);
return result != null && (bool) result;
}
//public static async Task<string?> ShowTextFieldDialog(string? msg = null)
//{
// var content = msg == null ? new TextFieldDialog() : new TextFieldDialog(msg);
// var result = await DialogHost.Show(content);
// return result as string;
//}
#endregion
public static async Task<LoginAgainVM?> ShowLoginAgainDialog(string username)
{
var vm = new LoginAgainVM
{
UserName = username
};
var content = new LoginAgainDialog()
{
DataContext = vm
};
var result = await DialogHost.Show(content);
if (result is true)
{
return vm;
}
return null;
}
public static async Task ShowProxyManager(MaProxy? currentProxy)
{
var vm = new ProxyManagerVM();
var view = new ProxyManagerView
{
DataContext = vm
};
await DialogHost.Show(view);
}
public static void CloseDialog()
{
DialogHost.Close(null);
}
public static async Task ShowLinkerDialog()
{
var vm = new LinkAccountVM();
var view = new LinkerView()
{
DataContext = vm
};
await DialogHost.Show(view);
}
}
+105
View File
@@ -0,0 +1,105 @@
using CodingSeb.Localization;
using CodingSeb.Localization.Loaders;
using System;
using System.IO;
using System.Reflection;
using System.Text;
namespace NebulaAuth.Core;
public static class LocManager
{
public const string CODE_BEHIND_PATH_PART = "CodeBehind";
public const string COMMON_PATH_PART = "Common";
public static void SetApplicationLocalization(LocalizationLanguage language)
{
Loc.Instance.CurrentLanguage = GetLanguageCode(language);
//Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultureInfo(GetLanguageCode(language));
//Thread.CurrentThread.CurrentCulture= CultureInfo.GetCultureInfo(GetLanguageCode(language));
}
public static string GetLanguageCode(LocalizationLanguage language)
{
return language switch
{
LocalizationLanguage.English => "en",
LocalizationLanguage.Russian => "ru",
LocalizationLanguage.Ukrainian => "ua",
_ => throw new ArgumentOutOfRangeException(nameof(language), language, null)
};
}
public static void Init()
{
Loc.LogOutMissingTranslations = true;
LocalizationLoader.Instance.FileLanguageLoaders.Add(new JsonFileLoader());
ReloadFiles();
}
public static void ReloadFiles()
{
string exampleFileFileName = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "localization.loc.json");
LocalizationLoader.Instance.ClearAllTranslations();
LocalizationLoader.Instance.AddFile(exampleFileFileName);
}
public static string? GetCodeBehind(params string[] path)
{
return GetConcat(CODE_BEHIND_PATH_PART, path);
}
public static string GetCodeBehindOrDefault(string def, params string[] path)
{
return GetCodeBehind(path) ?? def;
}
public static string? GetCommon(params string[] path)
{
return GetConcat(COMMON_PATH_PART, path);
}
public static string GetCommonOrDefault(string def, params string[] path)
{
return GetCommon(path) ?? def;
}
public static string? Get(params string[] path)
{
var sb = new StringBuilder();
foreach (var part in path)
{
sb.Append('.');
sb.Append(part);
}
sb.Remove(0, 1);
var fullPath = sb.ToString();
var message = Loc.Tr(fullPath, "~|*ABSENT|~");
return message == "~|*ABSENT|~" ? null : message;
}
public static string GetOrDefault(string def, params string[] path)
{
return Get(path) ?? def;
}
private static string? GetConcat(string first, string[] path)
{
string[] newArray = new string[path.Length + 1];
newArray[0] = first;
Array.Copy(path, 0, newArray, 1, path.Length);
return Get(newArray);
}
}
public enum LocalizationLanguage
{
English,
Russian,
Ukrainian
}
+12
View File
@@ -0,0 +1,12 @@
using System.Threading.Tasks;
namespace NebulaAuth.Core;
public static class UpdateManager
{
public static async Task CheckForUpdates()
{
}
}
+56
View File
@@ -0,0 +1,56 @@
using System;
using MaterialDesignThemes.Wpf;
namespace NebulaAuth.Core;
public class SnackbarController
{
public static SnackbarMessageQueue MessageQueue { get; } = new() { DiscardDuplicates = true};
private const int MIN_SNACKBAR_TIME = 1000;
public SnackbarController()
{
}
/// <summary>
///
/// </summary>
/// <param name="text"></param>
/// <param name="duration">Default duration is 1 second</param>
public static void SendSnackbar(string text, TimeSpan? duration = null)
{
duration ??= GetSnackbarTime(text);
MessageQueue.Enqueue(text, null, null, null, false, false, duration.Value);
}
/// <summary>
///
/// </summary>
/// <param name="text"></param>
/// <param name="duration">Default duration is 1 second</param>
public static void SendSnackbarWithButton(string text, string actionText = "OK", Action? action = null, TimeSpan? duration = null)
{
duration ??= GetSnackbarTime(text);
Action<object?> argAction;
if (action == null)
{
argAction = _ => { };
}
else
{
argAction = _ => action();
}
MessageQueue.Enqueue(text, actionText, argAction, null, false, false, duration);
}
private static TimeSpan GetSnackbarTime(string str)
{
if (str.Length <= 100) return TimeSpan.FromMilliseconds(MIN_SNACKBAR_TIME);
var length = str.Length / 0.07;
return TimeSpan.FromMilliseconds(length);
}
}
+70
View File
@@ -0,0 +1,70 @@
using NebulaAuth.Model;
using System;
using System.ComponentModel;
using System.Drawing;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Color = System.Drawing.Color;
namespace NebulaAuth.Core;
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!;
static ThemeManager()
{
Settings.Instance.PropertyChanged += SettingsOnPropertyChanged;
}
private static void SettingsOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(Settings.BackgroundColor))
{
UpdateBackground();
}
else if (e.PropertyName == nameof(Settings.IconColor))
{
UpdateIcon();
}
}
private static void UpdateIcon()
{
var color = Settings.Instance.IconColor;
if (color == null)
{
MainWindow.TaskbarItemInfo = null;
}
else
{
var c = color.Value;
var diameter = 14;
var bitmap = new Bitmap(diameter, diameter);
var graphics = Graphics.FromImage(bitmap);
graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
var brush = new SolidBrush(Color.FromArgb(c.A, c.R, c.G, c.B));
graphics.FillEllipse(brush, 0, 0, diameter, diameter);
var bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(bitmap.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty,
BitmapSizeOptions.FromEmptyOptions());
MainWindow.TaskbarItemInfo = new();
MainWindow.TaskbarItemInfo.Overlay = bitmapSource;
}
}
private static void UpdateBackground()
{
var color = Settings.Instance.BackgroundColor ?? DefaultBackgroundColor;
Application.Current.Resources["WindowBackground"] = new SolidColorBrush(color);
}
public static void InitializeTheme()
{
UpdateIcon();
UpdateBackground();
}
}
+77
View File
@@ -0,0 +1,77 @@
using System;
using System.Drawing;
using System.IO;
using System.Windows;
using System.Windows.Forms;
using NebulaAuth.Model;
using Application = System.Windows.Application;
namespace NebulaAuth.Core;
public static class TrayManager
{
private static NotifyIcon _notifyIcon;
private static readonly Window MainWindow = Application.Current.MainWindow!;
public static bool IsEnabled => Settings.Instance.HideToTray;
public static void InitializeTray()
{
_notifyIcon = new NotifyIcon();
_notifyIcon.Text = "NebulaAuth";
Stream iconStream = Application.GetResourceStream(new Uri("pack://application:,,,/Theme/nebula lock.ico"))!.Stream;
_notifyIcon.Icon = new Icon(iconStream);
_notifyIcon.MouseDoubleClick += NotifyIcon_MouseDoubleClick;
var contextMenu = new ContextMenuStrip();
contextMenu.Items.Add("Выйти", null!, onClick: OnExitClick);
_notifyIcon.ContextMenuStrip = contextMenu;
MainWindow.StateChanged += MainWindow_StateChanged;
}
private static void OnExitClick(object? sender, EventArgs e)
{
ExitApplication();
}
private static void NotifyIcon_MouseDoubleClick(object? sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
ShowMainWindow();
}
}
private static void MainWindow_StateChanged(object? sender, EventArgs e)
{
if (MainWindow.WindowState == WindowState.Minimized)
{
HideMainWindow();
}
}
private static void ShowMainWindow()
{
MainWindow.Show();
MainWindow.WindowState = WindowState.Normal;
_notifyIcon.Visible = false;
}
private static void HideMainWindow()
{
if(IsEnabled == false) return;
_notifyIcon.Visible = true;
MainWindow.Hide();
}
private static void ExitApplication()
{
_notifyIcon.Dispose();
Application.Current.Shutdown();
}
}
+3
View File
@@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<CodingSebLocalization />
</Weavers>
+211
View File
@@ -0,0 +1,211 @@
<w:FontScaleWindow x:Class="NebulaAuth.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:w="clr-namespace:NebulaAuth.Theme"
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
xmlns:windowStyle="clr-namespace:NebulaAuth.Theme.WindowStyle"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:converters="clr-namespace:NebulaAuth.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
xmlns:viewModel="clr-namespace:NebulaAuth.ViewModel"
WindowStartupLocation="CenterScreen"
MinHeight="500" MinWidth="500" DefaultFontSize="18" ScaleCoefficient="0.4"
Title="NebulaAuth" Height="800" Width="730" Style="{StaticResource MainWindow}"
RenderOptions.BitmapScalingMode="HighQuality" Foreground="#FFF5F5F5"
FontFamily="{md:MaterialDesignFont}"
TextElement.Foreground="{DynamicResource MaterialDesignBody}"
mc:Ignorable="d"
d:DataContext="{d:DesignInstance viewModel:MainVM}"
>
<b:Interaction.Behaviors>
<windowStyle:WindowChromeRenderedBehavior />
</b:Interaction.Behaviors>
<Window.InputBindings>
<KeyBinding Command="{Binding Path=CopyMafileFromBufferCommand}"
Key="V"
Modifiers="Control"/>
</Window.InputBindings>
<Border Name="DragNDropBorder" Panel.ZIndex="3" Opacity="1" AllowDrop="True" DragDrop.DragEnter="Rectangle_DragEnter" DragLeave="Rectangle_DragLeave" Drop="Rectangle_Drop">
<md:DialogHost DialogOpened="DialogHost_DialogOpened" DialogClosed="DialogHost_DialogClosed">
<Grid ZIndex="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Grid.RowSpan="2" Opacity="0.6" Stretch="UniformToFill" RenderOptions.BitmapScalingMode="LowQuality" HorizontalAlignment="Center" VerticalAlignment="Center" Visibility="{Binding Settings.BackgroundMode, Converter={StaticResource BackgroundImageVisibleConverter}}" Source="{Binding Settings.BackgroundMode, Mode=OneWay, Converter={StaticResource BackgroundSourceConverter}}" />
<Rectangle Grid.Row="0" Grid.RowSpan="2" Opacity="0.5" Fill="#FF1F1D1D" Visibility="{Binding Settings.BackgroundMode, Converter={StaticResource BackgroundImageVisibleConverter}}" />
<Rectangle Name="DragNDropOverlay" Grid.Row="0" Grid.RowSpan="3" Panel.ZIndex="1" Fill="#242123" Opacity="0.9" Visibility="Hidden" />
<StackPanel Name="DragNDropPanel" Grid.Row="0" ZIndex="2" w:FontScaleWindow.Scale="1" w:FontScaleWindow.ResizeFont="True" Grid.RowSpan="3" Visibility="Hidden" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBlock FontWeight="Bold" Foreground="#9a65b8">Отпустите, чтобы добавить mafile</TextBlock>
<md:PackIcon Foreground="#9a65b8" HorizontalAlignment="Center" Width="36" Height="36" Kind="FileReplaceOutline" />
</StackPanel>
<ToolBarTray IsLocked="True" Grid.Row="0">
<ToolBarTray.Background>
<SolidColorBrush Opacity="0.6" Color="#FF1A1C25" />
</ToolBarTray.Background>
<ToolBar Background="#00FFFFFF" ClipToBounds="False" Style="{StaticResource MaterialDesignToolBar}" w:FontScaleWindow.Scale="0.75" w:FontScaleWindow.ResizeFont="True">
<Menu>
<MenuItem Header="{Tr MainWindow.Menu.File.Caption}">
<MenuItem Header="{Tr MainWindow.Menu.File.Import}" Command="{Binding AddMafileCommand}" />
<MenuItem Header="{Tr MainWindow.Menu.File.Remove}" Command="{Binding RemoveMafileCommand}" />
<MenuItem Header="{Tr MainWindow.Menu.File.OpenFolder}" Command="{Binding OpenMafileFolderCommand}" />
<MenuItem IsEnabled="False" Header="⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" Height="15" />
<MenuItem Header="{Tr MainWindow.Menu.File.Settings}" Command="{Binding OpenSettingsDialogCommand}" />
</MenuItem>
</Menu>
<Menu>
<MenuItem Header="{Tr MainWindow.Menu.Account.Caption}">
<MenuItem Header="{Tr MainWindow.Menu.Account.Link}" Command="{Binding LinkAccountCommand}" />
<MenuItem Header="{Tr MainWindow.Menu.Account.Unlink}" Command="{Binding RemoveAuthenticatorCommand}"/>
<MenuItem IsEnabled="False" Header="⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯" Height="15" />
<MenuItem Header="{Tr MainWindow.Menu.Account.RefreshSession}" Command="{Binding RefreshSessionCommand}" />
<MenuItem Header="{Tr MainWindow.Menu.Account.LoginAgain}" Command="{Binding LoginAgainCommand}" />
</MenuItem>
</Menu>
<Separator />
<ComboBox MinWidth="100" Margin="8,0,8,0" VerticalAlignment="Center" md:HintAssist.Hint="{Tr MainWindow.AppBar.GroupsHint}" md:TextFieldAssist.HasClearButton="True" IsEditable="True" ItemsSource="{Binding Groups}" SelectedValue="{Binding SelectedGroup}">
<FrameworkElement.Resources>
<ResourceDictionary>
<Style TargetType="{x:Type md:PackIcon}">
<Setter Property="Width" Value="15" />
<Setter Property="Height" Value="15" />
</Style>
</ResourceDictionary>
</FrameworkElement.Resources>
<UIElement.InputBindings>
<KeyBinding Key="Enter" Command="{Binding AddGroupCommand}" CommandParameter="{Binding Path=Text, RelativeSource={RelativeSource AncestorType=ComboBox}}" />
</UIElement.InputBindings>
</ComboBox>
<ComboBox MinWidth="100" Margin="8,0,8,0" VerticalAlignment="Center" md:HintAssist.Hint="{Tr MainWindow.AppBar.Proxy.ProxyHint}" md:ComboBoxAssist.ShowSelectedItem="False" SelectedItem="{Binding SelectedProxy}" ItemsSource="{Binding Proxies}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type entities:MaProxy}">
<TextBlock Text="{Binding Converter={StaticResource ProxyTextConverter}, Mode=OneWay}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
<FrameworkElement.ContextMenu>
<ContextMenu>
<MenuItem Header="{Tr MainWindow.AppBar.Proxy.ProxyOpenManagerHint}" Command="{Binding OpenProxyManagerCommand}" />
</ContextMenu>
</FrameworkElement.ContextMenu>
<UIElement.InputBindings>
<KeyBinding Key="Delete" Command="{Binding RemoveProxyCommand}" />
</UIElement.InputBindings>
</ComboBox>
<md:PackIcon Kind="StopAlert" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#FFFF0000" Margin="3" ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyAlert.MafileProxyInUse}" ToolTipService.InitialShowDelay="600">
<UIElement.Visibility>
<Binding Path="ProxyExist">
<Binding.Converter>
<converters:ValueConverterGroup>
<converters:ReverseBooleanConverter />
<BooleanToVisibilityConverter />
</converters:ValueConverterGroup>
</Binding.Converter>
</Binding>
</UIElement.Visibility>
</md:PackIcon>
<md:PackIcon Kind="StopAlert" VerticalAlignment="Center" HorizontalAlignment="Center" Foreground="#FFFFA500" Margin="3" ToolTip="{Tr MainWindow.AppBar.Proxy.ProxyAlert.DefaultInUse}" ToolTipService.InitialShowDelay="600" Visibility="{Binding IsDefaultProxy, Converter={StaticResource BooleanToVisibilityConverter}}" />
<CheckBox FontSize="14" Style="{StaticResource MaterialDesignFilterChipAccentCheckBox}" IsChecked="{Binding TradeTimerEnabled}" Content="{Tr MainWindow.AppBar.TradeTimerHint}"/>
<CheckBox FontSize="14" Style="{StaticResource MaterialDesignFilterChipAccentCheckBox}" IsChecked="{Binding MarketTimerEnabled}" Content="{Tr MainWindow.AppBar.MarketTimerHint}"/>
<TextBox w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True" md:HintAssist.Hint="{Tr Common.Abbreviations.Time.Seconds}" Margin="10,0,0,0" MinWidth="30" Style="{StaticResource MaterialDesignFloatingHintTextBox}" Text="{Binding TimerCheckSeconds, UpdateSourceTrigger=PropertyChanged, Delay=700}" PreviewTextInput="UIElement_OnPreviewTextInput" />
</ToolBar>
</ToolBarTray>
<Grid Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.4*" />
<ColumnDefinition Width="0.6*" />
</Grid.ColumnDefinitions>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True" Margin="10,15,10,15" DisplayMemberPath="AccountName" ItemsSource="{Binding MaFiles}" SelectedValue="{Binding SelectedMafile}">
<FrameworkElement.ContextMenu>
<ContextMenu>
<MenuItem Header="{Tr MainWindow.ContextMenus.Mafile.AddToGroup}" ItemsSource="{Binding Groups}">
<ItemsControl.ItemContainerStyle>
<Style BasedOn="{StaticResource MaterialDesignMenuItem}" TargetType="{x:Type MenuItem}">
<Setter Property="MenuItem.Command" Value="{Binding DataContext.AddToGroupCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
<Setter Property="MenuItem.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}}}" />
</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" md:HintAssist.Hint="{Tr MainWindow.LeftPart.SearchBoxHint}" Text="{Binding SearchText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</Grid>
<md:Card Grid.Column="1" Margin="10,10,15,10" UniformCornerRadius="15">
<Control.Background>
<SolidColorBrush Color="#FF1A1D25" Opacity="0.6" />
</Control.Background>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid Row="0">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox w:FontScaleWindow.Scale="1.1" w:FontScaleWindow.ResizeFont="True" IsReadOnly="True" HorizontalContentAlignment="Center" Background="#00FFFFFF" Style="{StaticResource MaterialDesignFilledTextBox}" Text="{Binding Code, FallbackValue=Code}" PreviewMouseDown="SteamGuard_DoubleClick" />
<Grid Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ProgressBar Margin="5" Foreground="#FF9932CC" Height="15" md:TransitionAssist.DisableTransitions="True" Value="{Binding CodeProgress}" />
</Grid>
</Grid>
<Button w:FontScaleWindow.Scale="0.7" w:FontScaleWindow.ResizeFont="True" Grid.Row="1" Style="{StaticResource MaterialDesignOutlinedButton}" Margin="10" Command="{Binding GetConfirmationsCommand}" 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>
<ListBox md:RippleAssist.IsDisabled="True" Focusable="False" Grid.Row="2" w:FontScaleWindow.Scale="0.72" w:FontScaleWindow.ResizeFont="True" Margin="10" Visibility="{Binding ConfirmationsVisible, Converter={StaticResource BooleanToVisibilityConverter}}" ItemsSource="{Binding Confirmations}" SelectionChanged="Selector_OnSelectionChanged">
<ItemsControl.ItemContainerStyle>
<Style BasedOn="{StaticResource MaterialDesignListBoxItem}" TargetType="{x:Type ListBoxItem}">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
</Style>
</ItemsControl.ItemContainerStyle>
</ListBox>
<Button Grid.Row="3" Margin="1,0,1,3" w:FontScaleWindow.Scale="0.8" w:FontScaleWindow.ResizeFont="True" Command="{Binding ConfirmLoginCommand}" Content="{Tr MainWindow.RightPart.ConfirmLogin}"/>
</Grid>
</md:Card>
</Grid>
<Border Grid.Row="2" BorderBrush="#FF696969">
<Border.Background>
<SolidColorBrush Opacity="0.5" Color="DimGray" />
</Border.Background>
<Grid>
<ToolBarPanel Orientation="Horizontal" Margin="5" w:FontScaleWindow.Scale="0.73" w:FontScaleWindow.ResizeFont="True">
<TextBlock FontWeight="Bold" Text="{Tr MainWindow.Footer.Account}" />
<TextBlock Text="{Binding SelectedMafile.AccountName}" />
<TextBlock Text="|" Margin="10,0,10,0" />
<TextBlock FontWeight="Bold" Text="{Tr MainWindow.Footer.Group}" />
<TextBlock FontWeight="Normal" Text="{Binding SelectedMafile.Group, TargetNullValue=' ', FallbackValue=' ', Mode=OneWay}" />
</ToolBarPanel>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Right" w:FontScaleWindow.Scale="0.7" w:FontScaleWindow.ResizeFont="True" Margin="0,0,10,0">
<Hyperlink NavigateUri="https://zelenka.guru/achies/" Foreground="{DynamicResource PrimaryHueMidBrush}" RequestNavigate="Hyperlink_OnRequestNavigate">by achies</Hyperlink>
</TextBlock>
</Grid>
</Border>
<md:Snackbar Grid.Row="1" FontSize="20" MessageQueue="{Binding MessageQueue}" />
</Grid>
</md:DialogHost>
</Border>
</w:FontScaleWindow>
+144
View File
@@ -0,0 +1,144 @@
using MaterialDesignThemes.Wpf;
using NebulaAuth.Core;
using NebulaAuth.Model;
using NebulaAuth.View.Dialogs;
using NebulaAuth.ViewModel;
using NebulaAuth.ViewModel.Other;
using System;
using System.Diagnostics;
using System.Reflection;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Navigation;
using System.Windows.Threading;
namespace NebulaAuth;
public partial class MainWindow
{
private static string CodeCopiedString => LocManager.GetOrDefault("CodeCopied", "MainWindow", "CodeCopied");
public MainWindow()
{
InitializeComponent();
base.DataContext = new MainVM();
Application.Current.MainWindow = this;
TrayManager.InitializeTray();
ThemeManager.InitializeTheme();
base.Title = base.Title + " " + Assembly.GetExecutingAssembly().GetName().Version?.ToString(3);
this.Loaded += OnApplicationStarted;
}
private async void OnApplicationStarted(object? sender, EventArgs e)
{
if (Settings.Instance.IsPasswordSet == false) return;
await Dispatcher.BeginInvoke(ShowSetPasswordDialog, DispatcherPriority.ContextIdle);
}
private async Task ShowSetPasswordDialog()
{
var vm = new SetEncryptPasswordVM();
var dialog = new SetCryptPasswordDialog()
{
DataContext = vm
};
var result = await DialogHost.Show(dialog);
var pass = vm.Password;
if (result is true && string.IsNullOrWhiteSpace(pass) == false)
{
PHandler.SetPassword(pass);
}
}
private void Selector_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
var lb = (ListBox)sender;
lb.SelectedValue = null;
}
private async void SteamGuard_DoubleClick(object sender, MouseButtonEventArgs e)
{
var tb = (TextBox)sender;
if (tb.Text == CodeCopiedString) return;
var code = tb.Text;
try
{
Clipboard.SetText(code);
}
catch (Exception ex)
{
Shell.Logger.Error(ex);
return;
}
tb.Text = CodeCopiedString;
await Task.Delay(200);
if (tb.Text == CodeCopiedString)
{
tb.Text = code;
}
}
#region Dran'n'Drop
private void UIElement_OnPreviewTextInput(object sender, TextCompositionEventArgs e)
{
if (int.TryParse(e.Text, out _) == false) e.Handled = true;
}
private void Rectangle_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop) == false)
{
e.Handled = true;
return;
}
DragNDropPanel.Visibility = Visibility.Visible;
DragNDropOverlay.Visibility = Visibility.Visible;
}
private async void Rectangle_Drop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop) == false) return;
string[] filePaths = (string[])e.Data.GetData(DataFormats.FileDrop)!;
if (filePaths.Length == 0) return;
if (this.DataContext is MainVM mainVm)
{
await mainVm.AddMafile(filePaths);
}
DragNDropPanel.Visibility = Visibility.Hidden;
DragNDropOverlay.Visibility = Visibility.Hidden;
}
private void Rectangle_DragLeave(object sender, DragEventArgs e)
{
DragNDropPanel.Visibility = Visibility.Hidden;
DragNDropOverlay.Visibility = Visibility.Hidden;
}
private void DialogHost_DialogOpened(object sender, DialogOpenedEventArgs eventArgs)
{
DragNDropBorder.AllowDrop = false;
}
private void DialogHost_DialogClosed(object sender, DialogClosedEventArgs eventArgs)
{
DragNDropBorder.AllowDrop = true;
}
#endregion
private void Hyperlink_OnRequestNavigate(object sender, RequestNavigateEventArgs e)
{
Process.Start(new ProcessStartInfo(e.Uri.ToString())
{
UseShellExecute = true
});
}
}
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using NebulaAuth.Model.Entities;
namespace NebulaAuth.Model.Comparers;
public class ProxyComparer : IEqualityComparer<MaProxy>
{
public bool Equals(MaProxy? x, MaProxy? y)
{
return Equal(x, y);
}
public static bool Equal(MaProxy? x, MaProxy? y)
{
if (ReferenceEquals(x, y)) return true;
if (ReferenceEquals(x, null)) return false;
if (ReferenceEquals(y, null)) return false;
return x.Id == y.Id && x.Data.Equals(y.Data);
}
public int GetHashCode(MaProxy obj)
{
return HashCode.Combine(obj.Id, obj.Data);
}
}
@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using AchiesUtilities.Web.Proxy;
namespace NebulaAuth.Model.Comparers;
public class ProxyDataComparer : IEqualityComparer<ProxyData>
{
public bool Equals(ProxyData? x, ProxyData? y)
{
return Equal(x, y);
}
public static bool Equal(ProxyData? x, ProxyData? y)
{
if (ReferenceEquals(x, y)) return true;
if (ReferenceEquals(x, null)) return false;
if (ReferenceEquals(y, null)) return false;
return x.Address == y.Address
&& x.Port == y.Port
&& x.Username == y.Username
&& x.Password == y.Password;
}
public int GetHashCode(ProxyData obj)
{
return HashCode.Combine(obj.Address, obj.Port, obj.Username, obj.Password);
}
}
@@ -0,0 +1,21 @@
using System.Diagnostics.CodeAnalysis;
namespace NebulaAuth.Model.Entities;
public class LoginConfirmationResult
{
[MemberNotNullWhen(false, nameof(Error))]
public bool Success { get; set; }
public string IP { get; set; }
public string Country { get; set; }
public LoginConfirmationError? Error { get; set; }
}
public enum LoginConfirmationError
{
NoRequests,
MoreThanOneRequest
}
+32
View File
@@ -0,0 +1,32 @@
using System;
using AchiesUtilities.Web.Proxy;
using NebulaAuth.Model.Comparers;
namespace NebulaAuth.Model.Entities;
public class MaProxy
{
public int Id { get; }
public ProxyData Data { get; }
public MaProxy(int id, ProxyData data)
{
Id = id;
Data = data;
}
public override bool Equals(object? obj)
{
return obj is MaProxy p && ProxyComparer.Equal(this, p);
}
protected bool Equals(MaProxy other)
{
return Id == other.Id && Data.Equals(other.Data);
}
public override int GetHashCode()
{
return HashCode.Combine(Id, Data);
}
}
+38
View File
@@ -0,0 +1,38 @@
using SteamLib;
namespace NebulaAuth.Model.Entities;
public class Mafile : MobileDataExtended
{
public MaProxy? Proxy { get; set; }
public string? Group { get; set; }
public string? Password { get; set; }
public static Mafile FromMobileDataExtended(MobileDataExtended data)
{
return new Mafile
{
AccountName = data.AccountName,
DeviceId = data.DeviceId,
IdentitySecret = data.IdentitySecret,
RevocationCode = data.RevocationCode,
Secret1 = data.Secret1,
SerialNumber = data.SerialNumber,
SessionData = data.SessionData,
SharedSecret = data.SharedSecret,
ServerTime = data.ServerTime,
TokenGid = data.TokenGid,
Uri = data.Uri,
};
}
public static Mafile FromMobileDataExtended(MobileDataExtended data, MaProxy? proxy, string? group, string? password)
{
var result = FromMobileDataExtended(data);
result.Proxy = proxy;
result.Group = group;
result.Password = password;
return result;
}
}
@@ -0,0 +1,16 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using SteamLib.SteamMobile.Confirmations;
namespace NebulaAuth.Model.Entities;
public class MarketMultiConfirmation : Confirmation
{
public ObservableCollection<MarketConfirmation> Confirmations { get; }
public MarketMultiConfirmation(IEnumerable<MarketConfirmation> confirmations) : base(0, 0, 0, 0, ConfirmationType.Unknown, "")
{
Confirmations = new(confirmations);
Time = Confirmations.FirstOrDefault()?.Time ?? default;
}
}
@@ -0,0 +1,26 @@
using System;
using System.Runtime.Serialization;
namespace NebulaAuth.Model.Exceptions;
[Serializable]
public class CantAlignTimeException : Exception
{
public CantAlignTimeException()
{
}
public CantAlignTimeException(string message) : base(message)
{
}
public CantAlignTimeException(string message, Exception inner) : base(message, inner)
{
}
protected CantAlignTimeException(
SerializationInfo info,
StreamingContext context) : base(info, context)
{
}
}
+236
View File
@@ -0,0 +1,236 @@
using AchiesUtilities.Web.Proxy;
using NebulaAuth.Model.Entities;
using SteamLib.Account;
using SteamLib.Api.Mobile;
using SteamLib.Authentication;
using SteamLib.Authentication.LoginV2;
using SteamLib.Core.Interfaces;
using SteamLib.Exceptions;
using SteamLib.ProtoCore;
using SteamLib.ProtoCore.Services;
using SteamLib.SteamMobile;
using SteamLib.SteamMobile.Confirmations;
using SteamLib.Web;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using SteamLib.Api;
using SteamLib.Core.Enums;
namespace NebulaAuth.Model;
public static class MaClient
{
private static HttpClientHandler ClientHandler { get; }
private static HttpClient Client { get; }
private static DynamicProxy Proxy { get; }
public static ProxyData? DefaultProxy { get; set; }
static MaClient()
{
Proxy = new DynamicProxy(null);
var pair = ClientBuilder.BuildMobileClient(Proxy, null);
Client = pair.Client;
ClientHandler = pair.Handler;
}
public static void ClearCookies()
{
foreach (Cookie allCookie in ClientHandler.CookieContainer.GetAllCookies())
{
allCookie.Expired = true;
}
}
public static void SetAccount(Mafile? account)
{
ClearCookies();
if (account != null)
{
if (account.SessionData != null)
{
ClientHandler.CookieContainer.SetSteamMobileCookies(account.SessionData);
}
else
{
ClientHandler.CookieContainer.ClearSteamCookies();
ClientHandler.CookieContainer.AddMinimalMobileCookies();
AdmissionHelper.TransferCommunityCookies(ClientHandler.CookieContainer);
}
Proxy.SetData(account.Proxy?.Data);
}
}
public static Task<IEnumerable<Confirmation>> GetConfirmations(Mafile mafile)
{
ValidateMafile(mafile);
SetProxy(mafile);
return SteamMobileConfirmationsApi.GetConfirmations(Client, mafile, mafile.SessionData!.SteamId.Steam64);
}
public static async Task LoginAgain(Mafile mafile, string password, bool savePassword, ICaptchaResolver? resolver)
{
SetProxy(mafile);
var sgGenerator = new SteamGuardCodeGenerator(mafile.SharedSecret);
var options = new LoginV2ExecutorOptions(LoginV2Executor.NullConsumer, Client)
{
Logger = Shell.ExtensionsLogger,
SteamGuardProvider = sgGenerator,
DeviceDetails = LoginV2ExecutorOptions.GetMobileDefaultDevice(),
WebsiteId = "Mobile"
};
ClientHandler.CookieContainer.ClearMobileSessionCookies();
var result = await LoginV2Executor.DoLogin(options, mafile.AccountName, password);
AdmissionHelper.TransferCommunityCookies(ClientHandler.CookieContainer);
mafile.SessionData = (MobileSessionData)result;
if(PHandler.IsPasswordSet)
mafile.Password = (savePassword ? PHandler.Encrypt(password) : null);
Storage.UpdateMafile(mafile);
}
public static async Task RefreshSession(Mafile mafile)
{
ValidateMafile(mafile, true);
SetProxy(mafile);
var token = mafile.SessionData!.GetMobileToken();
if (token == null || token.Value.IsExpired)
{
var sessionToken = await SteamMobileApi.RefreshJwt(Client, mafile.SessionData!.RefreshToken.Token, mafile.SessionData.SteamId.Steam64);
var newToken = SteamTokenHelper.Parse(sessionToken);
mafile.SessionData.SetMobileToken(newToken);
}
var communityToken = mafile.SessionData!.GetToken(SteamDomain.Community);
if (communityToken == null || communityToken.Value.IsExpired)
{
var communityTokenString = await SteamGlobalApi.RefreshJwt(Client, SteamDomain.Community);
var newToken = SteamTokenHelper.Parse(communityTokenString);
mafile.SessionData.SetToken(SteamDomain.Community, newToken);
}
Storage.UpdateMafile(mafile);
ClientHandler.CookieContainer.SetSteamMobileCookies(mafile.SessionData);
}
public static Task<bool> SendConfirmation(Mafile mafile, Confirmation confirmation, bool confirm)
{
ValidateMafile(mafile);
SetProxy(mafile);
return SteamMobileConfirmationsApi.SendConfirmation(Client, confirmation, mafile.SessionData!.SteamId.Steam64, mafile, confirm);
}
public static Task<bool> SendMultipleConfirmation(Mafile mafile, IEnumerable<Confirmation> confirmations, bool confirm)
{
var enumerable = confirmations.ToList();
if (!enumerable.Any())
{
return Task.FromResult(result: false);
}
ValidateMafile(mafile);
SetProxy(mafile);
return SteamMobileConfirmationsApi.SendMultipleConfirmations(Client, enumerable, mafile.SessionData!.SteamId.Steam64, mafile, confirm);
}
public static Task<RemoveAuthenticator_Response> RemoveAuthenticator(Mafile mafile)
{
ValidateMafile(mafile);
SetProxy(mafile);
if (mafile.RevocationCode == null)
{
throw new InvalidOperationException("This mafile does not have R-Code");
}
var token = mafile.SessionData!.GetMobileToken()!;
return SteamMobileApi.RemoveAuthenticator(Client, token.Value.Token, mafile.RevocationCode);
}
private static void SetProxy(Mafile mafile)
{
Proxy.SetData(mafile.Proxy?.Data ?? DefaultProxy);
}
private static void ValidateMafile(Mafile mafile, bool ignoreAccessToken = false)
{
if (mafile.SessionData == null) throw new SessionInvalidException();
if (mafile.SessionData.RefreshToken.IsExpired)
throw new SessionExpiredException();
if (ignoreAccessToken == false)
{
var access = mafile.SessionData.GetMobileToken();
if (access == null || access.Value.IsExpired)
throw new SessionExpiredException();
}
}
public static async Task<LoginConfirmationResult> ConfirmLoginRequest(Mafile mafile)
{
if (mafile.SessionData == null)
{
throw new SessionExpiredException();
}
ValidateMafile(mafile);
var token = mafile.SessionData.GetMobileToken()!.Value;
SetProxy(mafile);
var uri = "https://api.steampowered.com/IAuthenticationService/GetAuthSessionsForAccount/v1?access_token=" + token.Token;
GetAuthSessionsForAccount_Response getsess;
try
{
getsess = await Client.GetProto<GetAuthSessionsForAccount_Response>(uri, new EmptyMessage());
}
catch (HttpRequestException ex)
when (ex.StatusCode == HttpStatusCode.Unauthorized)
{
throw new SessionExpiredException(string.Empty, ex);
}
if (getsess.ClientIds.Count == 0)
{
return new LoginConfirmationResult
{
Error = LoginConfirmationError.NoRequests
};
}
if (getsess.ClientIds.Count > 1)
{
return new LoginConfirmationResult
{
Error = LoginConfirmationError.MoreThanOneRequest
};
}
var clientId = getsess.ClientIds.Single();
var infoUri = "https://api.steampowered.com/IAuthenticationService/GetAuthSessionInfo/v1?access_token=" + token.Token;
var infoReq = new GetAuthSessionInfo_Request
{
ClientId = clientId
};
var infoResp = await Client.PostProto<GetAuthSessionInfo_Response>(infoUri, infoReq);
var updateUri = "https://api.steampowered.com/IAuthenticationService/UpdateAuthSessionWithMobileConfirmation/v1?access_token=" + token.Token;
var updateReq = new UpdateAuthSessionWithMobileConfirmation_Request
{
ClientId = clientId,
Confirm = true,
Persistence = 1,
Steamid = mafile.SessionData.SteamId.Steam64.ToUlong(),
Version = 1
};
updateReq.ComputeSignature(mafile.SharedSecret);
await Client.PostProtoEnsureSuccess(updateUri, updateReq);
return new LoginConfirmationResult
{
Country = infoResp.Country,
IP = infoResp.IP,
Success = true
};
}
}
+79
View File
@@ -0,0 +1,79 @@
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
namespace NebulaAuth.Model;
public static class PHandler //RETHINK: Use SecureString?
{
public static bool IsPasswordSet => _k.Length > 0;
private static byte[] _k = Array.Empty<byte>();
public static void SetPassword(string? password)
{
if (string.IsNullOrWhiteSpace(password))
{
_k = Array.Empty<byte>();
return;
}
var keyBytes = Encoding.UTF8.GetBytes(password);
_k = SHA256.HashData(keyBytes);
}
public static string Encrypt(string plainText)
{
if (_k.Length == 0) throw new Exception("Password not set");
byte[] encryptedBytes;
using (var aes = Aes.Create())
{
aes.Key = _k;
aes.IV = new byte[16];
var encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
var plainTextBytes = Encoding.UTF8.GetBytes(plainText);
using (var memoryStream = new MemoryStream())
{
using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
{
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length);
cryptoStream.FlushFinalBlock();
}
encryptedBytes = memoryStream.ToArray();
}
}
return Convert.ToBase64String(encryptedBytes);
}
public static string Decrypt(string encryptedText)
{
if (_k.Length == 0) throw new Exception("Password not set");
var encryptedBytes = Convert.FromBase64String(encryptedText);
string decryptedText = null;
using var aes = Aes.Create();
var keyBytes = _k;
var iv = new byte[aes.IV.Length];
aes.Key = keyBytes;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
using var decryptor = aes.CreateDecryptor(aes.Key, iv);
using var ms = new MemoryStream(encryptedBytes);
using var cs = new CryptoStream(ms, decryptor, CryptoStreamMode.Read);
using var reader = new StreamReader(cs);
decryptedText = reader.ReadToEnd();
return decryptedText;
}
}
+107
View File
@@ -0,0 +1,107 @@
using System;
using System.IO;
using AchiesUtilities.Collections;
using AchiesUtilities.Web.Proxy;
using System.Linq;
using NebulaAuth.Core;
using Newtonsoft.Json;
namespace NebulaAuth.Model;
public static class ProxyStorage
{
public const string FORMAT = ADDRESS_FORMAT + ":{USER}:{PASS}";
public const string ADDRESS_FORMAT = "{IP}:{PORT}";
public static ObservableDictionary<int, ProxyData> Proxies { get; } = new();
static ProxyStorage()
{
if(File.Exists("proxies.json") == false)
return;
try
{
var json = File.ReadAllText("proxies.json");
var proxies = JsonConvert.DeserializeObject<Proxies>(json) ?? throw new NullReferenceException();
Proxies = proxies.ProxiesData;
Proxies = new ObservableDictionary<int, ProxyData>(
Proxies.OrderBy(p => p.Key)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value));
if (proxies.DefaultProxy != null)
{
MaClient.DefaultProxy = Proxies[proxies.DefaultProxy.Value];
}
}
catch(Exception ex)
{
SnackbarController.SendSnackbar("Ошибка при загрузке прокси");
SnackbarController.SendSnackbar(ex.Message);
}
}
public static void SetProxy(int? id, ProxyData proxyData)
{
if (id == null)
{
if (Proxies.Any() == false)
{
id = 0;
}
else
{
id = Proxies.Keys.Max() + 1;
}
}
Proxies[id] = proxyData;
Save();
}
public static void RemoveProxy(int id)
{
Proxies.Remove(id);
Save();
}
public static bool CompareProxy(ProxyData proxyData1, ProxyData proxyData2)
{
return proxyData1.ToString(FORMAT) == proxyData2.ToString(FORMAT);
}
public static void Save()
{
var p = Create();
var json = JsonConvert.SerializeObject(p, Formatting.Indented);
File.WriteAllText("proxies.json", json);
}
private static Proxies Create()
{
int? def = null;
if (MaClient.DefaultProxy != null)
{
var search = Proxies.FirstOrDefault(p => p.Value.Equals(MaClient.DefaultProxy));
if (search.Value != null!)
{
def = search.Key;
}
}
return new Proxies
{
ProxiesData = Proxies,
DefaultProxy = def
};
}
}
public class Proxies
{
public ObservableDictionary<int, ProxyData> ProxiesData { get; set; }
public int? DefaultProxy { get; set; }
}
+94
View File
@@ -0,0 +1,94 @@
using NebulaAuth.Core;
using NebulaAuth.Model.Entities;
using SteamLib.Exceptions;
using System;
using System.Threading.Tasks;
namespace NebulaAuth.Model;
public static class SessionHandler
{
public static event EventHandler? LoginStarted;
public static event EventHandler? LoginCompleted;
public static async Task<T> Handle<T>(Func<Task<T>> func, Mafile mafile)
{
string? password = null;
try
{
if (!string.IsNullOrWhiteSpace(mafile.Password))
{
password = PHandler.Decrypt(mafile.Password);
}
}
catch
{
// ignored
}
var refreshed = false;
try
{
return await func();
}
catch (SessionExpiredException) when (mafile.SessionData is not { RefreshToken.IsExpired: true})
{
refreshed = await TryRefresh(mafile);
}
catch (SessionInvalidException)
when (password != null)
{
}
if (refreshed)
{
Shell.Logger.Debug("Token on mafile {name} {steamid} refreshed", mafile.AccountName,
mafile.SessionData?.SteamId);
try
{
return await func();
}
catch (Exception ex3)
when (password != null && ex3 is SessionExpiredException or SessionInvalidException)
{
}
}
if (password == null)
{
throw new SessionInvalidException();
}
try
{
LoginStarted?.Invoke(null, EventArgs.Empty);
await MaClient.LoginAgain(mafile, password, savePassword: true, null);
Shell.Logger.Debug("Mafile {name} {steamid} succesfully auto-relogined", mafile.AccountName,
mafile.SessionData?.SteamId);
}
finally
{
LoginCompleted?.Invoke(null, EventArgs.Empty);
}
return await func();
}
private static async Task<bool> TryRefresh(Mafile mafile)
{
try
{
await MaClient.RefreshSession(mafile);
SnackbarController.SendSnackbar("Сессия была обновлена автоматически");
return true;
}
catch (SessionInvalidException)
{
Shell.Logger.Debug("Token on mafile {name} {steamid} not refreshed", mafile.AccountName, mafile.SessionData?.SteamId);
return false;
}
}
}
+70
View File
@@ -0,0 +1,70 @@
using NebulaAuth.Core;
using Newtonsoft.Json;
using System;
using System.ComponentModel;
using System.IO;
using System.Windows.Media;
using CommunityToolkit.Mvvm.ComponentModel;
namespace NebulaAuth.Model;
public partial class Settings : ObservableObject
{
#region Properties
[ObservableProperty] private bool _disableTimersOnChange = true;
[ObservableProperty] private BackgroundMode _backgroundMode = BackgroundMode.Default;
[ObservableProperty] private bool _hideToTray;
[ObservableProperty] private int _timerSeconds = 60;
[ObservableProperty] private Color? _backgroundColor;
[ObservableProperty] private Color? _iconColor;
[ObservableProperty] private bool _isPasswordSet;
[ObservableProperty] private LocalizationLanguage _language = LocalizationLanguage.English;
[ObservableProperty] private bool _legacyMode = true;
[ObservableProperty] private bool _allowAutoUpdate;
#endregion
public static Settings Instance { get; }
static Settings()
{
if (File.Exists("settings.json") == false)
{
Instance = new();
Instance.PropertyChanged += SettingsOnPropertyChanged;
return;
}
try
{
var json = File.ReadAllText("settings.json");
var settings = JsonConvert.DeserializeObject<Settings>(json) ?? throw new NullReferenceException();
Instance = settings;
}
catch (Exception ex)
{
SnackbarController.SendSnackbar("Ошибка при загрузке настроек. Настройки были сброшены");
SnackbarController.SendSnackbar(ex.Message);
Instance = new();
}
Instance.PropertyChanged += SettingsOnPropertyChanged;
}
private static void SettingsOnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
Save();
}
public static void Save()
{
var json = JsonConvert.SerializeObject(Instance, Formatting.Indented);
File.WriteAllText("settings.json", json);
}
}
public enum BackgroundMode
{
Default, Custom, Color
}
+41
View File
@@ -0,0 +1,41 @@
using NebulaAuth.Model.Exceptions;
using NLog;
using SteamLib.Core;
using SteamLib.SteamMobile;
using System;
using Microsoft.Extensions.Logging;
using ILogger = Microsoft.Extensions.Logging.ILogger;
namespace NebulaAuth.Model;
public static class Shell
{
public static Logger Logger { get; } = LogManager.GetLogger("Logger");
public static ILogger ExtensionsLogger { get; private set; } = null!;
public static void Initialize()
{
var lp = new NLog.Extensions.Logging.NLogLoggerProvider();
var logger = lp.CreateLogger("SteamLib");
HealthMonitor.FatalLogger = logger;
AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
var loggerFactory = new NLog.Extensions.Logging.NLogLoggerFactory();
ExtensionsLogger = loggerFactory.CreateLogger("Logger");
try
{
TimeAligner.AlignTime();
}
catch (Exception ex)
{
throw new CantAlignTimeException("", ex);
}
ExtensionsLogger.LogDebug("Application started");
}
private static void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Logger.Fatal((Exception)e.ExceptionObject);
}
}
+210
View File
@@ -0,0 +1,210 @@
using NebulaAuth.Model.Entities;
using Newtonsoft.Json.Linq;
using SteamLib;
using SteamLib.SteamMobile;
using SteamLib.Utility.MaFiles;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using SteamLib.Exceptions;
namespace NebulaAuth.Model;
public static class Storage
{
public const string MAFILE_F = "maFiles";
public const string REMOVED_F = "maFiles_removed";
public static string MafileFolder { get; } = Path.GetFullPath(MAFILE_F);
public static string RemovedMafileFolder { get; } = Path.GetFullPath(REMOVED_F);
public static ObservableCollection<Mafile> MaFiles { get; } = new();
static Storage()
{
if (Directory.Exists(MafileFolder) == false)
{
Directory.CreateDirectory(MafileFolder);
}
if (Directory.Exists(RemovedMafileFolder) == false)
{
Directory.CreateDirectory(RemovedMafileFolder);
}
var files = Directory.GetFiles(MafileFolder);
foreach (var file in files)
{
if (Path.GetExtension(file).Equals(".mafile", StringComparison.InvariantCultureIgnoreCase) == false) continue;
try
{
var data = ReadMafile(file);
MaFiles.Add(data);
}
catch (Exception ex)
{
Shell.Logger.Error(ex, "Can't load mafile {file}", Path.GetFileName(file));
}
}
MaFiles = new ObservableCollection<Mafile>(MaFiles.OrderBy(m => m.AccountName));
}
public static void AddNewMafile(string path, bool overwrite)
{
Mafile data;
try
{
data = ReadMafile(path);
}
catch (Exception ex)
{
Shell.Logger.Warn(ex, "Can't load mafile");
throw new FormatException("File data is not valid", ex);
}
if (string.IsNullOrWhiteSpace(data.AccountName))
throw new FormatException("File data is not valid. Missing AccountName");
try
{
var code = SteamGuardCodeGenerator.GenerateCode(data.SharedSecret);
}
catch (Exception ex)
{
throw new FormatException("Can't generate code on this mafile", ex);
}
if (data.SessionData == null)
throw new SessionInvalidException("File data is not valid. Missing SessionData")
{
Data = { { "mafile", data } }
};
if (overwrite == false && File.Exists(GetMafilePath(data)))
{
throw new IOException("File already exist and overwrite is False");
}
SaveMafile(data);
}
public static Mafile ReadMafile(string path)
{
var str = File.ReadAllText(path);
var mafile = MafileSerializer.Deserialize(str, out var mafileData);
if (mafileData.IsExtended == false)
throw new FormatException("Mafile is not extended data");
var props = mafileData.UnusedProperties ?? new Dictionary<string, JProperty>();
var proxy = GetPropertyValue<MaProxy>("Proxy", props);
var group = GetPropertyValue<string>("Group", props);
var password = GetPropertyValue<string>("Password", props);
return Mafile.FromMobileDataExtended((MobileDataExtended)mafile, proxy, group, password);
}
public static string SerializeMafile(Mafile data)
{
if (Settings.Instance.LegacyMode)
{
var props = new Dictionary<string, object?>
{
{nameof(Mafile.Proxy), data.Proxy},
{nameof(Mafile.Group), data.Group},
{nameof(Mafile.Password), data.Password}
};
return MafileSerializer.SerializeLegacy(data, Formatting.Indented, props);
}
else
{
return MafileSerializer.Serialize(data);
}
}
private static T? GetPropertyValue<T>(string name, Dictionary<string, JProperty> dictionary)
{
if (dictionary.TryGetValue(name, out var prop) == false) return default;
var value = prop.Value;
try
{
return value.ToObject<T>();
}
catch (Exception ex)
{
Shell.Logger.Warn(ex, "Can't deserialize property {name}", name);
return default;
}
}
public static void SaveMafile(Mafile data)
{
var path = GetMafilePath(data);
var str = SerializeMafile(data);
File.WriteAllText(Path.GetFullPath(path), str);
var existed = MaFiles.SingleOrDefault(m => m.AccountName == data.AccountName);
if (existed != null)
{
var index = MaFiles.IndexOf(existed);
MaFiles[index] = data;
}
else
{
MaFiles.Add(data);
}
}
public static void UpdateMafile(Mafile data)
{
var path = GetMafilePath(data);
var str = SerializeMafile(data);
File.WriteAllText(Path.GetFullPath(path), str);
}
public static void RemoveMafile(Mafile data)
{
var path = GetMafilePath(data);
if (File.Exists(path))
{
File.Delete(path);
}
MaFiles.Remove(data);
}
public static void MoveToRemoved(Mafile data)
{
var path = GetMafilePath(data);
var copyPath = Path.Combine(REMOVED_F, data.SessionData!.SteamId + ".mafile");
var copyPathCompleted = copyPath;
var i = 0;
while (File.Exists(copyPathCompleted))
{
i++;
copyPathCompleted = copyPath + $" ({i})";
}
File.Copy(path, copyPathCompleted, false);
File.Delete(path);
MaFiles.Remove(data);
}
private static string GetMafilePath(Mafile data)
{
if (data.SessionData == null)
throw new NullReferenceException("SessionData was null can't retrieve SteamId");
var fileName = data.SessionData.SteamId + ".mafile";
return Path.Combine(MafileFolder, fileName);
}
}
+26
View File
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
throwExceptions="true">
<targets>
<target xsi:type="File" name="File" fileName="log.log"
deleteOldFileOnStartup="true"
>
<layout xsi:type='CompoundLayout'>
<layout xsi:type="JsonLayout" includeEventProperties="true" IncludeScopeProperties="true">
<attribute name="time" layout="${longdate}" />
<attribute name="level" layout="${level:upperCase=true}"/>
<attribute name="message" layout="${message}" />
<attribute name="exception" layout="${exception:format=@}" encode="false"/>
</layout>
<layout type='SimpleLayout' text="," />
</layout>
</target>
</targets>
<rules>
<logger name="*" minlevel="Trace" writeTo="File" />
</rules>
</nlog>
+58
View File
@@ -0,0 +1,58 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<UseWPF>true</UseWPF>
<UseWindowsForms>true</UseWindowsForms>
<LangVersion>latest</LangVersion>
<SatelliteResourceLanguages>ru</SatelliteResourceLanguages>
<ApplicationIcon>Theme\nebula lock.ico</ApplicationIcon>
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
<AssemblyVersion>1.4.0</AssemblyVersion>
</PropertyGroup>
<ItemGroup>
<None Remove="Theme\Background.jpg" />
<None Remove="Theme\nebula lock.ico" />
<None Remove="Theme\nebula.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.1.0" />
<PackageReference Include="MaterialDesignColors" Version="2.1.4" />
<PackageReference Include="MaterialDesignExtensions" Version="3.3.0" />
<PackageReference Include="MaterialDesignThemes" Version="4.9.0" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
<PackageReference Include="NLog" Version="5.1.2" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.2.2" />
</ItemGroup>
<ItemGroup>
<Resource Include="Theme\Background.jpg">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Resource>
<Resource Include="Theme\nebula lock.ico" />
<Resource Include="Theme\nebula.ico" />
</ItemGroup>
<ItemGroup>
<Folder Include="Model\Utility\" />
<Folder Include="Model\Exceptions\" />
<Folder Include="Theme\Fonts\Новая папка\" />
<Folder Include="ViewModel\Other\" />
<Folder Include="Utility\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\SteamLib\SteamLib\SteamLib.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="NLog.config">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
+64
View File
@@ -0,0 +1,64 @@
<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\nebula lock.ico</ApplicationIcon>
<SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion>
<AssemblyVersion>1.4.3</AssemblyVersion>
</PropertyGroup>
<ItemGroup>
<None Remove="Theme\Background.jpg" />
<None Remove="Theme\nebula lock.ico" />
<None Remove="Theme\nebula.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Autoupdater.NET.Official" Version="1.8.4" />
<PackageReference Include="CodingSeb.Localization.JsonFileLoader" Version="1.3.0" />
<PackageReference Include="CodingSeb.Localization.WPF" Version="1.3.0" />
<PackageReference Include="CodingSebLocalization.Fody" Version="1.3.0" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.2.2" />
<PackageReference Include="MaterialDesignColors" Version="2.1.4" />
<PackageReference Include="MaterialDesignExtensions" Version="3.3.0" />
<PackageReference Include="MaterialDesignThemes" Version="4.9.0" />
<PackageReference Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.77" />
<PackageReference Include="NLog" Version="5.2.8" />
<PackageReference Include="NLog.Extensions.Logging" Version="5.3.8" />
</ItemGroup>
<ItemGroup>
<Resource Include="Theme\Background.jpg">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</Resource>
<Resource Include="Theme\nebula lock.ico" />
<Resource Include="Theme\nebula.ico" />
</ItemGroup>
<ItemGroup>
<Folder Include="Model\Exceptions\" />
<Folder Include="ViewModel\Other\" />
<Folder Include="Utility\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SteamLibForked\SteamLibForked.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="NLog.config">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="localization.loc.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
Binary file not shown.

After

Width:  |  Height:  |  Size: 713 KiB

+9
View File
@@ -0,0 +1,9 @@
<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>
<SolidColorBrush x:Key="PrimaryHueMidBrush" Color="#FF9056B1"></SolidColorBrush>
<SolidColorBrush x:Key="PrimaryHueLightBrush" Color="#FF9C70B5"></SolidColorBrush>
<SolidColorBrush x:Key="PrimaryHueDarkForegroundBrush" Color="#FF851BC1"></SolidColorBrush>
</ResourceDictionary>
+200
View File
@@ -0,0 +1,200 @@
using System;
using System.Collections.Generic;
using System.Windows;
namespace NebulaAuth.Theme;
public partial class FontScaleWindow : Window
{
private readonly Func<double, double, double> _diagonal = (h, w) => Math.Sqrt(h * h + w * w);
private double _currentDiagonal;
private double _defaultDiagonal = 1;
private readonly HashSet<FrameworkElement> _cachedObjects = new();
private bool _loaded = false;
public FontScaleWindow()
{
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
var w = (FontScaleWindow)sender;
w._defaultDiagonal = _diagonal(MinHeight, MinWidth);
w.SizeChanged += OnSizeChanged;
w.Loaded -= OnLoaded;
_loaded = true;
w.Width += 1;
w.Width -= 1;
}
//<-------------------Window-------------------->
public double DefaultFontSize
{
get => (double)GetValue(DefaultFontSizeProperty);
set => SetValue(DefaultFontSizeProperty, value);
}
public double ScaleCoefficient
{
get => (double)GetValue(ScaleCoefficientProperty);
set => SetValue(ScaleCoefficientProperty, value);
}
// Using a DependencyProperty as the backing store for ScaleCoefficient. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ScaleCoefficientProperty =
DependencyProperty.Register("ScaleCoefficient", typeof(double), typeof(FontScaleWindow), new PropertyMetadata(1d));
public static readonly DependencyProperty DefaultFontSizeProperty =
DependencyProperty.Register("DefaultFontSize", typeof(double), typeof(FontScaleWindow), new PropertyMetadata(20d));
private static readonly DependencyPropertyKey ParentScaleWindowKey
= DependencyProperty.RegisterAttachedReadOnly(
"ParentScaleWindow",
typeof(FontScaleWindow), typeof(FontScaleWindow),
new FrameworkPropertyMetadata(default(FontScaleWindow),
FrameworkPropertyMetadataOptions.None));
public static readonly DependencyProperty ParentScaleWindowProperty
= ParentScaleWindowKey.DependencyProperty;
private static FontScaleWindow? GetParentScaleWindow(DependencyObject element) =>
(FontScaleWindow)element.GetValue(ParentScaleWindowProperty);
private static void SetParentScaleWindow(DependencyObject element, FontScaleWindow value) =>
element.SetValue(ParentScaleWindowKey, value);
public static readonly DependencyProperty ResizeFontProperty = DependencyProperty.RegisterAttached(
"ResizeFont", typeof(bool), typeof(FontScaleWindow), new FrameworkPropertyMetadata(false, ResizeCallBack));
public static void SetResizeFont(DependencyObject element, bool value)
=> element.SetValue(ResizeFontProperty, value);
public static bool GetResizeFont(DependencyObject element)
=> (bool)element.GetValue(ResizeFontProperty);
private static bool SetWindow(FrameworkElement el)
{
var w = GetWindow(el);
if (w is not FontScaleWindow fsWindow) return false;
SetParentScaleWindow(el, fsWindow);
if (fsWindow._cachedObjects.Contains(el)) return true;
fsWindow._cachedObjects.Add(el);
el.Unloaded += ObjOnUnloaded;
return true;
}
private static void ObjOnLoaded(object sender, RoutedEventArgs e)
{
var el = (FrameworkElement)sender;
if (GetParentScaleWindow(el) == null)
{
if (!SetWindow(el))
{
el.Loaded -= ObjOnLoaded;
return;
}
}
ResizeCallBack(sender, new DependencyPropertyChangedEventArgs());
}
private static void ResizeCallBack(object? sender, DependencyPropertyChangedEventArgs eventArgs)
{
if (sender is not FrameworkElement obj) return;
var window = GetParentScaleWindow(obj);
if (window != null && !window._cachedObjects.Contains(obj))
{
if (!SetWindow(obj)) return;
}
else if (window == null)
{
obj.Loaded += ObjOnLoaded;
}
if (window is not { _loaded: true }) return;
CalculateFontSizeResized(obj);
}
private static void ObjOnUnloaded(object sender, RoutedEventArgs e)
{
var obj = (FrameworkElement)sender;
var w = GetParentScaleWindow(obj)!;
w._cachedObjects.Remove(obj);
obj.Unloaded -= ObjOnUnloaded;
obj.Loaded -= ObjOnLoaded;
}
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
{
_currentDiagonal = _diagonal(e.NewSize.Width, e.NewSize.Height);
foreach (var cached in _cachedObjects)
{
CalculateFontSizeResized(cached);
}
}
//<-------------------Window-------------------->
//<-------------------Scaling-------------------->
public static readonly DependencyProperty ScaleProperty = DependencyProperty.RegisterAttached(
"Scale", typeof(double), typeof(FontScaleWindow), new FrameworkPropertyMetadata(0.9999d ,FrameworkPropertyMetadataOptions.Inherits, ScalePropertyCallback));
public static void SetScale(DependencyObject element, double value)
=> element.SetValue(ScaleProperty, value);
public static double GetScale(DependencyObject element)
=> (double)element.GetValue(ScaleProperty);
private static void ScalePropertyCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var window = GetParentScaleWindow(d);
if (window == null)
{
var w = GetWindow(d);
if (w is FontScaleWindow fsWindow)
{
SetParentScaleWindow(d, fsWindow);
}
}
ResizeElement(d);
}
public static void ResizeElement(DependencyObject d)
{
var set = d.ReadLocalValue(ScaleProperty) != DependencyProperty.UnsetValue;
if (set)
{
if (GetResizeFont(d))
{
CalculateFontSizeResized(d);
}
else
{
CalculateFontSize(d);
}
}
}
public static void CalculateFontSize(DependencyObject d)
{
var window = GetParentScaleWindow(d);
if (window == null) return;
var fontSize = GetScale(d) * window._defaultDiagonal;
d.SetValue(FontSizeProperty, fontSize);
}
public static void CalculateFontSizeResized(DependencyObject d)
{
var window = GetParentScaleWindow(d);
if (window == null) return;
var windowsScale = Math.Pow(window._currentDiagonal / window._defaultDiagonal, window.ScaleCoefficient);
var elScale = GetScale(d);
var fontSize = window.DefaultFontSize * elScale * windowsScale;
d.SetValue(FontSizeProperty, fontSize);
}
}
+146
View File
@@ -0,0 +1,146 @@
<UserControl
x:Class="LolzFucker.Theme.Palette"
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"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300">
<UserControl.Resources>
<Style TargetType="TextBlock">
<Setter Property="FontSize" Value="14"/>
<Setter Property="FontWeight" Value="DemiBold"/>
<Setter Property="Margin" Value="4"/>
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Border
Background="{DynamicResource PrimaryHueMidBrush}"
Grid.ColumnSpan="3">
<TextBlock
Foreground="{DynamicResource PrimaryHueMidForegroundBrush}"
Text="Primary - Mid"/>
</Border>
<Border
Background="{DynamicResource PrimaryHueLightBrush}"
Grid.Row="1"
Grid.Column="0">
<TextBlock
FontWeight="Bold"
Foreground="{DynamicResource PrimaryHueLightForegroundBrush}"
Text="Light"/>
</Border>
<Border
Background="{DynamicResource PrimaryHueMidBrush}"
Grid.Row="1"
Grid.Column="1">
<TextBlock
Foreground="{DynamicResource PrimaryHueMidForegroundBrush}"
Text="Mid"/>
</Border>
<Border
Background="{DynamicResource PrimaryHueDarkBrush}"
Grid.Row="1"
Grid.Column="2">
<TextBlock
Foreground="{DynamicResource PrimaryHueDarkForegroundBrush}"
Text="Dark"/>
</Border>
<Border
Background="{DynamicResource SecondaryHueLightBrush}"
Grid.Row="2"
Grid.Column="0">
<TextBlock
Foreground="{DynamicResource SecondaryHueLightForegroundBrush}"
Text="Light"/>
</Border>
<Border
Background="{DynamicResource SecondaryHueMidBrush}"
Grid.Row="2"
Grid.Column="1">
<TextBlock
Foreground="{DynamicResource SecondaryHueMidForegroundBrush}"
Text="Mid"/>
</Border>
<Border
Background="{DynamicResource SecondaryHueDarkBrush}"
Grid.Row="2"
Grid.Column="2">
<TextBlock
Foreground="{DynamicResource SecondaryHueDarkForegroundBrush}"
Text="Dark"/>
</Border>
<Border
Background="{DynamicResource MaterialDesignBackground}"
Grid.Row="3"
Grid.Column="0">
<TextBlock
Foreground="{DynamicResource SecondaryHueDarkForegroundBrush}"
Text="Background"/>
</Border>
<Border
Background="{DynamicResource MaterialDesignCardBackground}"
Grid.Row="3"
Grid.Column="1">
<TextBlock
Foreground="{DynamicResource SecondaryHueMidForegroundBrush}"
Text="CardBack"/>
</Border>
<Border
Background="{DynamicResource WindowBackground}"
Grid.Row="3"
Grid.Column="2">
<TextBlock
Foreground="{DynamicResource SecondaryHueMidForegroundBrush}"
Text="Paper"/>
</Border>
<Border
Background="{DynamicResource MaterialDesignBody}"
Grid.Row="4"
Grid.Column="0">
<TextBlock
Text="Body"/>
</Border>
<Border
Background="{DynamicResource MaterialDesignBodyLight}"
Grid.Row="4"
Grid.Column="1">
<TextBlock
Foreground="{DynamicResource SecondaryHueMidForegroundBrush}"
Text="BodyLight"/>
</Border>
<Border
Background="{DynamicResource MaterialDesignToolTipBackground}"
Grid.Row="4"
Grid.Column="2">
<TextBlock
Text="ToolTipBack"/>
</Border>
</Grid>
</UserControl>
+15
View File
@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace LolzFucker.Theme
{
/// <summary>
/// Логика взаимодействия для Palette.xaml
/// </summary>
public partial class Palette : UserControl
{
public Palette()
{
InitializeComponent();
}
}
}
@@ -0,0 +1,94 @@
using System;
using System.Runtime.InteropServices;
namespace NebulaAuth.Theme.WindowStyle;
public static class NativeMethods
{
public const int WM_NCCALCSIZE = 0x83;
public const int WM_NCPAINT = 0x85;
[DllImport("kernel32", SetLastError = true)]
private static extern IntPtr LoadLibrary(string lpFileName);
[DllImport("dwmapi.dll", PreserveSig = false)]
public static extern bool DwmIsCompositionEnabled();
[DllImport("kernel32", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
public enum DWMWINDOWATTRIBUTE : uint
{
NCRenderingEnabled = 1,
NCRenderingPolicy,
TransitionsForceDisabled,
AllowNCPaint,
CaptionButtonBounds,
NonClientRtlLayout,
ForceIconicRepresentation,
Flip3DPolicy,
ExtendedFrameBounds,
HasIconicBitmap,
DisallowPeek,
ExcludedFromPeek,
Cloak,
Cloaked,
FreezeRepresentation
}
[StructLayout(LayoutKind.Sequential)]
public struct MARGINS
{
public int leftWidth;
public int rightWidth;
public int topHeight;
public int bottomHeight;
}
private delegate int DwmExtendFrameIntoClientAreaDelegate(IntPtr hwnd, ref MARGINS margins);
public static int DwmExtendFrameIntoClientArea(IntPtr hwnd, ref MARGINS margins)
{
var hModule = LoadLibrary("dwmapi");
if (hModule == IntPtr.Zero)
{
return 0;
}
var procAddress = GetProcAddress(hModule, "DwmExtendFrameIntoClientArea");
if (procAddress == IntPtr.Zero)
{
return 0;
}
var delegateForFunctionPointer = (DwmExtendFrameIntoClientAreaDelegate)Marshal.GetDelegateForFunctionPointer(procAddress, typeof(DwmExtendFrameIntoClientAreaDelegate));
return delegateForFunctionPointer(hwnd, ref margins);
}
public static bool IsDwmAvailable()
{
if (LoadLibrary("dwmapi") == IntPtr.Zero)
{
return false;
}
return true;
}
internal enum WVR
{
ALIGNTOP = 0x0010,
ALIGNLEFT = 0x0020,
ALIGNBOTTOM = 0x0040,
ALIGNRIGHT = 0x0080,
HREDRAW = 0x0100,
VREDRAW = 0x0200,
VALIDRECTS = 0x0400,
REDRAW = HREDRAW | VREDRAW
}
[DllImport("user32.dll", SetLastError = true)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);
}
@@ -0,0 +1,76 @@
using System;
using System.Runtime.InteropServices;
using System.Windows;
namespace NebulaAuth.Theme.WindowStyle;
public static class WindowChromeHelper
{
public static Thickness LayoutOffsetThickness => new Thickness(0d, 0d, 0d, SystemParameters.WindowResizeBorderThickness.Bottom);
/// <summary>
/// Gets the properly adjusted window resize border thickness from system parameters.
/// </summary>
public static Thickness WindowResizeBorderThickness
{
get
{
var dpix = GetDpi(GetDeviceCapsIndex.LOGPIXELSX);
var dpiy = GetDpi(GetDeviceCapsIndex.LOGPIXELSY);
var dx = GetSystemMetrics(GetSystemMetricsIndex.CXFRAME);
var dy = GetSystemMetrics(GetSystemMetricsIndex.CYFRAME);
// This adjustment is needed since .NET 4.5
var d = GetSystemMetrics(GetSystemMetricsIndex.SM_CXPADDEDBORDER);
dx += d;
dy += d;
var leftBorder = dx / dpix;
var topBorder = dy / dpiy;
return new Thickness(leftBorder, topBorder, leftBorder, topBorder);
}
}
[DllImport("user32.dll")]
private static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("user32.dll")]
private static extern IntPtr GetDC(IntPtr hwnd);
[DllImport("gdi32.dll")]
private static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
private static float GetDpi(GetDeviceCapsIndex index)
{
var desktopWnd = IntPtr.Zero;
var dc = GetDC(desktopWnd);
float dpi;
try
{
dpi = GetDeviceCaps(dc, (int)index);
}
finally
{
ReleaseDC(desktopWnd, dc);
}
return dpi / 96f;
}
private enum GetDeviceCapsIndex
{
LOGPIXELSX = 88,
LOGPIXELSY = 90
}
[DllImport("user32.dll")]
private static extern int GetSystemMetrics(GetSystemMetricsIndex nIndex);
private enum GetSystemMetricsIndex
{
CXFRAME = 32,
CYFRAME = 33,
SM_CXPADDEDBORDER = 92
}
}
@@ -0,0 +1,114 @@
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Shell;
using Microsoft.Xaml.Behaviors;
namespace NebulaAuth.Theme.WindowStyle;
public class WindowChromeRenderedBehavior : Behavior<Window>
{
private Window window;
protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.ContentRendered += OnContentRendered;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.ContentRendered -= OnContentRendered;
}
private void OnContentRendered(object sender, EventArgs e)
{
window = Window.GetWindow(AssociatedObject);
if (window == null) return;
var oldWindowChrome = WindowChrome.GetWindowChrome(window);
if (oldWindowChrome == null) return;
var newWindowChrome = new WindowChrome
{
CaptionHeight = oldWindowChrome.CaptionHeight,
CornerRadius = oldWindowChrome.CornerRadius,
GlassFrameThickness = new Thickness(0, 0, 0, 1),
NonClientFrameEdges = NonClientFrameEdges.Bottom,
ResizeBorderThickness = oldWindowChrome.ResizeBorderThickness,
UseAeroCaptionButtons = oldWindowChrome.UseAeroCaptionButtons
};
WindowChrome.SetWindowChrome(window, newWindowChrome);
var hWnd = new WindowInteropHelper(window).Handle;
HwndSource.FromHwnd(hWnd)?.AddHook(WndProc);
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case NativeMethods.WM_NCPAINT:
handled = false;
RemoveFrame();
break;
case NativeMethods.WM_NCCALCSIZE:
handled = true;
var rcClientArea = (RECT)Marshal.PtrToStructure(lParam, typeof(RECT));
rcClientArea.Bottom += (int)(WindowChromeHelper.WindowResizeBorderThickness.Bottom / 2);
Marshal.StructureToPtr(rcClientArea, lParam, false);
return wParam == new IntPtr(1) ? new IntPtr((int)NativeMethods.WVR.REDRAW) : IntPtr.Zero;
}
return IntPtr.Zero;
}
private void RemoveFrame()
{
if (Environment.OSVersion.Version.Major >= 6 && NativeMethods.IsDwmAvailable())
{
if (NativeMethods.DwmIsCompositionEnabled() && SystemParameters.DropShadow)
{
// to get the aero shadow back, margins have to be set on at least one side
// don't use negative values for the margins because it causes flickering when restoring or maximizing the window
// setting the margin on the left or top sides seem best, since the window never appears to flicker there on resizing
// but the right and bottom sometimes do, otherwise there will occasionally be white flicker when resizing the window.
NativeMethods.MARGINS margins;
margins.bottomHeight = 0;
margins.leftWidth = 1;
margins.rightWidth = 0;
margins.topHeight = 0;
var helper = new WindowInteropHelper(window);
NativeMethods.DwmExtendFrameIntoClientArea(helper.Handle, ref margins);
}
}
}
[Serializable]
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
public static RECT Empty;
public RECT(int left, int top, int right, int bottom)
{
Left = left;
Top = top;
Right = right;
Bottom = bottom;
}
}
}
@@ -0,0 +1,105 @@
<ResourceDictionary x:Class="NebulaAuth.Theme.WindowStyle.WindowStyle"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:local="clr-namespace:NebulaAuth.Theme.WindowStyle">
<Style TargetType="Button" BasedOn="{StaticResource MaterialDesignFlatButton}">
<Setter Property="WindowChrome.IsHitTestVisibleInChrome" Value="True" />
</Style>
<ControlTemplate x:Key="WindowTemplate" TargetType="{x:Type Window}" >
<!--<i:Interaction.Behaviors>
<local:WindowChromeLoadedBehavior />
</i:Interaction.Behaviors>-->
<Grid>
<Border x:Name="RootBorder"
Margin="{x:Static local:WindowChromeHelper.LayoutOffsetThickness}"
BorderBrush="Transparent"
BorderThickness="1"
Background="{DynamicResource WindowBackground}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid x:Name="TitleBar"
Grid.Row="0"
Height="28"
Background="{DynamicResource WindowBackground}">
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center"
Foreground="{DynamicResource PrimaryHueMidBrush}"
Text="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=Title}" FontSize="17"/>
<!-- Window Buttons -->
<StackPanel
HorizontalAlignment="Right"
Orientation="Horizontal">
<StackPanel.Resources>
<Style TargetType="materialDesign:PackIcon">
<Setter Property="HorizontalAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</StackPanel.Resources>
<Button x:Name="MinimizeButton"
Click="OnMinimizeClick"
>
<materialDesign:PackIcon Kind="WindowMinimize"/>
</Button>
<Button x:Name="MaximizeRestoreButton"
Click="OnMaximizeRestoreClick"
>
<materialDesign:PackIcon Kind="WindowMaximize"/>
</Button>
<Button x:Name="CloseButton"
Click="OnCloseClick">
<materialDesign:PackIcon Kind="WindowClose"/>
</Button>
</StackPanel>
</Grid>
<!-- Main Window Content -->
<Grid x:Name="MainGrid"
Grid.Row="1"
Background="{DynamicResource MainBackgroundBrush}">
<AdornerDecorator>
<ContentPresenter />
</AdornerDecorator>
</Grid>
</Grid>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsActive" Value="True">
<Setter TargetName="RootBorder" Property="BorderBrush" Value="#1e1e24" />
</Trigger>
<Trigger Property="WindowState" Value="Maximized">
<Setter TargetName="RootBorder" Property="BorderThickness" Value="{Binding Source={x:Static local:WindowChromeHelper.WindowResizeBorderThickness}}" />
<Setter TargetName="RootBorder" Property="BorderBrush" Value="Transparent" />
<Setter TargetName="MaximizeRestoreButton" Property="Content" >
<Setter.Value>
<materialDesign:PackIcon Kind="WindowRestore"/>
</Setter.Value>
</Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style x:Key="MainWindow" TargetType="{x:Type Window}">
<Setter Property="UseLayoutRounding" Value="True" />
<Setter Property="TextOptions.TextFormattingMode" Value="Ideal" />
<Setter Property="TextOptions.TextRenderingMode" Value="ClearType" />
<Setter Property="WindowChrome.WindowChrome">
<Setter.Value>
<WindowChrome
CaptionHeight="38"
CornerRadius="0"
GlassFrameThickness="0"
NonClientFrameEdges="None"
ResizeBorderThickness="5"
UseAeroCaptionButtons="False" />
</Setter.Value>
</Setter>
<Setter Property="Template" Value="{StaticResource WindowTemplate}" />
</Style>
</ResourceDictionary>
@@ -0,0 +1,36 @@
using System.Windows;
namespace NebulaAuth.Theme.WindowStyle
{
/// <summary>
/// Логика взаимодействия для WindowStyle.xaml
/// </summary>
partial class WindowStyle
{
public WindowStyle()
{
InitializeComponent();
}
private void OnCloseClick(object sender, RoutedEventArgs e)
{
var window = (Window)((FrameworkElement)sender).TemplatedParent;
window.Close();
}
private void OnMaximizeRestoreClick(object sender, RoutedEventArgs e)
{
var window = (Window)((FrameworkElement)sender).TemplatedParent;
window.WindowState = window.WindowState == WindowState.Normal ? WindowState.Maximized : WindowState.Normal;
}
private void OnMinimizeClick(object sender, RoutedEventArgs e)
{
var window = (Window)((FrameworkElement)sender).TemplatedParent;
window.WindowState = WindowState.Minimized;
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

@@ -0,0 +1,39 @@
using CodingSeb.Localization;
using NebulaAuth.Core;
using SteamLib.Exceptions;
using SteamLib.Exceptions.Mobile;
using SteamLib.ProtoCore.Enums;
namespace NebulaAuth.Utility;
public static class ErrorTranslatorHelper
{
public static string TranslateLoginError(LoginError error)
{
var result = GetMessage("Login", error.ToString());
return result ?? error.ToString();
}
public static string TranslateEResult(EResult eResult)
{
var result = GetMessage("EResult", eResult.ToString());
return result ?? eResult.ToString();
}
public static string TranslateLinkerError(AuthenticatorLinkerError error)
{
return GetMessage("AuthenticatorLinkerError", error.ToString()) ?? error.ToString();
}
public static string? GetMessage(string path, string name)
{
var fullPath = LocManager.CODE_BEHIND_PATH_PART + "." + "ErrorTranslator" + "." + path + "." + name;
var message = Loc.Tr(fullPath, "|ABSENT|");
if (message == "|ABSENT|") return null;
return message;
}
}
+97
View File
@@ -0,0 +1,97 @@
using NebulaAuth.Core;
using NebulaAuth.Model;
using SteamLib.Exceptions;
using SteamLib.Exceptions.General;
using SteamLib.ProtoCore.Exceptions;
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace NebulaAuth.Utility;
public static class ExceptionHandler
{
private const string EXCEPTION_HANDLER_LOC_PATH = "ExceptionHandler";
public static bool Handle(Exception ex, string? prefix = null, string? postfix = null, bool handleAllExceptions = false)
{
string msg;
Shell.Logger.Error(ex);
switch (ex)
{
case SessionExpiredException:
{
msg = "SessionExpiredException".GetCodeBehindLocalization();
break;
}
case SessionInvalidException:
{
msg = "SessionExpiredException".GetCodeBehindLocalization();
break;
}
case TaskCanceledException e:
{
msg = e.InnerException is TimeoutException
? "TimeoutException".GetCodeBehindLocalization()
: "TaskCanceledException".GetCodeBehindLocalization();
break;
}
case HttpRequestException e:
{
var str = "RequestError".GetCommonLocalization() + ": ";
if (e.StatusCode != null)
{
msg = str + e.StatusCode;
}
else if (e.InnerException != null)
{
msg = (str + e.InnerException.Message);
}
else
{
msg = (str + e.Message);
}
break;
}
case UnsupportedResponseException:
{
msg = "UnsupportedResponseException".GetCodeBehindLocalization();
break;
}
case CantLoadConfirmationsException e:
{
msg = e.Message;
break;
}
case EResultException e:
{
msg = "Error".GetCommonLocalization() + ": " + ErrorTranslatorHelper.TranslateEResult(e.Result);
break;
}
case LoginException e:
{
msg = "LoginException".GetCodeBehindLocalization() + ": " + ErrorTranslatorHelper.TranslateLoginError(e.Error);
break;
}
case not null when handleAllExceptions:
{
msg = "UnknownException".GetCodeBehindLocalization() + ex.Message;
break;
}
default:
return false;
}
SnackbarController.SendSnackbar(prefix + msg + postfix);
return true;
}
private static string GetCommonLocalization(this string key)
{
return LocManager.GetCommonOrDefault(key, key);
}
private static string GetCodeBehindLocalization(this string key)
{
return LocManager.GetCodeBehindOrDefault(key, EXCEPTION_HANDLER_LOC_PATH, key);
}
}
+188
View File
@@ -0,0 +1,188 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:confirmations="clr-namespace:SteamLib.SteamMobile.Confirmations;assembly=SteamLibForked"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:theme="clr-namespace:NebulaAuth.Theme"
xmlns:converters="clr-namespace:NebulaAuth.Converters"
xmlns:entities="clr-namespace:NebulaAuth.Model.Entities"
>
<DataTemplate DataType="{x:Type confirmations:Confirmation}">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" DockPanel.Dock="Left" Kind="QuestionMark" Margin="0,0,10,0"/>
<TextBlock VerticalAlignment="Center" Grid.Column="1" Text="{Binding TypeName}"></TextBlock>
<TextBlock VerticalAlignment="Center" Grid.Column="2" HorizontalAlignment="Right" Text="{Binding Time, StringFormat=t}"/>
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="3" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ConfirmCommand}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
</Button>
<Button Grid.Column="4" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Close" />
</Button>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type confirmations:TradeConfirmation}">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="AccountArrowRight" Margin="0,0,10,0"></materialDesign:PackIcon>
<Image Grid.Column="1" VerticalAlignment="Center" DockPanel.Dock="Left" Width="24" Height="24" Source="{Binding UserAvatarUri}" Margin="0,0,10,0"></Image>
<TextBlock VerticalAlignment="Center" Grid.Column="2" TextWrapping="WrapWithOverflow" >
<Run Text="{Tr MainWindow.ConfirmationTemplates.TradeWith, IsDynamic=False}"/>
<Run Text="{Binding UserName}" FontWeight="Bold"></Run>
</TextBlock>
<materialDesign:PackIcon Grid.Column="3" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="5,0,5,0" Kind="Gift" Visibility="{Binding IsReceiveNothing, Converter={StaticResource BooleanToVisibilityConverter}}"/>
<TextBlock VerticalAlignment="Center" Grid.Column="4" HorizontalAlignment="Right" Text="{Binding Time, StringFormat=t}"/>
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="5" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ConfirmCommand}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
</Button>
<Button Grid.Column="6" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Close" />
</Button>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type confirmations:AccountRecoveryConfirmation}">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" DockPanel.Dock="Left" Kind="LockOutline" Margin="0,0,10,0"/>
<TextBlock VerticalAlignment="Center" Grid.Column="1" Text="{Tr MainWindow.ConfirmationTemplates.Recovery}"/>
<TextBlock VerticalAlignment="Center" Grid.Column="2" HorizontalAlignment="Right" Text="{Binding Time, StringFormat=t}"/>
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="3" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ConfirmCommand}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
</Button>
<Button Grid.Column="4" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Close" />
</Button>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type confirmations:MarketConfirmation}">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="ShoppingCart" Margin="0,0,10,0"></materialDesign:PackIcon>
<Image Grid.Column="1" VerticalAlignment="Center" DockPanel.Dock="Left" Width="32" Height="32" Source="{Binding ItemImageUri}" Margin="0,0,10,0"></Image>
<Grid Grid.Column="2">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock VerticalAlignment="Center" x:Name="ItemName" theme:FontScaleWindow.ResizeFont="True" TextWrapping="WrapWithOverflow" Text="{Binding ItemName}" >
</TextBlock>
<TextBlock Grid.Row="1" Foreground="LightGray" Text="{Binding PriceString}">
<TextBlock.FontSize>
<Binding ElementName="ItemName" Path="FontSize" ConverterParameter="0.7">
<Binding.Converter>
<converters:CoefficientConverter/>
</Binding.Converter>
</Binding>
</TextBlock.FontSize>
</TextBlock>
</Grid>
<TextBlock VerticalAlignment="Center" Grid.Column="4" HorizontalAlignment="Left" Margin="5,0,0,0" Text="{Binding Time, StringFormat=t}"/>
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="5" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ConfirmCommand}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
</Button>
<Button Grid.Column="6" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Close" />
</Button>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type confirmations:RegisterApiKeyConfirmation}">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" DockPanel.Dock="Left" Kind="KeyLink" Margin="0,0,10,0"/>
<TextBlock VerticalAlignment="Center" Grid.Column="1" Text="{Tr MainWindow.ConfirmationTemplates.ApiKey}"></TextBlock>
<TextBlock VerticalAlignment="Center" Grid.Column="2" HorizontalAlignment="Right" Text="{Binding Time, StringFormat=t}"/>
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="3" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ConfirmCommand}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
</Button>
<Button Grid.Column="4" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Close" />
</Button>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type entities:MarketMultiConfirmation}">
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<materialDesign:PackIcon VerticalAlignment="Center" Width="20" Height="20" Kind="ShoppingCartPlus" Margin="0,0,10,0"></materialDesign:PackIcon>
<TextBlock VerticalAlignment="Center" Grid.Column="1">
<Run Text="{Tr MainWindow.ConfirmationTemplates.Market}"/>
<Run Text="{Binding Confirmations.Count, Mode=OneWay}"/>
<Run> </Run>
<Run Text="{Tr Common.Abbreviations.Count, IsDynamic=False}"/>
</TextBlock>
<TextBlock VerticalAlignment="Center" Grid.Column="2" HorizontalAlignment="Left" Margin="5,0,0,0" Text="{Binding Time, StringFormat=t}"/>
<Button Margin="5,0,0,0" VerticalAlignment="Center" Grid.Column="3" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.ConfirmCommand}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Check"></materialDesign:PackIcon>
</Button>
<Button Grid.Column="4" Style="{StaticResource MaterialDesignIconButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.CancelCommand}"
CommandParameter="{Binding }">
<materialDesign:PackIcon Kind="Close" />
</Button>
</Grid>
</DataTemplate>
</ResourceDictionary>
@@ -0,0 +1,46 @@
<UserControl x:Class="NebulaAuth.View.Dialogs.ConfirmCancelDialog"
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:theme="clr-namespace:NebulaAuth.Theme"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="400"
Height="Auto" Width="Auto" MaxWidth="500"
Background="{DynamicResource WindowBackground}">
<Grid Margin="15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock theme:FontScaleWindow.Scale="0.8"
theme:FontScaleWindow.ResizeFont="True"
HorizontalAlignment="Center" Margin="10,10,10,15"
Text="Подтвердите действие"
x:Name="ConfirmTextBlock"
TextWrapping="WrapWithOverflow"/>
<Grid Grid.Row="1" Margin="10,0,10,5">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button DockPanel.Dock="Left"
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
CommandParameter="{StaticResource True}"
FontSize="{Binding ElementName=ConfirmTextBlock, Path=FontSize,
Converter={StaticResource CoefficientConverter},
ConverterParameter=0.85}">
ОК
</Button>
<Button Grid.Column="1" DockPanel.Dock="Right"
Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}"
CommandParameter="{StaticResource False}"
FontSize="{Binding ElementName=ConfirmTextBlock, Path=FontSize,
Converter={StaticResource CoefficientConverter},
ConverterParameter=0.85}">
Отмена
</Button>
</Grid>
</Grid>
</UserControl>
@@ -0,0 +1,21 @@
using System.Windows.Controls;
namespace NebulaAuth.View.Dialogs
{
/// <summary>
/// Логика взаимодействия для ConfirmCancelDialog.xaml
/// </summary>
public partial class ConfirmCancelDialog : UserControl
{
public ConfirmCancelDialog()
{
InitializeComponent();
}
public ConfirmCancelDialog(string msg)
{
InitializeComponent();
ConfirmTextBlock.Text = msg;
}
}
}
@@ -0,0 +1,39 @@
<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"
d:DesignHeight="250" d:DesignWidth="800"
theme:FontScaleWindow.Scale="0.9" theme:FontScaleWindow.ResizeFont="True"
Foreground="WhiteSmoke" Cursor="Hand"
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}" Margin="10" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" materialDesign:HintAssist.Hint="{Tr LoginAgainDialog.PasswordBox}"></TextBox>
<CheckBox IsEnabled="{Binding Source={x:Static model:PHandler.IsPasswordSet}}" IsChecked="{Binding SavePassword}" Margin="10" Grid.Row="2" Content="{Tr LoginAgainDialog.SaveEncryptedPassword}"/>
<Grid Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button IsDefault="True" Margin="10,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,10,5" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" CommandParameter="{StaticResource False}" Content="{Tr LoginAgainDialog.CancelButton}"/>
</Grid>
</Grid>
</UserControl>
@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace NebulaAuth.View.Dialogs
{
/// <summary>
/// Логика взаимодействия для LoginAgainDialog.xaml
/// </summary>
public partial class LoginAgainDialog : UserControl
{
public LoginAgainDialog()
{
InitializeComponent();
}
}
}
@@ -0,0 +1,34 @@
<UserControl x:Class="NebulaAuth.View.Dialogs.SetCryptPasswordDialog"
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:local="clr-namespace:NebulaAuth.View.Dialogs"
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
xmlns:theme="clr-namespace:NebulaAuth.Theme"
xmlns:model="clr-namespace:NebulaAuth.Model"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
mc:Ignorable="d"
Foreground="WhiteSmoke" Cursor="Hand"
d:DataContext="{d:DesignInstance other:LoginAgainVM}"
Background="{DynamicResource WindowBackground}">
<Grid MinHeight="100" MinWidth="300" MaxWidth="400" Margin="20">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock IsHitTestVisible="False" TextWrapping="Wrap" FontWeight="Normal" Text="{Tr SetEncryptedPasswordDialog.DialogText}" theme:FontScaleWindow.Scale="0.8" theme:FontScaleWindow.ResizeFont="True" Margin="10,0,0,0" HorizontalAlignment="Left"/>
<PasswordBox FontSize="16" materialDesign:PasswordBoxAssist.Password="{Binding Password}" Margin="10" Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintRevealPasswordBox}" materialDesign:HintAssist.Hint="{Tr SetEncryptedPasswordDialog.Password}"></PasswordBox>
<Grid Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Margin="10,5,5,5" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" CommandParameter="{StaticResource True}" Content="{Tr SetEncryptedPasswordDialog.Ok}"/>
<Button IsCancel="True" Grid.Column="1" Margin="5,5,10,5" Style="{StaticResource MaterialDesignOutlinedButton}" Command="{x:Static materialDesign:DialogHost.CloseDialogCommand}" CommandParameter="{StaticResource False}" Content="{Tr SetEncryptedPasswordDialog.Cancel}"/>
</Grid>
</Grid>
</UserControl>
@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace NebulaAuth.View.Dialogs
{
/// <summary>
/// Логика взаимодействия для SetCryptPasswordDialog.xaml
/// </summary>
public partial class SetCryptPasswordDialog : UserControl
{
public SetCryptPasswordDialog()
{
InitializeComponent();
}
}
}
@@ -0,0 +1,43 @@
<UserControl x:Class="NebulaAuth.View.Dialogs.WaitLoginDialog"
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"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
theme:FontScaleWindow.ResizeFont="True" theme:FontScaleWindow.Scale="1"
Foreground="WhiteSmoke"
Background="{DynamicResource WindowBackground}">
<Grid Margin="20">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ProgressBar Style="{StaticResource MaterialDesignCircularProgressBar}" IsIndeterminate="True"></ProgressBar>
<TextBlock Margin="10,0,0,0" Grid.Column="1" Text="{Tr WaitLoginDialog.Text}" VerticalAlignment="Center"></TextBlock>
</Grid>
<Grid x:Name="CaptchaGrid" Margin="0,25,0,0" Visibility="Collapsed" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Image x:Name="CaptchaImage" Stretch="Uniform"></Image>
<TextBox Grid.Row="1" Style="{StaticResource MaterialDesignFloatingHintTextBox}" x:Name="CaptchaTB"/>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Button Grid.Column="0" Click="SendCaptchaBtn_Click">Отправить</Button>
<Button Grid.Column="1" Click="CancelButton_OnClick">Отмена</Button>
</Grid>
</Grid>
</Grid>
</UserControl>
@@ -0,0 +1,72 @@
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);
}
}
}
+133
View File
@@ -0,0 +1,133 @@
<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:local="clr-namespace:NebulaAuth.View"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
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>
<TextBlock FontStyle="Normal" Foreground="DarkGray" HorizontalAlignment="Left" VerticalAlignment="Center" FontSize="18" Text="{Tr LinkerDialog.Title}"/>
<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"></materialDesign:PackIcon>
</Button>
</Grid>
<Separator Background="DarkGray" Grid.Row="1"></Separator>
<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="10" Tag="{Binding IsLogin}">
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0"/>
<TextBlock Text="{Tr LinkerDialog.Authorization}"/>
</StackPanel>
<StackPanel Grid.Row="4" Orientation="Horizontal" Margin="10" Tag="{Binding IsEmailCode}">
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0"/>
<TextBlock Text="{Tr LinkerDialog.EmailCode}"/>
</StackPanel>
<StackPanel Grid.Row="5" Orientation="Horizontal" Margin="10" Tag="{Binding IsPhoneNumber}">
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0"/>
<TextBlock Text="{Tr LinkerDialog.PhoneNumber}"/>
</StackPanel>
<StackPanel Grid.Row="6" Orientation="Horizontal" Margin="10" Tag="{Binding IsEmailConfirmation}">
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0"/>
<TextBlock Text="{Tr LinkerDialog.EmailLink}"/>
</StackPanel>
<StackPanel Grid.Row="7" Orientation="Horizontal" Margin="10" Tag="{Binding IsLinkCode}">
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0"/>
<TextBlock Text="{Tr LinkerDialog.SmsOrCode}"/>
</StackPanel>
<StackPanel Grid.Row="8" Orientation="Horizontal" Margin="10" Tag="{Binding IsCompleted}">
<materialDesign:PackIcon Width="20" Height="20" VerticalAlignment="Center" Margin="0,0,5,0"/>
<TextBlock Text="{Tr LinkerDialog.Completed}"/>
</StackPanel>
<Grid Grid.Row="9" Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox Padding="10" Text="{Binding FieldText}" Visibility="{Binding IsFieldVisible, Converter={StaticResource BooleanToVisibilityConverter}}" Style="{StaticResource MaterialDesignOutlinedTextBox}" FontSize="16" Margin="0,0,0,10"></TextBox>
<TextBox Grid.Row="1" Padding="10" 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>
<TextBlock Foreground="GhostWhite" FontSize="16" Text="{Binding HintText}" HorizontalAlignment="Stretch" TextWrapping="Wrap"/>
</GroupBox>
</Grid>
<Button Grid.Row="10" FontSize="18" Command="{Binding ProceedCommand}" Content="{Tr LinkerDialog.ProceedButton}"/>
</Grid>
</UserControl>
+15
View File
@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace NebulaAuth.View
{
/// <summary>
/// Логика взаимодействия для LinkerView.xaml
/// </summary>
public partial class LinkerView : UserControl
{
public LinkerView()
{
InitializeComponent();
}
}
}
+112
View File
@@ -0,0 +1,112 @@
<UserControl x:Class="NebulaAuth.View.ProxyManagerView"
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:local="clr-namespace:NebulaAuth.View"
xmlns:md="http://materialdesigninxaml.net/winfx/xaml/themes"
xmlns:other="clr-namespace:NebulaAuth.ViewModel.Other"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800"
Foreground="WhiteSmoke"
FontFamily="{md:MaterialDesignFont}"
MinHeight="500"
MinWidth="400"
MaxHeight="550"
d:DataContext="{d:DesignInstance other:ProxyManagerVM}"
Background="{DynamicResource WindowBackground}">
<d:DesignerProperties.DesignStyle>
<Style TargetType="UserControl">
<Setter Property="Background" Value="{DynamicResource WindowBackground}" />
</Style>
</d:DesignerProperties.DesignStyle>
<Grid Margin="15,10,15,15">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<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 ProxyManagerDialog.Title}"/>
<Button IsCancel="True" Grid.Column="1" Width="30" Height="30" Style="{StaticResource MaterialDesignIconForegroundButton}" HorizontalAlignment="Right" Command="{x:Static md:DialogHost.CloseDialogCommand}">
<md:PackIcon Kind="Close" Width="24" Height="24" Foreground="IndianRed"></md:PackIcon>
</Button>
</Grid>
<Separator Grid.Row="1"></Separator>
<Grid Grid.Row="2">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock HorizontalAlignment="Stretch"
Margin="15" FontSize="16">
<Run Text="{Tr ProxyManagerDialog.DefaultProxy, IsDynamic=False}"/>
<Run Text="{Binding DefaultProxy.Key, StringFormat='&#x0a;0:', FallbackValue='X', Mode=OneWay}"/>
<Run Text="{Binding DefaultProxy.Value.Address, Mode=OneWay, FallbackValue=''}"/>
</TextBlock>
<Button Grid.Column="1" Command="{Binding SetDefaultCommand}">
<md:PackIcon Kind="HeartBoxOutline" Width="20" Height="20"></md:PackIcon>
</Button>
<Button Grid.Column="2" Command="{Binding RemoveDefaultCommand}" Cursor="Hand">
<md:PackIcon Kind="ClearBox" Width="20" Height="20"></md:PackIcon>
</Button>
</Grid>
<md:Card Grid.Row="3" Margin="10">
<ListBox SelectedValue="{Binding SelectedProxy}" ItemsSource="{Binding Proxies}">
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem" BasedOn="{StaticResource MaterialDesignListBoxItem}">
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource AncestorType=UserControl}, Path=DataContext}"/>
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem Header="{Tr MainWindow.ContextMenus.Proxy.Copy}"
Command="{Binding PlacementTarget.Tag.CopyProxyCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
CommandParameter="{Binding Value}"/>
<MenuItem Header="{Tr MainWindow.ContextMenus.Proxy.CopyAddress}"
Command="{Binding PlacementTarget.Tag.CopyProxyAddressCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
CommandParameter="{Binding Value}"/>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock>
<Run Text="{Binding Key, Mode=OneWay}"/><Run Text=": "/>
<Run Text="{Binding Value.Address, Mode=OneWay}"/>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</md:Card>
<Grid Grid.Row="4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox Text="{Binding AddProxyField}" FontSize="12" md:TextFieldAssist.HasClearButton="True" AcceptsReturn="True" MaxHeight="100" Style="{StaticResource MaterialDesignOutlinedTextBox}" Margin="15" md:HintAssist.Hint="IP:PORT:USER:PASS{ID}"></TextBox>
<Button IsDefault="True" Grid.Column="1" Command="{Binding AddProxyCommand}">
<md:PackIcon Kind="Add"></md:PackIcon>
</Button>
<Button Grid.Column="2" Command="{Binding RemoveProxyCommand}">
<md:PackIcon Kind="Trash"></md:PackIcon>
</Button>
</Grid>
</Grid>
</UserControl>
+15
View File
@@ -0,0 +1,15 @@
using System.Windows.Controls;
namespace NebulaAuth.View
{
/// <summary>
/// Логика взаимодействия для ProxyManagerView.xaml
/// </summary>
public partial class ProxyManagerView : UserControl
{
public ProxyManagerView()
{
InitializeComponent();
}
}
}
+73
View File
@@ -0,0 +1,73 @@
<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="700"
Foreground="WhiteSmoke"
FontFamily="{materialDesign:MaterialDesignFont}"
MinHeight="300"
MinWidth="400"
MaxWidth="200"
d:DataContext="{d:DesignInstance other:SettingsVM}"
Background="{DynamicResource WindowBackground}">
<d:DesignerProperties.DesignStyle>
<Style TargetType="UserControl">
<Setter Property="Background" Value="{DynamicResource WindowBackground}" />
</Style>
</d:DesignerProperties.DesignStyle>
<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"></materialDesign:PackIcon>
</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>
<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}" ></ComboBox>
<CheckBox Margin="0,20,0,0" FontSize="16" IsChecked="{Binding DisableTimersOnChange}" Content="{Tr SettingsDialog.DisableTimersOnSwitch}"/>
<CheckBox Margin="0,20,0,0" FontSize="16" IsChecked="{Binding HideToTray}" Content="{Tr SettingsDialog.MinimizeToTray}"/>
<CheckBox Margin="0,20,0,0" FontSize="16" IsChecked="{Binding UseIcon}" Content="{Tr SettingsDialog.UseIndicator}"/>
<materialDesign:ColorPicker IsEnabled="{Binding UseIcon}" Color="{Binding IconColor, Delay=50}" />
<CheckBox Margin="0,20,0,0" 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}"></PasswordBox>
<Button Command="{Binding SetPasswordCommand}" Margin="5,0,0,0" VerticalAlignment="Bottom" Grid.Column="1"> <materialDesign:PackIcon Kind="ContentSave"/></Button>
</Grid>
<CheckBox Margin="0,25,0,0" FontSize="16" IsChecked="{Binding LegacyMode}" Content="{Tr SettingsDialog.LegacyMafileMode}" ToolTip="{Tr SettingsDialog.LegacyMafileModeHint}"/>
<CheckBox Margin="0,20,0,0" FontSize="16" IsChecked="{Binding AllowAutoUpdate}" Content="{Tr SettingsDialog.AllowAutoUpdate}"/>
</StackPanel>
</Grid>
</Grid>
</UserControl>
+23
View File
@@ -0,0 +1,23 @@
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace NebulaAuth.View
{
/// <summary>
/// Логика взаимодействия для SettingsView.xaml
/// </summary>
public partial class SettingsView : UserControl
{
public SettingsView()
{
InitializeComponent();
}
private void ColorPicker_OnColorChanged(object sender, RoutedPropertyChangedEventArgs<Color> e)
{
Debug.WriteLine(e.NewValue);
}
}
}
+191
View File
@@ -0,0 +1,191 @@
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 SteamLib.Exceptions;
using SteamLib.SteamMobile;
using System;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NebulaAuth.View.Dialogs;
namespace NebulaAuth.ViewModel;
public partial class MainVM : ObservableObject
{
[ObservableProperty]
private ObservableCollection<Mafile> _maFiles = Storage.MaFiles;
public SnackbarMessageQueue MessageQueue => SnackbarController.MessageQueue;
public Mafile? SelectedMafile
{
get => _selectedMafile;
set => SetMafile(value);
}
private Mafile? _selectedMafile;
public MainVM()
{
CreateCodeTimer();
_confirmTimer = new Timer(ConfirmByTimer, null, TimeSpan.FromSeconds(_timerCheckSeconds), TimeSpan.FromSeconds(_timerCheckSeconds));
Proxies = new ObservableCollection<MaProxy>(ProxyStorage.Proxies.Select(kvp =>
new MaProxy(kvp.Key, kvp.Value)));
Storage.MaFiles.CollectionChanged += MaFilesOnCollectionChanged;
QueryGroups();
SessionHandler.LoginStarted += SessionHandlerOnLoginStarted;
SessionHandler.LoginCompleted += SessionHandlerOnLoginCompleted;
}
private void SetMafile(Mafile? mafile)
{
if (mafile != SelectedMafile)
{
_selectedMafile = mafile;
OnPropertyChanged(nameof(SelectedMafile));
MaClient.SetAccount(mafile);
OnPropertyChanged(nameof(ConfirmationsVisible));
if (Settings.DisableTimersOnChange) OffTimer(dispatcher: false);
SetCurrentProxy();
OnPropertyChanged(nameof(IsDefaultProxy));
if (mafile != null) Code = SteamGuardCodeGenerator.GenerateCode(mafile.SharedSecret);
}
}
[RelayCommand]
public async Task LoginAgain()
{
if (SelectedMafile == null)
{
return;
}
var loginAgainVm = await DialogsController.ShowLoginAgainDialog(SelectedMafile.AccountName);
if (loginAgainVm == null)
{
return;
}
var password = loginAgainVm.Password;
var waitDialog = new WaitLoginDialog();
var wait = DialogHost.Show(waitDialog);
try
{
await MaClient.LoginAgain(SelectedMafile, password, loginAgainVm.SavePassword, waitDialog);
SnackbarController.SendSnackbar(GetLocalizationOrDefault("SuccessfulLogin"));
}
catch (LoginException ex)
{
SnackbarController.SendSnackbar(ErrorTranslatorHelper.TranslateLoginError(ex.Error), TimeSpan.FromSeconds(1.5));
}
catch (Exception ex)
when (ExceptionHandler.Handle(ex))
{
Shell.Logger.Error(ex);
}
finally
{
DialogsController.CloseDialog();
await wait;
}
}
private void MaFilesOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
SearchText = string.Empty;
}
[RelayCommand]
private async Task RefreshSession()
{
if (SelectedMafile == null) return;
try
{
await MaClient.RefreshSession(SelectedMafile);
SnackbarController.SendSnackbar(GetLocalizationOrDefault("SessionRefreshed"));
}
catch (Exception ex) when (ExceptionHandler.Handle(ex))
{
Shell.Logger.Error(ex);
}
}
[RelayCommand]
public async Task LinkAccount()
{
OffTimer(false);
await DialogsController.ShowLinkerDialog();
}
[RelayCommand]
private async Task RemoveAuthenticator()
{
var selectedMafile = SelectedMafile;
if (selectedMafile == null) return;
if (string.IsNullOrWhiteSpace(selectedMafile.RevocationCode))
{
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Error", "Error") + ": " + GetLocalizationOrDefault("MissingRCode"));
return;
}
try
{
if (await DialogsController.ShowConfirmCancelDialog(GetLocalizationOrDefault("ConfirmRemovingAuthenticator")))
{
var result = await SessionHandler.Handle(() => MaClient.RemoveAuthenticator(SelectedMafile), SelectedMafile);
SnackbarController.SendSnackbar(
result.Success ? GetLocalizationOrDefault("AuthenticatorRemoved") : GetLocalizationOrDefault("AuthenticatorNotRemoved"));
if (result.Success)
{
Storage.MoveToRemoved(selectedMafile);
MaFiles.Remove(selectedMafile);
}
}
else
{
SnackbarController.SendSnackbar(LocManager.GetCommonOrDefault("Canceled", "Canceled"));
}
}
catch (Exception ex)
when (ExceptionHandler.Handle(ex, handleAllExceptions: true))
{
Shell.Logger.Error(ex);
}
}
[RelayCommand]
private async Task ConfirmLogin()
{
if (SelectedMafile == null) return;
try
{
LoginConfirmationResult res = await SessionHandler.Handle(() => MaClient.ConfirmLoginRequest(SelectedMafile), SelectedMafile);
if (res.Success)
{
SnackbarController.SendSnackbar($"{GetLocalizationOrDefault("ConfirmLoginSuccess")} {res.IP} ({res.Country})");
}
else
{
string msg = res.Error switch
{
LoginConfirmationError.NoRequests => GetLocalizationOrDefault("ConfirmLoginFailedNoRequests"),
LoginConfirmationError.MoreThanOneRequest => GetLocalizationOrDefault("ConfirmLoginFailedMoreThanOneRequest"), //TODO
_ => throw new ArgumentOutOfRangeException()
};
SnackbarController.SendSnackbar(msg);
}
}
catch (Exception ex)
when (ExceptionHandler.Handle(ex, handleAllExceptions: true))
{
Shell.Logger.Error(ex);
}
}
}
+43
View File
@@ -0,0 +1,43 @@
using CommunityToolkit.Mvvm.ComponentModel;
using SteamLib.SteamMobile;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Windows;
namespace NebulaAuth.ViewModel;
public partial class MainVM
{
private Timer _codeTimer;
[ObservableProperty] private double _codeProgress;
[ObservableProperty] private string _code;
[MemberNotNull(nameof(_codeTimer))]
[MemberNotNull(nameof(_code))]
private void CreateCodeTimer()
{
var currentTime = TimeAligner.GetSteamTime();
_codeTimer = new Timer(UpdateCode, null, 0, 1000);
}
private void UpdateCode(object? state = null)
{
var currentTime = TimeAligner.GetSteamTime();
var untilChange = currentTime - currentTime / 30L * 30L;
var codeProgress = untilChange / 30D * 100;
string? code = null;
if (untilChange == 0 && SelectedMafile != null)
{
code = SteamGuardCodeGenerator.GenerateCode(SelectedMafile!.SharedSecret);
}
if(Application.Current == null) return;
Application.Current.Dispatcher.BeginInvoke(() =>
{
CodeProgress = codeProgress;
if (code != null) Code = code;
});
}
}
@@ -0,0 +1,105 @@
using CommunityToolkit.Mvvm.Input;
using NebulaAuth.Core;
using NebulaAuth.Model;
using NebulaAuth.Model.Entities;
using NebulaAuth.Utility;
using SteamLib.SteamMobile.Confirmations;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
namespace NebulaAuth.ViewModel;
public partial class MainVM //Confirmations
{
public ObservableCollection<Confirmation> Confirmations { get; } = new();
private Mafile? _confirmationsLoadedForMafile;
public bool ConfirmationsVisible => SelectedMafile == _confirmationsLoadedForMafile;
[RelayCommand]
public async Task GetConfirmations()
{
if (SelectedMafile == null) return;
var maf = SelectedMafile;
List<Confirmation> conf;
try
{
var confI = await SessionHandler.Handle(() => MaClient.GetConfirmations(SelectedMafile), SelectedMafile);
conf = confI.ToList();
}
catch (Exception ex)
when (ExceptionHandler.Handle(ex))
{
return;
}
_confirmationsLoadedForMafile = maf;
OnPropertyChanged(nameof(ConfirmationsVisible));
Confirmations.Clear();
var marketConfirmations = conf.Where(c => c.ConfType == ConfirmationType.MarketSellTransaction).ToList();
if (marketConfirmations.Count > 1)
{
var indexOfLast = conf.IndexOf(marketConfirmations.First());
foreach (var mCon in marketConfirmations)
{
conf.Remove(mCon);
}
var mConf = new MarketMultiConfirmation(marketConfirmations.Cast<MarketConfirmation>());
conf.Insert(indexOfLast, mConf);
}
foreach (var con in conf)
{
Confirmations.Add(con);
}
}
private async Task SendConfirmation(Mafile mafile, Confirmation confirmation, bool confirm)
{
bool result;
try
{
if (confirmation is MarketMultiConfirmation multi)
{
result = await MaClient.SendMultipleConfirmation(mafile, multi.Confirmations, confirm);
}
else
{
result = await MaClient.SendConfirmation(mafile, confirmation, confirm);
}
}
catch (Exception ex)
when(ExceptionHandler.Handle(ex))
{
return;
}
if (result)
{
Confirmations.Remove(confirmation);
}
else
{
SnackbarController.SendSnackbar(GetLocalizationOrDefault("ConfirmationError"));
}
}
[RelayCommand]
private Task Confirm(Confirmation? confirmation)
{
if (SelectedMafile == null || confirmation == null) return Task.CompletedTask;
return SendConfirmation(SelectedMafile, confirmation, true);
}
[RelayCommand]
private Task Cancel(Confirmation? confirmation)
{
if (SelectedMafile == null || confirmation == null) return Task.CompletedTask;
return SendConfirmation(SelectedMafile, confirmation, false);
}
}
+207
View File
@@ -0,0 +1,207 @@
using CommunityToolkit.Mvvm.Input;
using MaterialDesignThemes.Wpf;
using Microsoft.Win32;
using NebulaAuth.Core;
using NebulaAuth.Model;
using NebulaAuth.View;
using NebulaAuth.ViewModel.Other;
using System;
using System.Collections.Specialized;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using AchiesUtilities.Extensions;
using NebulaAuth.Model.Entities;
using SteamLib.Exceptions;
namespace NebulaAuth.ViewModel;
public partial class MainVM //File //TODO: Refactor
{
public Settings Settings => Settings.Instance;
[RelayCommand]
private void OpenMafileFolder()
{ var mafile = SelectedMafile;
var path = Storage.MafileFolder;
if (mafile?.SessionData != null)
{
var mafPath = Path.Combine(Storage.MafileFolder, mafile.SessionData.SteamId.Steam64 + ".maFile");
if (File.Exists(mafPath))
{
path = $"/e, /select, \"{mafPath}\""; ;
}
}
Process.Start("explorer", path);
}
[RelayCommand]
private Task AddMafile()
{
var openFileDialog = new OpenFileDialog
{
Filter = "Mafile|*.mafile;*.maFile",
Multiselect = false,
};
var fs = openFileDialog.ShowDialog();
if (fs != true) return Task.CompletedTask;
var path = openFileDialog.FileName;
return AddMafile(new[] { path });
}
public async Task AddMafile(string[] path)
{
bool? confirmOverwrite = null;
var added = 0;
var notAdded = 0;
var errors = 0;
foreach (var str in path)
{
try
{
Storage.AddNewMafile(str, confirmOverwrite ?? false);
added++;
}
catch (FormatException)
{
errors++;
}
catch (IOException)
{
confirmOverwrite ??= await DialogsController.ShowConfirmCancelDialog(GetLocalizationOrDefault("ConfirmMafileOverwrite"));
if (confirmOverwrite == true)
{
Storage.AddNewMafile(str, true);
added++;
}
else if (confirmOverwrite == false)
{
notAdded++;
}
}
catch (SessionInvalidException ex)
{
if (path.Length == 1 && ex.Data.Contains("mafile"))
{
var mafile = (Mafile)ex.Data["mafile"]!;
if (await HandleAddMafileWithoutSession(mafile))
{
added++;
}
else
{
errors++;
}
}
else
{
SnackbarController.SendSnackbar($"{GetLocalizationOrDefault("MafileImportError")} {Path.GetFileName(str)}{GetLocalizationOrDefault("MissingSessionInMafile")}", TimeSpan.FromSeconds(4));
}
}
}
var msg = GetLocalizationOrDefault("Import");
if (added > 0)
{
msg += $" {GetLocalizationOrDefault("ImportAdded")} {added}.";
}
if (notAdded > 0)
{
msg += $" {GetLocalizationOrDefault("ImportSkipped")} {notAdded}.";
}
if (errors > 0)
{
msg += $" {GetLocalizationOrDefault("ImportErrors")} {errors}.";
}
SnackbarController.SendSnackbar(msg, TimeSpan.FromSeconds(2));
}
private async Task<bool> HandleAddMafileWithoutSession(Mafile data)
{
var oldMafile = SelectedMafile;
SelectedMafile = data;
await LoginAgain();
var result = data.SessionData != null;
if (result)
{
var existed = MaFiles.FirstOrDefault(m => m.AccountName == data.AccountName); //TODO: more elegant way to handle overwrite
if (existed != null)
{
MaFiles.Remove(existed);
}
MaFiles.Add(data);
SelectedMafile = data;
}
else
{
SelectedMafile = oldMafile;
}
return result;
}
[RelayCommand]
private async Task RemoveMafile()
{
if (SelectedMafile == null) return;
var confirm =
await DialogsController.ShowConfirmCancelDialog(GetLocalizationOrDefault("RemoveMafileConfirmation"));
if (!confirm) return;
try
{
Storage.MoveToRemoved(SelectedMafile);
}
catch (UnauthorizedAccessException)
{
SnackbarController.SendSnackbar(GetLocalizationOrDefault("CantRemoveAlreadyExist"));
}
catch (Exception ex)
{
SnackbarController.SendSnackbar(
$"{GetLocalizationOrDefault("CantRemoveMafile")} {ex.Message}");
}
}
[RelayCommand]
private async Task OpenSettingsDialog()
{
var vm = new SettingsVM();
var view = new SettingsView()
{
DataContext = vm
};
await DialogHost.Show(view);
}
[RelayCommand]
private async Task CopyMafileFromBuffer()
{
StringCollection files;
try
{
files = Clipboard.GetFileDropList();
}
catch (Exception ex)
{
Shell.Logger.Error(ex);
return;
}
var arr = files.Cast<string>().ToArray();
if (arr.All(p => p.ContainsIgnoreCase("mafile") == false)) return;
await AddMafile(arr);
}
}
+139
View File
@@ -0,0 +1,139 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using NebulaAuth.Model;
using NebulaAuth.Model.Entities;
using System;
using System.Collections.ObjectModel;
using System.Linq;
namespace NebulaAuth.ViewModel;
public partial class MainVM //Groups
{
[ObservableProperty]
private ObservableCollection<string> _groups = new();
public string? SelectedGroup
{
get => _selectedGroup;
set
{
if (SetProperty(ref _selectedGroup, value))
PerformSearch();
}
}
private string? _selectedGroup;
public string SearchText
{
get => _searchText;
set
{
if(SetProperty(ref _searchText, value))
PerformSearch();
}
}
private string _searchText = string.Empty;
[RelayCommand]
private void AddGroup(string? value)
{
var mafile = SelectedMafile;
if (mafile == null) return;
if (string.IsNullOrEmpty(value)) return;
mafile.Group = value;
Storage.UpdateMafile(mafile);
QueryGroups();
SelectedGroup = value;
OnPropertyChanged(nameof(SelectedMafile)); //For bindings
}
[RelayCommand]
private void AddToGroup(object[]? value)
{
if (value == null) return;
if (value.Length < 2) return;
var group = (string?)value[0];
var mafile = (Mafile?)value[1];
if (group == null || mafile == null) return;
mafile.Group = group;
Storage.UpdateMafile(mafile);
QueryGroups();
PerformSearch();
OnPropertyChanged(nameof(SelectedMafile));
}
[RelayCommand]
private void RemoveGroup(Mafile? mafile)
{
if (mafile?.Group == null) return;
var mafGroup = mafile.Group;
mafile.Group = null;
Storage.UpdateMafile(mafile);
OnPropertyChanged(nameof(SelectedMafile)); //For bindings
QueryGroups();
if (Groups.All(g => g.Equals(mafGroup) == false))
{
SelectedGroup = null;
}
PerformSearch();
}
private void QueryGroups()
{
var groups = Storage.MaFiles
.Where(m => string.IsNullOrWhiteSpace(m.Group) == false)
.Select(m => m.Group)
.Distinct()
.Order()
.ToList();
Groups = new ObservableCollection<string>(groups!);
}
private void PerformSearch()
{
if (string.IsNullOrWhiteSpace(SelectedGroup) && string.IsNullOrWhiteSpace(SearchText))
{
MaFiles = Storage.MaFiles;
return;
}
long? searchSteamId = null;
if (long.TryParse(SearchText, out var steamId))
{
searchSteamId = steamId;
}
var query = Storage.MaFiles.AsEnumerable();
if (!string.IsNullOrWhiteSpace(SearchText))
{
query = query.Where(SearchPredicate);
}
if (!string.IsNullOrWhiteSpace(SelectedGroup))
{
query = query.Where(m => m.Group != null && m.Group.Equals(SelectedGroup));
}
var perform = query.ToList();
MaFiles = new ObservableCollection<Mafile>(perform);
if (SelectedMafile != null && !MaFiles.Contains(SelectedMafile))
{
SelectedMafile = MaFiles.FirstOrDefault();
}
bool SearchPredicate(Mafile mafile)
{
if (!mafile.AccountName.Contains(SearchText, StringComparison.CurrentCultureIgnoreCase))
{
return mafile.SessionData != null && mafile.SessionData.SteamId.Steam64.Id.Equals(searchSteamId);
}
return true;
}
}
}
@@ -0,0 +1,17 @@
using NebulaAuth.Core;
namespace NebulaAuth.ViewModel;
public partial class MainVM
{
private const string LOC_PATH = "MainVM";
private static string? GetLocalization(string key)
{
return LocManager.GetCodeBehind(LOC_PATH, key);
}
private static string GetLocalizationOrDefault(string key)
{
return LocManager.GetCodeBehindOrDefault(key, LOC_PATH, key);
}
}
+37
View File
@@ -0,0 +1,37 @@
using MaterialDesignThemes.Wpf;
using System;
using System.Windows;
using NebulaAuth.View.Dialogs;
namespace NebulaAuth.ViewModel;
public partial class MainVM //Other
{
private void SessionHandlerOnLoginCompleted(object? sender, EventArgs e)
{
var currentSession = DialogHost.GetDialogSession(null);
Application.Current.Dispatcher.BeginInvoke(() =>
{
if (currentSession is { Content: WaitLoginDialog, IsEnded: false })
{
try
{
currentSession.Close();
}
catch
{
//Ignored
}
}
});
}
private async void SessionHandlerOnLoginStarted(object? sender, EventArgs e)
{
if (DialogHost.IsDialogOpen(null)) return;
await Application.Current.Dispatcher.BeginInvoke(async () =>
{
await DialogHost.Show(new WaitLoginDialog());
});
}
}
+121
View File
@@ -0,0 +1,121 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using NebulaAuth.Core;
using NebulaAuth.Model;
using NebulaAuth.Model.Entities;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using NebulaAuth.Model.Comparers;
namespace NebulaAuth.ViewModel;
public partial class MainVM
{
public ObservableCollection<MaProxy> Proxies { get; }
public MaProxy? SelectedProxy
{
get => _selectedProxy;
set
{
if (SetProperty(ref _selectedProxy, value))
{
OnPropertyChanged(nameof(IsDefaultProxy));
OnProxyChanged();
};
}
}
private MaProxy? _selectedProxy;
[ObservableProperty] private bool _proxyExist = true;
public bool IsDefaultProxy => SelectedProxy == null && MaClient.DefaultProxy != null;
private bool _handleProxyChange;
private void SetCurrentProxy()
{
if (ReferenceEquals(_selectedProxy, SelectedMafile?.Proxy) == false && _selectedProxy?.Equals(SelectedMafile?.Proxy) == false)
_handleProxyChange = true;
if (SelectedMafile == null)
{
SelectedProxy = null;
return;
}
if (SelectedMafile.Proxy == null)
{
SelectedProxy = SelectedMafile.Proxy;
}
else
{
var existed = Proxies.FirstOrDefault(p => p.Id == SelectedMafile.Proxy.Id);
if (existed == null || ProxyDataComparer.Equal(existed.Data, SelectedMafile.Proxy.Data) == false)
{
SelectedProxy = SelectedMafile.Proxy;
}
else
{
SelectedProxy = existed;
}
}
CheckProxyExist();
}
private void CheckProxyExist()
{
if (SelectedProxy == null)
{
ProxyExist = true;
return;
}
var selectedId = SelectedProxy.Id;
ProxyExist = ProxyStorage.Proxies.TryGetValue(selectedId, out var existedProxy)
&& ProxyStorage.CompareProxy(SelectedProxy.Data, existedProxy);
}
[RelayCommand]
private async Task OpenProxyManager()
{
await DialogsController.ShowProxyManager(SelectedProxy);
var oldSelection = SelectedProxy;
Proxies.Clear();
foreach (var kvp in ProxyStorage.Proxies)
{
Proxies.Add(new MaProxy(kvp.Key, kvp.Value));
}
SelectedProxy = oldSelection;
SetCurrentProxy();
OnPropertyChanged(nameof(IsDefaultProxy));
_handleProxyChange = false;
}
[RelayCommand]
private void RemoveProxy()
{
if (SelectedMafile == null) return;
SelectedMafile.Proxy = null;
SelectedProxy = null;
Storage.UpdateMafile(SelectedMafile);
ProxyExist = true;
}
private void OnProxyChanged()
{
if (_handleProxyChange)
{
_handleProxyChange = false;
return;
}
if (SelectedMafile == null) return;
ProxyExist = true;
SelectedMafile.Proxy = SelectedProxy;
Storage.UpdateMafile(SelectedMafile);
}
}
+140
View File
@@ -0,0 +1,140 @@
using CommunityToolkit.Mvvm.ComponentModel;
using NebulaAuth.Core;
using NebulaAuth.Model;
using NebulaAuth.Model.Entities;
using NebulaAuth.Utility;
using SteamLib.Exceptions;
using SteamLib.SteamMobile.Confirmations;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
namespace NebulaAuth.ViewModel;
public partial class MainVM //Timer
{
private readonly Timer _confirmTimer;
[ObservableProperty] private bool _marketTimerEnabled;
[ObservableProperty] private bool _tradeTimerEnabled;
private int _timerCheckSeconds = Settings.Instance.TimerSeconds;
public int TimerCheckSeconds
{
get => _timerCheckSeconds;
set => SetTimer(value);
}
private async void ConfirmByTimer(object? state = null)
{
if (SelectedMafile == null)
return;
var selected = SelectedMafile;
if (InterruptTimer(selected, null)) return;
List<Confirmation> conf;
try
{
conf = (await HandleTimerRequest(() => MaClient.GetConfirmations(selected), SelectedMafile)).ToList();
}
catch (ApplicationException ex)
{
Shell.Logger.Warn(ex, "Error GetConf in timer.");
return;
}
if (InterruptTimer(selected, conf)) return;
var toConfirm = new List<Confirmation>();
if (MarketTimerEnabled)
{
var market = conf.Where(c => c.ConfType == ConfirmationType.MarketSellTransaction);
toConfirm.AddRange(market);
}
if (TradeTimerEnabled)
{
var trade = conf.Where(c => c.ConfType == ConfirmationType.Trade);
toConfirm.AddRange(trade);
}
if (InterruptTimer(selected, toConfirm)) return;
bool result;
try
{
Shell.Logger.Debug("Sending confirmations. Count: {count}", toConfirm.Count);
result = await HandleTimerRequest(() => MaClient.SendMultipleConfirmation(SelectedMafile, toConfirm, confirm: true), SelectedMafile);
}
catch (ApplicationException ex)
{
Shell.Logger.Warn(ex, "MultiConf error in Timer.");
return;
}
SnackbarController.SendSnackbar(result ? $"{GetLocalizationOrDefault("TimerConfirmed")} {toConfirm.Count}" : GetLocalizationOrDefault("TimerNotConfirmed"));
}
private bool InterruptTimer(Mafile cachedValue, List<Confirmation>? confirmations)
{
return SelectedMafile == null
|| (MarketTimerEnabled || TradeTimerEnabled) == false
|| SelectedMafile != cachedValue
|| confirmations is { Count: 0 };
}
private void OffTimer(bool dispatcher)
{
if (dispatcher)
{
Application.Current.Dispatcher.BeginInvoke(new Action(OffTimerInline));
}
else
{
OffTimerInline();
}
return;
void OffTimerInline()
{
MarketTimerEnabled = false;
TradeTimerEnabled = false;
}
}
private async Task<T> HandleTimerRequest<T>(Func<Task<T>> func, Mafile mafile)
{
Exception innerException;
try
{
return await SessionHandler.Handle(func, mafile);
}
catch (SessionInvalidException ex)
{
Shell.Logger.Warn("Session error while requesting in timer. Timer disabled");
SnackbarController.SendSnackbar(GetLocalizationOrDefault("TimerSessionError"));
OffTimer(dispatcher: true);
innerException = ex;
}
catch (Exception ex) when (ExceptionHandler.Handle(ex, GetLocalizationOrDefault("TimerPrefix")))
{
innerException = ex;
}
throw new ApplicationException("Swallowed", innerException);
}
private void SetTimer(int value)
{
if (_timerCheckSeconds == value) return;
if (value < 10)
{
SnackbarController.SendSnackbar(GetLocalizationOrDefault("TimerTooFast"));
OnPropertyChanged(nameof(TimerCheckSeconds));
return;
}
Settings.TimerSeconds = value;
_timerCheckSeconds = value;
OnPropertyChanged(nameof(TimerCheckSeconds));
_confirmTimer.Change(value * 1000, value * 1000);
}
}
+418
View File
@@ -0,0 +1,418 @@
using AchiesUtilities.Collections;
using AchiesUtilities.Web.Proxy;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using MaterialDesignThemes.Wpf;
using NebulaAuth.Model;
using NebulaAuth.Model.Entities;
using NebulaAuth.Utility;
using Newtonsoft.Json;
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 System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Windows;
using NebulaAuth.Core;
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 Microsoft.Extensions.Logging.ILogger Logger2 => Shell.ExtensionsLogger;
#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;
[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
#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;
FieldText = string.Empty;
IsFieldVisible = false;
HintText = string.Empty;
_sessionData = (MobileSessionData)await LoginV2Executor.DoLogin(_loginV2ExecutorOptions, userName, pass);
Handler.CookieContainer.SetSteamMobileCookies(_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, backupHandler: Backup, Logger2);
_linker = new SteamAuthenticatorLinker(linkOptions);
var result = await _linker.LinkAccount(_sessionData);
IsLinkCode = true;
IsCompleted = true;
var mafile = Mafile.FromMobileDataExtended(result);
Storage.SaveMafile(mafile);
File.Delete(Path.Combine("mafiles_backup", mafile.AccountName + ".mafile"));
HintText =
string.Format(GetLocalizationOrDefault("MafileLinked"),
mafile.RevocationCode,
mafile.SessionData?.SteamId.Steam64);
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();
return;
}
if (!string.IsNullOrWhiteSpace(phoneText) && phoneText.Length >= 4 && long.TryParse(phoneText, out var phone))
{
HintText = string.Empty;
IsFieldVisible = false;
_phoneNumberTcs.SetResult(phone);
_phoneNumberTcs = new();
return;
}
else
{
HintText = GetLocalizationOrDefault("PleaseEnterCorrectPhone");
CanProceed = true;
return;
}
}
if (IsEmailConfirmation == false)
{
HintText = string.Empty;
_emailConfTcs.SetResult();
_emailConfTcs = new();
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();
return;
}
else
{
HintText = GetLocalizationOrDefault("PleaseEnterCorrectCode");
CanProceed = true;
return;
}
}
}
[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>();
}
private void Backup(MobileDataExtended data)
{
if (Directory.Exists("mafiles_backup") == false)
{
Directory.CreateDirectory("mafiles_backup");
}
var json = JsonConvert.SerializeObject(data, Formatting.Indented);
File.WriteAllText(Path.Combine("mafiles_backup", data.AccountName + ".mafile"), json);
}
#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(null);
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
private static string GetLocalizationOrDefault(string key)
{
return LocManager.GetCodeBehindOrDefault(key, LOCALIZATION_KEY, key);
}
private static string GetLocalizationCommon(string key)
{
return LocManager.GetCommonOrDefault(key, key);
}
}
@@ -0,0 +1,10 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace NebulaAuth.ViewModel.Other;
public partial class LoginAgainVM : ObservableObject
{
[ObservableProperty] private string _password = null!;
[ObservableProperty] private bool _savePassword;
[ObservableProperty] private string _userName = null!;
}
@@ -0,0 +1,160 @@
using AchiesUtilities.Collections;
using AchiesUtilities.Web.Proxy;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using NebulaAuth.Core;
using NebulaAuth.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows;
namespace NebulaAuth.ViewModel.Other;
public partial class ProxyManagerVM : ObservableObject
{
private const string LOCALIZATION_KEY = "ProxyManagerVM";
[ObservableProperty] private KeyValuePair<int, ProxyData>? _selectedProxy;
[ObservableProperty] private string _addProxyField = string.Empty;
[ObservableProperty] private KeyValuePair<int, ProxyData>? _defaultProxy;
public ObservableDictionary<int, ProxyData> Proxies => ProxyStorage.Proxies;
private static readonly Regex IdRegex = new(@"(?:\{(\d+)\})");
public ProxyManagerVM()
{
if (MaClient.DefaultProxy != null)
DefaultProxy = Proxies.FirstOrDefault(kvp => kvp.Value.Equals(MaClient.DefaultProxy));
}
[RelayCommand]
private void AddProxy()
{
if (string.IsNullOrEmpty(AddProxyField)) return;
if (AddProxyField.Contains(Environment.NewLine))
{
var split = AddProxyField.Split(Environment.NewLine);
var idPresent = (bool?)null;
var proxies = new List<KeyValuePair<int?, ProxyData>>();
var i = 0;
foreach (var str in split)
{
i++;
int? id = null;
var match = IdRegex.Match(str);
if (match.Success) id = int.Parse(match.Groups[1].Value);
idPresent ??= match.Success;
if (idPresent.Value != match.Success)
{
SnackbarController.SendSnackbar(GetLocalizationOrDefault("WrongFormatSomeIdsMissing"));
return;
}
try
{
var proxy = ProxyData.Parse(str, ProxyStorage.FORMAT);
if (id != null && proxies.Any(kvp => kvp.Key == id))
{
SnackbarController.SendSnackbar(string.Format(GetLocalizationOrDefault("DuplicateId"), id));
return;
}
proxies.Add(new KeyValuePair<int?, ProxyData>(id, proxy));
}
catch (FormatException)
{
SnackbarController.SendSnackbar(string.Format(GetLocalizationOrDefault("WrongFormatOnLine"), i));
return;
}
}
foreach (var kvp in proxies)
{
ProxyStorage.SetProxy(kvp.Key, kvp.Value);
}
}
else
{
int? id = null;
var input = AddProxyField;
if (IdRegex.IsMatch(AddProxyField))
{
id = int.Parse(IdRegex.Match(AddProxyField).Groups[1].Value);
input = IdRegex.Replace(input, "");
}
ProxyData data;
try
{
data = ProxyData.Parse(input, ProxyStorage.FORMAT);
}
catch (FormatException)
{
SnackbarController.SendSnackbar(GetLocalizationOrDefault("WrongFormat"));
return;
}
ProxyStorage.SetProxy(id, data);
}
AddProxyField = string.Empty;
CheckIfDefaultProxyStay();
}
private void CheckIfDefaultProxyStay()
{
if (!DefaultProxy.HasValue || Proxies.Any(kvp => kvp.Equals(DefaultProxy.Value))) return;
DefaultProxy = null;
MaClient.DefaultProxy = null;
}
[RelayCommand]
private void RemoveProxy()
{
if (SelectedProxy == null) return;
ProxyStorage.RemoveProxy(SelectedProxy.Value.Key);
CheckIfDefaultProxyStay();
}
[RelayCommand]
private void SetDefault()
{
if (SelectedProxy == null) return;
DefaultProxy = SelectedProxy;
MaClient.DefaultProxy = SelectedProxy.Value.Value;
ProxyStorage.Save();
}
[RelayCommand]
private void RemoveDefault()
{
DefaultProxy = null;
MaClient.DefaultProxy = null;
ProxyStorage.Save();
}
[RelayCommand]
private void CopyProxy(ProxyData? obj)
{
if (obj == null) return;
Clipboard.SetText(obj.ToString(ProxyStorage.FORMAT));
}
[RelayCommand]
private void CopyProxyAddress(ProxyData? obj)
{
if (obj == null) return;
Clipboard.SetText(obj.ToString(ProxyStorage.ADDRESS_FORMAT));
}
private static string GetLocalizationOrDefault(string key)
{
return LocManager.GetCodeBehindOrDefault(key, LOCALIZATION_KEY, key);
}
}
@@ -0,0 +1,8 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace NebulaAuth.ViewModel.Other;
public partial class SetEncryptPasswordVM : ObservableObject
{
[ObservableProperty] private string? _password;
}
+122
View File
@@ -0,0 +1,122 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using NebulaAuth.Model;
using System.Collections.Generic;
using System.Windows.Media;
using NebulaAuth.Core;
namespace NebulaAuth.ViewModel.Other;
public partial class SettingsVM : ObservableObject
{
public Settings Settings => Settings.Instance;
public bool DisableTimersOnChange
{
get => Settings.DisableTimersOnChange;
set => Settings.DisableTimersOnChange = value;
}
public BackgroundMode BackgroundMode
{
get => Settings.BackgroundMode;
set => Settings.BackgroundMode = value;
}
public bool HideToTray
{
get => Settings.HideToTray;
set => Settings.HideToTray = value;
}
public Dictionary<BackgroundMode, string> BackgroundModes => new()
{
{BackgroundMode.Default, LocManager.GetOrDefault("Default", "SettingsDialog", "BackgroundMode", "Default")},
{BackgroundMode.Custom, LocManager.GetOrDefault("Default", "SettingsDialog", "BackgroundMode", "Custom")},
{BackgroundMode.Color, LocManager.GetOrDefault("Default", "SettingsDialog", "BackgroundMode", "NoBackground")},
};
public Dictionary<LocalizationLanguage, string> Languages { get; } = new()
{
{LocalizationLanguage.English, "English"},
{LocalizationLanguage.Russian, "Русский"},
{LocalizationLanguage.Ukrainian, "Українська"}
};
public Color? BackgroundColor
{
get => Settings.BackgroundColor;
set => Settings.BackgroundColor = value;
}
public Color? IconColor
{
get => Settings.IconColor;
set => Settings.IconColor = value;
}
public bool UseIcon
{
get => IconColor != null;
set
{
if (value == false)
IconColor = null;
else
IconColor = Color.FromRgb(202, 39, 39);
OnPropertyChanged();
OnPropertyChanged(nameof(IconColor));
}
}
public bool UseBackground
{
get => BackgroundColor != null;
set
{
if (value == false)
BackgroundColor = null;
else
BackgroundColor = Color.FromRgb(202, 39, 39);
OnPropertyChanged();
OnPropertyChanged(nameof(BackgroundColor));
}
}
public LocalizationLanguage Language
{
get => Settings.Language;
set
{
Settings.Language = value;
LocManager.SetApplicationLocalization(value);
}
}
public bool LegacyMode
{
get => Settings.LegacyMode;
set => Settings.LegacyMode = value;
}
public bool AllowAutoUpdate
{
get => Settings.AllowAutoUpdate;
set => Settings.AllowAutoUpdate = value;
}
[ObservableProperty] private string _password;
[RelayCommand]
public void SetPassword()
{
PHandler.SetPassword(Password);
Settings.IsPasswordSet = PHandler.IsPasswordSet;
}
}
+949
View File
@@ -0,0 +1,949 @@
{
"LanguageName": {
"en": "English",
"ru": "Русский",
"ua": "Українська"
},
"LanguageWord": {
"en": "Language",
"ru": "Язык",
"ua": "Мова"
},
"Common": {
"No": {
"en": "No",
"ru": "Нет",
"ua": "Ні"
},
"Yes": {
"en": "Yes",
"ru": "Да",
"ua": "Так"
},
"Error": {
"en": "Error",
"ru": "Ошибка",
"ua": "Помилка"
},
"RequestError": {
"en": "Request error",
"ru": "Ошибка запроса",
"ua": "Помилка запиту"
},
"Canceled": {
"en": "Canceled",
"ru": "Отменено",
"ua": "Скасовано"
},
"Abbreviations": {
"Time": {
"Seconds": {
"en": "sec",
"ru": "сек",
"ua": "сек"
}
},
"Count": {
"Items": {
"en": "items",
"ru": "шт",
"ua": "шт"
}
}
}
},
"MainWindow": {
"Menu": {
"File": {
"Caption": {
"en": "File",
"ru": "Файл",
"ua": "Файл"
},
"Import": {
"en": "Import",
"ru": "Импорт",
"ua": "Імпорт"
},
"Remove": {
"en": "Remove",
"ru": "Удалить",
"ua": "Видалити"
},
"OpenFolder": {
"en": "Open folder",
"ru": "Открыть папку",
"ua": "Відкрити папку"
},
"Settings": {
"en": "Settings",
"ru": "Настройки",
"ua": "Налаштування"
}
},
"Account": {
"Caption": {
"en": "Account",
"ru": "Аккаунт",
"ua": "Аккаунт"
},
"Link": {
"en": "Link",
"ru": "Привязать",
"ua": "Прив'язати"
},
"Unlink": {
"en": "Unlink",
"ru": "Отвязать",
"ua": "Відв'язати"
},
"RefreshSession": {
"en": "Refresh session",
"ru": "Обновить сессию",
"ua": "Оновити сесію"
},
"LoginAgain": {
"en": "Login",
"ru": "Войти",
"ua": "Увійти"
}
}
},
"AppBar": {
"GroupsHint": {
"en": "Groups",
"ru": "Группы",
"ua": "Групи"
},
"Proxy": {
"ProxyHint": {
"en": "Proxy",
"ru": "Прокси",
"ua": "Проксі"
},
"ProxyOpenManagerHint": {
"en": "Open proxy manager",
"ru": "Открыть менеджер прокси",
"ua": "Відкрити менеджер проксі"
},
"ProxyAlert": {
"DefaultInUse": {
"en": "Default proxy is in use",
"ru": "Используется прокси по умолчанию",
"ua": "Використовується проксі за замовчуванням"
},
"MafileProxyInUse": {
"en": "Mafile proxy is in use",
"ru": "Используется прокси из mafile",
"ua": "Використовується проксі з mafile"
}
}
},
"TradeTimerHint": {
"en": "Trade",
"ru": "Трейд",
"ua": "Трейд"
},
"MarketTimerHint": {
"en": "Market",
"ru": "Маркет",
"ua": "Маркет"
}
},
"LeftPart": {
"SearchBoxHint": {
"en": "Search",
"ru": "Поиск",
"ua": "Пошук"
}
},
"RightPart": {
"LoadConfirmations": {
"en": "Load confirmations",
"ru": "Загрузить подтверждения",
"ua": "Завантажити підтвердження"
},
"ConfirmLogin": {
"en": "Confirm login",
"ru": "Подтвердить вход",
"ua": "Підтвердити вхід"
}
},
"Footer": {
"Account": {
"en": "Account: ",
"ru": "Аккаунт: ",
"ua": "Аккаунт: "
},
"Group": {
"en": "Group: ",
"ru": "Группа: ",
"ua": "Група: "
}
},
"ConfirmationTemplates": {
"TradeWith": {
"en": "Trade with",
"ru": "Трейд с",
"ua": "Трейд з"
},
"Recovery": {
"en": "Recovery",
"ru": "Восстановление",
"ua": "Відновлення"
},
"ApiKey": {
"en": "Api key",
"ru": "Api ключ",
"ua": "Api ключ"
},
"Market": {
"en": "Market",
"ru": "Маркет",
"ua": "Маркет"
}
},
"CodeCopied": {
"ru": "Скопировано",
"en": "Copied",
"ua": "Скопійовано"
},
"ContextMenus": {
"Mafile": {
"AddToGroup": {
"en": "Add to group",
"ru": "Добавить в группу",
"ua": "Додати в групу"
},
"RemoveFromGroup": {
"en": "Remove from group",
"ru": "Удалить из группы",
"ua": "Видалити з групи"
}
},
"Proxy": {
"Copy": {
"en": "Copy",
"ru": "Копировать",
"ua": "Копіювати"
},
"CopyAddress": {
"en": "Copy address",
"ru": "Копировать адрес",
"ua": "Копіювати адресу"
}
}
}
},
"SettingsDialog": {
"Title": {
"en": "Settings",
"ru": "Настройки",
"ua": "Налаштування"
},
"BackgroundHint": {
"en": "Background",
"ru": "Фон",
"ua": "Фон"
},
"BackgroundMode": {
"Default": {
"en": "Default",
"ru": "По умолчанию",
"ua": "За замовчуванням"
},
"Custom": {
"en": "Custom",
"ru": "Свой",
"ua": "Свій"
},
"NoBackground": {
"en": "No background",
"ru": "Без фона",
"ua": "Без фону"
}
},
"DisableTimersOnSwitch": {
"en": "Disable timers on account switch",
"ru": "Отключать таймеры при смене аккаунта",
"ua": "Вимикати таймери при зміні аккаунта"
},
"MinimizeToTray": {
"en": "Minimize to tray",
"ru": "Сворачивать в трей",
"ua": "Згортати в трей"
},
"UseIndicator": {
"en": "Use indicator",
"ru": "Использовать индикатор",
"ua": "Використовувати індикатор"
},
"UseCustomColor": {
"en": "Custom window color",
"ru": "Свой цвет окна",
"ua": "Свій колір вікна"
},
"PasswordBox": {
"CurrentCryptPassword": {
"en": "Current encryption password",
"ru": "Текущий пароль шифрования",
"ua": "Поточний пароль шифрування"
},
"Hint": {
"en": "Doesn't saved on disk",
"ru": "Не сохраняется в файл",
"ua": "Не зберігається у файлах"
}
},
"LegacyMafileMode": {
"en": "Legacy mafile mode",
"ru": "Режим старых мафайлов",
"ua": "Режим старих мафайлів"
},
"LegacyMafileModeHint": {
"en": "Use old mafile format for compatibility with old versions of Steam Desktop Authenticator",
"ru": "Использовать старый формат мафайлов для совместимости со старыми версиями Steam Desktop Authenticator",
"ua": "Використовувати старий формат мафайлів для сумісності зі старими версіями Steam Desktop Authenticator"
},
"AllowAutoUpdate": {
"en": "Allow auto update",
"ru": "Разрешить автообновление",
"ua": "Дозволити автооновлення"
}
},
"LoginAgainDialog": {
"Title": {
"en": "Login",
"ru": "Вход",
"ua": "Вхід"
},
"LoginFor": {
"en": "Login for",
"ru": "Войти в",
"ua": "Увійти в"
},
"LoginButton": {
"en": "Login",
"ru": "Войти",
"ua": "Увійти"
},
"CancelButton": {
"en": "Cancel",
"ru": "Отмена",
"ua": "Відміна"
},
"PasswordBox": {
"en": "Password",
"ru": "Пароль",
"ua": "Пароль"
},
"SaveEncryptedPassword": {
"en": "Save encrypted password to mafile",
"ru": "Сохранить зашифрованный пароль в мафайл",
"ua": "Зберегти зашифрований пароль в мафайлі"
}
},
"ProxyManagerDialog": {
"Title": {
"en": "Proxy manager",
"ru": "Менеджер прокси",
"ua": "Менеджер проксі"
},
"DefaultProxy": {
"en": "Default proxy:",
"ru": "Прокси по умолчанию:",
"ua": "Проксі за замовчуванням:"
}
},
"WaitLoginDialog": {
"Text": {
"en": "Login in progress. Please wait",
"ru": "Производится логин. Ожидайте",
"ua": "Виконується вхід. Зачекайте"
}
},
"LinkerDialog": {
"Title": {
"en": "Linker",
"ru": "Привязка",
"ua": "Прив'язка"
},
"Proxy": {
"en": "Proxy",
"ru": "Прокси",
"ua": "Проксі"
},
"Authorization": {
"en": "Authorization",
"ru": "Авторизация",
"ua": "Авторизація"
},
"EmailCode": {
"en": "Email code",
"ru": "Код из письма",
"ua": "Код з листа"
},
"PhoneNumber": {
"en": "Phone number",
"ru": "Номер телефона",
"ua": "Номер телефону"
},
"EmailLink": {
"en": "Email link",
"ru": "Ссылка из письма",
"ua": "Посилання з листа"
},
"SmsOrCode": {
"en": "Sms or code",
"ru": "Смс или код",
"ua": "Смс або код"
},
"Completed": {
"en": "Completed",
"ru": "Завершено",
"ua": "Завершено"
},
"Message": {
"en": "Message:",
"ru": "Сообщение:",
"ua": "Повідомлення:"
},
"ProceedButton": {
"en": "Proceed",
"ru": "Продолжить",
"ua": "Продовжити"
}
},
"SetEncryptedPasswordDialog": {
"DialogText": {
"en": "You have previously set an encryption password for passwords in mafiles, specify it so that the application can automatically log in in case of problems with the session. (Optional)",
"ru": "Ранее вы устанавливали пароль шифрования для паролей в мафайлах, укажите его, чтобы приложение могло автоматически авторизоваться в случае проблем с сессией. (Не обязательно)",
"ua": "Раніше ви встановлювали пароль шифрування для паролів в мафайлах, вкажіть його, щоб додаток міг автоматично авторизуватися у випадку проблем з сесією. (Не обов'язково)"
},
"Ok": {
"en": "Ok",
"ru": "Ок",
"ua": "Ок"
},
"Cancel": {
"en": "Cancel",
"ru": "Отмена",
"ua": "Відміна"
},
"Password": {
"en": "Password",
"ru": "Пароль",
"ua": "Пароль"
}
},
"CodeBehind": {
"MainVM": {
"SuccessfulLogin": {
"en": "Successful login",
"ru": "Успешный вход",
"ua": "Успішний вхід"
},
"SessionRefreshed": {
"en": "Session refreshed",
"ru": "Сессия обновлена",
"ua": "Сесія оновлена"
},
"MissingRCode": {
"en": "Missing RCode",
"ru": "Отсутствует RCode",
"ua": "Відсутній RCode"
},
"ConfirmRemovingAuthenticator": {
"ru": "Вы точно уверены что хотите удалить аутентификатор?",
"en": "Are you sure you want to remove authenticator?",
"ua": "Ви точно впевнені що хочете видалити аутентифікатор?"
},
"AuthenticatorRemoved": {
"ru": "Аутентификатор удален",
"en": "Authenticator removed",
"ua": "Аутентифікатор видалено"
},
"AuthenticatorNotRemoved": {
"ru": "Что-то пошло не так",
"en": "Something went wrong",
"ua": "Щось пішло не так"
},
"ConfirmLoginSuccess": {
"ru": "Вход осуществлен:",
"en": "Login success:",
"ua": "Вхід здійснено:"
},
"ConfirmLoginFailedNoRequests": {
"ru": "Запросов на вход не обнаружено",
"en": "No login requests found",
"ua": "Запитів на вхід не виявлено"
},
"ConfirmLoginFailedMoreThanOneRequest": {
"ru": "Обнаружено несколько запросов на вход. В целях безопасности операция отменена",
"en": "Multiple login requests found. For security reasons, the operation is canceled",
"ua": "Виявлено декілька запитів на вхід. З метою безпеки операція скасована"
},
"ConfirmationError": {
"ru": "Ошибка подтверждения",
"en": "Confirmation error",
"ua": "Помилка підтвердження"
},
"ConfirmMafileOverwrite": {
"ru": "Внимание, мафайл уже присутствует в папке mafiles.\r\nПерезаписать мафайлы, которые конфликтуют?",
"en": "Attention, mafile already exists in mafiles folder.\r\nOverwrite conflicting mafiles?",
"ua": "Увага, мафайл вже присутній у папці mafiles.\r\nПерезаписати мафайли, які конфліктують?"
},
"MafileImportError": {
"ru": "Ошибка импорта мафайла",
"en": "Mafile import error",
"ua": "Помилка імпорту мафайла"
},
"MissingSessionInMafile": {
"ru": ". У него отсутствует сессия. Добавьте этот файл отдельно от остальных",
"en": ". It has no session. Add this file separately from the others",
"ua": ". В нього відсутня сесія. Додайте цей файл окремо від інших"
},
"Import": {
"ru": "Импорт",
"en": "Import",
"ua": "Імпорт"
},
"ImportAdded": {
"ru": "добавлено:",
"en": "added:",
"ua": "додано:"
},
"ImportSkipped": {
"ru": "пропущено:",
"en": "skipped:",
"ua": "пропущено:"
},
"ImportErrors": {
"ru": "ошибки:",
"en": "errors:",
"ua": "помилки:"
},
"RemoveMafileConfirmation": {
"ru": "Удалить мафайл? Это действие переместит мафайл в mafiles_removed",
"en": "Remove mafile? This action will move mafile to mafiles_removed",
"ua": "Видалити мафайл? Ця дія перемістить мафайл в mafiles_removed"
},
"CantRemoveAlreadyExist": {
"ru": "Не удалось переместить мафайл, возможно в папке уже существует мафайл с таким именем.",
"en": "Can't move mafile, maybe mafile with same name already exists in mafiles_removed folder.",
"ua": "Не вдалося перемістити мафайл, можливо в папці вже існує мафайл з таким іменем."
},
"CantRemoveMafile": {
"ru": "Не удалось удалить (переместить) мафайл:",
"en": "Can't remove mafile:",
"ua": "Не вдалося видалити (перемістити) мафайл:"
},
"TimerConfirmed": {
"ru": "Авто: подтверждено ",
"en": "Auto: confirmed ",
"ua": "Авто: підтверджено "
},
"TimerNotConfirmed": {
"ru": "Авто: подтверждение не сработало",
"en": "Auto: confirmation was unsuccessful",
"ua": "Авто: підтвердження не спрацювало"
},
"TimerSessionError": {
"ru": "Авто: необходимо обновить сессию или перелогиниться",
"en": "Auto: need to refresh session or relogin",
"ua": "Авто: необхідно оновити сесію або перелогінитися"
},
"TimerPrefix": {
"ru": "Авто: ",
"en": "Auto: ",
"ua": "Авто: "
},
"TimerTooFast": {
"ru": "Слишком быстрый таймер.",
"en": "Too fast timer.",
"ua": "Занадто швидкий таймер."
}
},
"ErrorTranslator": {
"Login": {
"CaptchaRequired": {
"en": "Captcha",
"ru": "Капча",
"ua": "Капча"
},
"InvalidCredentials": {
"en": "Invalid password",
"ru": "Неверный пароль",
"ua": "Невірний пароль"
},
"EmailAuthRequired": {
"en": "Email code required",
"ru": "Требуется код с почты",
"ua": "Потрібен код з пошти"
},
"InvalidEmailAuthCode": {
"en": "Invalid email code",
"ru": "Неверный код с почты",
"ua": "Невірний код з пошти"
},
"InvalidTwoFactorCode": {
"en": "Invalid two factor code",
"ru": "Неверный двухфакторный код",
"ua": "Невірний двофакторний код"
},
"UndefinedError": {
"en": "Undefined error",
"ru": "Неизвестная ошибка",
"ua": "Невідома помилка"
}
},
"EResult": {
"InvalidPassword": {
"ru": "Неверный пароль",
"en": "Invalid password",
"ua": "Невірний пароль"
},
"InvalidState": {
"ru": "Неверное состояние (11)",
"en": "Invalid state (11)",
"ua": "Невірний стан (11)"
},
"Timeout": {
"ru": "Таймаут (16)",
"en": "Timeout (16)",
"ua": "Таймаут (16)"
},
"Banned": {
"ru": "Забанен (17)",
"en": "Banned (17)",
"ua": "Забанений (17)"
},
"AccountNotFound": {
"ru": "Аккаунт не найден (18)",
"en": "Account not found (18)",
"ua": "Аккаунт не знайдено (18)"
},
"InvalidSteamID": {
"ru": "Неправильный SteamID (19)",
"en": "Invalid SteamID (19)",
"ua": "Неправильний SteamID (19)"
},
"ServiceUnavailable": {
"ru": "Сервер не отвечает (20)",
"en": "Service unavailable (20)",
"ua": "Сервер не відповідає (20)"
},
"Pending": {
"ru": "Ожидание (21)",
"en": "Pending (21)",
"ua": "Очікування (21)"
},
"LimitExceeded": {
"ru": "Лимит превышен (25)",
"en": "Limit exceeded (25)",
"ua": "Ліміт перевищено (25)"
},
"Expired": {
"ru": "Истекло (27)",
"en": "Expired (27)",
"ua": "Термін дії закінчився (27)"
},
"Blocked": {
"ru": "Заблокировано (40)",
"en": "Blocked (40)",
"ua": "Заблоковано (40)"
},
"AccountDisabled": {
"ru": "Аккаунт отключен (43)",
"en": "Account disabled (43)",
"ua": "Аккаунт відключено (43)"
},
"Suspended": {
"ru": "Отключено (Suspended 51)",
"en": "Suspended (51)",
"ua": "Відключено (Suspended 51)"
},
"Cancelled": {
"ru": "Отмена (52)",
"en": "Cancelled (52)",
"ua": "Скасовано (52)"
},
"ExpiredLoginAuthCode": {
"ru": "Истекло время кода (71)",
"en": "Expired login auth code (71)",
"ua": "Термін дії коду закінчився (71)"
},
"AccountLockedDown": {
"ru": "Аккаунт заблокирован (КТ 73)",
"en": "Account locked down (73)",
"ua": "Аккаунт заблоковано (КТ 73)"
},
"UnexpectedError": {
"ru": "Неожиданная ошибка (79)",
"en": "Unexpected error (79)",
"ua": "Непередбачувана помилка (79)"
},
"Disabled": {
"ru": "Отключено (80)",
"en": "Disabled (80)",
"ua": "Відключено (80)"
},
"RegionLocked": {
"ru": "Регион заблокирован (83)",
"en": "Region locked (83)",
"ua": "Регіон заблоковано (83)"
},
"RateLimitExceeded": {
"ru": "Слишком много запросов (84)",
"en": "Rate limit exceeded (84)",
"ua": "Забагато запитів (84)"
},
"TwoFactorCodeMismatch": {
"ru": "2FA код не подошел (88)",
"en": "2FA code mismatch (88)",
"ua": "2FA код не підійшов (88)"
},
"TwoFactorActivationCodeMismatch": {
"ru": "2FA код не прошел проверку (89)",
"en": "2FA activation code mismatch (89)",
"ua": "2FA код не пройшов перевірку (89)"
},
"TimeNotSynced": {
"ru": "Время не синхронизировано (93)",
"en": "Time not synced (93)",
"ua": "Час не синхронізовано (93)"
},
"SMSCodeFailed": {
"ru": "Ошибка SMS-кода (94)",
"en": "SMS code failed (94)",
"ua": "Помилка SMS-коду (94)"
},
"AccountLimitExceeded": {
"ru": "Лимит аккаунта превышен (95)",
"en": "Account limit exceeded (95)",
"ua": "Ліміт аккаунта перевищено (95)"
},
"EmailSendFailure": {
"ru": "Не удалось отправить письмо (99)",
"en": "Email send failure (99)",
"ua": "Не вдалося відправити лист (99)"
},
"NeedCaptcha": {
"ru": "Капча (101)",
"en": "Need captcha (101)",
"ua": "Капча (101)"
},
"IPBanned": {
"ru": "IP заблокирован (105)",
"en": "IP banned (105)",
"ua": "IP заблоковано (105)"
},
"LimitedUserAccount": {
"ru": "Аккаунт с лимитом (112)",
"en": "Limited user account (112)",
"ua": "Аккаунт з лімітом (112)"
},
"AccountDeleted": {
"ru": "Аккаунт удален (114)",
"en": "Account deleted (114)",
"ua": "Аккаунт видалено (114)"
},
"PhoneNumberIsVOIP": {
"ru": "Номера VOIP запрещены (127)",
"en": "Phone number is VOIP (127)",
"ua": "Номера VOIP заборонені (127)"
}
},
"AuthenticatorLinkerError": {
"PhoneAlreadyAttached": {
"ru": "Телефон привязан, попробуйте через 20 минут",
"en": "Phone already attached, try again in 20 minutes",
"ua": "Телефон прив'язано, спробуйте через 20 хвилин"
},
"InvalidPhoneNumber": {
"ru": "Неверный номер телефона",
"en": "Invalid phone number",
"ua": "Невірний номер телефону"
},
"CantAttachPhone": {
"ru": "Не удается привязать телефон",
"en": "Can't attach phone",
"ua": "Не вдається прив'язати телефон"
},
"CantConfirmAttachingEmail": {
"ru": "Не удалось подтвердить письмо",
"en": "Can't confirm attaching email",
"ua": "Не вдалося підтвердити лист"
},
"CantSendSms": {
"ru": "Не удалось отправить SMS",
"en": "Can't send SMS",
"ua": "Не вдалося відправити SMS"
},
"AuthenticatorPresent": {
"ru": "Аутентификатор привязан",
"en": "Authenticator present",
"ua": "Аутентифікатор прив'язано"
},
"BadConfirmationCode": {
"ru": "Код не подошел",
"en": "Bad confirmation code",
"ua": "Код не підійшов"
},
"UnableToGenerateCorrectCodes": {
"ru": "Не удается сгенерировать коды",
"en": "Unable to generate correct codes",
"ua": "Не вдається згенерувати коди"
},
"InvalidStateWithStatus2": {
"ru": "Неверное состояние (InvalidState) со статусом 2. Попробуйте привязку с помощью СМС. Если СМС придет, но ошибка повторится. Нужно попробовать еще раз.",
"en": "Invalid state (InvalidState) with status 2. Try to attach with SMS. If SMS will come, but error will repeat. You need to try again.",
"ua": "Невірний стан (InvalidState) зі статусом 2. Спробуйте прив'язати з допомогою СМС. Якщо СМС прийде, але помилка повториться. Потрібно спробувати ще раз."
},
"GeneralFailure": {
"ru": "General Failure",
"en": "General Failure",
"ua": "General Failure"
}
}
},
"ExceptionHandler": {
"SessionExpiredException": {
"ru": "Сессия истекла. Попробуйте обновить ее через меню",
"en": "Session expired. Try to refresh it through menu",
"ua": "Сесія закінчилася. Спробуйте оновити її через меню"
},
"SessionInvalidException": {
"ru": "Сессия невалидна. Нужно залогиниться заново",
"en": "Session invalid. Need to login again",
"ua": "Сесія невалідна. Потрібно залогінитися знову"
},
"TaskCanceledException": {
"ru": "Произошла отмена операции",
"en": "Operation canceled",
"ua": "Операція скасована"
},
"TimeoutException": {
"ru": "Таймаут подключения. Проверьте ваше соединение с Proxy или интернетом",
"en": "Connection timeout. Check your connection with Proxy or internet",
"ua": "Таймаут підключення. Перевірте ваше з'єднання з Proxy або інтернетом"
},
"UnsupportedResponseException": {
"ru": "Получен неожиданный ответ от Steam. Результат сохранен в log.log. Свяжитесь с разработчиком",
"en": "Unexpected response from Steam. Result saved in log.log. Contact developer",
"ua": "Отримано неочікувану відповідь від Steam. Результат збережено в log.log. Зв'яжіться з розробником"
},
"LoginException": {
"ru": "Ошибка входа: ",
"en": "Login error: ",
"ua": "Помилка входу: "
},
"UnknownException": {
"ru": "Неизвестная ошибка: ",
"en": "Unknown error: ",
"ua": "Невідома помилка: "
}
},
"ProxyManagerVM": {
"WrongFormatSomeIdsMissing": {
"ru": "Неверный формат. Некоторые прокси не имеют ID",
"en": "Wrong format. Some proxies have no ID",
"ua": "Невірний формат. Деякі проксі не мають ID"
},
"DuplicateId": {
"ru": "Прокси с ID {0} добавлен несколько раз",
"en": "Proxy with ID {0} added multiple times",
"ua": "Проксі з ID {0} додано декілька разів"
},
"WrongFormatOnLine": {
"ru": "Прокси на строке {0} имеет неверный формат.",
"en": "Proxy on line {0} has wrong format.",
"ua": "Проксі на строці {0} має невірний формат."
},
"WrongFormat": {
"ru": "Прокси имеет неверный формат.",
"en": "Proxy has wrong format.",
"ua": "Проксі має невірний формат."
}
},
"LinkVM": {
"EnterLoginAndPassword": {
"ru": "Введите логин и пароль",
"en": "Enter login and password",
"ua": "Введіть логін і пароль"
},
"CantLogin": {
"ru": "Не удалось залогиниться: ",
"en": "Can't login: ",
"ua": "Не вдалося залогінитися: "
},
"MafileLinked": {
"ru": "Мафайл успешно привязан. RCode: {0}\r\nИмя файла: {1}.mafile",
"en": "Mafile successfully linked. RCode: {0}\r\nFile name: {1}.mafile",
"ua": "Мафайл успішно прив'язано. RCode: {0}\r\nІм'я файлу: {1}.mafile"
},
"ErrorWithCode": {
"ru": "Ошибка с кодом: ",
"en": "Error with code: ",
"ua": "Помилка з кодом: "
},
"UnknownError": {
"ru": "Неизвестная ошибка: ",
"en": "Unknown error: ",
"ua": "Невідома помилка: "
},
"PleaseEnterCorrectPhone": {
"ru": "Пожалуйста, введите корректный номер телефона",
"en": "Please enter correct phone number",
"ua": "Будь ласка, введіть коректний номер телефону"
},
"PleaseEnterCorrectCode": {
"ru": "Пожалуйста, введите корректный код",
"en": "Please enter correct code",
"ua": "Будь ласка, введіть коректний код"
},
"EnterEmailCode": {
"ru": "Введите код из письма",
"en": "Enter code from email",
"ua": "Введіть код з листа"
},
"ClickOnEmailLink": {
"ru": "На почту было отправлено письмо, нажмите по ссылке затем - 'Продолжить'",
"en": "Email was sent, click on link and then - 'Proceed'",
"ua": "На пошту було відправлено лист, натисніть по посиланню потім - 'Продовжити'"
},
"PhoneHint": {
"ru": "На телефон {0} была отправлена СМС",
"en": "SMS was sent to phone {0}",
"ua": "На телефон {0} було відправлено СМС"
},
"EnterPhoneNumber": {
"ru": "Введите номер телефона (без знака '+' и др. символов)",
"en": "Enter phone number (without '+' and other symbols)",
"ua": "Введіть номер телефону (без знака '+' та ін. символів)"
}
}
},
"CantAlignTimeError": {
"ru": "Не удается синхронизировать время со Steam. Возможно отсутствует подключение к интернету или Steam недоступен. Подробная ошибка сохранена в log.log",
"en": "Can't align time with Steam. Maybe no internet connection or Steam is unavailable. Detailed error saved in log.log",
"ua": "Не вдається синхронізувати час з Steam. Можливо відсутнє підключення до інтернету або Steam недоступний. Детальна помилка збережена в log.log"
}
}
@@ -0,0 +1,51 @@
using SteamLib.Core.Enums;
using SteamLib.Core.Interfaces;
using System.Diagnostics.CodeAnalysis;
using Newtonsoft.Json;
namespace SteamLib.Account;
//WARNING: Any changes here should be reflected in MafileSerializer.cs
public sealed class MobileSessionData : SessionData, IMobileSessionData
{
public SteamAuthToken? MobileToken { get; private set; }
public SteamAuthToken? GetMobileToken() => MobileToken;
[MemberNotNull(nameof(MobileToken))]
public void SetMobileToken(SteamAuthToken token)
{
if (token.Type != SteamAccessTokenType.Mobile)
throw new ArgumentException("Token must be of type MobileAccess", nameof(token))
{
Data = { { "ActualType", token.Type } }
};
MobileToken = token;
}
[JsonConstructor]
public MobileSessionData(string sessionId, SteamId steamId, SteamAuthToken refreshToken,
SteamAuthToken? mobileToken, IDictionary<SteamDomain, SteamAuthToken>? tokens)
: base(sessionId, steamId, refreshToken, tokens)
{
MobileToken = mobileToken;
}
public MobileSessionData(string sessionId, SteamId steamId, SteamAuthToken refreshToken,
SteamAuthToken? mobileToken, IEnumerable<SteamAuthToken>? tokens)
: base(sessionId, steamId, refreshToken, tokens)
{
MobileToken = mobileToken;
}
public override MobileSessionData Clone() => (MobileSessionData)((ISessionData)this).Clone();
object ICloneable.Clone()
{
return new MobileSessionData(SessionId, SteamId, RefreshToken, MobileToken, Tokens);
}
}
+62
View File
@@ -0,0 +1,62 @@
using Newtonsoft.Json;
using SteamLib.Core.Enums;
using SteamLib.Core.Interfaces;
using SteamLib.Web.Converters;
using System.Collections.Concurrent;
namespace SteamLib.Account;
public class SessionData : ISessionData
{
[JsonIgnore]
public bool? IsValid { get; set; }
public string SessionId { get; }
[JsonConverter(typeof(SteamIdToSteam64Converter))]
public SteamId SteamId { get; }
public SteamAuthToken RefreshToken { get; }
public ConcurrentDictionary<SteamDomain, SteamAuthToken> Tokens { get; }
[JsonConstructor]
public SessionData(string sessionId, SteamId steamId, SteamAuthToken refreshToken, IDictionary<SteamDomain, SteamAuthToken>? tokens)
{
SessionId = sessionId;
SteamId = steamId;
RefreshToken = refreshToken;
Tokens = new ConcurrentDictionary<SteamDomain, SteamAuthToken>(tokens ?? new Dictionary<SteamDomain, SteamAuthToken>());
}
public SessionData(string sessionId, SteamId steamId, SteamAuthToken refreshToken, IEnumerable<SteamAuthToken>? tokens)
{
SessionId = sessionId;
SteamId = steamId;
RefreshToken = refreshToken;
tokens ??= Array.Empty<SteamAuthToken>();
Tokens = new ConcurrentDictionary<SteamDomain, SteamAuthToken>(tokens.ToDictionary(t => t.Domain, t => t));
}
public virtual SteamAuthToken? GetToken(SteamDomain domain)
{
if (domain == SteamDomain.Undefined)
throw new ArgumentException("SteamDomain.Undefined is not allowed");
if (Tokens.TryGetValue(domain, out var token))
return token;
return null;
}
public void SetToken(SteamDomain domain, SteamAuthToken token)
{
Tokens[domain] = token;
}
public virtual SessionData Clone() => (SessionData)((ICloneable)this).Clone();
object ICloneable.Clone()
{
return new SessionData(SessionId, SteamId, RefreshToken, Tokens);
}
}
+49
View File
@@ -0,0 +1,49 @@
using AchiesUtilities.Models;
using AchiesUtilities.Newtonsoft.JSON.Converters.Special;
using Newtonsoft.Json;
using SteamLib.Authentication;
using SteamLib.Core.Enums;
using SteamLib.Web.Converters;
namespace SteamLib.Account;
public readonly struct SteamAuthToken
{
public string Token { get; }
[JsonConverter(typeof(SteamIdToSteam64Converter))]
public SteamId SteamId { get; }
[JsonConverter(typeof(UnixTimeStampConverter))]
public UnixTimeStamp Expires { get; }
public SteamDomain Domain { get; init; }
public SteamAccessTokenType Type { get; }
[JsonIgnore]
public bool IsExpired => Expires.Time < DateTime.UtcNow;
[JsonIgnore]
public string SignedToken { get; }
public SteamAuthToken(string token, long steamId, UnixTimeStamp expires, SteamDomain domain, SteamAccessTokenType type)
{
Token = token;
Expires = expires;
Domain = domain;
Type = type;
SteamId = SteamId.FromSteam64(steamId);
SignedToken = SteamTokenHelper.CombineJwtWithSteamId(SteamId.Steam64.Id, Token);
}
[JsonConstructor]
public SteamAuthToken(string token, SteamId steamId, UnixTimeStamp expires, SteamDomain domain, SteamAccessTokenType type)
{
Token = token;
SteamId = steamId;
Expires = expires;
Domain = domain;
Type = type;
SignedToken = SteamTokenHelper.CombineJwtWithSteamId(SteamId.Steam64.Id, Token);
}
}
+180
View File
@@ -0,0 +1,180 @@
using SteamLib.Utility;
namespace SteamLib.Account;
public struct SteamId //TODO: validation in parse methods
{
public SteamId64 Steam64 { get; }
public SteamId2 Steam2 { get; }
public SteamId3 Steam3 { get; }
public SteamId(SteamId64 steam64, char type = 'U', short universe = 0)
{
Steam64 = steam64;
Steam2 = steam64.ToSteam2(universe);
Steam3 = steam64.ToSteam3(type);
}
public SteamId(SteamId2 steam2, char type = 'U')
{
Steam2 = steam2;
Steam64 = steam2.ToSteam64();
Steam3 = steam2.ToSteam3(type);
}
public SteamId(SteamId3 steam3, byte universe = 0)
{
Steam3 = steam3;
Steam64 = steam3.ToSteam64();
Steam2 = steam3.ToSteam2(universe);
}
public static SteamId FromSteam64(long steam64, char type = 'U', short universe = 0) => new(new SteamId64(steam64), type, universe);
public override string ToString() => Steam64.ToString();
public static bool TryParse(string input, out SteamId result) => SteamIdParser.TryParse(input, out result);
/// <exception cref="FormatException"></exception>
public static SteamId Parse(string input) => SteamIdParser.Parse(input);
public static bool operator ==(SteamId left, SteamId right) => left.Equals(right);
public static bool operator !=(SteamId left, SteamId right) => !left.Equals(right);
public bool Equals(SteamId other) => Steam64.Equals(other.Steam64);
public override bool Equals(object? obj) => obj is SteamId other && Equals(other);
public override int GetHashCode() => Steam64.GetHashCode();
}
public struct SteamId64
{
public const long SEED = 76561197960265728L;
public long Id { get; }
public SteamId64(long id)
{
if (id < SEED)
throw new ArgumentOutOfRangeException(nameof(id),$"Invalid SteamID provided {id}");
Id = id;
}
public SteamId2 ToSteam2(short universe = 0)
{
var accountIdLowBit = (byte)(Id & 1);
var accountIdHighBits = (int)(Id >> 1) & 0x7FFFFFF;
return new SteamId2((byte)universe, accountIdLowBit, accountIdHighBits);
}
public SteamId3 ToSteam3(char type = 'U')
{
var accountIdLowBit = (byte)(Id & 1);
var accountIdHighBits = (int)(Id >> 1) & 0x7FFFFFF;
return new SteamId3(accountIdLowBit + accountIdHighBits * 2, type);
}
public override string ToString()
{
return Id.ToString();
}
public ulong ToUlong() => (ulong)Id;
public long ToLong() => Id;
public bool Equals(SteamId64 other) => Id == other.Id;
public override bool Equals(object? obj) => obj is SteamId64 other && Equals(other);
public override int GetHashCode() => Id.GetHashCode();
public static bool operator ==(SteamId64 left, SteamId64 right) => left.Equals(right);
public static bool operator !=(SteamId64 left, SteamId64 right) => !left.Equals(right);
public static bool TryParse(string input, out SteamId64 result) => SteamIdParser.TryParse64(input, out result);
/// <exception cref="FormatException"></exception>
public static SteamId64 Parse(string input) => SteamIdParser.Parse64(input);
public static implicit operator long(SteamId64 steamId64) => steamId64.ToLong();
}
public struct SteamId2
{
public byte Universe { get; }
public byte LowestBit { get; }
public int HighestBit { get; }
public SteamId2(byte lowestBit, int highestBit)
{
if(lowestBit > 1)
throw new ArgumentOutOfRangeException(nameof(lowestBit), $"Invalid SteamID2 lowestBit provided {lowestBit}. Max value is 1");
if(highestBit < 0)
throw new ArgumentOutOfRangeException(nameof(highestBit), $"Invalid SteamID2 highestBit provided {highestBit}");
LowestBit = lowestBit;
HighestBit = highestBit;
Universe = 0;
}
public SteamId2(byte universe, byte lowestBit, int highestBit)
{
Universe = universe;
LowestBit = lowestBit;
HighestBit = highestBit;
}
public SteamId64 ToSteam64() => new SteamId64(SteamId64.SEED + LowestBit + HighestBit * 2);
public SteamId3 ToSteam3(char type = 'U') => new SteamId3(HighestBit * 2 + LowestBit);
public override string ToString() => $"STEAM_{Universe}:{LowestBit}:{HighestBit}";
public bool Equals(SteamId2 other) => Universe == other.Universe && LowestBit == other.LowestBit && HighestBit == other.HighestBit;
public override bool Equals(object? obj) => obj is SteamId2 other && Equals(other);
public override int GetHashCode() => HashCode.Combine(Universe, LowestBit, HighestBit);
public static bool operator ==(SteamId2 left, SteamId2 right) => left.Equals(right);
public static bool operator !=(SteamId2 left, SteamId2 right) => !left.Equals(right);
public static bool TryParse(string input, out SteamId2 result) => SteamIdParser.TryParse2(input, out result);
/// <exception cref="FormatException"></exception>
public static SteamId2 Parse(string input) => SteamIdParser.Parse2(input);
}
public struct SteamId3
{
public char Type { get; }
public int Id { get; }
public SteamId3(int id, char type = 'U')
{
if (id < 0)
throw new ArgumentOutOfRangeException(nameof(id), $"Invalid SteamID provided {id}");
Type = type;
Id = id;
}
public SteamId2 ToSteam2(byte universe = 0)
{
var bit = Id % 2;
var highestBits = Id / 2;
return new SteamId2(universe, (byte)bit, highestBits);
}
public SteamId64 ToSteam64() => new SteamId64(SteamId64.SEED + Id);
public override string ToString() => $"[{Type}:1:{Id}]";
public string ToString(bool withBrackets)
{
if (withBrackets)
{
return ToString();
}
else
{
return $"{Type}:1:{Id}";
}
}
public int ToInt() => Id;
public bool Equals(SteamId3 other) => Type == other.Type && Id == other.Id;
public override bool Equals(object? obj) => obj is SteamId3 other && Equals(other);
public readonly override int GetHashCode() => HashCode.Combine(Type, Id);
public static bool operator ==(SteamId3 left, SteamId3 right) => left.Equals(right);
public static bool operator !=(SteamId3 left, SteamId3 right) => !left.Equals(right);
public static bool TryParse(string input, out SteamId3 result) => SteamIdParser.TryParse3(input, out result);
/// <exception cref="FormatException"></exception>
public static SteamId3 Parse(string input) => SteamIdParser.Parse3(input);
}
@@ -0,0 +1,305 @@
using JetBrains.Annotations;
using Newtonsoft.Json.Linq;
using SteamLib.Authentication;
using SteamLib.Core;
using SteamLib.Core.Enums;
using SteamLib.Core.Interfaces;
using SteamLib.Core.StatusCodes;
using SteamLib.Exceptions;
using SteamLib.Exceptions.Mobile;
using SteamLib.ProtoCore;
using SteamLib.ProtoCore.Enums;
using SteamLib.ProtoCore.Exceptions;
using SteamLib.ProtoCore.Services;
using SteamLib.SteamMobile;
using SteamLib.SteamMobile.AuthenticatorLinker;
namespace SteamLib.Api.Mobile;
[PublicAPI]
public static class SteamAuthenticatorLinkerApi
{
private const string PHONE_REQ = SteamConstants.STEAM_COMMUNITY + "steamguard/phoneajax";
#region Global
public static Task<AccountPhoneStatus_Response> HasPhone(HttpClient client, string accessToken)
{
const string uri = SteamConstants.STEAM_API + "IPhoneService/AccountPhoneStatus/v1";
var reqUri = AddAccessToken(uri, accessToken);
return client.PostProto<AccountPhoneStatus_Response>(reqUri, request: null);
}
/// <summary>
///
/// </summary>
/// <param name="client"></param>
/// <param name="data"></param>
/// <param name="deviceId"></param>
/// <returns></returns>
/// <exception cref="AuthenticatorLinkerException"></exception>
/// <exception cref="EResultException"></exception>
public static async Task<AddAuthenticator_Response> LinkRequest(HttpClient client, ISessionData data, string deviceId)
{
const string uri = SteamConstants.STEAM_API + "ITwoFactorService/AddAuthenticator/v1";
data.EnsureValidated();
var reqUri = AddAccessToken(uri, data.GetAccessToken());
var req = new AddAuthenticator_Request
{
SteamId = (ulong)data.SteamId.Steam64.Id,
AuthenticatorType = 1,
DeviceIdentifier = deviceId,
Version = 2
};
var resp = await client.PostProtoMsg<AddAuthenticator_Response>(reqUri, req);
if (resp is {Result: EResult.InvalidState, ResponseMsg.Status: 2})
{
throw new AuthenticatorLinkerException(AuthenticatorLinkerError.InvalidStateWithStatus2);
}
return resp.GetResponseEnsureSuccess();
}
public static async Task<LinkResult> FinalizeLink(HttpClient client, string confirmationCode, ISessionData data,
byte[] sharedSecret, bool validateSmsCode)
{
const string uri = SteamConstants.STEAM_API + "ITwoFactorService/FinalizeAddAuthenticator/v1";
data.EnsureValidated();
var reqUri = AddAccessToken(uri, data.GetAccessToken());
var time = await TimeAligner.GetSteamTimeAsync();
if (validateSmsCode)
{
var validateSmsReq = new FinalizeAddAuthenticator_Request
{
SteamId = data.SteamId.Steam64.ToUlong(),
AuthenticatorTime = (ulong) time,
ConfirmationCode = confirmationCode,
ValidateConfirmationCode = true
};
var validateResp = await client.PostProto<FinalizeAddAuthenticator_Response>(reqUri, validateSmsReq);
if (validateResp.Success == false || validateResp.Status != 2)
{
if (validateResp.Status == 89)
throw new AuthenticatorLinkerException(AuthenticatorLinkerError.BadConfirmationCode);
throw new AuthenticatorLinkerException("Can't accept sms code. Status: " + validateResp.Status);
}
}
var tries = 0;
while (tries < 30)
{
tries++;
time = await TimeAligner.GetSteamTimeAsync();
var code = SteamGuardCodeGenerator.GenerateCode(sharedSecret, time);
var req = new FinalizeAddAuthenticator_Request
{
SteamId = data.SteamId.Steam64.ToUlong(),
AuthenticatorCode = code,
AuthenticatorTime = (ulong)time,
ConfirmationCode = confirmationCode,
};
var resp = await client.PostProto<FinalizeAddAuthenticator_Response>(reqUri, req);
if (resp.Success && resp.WantMore == false)
{
return new LinkResult();
}
if (resp.Status == 89)
{
return new LinkResult(LinkError.BadConfirmationCode);
}
if (resp.Status == 88 && tries >= 30)
{
return new LinkResult(LinkError.UnableToGenerateCorrectCodes);
}
}
return new LinkResult(LinkError.GeneralFailure);
}
public static async Task<bool> IsValidPhoneNumber(HttpClient client, long phoneNumber, string sessionId)
{
return await CheckPhoneNumber(client, phoneNumber, sessionId) == CheckPhoneResult.Valid;
}
public static async Task<CheckPhoneResult> CheckPhoneNumber(HttpClient client, long phoneNumber, string sessionId)
{
var phone = '+' + phoneNumber.ToString();
var data = new Dictionary<string, string>
{
{"phoneNumber", phone},
{"sessionID", sessionId}
};
var reqContent = new FormUrlEncodedContent(data);
var req = new HttpRequestMessage(HttpMethod.Post, SteamConstants.STEAM_STORE + "phone/validate");
req.Content = reqContent;
req.Headers.Referrer = new Uri("https://store.steampowered.com/phone/add");
var resp = await client.SendAsync(req);
var content = await resp.EnsureSuccessStatusCode().Content.ReadAsStringAsync();
var j = JObject.Parse(content);
if (j["success"]!.Value<bool>() == false)
return CheckPhoneResult.GeneralFailure;
if (j["is_voip"]!.Value<bool>())
return CheckPhoneResult.VoIp;
return j["is_valid"]!.Value<bool>() ? CheckPhoneResult.Valid : CheckPhoneResult.NotValid;
}
public static async Task<bool> AttachPhone(HttpClient client, long phoneNumber, string accessToken)
{
const string uri = SteamConstants.STEAM_API + "IPhoneService/SetAccountPhoneNumber/v1";
var phone = '+' + phoneNumber.ToString();
var req = new SetAccountPhoneNumber_Request
{
PhoneNumber = phone
};
var reqUri = AddAccessToken(uri, accessToken);
var resp =
await client.PostProtoMsg<SetAccountPhoneNumber_Response>(reqUri, req);
return resp.Result == EResult.Pending;
}
public static async Task<bool> CheckEmailConfirmation(HttpClient client, string accessToken)
{
const string uri = SteamConstants.STEAM_API + "IPhoneService/IsAccountWaitingForEmailConfirmation/v1";
var reqUri = AddAccessToken(uri, accessToken);
var i = 0;
while (i < 5)
{
i++;
var resp = await client.PostProto<IsAccountWaitingForEmailConfirmation_Response>(reqUri, new EmptyMessage()); ;
if (resp.IsWaiting == false) return true;
await Task.Delay(resp.SecondsToWait * 1000);
}
return false;
}
public static Task<EResult> SendSmsCode(HttpClient client, string accessToken)
{
const string uri = SteamConstants.STEAM_API + "IPhoneService/SendPhoneVerificationCode/v1";
var reqUri = AddAccessToken(uri, accessToken);
return client.PostProto(reqUri, new SendPhoneVerificationCode_Request());
}
public static async Task<bool> CheckSmsCode(HttpClient client, int smsCode, string sessionId)
{
var data = new Dictionary<string, string>
{
{"op", "check_sms_code"},
{"arg", smsCode.ToString()},
{"checkfortos", "0"},
{"skipvoip", "1"},
{"sessionid", sessionId}
};
var content = new FormUrlEncodedContent(data);
var resp = await client.PostAsync(PHONE_REQ, content);
var respContent = await resp.EnsureSuccessStatusCode().Content.ReadAsStringAsync();
var success = SteamStatusCode.Translate<SteamStatusCode>(respContent, out _).Equals(SteamStatusCode.Ok);
if (success) return true;
await Task.Delay(5000);
var hasPhone = await HasPhone(client, sessionId);
return hasPhone.HasPhone;
}
private static FormUrlEncodedContent CreateAjaxContent(string op, string arg, string sessionId)
{
var data = new Dictionary<string, string>
{
{"op",op},
{"arg", arg},
{"sessionid", sessionId}
};
return new FormUrlEncodedContent(data);
}
public static string GenerateDeviceId() => "android:" + Guid.NewGuid();
private static string AddAccessToken(string uri, string accessToken) => uri + "?access_token=" + accessToken;
#endregion
#region ForLinker
internal static Task<AddAuthenticator_Response> LinkRequest(this SteamAuthenticatorLinker linker)
{
if (linker.SessionData == null)
throw new InvalidOperationException("SessionData is null");
return LinkRequest(linker.Client, linker.SessionData, linker.DeviceId ??= GenerateDeviceId());
}
internal static Task<bool> IsValidPhoneNumber(this SteamAuthenticatorLinker linker, long phoneNumber)
{
if (linker.SessionData == null)
throw new InvalidOperationException("SessionData is null");
return IsValidPhoneNumber(linker.Client, phoneNumber, linker.SessionData.SessionId);
}
internal static Task<AccountPhoneStatus_Response> HasPhone(this SteamAuthenticatorLinker linker)
{
if (linker.SessionData == null)
throw new InvalidOperationException("SessionData is null");
return HasPhone(linker.Client, linker.SessionData.GetAccessToken());
}
internal static Task<bool> AttachPhone(this SteamAuthenticatorLinker linker, long phoneNumber)
{
if (linker.SessionData == null)
throw new InvalidOperationException("SessionData is null");
return AttachPhone(linker.Client, phoneNumber, linker.SessionData.GetAccessToken());
}
internal static Task<bool> CheckEmailConfirmation(this SteamAuthenticatorLinker linker)
{
if (linker.SessionData == null)
throw new InvalidOperationException("SessionData is null");
return CheckEmailConfirmation(linker.Client, linker.SessionData.GetAccessToken());
}
internal static Task<EResult> SendSmsCode(this SteamAuthenticatorLinker linker)
{
if (linker.SessionData == null)
throw new InvalidOperationException("SessionData is null");
return SendSmsCode(linker.Client, linker.SessionData.GetAccessToken());
}
internal static Task<bool> CheckSmsCode(this SteamAuthenticatorLinker linker, int smsCode)
{
if (linker.SessionData == null)
throw new InvalidOperationException("SessionData is null");
return CheckSmsCode(linker.Client, smsCode, linker.SessionData.SessionId);
}
internal static Task<LinkResult> FinalizeLink(this SteamAuthenticatorLinker linker, string confirmationCode, byte[] sharedSecret, bool validateSmsCode)
{
if (linker.SessionData == null)
throw new InvalidOperationException("SessionData is null");
return FinalizeLink(linker.Client, confirmationCode, linker.SessionData, sharedSecret, validateSmsCode);
}
#endregion
private static string GetAccessToken(this ISessionData s) => s.GetToken(SteamDomain.Community)?.Token ?? throw new SessionInvalidException("Access token was null. MobileEndpoints requires valid AccessToken");
}
@@ -0,0 +1,89 @@
using System.Net;
using JetBrains.Annotations;
using Newtonsoft.Json.Linq;
using SteamLib.Core;
using SteamLib.Exceptions;
using SteamLib.ProtoCore;
using SteamLib.ProtoCore.Enums;
using SteamLib.ProtoCore.Exceptions;
using SteamLib.ProtoCore.Services;
namespace SteamLib.Api.Mobile;
[PublicAPI]
public static class SteamMobileApi
//TODO: cancellation token
//TODO: HealthMonitor
{
private const string GENERATE_ACCESS_TOKEN =
SteamConstants.STEAM_API + "IAuthenticationService/GenerateAccessTokenForApp/v1";
/// <summary>
///
/// </summary>
/// <param name="client"></param>
/// <param name="refreshToken"></param>
/// <param name="steamId"></param>
/// <returns>Refreshed AccessToken</returns>
/// <exception cref="SessionInvalidException"></exception>
public static async Task<string> RefreshJwt(HttpClient client, string refreshToken, long steamId)
{
var req = new GenerateAccessTokenForApp_Request
{
RefreshToken = refreshToken,
SteamId = steamId,
TokenRenewalType = true
};
try
{
var resp = await client.PostProto<GenerateAccessTokenForApp_Response>(GENERATE_ACCESS_TOKEN, req);
return resp.AccessToken;
}
catch (EResultException ex)
when (ex.Result == EResult.AccessDenied)
{
throw new SessionInvalidException("RefreshToken is not accepted by Steam. You must login again and use new token");
}
}
public static async Task<bool> HasPhoneAttached(HttpClient client, string sessionId)
{
var data = new Dictionary<string, string>
{
{"op", "has_phone"},
{"arg", "null"},
{"sessionid", sessionId}
};
var content = new FormUrlEncodedContent(data);
var resp = await client.PostAsync(SteamConstants.STEAM_COMMUNITY + "steamguard/phoneajax", content);
var respContent = await resp.EnsureSuccessStatusCode().Content.ReadAsStringAsync();
var json = JObject.Parse(respContent);
return json["has_phone"]!.Value<bool>();
}
public static async Task<RemoveAuthenticator_Response> RemoveAuthenticator(HttpClient client, string accessToken, string rCode)
{
var req = SteamConstants.STEAM_API + "ITwoFactorService/RemoveAuthenticator/v1?access_token=" + accessToken;
var reqData = new RemoveAuthenticator_Request
{
RevocationCode = rCode,
RevocationReason = 1,
SteamGuardScheme = 1
};
try
{
return await client.PostProto<RemoveAuthenticator_Response>(req, reqData);
}
catch (HttpRequestException ex)
when (ex.StatusCode is HttpStatusCode.Unauthorized)
{
throw new SessionExpiredException("Session expired");
}
}
}
@@ -0,0 +1,96 @@
using SteamLib.Core;
using SteamLib.Exceptions;
using SteamLib.SteamMobile.Confirmations;
using SteamLib.SteamMobile;
using System.Net;
using AchiesUtilities.Web.Extensions;
using SteamLib.Web.Scrappers.JSON;
using SteamLib.Core.StatusCodes;
using SteamLib.Utility;
namespace SteamLib.Api.Mobile;
public static class SteamMobileConfirmationsApi
{
private const string CONF = SteamConstants.STEAM_COMMUNITY + "mobileconf";
private const string CONF_OP = SteamConstants.STEAM_COMMUNITY + "mobileconf/ajaxop";
private const string MULTI_CONF_OP = SteamConstants.STEAM_COMMUNITY + "mobileconf/multiajaxop";
public static async Task<IEnumerable<Confirmation>> GetConfirmations(HttpClient client, MobileData data, long steamId)
{
var nvc = GetConfirmationKvp(steamId, data.DeviceId, data.IdentitySecret, "list");
var req = new Uri(CONF + "/getlist" + nvc.ToQueryString());
var reqMsg = new HttpRequestMessage(HttpMethod.Get, req);
var resp = await client.SendAsync(reqMsg);
var respStr = await resp.Content.ReadAsStringAsync();
if (resp.StatusCode == HttpStatusCode.Redirect)
{
throw new SessionExpiredException("Mobile session expired");
}
resp.EnsureSuccessStatusCode();
return HealthMonitor.LogOnException(respStr, MobileConfirmationScrapper.Scrap, typeof(SessionExpiredException));
}
public static async Task<bool> SendConfirmation(HttpClient client, Confirmation confirmation, long steamId, MobileData data, bool confirm)
{
var op = confirm ? "allow" : "cancel";
var query = GetConfirmationKvp(steamId, data.DeviceId, data.IdentitySecret, op).ToList();
var id = confirmation.Id.ToString();
var key = confirmation.Nonce.ToString();
query.Insert(0, new KeyValuePair<string, string>("op", op));
query.Add(new KeyValuePair<string, string>("cid", id));
query.Add(new KeyValuePair<string, string>("ck", key));
var req = CONF_OP + query.ToQueryString();
var resp = await client.GetStringAsync(req);
var successCode = HealthMonitor.LogOnException(resp, () => SteamStatusCode.Translate<SteamStatusCode>(resp, out _));
return successCode.Equals(SteamStatusCode.Ok);
}
public static async Task<bool> SendMultipleConfirmations(HttpClient client, IEnumerable<Confirmation> confirmations,
long steamId, MobileData data, bool confirm)
{
var op = confirm ? "allow" : "cancel";
var query = GetConfirmationKvp(steamId, data.DeviceId, data.IdentitySecret, op).ToList();
query.Insert(0, new KeyValuePair<string, string>("op", op));
foreach (var confirmation in confirmations)
{
query.Add(new KeyValuePair<string, string>("cid[]", confirmation.Id.ToString()));
query.Add(new KeyValuePair<string, string>("ck[]", confirmation.Nonce.ToString()));
}
var content = new FormUrlEncodedContent(query);
var resp = await client.PostAsync(MULTI_CONF_OP, content);
var respStr = await resp.Content.ReadAsStringAsync();
var successCode = HealthMonitor.LogOnException(respStr, () => SteamStatusCode.Translate<SteamStatusCode>(respStr, out _));
return successCode.Equals(SteamStatusCode.Ok);
}
internal static IEnumerable<KeyValuePair<string, string>> GetConfirmationKvp(long steamId, string deviceId, string identitySecret, string tag = "conf")
{
var time = TimeAligner.GetSteamTime();
var hash = EncryptionHelper.GenerateConfirmationHash(time, identitySecret, tag);
return new KeyValuePair<string, string>[]
{
new("p", deviceId),
new("a", steamId.ToString()),
new("k", hash),
new("t", time.ToString()),
new("m", "react"),
new("tag", tag)
};
}
}
+78
View File
@@ -0,0 +1,78 @@
using Newtonsoft.Json;
using SteamLib.Authentication;
using SteamLib.Core;
using SteamLib.Core.Enums;
using SteamLib.Core.StatusCodes;
using SteamLib.Exceptions;
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);
var jwtRefresh = JsonConvert.DeserializeObject<JwtRefreshJson>(respStr);
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);
if (updateResp != "{\"result\":1}")
{
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; }
}
}
@@ -0,0 +1,176 @@
using AchiesUtilities.Extensions;
using JetBrains.Annotations;
using SteamLib.Account;
using SteamLib.Core;
using SteamLib.Core.Enums;
using SteamLib.Core.Interfaces;
using System.Net;
namespace SteamLib.Authentication;
[PublicAPI]
public static class AdmissionHelper
{
public const string ACCESS_COOKIE_NAME = "steamLoginSecure";
public const string REFRESH_COOKIE_NAME = "steamRefresh_steam";
public const string LANGUAGE_COOKIE_NAME = "Steam_Language";
#region Main
/// <summary>
/// Clear and set new session
/// </summary>
public static void SetSteamCookies(this CookieContainer container, ISessionData sessionData, string setLanguage = "english")
{
container.ClearSteamCookies(setLanguage);
AddRefreshToken(container, sessionData.RefreshToken);
var community = SteamDomains.GetDomainUri(SteamDomain.Community);
container.Add(community, new Cookie("sessionid", sessionData.SessionId, "/"));
container.Add(community, new Cookie("Steam_Language", setLanguage, "/"));
TransferCommunityCookies(container);
foreach (var domain in SteamDomains.AllDomains)
{
var token = sessionData.GetToken(domain);
if (token == null) continue;
AddTokenCookie(container, token.Value);
}
}
public static void SetDomainCookie(this CookieContainer container, SteamDomain domain, SteamAuthToken token)
{
var uri = SteamDomains.GetDomainUri(domain);
foreach (var cookie in container.GetCookies(uri).Where(c => c.Expired == false && c.Name.EqualsIgnoreCase(ACCESS_COOKIE_NAME)))
{
cookie.Expired = true;
}
AddTokenCookie(container, token);
}
/// <summary>
/// Clear and set new session
/// </summary>
public static void SetSteamMobileCookies(this CookieContainer container, IMobileSessionData mobileSession,
string setLanguage = "english")
{
container.ClearSteamCookies(setLanguage);
container.AddMinimalMobileCookies();
AddRefreshToken(container, mobileSession.RefreshToken);
var community = SteamDomains.GetDomainUri(SteamDomain.Community);
container.Add(community, new Cookie("steamid", mobileSession.SteamId.Steam64.ToString()));
container.Add(community, new Cookie("sessionid", mobileSession.SessionId));
container.Add(community, new Cookie("Steam_Language", setLanguage));
TransferCommunityCookies(container);
foreach (var domain in SteamDomains.AllDomains)
{
var token = mobileSession.GetToken(domain);
if (token == null) continue;
AddTokenCookie(container, token.Value);
}
}
public static void AddMinimalMobileCookies(this CookieContainer container)
{
var community = SteamDomains.GetDomainUri(SteamDomain.Community);
container.Add(community, new Cookie("mobileClientVersion", "777777 3.6.1"));
container.Add(community, new Cookie("mobileClient", "android"));
}
#endregion
#region Clear
public static void ClearSteamCookies(this CookieContainer container, string setLanguage = "english")
{
var cookies = container.GetAllCookies().Where(IsSteamCookie).ToList();
foreach (var cookie in cookies)
{
cookie.Expired = true;
}
container.Add(SteamDomains.GetDomainUri(SteamDomain.Community), new Cookie(LANGUAGE_COOKIE_NAME, setLanguage));
TransferCommunityCookies(container);
}
public static void ClearMobileSessionCookies(this CookieContainer container, string setLanguage = "english")
{
container.ClearSteamCookies(setLanguage);
container.AddMinimalMobileCookies();
TransferCommunityCookies(container);
}
#endregion
#region Helpers
public static void TransferCommunityCookies(CookieContainer container)
{
var cookies = container.GetAllCookies();
foreach (Cookie cookie in cookies)
{
if (cookie.Domain.Contains("steamcommunity.com") == false || cookie.Expired || cookie.Name.EqualsIgnoreCase(ACCESS_COOKIE_NAME)) continue;
container.Add(SteamDomains.GetDomainUri(SteamDomain.Store), new Cookie(cookie.Name, cookie.Value, cookie.Path) { Expires = cookie.Expires, Secure = cookie.Secure, HttpOnly = cookie.HttpOnly });
container.Add(SteamDomains.GetDomainUri(SteamDomain.Help), new Cookie(cookie.Name, cookie.Value, cookie.Path) { Expires = cookie.Expires, Secure = cookie.Secure, HttpOnly = cookie.HttpOnly });
container.Add(SteamDomains.GetDomainUri(SteamDomain.TV), new Cookie(cookie.Name, cookie.Value, cookie.Path) { Expires = cookie.Expires, Secure = cookie.Secure, HttpOnly = cookie.HttpOnly });
}
}
public static void AddRefreshToken(CookieContainer container, SteamAuthToken token)
{
if (token.Type is not (SteamAccessTokenType.Refresh or SteamAccessTokenType.MobileRefresh))
throw new ArgumentException($"Token must be of type Refresh or MobileRefresh. Provided token has type: {token.Type}", nameof(token));
var refreshToken = token.SignedToken;
container.Add(SteamDomains.LoginSteamDomain, new Cookie(REFRESH_COOKIE_NAME, refreshToken)
{
Expires = token.Expires.ToLocalDateTime()
});
}
public static void AddTokenCookie(CookieContainer container, SteamAuthToken token)
{
if (token.Type is not SteamAccessTokenType.AccessToken)
throw new ArgumentException($"Token must be of type AccessToken. Provided token has type: {token.Type}", nameof(token));
AddTokenCookie(container, token.Domain, token);
}
private static void AddTokenCookie(CookieContainer container, SteamDomain domain, SteamAuthToken token)
{
var domainUri = SteamDomains.GetDomainUri(token.Domain);
container.Add(domainUri, new Cookie(ACCESS_COOKIE_NAME, token.SignedToken)
{
HttpOnly = true,
Secure = true,
Expires = token.Expires.ToLocalDateTime()
});
}
public static bool IsSteamCookie(Cookie cookie) =>
cookie.Domain.Contains("steamcommunity.com") || cookie.Domain.Contains("steampowered.com") || cookie.Domain.Contains("steam.tv");
public static string? GetSessionId(this CookieContainer container, string domain = "steamcommunity.com")
{
var cookies = container.GetAllCookies();
return cookies
.FirstOrDefault(c => c.Name.Equals("sessionid", StringComparison.InvariantCultureIgnoreCase)
&& c.Expired == false
&& c.Domain.Contains(domain, StringComparison.InvariantCultureIgnoreCase))?
.Value;
}
#endregion
}
@@ -0,0 +1,108 @@
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;
}
}
@@ -0,0 +1,21 @@
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;
}
}
@@ -0,0 +1,18 @@
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; }
}

Some files were not shown because too many files have changed in this diff Show More